/* ============================================================
   CASCADIA — shared Northwest parallax scene
   One implementation, used by the homepage hero, the /trades hub
   and every per-trade page. Add .compact for the shorter header
   variant. If this file changes, every page changes with it — that's
   the point; three copies of the geometry would drift within a week.

   Geometry rule (do not break this):
   every terrain box is EXACTLY the plate's aspect ratio
   (3168:1344 = 2.357, so height = 100/2.357 = 42.43vw at full width),
   so nothing is ever cropped and no hard straight edge can appear.
   Each plate is solid to its own bottom edge, and the offsets satisfy
       bottom_front >= bottom_back + P * (crest_front - solid_back)
   measured from the alpha channels:
       peak .348/.796  midridge .216/.458
       nearridge .183/.341  trees .152/.623
   That inequality is what keeps the summit seated instead of floating.
   ============================================================ */

.scene{position:absolute;inset:-2px;z-index:0;overflow:hidden;pointer-events:none;
  /* sampled straight off the sky photograph, so any exposed pixel still reads
     as sky. The gradient IS the sky — the photo can't be relied on here
     because its bottom edge would expose on scroll. */
  background:linear-gradient(180deg,#2D8CCE 0%,#53A2DB 25%,#A6C5E4 50%,#CED3D8 72%,#E7E1D4 88%,#EFE0CD 100%)}
.scene>*{position:absolute;left:0;width:100%;will-change:transform}
/* ...except the two the parallax never moves. skytex and scrim carry
   data-depth="0", and the loop skips a zero depth (`if(d)`), so their
   transform is set once and never changes — but the blanket rule above
   still promoted each to its own full-screen compositor layer on every
   device. Two textures the size of the viewport, allocated to hold
   something that does not move. */
.scene>.skytex,.scene>.scrim{will-change:auto}

/* the photograph rides on top at low opacity for cirrus texture only, and
   does not parallax — an infinitely distant sky shouldn't shift */
.skytex{top:0;height:100%;background:url('/static/a9-sky.73f689e629.webp') center center/cover no-repeat;opacity:.42;
  animation:skyBreathe 40s ease-in-out infinite alternate}
@keyframes skyBreathe{from{opacity:.36}to{opacity:.48}}

/* Drifting cloud bands. One element each, repeat-x, tile width in vw, and the
   animation advances by exactly one tile width — so the loop is mathematically
   seamless at any viewport size with no inner track to fight the parallax
   transform. Tiles are large and mismatched so the repeat never reads as
   wallpaper. */
.cloud{background-image:url('/static/a9-v2-clouds.784c9cc8bd.webp');background-repeat:repeat-x;background-position:0 0}
.cloud.f1{top:-1.5vw;height:22.91vw;background-size:54vw auto;opacity:.32;animation:driftA 230s linear infinite}
.cloud.f2{top:7vw;   height:18.67vw;background-size:44vw auto;opacity:.26;animation:driftB 360s linear infinite}
.cloud.n1{top:-7vw;  height:32.24vw;background-size:76vw auto;opacity:.48;animation:driftC 170s linear infinite}
@keyframes driftA{to{background-position:-54vw 0}}
@keyframes driftB{to{background-position:44vw 0}}
@keyframes driftC{to{background-position:-76vw 0}}

/* terrain planes, far to near */
.peak     {bottom:19.5vw; height:42.43vw;background:url('/static/a9-v2-peak.c8b4bfd757.webp')      center bottom/cover no-repeat}
.midridge {bottom:-3.3vw; height:42.43vw;background:url('/static/a9-v2-midridge.9ea3430824.webp')  center bottom/cover no-repeat}
.nearridge{bottom:-12.8vw;height:42.43vw;background:url('/static/a9-v2-nearridge.229e75cdaf.webp') center bottom/cover no-repeat}
.trees    {bottom:-19vw;  height:42.43vw;background:url('/static/a9-v2-trees.dc5e0ffb1b.webp')     center bottom/cover no-repeat}
.haze{bottom:14vw;height:14vw;background:linear-gradient(180deg,rgba(255,255,255,0),rgba(226,238,247,.34) 50%,rgba(255,255,255,0));filter:blur(12px)}

/* Legibility, spent sparingly. One tight warm radial behind the copy column
   only — a full-frame wash turns the whole photograph milky. */
.scrim{inset:0;height:100%;
  background:
    radial-gradient(62% 64% at 2% 50%,rgba(253,250,245,.96) 0%,rgba(253,250,245,.82) 32%,rgba(253,250,245,.34) 55%,rgba(253,250,245,0) 74%),
    linear-gradient(180deg,rgba(253,250,245,0) 84%,var(--bg) 100%)}
/* The hub's left column is much taller than the homepage's (headline + sub +
   pills + CTAs + proof row), so a radial has faded out by the time it reaches
   the proof row and that row lands on the dark treeline. A directional wash
   covers the whole column height evenly and still leaves the right half of
   the photograph completely open for the device frame. */
.head.full .scrim,
#hero .scrim{
  background:
    linear-gradient(100deg,rgba(253,250,245,.95) 0%,rgba(253,250,245,.88) 24%,rgba(253,250,245,.55) 42%,rgba(253,250,245,.12) 56%,rgba(253,250,245,0) 66%),
    linear-gradient(180deg,rgba(253,250,245,0) 84%,var(--bg) 100%)}

/* centred variant, for headers whose copy is centred rather than left-aligned */
.scene.centred .scrim{
  background:
    radial-gradient(78% 76% at 50% 42%,rgba(253,250,245,.94) 0%,rgba(253,250,245,.72) 34%,rgba(253,250,245,.26) 58%,rgba(253,250,245,0) 76%),
    linear-gradient(180deg,rgba(253,250,245,0) 84%,var(--bg) 100%)}

/* ---- COMPACT VARIANT -------------------------------------------------
   Used on the hub and trade pages. The scene box is short, so the terrain
   is scaled up and re-seated: the same inequality re-solved at 150% plate
   width (P = 63.64vw), taking each pair at its minimum plus a 3vw margin,
   which is the widest the planes can spread before a gap would open. */
/* A short header only shows the top slice of the sky, so the full ramp gets
   compressed and the top corners go abruptly saturated. Compact gets its own
   lighter ramp — same colours, entered further down. */
.scene.compact{background:linear-gradient(180deg,#5AA8DF 0%,#82BCE5 26%,#A6C5E4 50%,#CED3D8 74%,#E7E1D4 90%,#EFE0CD 100%)}
.scene.compact>.peak,.scene.compact>.midridge,
.scene.compact>.nearridge,.scene.compact>.trees{width:150%;left:-25%}
/* First solve pushed the summit above the frame, so a compact header showed
   only the mountain's lower flanks — a washed mass with no peak. Re-seated so
   the summit clears the top of the copy. Margins on the overlap inequality
   here are 5.5-10.2vw, comfortably wider than the desktop solve. */
.scene.compact .peak     {bottom:-6.1vw; height:63.64vw}
.scene.compact .midridge {bottom:-36.7vw;height:63.64vw}
.scene.compact .nearridge{bottom:-44vw;  height:63.64vw}
.scene.compact .trees    {bottom:-50.5vw;height:63.64vw}
.scene.compact .haze{bottom:-30vw;height:20vw}
.scene.compact .cloud.f1{top:-2vw; height:29.7vw;background-size:70vw auto}
.scene.compact .cloud.f2{top:6vw;  height:24.2vw;background-size:57vw auto}
.scene.compact .cloud.n1{top:-9vw; height:41.8vw;background-size:98.5vw auto}
.scene.compact .cloud.f1{animation-name:cDriftA}
.scene.compact .cloud.f2{animation-name:cDriftB}
.scene.compact .cloud.n1{animation-name:cDriftC}
@keyframes cDriftA{to{background-position:-70vw 0}}
@keyframes cDriftB{to{background-position:57vw 0}}
@keyframes cDriftC{to{background-position:-98.5vw 0}}

/* ---- narrow viewports ------------------------------------------------
   On mobile the hero is far taller than the viewport because columns stack,
   so terrain anchored to the hero's bottom falls below the fold. Cap the
   scene at one screen so the treeline lands inside the first view. */
@media(max-width:960px){
  .scene:not(.compact){height:100svh;bottom:auto;top:0}
  .scrim,.scene.centred .scrim{background:
    linear-gradient(180deg,rgba(253,250,245,.92) 0%,rgba(253,250,245,.68) 30%,rgba(253,250,245,.18) 54%,rgba(253,250,245,0) 70%),
    linear-gradient(180deg,rgba(253,250,245,0) 84%,var(--bg) 100%)}
  .scene>.peak,.scene>.midridge,.scene>.nearridge,.scene>.trees{width:150%;left:-25%}
  .scene .peak     {bottom:60vw;  height:63.64vw}
  .scene .midridge {bottom:20.1vw;height:63.64vw}
  .scene .nearridge{bottom:-0.4vw;height:63.64vw}
  .scene .trees    {bottom:-15.4vw;height:63.64vw}
  .scene .haze{bottom:26vw;height:22vw}
  /* The drift stops here, and the reason is compositing rather than taste.
     `background-position` is not a property the GPU can composite, so each
     of these keyframes forces a full repaint of a full-screen layer every
     frame — three of them at once, permanently, on layers that will-change
     has already promoted to their own textures. Desktop absorbs it. A phone
     in an in-app WebView has a far smaller GPU memory budget, and once the
     compositor starts evicting and re-uploading those textures it reads as
     the sky flashing, which is what a visitor arriving from Facebook saw on
     2026-08-03.

     Keeping the motion would mean driving it with `transform` instead — but
     the parallax loop already owns `transform` on these exact elements, so
     that needs an inner track element and a markup change across eight
     pages. Worth doing on its own; not worth bundling into a live layout
     fix. The clouds hold still on a phone until then, which costs a detail
     nobody looks for on a 390px screen.

     The mDriftA/B/C keyframes are gone with them — they existed only for
     these three rules. Desktop drift (driftA/B/C) and the compact header's
     (cDriftA/B/C) are untouched. */
  .scene .cloud.f1{top:-3vw; height:44.13vw;background-size:104vw auto;animation:none}
  .scene .cloud.f2{top:13vw; height:35.64vw;background-size:84vw auto;animation:none}
  .scene .cloud.n1{top:-13vw;height:61.13vw;background-size:144vw auto;animation:none}
  /* compact headers are short enough on mobile to keep their own seating */
  .scene.compact{height:100%;top:0;bottom:-2px}
  .scene.compact .peak     {bottom:44vw}
  .scene.compact .midridge {bottom:4vw}
  .scene.compact .nearridge{bottom:-16vw}
  .scene.compact .trees    {bottom:-31vw}
  /* The compact headers stop drifting too — same repaint cost, same reason
     as the full scene above. This needs its own rule at .scene.compact
     .cloud.fN specificity: the compact variant sets animation-name at
     (0,4,0), which outranks the (0,3,0) rule above no matter what order
     the file is in, so the trade pages were still animating after the
     full-scene fix. Measured on the deployed preview, not assumed —
     /trades/roofing reported cDriftA while / reported none. */
  .scene.compact .cloud.f1,
  .scene.compact .cloud.f2,
  .scene.compact .cloud.n1{animation:none}
}

@media(prefers-reduced-motion:reduce){
  .cloud,.skytex{animation:none!important}
}


/* ---- adapter: atlas9-site homepage ----------------------------------
   The site's hero is #hero and its own rule `#hero > div:not(.sfx)` would
   otherwise lift the scene to z-index 2 and paint it over the copy. The scene
   must sit behind, and the hero's own background gradient stays as a fallback
   for the moment before the plates decode. */
#hero > div.scene{z-index:0}
#hero{isolation:isolate}
/* the copy sits over a photograph, so each text element carries its own halo —
   cheaper than darkening the whole scene to make type work */
#hero .hero-sub{color:#48525F;text-shadow:0 1px 14px rgba(253,250,245,.95),0 1px 3px rgba(253,250,245,.9)}
#hero h1{text-shadow:0 2px 20px rgba(253,250,245,.7)}
#hero .wall-cap{text-shadow:0 1px 18px rgba(253,250,245,.98),0 1px 4px rgba(253,250,245,.95),0 0 30px rgba(253,250,245,.8)}
#hero .logo,nav .logo{filter:drop-shadow(0 1px 12px rgba(253,250,245,.95)) drop-shadow(0 1px 3px rgba(253,250,245,.9));transition:filter .35s}
nav.scrolled .logo{filter:none}


/* ---- homepage trade picker (#trades) --------------------------------
   A taste of the /trades switcher, not the whole thing. Its only job is to
   make a roofer realise there's a page about roofers, and click. */
#trades{--accent:#FF5A36}
.tradepick{position:relative}
.tp-frame{border-radius:16px;overflow:hidden;background:#fff;border:1px solid var(--line);box-shadow:var(--shadow)}
.tp-bar{height:32px;background:linear-gradient(#fff,#F6F1EA);border-bottom:1px solid var(--line);display:flex;align-items:center;gap:6px;padding:0 12px}
.tp-bar i{width:8px;height:8px;border-radius:50%;background:rgba(27,36,48,.16);display:block}
.tp-bar u{flex:1;height:15px;border-radius:8px;background:rgba(27,36,48,.06);margin-left:9px;text-decoration:none;
  display:flex;align-items:center;padding:0 9px;font-size:.56rem;color:var(--mut);font-family:ui-monospace,monospace}
.tp-shots{position:relative;aspect-ratio:16/10.6;overflow:hidden;background:#fff}
.tp-shots img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;object-position:top center;
  opacity:0;transform:scale(1.04);transition:opacity .6s cubic-bezier(.2,.7,.2,1),transform 1s cubic-bezier(.2,.7,.2,1)}
.tp-shots img.on{opacity:1;transform:scale(1)}
.tp-pills{display:flex;gap:9px;flex-wrap:wrap;margin:22px 0 26px}
/* a pill must never change size when it becomes active — if it does, the row
   reflows and the pills slide out from under the cursor */
.tp-pills a{position:relative;padding:10px 19px 10px 26px;border-radius:100px;font-size:.86rem;font-weight:600;
  background:#fff;border:1.5px solid var(--line);color:var(--ink);
  transition:transform .3s cubic-bezier(.2,.7,.2,1),background .3s,color .3s,border-color .3s,box-shadow .3s}
.tp-pills a::before{content:'';position:absolute;left:11px;top:50%;width:6px;height:6px;margin-top:-3px;border-radius:50%;
  background:var(--accent);opacity:0;transition:opacity .3s,background .5s}
.tp-pills a:hover{transform:translateY(-2px);border-color:rgba(27,36,48,.28)}
.tp-pills a.on{background:var(--ink);color:#fff;border-color:var(--ink);transform:translateY(-2px);box-shadow:0 12px 26px -14px rgba(27,36,48,.6)}
.tp-pills a.on::before{opacity:1}
/* The "any trade" pill. Dashed and quieter than the six so it reads as an
   invitation rather than a seventh trade — the row is a set of examples, not
   the menu, and a carpet installer who reads it as the menu leaves. */
.tp-pills a.more{padding-left:19px;border-style:dashed;border-color:rgba(27,36,48,.3);
  background:rgba(255,255,255,.55);color:var(--mut)}
.tp-pills a.more::before{display:none}
.tp-pills a.more:hover{background:#fff;color:var(--ink);border-color:var(--accent)}

/* ---- mobile: one floating action, not two -------------------------------
   The chat launcher and the call bubble both want the bottom-right thumb
   zone. On a phone a contractor wants to dial, not type, so below 700px the
   launcher and its nudge step aside and the call bubble takes the corner.

   The widget is NOT removed — only its launcher is hidden. #a9cw stays in the
   DOM so the panel can still be opened by the "Ask Atlas" buttons further down
   the page, and the panel carries its own close button, so there is always a
   way back out without the launcher.

   The widget injects its <style> into <head> at runtime, i.e. after this file,
   so an equally specific rule here would lose the cascade. `html body` buys
   the specificity to win without reaching for !important.

   z-index is one above the widget's 2147483000 so the call bubble can never
   end up behind an open panel. */
.callfab{display:none}
@media(max-width:700px){
  html body #a9cw .a9-launcher,
  html body #a9cw .a9-nudge{display:none}
  /* Icon only. This was a 165px pill carrying the number — 42% of a 390px
     screen, parked on top of whatever was behind it, which in practice was the
     hero's second CTA ("How it works", "See the example"). The number is
     already on the page three times: the hero button, the CTA line and the
     footer. Down here it only has to be a phone. */
  .callfab{position:fixed;right:16px;bottom:16px;z-index:2147483001;
    width:54px;height:54px;border-radius:50%;padding:0;
    display:inline-flex;align-items:center;justify-content:center;
    background:var(--accent,#FF5A36);color:#fff;text-decoration:none;
    box-shadow:0 10px 24px -8px rgba(27,36,48,.45),0 2px 6px rgba(27,36,48,.2);
    transition:opacity .25s cubic-bezier(.2,.7,.2,1),transform .25s cubic-bezier(.2,.7,.2,1)}
  .callfab svg{flex:none}
  .callfab .fab-num{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
  .callfab:active{transform:scale(.94)}
  /* The phone is always on. It used to hide over the hero, which was right when
     it was a 165px pill sitting on top of the hero's CTA row — but as a 54px
     circle in the corner it covers nothing worth protecting, and hiding the
     money button for the first screen and a half of a page costs more than the
     small redundancy with the hero's own call button. */

  /* Back to top. These pages run long on a phone and there was no way back up
     short of swiping the whole way. Same 54px circle as the phone so the two
     read as a pair, but deliberately quieter — smoked glass rather than coral,
     because getting back up the page is a convenience and calling is the
     point. Left side so it never contends for the thumb that dials. */
  .totop{position:fixed;left:16px;bottom:16px;z-index:2147483001;
    width:54px;height:54px;border-radius:50%;
    display:inline-flex;align-items:center;justify-content:center;
    background:rgba(27,36,48,.34);color:#fff;
    -webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);
    border:1px solid rgba(255,255,255,.3);
    box-shadow:0 8px 20px -8px rgba(27,36,48,.35);
    transition:opacity .25s cubic-bezier(.2,.7,.2,1),transform .25s cubic-bezier(.2,.7,.2,1)}
  .totop svg{flex:none}
  .totop:active{transform:scale(.94)}
  /* Hidden until you are actually down the page — there is nothing to go back
     to at the top. Driven by scroll distance, NOT by the hero: the hero is
     1313px on the homepage and 744px on a trade page, so keying off it meant
     the control appeared a screen and a half down on one page and almost
     immediately on another. Scroll distance behaves the same everywhere. */
  .totop.tucked{opacity:0;transform:scale(.7) translateY(10px);pointer-events:none}
}
/* Hidden outside the mobile range entirely — desktop has the chat launcher in
   one corner already, and a scrollbar in the other. */
.totop{display:none}
@media(max-width:700px){.totop{display:inline-flex}}
@media(max-width:700px) and (prefers-reduced-motion:reduce){
  .callfab{transition:none}
}
/* Hidden until the widget is confirmed present — a button that silently does
   nothing because the chatbot script failed is worse than no button. */
.askatlas{display:none;text-align:center;margin-top:30px}
.askatlas.ready{display:block}
#trades .kicker{color:var(--accent);transition:color .5s}
#trades .h2 mark{color:var(--accent);transition:color .5s}
@media(max-width:960px){.tp-pills{justify-content:center}.tradepick{max-width:560px;margin:0 auto 8px}}


/* ---- floating chips ----------------------------------------------------
   The site scopes these to `.hero-product .float-chip`, but the hero markup
   uses `.hero-showcase` and the new trades picker is elsewhere entirely — so
   the chips were rendering as bare text in both places. Give .float-chip its
   own base styling and let the existing chip1/chip2 rules do the positioning. */
.float-chip{
  background:#fff;border-radius:14px;box-shadow:var(--shadow);
  padding:12px 18px;font-size:.82rem;font-weight:600;
  display:inline-flex;align-items:center;gap:9px;white-space:nowrap;
}
.hero-showcase{position:relative}
.hero-showcase .float-chip{position:absolute;z-index:3}

/* chip2 sat at the wall's bottom-right, which is exactly where the fixed Atlas
   chat bubble lives — lift it clear rather than fighting for the corner */
.hero-showcase .chip2{bottom:86px;right:-10px}
@media(max-width:960px){.hero-showcase .chip2{bottom:64px;right:0}}
