/* =========================================================
   7 STAR WEB STUDIO - prototype styles (wd-s2)
   Dark theme. Brand red #CC0000. Glass + strong shadow.
   ASCII only.
   ========================================================= */

:root{
  --bg:#0a0a0a;
  --bg-2:#111112;
  --red:#CC0000;
  --white:#ffffff;
  --gray:#a8a8a8;
  --gray-dim:#6c6c6c;

  --glass-bg:rgba(255,255,255,0.04);
  /* wd-s10: border .10 -> .14 and the top highlight .08 -> .20. Both are what "more defined"
     actually means on a frosted panel - the edge is the only thing separating it from the
     dark behind it. */
  --glass-brd:rgba(255,255,255,0.14);
  --glass-hi:rgba(255,255,255,0.20);
  --glass-lo:rgba(0,0,0,0.32);

  --shadow-lg:0 24px 70px rgba(0,0,0,0.65);
  --shadow-md:0 8px 32px rgba(0,0,0,0.45);
}

*{box-sizing:border-box;margin:0;padding:0;}

/* >> DELIBERATELY NOT HERE: `html{scroll-behavior:smooth}` (wd-s18). The GCH example site
   carries that line and this one must NOT, and the difference is the pinned hero. GCH runs
   no GSAP at all; this page pins its hero with ScrollTrigger (`pin:true, scrub:.7`), and
   ScrollTrigger writes the scroll position itself on every refresh and pin release. With a
   global smooth mode those writes become animations and the pin fights them.
   The travel is done per click in `assets/js/nav-scroll.js` instead - one explicit
   `scrollTo({behavior:"smooth"})`, which leaves ScrollTrigger's own scrolling alone.
   >> THIS IS THE "SAME NUMBER, DIFFERENT CONDITIONS" TRAP from the shared standard: the
   donor's declaration is right for the donor and wrong here. Do not add it back. */

/* >> STOPS iPHONE SAFARI RESIZING TEXT ON ITS OWN (wd-s30, owner - iPhone only). Without it,
   iOS will inflate text in some layouts, notably after the phone is turned sideways and back,
   so type renders LARGER than the stylesheet asks for. A heading measured to fit then no
   longer fits, and the stats headline broke mid-word on iPhone only while Android and every
   desktop browser tested were clean - Android ignores this property, which is exactly the
   split that was reported.
   It has no effect on any desktop browser, so it is mobile-only by nature. `100%` rather than
   `none`: `none` also blocks the visitor's own pinch-zoom text scaling on some versions. */
html{-webkit-text-size-adjust:100%;text-size-adjust:100%;}

/* >> THE 404 AND THE OTHER INNER PAGES PUT THE NEAR-BLACK ON THE ROOT, NOT ON `body`
   (owner, wd-s34 - the phone bug he photographed).
   On those pages the backdrop picture is a `z-index:-1` pseudo-element on `html`. That paints
   above the canvas but BELOW any background `body` paints inside its own box - so on a page
   shorter than the screen (the 404 is 406px of content in an 844px viewport) a phone painted
   body's near-black over the picture and left it showing only below the content.
   >> SCOPED BY `:not(:has(.bp-bg))`, THE SAME SELECTOR THE BACKDROP ITSELF USES, so the HOME
   PAGE IS EXCLUDED: it carries `.bp-bg`, its picture is an `<img>`, and its own `body`
   background is what sits behind that. The shared `body` rule below is untouched. */
html:not(:has(.bp-bg)){background:var(--bg);}
html:not(:has(.bp-bg)) body{background:transparent;}

body{
  background:var(--bg);
  color:var(--white);
  font-family:'Barlow',system-ui,sans-serif;
  -webkit-font-smoothing:antialiased;
  overflow-x:hidden;
}

/* radial brand glow behind everything */
body::before{
  content:"";position:fixed;inset:0;z-index:0;pointer-events:none;
  background:
    radial-gradient(60rem 40rem at 78% 18%, rgba(204,0,0,0.16), transparent 60%),
    radial-gradient(50rem 40rem at 12% 82%, rgba(204,0,0,0.07), transparent 60%);
}

/* ================= EXPERIMENT, wd-s23 SOSI 1 - THE BLUEPRINT BACKDROP HELD STATIC
   The backdrop stops travelling with the hero. It is the SAME picture the hero brings into
   focus; once the sequence completes and the stage unpins, it simply stays where it is and
   the rest of the site scrolls over it.
   >> THIS IS A LOOK-SEE, NOT A DECISION. Revert = delete this block, restore
      `.bp-bg{position:absolute}` and put the `<img id="bg">` back inside `.bp-stage`,
      delete `assets/js/site-backdrop.js` and its script tag on the three pages.

   >> ONE IMAGE, PROMOTED TO FIXED - NOT A SECOND COPY UNDERNEATH.
      The first build of this put a separate fixed layer behind the whole page and left the
      hero's own backdrop where it was. Same picture twice: as the stage scrolls off, the
      hero's copy TRAVELS UP over a stationary copy of itself, which ghosts.
      **The backdrop the visitor watches come into focus has to be the one that stays**, so
      the `<img id="bg">` is hoisted out of `.bp-stage` to body level and made `fixed`.
      Nothing about the sequence changes - during the pin the stage filled the viewport at
      top:0, so an absolute child of it and a fixed child of the viewport draw identically.

   >> IT HAD TO LEAVE `.bp-stage`, IT COULD NOT JUST BE SET TO `fixed` IN PLACE.
      `position:sticky` creates a stacking context. A `z-index:-1` child is trapped inside
      it, and the stage's own context paints at z=0 - ABOVE the static sections further down
      the page. The backdrop would have covered their text. Out at body level it is a
      sibling of the sections and -1 puts it genuinely behind them.

   >> Z-INDEX -1 DOES NOT HIDE IT. `body` carries `background:var(--bg)`, which propagates to
      the canvas, and the canvas paints BELOW negative z-index. The near-black stays as the
      fallback if the image ever fails to load.

   >> KNOWN CHANGE TO THE HERO, AND IT IS THE ONLY ONE: the red glow (`body::before`, z-index
      0) now paints OVER the backdrop instead of behind the whole stage, so the hero's
      picture picks up a faint warm wash top-right. It cannot be avoided while the backdrop
      is behind everything - and with an opaque picture over the full viewport the glow has
      nowhere else to land, so tinting the picture is the only way it survives at all.
   ================================================================================== */

/* The two inner pages have no hero, so there is no sequence to inherit a backdrop from -
   they get the picture as a root layer, sharp from load. `:has()` scopes it: the home page
   is the only file carrying a `.bp-bg`. */
html:not(:has(.bp-bg))::before{
  content:"";
  position:fixed; inset:0; z-index:-1; pointer-events:none;
  background-image:url("https://media.7starwebstudio.com/7star/hero/bg-land.webp");
  background-size:cover; background-position:center center;
  /* >> ONE BRIGHTNESS FOR THE PLATE, READ BY BOTH SURFACES THAT SHOW IT (wd-s24 SOSI 1).
        The home page paints this picture through `.bp-bg`, these two pages through this
        pseudo-element. Same photograph, two mechanisms - so the multiply is a custom
        property both read rather than a number typed in two places, or the inner pages
        quietly keep the old level the first time the hero's is tuned.
        `.bp-bg` in hero-blueprint-index.html carries the reasoning and the headroom. */
  filter:brightness(var(--sbg-bright,1.6));
}
@media (orientation:portrait){
  html:not(:has(.bp-bg))::before{ background-image:url("https://media.7starwebstudio.com/7star/hero/bg-port.webp"); }
}

/* the veil, on every page - dialled by ?veil=N or ?pveil=N.
   >> IT ONLY EXISTS WHEN ONE IS ACTUALLY SET, AND THAT IS A PERFORMANCE RULE, NOT TIDINESS.
      A second full-viewport FIXED layer costs a full-viewport composite on every scroll
      frame whether it is painting black at 0.4 or painting nothing at 0. It was on for
      everyone by default at zero alpha - pure cost, no pixels. The script adds `.has-veil`
      only when a value is passed. */
html.has-veil::after{
  content:"";
  position:fixed; inset:0; z-index:-1; pointer-events:none;
  background:rgba(0,0,0,var(--sbg-veil,0));
}
@media (orientation:portrait){
  /* portrait gets its own value, because the shaft is a far bigger share of the frame here
     - see the PORTRAIT SHAFT note at the foot of this block */
  html.has-pveil::after{
    content:"";
    position:fixed; inset:0; z-index:-1; pointer-events:none;
    background:rgba(0,0,0,var(--sbg-pveil,0));
  }
}

/* >> A COMPOSITOR PROMOTION (`will-change:transform`) WAS ADDED HERE AND HAS BEEN REMOVED.
      It was a fix for a misdiagnosis. The reasoning was sound in general - an unpromoted
      fixed layer is repainted every scroll frame - but it was not what made this page
      sticky, and the owner had already told me the phone scrolled fine WITH the fixed
      backdrop in place. A promotion costs a full-screen texture in GPU memory, so it does
      not stay on the page as insurance against a cause we now know it does not address.
      >> IF A REAL FIXED-LAYER SCROLL COST EVER SHOWS UP, this is the first thing to try.
         It was not wrong, it was aimed at the wrong thing. */

/* ?bg=off - today's site, for the A/B. On the home page the script also puts the image back
   inside `.bp-stage`, so this restores the real previous behaviour rather than mimicking it. */
html.bg-off::before, html.bg-off::after{ display:none; }
html.bg-off .bp-bg{ position:absolute; z-index:auto; }

/* ---------- wd-s23 SOSI 1, ROUND 2 - the owner's section-by-section verdict ----------
   Everything below is scoped `html:not(.bg-off)`, so `?bg=off` still restores the real
   previous page with none of it applied.

   >> THE POSITION THIS ROUND TAKES, AND IT IS THE OWNER'S, NOT A COMPROMISE: THE BACKDROP
      DOES NOT HAVE TO BE EQUALLY PRESENT ON EVERY SECTION. It earns its place on Portfolio
      and it is a liability on Pricing and Contact. That is the wd-s8 band alternation doing
      exactly the job it was built for - the bands decide where the fixed layer shows - so
      this extends that scheme rather than arguing with it.

   1. PRICING AND CONTACT - the diagonal light shaft is the problem, not the picture.
      A FIXED backdrop pins that shaft to one place on screen forever, so on these two it
      cuts straight through a centred panel and leaves the left of the card darker than the
      right. Owner: "the diag line and lighting difference on either side of line."
      Pricing carries NO band today (wd-s8, deliberate) and Contact carries 0.50 - at 0.50
      the shaft is halved, not removed. Both go to a heavier band, which flattens it.
      >> THIS OVERRIDES A LOCKED wd-s8 DECISION (Pricing is one of the three bare sections).
         If the backdrop stays, that needs a DESIGN.md row of its own - the reason bare
         mattered was a subtle glow behind it, and a photograph is not that. */
html:not(.bg-off) .offer,
html:not(.bg-off) .contact{ background:rgba(0,0,0,var(--sbg-band,0.80)); }

/*  2. THE FOOTER SPLIT. The footer carries no background at all - just the .5px top rule -
       so with Contact at a heavy band and the footer bare, the boundary reads as two
       different shades of background rather than as one page. Owner flagged exactly that.
       It takes the SAME value as Contact, from the same custom property, so the two cannot
       drift apart and the seam has nothing to show. */
/* >> THE FOOTER IS THE DARK CARD (owner, wd-s34). It was rgba(0,0,0,.80) flat - the only
   dark surface on the site that did not match the stat cards, the step cards, the example
   cards and the offer panel. Same gradient, same blur, same border colour as all four. */
html:not(.bg-off) footer{ background:rgba(0,0,0,var(--sbg-band,0.80)); }
/* >> THE FOUR DARK CARDS' RECIPE, ON THE START PAGE ONLY (owner, wd-s34) - same gradient and
   blur as its stat tiles, example cards, step cards and offer panel. The home page keeps the
   flat rgba(0,0,0,.80) above, which is what the repo carries. One shared footer rule, so the
   change is scoped rather than written into it. */
html:not(.bg-off) body:has(.start-page) footer{
  background:linear-gradient(180deg, rgba(32,32,37,.80), rgba(15,15,18,.88));
  backdrop-filter:blur(16px) saturate(130%);
  -webkit-backdrop-filter:blur(16px) saturate(130%);
}

/*  3-5. TEXT OVER THE PICTURE, ON THE PORTFOLIO SECTION THE OWNER LIKES.
       All three of these were coloured against near-black and are now sitting on mid-cyan.
       >> THIS IS THE "SAME NUMBER, DIFFERENT CONDITIONS" TRAP FROM THE SHARED STANDARD, and
          the house already has the answer for it: the legibility text-shadow from the 7Star
          HTML Component Standards - "any text set over a photo/video". Copied value for
          value, not re-derived.

   3. The heading. Owner: "the header could pop more, now that it's against the cyan bg",
      then later - once the two helper lines were approved - asked for the same treatment
      here.

      >> ONLY HALF OF THAT TREATMENT CAN APPLY, AND IT IS WORTH KNOWING WHICH HALF. The
         helper lines got a COLOUR lift plus a WEIGHT bump. This heading is already pure
         white, so there is no colour left to lift - white is the ceiling. The weight is the
         whole of it: 700 -> 800.
      >> 800 IS A REAL WEIGHT, NOT A SYNTHESISED ONE. The page loads Barlow Condensed at
         400;600;700;800. Same check as the helper lines - a weight the link does not carry
         comes back as faux-bold, and smeared strokes on busy linework are worse than the
         drawn face one step down.
      >> THE SHADOW STAYS AND IS UNCHANGED. It is safe here in a way it is not under the
         deck: `.work h2` lives in `.work-head`, outside `.work-item`, so nothing
         re-rasterises it every frame.
      >> THE SIZE IS UNTOUCHED - it is a wd-s10 value. Weight thickens the strokes without
         changing how much room the line takes or where it wraps. */
html:not(.bg-off) .work h2{
  font-weight:800;
  text-shadow:0 2px 12px rgba(0,0,0,.85), 0 1px 4px rgba(0,0,0,.6);
}

/*  4. The helper line under the heading - APPROVED ON PHONE, THEN EXTENDED TO THE LAPTOP
       (owner, wd-s23: "make the same change for laptop also"). It went in behind an
       820px media query so it could be judged on a phone alone; that query is gone and the
       three declarations sit on the one rule, at every width.

       >> THE COLOUR: `--gray-dim` is #6c6c6c, picked to sit quietly on #0a0a0a. On cyan it
          is not quiet, it is illegible - a dim grey needs a dark ground to read as grey at
          all. It lifts to #c9c9c9 and keeps the body-weight legibility shadow.
       >> THE WEIGHT DOES MORE THAN ANOTHER STEP OF BRIGHTNESS WOULD: the picture behind it
          is BUSY, not dark - blueprint linework runs straight through the letterforms - and
          against detail it is stroke thickness that separates type from its ground, not
          tone. Barlow 400 is a thin face at .98rem; 600 thickens every stroke without
          touching the size, so the line does not start competing with the heading above it.
       >> 600 IS A REAL WEIGHT HERE, NOT A SYNTHESISED ONE. The page loads Barlow at
          300;400;500;600, so this is the drawn face. Asking for a weight the link does not
          carry gets a smeared faux-bold, which on a busy ground looks worse than 400.

       >> ONE RULE, ONE BREAKPOINT-FREE LOOK - AND THAT IS THE POINT OF FOLDING IT BACK IN.
          A phone-only override would have meant the same line reading two different ways
          on two devices, which is a second look for one element by accident rather than by
          decision. The house rule is one look per element type; the media query was a
          testing scaffold, not a design call.

       >> IT IS NOT INSIDE `.work-item`, WHICH IS WHY IT WAS THE SAFE ONE TO TRY FIRST.
          `.work-sub` lives in `.work-head`; the deck script rewrites `.work-item`'s
          transform every frame and never touches this. The link and the helper UNDER the
          deck do live in there, and they stay reverted until each is judged on its own. */
html:not(.bg-off) .work-sub{
  color:#c9c9c9; font-weight:600;
  text-shadow:0 1px 8px rgba(0,0,0,.9), 0 1px 3px rgba(0,0,0,.7);
}

/*  5. The quiet text link under the deck.
       >> THIS IS A DELIBERATE DEVIATION FROM THE 7 STAR QUIET TEXT LINK STANDARD, WHICH
          FIXES REST OPACITY AT .72. White at .72 over near-black is a quiet link; the same
          number over a lit photograph is a washed-out one. The standard's own wording
          already concedes the point - it changes colour on a light background - so this is
          the same allowance applied to a photographic one. Rest opacity goes to 1.
       >> THE COST, SAID OUT LOUD: the link's rest state and its HOVER state are now the same
          brightness, so hover is carried by the underline alone. The alternative is a link
          nobody can read at rest, which is worse.
       >> THE SHADOW IS THE ROUND-2 ONE, UNCHANGED - NOT DEEPENED. That matters: `.work-open`
          sits inside `.work-item`, whose transform the deck script rewrites every frame, so
          everything in it is re-rasterised per frame and a blurred shadow is the most
          expensive thing that can live there. Opacity is free; blur is not. Round 3 deepened
          this to a 10px blur AND added a second shadow below - that is what was reverted,
          and it is not coming back.
       >> WEIGHT IS ALREADY 600 FROM THE HOUSE QUIET-LINK RULE AND STAYS THERE. 700 was the
          obvious next step and is wrong: the page loads Barlow at 300;400;500;600 only, so
          700 would be a synthesised faux-bold - smeared strokes, which on busy linework
          reads worse than the real 600.
       >> IF THE BACKDROP IS KEPT, THIS NEEDS A DESIGN.md ROW. A standard bent without a
          record is a standard that quietly stops existing. */
html:not(.bg-off) .work-open{
  opacity:1;
  text-shadow:0 1px 8px rgba(0,0,0,.9), 0 1px 3px rgba(0,0,0,.7);
}

/*  6. THE HELPER LINE UNDER THE DECK - now the same three declarations as the one under the
       heading: colour, weight, and the body-weight legibility shadow.
       `.work-tag` is `--gray-dim` too, and it sits below the deck where the backdrop is at
       its brightest, so it was the worst-off line in the section.

       >> THE SHADOW WAS HELD BACK AND HAS BEEN PUT IN. It was left off because this line
          lives inside `.work-item`, whose transform the deck script rewrites every frame,
          so a blurred shadow here is re-rasterised per frame - and that was my read on what
          made the owner's phone sticky.
       >> TWO THINGS KILLED THAT REASONING. The owner traced the stickiness to his phone's
          CACHE, not the code - it survived a full revert and went away when he cleared it.
          And `.work-open`, directly above this line, sits in the SAME `.work-item` and has
          carried the shadow throughout, on a page that scrolls fine. A cost one line can
          afford is not a cost its neighbour cannot.
       >> WHAT IS LEFT OF THE ORIGINAL CAUTION, BECAUSE IT IS STILL TRUE: blur radius is the
          expensive part and it is paid per glyph, per frame, inside a transformed layer. If
          scroll performance is ever genuinely in question again, the shadows INSIDE
          `.work-item` are the first place to look - and the fix is a zero-blur offset
          (`0 1px 0`), not deleting them. Round 3's mistake was a 10px blur, not a shadow.
       >> HIERARCHY IS UNCHANGED BY THIS: the link above is WHITE at full opacity, this is
          #c9c9c9 at .9rem, so the thing meant to be clicked is still the loudest thing
          under the card. */
html:not(.bg-off) .work-tag{
  color:#c9c9c9; font-weight:600;
  text-shadow:0 1px 8px rgba(0,0,0,.9), 0 1px 3px rgba(0,0,0,.7);
}

/*  6b. THE MARQUEE NAMES - wd-s24. RESTORING A PARITY, NOT SETTING A NEW LEVEL.
       wd-s18 put `.mq-item` on `--gray-dim` for one stated reason: to sit at exactly the
       same level as `.work-tag`, so a scrolling list of single words could not pull rank on
       the sentence that closes the section. Round 6 above then lifted `.work-tag` to
       #c9c9c9/600 and left the marquee on #6c6c6c/400 - so that decision quietly broke, and
       the strip has been the dimmest thing in the section since.
       >> NO SHADOW HERE, UNLIKE ITS TWIN ABOVE. These names sit inside `.mq-strip.glass`,
          and the standard excludes text on a glass panel - the panel IS the controlled
          surface. `.work-tag` needs one because it sits on the bare picture. */
html:not(.bg-off) .mq-item{ color:#c9c9c9; font-weight:600; }

/* ---------- wd-s23 SOSI 1 - ROUND 3 REVERTED, AND WHY IT IS RECORDED RATHER THAN DELETED

   ROUND 3 was three readability tweaks on this section: `.work-sub` bolded to 600 and
   brightened, `.work-open` taken from .88 opacity to 1 with a deeper shadow, and a
   text-shadow added to `.work-tag` for the first time. All three are OUT.

   >> THE OWNER PINNED IT PRECISELY: the phone scrolled fine before those three, and was
      sticky immediately after. That is the evidence, and it beats my reasoning about fixed
      layers - which was aimed at the ORIGINAL backdrop change, something he had already
      been scrolling smoothly for two rounds.

   >> THE MECHANISM THAT FITS, AND IT IS THE ONE TO REMEMBER: `.work-open` AND `.work-tag`
      LIVE INSIDE `.work-item`, AND THE DECK SCRIPT REWRITES THAT ELEMENT'S TRANSFORM ON
      EVERY SINGLE FRAME. Anything inside it is re-rasterised every frame, and a BLURRED
      text-shadow is the most expensive thing you can put in that position - blur radius is
      the cost, and it is paid per glyph. Round 2 had one short one-line link carrying an
      8px shadow and that was affordable. Round 3 added a second shadow on a full-width
      two-line paragraph and deepened the first to 10px. On a phone that tipped it over.
      >> SO: NO BLURRED TEXT-SHADOW ON ANYTHING INSIDE THE DECK RAIL. If those lines need to
         read against the picture, buy it with COLOUR, WEIGHT, or a ZERO-BLUR offset shadow
         (`0 1px 0`), which costs almost nothing. That is the retry, and it is not started.

   >> THE SHADOW BOXES THE OWNER SEES ABOVE AND BELOW THE DECK ARE THESE SAME SHADOWS. On a
      laptop these are short centred lines and a soft shadow reads as a shadow. On a phone
      they run full width and wrap, so four blurred wrapped lines merge into what looks like
      a dark rectangle. `.work-sub`'s round-2 shadow is the upper box and is still in place.
   ------------------------------------------------------------------------------------- */

/* ---------- wd-s23 SOSI 1, ROUND 4 - the two mobile findings ----------

   >> THE PORTRAIT SHAFT - the "funky shading" under the deck, and it is NOT a CSS shadow.
      It is the backdrop's OWN diagonal light shaft. `bg-port.webp` carries a heavy dark
      band across its upper-middle, far wider as a share of the frame than the landscape
      file's, and a FIXED layer pins that band to one place on the screen forever. On a
      phone it lands directly under the slide deck.
      >> THIS IS THE SAME DEFECT THE OWNER ALREADY REJECTED ON PRICING AND CONTACT - "the
         diag line and lighting difference on either side of line". It reads worse in
         portrait because the shaft occupies more of the picture, which is why a section he
         LIKES on a laptop is the one showing it on a phone.
      `?pveil=N` flattens it portrait-only, so the laptop is untouched. It is a stopgap:
      the real fix is to even out the shaft in the image, the way the GCH material shots
      were flattened - and that is a decision, because it means regenerating an asset the
      locked hero sequence reads. Not started.

   >> THE GLASS DIAGNOSTIC. The nav and the marquee strip carry `backdrop-filter:blur(20px)`.
      That was cheap while the pixels behind them were flat near-black; behind a detailed
      photograph the browser now has a real blur to compute on every scroll frame, on the
      one element that is on screen the whole time.
      `?glass=off` kills every backdrop-filter on the page so the cost can be isolated in
      one reload. IT IS A TEST, NOT A FIX - the frosted nav is a house standard and this
      makes it plain glass. If it turns out to be the culprit, the answer is a decision
      about the blur radius on phones, not this switch. */
html.glass-off *{ backdrop-filter:none !important; -webkit-backdrop-filter:none !important; }

/* ---------- wd-s23 SOSI 1, ROUND 5 - THE TREATMENT GOES SITE-WIDE ----------
   Owner, after approving all four lines on the Portfolio section: apply the same three
   declarations - colour, weight, shadow - across the whole site, the stats page included.

   >> THE RULE THAT DECIDES WHO GETS IT: THE SURFACE THE TEXT SITS ON, NOT WHAT THE TEXT IS.
      Text sitting DIRECTLY on the picture gets the treatment. Text on a frosted panel or on
      one of the heavy bands does not - it already has a surface between it and the picture,
      and changing it would be re-styling something that was approved and is not in trouble.
      That single test is what keeps this a scoped pass instead of a find-and-replace across
      42 colour declarations.

   >> DELIBERATELY EXCLUDED, AND EACH FOR ITS OWN REASON:
      - EVERYTHING INSIDE A GLASS PANEL - the stat cards, the catalogue card, the price card,
        the contact form. Checked on screen: those panels render as near-black cards and
        their text is perfectly legible. The panel IS the surface.
      - PRICING, CONTACT AND THE FOOTER - they carry the 0.80 band, so they are near-black
        again. A shadow there would do nothing visible and a weight change would alter a
        look the owner has already signed off.
      - THE HERO - it has `.bp-scrim`, a purpose-built gradient behind its copy. Solved.
      - FORM PLACEHOLDERS, THE BROWSER-MOCK CHROME, THE STRUCK-THROUGH OLD PRICE. Bolding a
        placeholder makes it look like a filled field; bolding fake UI inside a screenshot
        makes it stop reading as a screenshot.

   >> ONE RULE PER ELEMENT TYPE, DECLARED ONCE, SHARED BY EVERY USE - the house rule, and the
      reason these are three grouped selector lists rather than a rule per page. A heading on
      the stats page and a heading on the home page are the same job; if they are ever to
      differ that is a decision, not an accident of having been written twice.

   >> TWO OF THE FIVE HEADINGS WERE ALREADY AT 800 (`.stats-head h1`, `.cat-head h1`), so for
      those this only adds the shadow. Declaring 800 on all five costs nothing and stops the
      set drifting apart later. */

/* headings - white already, so weight and shadow are the whole treatment */
html:not(.bg-off) .work h2,
html:not(.bg-off) .say h2,
html:not(.bg-off) .how h2,
.faq h2,
.start-head h1,
.start-ex h2,
.start-price h2,
html:not(.bg-off) .stats-head h1,
html:not(.bg-off) .cat-head h1,
html:not(.bg-off) .cat-head h2,
/* the stats page's own closing heading - "Let's build yours." It is the last thing on that
   page and the one the whole argument lands on, so leaving it off the list was a miss, not a
   scoping decision. Already 800 in its own rule; this only adds the shadow. */
html:not(.bg-off) .stats-close h2{
  font-weight:800;
  text-shadow:0 2px 12px rgba(0,0,0,.85), 0 1px 4px rgba(0,0,0,.6);
}

/* the small caps kicker above a heading. It is the worst-off text on the site - #6c6c6c,
   .76rem and letter-spaced, which spreads what little ink it has even thinner. Weight stays
   at its own 600; only the colour and the shadow change. */
html:not(.bg-off) .kicker{
  color:#c9c9c9;
  text-shadow:0 1px 8px rgba(0,0,0,.9), 0 1px 3px rgba(0,0,0,.7);
}

/* helper and intro copy sitting on the picture */
html:not(.bg-off) .say p,
html:not(.bg-off) .how-sub,
/* >> `.how-terms` - "Payment is simple: half to start, half before it goes live." - IS
   DELIBERATELY NOT IN THIS LIST (owner, wd-s23). It was added, seen, and taken back out.
   It is a TERMS line, not a helper line: it sits at the very bottom of How it works, below
   the four steps, and its job is to be available rather than to be read on the way past.
   Given the treatment it pulled level with `.how-sub` at the top of the section - two lines
   of equal voice bracketing the steps, which is not the hierarchy that section has.
   >> THE SCOPING RULE STILL HOLDS - it does sit on the picture, so by surface alone it
      qualifies. This is the exception the rule allows for: legibility is the floor, not the
      goal, and the quietest thing on a section is allowed to stay the quietest. */
html:not(.bg-off) .stats-intro,
.start-intro,
.start-sub,
.start-stats-line,
.start-note,
.start-reply,
html:not(.bg-off) .cat-intro{
  color:#c9c9c9; font-weight:600;
  text-shadow:0 1px 8px rgba(0,0,0,.9), 0 1px 3px rgba(0,0,0,.7);
}

/* the house quiet link, wherever it sits on the picture. `.work-open` is handled above with
   its own note; this is its opposite number in How it works. The two contact-form links are
   NOT here - they live on a glass panel inside a banded section. */
html:not(.bg-off) .how-next a{
  opacity:1;
  text-shadow:0 1px 8px rgba(0,0,0,.9), 0 1px 3px rgba(0,0,0,.7);
}

/* ===== WHERE AN ANCHOR JUMP STOPS - solved wd-s10, owner. Two attempts, and the second is
   the one that is right for a SEE-THROUGH nav.

   ATTEMPT 1 (wrong): `scroll-margin-top` big enough to clear the nav. It does clear it - but
   scroll-margin stops the SECTION'S TOP EDGE that far down the screen, so the strip above it
   is the BOTTOM OF THE PREVIOUS SECTION. Behind a frosted bar that is see-through, so the
   tail of the last section reads through the nav and a stray line of it sits just above it.
   The owner caught it on four separate jumps.

   ATTEMPT 2 (this one): stop at the very top - `scroll-margin-top:0` - and let the SECTION'S
   OWN TOP PADDING carry the clearance. The strip behind the nav is then the section's own
   empty padding, so there is nothing to read through the glass, and the heading clears the
   bar by design rather than by luck.

   >> THE RULE THIS LEAVES BEHIND: with a fixed TRANSLUCENT nav the clearance belongs to the
   SECTION, not to the scroll. Padding removes the problem; scroll-margin only moves it.

   Measured, not guessed: the nav's bottom edge is 78px on a laptop and 95px on a phone (it
   wraps to two rows there). The four target sections carry more top padding than that,
   leaving ~33px of air under the bar at either size. >> IF THE NAV'S HEIGHT OR ITS 1.1rem
   TOP OFFSET EVER CHANGES, RE-MEASURE AND RESET THOSE FOUR PADDINGS. */
[id]{scroll-margin-top:0;}

/* ===== SECTION TEXT REVEAL (wd-s18) - the kicker, heading and helper line of every
   section rise into place as it arrives. Driven by `assets/js/section-reveal.js`.

   >> VALUES PORTED FROM THE7STARAGENCY.COM'S HERO TAGLINE, via the GCH example site which
   already carries this effect. 30px of rise, .6s ease, 28ms per character. Not re-derived -
   if one of these numbers is ever changed, GCH's `.char` rules are the other copy and the
   two are supposed to match.

   >> THE MASK IS THE HALF THAT MATTERS. `.rv-line` is `overflow:hidden`, so characters rise
   from behind its own bottom edge rather than fading in mid-air. Remove it and the whole
   thing collapses into an ordinary fade.
   The padding/negative-margin pair protects descenders from that same mask.

   >> KNOWN LIMIT, AND IT IS THE DONOR'S TOO: THE MASK IS PER LINE OF MARKUP, NOT PER LINE
   OF RENDERED TEXT. A heading broken with `<br>` gets one mask per line and the effect is
   exact. A heading that WRAPS on its own - the stats page's h1, or any heading on a narrow
   phone - is one mask around all of it, so its characters rise past each other inside the
   box instead of out from under a clean edge. It still reads as a rise; it is just softer.
   >> THE CHEAP FIX IS A `<br>` where the line should break. The expensive one is grouping
   characters by their measured `offsetTop` after layout and re-grouping on resize - real,
   but it is a different piece of work and it was not what was asked for.

   >> NOTHING HERE HIDES ANYTHING BY DEFAULT. Every class below is added by the script, so a
   blocked or failed script leaves the page fully readable, and a visitor who asked for
   reduced motion gets the untouched page - the script returns before it touches the DOM.
   That is why there is no `prefers-reduced-motion` block here: there is nothing to undo. */
.rv-line{display:block;overflow:hidden;padding-bottom:.1em;margin-bottom:-.1em;}
/* >> .5s, NOT THE DONOR'S .6s (owner, wd-s18 - "speed it up a little"). Its partner is
   STEP in section-reveal.js, which came down 28ms -> 20ms in the same pass. The two are one
   setting split across two files: the step decides how long the run takes, the duration
   decides how long each character takes to arrive. Change one without the other and the
   effect either bunches up or trails. */
.rv-char{
  display:inline-block;opacity:0;transform:translateY(30px);
  transition:opacity .5s ease, transform .5s ease;
}
.rv-char.visible{opacity:1;transform:none;}
/* the kicker and the helper line rise as whole blocks - a paragraph split per character
   reads as a gimmick, and the donor treats its sub-line exactly this way. 18px, not 30 */
.rv-block{opacity:0;transform:translateY(18px);transition:opacity .5s ease, transform .5s ease;}
.rv-block.visible{opacity:1;transform:none;}

/* ==================================================================================
   THE HERO MARQUEE (wd-s18) - a `Built for` label, then the business types scrolling.

   >> MECHANISM COPIED FROM THE GCH EXAMPLE SITE'S REVIEWS STRIP, not written fresh.
   Source: `sites/example/gch-gangnam-chimaek-house/site/assets/css/style.css`, the
   `.marquee` block. Three rules came across with it and all three are load-bearing:

   1. >> NO `will-change` AND NO `translateZ(0)` ON THE TRACK. It is thousands of pixels
      wide; promoting it to its own compositor layer asks the GPU for a texture past the
      size limit, the layer fails, and THE WHOLE STRIP PAINTS AS NOTHING. That failure
      looks like a gap in the loop, which sends you hunting in the wrong place.
   2. >> THE LIST IS WRITTEN TWICE AND THE ANIMATION TRAVELS EXACTLY -50%, which lands on
      the duplicate's first item so there is no seam.
   3. >> HALF THE TRACK MUST STAY WIDER THAN THE WIDEST SCREEN THIS IS SEEN ON, or the
      wrap point happens ON screen and opens a real blank band once per cycle - a bug that
      only appears on big monitors, so never on the machine it was built on. Thirty-four
      items is what keeps that margin; cutting the list back is what brings it back.

   >> THE PAUSE IS SCOPED TO THE WHOLE STRIP HERE, AND THAT IS A DELIBERATE DEPARTURE.
   GCH scopes its pause to `:has(.quote:hover)` because its strip sits inside a padded
   section with a heading, so a section-level hover froze the marquee while the cursor was
   nowhere near it. This strip has no padding and no heading - it IS the band, edge to
   edge and about 40px tall - so hovering it and hovering an item are the same gesture.
   ================================================================================== */
.mq-strip{
  /* >> IT SITS BETWEEN THE PORTFOLIO DECK AND HOW-IT-WORKS, NOT IN THE HERO (owner, wd-s18).
     It was built in the hero first, under the animation frame, and moved after seeing it
     run. Three things were wrong there and only the last one is about taste:
       - the frame's payoff is the moment meant to be watched, and a moving band directly
         under it gives the eye two things moving at once;
       - it answers "is this for me?" before the visitor has been told who is talking, which
         is the same objection that cut the button and the scroll cue from that spot in wd-s4;
       - the reference site the owner brought this idea from runs its own marquee AFTER its
         work showcase, not in its hero. It reinforces a case already made.
     Down here it is doing that job: the visitor has just seen the examples.
     >> AND IT LIVES INSIDE `.work-stage`, THE PINNED PART - not after the section. Sitting
     between the two sections it was only ever on screen in the dark gap between them, so the
     deck it is meant to sit under had already scrolled away by the time it appeared. Inside
     the stage it is pinned WITH the cards and is on the same screen as them the whole time
     the section holds, which is the point.
     >> `margin-top:auto` PUTS IT ON THE STAGE FLOOR. The stage is a centred flex column, so
     an auto margin on the last child absorbs the free space and the cards stay centred in
     what is left - nothing has to be measured against the stage height.
     >> IT IS A FROSTED BAR NOW, NOT A FULL-BLEED BAND (owner, wd-s18). It carries the shared
     `.glass` class - the same one the nav, the price card and the contact panel carry - so it
     cannot drift from them, and the geometry below matches `.nav` exactly rather than
     approximately. The red rule above and grey rule below are gone; the panel's own border
     and its four shadows do that job, and doing it twice was what made the old version read
     as a divider rather than an object.
     >> WHICH MEANS IT STOPS BEING EDGE TO EDGE, AND THAT IS THE POINT OF THE CHANGE. A band
     runs to the screen edges; a bar sits ON the page with its corners showing.
     >> AND THE SIDE MARGINS ARE ZERO, WHICH LOOKS LIKE A DETAIL AND IS ACTUALLY THE FIX FOR
     THE CLIPPED CORNERS. They were -.1rem, to close the 0.1rem gap between `.work`'s 1.5rem
     padding and the nav's 1.4rem inset so the bar lined up with the nav down both edges.
     But `.work-stage` is `overflow:hidden` - it has to be, the deck's outgoing cards fly out
     through it - and a negative margin puts the bar 1.6px OUTSIDE the box that is doing the
     clipping. Measured: stage 24.0 to 1341.0, strip 22.4 to 1342.6. So exactly 1.6px was
     being sliced off each rounded corner, which is what reads as "cut off at the very ends".
     >> THE NAV ALIGNMENT IS THE THING GIVEN UP, AND IT IS THE RIGHT ONE TO GIVE UP: 1.6px on
     a 1320px bar is not visible, a chopped corner is. Do not put the negative margin back
     without also giving the stage that much horizontal padding to clip against.
     >> AND THE AUTO TOP MARGIN IS WHAT BRINGS THE DECK UP. An auto margin makes the stage
     ignore its own `justify-content:center`, so the heading, helper line and cards pack to
     the TOP of the stage and all the leftover height collects above the strip. That is the
     owner's ask - the whole portfolio group moves up, and the strip sits on the floor with
     both of them on one screen.
     >> BOTH SIDE MARGINS ARE `auto`, NOT JUST THE TOP (owner, wd-s18 - the bar was sitting
     right under the helper line). It was `auto 0 3rem`, which sent ALL the leftover height
     above the bar and pinned it 3rem off the floor - so on a shorter window there was nothing
     left to send and the gap above collapsed. Measured: 89px at 1920x1080, 59 at 1440x900,
     25 at 1512x850 and **4px at 1280x720**. That is the complaint, and it is a window-height
     problem, not a spacing value.
     >> `auto` ON BOTH SIDES SPLITS THE LEFTOVER EVENLY INSTEAD, so the bar self-centres
     between the helper line and the bottom edge at ANY window height and cannot close up on
     either. Same technique as the hero stat line on a phone, and the same reason: a fixed
     margin can only be right at one screen size.
     >> AND THE DECK STILL PACKS TO THE TOP, which is the job the original auto margin was
     doing. ANY auto margin defeats the stage's `justify-content:center` - it does not have to
     be the top one - so nothing above the bar moves. */
  margin:auto 0;
  /* >> `align-self:stretch` IS THE FIX FOR A REAL BUG, NOT A TIDY-UP. `.work-stage` sets
     `align-items:center`, so this strip did NOT take the stage's width the way it did in the
     hero - it sized to its own contents, which is the entire scrolling track, thousands of
     pixels wide. Centred, that put the `BUILT FOR` label far off the left edge of the screen
     and the strip started mid-list. The label is the one thing that stops this reading as a
     client logo wall, so losing it is a hard rule 3 problem, not a cosmetic one.
     >> AND IT IS THE "SAME DECLARATION, DIFFERENT CONDITIONS" TRAP. Nothing about the strip
     changed when it moved out of the hero; the hero's flex container simply has no
     `align-items`, so the default stretch was doing this silently.
     `min-width:0` on the window below is the other half - a flex item will not shrink under
     its content width without it, so the strip would still have been track-wide. */
  align-self:stretch;
  /* >> THE OUTER LAYER IS A SURFACE, NOT THE BAR ITSELF (owner, wd-s18 - he pointed at the
     "See the numbers" CTA and asked for that arrangement). That control is a frosted pill,
     `.say-cta-bar`, with a liquid-glass button sitting on it. Here the same two layers hold
     the label and the moving list: `.mq-strip` is the surface, `.mq-inner` is the object.
     >> ROUNDED RECTANGLE, NOT A PILL. A full 999px was tried and the owner's call was that
     the semicircular ends were too much for a bar this long; 14px is the nav's own radius.
     >> AND THE PADDING IS THIN ON PURPOSE - "less of the surface area of that edge visible".
     The CTA shows .55rem of surface all round its button, which is right for something the
     size of a button; on a bar this wide the same ring reads as a frame. .3rem leaves a hint
     of the surface at the edge, which is all the layering needs to register. */
  display:flex;align-items:center;
  /* >> NO BORDERS OF ITS OWN. `.glass` supplies the border and the inset pair; adding a red
     top rule back would sit on top of the panel's lit top edge and flatten it. The two rules
     this replaced were a previous, non-glass version of the same idea. */
  overflow:hidden;
}
/* the object that sits ON the surface - it carries `btn-liquid` in the markup, so its
   depth is the house button treatment and there is no second copy of those eight shadows */
/* >> BOTH SELECTORS BELOW CARRY TWO CLASSES, AND THAT IS NOT STYLE - IT IS THE ONLY WAY THEY
   WIN. `.glass` and `.btn-liquid` are both defined LATER in this file, so at equal
   specificity file order decides and every value here was being thrown away: the bar kept
   `.glass`'s 16px radius and the inner kept `.btn-liquid`'s 999px pill and its button
   padding. Measured in the browser, not spotted by reading. Same trap the hover/active note
   further down describes - it has now cost time twice in this file. */
.mq-strip.glass{
  padding:.3rem;
  border-radius:14px;
  /* >> A SANCTIONED DEVIATION FROM THE SHARED GLASS FILL, AND IT IS RECORDED IN DESIGN.MD
     (owner, wd-s18 - "make the frosted effect darker for this marque, i think drawing eye
     away from examples"). The house recipe says the fill and blur do not change, and that
     holds for every other surface on the site. This one earns the exemption on POSITION:
     every other glass panel is the thing being looked at, and this one sits directly under
     the deck whose entire job is to be looked at. A bar at the shared brightness with a red
     glow around it wins that fight, which is backwards.
     >> THE STRUCTURE IS UNTOUCHED - `.glass` still owns all four shadows and the border, and
     the top-edge highlight is dimmed by redefining its TOKEN here rather than by rewriting
     the shadow. Change the shared recipe and this still follows it.
     >> AND THE TOP-EDGE HIGHLIGHT IS THE ONE VALUE THAT CAME BACK UP - .10 to .14, against the
     shared .20 (owner, wd-s18: the bar reads dark enough now but the frosted depth got lost
     with it). THE FILL STAYED DARK. That split is the house standard's own point: the inset
     pair is what makes a panel read as a solid object with thickness, and raising the fill
     alpha to buy back "solidity" is named in the parent brand file as the wrong fix. So the
     edge is what gets restored and the surface stays down - the dimming survives, the depth
     comes back.
     >> THE OTHER TWO OPTIONS ON THE TABLE WERE DECLINED. A red rule inside the container is
     the thing removed earlier this session and the reason has not changed: the panel's own
     border and lit edge already draw that line, doing it twice reads as a divider rather than
     an object, and brand red on a supporting strip is the same "look at me" signal the glow
     was just taken down for. Lifting the fill was the other, and it is the wrong lever - see
     above. ONE VALUE MOVES THIS: `--glass-hi` on this rule. Nothing else. */
  --glass-hi:rgba(255,255,255,0.14);
  /* >> LIGHTENED .26 -> .14, wd-s24 (owner). This softens the wd-s18 dimming above rather
     than cancelling it - the fill is still a dark tint, not the house white one, so the bar
     still sits under the deck rather than competing with it.
     >> WHY THE wd-s18 REASONING SURVIVES A CHANGE OF VALUE: that call was made against a
     NEAR-BLACK backdrop, where .26 read as a subtle darkening. The backdrop is now a lit
     picture at 1.6x, and against that the same .26 reads as a heavy black slab. Same intent,
     different ground, different number.
     Steps if it wants to go further: .10, then .06, then `var(--glass-bg)` for the house
     recipe - at which point the wd-s18 deviation is gone and DESIGN.md row 77 needs updating. */
  background:rgba(0,0,0,0.14);
}
.mq-strip .mq-inner{
  display:flex;align-items:center;gap:1.1rem;
  width:100%;min-width:0;
  padding:.55rem 1.2rem;
  /* 14px outer minus the .3rem ring - concentric, or the corners look wrong against it */
  border-radius:10px;
  overflow:hidden;
  /* it is a div wearing a button's class - it must not offer a button's pointer */
  cursor:default;
  /* >> THE SAME EIGHT LAYERS `.btn-liquid` DECLARES, WITH THE WHITES HALVED AND THE RED GLOW
     TAKEN DOWN FROM .22 TO .07. The order and the geometry are copied exactly so the object
     still reads as the house button; only the intensity moves. The red glow is what was
     actually pulling the eye - it is a halo, and a halo on a supporting element under a
     showcase is the wrong signal. It is kept rather than deleted because at .07 it is what
     stops the bar looking grey. */
  box-shadow:
    0 0 8px rgba(0,0,0,0.10),
    0 3px 10px rgba(0,0,0,0.35),
    inset 3px 3px .5px -3.5px rgba(255,255,255,0.28),
    inset -3px -3px .5px -3.5px rgba(255,255,255,0.28),
    inset 1px 1px 1px -.5px rgba(255,255,255,0.18),
    inset -1px -1px 1px -.5px rgba(255,255,255,0.18),
    inset 0 0 8px 7px rgba(255,255,255,0.03),
    0 0 14px rgba(204,0,0,0.07);
}
/* >> THE INNER LAYER'S BACKDROP BLUR IS REMOVED, AND IT DOES TWO JOBS AT ONCE.
   `.btn-liquid::before` frosts what is behind the button. Here that is the strip, which is
   ALREADY frosting the page behind it - so this was a blur sampling an already-blurred
   surface, contributing almost nothing visually while costing a full backdrop re-sample.
   1. IT IS HALF THE DIM. A flat dark fill instead of a white one is what takes the object
      itself down without touching the text on it.
   2. IT IS THE CHOPPY SCROLLING (owner, wd-s18 - "the auto scroll of names"). A
      `backdrop-filter` has to re-sample everything behind it EVERY FRAME the content on it
      moves, and this bar had two of them stacked, over a section that is already running a
      scroll-driven rAF loop writing transforms to five deck cards. That is the frame budget
      gone. One backdrop-filter on the outer surface is what the look needs; the second was
      paying full price for nothing.
   >> DO NOT "RESTORE" THIS TO MATCH `.btn-liquid`. It is a deliberate removal on this one
   element, for an element that is not a control. */
.mq-strip .mq-inner::before{
  background:rgba(0,0,0,0.24);
  backdrop-filter:none;
  -webkit-backdrop-filter:none;
}
/* >> THE BUTTON BEHAVIOUR IS SWITCHED OFF, AND IT HAS TO BE. `.btn-liquid` grows on hover
   and dips on press because it is a control; this bar is not one, and a strip that swells
   when the cursor crosses it reads as a broken link. Specificity, not `!important` - two
   classes beat the one on `.btn-liquid:hover`. */
@media (hover:hover) and (pointer:fine){
  .mq-strip .mq-inner:hover{transform:none;filter:none;}
}
.mq-strip .mq-inner:active{transform:none;filter:none;}

/* >> THE LIST FADES OUT AT BOTH ENDS RATHER THAN BEING SLICED (owner, wd-s18 - "cutting off
   slightly the very ends"). With a hard clip a name is cut through mid-letter at the moment
   it leaves, which reads as a rendering fault rather than as motion. The mask takes the last
   28px at each end down to nothing, so words arrive and leave instead of being chopped. */
.mq-view{
  -webkit-mask-image:linear-gradient(90deg,transparent 0,#000 28px,#000 calc(100% - 28px),transparent 100%);
          mask-image:linear-gradient(90deg,transparent 0,#000 28px,#000 calc(100% - 28px),transparent 100%);
}

/* >> THE LABEL SITS OUTSIDE THE SCROLLING TRACK AND NEVER MOVES, AND THAT IS A HARD RULE 3
   MATTER, NOT A LAYOUT ONE. A bare row of business names under a hero reads as a client
   logo wall; we have no clients. The label is the whole difference between "who we serve"
   and "who we have served", so it must be on screen at every moment of the loop - which it
   cannot be if it scrolls. */
.mq-label{
  flex:0 0 auto;
  font-family:'Barlow Condensed',sans-serif;font-weight:700;
  font-size:.8rem;letter-spacing:.2em;text-transform:uppercase;
  /* >> wd-s27 - THE SAME TREATMENT AS THE 84% LINE, value for value. Brand red is darker
     than the backdrop it sits on and near-opposite it in hue, so it vibrates rather than
     separates; the tight achromatic halo gives every letter a neutral edge and lets the
     colour stay saturated. Safe to carry a blurred layer because the LABEL IS STATIC - it
     sits outside `.mq-track`, which is the part that moves. Never copy this onto anything
     inside the track: blur is paid per glyph per frame. */
  color:#FF2A2A;
  text-shadow:
    0 1px 0 rgba(0,0,0,.95), 0 -1px 0 rgba(0,0,0,.95),
    1px 0 0 rgba(0,0,0,.95), -1px 0 0 rgba(0,0,0,.95),
    0 1px 6px rgba(0,0,0,.85);
}
/* the window the track moves behind - the strip's own overflow would clip the label too.
   `min-width:0` lets it shrink below the track's width; without it the flex item refuses to
   go under its content size and the whole strip grows to thousands of pixels. */
.mq-view{flex:1 1 auto;min-width:0;overflow:hidden;}
/* >> NO `will-change` AND NO `translateZ(0)` ON THIS ELEMENT. THIS IS THE OBVIOUS FIX FOR A
   CHOPPY MARQUEE AND IT IS THE WRONG ONE HERE - GCH ALREADY PAID FOR THAT LESSON.
   Promoting a track to its own compositor layer asks the GPU for a texture as wide as the
   track, doubled on a 2x display. GCH's reviews strip is about 7,900px, so 15,800px, which is
   past the limit: the layer fails and THE WHOLE STRIP PAINTS AS NOTHING. That is written up
   in `sites/example/gch-gangnam-chimaek-house/site/assets/css/style.css` above its own
   `.marquee`, and the symptom there was read as "a large gap in the loop" for a while before
   anyone worked out the cards simply never drew.
   >> THIS TRACK IS 6,200px, WHICH IS THE SAME NEIGHBOURHOOD, AND THE FAILURE IS INVISIBLE TO
   EVERY CHECK WE CAN RUN. It needs a real 2x display to show up; a headless screenshot at 1x
   renders it perfectly. Measured, then left unpromoted on GCH's evidence rather than on a
   test we cannot perform.
   >> THE CHOPPINESS FIX IS THE `backdrop-filter` REMOVAL ON `.mq-inner::before` INSTEAD, and
   that is also GCH's answer, arrived at independently on that site: "Do not put
   backdrop-filter back on a MOVING element." Same conclusion, same reason. */
.marquee{display:flex;width:max-content;}
.mq-item{
  /* >> `--gray-dim`, NOT `--gray` (owner, wd-s18). At `--gray` the business names were
     BRIGHTER than `.work-tag` directly above them - the section's own closing line - so a
     scrolling list of single words was pulling rank on the sentence that finishes the
     section. Matching `.work-tag` exactly puts them at the same level in the hierarchy,
     which is where a supporting strip belongs. */
  font-family:'Barlow',sans-serif;font-size:.86rem;color:var(--gray-dim);white-space:nowrap;
}
/* >> THE SEPARATOR IS A DRAWN CIRCLE, NOT A TYPED BULLET (owner wants a coloured bullet).
   Drawn rather than typed for two reasons: a real box takes an exact size and sits on the
   optical centre, where a text bullet's size and baseline are whatever the font decides;
   and the bullet character is non-ASCII, which the house rule keeps out of source files.
   Generated rather than written into the markup so the list stays a clean set of names -
   add or remove an item and the dividers look after themselves. */
.mq-item::after{
  content:"";display:inline-block;vertical-align:middle;
  width:5px;height:5px;border-radius:50%;background:var(--red);
  margin:0 .85rem;
}
@media (prefers-reduced-motion: no-preference){
  /* 90s -> 70s (owner, wd-s18 - "speed up slightly") */
  .marquee{animation:mq-slide 70s linear infinite;}
}
/* >> NO PAUSE ON HOVER, AND IT WAS REMOVED ON PURPOSE (owner, wd-s18). The donor pauses
   because its items are REVIEWS - text somebody may want to stop and finish reading. These
   are single words, nothing is clickable, and there is nothing to stop for; a strip that
   halts under the cursor just reads as broken. Do not port GCH's `:has(.quote:hover)` rule
   across on the grounds that the donor has it. */
/* >> REDUCED MOTION LEAVES THE LIST STATIC RATHER THAN HIDING IT. The first few items stay
   on screen and say who this is for, which is the point of the strip; only the travel is
   removed. That is why the animation lives inside a `no-preference` query rather than being
   switched off in a `reduce` one. */
@keyframes mq-slide{ from{transform:translateX(0)} to{transform:translateX(-50%)} }

/* ---------- shared glass ----------
   >> ONE RECIPE FOR EVERY FROSTED SURFACE ON THE SITE (locked wd-s10, owner). The nav, the
   how-it-works cards, the price card, the contact panel, the button pills and the catalog
   cards all carry this class, so they can never drift apart - change it here and they all
   move together.

   >> THE FOUR SHADOWS ARE WHAT MAKE IT READ 3D, and they are two separate jobs:
     1+2. OUTSIDE - a wide soft shadow that lifts the panel off the page, plus a tight one
          directly under it that stops it looking like it is floating in space.
     3+4. INSIDE - a bright line on the TOP edge and a dark line on the BOTTOM edge. That
          pair is the whole illusion: it says the light is above and the panel has
          thickness. It is the same pair the parent brand standard puts on every button,
          which is why the buttons always looked more solid than the panels did.
   The fill and the blur are deliberately NOT changed - the nav has to stay see-through. */
.glass{
  background:var(--glass-bg);
  backdrop-filter:blur(20px) saturate(140%);
  -webkit-backdrop-filter:blur(20px) saturate(140%);
  border:1px solid var(--glass-brd);
  border-radius:16px;
  box-shadow:
    var(--shadow-md),
    0 2px 8px rgba(0,0,0,0.35),
    inset 0 1px 0 var(--glass-hi),
    inset 0 -2px 0 var(--glass-lo);
}
/* >> THE ONE EXEMPTION: the meter pills on why-a-website.html. That page is a LAST KNOWN
   GOOD and is not being reopened, and a 26px-tall bar does not want a panel's depth on it -
   the inset bottom line alone would eat a tenth of its height. It keeps the original two. */
.meter.glass{
  border:1px solid rgba(255,255,255,0.10);
  box-shadow:var(--shadow-md), inset 0 1px 0 rgba(255,255,255,0.08);
}

/* ---------- nav ---------- */
.nav{
  position:fixed;top:1.1rem;left:1.4rem;right:1.4rem;z-index:50;
  display:flex;align-items:center;justify-content:space-between;
  gap:1.5rem;padding:.7rem 1.1rem;border-radius:14px;
}
/* wd-s8: this is an <a> to the home page on every page. It must not pick up link colour or
   an underline - the wordmark's red 7 is set by its own span and would be overridden. */
.nav-logo{display:flex;align-items:center;gap:12px;min-width:0;
  text-decoration:none;color:var(--white);transition:opacity .2s;}
/* mouse-only, see the HOVER AND PRESS block above `.btn-liquid` */
@media (hover:hover) and (pointer:fine){
  .nav-logo:hover{opacity:.82;}
}
.nav-logo:active{opacity:.7;}
.nav-logo img{width:42px;height:auto;display:block;flex-shrink:0;
  filter:drop-shadow(0 3px 10px rgba(0,0,0,0.7));}
.nav-wordmark{
  font-family:'Barlow Condensed',sans-serif;font-weight:700;
  font-size:20px;letter-spacing:.15em;text-transform:uppercase;
  white-space:nowrap;
}
/* the 7 runs slightly larger than the rest of the wordmark (owner, wd-s3). `em` here is
   relative to .nav-wordmark's own size, so it scales with the mobile override too. */
.nav-wordmark .seven{color:var(--red);font-size:1.14em;line-height:1;}
.nav-links{display:flex;gap:1.6rem;}
.nav-short{display:none;}
.nav-links a{
  white-space:nowrap;
  color:var(--gray);text-decoration:none;font-size:.9rem;font-weight:500;
  transition:color .2s;
}
/* mouse-only, see the HOVER AND PRESS block above `.btn-liquid` */
@media (hover:hover) and (pointer:fine){
  .nav-links a:hover{color:var(--white);}
}
.nav-links a:active{color:var(--white);}

/* ---------- liquid glass button ---------- */
.btn-liquid{
  /* inline-flex + no underline so the SAME class works on a <button> and on an <a>.
     A button is inline-block by default and a link is plain inline, so without this an
     anchor version renders with collapsed padding and the wrong height (wd-s6, when the
     section 2 button became a link to the stats page). */
  display:inline-flex;align-items:center;justify-content:center;text-decoration:none;
  position:relative;isolation:isolate;cursor:pointer;
  font-family:'Barlow',sans-serif;font-weight:600;font-size:.95rem;
  color:var(--white);background:transparent;border:0;
  padding:.85rem 1.9rem;border-radius:999px;
  transition:transform .3s cubic-bezier(.2,.7,.3,1), filter .3s;
  box-shadow:
    0 0 8px rgba(0,0,0,0.10),
    0 3px 10px rgba(0,0,0,0.35),
    inset 3px 3px .5px -3.5px rgba(255,255,255,0.55),
    inset -3px -3px .5px -3.5px rgba(255,255,255,0.55),
    inset 1px 1px 1px -.5px rgba(255,255,255,0.35),
    inset -1px -1px 1px -.5px rgba(255,255,255,0.35),
    inset 0 0 8px 7px rgba(255,255,255,0.06),
    0 0 14px rgba(204,0,0,0.22);
}
.btn-liquid::before{
  content:"";position:absolute;inset:0;z-index:-1;border-radius:inherit;
  background:rgba(255,255,255,0.05);
  backdrop-filter:blur(8px) saturate(150%);
  -webkit-backdrop-filter:blur(8px) saturate(150%);
}
/* ===================== HOVER AND PRESS - THE RULE FOR THE WHOLE SITE (locked wd-s8) =====
   >> EVERY :hover ON THIS SITE IS WRAPPED IN `@media (hover:hover) and (pointer:fine)`,
   AND EVERY HOVERABLE THING GETS A `:active` PRESS INSTEAD.

   A touchscreen has no hover - a finger is either on the glass or off it. Left unguarded, a
   phone applies the hover look ON THE TAP and LEAVES IT ON after the finger lifts, until
   something else is tapped. So a tapped button sits there enlarged and brightened while the
   visitor reads on. Nothing breaks and nothing slows down; it just looks stuck.

   `pointer:fine` is in there as well as `hover:hover` because some devices report a coarse
   pointer that can also hover. Requiring both means the effect is reserved for a real mouse.

   THE PRESS IS THE TOUCH EQUIVALENT and it is not optional - remove hover without adding it
   and a phone tap gives no feedback at all, which reads as a dead button. It dips IN rather
   than growing, so the finger never covers the thing that is moving.

   >> `:active` IS DECLARED AFTER THE HOVER BLOCK ON PURPOSE. Same specificity, so file order
   decides which wins while a mouse is held down on a hovered button. (Same trap as wd-s3.) */
@media (hover:hover) and (pointer:fine){
  .btn-liquid:hover{transform:scale(1.06);filter:brightness(1.12);}
}
/* faster going down than coming back up - a press should feel immediate, the release can
   spring. The .3s in the base rule handles the way back. */
.btn-liquid:active{transform:scale(.96);filter:brightness(.92);transition-duration:.09s;}
.btn-sm{padding:.6rem 1.3rem;font-size:.88rem;}

/* PRIMARY variant - for the hero CTA and any button sitting on near-black.
   The plain glass button disappears there: a frosted fill needs something
   behind it to catch. This keeps the glass edges but carries red mass. */
.btn-liquid.btn-primary{
  font-size:1.02rem;padding:.95rem 2.4rem;
  box-shadow:
    /* lit surface - this pair is what makes it read 3D (parent brand standard) */
    inset 0 1px 0 rgba(255,255,255,0.42),
    inset 0 -2px 0 rgba(0,0,0,0.34),
    /* liquid glass edge */
    inset 3px 3px .5px -3.5px rgba(255,255,255,0.80),
    inset -3px -3px .5px -3.5px rgba(255,255,255,0.55),
    inset 1px 1px 1px -.5px rgba(255,255,255,0.40),
    inset 0 0 12px 9px rgba(255,255,255,0.06),
    /* lift off the page */
    0 10px 30px rgba(0,0,0,0.60),
    0 0 34px rgba(204,0,0,0.40);
}
.btn-liquid.btn-primary::before{
  /* lighter at the top so the surface reads as lit from above, and translucent
     enough that the frost behind it still shows */
  background:linear-gradient(180deg, rgba(226,26,26,.60) 0%,
                                     rgba(178,0,0,.52) 52%,
                                     rgba(122,0,0,.50) 100%);
}
/* mouse-only, see the HOVER AND PRESS block above `.btn-liquid` */
@media (hover:hover) and (pointer:fine){
  .btn-liquid.btn-primary:hover{transform:scale(1.06);filter:brightness(1.16);}
}
.btn-liquid.btn-primary:active{transform:scale(.96);filter:brightness(.9);transition-duration:.09s;}

/* ---------- hero : must fit one screen while pinned ---------- */
.hero{
  position:relative;z-index:1;height:100vh;
  padding:5.4rem 1.5rem 1.6rem;
  display:flex;flex-direction:column;justify-content:center;
}
.hero-inner{max-width:1240px;margin:0 auto;width:100%;
  display:flex;flex-direction:column;align-items:center;min-height:0;gap:clamp(.7rem,2vh,1.4rem);}

.hero-copy{text-align:center;flex:0 0 auto;}
.kicker{
  font-family:'Barlow Condensed',sans-serif;font-weight:600;
  letter-spacing:.26em;text-transform:uppercase;
  /* larger + brighter than the original .78rem/--gray-dim (owner, wd-s3): this line is the
     CATEGORY LINE and carries the search phrase, so it should not read as fine print. */
  font-size:.88rem;color:var(--gray);margin-bottom:.7rem;
  /* >> margin-top BALANCES THE TWO GAPS AROUND THIS LINE (owner, wd-s3). Measured before:
     8.2px from the nav's bottom edge to this line's top, but 11.2px from this line to the
     h1 - so it sat visibly closer to the header. 3px of margin-top evens them up.
     >> MEASURED, NOT REASONED: margin here moves the line down 1:1. `.hero` is
     `justify-content:center`, so the obvious guess is that a taller block re-centres and
     eats half the shift - it does NOT, because at this viewport the hero content has no
     free space left to redistribute. 6px was tried first and overshot to 14.2px.
     Re-measure if you touch it rather than trusting the centring model. */
  margin-top:.1875rem;
}
/* >> SCOPED TO .hero-copy ON PURPOSE - do NOT loosen back to `.hero h1`.
   The hero SECTION also contains the demo-site mock, which has its own <h1>. As
   `.hero h1` this rule leaked UPPERCASE and a heavy text-over-imagery text-shadow
   into the mock headline, so "AC out? We're there today." rendered as ALL CAPS with
   a drop shadow - wider, heavier and taller than the mock is designed for. Every
   real page headline on this site sits inside .hero-copy, so scoping costs nothing
   and keeps the next embedded example safe too. */
.hero-copy h1{
  font-family:'Barlow Condensed',sans-serif;font-weight:800;
  font-size:clamp(1.9rem,4.7vw,3.7rem);line-height:.92;letter-spacing:.01em;
  text-transform:uppercase;
  text-shadow:0 3px 22px rgba(0,0,0,0.85);
}
.hero-copy h1 .accent{color:var(--red);text-shadow:0 3px 26px rgba(204,0,0,0.45);}
.hero .sub{
  margin:.8rem auto 0;max-width:46ch;color:var(--gray);
  font-size:.95rem;line-height:1.55;
}


/* The stat line under the animation. HIDDEN on desktop: the frame leaves about 14px
   below it there, so this would only fit by shrinking the frame. Shown on phones,
   where the leftover space below the frame is what it fills. See COPY.md. */
.hero-stat{
  display:block;margin:.55rem auto 0;max-width:70ch;text-align:center;
  font-size:.84rem;line-height:1.45;color:var(--gray);
  /* >> BALANCE THE TWO LINES. Left to wrap naturally, the first line ran nearly the full
     width and the second was three words - and a short isolated line READS bigger and
     brighter than a packed one, even at identical size. Owner spotted it as "media page
     looks larger". Nothing was larger; the line lengths were. */
  text-wrap:balance;
}
/* wd-s18: it lives in section 2 now, between the paragraph and the CTA, so it needs its own
   space above and below. In the hero it was pushed around by the leftover-height rules that
   are now scoped `.hero .hero-stat` and inert. */
.say .hero-stat{margin:1.4rem auto 1.6rem;}
/* >> ONE SENTENCE, TWO PLACES, EXACTLY ONE OF THEM SHOWING (owner, wd-s18). Laptop reads it
   in section 2; phone reads it in the hero. The pair of `display` rules here and in the
   760px block below are the whole mechanism - there is no third state, and both copies must
   never be visible at once.
   >> THE REASON THE TWO SURFACES DIVERGE IS MEASURED, NOT TASTE. Moving the line to section 2
   left the phone with 304px of dead space under the frame, a third of the screen, and the
   laptop with 26px at 1280 and 57px at 1440. The phone needs it back; the laptop has nowhere
   to put it, and putting it back there would also re-squeeze the frame, which is now a true
   1.60 at every laptop size for the first time since wd-s6.
   >> THE PHONE RULES BELOW WERE NEVER DELETED WHEN IT MOVED OUT - they were scoped
   `.hero .hero-stat` and left inert on purpose, so this is a `display` change and nothing
   else. If the owner wants it back on laptop later, delete this rule and its partner. */
.hero .hero-stat{display:none;}
/* >> wd-s27 - THE RED WAS DARKER THAN ITS OWN BACKGROUND, WHICH IS WHY IT READ AS BLUR
   RATHER THAN AS COLOUR. Measured on section 2's ground: #CC0000 scores 2.14 contrast, and
   brand red is also near-opposite the backdrop's cyan, so the two vibrate against each other
   instead of separating - the same clash that moved the inner page's turnaround line to white
   in wd-s25. Weight was already 800; the shadow pair is the house treatment for type on a
   picture, and this line sits on a 0.50 veil, not a heavy band, so it qualifies.
   >> A LIGHTER RED WAS TRIED FIRST AND WAS THE WRONG LEVER - IT BUYS CONTRAST BY SPENDING
      SATURATION,
      which is what "washed out" was: #FF5A5A drops saturation from 204 to 165. The fix that
      costs no colour is a TIGHT ACHROMATIC HALO - a zero-blur dark ring one pixel out on all
      four sides. It gives every letterform a neutral edge, so the red never touches the cyan
      and the vibration has nowhere to happen. Against that halo the red reads at 5.62 rather
      than the 3.37 it scores against the section, and it stays a saturated red. */
.hero-stat b{
  color:#FF2A2A;font-weight:800;
  text-shadow:
    0 1px 0 rgba(0,0,0,.95), 0 -1px 0 rgba(0,0,0,.95),
    1px 0 0 rgba(0,0,0,.95), -1px 0 0 rgba(0,0,0,.95),
    0 1px 6px rgba(0,0,0,.85);
}
.say .hero-stat{text-shadow:0 1px 8px rgba(0,0,0,.9), 0 1px 3px rgba(0,0,0,.7);}
.hero-stat-src{
  display:inline;margin-left:.5rem;font-size:.72rem;letter-spacing:.1em;
  text-transform:uppercase;color:var(--gray-dim);
}
/* the source sits on the same line on a laptop - a second line here costs frame height */
.hero-stat-src::before{content:"- ";}

/* ---------- the scene : sized box, workers hang off its edges ---------- */
.scene{
  position:relative;flex:0 1 auto;
  height:min(54vh,470px);aspect-ratio:16/10;margin:0 auto;
}

.easel{position:absolute;inset:0;}
.canvas-frame{
  position:absolute;inset:0;
  background:#ffffff;border-radius:10px;overflow:hidden;
  box-shadow:var(--shadow-lg), 0 0 0 1px rgba(255,255,255,0.08);
}

/* browser chrome - always visible so the shape reads as a WEBSITE */
.browser-bar{
  position:absolute;top:0;left:0;right:0;height:6.5%;z-index:4;
  background:#1b1c1f;display:flex;align-items:center;gap:5px;padding:0 2%;
}
.browser-bar i{width:6px;height:6px;border-radius:50%;background:#3a3c42;flex-shrink:0;}
.browser-bar i:first-child{background:#4a3033;}
.browser-bar span{flex:1;height:52%;margin-left:6%;border-radius:3px;background:#252629;}

/* sits OUTSIDE the canvas so it never covers the site being painted */
.sample-tag{
  position:absolute;right:0;top:calc(100% + 1.5rem);z-index:6;
  font-family:'Barlow Condensed',sans-serif;font-weight:600;
  font-size:.66rem;letter-spacing:.2em;text-transform:uppercase;
  color:var(--gray-dim);
  border:1px solid rgba(255,255,255,.12);border-radius:4px;padding:.22rem .6rem;
  opacity:0;
}

/* ---------- the mock site being painted ---------- */
.mock{
  position:absolute;top:6.5%;left:0;right:0;bottom:0;color:#14161a;
  font-family:'Barlow',sans-serif;display:flex;flex-direction:column;
}
/* each region is revealed independently, soft brushed edge */
.mock-region{
  --paint:0;
  -webkit-mask-image:linear-gradient(100deg,#000 0 0);
  mask-image:linear-gradient(100deg,#000 0 0);
  -webkit-mask-size:calc(var(--paint) * 118%) 100%;
  mask-size:calc(var(--paint) * 118%) 100%;
  -webkit-mask-repeat:no-repeat;
  mask-repeat:no-repeat;
  filter:blur(calc((1 - var(--paint)) * 5px));
  opacity:calc(.15 + var(--paint) * .85);
}
.mock-region[data-region="1"]{flex:0 0 15%;}
.mock-region[data-region="2"]{flex:0 0 45%;}
.mock-region[data-region="3"]{flex:0 0 27%;}
.mock-region[data-region="4"]{flex:0 0 13%;}

.mock-nav{
  height:100%;display:flex;align-items:center;justify-content:space-between;
  padding:0 4%;border-bottom:1px solid #e6e6e6;background:#fff;
}
.mock-logo{font-family:'Barlow Condensed',sans-serif;font-weight:800;
  letter-spacing:.08em;font-size:clamp(.7rem,1.7vw,1.05rem);color:#14161a;}
.mock-logo em{font-style:normal;color:#0a6cbf;}
.mock-navlinks{display:flex;gap:10px;}
.mock-navlinks i{display:block;width:34px;height:6px;border-radius:3px;background:#d8dbe0;}
.mock-cta{background:#0a6cbf;color:#fff;font-size:.62rem;font-weight:600;
  padding:.4rem .8rem;border-radius:4px;letter-spacing:.04em;}

.mock-hero{
  height:100%;padding:0 4%;display:flex;flex-direction:column;
  justify-content:center;gap:.5rem;
  background:linear-gradient(105deg,#eef4fa 0%,#dce9f5 100%);
}
.mock-eyebrow{font-size:.56rem;letter-spacing:.2em;color:#5d7a92;font-weight:600;}
.mock-hero h2{font-family:'Barlow Condensed',sans-serif;font-weight:800;
  font-size:clamp(1.3rem,4.2vw,2.7rem);line-height:.98;color:#0e2233;}
.mock-sub{font-size:clamp(.6rem,1.35vw,.85rem);color:#456;max-width:34ch;}
.mock-btn{align-self:flex-start;margin-top:.35rem;background:#0a6cbf;color:#fff;
  font-size:.68rem;font-weight:600;padding:.5rem 1.1rem;border-radius:5px;}

.mock-cards{height:100%;display:flex;gap:3%;padding:4%;background:#fff;}
.mock-card{
  flex:1;border:1px solid #e6e6e6;border-radius:6px;padding:4%;
  display:flex;flex-direction:column;gap:7px;
  box-shadow:0 2px 10px rgba(0,0,0,.05);
}
.mock-card b{font-family:'Barlow Condensed',sans-serif;font-size:clamp(.62rem,1.5vw,.95rem);
  letter-spacing:.04em;color:#14161a;}
.mock-card span{display:block;height:5px;border-radius:3px;background:#e4e7eb;}
.mock-card span:last-child{width:64%;}

.mock-foot{
  height:100%;display:flex;align-items:center;justify-content:space-between;
  padding:0 4%;background:#0e2233;color:#fff;
}
.stars{color:#ffc23c;font-size:clamp(.55rem,1.3vw,.8rem);letter-spacing:.04em;}
.mock-phone{font-family:'Barlow Condensed',sans-serif;font-weight:700;
  font-size:clamp(.7rem,1.8vw,1.15rem);letter-spacing:.06em;}

/* ---------- workers (alpha-cut Leonardo renders) ---------- */
.worker{position:absolute;z-index:5;pointer-events:none;
  filter:drop-shadow(0 14px 22px rgba(0,0,0,.65));}
.worker img{height:100%;width:auto;display:block;}

/* all four reach UP, so each stands just below the band he paints */
.w1{height:54%;left:-7%;top:-4%;}      /* nav strip   */
.w2{height:50%;right:-7%;top:12%;}     /* hero band   */
.w3{height:44%;left:-6.5%;top:44%;}    /* cards       */
.w4{height:42%;right:-6%;bottom:-2%;}  /* footer      */

.scene-hint{
  position:absolute;left:0;right:0;top:calc(100% + 4.6rem);
  text-align:center;color:var(--gray-dim);
  font-family:'Barlow Condensed',sans-serif;letter-spacing:.24em;
  text-transform:uppercase;font-size:.72rem;
}

/* ---------- after-hero spacer ---------- */
.after{position:relative;z-index:1;min-height:80vh;display:grid;place-items:center;padding:4rem 1.5rem;}
.panel{max-width:560px;padding:2.4rem;text-align:center;}
.panel h3{font-family:'Barlow Condensed',sans-serif;font-weight:700;
  font-size:1.7rem;letter-spacing:.06em;text-transform:uppercase;margin-bottom:.7rem;}
.panel p{color:var(--gray);line-height:1.6;}

/* =========================================================
   SECTION 2 - THE PANEL UNDER THE ANIMATION (wd-s4)
   Copy is LOCKED in COPY.md. The animation above says everything
   without a word; this is where it gets said.
   ========================================================= */

/* >> SITS HIGH ON PURPOSE (wd-s4). Nothing follows the frame now, so this is the first
   thing that speaks - it should arrive the moment the animation lets go of the scroll,
   not after a screen of empty dark. The block still stands on its own (it is taller than
   its contents), but the top padding is small so the headline is what comes up from the
   bottom edge, not the gap above it.
   It is never visible on landing: the hero is pinned and fills the screen until the
   animation finishes, so there is nothing to hide from. */
/* ================= SECTION BANDS - THE PAGE'S SEPARATION (LOCKED wd-s8) =================
   >> HERO, PORTFOLIO AND PRICE CARRY NO BACKGROUND OF ANY KIND. Not a colour, not a
   gradient, not a veil. The other three - this section, How it works and Contact - carry a
   BLACK veil at 0.50. That alternation is the whole scheme, and the owner locked it.

   >> WHY BARE MATTERS SO MUCH ON THOSE THREE. The red bloom is not a property of the hero.
   It is `body::before` at the top of this file: ONE FIXED layer behind the entire page, two
   red radial washes, which every section shows through because no section paints over it.
   Being `fixed`, it stays put on screen while the page scrolls, so a bare section reads as
   travelling across a light source that never moves. Put ANY background on one of those
   three and that effect is gone on that band. This is the constraint to protect.

   >> DARK VEIL, AND A REAL STEP - NOT A SUBTLE ONE. Tried and rejected, in this order:
   a 2% WHITE lift (too quiet to read as separation at all, and it drifts toward the same
   pale film the glass panels use, so it fights them); a SOLID #0e0e11 band (opaque, so it
   covered the fixed glow and quietly stripped the red wash off the whole page below the
   hero); a red-glow band copied onto Portfolio and Price (doubled the red but took away the
   thing the owner actually wanted there); and 0.90 (flat black, kills the glow's spill into
   the band). On a page whose signature IS the glow, the contrast that reads is a glowing
   band against a genuinely darker one.
   ======================================================================================= */
/* >> wd-s27 - `.how` MOVED 0.50 -> 0.02 (owner). It read 22 against Portfolio's 44, which is
   a band, not a separation. Now set to land at 35: 43 x 0.81 = 34.8. This overrides the
   wd-s8 alternation for this one section - `.say` and `.contact` are untouched and still
   carry 0.50 / 0.80. */
.how,
.faq    {background:rgba(0,0,0,0.19);}
.contact{background:rgba(0,0,0,0.50);}

/* >> THIS SECTION CANNOT TAKE A PLAIN BACKGROUND - IT IS THE ONE INSET SECTION ON THE PAGE.
   `.say` carries `max-width:1000px;margin:0 auto` on the SECTION itself (it used to be what
   made the hairlines content-width), so a background on it stops at 1000px and reads as a
   floating box rather than a band. The veil is painted by a full-bleed layer BEHIND the
   section instead, leaving the section box alone.
   >> IT ONLY LOOKED WRONG ON ONE SIDE, which is what made it hard to see: the glow is
   brightest in the upper RIGHT, so the box's right edge had something to contrast against
   while its left edge was black on black. The asymmetry was in the GLOW, not in the box.
   Owner caught it in one look. */
/* >> THE FULL BLEED IS A SHADOW SPREAD, NOT A 100vw BOX (fixed wd-s22 sweep).
   It used to be `left:50%;transform:translateX(-50%);width:100vw`, with a comment saying
   `body{overflow-x:hidden}` absorbed the difference. It did not. **`vw` INCLUDES the
   scrollbar**, so the layer came out about 15px wider than the content area and hung half
   of that off each side - and the page scrolled 8px sideways at every window width. Found
   by hiding one section at a time until the sideways scroll went to zero; nothing about
   the layer looked wrong on screen, which is why it survived this long.
   `body{overflow-x:hidden}` does not save you here, and `clip` was tried and did not
   either - the fix has to be that no box is wider than the page in the first place.

   >> A BOX SHADOW NEVER CREATES SCROLLABLE OVERFLOW, WHICH IS THE WHOLE TRICK. The layer
   is now exactly the section's width, and the veil either side of it is a spread shadow in
   the same colour. `clip-path` then trims the shadow's top and bottom, since a spread goes
   in all four directions and only the horizontal reach is wanted. No viewport units, so
   nothing to get wrong on a machine whose scrollbar is a different width. */
.say::before{
  content:"";position:absolute;top:0;bottom:0;left:0;right:0;
  background:rgba(0,0,0,0.50);
  box-shadow:0 0 0 100vmax rgba(0,0,0,0.50);
  clip-path:inset(0 -100vmax);
  z-index:-1;pointer-events:none;
}

.say{
  position:relative;z-index:1;
  min-height:62vh;
  display:grid;place-items:center;
  /* equal top and bottom, which used to be about the two hairlines and now just keeps the
     text centred in the band */
  padding:2.5rem 1.5rem;
  /* >> THE HAIRLINES ARE GONE (owner, wd-s8). They were `.5px solid #1a1a1a` top and bottom,
     the 7 Star section-hairline standard, added wd-s6 and matched to the agency site wd-s7.
     The BAND now does the separating, and the two marks fought each other: the hairline stops
     at 1000px while the band runs edge to edge, so the line ended short inside a band that
     did not.
     >> THIS DOES NOT CONTRADICT THE 7 STAR HAIRLINE STANDARD. That standard says what a
     section rule must LOOK like IF one is used - flat near-black, half a pixel, content-width
     - not that every section must carry one. If a hairline is ever wanted here again, it is
     `.5px solid #1a1a1a` and nothing else.
     >> `max-width:1000px` STAYS, and it is now load-bearing for a different reason: it is why
     this section needs the full-bleed `::before` above to paint its band. */
  max-width:1000px;margin:0 auto;
}
.say-inner{max-width:44rem;text-align:center;}

/* NOT the uppercase .panel h3 treatment - this is a spoken sentence with
   punctuation in it, and caps make it read as shouting. */
.say h2{
  font-family:'Barlow Condensed',sans-serif;font-weight:700;
  font-size:clamp(2rem,4.6vw,3.2rem);line-height:1.06;letter-spacing:.01em;
  margin-bottom:1.5rem;
}
/* second sentence red, same move as the hero headline - keeps the two
   screens feeling like one page */
.say h2 .accent{color:var(--red);text-shadow:0 3px 26px rgba(204,0,0,0.35);}

.say p{
  color:var(--gray);line-height:1.75;
  font-size:clamp(1rem,1.35vw,1.14rem);
  max-width:40rem;margin:0 auto;
}

/* the button sits on a small glass bar, exactly as it does under the animation.
   A frosted button needs a surface behind it to catch or it goes flat on
   near-black - the bar IS that surface. Same classes on purpose. */
.say-cta{margin-top:2.6rem;display:flex;justify-content:center;}
.say-cta-bar{display:inline-flex;padding:.55rem;border-radius:999px;}

/* =========================================================
   SECTION 3 - EXAMPLES (wd-s4)
   >> PLACEMENT TEST. The words here are a DRAFT in COPY.md and are
   not approved. Built so the owner can judge whether ONE example
   carries the section or a second one needs building.
   ========================================================= */

/* >> THE NAV CLEARANCE IS NOT ON THIS SECTION'S PADDING, AND THAT IS THE FIX FOR THE
   PIN/ANCHOR MISMATCH (found wd-s10 by reading the CSS, fixed wd-s17 once a second card
   existed and the deck actually turned on).

   THE BUG: tapping `Portfolio` stops the SECTION's top at the top of the screen, but the
   deck pins at the top of `.work-stage`, which sat one section-padding lower - 7rem on a
   laptop, 8rem on a phone. So the visitor landed with the deck low and it snapped into
   place the moment they started scrolling.

   THE FIX: the top padding goes to 0 and `.work-stage` carries the clearance on its own,
   which it already did for the pinned state (6.5rem, the same number the stats page pins
   to). One number, one place, and the anchor and the pin now land on the same line.

   >> `.is-single` PUTS THE PADDING BACK, because with one card there is no pinning and no
   sticky stage - the section is an ordinary block again and needs its own clearance.
   Do not merge these two rules back together. */
/* >> THE DECK IS FULL BLEED - NO HORIZONTAL PADDING ON THIS SECTION (wd-s23, owner:
   "gch rolodex cards scroll from edge to edge, as if passing through side of phone. we are
   boxing in this same feature in web studio?").

   IT WAS `padding:0 1.5rem 7rem`. `.work-stage` carries `overflow:hidden` - that is what
   clips the neighbouring cards - so a horizontal padding on the SECTION moved the clip line
   1.5rem in from each screen edge. The cards were being cut on an invisible box instead of
   running off the phone.

   >> IT WAS ALWAYS WRONG AND IT WAS ALWAYS INVISIBLE. On near-black the clipped edge was
      black meeting black. It only announced itself once a lit backdrop put picture on both
      sides of the cut, which is also why the owner reads it as part of the "shading" under
      the deck - the neighbour card's own link and helper line are chopped mid-sentence at
      that line and read as a smudge rather than as a card continuing off-screen.

   >> BUILT FROM THE LIVE ONE: this is exactly what GCH's beer deck does. `.beer{padding:0}`
      with `.beer-head{padding:0 1.2rem}` - the section is edge to edge and only the TEXT
      takes an inset. Copied, not re-derived.
      Reference: sites/example/gch-gangnam-chimaek-house/site/assets/css/style.css

   Revert = put `1.5rem` back here and drop the two padding lines below. */
.work{position:relative;z-index:1;padding:0 0 7rem;}
.work.is-single{padding-top:7rem;}
/* the inset the section used to carry, moved onto the two children that are TEXT and must
   not touch the screen edge. The rail is deliberately not in this list - it is the thing
   that has to bleed. */
.work-head{padding-left:1.5rem;padding-right:1.5rem;}
.work .mq-strip{margin-left:1.5rem;margin-right:1.5rem;}

/* ==================================================================================
   THE EXAMPLES DECK (wd-s7) - cards turn sideways while the section is pinned.
   Trialled in `_lab/deck.html`; the reasoning for every value is in DESIGN.md.

   >> FOUR THINGS MUST SURVIVE ANY EDIT HERE, all learned the hard way:
   1. NOTHING MOVES VERTICALLY. The owner ruled it out explicitly. That is why the card is
      anchored to the TOP of the rail rather than centred in it, why `transform-origin` is
      `center top` (scaling about the centre pushes a card's top edge DOWN), and why there
      are no spacers around the track.
   2. THE RAIL IS SIZED TO THE CARD, not given the leftover space. As `flex:1` it swallowed
      the rest of the pinned screen and left a screen of dead space under the caption - worst
      on a phone, where the card is small against a tall screen.
   3. CARD WIDTH IS A BUDGET. Everything has to fit inside ONE pinned screen, so the width
      carries a height cap. On a wide but short window a 50vw card is taller than the room
      available and the caption falls off the bottom.
   4. 6.5rem OF NAV CLEARANCE - the same number `.stat` pins to on the stats page, so the
      site's two pinned sections stop on the same line.
   ================================================================================== */
.work-track{position:relative;}
.work-stage{
  position:sticky;top:0;height:100svh;
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  padding-top:6.5rem;
  overflow:hidden;
  /* declared once; the rail height is derived from it */
  /* >> 21rem -> 26rem, wd-s18. THIS NUMBER IS THE HEIGHT BUDGET FOR EVERYTHING IN THE STAGE
     THAT IS NOT THE CARD - the nav clearance, the heading, the helper line, the rail's own
     padding - and the marquee strip has just been added to that list. Leave it at 21rem and
     on a shorter laptop the card is sized as if the strip were not there, the stage overflows
     its own `overflow:hidden`, and the strip is simply cut off the bottom of the screen.
     >> IT DOES NOTHING ON A 900px-TALL LAPTOP, WHICH IS EXACTLY WHY IT IS EASY TO MISS. At
     1440x900 the `50vw` term wins and this branch never applies; it only bites on a short
     window, which is not the machine this gets built on. Checked at 1024x800 and 1440x800. */
  --cardw:min(62rem,50vw,calc((100svh - 26rem) * 1.6));
}
/* >> 100svh NOT 100vh - on a phone `vh` is the TALLEST the viewport gets, so a pinned stage
   sized in vh is taller than the screen until the URL bar collapses. Headless browsers have
   no URL bar, so the bug is invisible to screenshots. */

/* wd-s27 - PORTFOLIO-ONLY RETINT, RIGHT SIDE OF THE DIAGONAL ONLY.
   >> IT IS A MULTIPLY, NOT AN OVERLAY, AND THAT IS THE WHOLE POINT. Blue is already at the
      ceiling once the site's 1.6 has been applied, so a light-coloured overlay can only add
      red and green and the picture goes yellow. `backdrop-filter:brightness` scales all
      three channels, so the hue holds.
   The mask starts at the shaft and ramps to the bottom-right corner, so the left side is
   mathematically untouched. Lands 44 left / 44 right. Delete this rule to revert. */
@media (orientation:landscape){
  html:not(.bg-off) .work-stage::before{
    content:"";position:absolute;inset:0;pointer-events:none;z-index:0;
    backdrop-filter:brightness(1.45);
    -webkit-backdrop-filter:brightness(1.45);
    -webkit-mask-image:linear-gradient(148deg, transparent 40%, #000 100%);
            mask-image:linear-gradient(148deg, transparent 40%, #000 100%);
  }
  html:not(.bg-off) .work-head,
  html:not(.bg-off) .work-rail{ position:relative; z-index:1; }
}

.work-head{text-align:center;max-width:74rem;margin:0 auto 2.2rem;flex:0 0 auto;}

/* height = the frame (card width / 1.6, it is 16:10) + the link line and the caption */
.work-rail{position:relative;width:100%;flex:0 0 auto;
  height:calc(var(--cardw) / 1.6 + 6rem);}

.work-item{
  position:absolute;top:0;left:50%;
  width:var(--cardw);
  transform-origin:center top;
  will-change:transform,filter,opacity;
}

/* ---------- SINGLE EXAMPLE: no deck, no pinning, no scroll track ----------
   Added by the script when it finds fewer than two cards. One card cannot rotate, and
   spending a pinned screen on a card that never moves is worse than the plain section it
   replaced. This puts the section back exactly as it was. */
.work.is-single .work-track{height:auto !important;}
.work.is-single .work-stage{position:static;height:auto;padding-top:0;overflow:visible;}
.work.is-single .work-rail{height:auto;}
.work.is-single .work-item{position:relative;top:auto;left:auto;transform:none;
  width:min(100%,62rem);margin:0 auto;filter:none;opacity:1;}

/* >> DO NOT PUT `text-align:center` ON THIS WRAPPER. It was here, and it CASCADED STRAIGHT
   INTO THE MOCK - Ridgeline's hero copy is left-aligned by its own CSS, but text-align
   inherits, and the mock does not restate it on every element. Result: the same mock read
   LEFT inside the animation and CENTRED inside this card, and it looked like two different
   designs. Owner caught it.
   Same failure as the wd-s3 `.hero h1` leak: page styling reaching into the embedded site.
   Centre the section's OWN text instead, one element at a time. */
.work-inner{max-width:74rem;margin:0 auto;}
.work h2,.work-sub,
/* >> THE EXAMPLE FRAME IS A LINK (wd-s5). Until now this section promised a website and
   handed over a picture - the one thing on the page not yet telling the truth. The whole
   card is the hit area, with a lift on hover so it reads as clickable rather than as a
   screenshot. See DESIGN.md decision 52 for why it opens in a NEW TAB.
   >> ITS HREF CURRENTLY POINTS AT LOCALHOST - swap it for the deployed
   `/example/ridgeline/` before this page ships. */
.work-link{display:block;text-decoration:none;color:inherit;}
.work-link .work-card{
  transition:box-shadow .2s, transform .3s cubic-bezier(.2,.7,.3,1);
  /* grows DOWNWARD only. Scaling about the centre would push the card's top edge up, and
     nothing in this section is allowed to move vertically (owner, wd-s7). Same reason
     `.work-item` uses this origin. */
  transform-origin:center top;
}
/* >> THE wd-s7 TRANSFORM BAN APPLIES TO `.work-item`, NOT TO THIS (clarified wd-s8).
   The deck script rewrites the ITEM's transform every frame, so a hover transform there is
   overwritten instantly or fights the rotation. `.work-card` is the item's CHILD and the
   script never touches it, so its own transform composes on top of the rotation cleanly and
   survives the deck being switched on by a second example.
   >> DO NOT MOVE THIS SCALE UP ONTO `.work-item`. That is the rule wd-s7 wrote down.
   The grow echoes the glass buttons (owner, wd-s8) but at 1.03, not their 1.06 - the same
   percentage on a card this size is a much bigger movement on screen. */
/* mouse-only, see the HOVER AND PRESS block above `.btn-liquid` */
@media (hover:hover) and (pointer:fine){
  .work-link:hover .work-card{
    transform:scale(1.03);
    box-shadow:0 46px 96px rgba(0,0,0,.82),0 0 0 1px rgba(255,255,255,0.30);
  }
}
/* the touch press. It dips instead of growing - on a phone the finger is ON the card, so a
   grow would happen underneath it, while a dip is visible all round the edge. */
.work-link:active .work-card{
  transform:scale(.985);
  box-shadow:0 46px 96px rgba(0,0,0,.82),0 0 0 1px rgba(255,255,255,0.30);
  transition-duration:.09s;
}
/* >> THE HOUSE QUIET LINK. Shared by the examples card and section 4's hand-off to the price
   (wd-s10) - ONE set of declarations, so a second variant cannot appear on the page. Quiet at
   rest, underline only on hover, no colour of its own: it moves the visitor along, it does not
   compete with a red button. A red underline was tried on section 4's and removed - it invented
   a third link look on a page that already had this one. */
.work-open,
.how-next a,
.faq-item a,
.start-reply a,
.nav-domain,
.cf-done a,
.cf-error a{
  font-size:.95rem;font-weight:600;color:#fff;opacity:.72;text-decoration:none;
}
.work-open{display:block;text-align:center;margin-top:1.1rem;}
.how-next a{display:inline-block;}
/* >> THE TWO CONTACT-FORM LINKS SIT INSIDE A SENTENCE, so they take the size and the colour
   of the sentence rather than the standard's own .95rem/white (wd-s12). Everything that makes
   the house link what it is - the weight, the .72 rest opacity, no underline until hover or
   press - is inherited from the shared rule above. **Size is the only thing that HAS to
   differ:** a .95rem link mid-way through a 1rem line renders visibly smaller than the words
   either side of it, which reads as a mistake. The error line's link keeps its parent's red
   for the same reason - a white word inside a red sentence looks like a rendering fault, not
   a link. Recorded as a scoped exemption in DESIGN.md, per the standard's own rule. */
.faq-item a,.start-reply a,.cf-done a,.cf-error a{font-size:inherit;}
/* the reply line's email link sits inside its sentence, so it takes the sentence's colour as
   well as its size - a white word inside a #c9c9c9 line reads as a different setting */
.start-reply a{color:inherit;}
.cf-error a{color:inherit;}
/* mouse-only, see the HOVER AND PRESS block above `.btn-liquid` */
@media (hover:hover) and (pointer:fine){
  .work-link:hover .work-open,
  .how-next a:hover,
  .faq-item a:hover,
  .start-reply a:hover,
  .nav-domain:hover,
  .cf-done a:hover,
  .cf-error a:hover{opacity:1;text-decoration:underline;}
}
.work-link:active .work-open,
.how-next a:active,
.faq-item a:active,
.start-reply a:active,
.cf-done a:active,
.cf-error a:active{opacity:1;text-decoration:underline;}

.work-tag{text-align:center;}

.work h2{
  font-family:'Barlow Condensed',sans-serif;font-weight:700;
  font-size:clamp(1.9rem,3.8vw,2.7rem);line-height:1.1;margin-bottom:.7rem;
}
/* wd-s10: was 2.8rem. The gap under the sub-line is carried by `.work-head`'s own bottom
   margin, so this one stacked on top of it and pushed the card and its caption down the
   screen. Zero here, the head owns the spacing. */
.work-sub{color:var(--gray-dim);font-size:.98rem;margin-bottom:0;}

/* the card that holds a live mock. `container-type` is what lets the mock
   size itself off THIS box instead of the window - same approach the
   animation frame uses, so one example looks right at any window size. */
.work-card{
  position:relative;margin:0 auto;
  /* the width lives on `.work-item` now (the deck sizes it); the card fills it */
  width:100%;
  aspect-ratio:16/10;
  /* >> BACKGROUND MUST BE #0a0a0a, NOT WHITE. Same rule as the animation frame, and it is
     in DESIGN.md for the same reason: a white background under rounded corners leaks white
     SLIVERS at the four corners, because the clip is anti-aliased and the pixels that get
     partially clipped show whatever is behind. Dark-on-dark hides it. Radius matched to the
     animation frame at 12px too - it was 14px, which is a visible difference when the two
     frames appear one screen apart. */
  border-radius:12px;overflow:hidden;background:#0a0a0a;
  box-shadow:var(--shadow-lg),0 0 0 1px rgba(255,255,255,0.09);
  container-type:inline-size;
}
.work-card .rl{font-size:clamp(7px,1.58cqw,16px);}

/* >> POSITIONED with top:7%, NOT padding-top:7%. A percentage padding is measured
   against the box's WIDTH, never its height - so padding pushed the mock down 69px
   inside a box only 43px shorter, which left a white strip under the browser bar AND
   squeezed the mock until its bottom row of cards was cut off. Same rule the
   animation frame uses (.seq-body). */
.work-scroll{position:absolute;top:7%;left:0;right:0;bottom:0;isolation:isolate;
  /* same hairline fix as the animation frame - the mock is white and square-cornered,
     so it peeks past the card's rounded clip unless it is rounded too. Match .work-card. */
  overflow:hidden;border-bottom-left-radius:12px;border-bottom-right-radius:12px;}

/* The picture of the example site inside a card (wd-s17). Anchored to the TOP, never
   centred - a website starts at its top, and centring would crop the hero from both ends
   and show a band out of the middle of the page.

   >> BOTH SHOTS ARE TAKEN AT THIS BOX'S OWN ASPECT RATIO, AND THAT IS NOT A NICETY - IT IS
   THE FIX FOR A REAL BUG (owner caught it in Firefox, wd-s17). The box is 1.7204:1 -
   the card is 16:10 and the browser bar takes the top 7%, so 1.6/0.93. The shots were
   briefly taken at 16:9 (1.778) instead, which leaves 3% of width that `cover` HAS to throw
   away - and how much each engine throws away, and from which side, is its own business.
   Firefox cut a slice off the left of the GCH shot and took the restaurant's whole logo and
   nav rail with it; every other browser trimmed the sliver evenly and it was invisible.
   >> SHOOT AT THE BOX'S RATIO AND THERE IS NOTHING LEFT TO CROP, so no engine can disagree.
   Capture 1920x1116 at double density, save 2000x1162.
   (The first GCH shot never showed this because it happened to be taken at 1100x640, which
   is 1.72 - the right ratio by luck rather than by intent.) */
.work-shot{display:block;width:100%;height:100%;object-fit:cover;object-position:center top;}

/* honesty label - Ridgeline is a demo we built, never a client.
   >> NO LONGER UPPERCASE OR TRACKED (wd-s7). It used to be four words, which is a LABEL, and
   uppercase with .22em tracking is how a label is set. The owner's new line is a full sentence
   with an invitation in it - at that length, caps plus heavy tracking is close to unreadable on
   a phone and reads as shouting on a laptop. Sentence case, normal tracking, same muted colour.
   If it ever goes back to a few words, the old treatment is the right one again. */
.work-tag{
  display:block;margin-top:1.4rem;text-align:center;
  font-size:.9rem;line-height:1.5;color:var(--gray-dim);
}

/* ---------- mobile ---------- */
@media (max-width:820px){
  /* >> THE NAV LINKS COME BACK ON A SECOND ROW INSIDE THE BAR (owner, wd-s6).
     They were hidden on a phone, which left the page with no way to reach Portfolio,
     How it works or Pricing at all. Same pattern Ridgeline already uses: let the bar
     wrap, then give the links their own full-width row under the logo and button.
     It also makes the bar taller, which eats some of the dead space under it. */
  .nav{flex-wrap:wrap;gap:.6rem;}
  /* wd-s34: five links need 345px against a 271-315px row, so the home page's longest
     label goes short on the stacked nav. `nowrap` (base rule) keeps each label one line. */
  .nav-full{display:none;}
  .nav-short{display:inline;}
  .nav-links{
    display:flex;order:3;flex-basis:100%;justify-content:space-between;
    gap:.5rem;font-size:.86rem;
  }
  .nav-wordmark{font-size:16px;letter-spacing:.12em;}
  .nav-logo img{width:34px;}
  /* >> HERO CONTENT SITS UP UNDER THE NAV ON MOBILE (wd-s4, owner).
     On desktop `.hero` centres its content in a full screen and that reads well, because
     the frame is tall enough to nearly fill it. On a phone the frame is limited by WIDTH,
     so it comes out short - the content only fills about 40% of a tall screen, and
     centring splits all that leftover space evenly, which dumped roughly 250px of dark
     nothing between the nav and the first line of text.
     THE TOTAL EMPTY SPACE IS FIXED - all we get to choose is where it goes. Centring in a
     shortened box was tried and it takes an absurd amount of bottom padding to make a real
     difference, so: start the content at the top and let the leftover fall BELOW the frame,
     where it reads as the page continuing rather than as a hole. Top padding clears the
     fixed nav and leaves about 45px of air under it.
     Flexbox alignment and padding - nothing browser-specific, so this behaves the same
     everywhere. */
  /* >> TRIED wd-s6 AND REVERTED: shrinking the hero to its content height does NOT pull
     section 2 up into the empty space below the frame. `pin:true` reserves the full 2050px
     of scroll distance in a spacer, so whatever sits under the pinned hero is that spacer,
     not the next section - the void just changes owner and looks identical. Getting section
     2 on screen during the animation would mean pinning only the frame or shortening the
     scroll distance, both of which are surgery on the locked sequence. Do not retry. */
  /* >> THE TEXT AND THE FRAME ARE CENTRED AS ONE GROUP, PHONE ONLY (owner, wd-s6).
     This replaces the wd-s4 top-aligned version. It works now where it did not then,
     because some of the leftover has already gone into the text-to-frame gap above, so
     centring no longer has ~250px to dump above the first line. Top padding is cut to
     just clear the fixed nav and let the centring do the rest - leaving it at 7.4rem
     would push down twice. Desktop is a separate rule and is untouched. */
  /* >> 7.4rem OF TOP PADDING IS NOT SPARE SPACE - IT IS THE NAV'S CLEARANCE (owner, wd-s6).
     It was cut to 6.2 to fund bigger gaps lower down, and the kicker ended up under the
     frosted bar. The bar is FIXED, so it does not push content down - only this padding
     keeps the first line clear of it. Do not spend it again. */
  /* >> `svh`, NOT `vh` (wd-s6, from the owner's real-phone screenshots).
     On a phone `100vh` means the viewport with the URL bar HIDDEN, so while the bar is
     showing, the hero's layout box is taller than anything you can see. Everything
     balanced inside it then sits low on the real screen - more space above the stat than
     below, which is exactly what showed up in Chrome and Firefox on two phones while the
     measurements here said it was even. `svh` is the SMALL viewport - the one that
     accounts for the bar - so the layout matches what is actually on screen.
     Ridgeline already used svh; this page never got the same treatment.
     `dvh` was not used on purpose: it changes as the bar hides on scroll, and this hero
     is pinned during the animation, so a height that moves would shift the whole sequence. */
  .hero{padding:9.35rem 1.2rem 1rem;justify-content:flex-start;height:100svh;}
  /* >> AUTO MARGINS, NOT A FIXED ONE. In a flex column, `auto` top AND bottom margins
     absorb the leftover space equally, so the stat stays centred between the frame and
     the bottom of the screen on ANY phone height. A fixed margin balanced at 780 tall
     and drifted badly at 852 - taller screens simply have more leftover, and all of it
     was landing below the stat. `flex:1` on .hero-inner is what gives it space to split. */
  /* the phone half of the pair - see the note beside `.hero .hero-stat{display:none}` above.
     `display` has to be restated here because the base rule is the same specificity and sits
     earlier in the file, so file order would otherwise keep it hidden. */
  .say .hero-stat{display:none;}
  .hero .hero-stat{display:block;margin:auto auto;max-width:34ch;font-size:.88rem;
    /* the auto margins balance the BOXES; a paragraph carries more leading above its
       first line than below its last, so the visible text sat about 20px low. This
       nudges the ink into balance without changing the layout. */
    transform:translateY(-20px);}
  /* on a phone the source drops to its own line - there is width to spare here and
     the height is free, unlike on a laptop */
  .hero-stat-src{display:block;margin:.5rem 0 0;}
  .hero-stat-src::before{content:"";}
  /* >> THE GAP BETWEEN THE TEXT AND THE FRAME, PHONE ONLY (owner, wd-s6).
     Desktop keeps `clamp(.7rem,2vh,1.4rem)` - untouched. On a phone the leftover space is
     fixed, and all we choose is where it goes; putting some of it here makes it read as
     deliberate spacing rather than as a hole under the frame. Step one of two - the group
     gets centred as a whole next. */
  /* >> THE FRAME AND THE STAT BOTH SIT LOWER (owner, wd-s6). Measured on a real 390px
     phone there were 133px of genuine spare below everything - my earlier reading of
     "no room" was taken at 500px wide, which is the narrowest window Chrome will open,
     and at that width the frame is much bigger and eats the space. Measure phones in a
     fixed-size frame, never by resizing the browser. */
  .hero-inner{gap:3.15rem;flex:1 1 auto;}
  .say{min-height:auto;padding:4.5rem 1.2rem 3.5rem;}
  /* wd-s10, owner: bottom matched to the top (8rem) so the band is even, AND the section is
     held to a full screen so the NEXT section's heading cannot peek in at the bottom while
     you are looking at this one. `min-height` rather than a bigger padding because it is
     self-adjusting - the next heading always lands at one screen plus its own 8rem of top
     padding, on any phone height, and it stays right when a second example card grows the
     section. 100svh not 100vh - see the note on the pinned stage. */
  /* >> PHONE CARRIES THE SAME PIN/ANCHOR FIX AS THE LAPTOP (wd-s17). The clearance was on
     this padding, so the anchor and the deck's pin landed 8rem apart here too - and the
     phone number is bigger, so the snap was worse. Padding to 0 for the deck, and
     `.work-stage` below takes the 8rem. `.is-single` puts it back, because with one card
     there is no sticky stage to carry it. */
  .work{padding-top:0;padding-bottom:8rem;min-height:100svh;}
  .work.is-single{padding-top:8rem;}
  /* >> THE PHONE DECK VALUES, PORTED FROM `_lab/deck.css` (wd-s17) - the owner has already
     seen and approved these on his phone in the lab, so they are copied, not re-derived.
     >> `--cardw` IS 86vw HERE AND THAT IS THE WHOLE POINT. The desktop value is
     `min(62rem, 50vw, ...)`, and 50vw on a phone is a card about 200px wide - a website
     rendered at thumbnail size. It has never been seen, because `.is-single` overrode the
     width while there was only one card. At 86vw the card is big and its neighbours bleed
     off both edges, which is what makes it read as a deck.
     >> THE THIRD LAB RULE - `.deck-track{height:260vh}` - IS DELIBERATELY NOT PORTED. The
     track height here is set by the script as one screen per card, so the lab's fixed
     figure would fight it. */
  .work-stage{--cardw:86vw;padding-top:8rem;}
  .work-head{margin-bottom:.9rem;}
  /* >> PHONE ONLY - BALANCE THE GROUP (owner, wd-s18). The laptop is right as it is: there
     the card is tall and only about 50px of height is left over. A phone card is 86vw, so it
     is far shorter than the screen and roughly 225px was left over - and with the strip's
     auto top margin taking all of it, every one of those pixels pooled into ONE gap between
     the closing line and the strip. It read as a hole.
     >> THE LEFTOVER HEIGHT IS SHARED OUT BY THREE AUTO MARGINS: above the TEXT, below the
     card, and above the strip. Measured at 390x844 that is about 75px each instead of one
     gap of 225.
     >> AND THE FIRST OF THOSE THREE SITS ON `.work-head`, NOT ON `.work-rail`. Putting it on
     the rail was tried and it is the wrong place: it opens the gap BETWEEN the helper line
     and the top of the thumbnail, which pulls the text away from the picture it introduces
     and reads as three floating pieces. The owner's words: "moved out of seq." The heading
     and its helper line have to stay tight on top of the card - that pairing is not spare
     space, it is what makes them read as one thing.
     >> AND NOTHING PUSHES THE HEADING DOWN, BECAUSE EVERY SECTION HAS TO PIN TO THE SAME
     PLACE (owner, wd-s18). A nav link is an anchor jump, so `How it works`, `Pricing`,
     `Contact` and `Portfolio` all land at their own section top - and the heading then sits
     wherever that section's top padding puts it. Measured on a 390x844 phone with the nav
     bottom at 95px: how / pricing / contact all put their h2 at 126px, a 31px gap under the
     bar. Portfolio was at 204px - a 109px gap - because an auto margin was pushing the text
     down to close a hole further down the section. Two right-looking sections and one that
     drops, on the same tap. The hole is dealt with below the card instead, where there is
     nothing to line up with.
     >> SO THIS BLOCK OWNS THE STAGE'S SPARE HEIGHT, AND IT SPLITS IT IN TWO: one share above
     the strip and one below it. Everything above stays packed - heading, helper, card, links -
     so the group reads as one piece and its top edge lands where the other sections' do. */
  .work-rail{margin-bottom:auto;}
  .mq-strip{margin-top:0;margin-bottom:auto;}
  /* >> THE BAR IS BIGGER ON A PHONE THAN ON A LAPTOP - TRIAL, wd-s18 (owner: "would like to
     see what it looks like with marque slightly larger to take up some of that empty space
     above and below"). THE PREVIOUS VALUES ARE ON THE LINE BELOW EACH ONE. To revert, this
     whole block goes back to `padding:.5rem .9rem;gap:.9rem` with no font overrides and no
     `.mq-strip.glass` padding change - the owner flagged he may want the locked version back.
     >> IT IS THE ONE ELEMENT ON A PHONE THAT HAS ROOM TO GROW INTO. The stage held about
     160px of empty space under the bar and as much above it; the deck cannot use that (the
     card is width-bound at this size) and the text group is already positioned to pin under
     the nav. Growing the bar is the only thing here that spends the space without moving
     something the owner has already approved.
     >> THE TYPE GOES UP WITH IT, DELIBERATELY. A taller bar with the laptop's type in it just
     reads as a bar with more padding - the thing that makes it feel bigger is the words. */
  .mq-strip.glass{padding:.4rem;}          /* was .3rem, laptop value */
  .mq-strip .mq-inner{padding:1.1rem 1rem;gap:1rem;}   /* was .5rem .9rem / gap .9rem */
  /* wd-s24, owner - the strip read too small on a phone. Colour and weight are lifted for
     every screen in the wd-s23 block above; only the SIZE is phone-only, because the
     laptop's was never the complaint. */
  .mq-item{font-size:1.12rem;}             /* was .98rem (wd-s18), .86rem before that */
  .mq-label{font-size:.94rem;}             /* was .86rem (wd-s18), .8rem before that */
  /* wd-s10, owner: hold the group in the MIDDLE of that screen. Without this the section is
     a normal block, so every pixel the min-height adds pools at the BOTTOM and the grouping
     sits high in its own band. Centring splits the leftover evenly above and below.
     >> SCOPED TO `.is-single` ON PURPOSE - that class is on while there is one example. When
     a second card turns the deck on, the stage pins itself and must not be centred by its
     parent. The padding still sets the minimum nav clearance either way. */
  .work.is-single{display:flex;flex-direction:column;justify-content:center;}
  .work-sub{margin-bottom:0;}
  .w1{height:46%;left:-9%;top:-2%;}  .w2{height:42%;right:-9%;top:14%;}
  .w3{height:38%;left:-8%;top:46%;} .w4{height:36%;right:-8%;bottom:0;}
  .scene-hint{margin-top:4rem;}
}

/* Small phones: the logo, wordmark and button together are a few pixels wider than
   the bar, so "Get started" dropped onto a row of its own and pushed the links to a
   third row. Trim all three just enough to hold one row. Keep this AFTER the mobile
   block - equal specificity means file order decides. */
@media (max-width:400px){
  .nav{gap:.45rem;padding:.6rem .85rem;}
  .nav-logo{gap:8px;}
  .nav-logo img{width:28px;}
  .nav-wordmark{font-size:14px;letter-spacing:.1em;}
  .btn-sm{padding:.5rem .95rem;font-size:.8rem;}
  .nav-links{font-size:.8rem;}
}

/* SHORT PHONES. The extra separation added above is funded by spare height, and a short
   screen does not have it - at 640px tall the source line under the stat fell off the
   bottom. Give the spacing back here; everything visible beats everything spaced. */
@media (max-width:760px) and (max-height:760px){
  /* gaps only - the top padding is nav clearance and is never taken from */
  .hero{padding-bottom:.6rem;}
  .hero-inner{gap:1.3rem;}
  .hero .hero-stat{margin-top:1.3rem;font-size:.82rem;}
}

@media (prefers-reduced-motion:reduce){
  .mock-region{--paint:1 !important;filter:none;opacity:1;
    -webkit-mask-image:none;mask-image:none;}
  .sample-tag{opacity:1;}
}


/* =========================================================
   THE STATS PAGE  (why-a-website.html)
   Numbers first, then what they mean, then where they came from - in that
   order every time, because the source line is the argument, not a footnote.
   ========================================================= */
/* >> NO BOTTOM PADDING ON THIS PAGE (wd-s7). The closing item's tail is sized by script so
   the page stops scrolling at the exact moment the last card pins, and padding below the
   list is page the tail cannot use. Measured: with 6rem here the tail needed to be 92px on
   a 695-tall window, which is shorter than the closing block itself, so it could not shrink
   that far and 63px of scroll-behind survived. At 0 the required tail is 188px and the fit
   is exact. The space under the button now comes from the tail, not from padding. */
.stats-page{max-width:60rem;margin:0 auto;padding:9rem 1.5rem 0;}
/* >> MATCHED TO THE HOMEPAGE HERO H1 (owner, wd-s7). It was clamp(2.2rem,6vw,4.2rem) - BIGGER
   than the hero on the front page, which is the wrong way round, and at 67px the first sentence
   needed 994px against a 912px column, so "worth -" wrapped onto a line of its own. At the hero's
   scale it needs 876px and sits on one line. Measured AFTER document.fonts.ready - measured
   before the webfont lands it reads 763px and looks like it fits.
   >> THE COPY CHANGED AFTER THAT MEASUREMENT AND THE CAP WAS NEVER RE-MEASURED. The 876px
      figure belongs to the old headline. Measured on the live page at 1720 wide, fonts ready:
      "Consumers trust businesses with websites." needs **1076.3px** in a **912px** column -
      164px over, and it has been over since the words were rewritten.
   >> WHAT AN OVER-WIDTH HEADLINE ACTUALLY DOES HERE, AND IT IS WORSE THAN A SECOND LINE:
      Chrome fills the line and breaks INSIDE the word - "...WITH WE / BSITES." - which reads
      as a rendering fault rather than as a wrap. Resizing the window just moves where the
      word splits. Mobile never showed it: below ~1055px the clamp is off its cap, the type
      is small enough to wrap at the space, and the line breaks cleanly.
   >> THE CAP IS NOW 3.1rem, WHICH IS THE MEASURED FIT, NOT A GUESS. The sentence scales at
      18.18px of width per 1px of font-size, so 912 / 18.18 = 50.2px = 3.14rem. 3.1rem
      (49.6px) needs ~902px and puts the sentence back on ONE line with the accent clause
      under it, which is the layout this rule was written to produce.
   >> WIDENING `.stats-page` WAS THE OTHER WAY AND IS THE WRONG ONE. It would need 72rem to
      clear 1076px, and that measure applies to the stat cards and the intro paragraph too -
      a 1100px line length is past comfortable reading and would be a worse page to fix a
      headline. Shrink the one element that is over, not the column everything sits in.
   >> ONLY LAPTOPS MOVE. The cap binds above ~1055px viewport; every width below that was
      already on `4.7vw` or the 1.9rem floor and renders exactly as before. */
/* >> wd-s30 - THE MID-WORD BREAK IS FIXED BY MAKING THE LINE FIT, NOT BY FORBIDDING THE
   BREAK. The guard below has been here since wd-s27 and does not work: reproduced in CHROME
   at 320, 375, 390 and 430 (clean only at 414), so it was never a Safari quirk either.
   >> WHY FORBIDDING IT CANNOT WORK: `word-break:normal` and `overflow-wrap:normal` tell the
   browser it MAY NOT break at unusual points - they do not create a place for the line to
   go. When the first line cannot fit, something has to give, and Chrome and Safari both give
   the word. You cannot forbid a break the layout still needs.
   >> SO TWO CHANGES, AND THEY WORK TOGETHER:
   1. A phone-only `<br>` after "trust" in the markup, which makes line 2 "BUSINESSES WITH
      WEBSITES." - the binding line at 331.3px when the type is 30.4px.
   2. The vw term 4.7 -> 7.9, so the type SCALES with the phone instead of sitting on a flat
      1.9rem floor that a 320px screen cannot hold.
   >> THE NUMBERS THE 8.1 COMES FROM: the binding line needs 331.3px at 30.4px, so it scales
   at 10.9px of width per 1px of type. The column is the viewport less 38.4px of padding.
   Solving for a fit at the narrowest phone, 320px, gives 25.8px = 8.06vw. 8.1 was tried
   FIRST and was 0.8px too wide - it dropped the closing full stop onto a line of its own at
   320, which is the same class of fault one notch quieter. 7.9 leaves ~6px of slack there
   and still clears every width above it.
   >> THE 3.1rem CAP IS UNTOUCHED and still binds on every laptop - at 8.1vw it takes over at
   about 628px, and above that nothing on this page renders differently than before. */
.stats-head h1{
  font-family:'Barlow Condensed',sans-serif;font-weight:800;
  font-size:clamp(1.55rem,7.9vw,3.1rem);line-height:.95;letter-spacing:.01em;
  text-transform:uppercase;margin:0;
  /* >> wd-s27 - THE MID-WORD BREAK GUARD, AND IT IS UNSCOPED ON PURPOSE.
     iPhone Safari was still splitting "BUSINESSE / S" after the phone-only `text-wrap`
     fix, and there is no width reason for it: measured at the NARROWEST iPhone column,
     282px, the word "BUSINESSES" is 135px and the longest natural line is 214px. Nothing
     here overflows, so the break was a browser choice rather than a fit problem.
     These three forbid it outright and cost nothing when it was never going to happen.
     Kept out of the media query so a tablet, a landscape phone and a split-screen window
     are covered too - the earlier fix only reached 760px and under. */
  word-break:normal;
  overflow-wrap:normal;
  -webkit-hyphens:none;hyphens:none;
}
/* the category line above the headline uses the SHARED .kicker (owner, wd-s7) - same size,
   font, tracking and colour as the homepage hero kicker, on purpose. Only the hero's centring
   is undone here; this page's head is left-aligned. */
/* >> THE PHONE-ONLY BREAK THAT GOES WITH THE RULE ABOVE (wd-s30). Off by default, so the
   laptop keeps "Consumers trust businesses with websites." on ONE line - which is the layout
   the 3.1rem cap was measured to produce and must not change.
   >> 959px, NOT A PHONE BREAKPOINT, AND THAT IS THE POINT. The single line needs 901.8px at
   the 3.1rem cap. The column is the viewport less 48px until `.stats-page` reaches its 60rem
   max-width, so the line only fits from about 950px up; at 960 the column caps at 912px and
   keeps 10px of slack. Below that it does NOT fit, at any width, which is why the break has
   to stay on well past the phones.
   >> THAT BAND IS THE FAULT wd-s23 MISSED. Its 3.1rem cap was measured in a 912px column at
   a 1720px viewport - true there, and it says nothing about a 820px window, where the column
   is 772px and the same line is 130px over. The page has been breaking "WE / BSITES" on every
   tablet and small laptop since, and only the phone was ever looked at.
   >> AND THE SPACE BEFORE THE TAG IS LOAD-BEARING. With the break switched off, that space is
   the only thing keeping "TRUST" and "BUSINESSES" apart - written without it, the desktop
   read "CONSUMERS TRUSTBUSINESSES". The home page's `.br-phone` note records the same trap.
   Same idea as `.br-phone`, different breakpoint, because it is solving a different line. */
.br-stats{display:none;}
@media (max-width:959px){ .br-stats{display:inline;} }
.stats-head .kicker{margin-top:0;}
.stats-head h1 .accent{color:var(--red);}
.stats-intro{margin:1.4rem 0 0;max-width:52ch;color:var(--gray);font-size:1.02rem;line-height:1.6;}
/* 404 page (wd-s33): its CTA sits straight under the left-aligned head, so it aligns left
   with it. Only the 404 has .say-cta as a direct child of .stats-page - the stats page's own
   CTA lives inside the centered .stats-close and is untouched. */
.stats-page > .say-cta{justify-content:flex-start;}

/* ============ OPTION 2 : ROLODEX (wd-s6, trial) ============
   The owner's saved reference - cards stacked in depth, the front one advancing while the
   ones behind recede and blur. Sticky does the stacking; a small scroll handler does the
   depth, because scale and blur cannot be driven by CSS alone in Firefox and Safari
   (`animation-timeline` is Chrome-only today, and this site is checked in four browsers).

   >> EVERY CARD STAYS STICKY, INCLUDING THE LAST. Option 1 made the last one static and it
   slid UNDER the whole stack - positioned elements paint above non-positioned ones.

   >> TO REMOVE: restore this to `display:grid; gap:2.4rem` with the rgba background, and
   delete the script at the bottom of why-a-website.html. */
.stat-list{
  list-style:none;margin:4rem 0 0;display:block;
  /* >> THE TAIL LETS THE LAST CARD FINISH ITS TRAVEL. Its size is set by the script, to one
     card height - the measured amount needed. A guessed 80vh was tried first and left a
     screen and a half of black between the last card and the closing block. */
  padding:0;
}
.stat{
  padding:2rem clamp(1.2rem,3vw,2.2rem);border-radius:18px;
  /* >> THE CARDS ARE GLASS, NOT A FLAT FILL (owner, wd-s7: "more 3-d or blurry transparent...
     the cards themselves need to be more defined"). Was a solid #131315 with a .09 border,
     which read as a shape drawn on the background rather than an object sitting above it.
     Three things give it an edge, and all three are needed:
       1. a translucent gradient fill + backdrop blur - the house .glass recipe, so it matches
          the nav, the buttons and the meter pills. Kept at .78-.88 alpha rather than the
          nav's .04: a card carries body copy, and the card BEHIND it in the stack shows
          through anything more transparent than this.
       2. a brighter border (.09 -> .15) - this is most of what "more defined" means.
       3. the lit-from-above pair the parent brand standard puts on every button - inset
          highlight on the top edge, inset shadow on the bottom - plus a drop shadow BELOW
          as well as the existing one above, so it lifts off the page instead of only
          shading the card it covers. */
  background:linear-gradient(180deg, rgba(32,32,37,.80), rgba(15,15,18,.88));
  backdrop-filter:blur(16px) saturate(130%);
  -webkit-backdrop-filter:blur(16px) saturate(130%);
  border:1px solid rgba(255,255,255,.15);
  position:sticky;top:6.5rem;margin-bottom:2.4rem;
  /* >> z-index KEEPS THE CARDS ABOVE THE CLOSING BLOCK (wd-s7). The closing block became
     sticky too, and a positioned element with z-index auto paints in DOM order - it sits
     AFTER the last card, so without this it would slide OVER the card instead of behind
     it. Among themselves the cards are unchanged: all share z-index 1, so DOM order still
     decides, exactly as it did when they were all auto. */
  z-index:1;
  box-shadow:
    0 -18px 40px rgba(0,0,0,.55),      /* shades the card this one is covering */
    0 26px 60px rgba(0,0,0,.62),       /* lifts this one off the page */
    inset 0 1px 0 rgba(255,255,255,.14),
    inset 0 -2px 0 rgba(0,0,0,.34);
  /* the handler writes these two - declared here so the first paint is not unstyled */
  transform:scale(1);filter:blur(0px);
  transform-origin:50% 0;
  will-change:transform,filter;
}
.stat-fig{
  font-family:'Barlow Condensed',sans-serif;font-weight:800;
  font-size:clamp(2.6rem,7vw,4rem);line-height:1;letter-spacing:.01em;
  color:var(--white);margin:0;
}
/* the joining words sit small and quiet so the numbers keep the weight */
.stat-vs{font-size:.34em;font-weight:600;color:var(--gray-dim);
  letter-spacing:.12em;text-transform:uppercase;}
.stat-say{margin:1rem 0 0;color:var(--gray);font-size:1.02rem;line-height:1.6;max-width:56ch;}
.stat-say b{color:var(--white);font-weight:600;}
.stat-note{margin:.9rem 0 0;color:var(--gray-dim);font-size:.95rem;line-height:1.55;
  font-style:italic;max-width:56ch;}
.stat-src{
  margin:1.4rem 0 0;padding-top:1rem;border-top:1px solid rgba(255,255,255,.08);
  font-size:.76rem;letter-spacing:.06em;text-transform:uppercase;color:var(--gray-dim);
}


/* margin trimmed wd-s6: the list already carries a tail for the last card's travel, so
   5rem on top of it stacked two gaps into one very large one. */
/* >> THE CLOSING ITEM IS A FULL SCREEN TALL, AND ITS CONTENT SITS AT THE TOP (wd-s6).
   A sticky card can only reach the nav if there is roughly a screen of page below it -
   otherwise scrolling bottoms out first and the card stops short with the previous one
   still showing. A 230px closing block was not enough on a tall window, which is why it
   pinned at 633 and not at 813. Giving this item a screen of height supplies that travel.
   The content stays TOP-ALIGNED so the headline and button sit directly under the card the
   moment it pins - the rest of the height is below the fold and never seen. */
.stat-close-item{list-style:none;min-height:100svh;}
/* >> THE CLOSING BLOCK PARKS UNDER THE PINNED CARD AND STAYS THERE (owner, wd-s7).
   It used to keep scrolling up behind the last card - measured at ~600px of travel on
   every viewport size checked. Two things stop it, and both are needed:
     1. the script sizes the tail so the page runs out of scroll at the exact moment the
        last card pins, so in the normal case there is nothing left to scroll;
     2. this sticky, whose `top` the script sets to the card's pin offset plus the card's
        own height - so if any scroll does survive (a phone hiding its URL bar mid-scroll
        grows the viewport and shortens the page; a very short window cannot shrink the
        tail past the block's own height) the block holds position instead of sliding.
   >> NO `top` HERE ON PURPOSE. Sticky with `top:auto` has no vertical stickiness, so with
   the script disabled - reduced motion, JS off - this behaves exactly as it did before
   rather than pinning at y=0 behind the nav. */
.stats-close{position:sticky;margin:.5rem 0 0;text-align:center;}
.stats-close h2{font-family:'Barlow Condensed',sans-serif;font-weight:800;
  font-size:clamp(1.9rem,5vw,3rem);text-transform:uppercase;margin:0 0 1.2rem;}

/* ---------- the stat meter bars (LOCKED wd-s7) ---------- */
/* WOW TRIAL 3b - GLASS METER BARS (owner's redesign of the rings, wd-s7).
   Full card width on every size, so there is no dead space beside them and nothing for the
   hairline rule above the source line to cut through - the two faults with the rings.
   The pill reuses the site's own .glass class rather than restating it, so it stays matched
   to the nav and the buttons if that recipe is ever changed. */
/* >> THE BAR HEIGHT IS A BUDGET, NOT A TASTE CHOICE. Card 2 carries two bars, which makes
   it the tallest card, and every card is equalised to the tallest - so each pixel of bar
   height is paid for FIVE times and comes straight out of the room the closing block needs
   at the bottom of the page. At 34px tall the cards went 365 -> 407 and the Get started
   button landed exactly on the bottom edge of a 695-tall window. At 26px they land at 382,
   which keeps a margin. Re-measure if this ever changes. */
.meterset{margin:.8rem 0 0;display:flex;flex-direction:column;gap:.35rem;}
.meter{
  position:relative;width:100%;height:26px;border-radius:999px;overflow:hidden;
  display:flex;align-items:center;
}
.meter-fill{
  position:absolute;left:0;top:0;bottom:0;width:0;border-radius:999px;
  background:linear-gradient(90deg,#a80000,#e5333c);
  box-shadow:0 0 18px rgba(204,0,0,.30), inset 0 1px 0 rgba(255,255,255,.16);
}
/* the value sits right-justified INSIDE the pill, in the part the fill has not reached */
.meter-val{
  position:relative;margin-left:auto;padding-right:.85rem;
  font-family:'Barlow Condensed',sans-serif;font-weight:700;font-size:.92rem;
  letter-spacing:.02em;color:var(--white);
}
/* the big figures count too - fixed width per number so the digits growing cannot shove
   the "%" or the joining words sideways while it runs */
.cnt{display:inline-block;text-align:left;font-variant-numeric:tabular-nums;}

@media (max-width:760px){
  /* >> SECTION 2 BECOMES AN OBJECT, NOT TEXT IN SPACE (owner's diagnosis, wd-s6).
     The section had no edges - nothing marked where it began or ended - so it read as
     the GAP BETWEEN two sections rather than as a section. Three things were tried
     first and none of them worked, because all three treated it as text that needed to
     be more noticeable: an entrance animation, bigger type, and giving it a whole screen.
     >> THE AGENCY SITE IS THE REFERENCE. Every section there is a bounded thing - stat
     cards with edges, panels with borders, hairline rules at the boundaries - which is
     why scrolling it feels like moving between blocks. This gives section 2 the same.
     The panel uses the site's OWN glass recipe, so it matches the nav and the button bar
     rather than introducing a second look. */
  .say{padding:2.6rem 1.2rem;}
  .say-inner{
    background:var(--glass-bg);
    backdrop-filter:blur(20px) saturate(140%);
    -webkit-backdrop-filter:blur(20px) saturate(140%);
    border:1px solid var(--glass-brd);
    border-radius:18px;
    /* same four-shadow stack as .glass (wd-s10) - this panel is mobile-only styling, so it
       cannot just take the class, but the values must not drift from it */
    box-shadow:
      var(--shadow-md),
      0 2px 8px rgba(0,0,0,0.35),
      inset 0 1px 0 var(--glass-hi),
      inset 0 -2px 0 var(--glass-lo);
    padding:2.4rem 1.4rem 2.2rem;
  }

  /* top was 11rem - moved up 1.5rem (owner, wd-s7). Still ~54px clear of the frosted nav,
     so the heading is never pegged against it. bottom stays 0 - see the desktop rule. */
  .stats-page{padding:9.5rem 1.2rem 0;}

  /* >> THE PHONE CANNOT FIT SENTENCE 1 ON ONE LINE AT ALL (measured, wd-s7). At the phone size
     (30.4px) "What a website is actually worth -" needs 621px against a ~374px column; it only
     fits at 18px, which is not a heading. So the fix is not a smaller font, it is an EVEN wrap -
     without this the line breaks as "...ACTUALLY" / "WORTH -" and leaves a two-word orphan.
     balance splits it down the middle instead. Unsupported browsers fall back to the old wrap,
     which is exactly what it looks like today - nothing breaks. Scoped to the phone on purpose:
     on the laptop sentence 1 already fits on one line and sentence 2 breaks well as it is. */
  /* >> THE PHONE WRAPS LIKE THE LAPTOP (owner, wd-s7): the sentence flows, and the red
     "Here's the data." stays on the SAME line as "websites." rather than dropping to a
     line of its own. Forced three-line spans were tried and rejected - holding a chosen
     first line meant solving the size down to ~22px, which is barely above the body copy.
     balance only evens the line lengths; it never moves the red clause down. */
  /* >> wd-s27 - `text-wrap:balance` REMOVED. It was splitting "BUSI / NESSES" on Android
     Chrome: measured at weight 700, "CONSUMERS TRUST BUSINESSES" needs 352.8px at the
     30.4px floor against a 345.6px column on a 384px phone, and the page sets 800. Balance
     cannot fit that line and splits the word rather than moving it down.
     The unconditional break guard lives on the base rule above - iPhone Safari kept doing
     it after this fix, so forbidding it belongs everywhere, not only under 760px. */
  .stats-head h1{text-wrap:normal;}
  .stat{padding:1.5rem 1.1rem;}
  .stat-list{gap:1.6rem;margin-top:2.6rem;}

  /* the meter bars, phone (LOCKED wd-s7) */
  .meterset{margin-top:.75rem;gap:.32rem;}
  .meter{height:24px;}
  .meter-val{font-size:.85rem;padding-right:.75rem;}
}

/* ==================================================================================
   SECTION 4 - HOW IT WORKS (wd-s7)

   >> DELIBERATELY NOT PINNED. The page already spends a pinned screen on the hero and
   another on the examples deck; a third would make it feel like the site holds the visitor
   still every time they try to move. This section just scrolls past.

   >> FOUR ACROSS ON A LAPTOP, ONE COLUMN ON A PHONE. Four is the most that reads as a
   SEQUENCE rather than a grid of features - past that the eye stops counting and starts
   scanning, and the whole point of this section is that it is four steps in order.
   ================================================================================== */
.how,.faq{position:relative;z-index:1;padding:7rem 1.5rem 6rem;}
.how-inner{max-width:74rem;margin:0 auto;text-align:center;}
.how h2,.faq h2{
  font-family:'Barlow Condensed',sans-serif;font-weight:700;
  font-size:clamp(1.9rem,3.8vw,2.7rem);line-height:1.1;margin-bottom:.7rem;
}
.how-sub{color:var(--gray-dim);font-size:.98rem;margin-bottom:2.8rem;}

/* OPTION B - one full-width row per step, heading on the left, text on the right.
   To go back to OPTION A (two across, two rows): grid-template-columns:repeat(2,1fr) here,
   drop the .how-step grid below, and put .how-num back to position:absolute. */
.how-steps{
  list-style:none;display:grid;grid-template-columns:1fr;gap:1.4rem;
  text-align:left;
}
.how-step{
  position:relative;padding:1.9rem 2rem 1.8rem;border-radius:16px;
  display:grid;grid-template-columns:17rem 1fr;column-gap:3rem;align-items:start;
  /* >> wd-s27 - THE DARK FILL FROM THE STATS-PAGE CARDS, VALUE FOR VALUE (owner). Copied off
     `.stat` rather than re-derived: same gradient, same blur, same border. The section moved
     22 -> 35 this session and at the house .04 fill these four read as washed panels on a lit
     ground; a card carrying body copy is the sanctioned deviation the standard already names.
     Everything else on the card - padding, grid, the number on the edge - is untouched. */
  background:linear-gradient(180deg, rgba(32,32,37,.80), rgba(15,15,18,.88));
  backdrop-filter:blur(16px) saturate(130%);
  -webkit-backdrop-filter:blur(16px) saturate(130%);
  border:1px solid rgba(255,255,255,.15);
}
.how-head{display:flex;align-items:center;gap:.85rem;}
/* >> THE NUMBER SITS ON THE CARD'S TOP EDGE, not inside the padding. Inside, it read as the
   first line of the step and the heading looked like a subtitle of the number. On the edge it
   reads as a marker on the card, which is what it is. */
.how-num{
  flex:0 0 auto;
  display:inline-flex;align-items:center;justify-content:center;
  width:1.9rem;height:1.9rem;border-radius:999px;
  background:var(--red);color:#fff;
  font-family:'Barlow Condensed',sans-serif;font-weight:700;font-size:1rem;
  box-shadow:0 6px 18px rgba(204,0,0,.35);
}
.how-step h3,
.faq-item summary{
  font-family:'Barlow Condensed',sans-serif;font-weight:700;
  font-size:1.18rem;line-height:1.2;margin-bottom:0;
}
.how-step p,.faq-item p{color:var(--gray);font-size:.92rem;line-height:1.55;}
/* steps 2, 3 and 4 carry two paragraphs (wd-s10). Without this they butt together and read as
   one block with a stray line break in it. */
.how-step p + p{margin-top:.75rem;}

/* the payment terms and the hand-off to the price section. Both are quiet on purpose - this
   section hands forward to section 5, it does not close. */
/* >> wd-s27 - COLOUR ONLY, EVERY SCREEN (owner). #6c6c6c -> #8a8a8a. Started laptop-only and
   he asked for the phone to match, so it is one value on the base rule rather than a
   breakpoint. It stays BELOW `--gray` #a8a8a8, which is what `.how-sub` and the helper copy
   use, so the hierarchy the wd-s23 exclusion protects is intact - that exclusion was about
   weight and shadow pulling this line level with the section's own sub-heading, and neither
   is added here.
   >> AND COLOUR ALONE COULD NOT FIX IT ONCE THE SECTION WENT 22 -> 35. Measured on the new
      ground: #6c6c6c scores 1.18, #8a8a8a 1.79, and even `--gray` only reaches 2.60 - so
      going brighter spends the hierarchy and still does not make it readable. The SHADOW is
      the lever that works: with the house pair behind it, #8a8a8a reads 6.08. It buys
      separation without adding voice, which is exactly what the wd-s23 exclusion was
      protecting - that was about WEIGHT, and no weight is added. Both screens, one rule. */
.how-terms{
  margin-top:2.2rem;color:#8a8a8a;font-size:.92rem;
  text-shadow:0 1px 8px rgba(0,0,0,.9), 0 1px 3px rgba(0,0,0,.7);
}
/* the link itself is the house quiet link - styled with `.work-open`, further up */
.how-next{margin-top:.6rem;}

/* SECTION 5b - QUESTIONS (wd-s34). Panel is `.cat-card`, type is `.how-step`, both grouped
   above. Only the accordion mechanics live here. Row line = the `.cat-row` line. */
.faq-list{max-width:52rem;margin:0 auto;text-align:left;}
.faq-item{border-bottom:1px solid rgba(255,255,255,0.07);}
.faq-item:last-child{border-bottom:none;}
.faq-item summary{
  list-style:none;cursor:pointer;padding:1.3rem 2.2rem 1.3rem 0;position:relative;
}
.faq-item summary::-webkit-details-marker{display:none;}
.faq-item summary::after{
  content:"+";position:absolute;right:0;top:50%;transform:translateY(-50%);
  font-size:1.5rem;font-weight:400;color:#fff;opacity:.72;transition:transform .2s ease;
}
.faq-item[open] summary::after{transform:translateY(-50%) rotate(45deg);}
.faq-item p{padding:0 2.2rem 1.4rem 0;}

@media (max-width:900px){
  .how-steps{grid-template-columns:1fr;gap:1.6rem;}
  .how,.faq{padding:8rem 1.2rem 4rem;}
  /* the left column stacks above the text - 17rem of heading column has nowhere to go on a
     phone, and the number stays beside the heading either way */
  .how-step{grid-template-columns:1fr;row-gap:.6rem;padding:1.6rem 1.3rem 1.4rem;}
}

/* ==================================================================================
   SECTION 5 - THE OFFER AND THE PRICE (wd-s7)

   >> THE STRUCK-THROUGH $1,497 IS AN ANCHOR, NOT DECORATION, so it is set smaller and dimmer
   than the price being asked. Give the two numbers similar weight and the eye reads them as a
   choice; the whole job of the anchor is to be read FIRST and then dismissed.

   >> THE BUTTON SITS ON A FROSTED PILL. House rule from the parent brand standard: a
   see-through button on near-black has nothing behind it to catch and goes flat. The pill is
   the surface. It HUGS the button - `inline-flex` - never a full-width bar, which reads as an
   empty shelf.
   ================================================================================== */
.offer{position:relative;z-index:1;padding:7rem 1.5rem 6rem;}
.offer-inner{max-width:52rem;margin:0 auto;text-align:center;}
.offer h2{
  font-family:'Barlow Condensed',sans-serif;font-weight:700;
  font-size:clamp(1.9rem,4.2vw,3rem);line-height:1.08;text-transform:uppercase;
  margin-bottom:.8rem;
}
.offer h2 .accent{color:var(--red);}
/* >> A LINE BREAK THAT ONLY EXISTS ON A PHONE (wd-s19, owner).
   On a phone the pricing heading's first line does not fit, so it broke itself after
   "NOT A" and left "SUBSCRIPTION." stranded on a line of its own. This breaks it at the
   comma instead, where the sentence already pauses:
       OWN YOUR WEBSITE,  /  NOT A SUBSCRIPTION.  /  ONE PRICE. ONE WEBSITE.
   >> 519px IS MEASURED, NOT PICKED. The first line fits down to 520px and wraps at 519 -
   the heading is pinned at 30.4px through this whole range, so the number is stable. Above
   it nothing changes; every phone is far below it.
   >> AND THE SPACE AFTER THE TAG IS LOAD-BEARING - with the break switched off, that space
   is what keeps "Website, Not" apart on a laptop.
   >> `section-reveal.js` HAS TO AGREE WITH THIS. It rebuilds every heading and turns each
   `<br>` into its own masked block, so it checks whether a `<br>` is switched off before
   counting it. See the note there before adding another one of these. */
.br-phone{display:none;}
@media (max-width:519px){ .br-phone{display:inline;} }
/* wd-s10: was 44ch, which broke the rewritten sub into three lines with a short last one.
   `balance` makes the browser even the lines out instead of filling the first and dumping
   the remainder on the last. */
.offer-sub{color:var(--gray);font-size:1rem;line-height:1.6;max-width:50rem;
  margin:0 auto 2.6rem;text-wrap:balance;}

/* >> THE PICK LINE (wd-s30) - deliberately QUIETER than `.offer-sub` above it, and smaller.
   It is an instruction, not a pitch: the sub sells the position and this one just tells the
   visitor how to read the two cards. Same slot, half the voice. Sits on the picture, so it
   takes the full three-part treatment down in the wd-s25 block, not colour alone. */
.offer-pick{font-size:.92rem;line-height:1.5;max-width:34rem;
  margin:-1.4rem auto 2rem;text-wrap:balance;}

/* >> wd-s27 - THE TWO BIG CARDS GET A HEAVIER FILL, AND ONLY THESE TWO.
   They are the only panels sitting on a 0.80 band, so what shows through them is the
   picture at a fifth strength - the card reads 12 where the same recipe on a bare section
   reads 27. Overriding the token rather than the rule keeps them on the shared `.glass`
   recipe; every other panel is untouched. Lifting the BAND instead was the other option and
   is the wrong one - the band is at 0.80 to flatten the backdrop's diagonal out of a centred
   panel (wd-s23), and raising it puts the shaft back through the card. Delete to revert. */
/* >> wd-s30 (owner): 0.10 -> 0.16. The two-card layout made the old value read too dim -
   with one wide card the fill covered a lot of ground and 0.10 was enough; halved in width
   there is less of it and it stopped separating from the band. THIS IS THE DIAL - move this
   one number, not the border and not the blur. */
.offer-card,
.contact-form{ --glass-bg:rgba(255,255,255,0.16); }

.offer-card{padding:2.6rem 2rem 2.2rem;border-radius:20px;}

/* >> THE TWO-CARD GRID (wd-s30).
   >> `.offer-inner` WIDENS FROM 52rem TO 74rem, AND ONLY THE CARDS USE THE EXTRA. The h2 and
   the sub keep their own caps below, because a headline that grows with the grid is the
   large-screen drift wd-s24 fixed on GCH, arriving by a different route. */
.offer-inner{max-width:74rem;}
.offer h2{max-width:52rem;margin-left:auto;margin-right:auto;}
.offer-grid{
  display:grid;grid-template-columns:repeat(2,minmax(0,1fr));
  gap:1.6rem;align-items:stretch;text-align:center;
}

/* >> EQUAL HEIGHT, WITH THE TWO BUTTONS ON ONE BASELINE (owner, wd-s30). The cards were
   built `align-items:start`, so each stopped at its own content and the shorter one ended
   well above the other - visible on a laptop where they sit side by side, and the owner
   flagged the mismatch.
   >> THE FIX IS TWO HALVES AND THE SECOND IS THE ONE THAT MATTERS. Stretching alone makes
   the panels equal but leaves the shorter card's button floating mid-panel with dead glass
   under it, which reads worse than the ragged bottom did. `margin-top:auto` on the CTA
   pushes it to the foot of whichever card it is in, so both buttons land on the same line
   and any leftover difference becomes air ABOVE the button, where nothing is looking.
   >> IT IS DELIBERATELY NOT A FIXED HEIGHT. Both cards size themselves from the taller one's
   content, so adding a bullet to either never needs a number re-measured.
   Most of the gap was closed in the markup anyway - two bullets took Services from 6 to 8,
   level with Products - and this only has to absorb what is left.
   The CTA half of this lives on `.offer-cta` in its own rule further down, not here - that
   rule already existed and a second copy up here would be two places to change. */
.offer-card{display:flex;flex-direction:column;}
/* >> AND THE SIDE EFFECT THAT COMES WITH MAKING THE CARD A FLEX COLUMN, WHICH IS NOT
   OBVIOUS: a flex item with `margin:0 auto` stops filling its parent and shrinks to fit its
   own content. `.offer-for`, `.offer-list` and `.offer-scope` all carry auto margins for
   centering, so all three quietly narrowed the moment the card became flex - measured, the
   helper line went from its 416px cap to 264px. `width:100%` puts them back; the max-width
   and the auto margins still do the capping and the centering exactly as before, so the
   equal-height change is layout-neutral everywhere except the button. */
.offer-for,
.offer-list,
.offer-scope{width:100%;}

/* the card's own label and one-liner - the RE VM product-card structure the owner asked for:
   a header naming the type, a brief description, then the price and the specs */
.offer-name{
  font-family:'Barlow Condensed',sans-serif;font-weight:700;
  font-size:1.6rem;line-height:1;text-transform:uppercase;letter-spacing:.06em;
  color:var(--white);margin-bottom:.5rem;
}
.offer-for{
  color:var(--gray);font-size:.95rem;line-height:1.5;
  max-width:26rem;margin:0 auto 1.6rem;text-wrap:balance;
}
/* the second clause of both card helper lines. A BLOCK, so it always starts its own line -
   the hyphen leads line 2 at every width, on both cards, without asking the wrap algorithm
   for anything. See the note in the markup for the two attempts that failed first. */
.offer-for-b{display:block;}

.offer-price{display:flex;align-items:baseline;justify-content:center;gap:.9rem;
  flex-wrap:wrap;}
.offer-was{
  font-family:'Barlow Condensed',sans-serif;font-weight:600;
  font-size:1.5rem;color:var(--gray-dim);text-decoration:line-through;
}
/* >> THE CAP CAME DOWN 4.6rem -> 3.6rem WHEN THE SECOND CARD LANDED (wd-s30). The old
   number was set for one card in a 52rem column; in a half-width card the price row is
   $1,497 struck, the price, and "launch rate" on one line, and at 4.6rem that row wraps and
   strands the rate on its own. The 9vw term is untouched - it is what carries the phone. */
.offer-now{
  font-family:'Barlow Condensed',sans-serif;font-weight:800;
  font-size:clamp(3.2rem,9vw,3.6rem);line-height:1;color:var(--white);
}
.offer-rate{
  font-family:'Barlow Condensed',sans-serif;font-weight:600;
  letter-spacing:.2em;text-transform:uppercase;font-size:.78rem;color:var(--gray);
}
.offer-save{
  margin-top:.5rem;font-family:'Barlow Condensed',sans-serif;font-weight:700;
  letter-spacing:.14em;text-transform:uppercase;font-size:.86rem;color:var(--red);
}

.offer-list{
  list-style:none;text-align:left;max-width:34rem;margin:2rem auto 0;
  display:grid;gap:.7rem;
}
.offer-list li{
  position:relative;padding-left:1.7rem;color:var(--gray);
  font-size:.98rem;line-height:1.5;
}
/* a drawn check, not a font glyph - a unicode tick renders differently on every platform and
   this file is ASCII only anyway */
.offer-list li::before{
  content:"";position:absolute;left:.15rem;top:.42em;
  width:.5rem;height:.28rem;border-left:2px solid var(--red);
  border-bottom:2px solid var(--red);transform:rotate(-45deg);
}

/* the scope pair - what is in, what is out. Quiet on purpose: it answers a question rather
   than selling. The divider is a light translucent rule, NOT the 7 Star section hairline -
   that standard is for section boundaries on the page background; #1a1a1a would vanish here. */
.offer-scope{
  max-width:34rem;margin:1.5rem auto 0;padding-top:1.15rem;text-align:left;
  border-top:1px solid rgba(255,255,255,0.10);
}
.offer-scope p{color:var(--gray-dim);font-size:.88rem;line-height:1.5;}
.offer-scope p + p{margin-top:.45rem;}
.offer-scope b{color:var(--gray);font-weight:600;}

/* `auto` not a fixed margin (wd-s30) - it pushes the button to the foot of its card so both
   cards' buttons sit on one baseline. The 2.2rem is now a MINIMUM gap above it, carried as
   padding, so a card whose content already fills the height still clears its list. */
.offer-cta{margin-top:auto;padding-top:2.2rem;}
.offer-cta-bar{
  display:inline-flex;padding:.55rem;border-radius:999px;
}

@media (max-width:900px){
  .offer{padding:8rem 1.2rem 4rem;}
  .offer-card{padding:2rem 1.2rem 1.8rem;}
  .offer-list{margin-top:1.6rem;}
  /* one column - two cards side by side on a phone makes both unreadable, and the pick line
     above still tells the visitor there are two */
  .offer-grid{grid-template-columns:minmax(0,1fr);gap:1.2rem;}
}

/* ==================================================================================
   SECTION 6 - CONTACT (wd-s7)
   >> THE FORM IS NOT WIRED - see the note in index.html. Do not deploy the page in this
   state; a form that silently swallows a lead is worse than no form.
   ================================================================================== */
.contact{position:relative;z-index:1;padding:7rem 1.5rem 7rem;}
.contact-inner{max-width:44rem;margin:0 auto;text-align:center;}
.contact h2{
  font-family:'Barlow Condensed',sans-serif;font-weight:700;
  font-size:clamp(1.9rem,4vw,2.9rem);line-height:1.1;text-transform:uppercase;
  margin-bottom:.7rem;
}
.contact-sub{color:var(--gray);font-size:1rem;line-height:1.6;margin-bottom:2.4rem;}

.contact-form{padding:2rem 1.6rem 1.6rem;border-radius:20px;text-align:left;
  display:grid;gap:1rem;}
/* >> THIS RULE IS LOAD-BEARING, wd-s12. The `hidden` attribute works by setting
   `display:none`, and ANY display value declared in a stylesheet beats it - so the line
   above silently cancels it out. Without this the form stays on screen after a successful
   send and the thank-you message appears UNDERNEATH it. Caught in a render, not by
   reading the code. The same trap is waiting on every element that gets a display value
   AND is toggled with `hidden`. */
.contact-form[hidden]{display:none;}
.cf-row{display:grid;grid-template-columns:1fr 1fr;gap:1rem;}
.cf-field{display:grid;gap:.4rem;}
.cf-field span{
  font-family:'Barlow Condensed',sans-serif;font-weight:600;
  letter-spacing:.12em;text-transform:uppercase;font-size:.72rem;color:var(--gray);
}
/* "(optional)" sits inside the label, quieter than it - it is a permission, not a heading */
.cf-field span i{font-style:normal;color:var(--gray-dim);}
.cf-field input,.cf-field textarea,.cf-field select{
  font-family:'Barlow',sans-serif;font-size:.98rem;color:var(--white);
  background:rgba(255,255,255,0.04);
  border:1px solid var(--glass-brd);border-radius:10px;
  padding:.7rem .8rem;width:100%;resize:vertical;
  transition:border-color .2s,background .2s;
}
/* the dropdown has to be told twice: `appearance:none` strips the OS control (which renders
   as a light native widget on a dark form), and the OPTIONS are drawn by the OS, so they need
   their own dark colours or the open list is white text on white */
.cf-field select{
  appearance:none;-webkit-appearance:none;
  background-image:linear-gradient(45deg,transparent 50%,var(--gray) 50%),
                   linear-gradient(135deg,var(--gray) 50%,transparent 50%);
  background-position:calc(100% - 18px) 55%, calc(100% - 13px) 55%;
  background-size:5px 5px,5px 5px;background-repeat:no-repeat;
  padding-right:2.2rem;cursor:pointer;
}
.cf-field select option{background:#141416;color:var(--white);}
/* >> THE HELPER TEXT INSIDE A BOX IS DIMMER THAN WHAT THEY TYPE (owner, wd-s12). There was no
   rule for it at all before, so it fell to the browser default - roughly 54% of the text
   colour, which on this dark form came out close enough to real typed text to be mistaken for
   it at a glance. `--gray-dim` is the site's existing quiet-text token, so this borrows a
   value rather than inventing one.
   >> IT IS SAFE TO GO THIS QUIET *BECAUSE EVERY BOX HAS A REAL LABEL ABOVE IT.* The helper
   text is an example, never the only thing naming the field - so nothing is lost if it reads
   as faint. Do NOT copy this dimness onto anything that is the sole label for a control. */
.cf-field input::placeholder,
.cf-field textarea::placeholder{color:var(--gray-dim);opacity:1;}
/* >> AND THE DROPDOWN MATCHES IT WHILE NOTHING IS CHOSEN. "Choose one" was rendering in full
   white next to nine dimmed placeholders, so it read as an answer already given. The select is
   `required` with an empty first option, so `:invalid` is true until they actually pick - no
   JavaScript needed. It snaps to white the moment they choose. */
.cf-field select:invalid{color:var(--gray-dim);}
/* >> A VISIBLE FOCUS STATE IS NOT OPTIONAL. The default outline is removed by nothing here,
   but a glass field on near-black gives so little edge contrast that without this the visitor
   cannot tell which field they are typing in - and keyboard users cannot navigate at all. */
.cf-field input:focus,.cf-field textarea:focus{
  outline:none;border-color:rgba(204,0,0,.75);background:rgba(255,255,255,0.07);
}
.cf-send{margin-top:.4rem;text-align:center;}
.cf-send-bar{display:inline-flex;padding:.55rem;border-radius:999px;}
.cf-note{text-align:center;color:var(--gray-dim);font-size:.86rem;margin-top:.2rem;}

/* ---- WIRED wd-s12 - the three states the form gained when it got a destination ---- */

/* >> THE HONEYPOT. Hidden from people, left empty by them, filled in by a bot - the
   Worker throws away anything that arrives with it set.
   >> IT IS NOT `display:none`, AND THAT IS THE WHOLE TRICK. The better bots skip
   display:none inputs precisely because they know it is a trap. This is a real,
   laid-out, focusable field that simply sits outside the visible page.
   `aria-hidden` plus `tabindex="-1"` on the input keep it away from screen readers
   and off the keyboard path, so no real person can land in it by accident. */
.cf-hp{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden;}

/* the button while the send is in flight - it stays the same size so nothing shifts */
.contact-form button[type="submit"][disabled]{opacity:.6;cursor:default;}
@media (hover:hover) and (pointer:fine){
  .contact-form button[type="submit"][disabled]:hover{transform:none;filter:none;}
}

/* >> THE FAILURE LINE. Red, because it is the one message on this form that needs to
   be noticed - but it carries a LINK, because a failed send has to leave them a way
   through (hard rule 11). The form keeps everything they typed. */
.cf-error{
  text-align:center;color:#ff6b6b;font-size:.9rem;line-height:1.5;margin:.2rem 0 0;
}
/* >> THE LINK INSIDE IT IS STYLED BY THE HOUSE QUIET-LINK RULE further up the file, NOT here.
   It used to carry its own `color` + `text-decoration` at this spot, which SILENTLY BEAT the
   shared rule - equal specificity, and this block sits later in the file, so file order
   decided. Same trap as decision 26. Do not re-add a link declaration here. */

/* >> THE SUCCESS PANEL replaces the form in the same box, so the page does not jump.
   It reuses `.glass` and the form's own padding and radius - one look per element
   type per site, so this cannot drift away from the panel it stands in for. */
.cf-done{
  padding:2.6rem 1.6rem;border-radius:20px;text-align:center;
}
.cf-done h3{
  font-family:'Barlow Condensed',sans-serif;font-weight:700;
  font-size:clamp(1.5rem,3vw,2.1rem);line-height:1.15;text-transform:uppercase;
  color:var(--white);margin:0 0 .8rem;
}
.cf-done p{color:var(--gray);font-size:1rem;line-height:1.6;margin:0 auto;max-width:34rem;}
/* Balance the two lines so "next steps." is not left alone on line 2 (owner, wd-s12).
   >> LAPTOP ONLY. `text-wrap:balance` re-breaks the lines at EVERY width, and the phone was
   already approved as it stands - so it is scoped above the 700px breakpoint and the phone
   renders byte-identical to before. Same fix as decision 60 on the price sub-line. */
@media (min-width:701px){
  .cf-done p{text-wrap:balance;}
}
.cf-done p + p{margin-top:.9rem;}
/* the email link here is the house quiet link too - shared rule further up, not declared here */
.cf-done:focus{outline:none;}

/* the no-JavaScript fallback - same quiet weight as the note under the button */
.cf-noscript{text-align:center;color:var(--gray);font-size:.9rem;line-height:1.5;}
.cf-noscript a{color:var(--white);}

@media (max-width:700px){
  .contact{padding:8rem 1.2rem 4.5rem;}
  .cf-row{grid-template-columns:1fr;}
}

/* ==================================================================================
   THE A-LA-CARTE CATALOG - services.html (wd-s9)
   Copy is DRAFT in COPY.md - change it there first.

   >> THIS PAGE IS NOT PART OF THE PITCH. It is handed to someone who is ALREADY a client.
   That is why it carries no offer, no $997, and no "book a call" - the sale already
   happened. Putting a price list next to the home page's one price gives a prospect a
   second set of numbers to weigh, which is exactly what the one-price positioning avoids.

   >> NO BACKGROUND ON THE PAGE BODY, same reason as the hero, Portfolio and Price bands:
   `body::before` is one FIXED red glow behind everything, and painting a band here would
   cover it. The page rides the glow like the stats page does.
   ================================================================================== */
.cat-page{max-width:56rem;margin:0 auto;padding:9rem 1.5rem 6rem;position:relative;z-index:1;}

.cat-head{text-align:center;margin-bottom:3rem;}
.cat-head .kicker{margin-top:0;}
.cat-head h1,
.cat-head h2{
  font-family:'Barlow Condensed',sans-serif;font-weight:800;
  font-size:clamp(2.1rem,5.4vw,3.4rem);line-height:1.05;text-transform:uppercase;
  margin-top:.6rem;
}
/* wd-s36: the monthly maintenance heading further down the page - same look as the h1 */
.cat-head-sub{margin:4.5rem 0 2rem;}
.cat-head h1 .accent{color:var(--red);text-shadow:0 3px 26px rgba(204,0,0,0.35);}
/* text-wrap:balance - without it the last line orphaned a single word ("thing.") under a
   centred two-line paragraph, which reads as a mistake rather than a line break */
.cat-intro{margin:1.3rem auto 0;max-width:46ch;color:var(--gray);
  font-size:1.02rem;line-height:1.65;text-wrap:balance;}

/* >> THE DARK FILL FROM THE HOW-IT-WORKS STEP CARDS, VALUE FOR VALUE (owner, wd-s30). Copied
   off `.how-step` rather than re-derived - same gradient, same blur, same border - so the two
   can never drift into being two different dark panels.
   >> IT IS THE SANCTIONED DEVIATION FROM THE HOUSE `.glass` RECIPE, NOT AN OVERRIDE OF IT.
   The shared standard names one exception: a panel carrying body copy over another surface
   needs a heavier fill, or the ground reads through the text. This card is 28 rows of body
   copy on a lit backdrop, which is exactly that case, and it is the same argument that took
   the four `.how-step` cards off the house fill in wd-s27.
   The class stays on the element - padding, radius and the row rules are all untouched. */
.cat-card,
.faq-list{
  padding:.6rem 2rem;border-radius:20px;
  background:linear-gradient(180deg, rgba(32,32,37,.80), rgba(15,15,18,.88));
  backdrop-filter:blur(16px) saturate(130%);
  -webkit-backdrop-filter:blur(16px) saturate(130%);
  border:1px solid rgba(255,255,255,.15);
}

/* Each service is one row: what it is on the left, what it costs on the right.
   >> THE RULE BETWEEN ROWS IS A LIGHT TRANSLUCENT LINE, NOT THE #1a1a1a SECTION HAIRLINE.
   That standard governs SECTION boundaries on the page background. Inside a glass panel the
   surface is lighter, and a near-black line on it disappears. */
.cat-row{
  display:grid;grid-template-columns:1fr auto;align-items:baseline;gap:1.5rem;
  padding:1.5rem 0;border-bottom:1px solid rgba(255,255,255,0.07);
}
.cat-row:last-of-type{border-bottom:none;}

.cat-name{
  font-family:'Barlow Condensed',sans-serif;font-weight:700;
  font-size:1.28rem;letter-spacing:.01em;line-height:1.2;color:var(--white);
}
.cat-desc{color:var(--gray);font-size:.95rem;line-height:1.55;margin-top:.35rem;}

.cat-price{
  font-family:'Barlow Condensed',sans-serif;font-weight:800;
  font-size:2rem;line-height:1;color:var(--white);white-space:nowrap;
}
/* the one row with no fixed number - set as words, not as a fake price */
.cat-price.cat-price-var{
  font-size:1rem;font-weight:600;color:var(--gray);text-transform:uppercase;
  letter-spacing:.1em;max-width:9rem;white-space:normal;text-align:right;line-height:1.3;
}

/* the delivery promise - one honest number, stated once, never sold as a feature */
.cat-turn{
  margin-top:2.2rem;text-align:center;
  font-family:'Barlow Condensed',sans-serif;font-weight:700;
  letter-spacing:.14em;text-transform:uppercase;font-size:.9rem;color:var(--red);
}

.cat-foot{max-width:44rem;margin:3.2rem auto 0;text-align:center;}
.cat-foot p{color:var(--gray);line-height:1.7;font-size:1rem;}
.cat-foot p + p{margin-top:1rem;}
.cat-foot b{color:var(--white);font-weight:600;}

/* >> THE BUTTON'S CENTERING IS NOT ON THE BUTTON. `.offer-cta-bar` is `inline-flex`, so it is
   only as wide as the button inside it and it sits wherever the text alignment puts it. On the
   home page that is centred because `.offer-inner` carries `text-align:center` - the pill
   itself says nothing about being centred. Reused here without that parent, it went hard left.
   Same trap as the section hairline: when copying a rule from a reference, check the BOX it is
   drawn on, not just the rule. Scoped to this page so the home page is untouched. */
.cat-page .offer-cta{text-align:center;}

/* The ownership line closes the page AFTER the button, so it needs less air above it than
   the block before the button does - otherwise it floats away and reads as orphaned rather
   than as a closing note. Set smaller and dimmer for the same reason: it is a reassurance,
   not a pitch, and it must not compete with the CTA sitting directly above it. */
.cat-foot-last{margin-top:2.2rem;}
.cat-foot-last p{font-size:.94rem;color:var(--gray-dim);}

@media (max-width:700px){
  .cat-page{padding:9.5rem 1.2rem 4rem;}
  .cat-card,.faq-list{padding:.4rem 1.3rem;}
  /* >> STACK, DO NOT SHRINK. Holding two columns on a phone squeezes the service name into
     a two-word-per-line column beside a price with nothing under it. The price moves below
     its own description and reads as belonging to it. */
  .cat-row{grid-template-columns:1fr;gap:.5rem;padding:1.3rem 0;}
  .cat-price{font-size:1.75rem;}
  .cat-price.cat-price-var{text-align:left;max-width:none;}
}

/* =========================================================
   FOOTER - wd-s17
   Copied from the agency's footer in the video-marketing project's
   html-live-prod/homepage-index.html (READ-ONLY reference). Geometry, the
   hairline and the gray-to-red social treatment are its values, not new ones.

   >> IT CARRIES NO BACKGROUND, DELIBERATELY. The fixed red bloom is body::before,
   one layer behind the whole page, so anything painted here would cover it and
   cut the wash off at the bottom of the page - the same reason the hero,
   Portfolio and Price bands stay bare (DESIGN.md, wd-s8).

   >> THE HAIRLINE IS FULL-BLEED HERE, WHICH IS NOT THE SECTION HAIRLINE STANDARD.
   The shared standard is a content-width line at max-width:1000px, and it is for
   separating one SECTION of a page from the next. This one divides the page from
   its footer, which is the mark a full-width line is actually for - and it is
   what the agency's own footer does. Do not "fix" it to 1000px.

   >> 13.5px, NOT THE AGENCY'S 12px - AND THAT IS HOW YOU COPY A SIZE ACROSS TWO
   DIFFERENT TYPEFACES. The agency runs Inter, this site runs Barlow. Barlow is
   the narrower face with the smaller x-height, so the same NUMBER renders visibly
   smaller and the two footers do not match. Measured in the browser on one string
   at one window width: Inter 12px = 282.1px wide, Barlow 12px = 251.1px, Barlow
   13.5px = 282.5px. 13.5 is the match, and the icon went 15 -> 17px with it.
   >> THE RULE THIS LEAVES BEHIND: copying a px value only reproduces the look when
   both sides share the typeface. Otherwise copy the RENDERED SIZE and solve for
   the number.
   ========================================================= */
footer{
  position:relative;z-index:1;
  padding:1.5rem 2rem;border-top:.5px solid #1a1a1a;
  display:flex;justify-content:space-between;align-items:center;
  flex-wrap:wrap;gap:8px;
}
.footer-copy{font-size:13.5px;color:#666;}
.footer-tag{font-size:13.5px;color:var(--red);}
/* >> START PAGE ONLY, LAPTOP ONLY: THE SAME TREATMENT AS SECTION 2's 84% LINE ON THE HOME
   PAGE, value for value (owner, wd-s34). On the lit backdrop brand red is darker than its
   ground and near-opposite it in hue, so it vibrates rather than separates and the line reads
   blurry. The tight achromatic halo gives every letterform a neutral edge and costs no
   saturation. Copied from `.hero-stat b`, not re-derived.
   >> SCOPED BY `body:has(.start-page)` SO IT CANNOT REACH THE HOME PAGE, and by a min-width
   so the phone is left exactly as it is - both the owner's call.
   wd-s35: the home page joins it (owner), scoped by `.hero-stat`, which only the home page has. */
@media (min-width:821px){
  body:has(.start-page) .footer-tag,
  body:has(.hero-stat) .footer-tag{
    color:#FF2A2A;
    text-shadow:
      0 1px 0 rgba(0,0,0,.95), 0 -1px 0 rgba(0,0,0,.95),
      1px 0 0 rgba(0,0,0,.95), -1px 0 0 rgba(0,0,0,.95),
      0 1px 6px rgba(0,0,0,.85);
  }
}
/* Three children and a phone is narrow, so it has to be allowed to wrap. Without
   this the row runs past the screen edge instead of dropping a line. */
.footer-right{display:flex;align-items:center;gap:14px;flex-wrap:wrap;}
.footer-mail{color:#666;text-decoration:none;font-size:13.5px;transition:color .2s;}
/* One look for a social link, declared once - a second one goes on THIS rule, it
   does not get its own. Only a glyph box would ever differ. */
.footer-social{
  display:inline-flex;align-items:center;gap:6px;
  color:#666;text-decoration:none;font-size:13.5px;transition:color .2s;
}
.footer-social svg{width:17px;height:17px;fill:#666;transition:fill .2s;}
/* Mouse only, then the same treatment on press - the project's standing touch
   rule. A phone leaves a hover look stuck on until something else is tapped. */
@media (hover:hover) and (pointer:fine){
  .footer-mail:hover{color:var(--red);}
  .footer-social:hover{color:#fff;}
  .footer-social:hover svg{fill:var(--red);}
}
.footer-mail:active{color:var(--red);}
.footer-social:active{color:#fff;}
.footer-social:active svg{fill:var(--red);}

@media (max-width:700px){
  /* Stacked and left-aligned. Held as a split row, the right-hand group wraps to
     its own line anyway and lands ragged against the copyright above it. */
  footer{flex-direction:column;align-items:flex-start;gap:10px;padding:1.5rem 1.2rem;}
}

/* >> THE MOCK'S NAV AND SERVICE BAND MUST NEVER BE FLEX-SHRUNK (wd-s17, owner caught it in
   FIREFOX - Chrome renders it correctly at every size I could test, which is why a Chrome
   harness passed a card that was visibly wrong on his screen. Same trap as wd-s7.)

   THE SYMPTOM: on the Ridgeline card the four service cards along the bottom, and the
   browser address bar's contents, vanished - the house photo filled the whole card. GCH's
   card was never affected, because it is a picture and cannot reflow.

   THE CAUSE: `.rl` is `height:100%` with `display:flex; flex-direction:column` and three
   children - the nav, the hero and the service band. The hero takes the remainder, but the
   other two carry the browser default `flex-shrink:1`, so when an engine decides the box is
   tight it is free to squeeze them toward nothing. Chrome does not; Firefox does.
   MEASURED, which is what ruled out the obvious suspects: the mock's font-size, its content
   height and its box height are IDENTICAL in every state - 13.03px and 479px into a 479px
   box - and `scrollHeight` equals `clientHeight`, so nothing is overflowing and nothing is
   being clipped. The bands were being reduced, not cut off.

   >> THE RULE: a flex child that must keep its height says so. Leaving it to the engine's
   discretion is not a decision, it is a coin toss that lands differently per browser.

   Scoped to `.work-card` on purpose - the hero animation runs its own copy of this mock and
   is LOCKED (wd-s3). Do not widen this selector. */
.work-card .rl-nav,
.work-card .rl-svc{flex-shrink:0;}
/* >> AND THE OTHER HALF, WHICH IS THE ONE THAT ACTUALLY BITES IN FIREFOX: a flex item will
   NOT shrink below the height of its own content unless it is told it may. The hero holds a
   two-line headline, a paragraph, two buttons and a trust row; add those up and Firefox
   refuses to make the hero any shorter, so the column runs past the bottom of the card and
   the service band - the last thing in it - is what falls off the edge.
   `min-height:0` lifts that floor so the hero takes whatever is left instead of demanding
   its content height, and `overflow:hidden` keeps its own contents inside once it does. */
.work-card .rl-hero{min-height:0;overflow:hidden;}

/* ============================================================
   THE FAKE BROWSER BAR ON A PORTFOLIO CARD
   Lifted out of `_lab/lab.css` in wd-s22, when the search hero was archived. It was the
   ONE thing the live home page still needed from the lab stylesheet, and keeping a 50 KB
   lab file linked into production for four rules meant every visitor downloaded the whole
   prototype folder's styles. Nothing in production points at `_lab/` any more.

   Values are carried across unchanged, comment included, because the reason for the
   colour is the expensive part:
   chrome lightened wd-s3 (owner): #1b1c1f -> #23252a -> #31343b, dots and address bar
   lifted to match. It was reading almost black against the frame's own #0a0a0a, so the
   fake browser had no separation from the page behind it.

   `.seq-body` was deliberately NOT brought across - it belongs to the animation's mock,
   which lives in the lab, and the card has no use for it.
   ============================================================ */
.seq-chrome{
  position:absolute;top:0;left:0;right:0;height:7%;z-index:6;
  background:#31343b;display:flex;align-items:center;gap:6px;padding:0 2%;
}
.seq-chrome i{width:7px;height:7px;border-radius:50%;background:#767b86;flex-shrink:0;}
.seq-chrome i:first-child{background:#8a5257;}
.seq-chrome span{
  flex:1;height:56%;margin-left:5%;border-radius:4px;background:#41454d;
  display:flex;align-items:center;padding:0 10px;
  font-family:'Barlow',sans-serif;font-size:9px;color:#b0b4bc;letter-spacing:.03em;
}

/* ==================================================================================
   wd-s25 - READABILITY PASS: THE NAV, THE FROSTED PANELS, AND THREE MISSED LINES
   ----------------------------------------------------------------------------------
   >> WHY THIS REOPENS THE wd-s23 EXCLUSION, AND IT IS A FAIR REOPEN RATHER THAN A
      FLIP-FLOP. That standard excludes text inside a frosted panel on the grounds that
      "the panel IS the surface" - a controlled ground. TRUE ON A NEAR-BLACK PAGE AND NOT
      TRUE NOW. A glass panel is see-through by definition, and what shows through it is
      the backdrop at brightness 1.6. The ground changed underneath the rule.
      Owner, wd-s25: the nav is hard to read over the lighter pages and the card copy
      reads too dark.

   >> THE SPLIT: PANEL TEXT TAKES COLOUR AND WEIGHT BUT NOT THE SHADOW.
      Inside a panel the ground is a soft blur, not detail - so weight does the work the
      shadow would, and a dark blur behind letters on a light frosted surface reads as
      grime rather than lift. Text sitting on the PICTURE keeps the full three.

   >> DELIBERATELY NOT SCOPED `html:not(.bg-off)` LIKE THE wd-s23 BLOCK ABOVE. That
      scoping exists for a QC switch that can never fire in production, and the owner has
      flagged leftover test plumbing as its own problem. Not adding more of it.
   ================================================================================== */

/* 1. THE NAV - the one element that sits over every background the site has, including
      the lightest. Hover stays white, declared in its own rule above. */
.nav-links a{
  color:#c9c9c9; font-weight:600;
  text-shadow:0 1px 8px rgba(0,0,0,.9), 0 1px 3px rgba(0,0,0,.7);
}

/* 2. TEXT ON THE PICTURE THAT WAS MISSED IN wd-s23 - the two section intros, and the
      three lines on the catalog page the owner named. Full treatment, they are not in
      panels. `.cat-turn` keeps its RED and its 700 - colour is already doing its job
      there, so only the shadow is added. */
.offer-sub,
.offer-pick,
.contact-sub,
.cat-foot p,
.cat-foot-last p{
  color:#c9c9c9; font-weight:600;
  text-shadow:0 1px 8px rgba(0,0,0,.9), 0 1px 3px rgba(0,0,0,.7);
}
.cat-turn{ text-shadow:0 1px 8px rgba(0,0,0,.9), 0 1px 3px rgba(0,0,0,.7); }
/* `.cat-foot b` is the emphasis inside those lines and must stay ABOVE them, or the
   treatment flattens the two together. */
.cat-foot b{ color:var(--white); font-weight:700; }

/* 3. BODY COPY INSIDE FROSTED PANELS - how it works, pricing, contact, catalog cards.
      Colour and weight only, per the split above. */
.how-step p,
.faq-item p,
.offer-list li,
.offer-for,
.cf-field span,
.cat-desc{
  color:#c9c9c9; font-weight:600;
}

/* >> NOT IN THIS PASS, ON PURPOSE:
   - `.how-terms` - excluded by the owner in wd-s23 and that decision stands. It is a
     terms line at the foot of the section; treated, it pulls level with `.how-sub` at
     the top and brackets the steps with two lines of equal voice.
   - The STATS PAGE CARDS - owner, wd-s25: "internal text within cards fine". Only that
     page's nav changes, which it gets from rule 1 above.
   - `.cf-field span i` - the "(optional)" marker inside a field label. It is meant to
     sit UNDER its label, so lifting it would erase the distinction.
   - Form placeholders - excluded by the shared standard: a bold placeholder reads as a
     filled field. */

/* ==================================================================================
   wd-s25 ROUND 2 - THE TWO INNER PAGES GO DARKER, AND TWO THINGS THE LIT PAGE EXPOSED
   ----------------------------------------------------------------------------------
   Owner, after QC'ing the live site: the lighter parts of the backdrop on the two inner
   pages make the copy hard to read, and he asked for them to sit on the same shading
   How it works uses.
   ================================================================================== */

/* 1. THE PAGE VEIL - NOW `rgba(0,0,0,0.19)`, THE VALUE `.how` CARRIES (wd-s30, see the note
      directly below). It was 0.50 in wd-s25, matching `.how` and `.contact` as they stood
      then; `.how` has since dropped to 0.19 and `.contact` is still 0.50, so this now tracks
      How it works specifically, not both bands.
      >> IT IS A SECOND ROOT LAYER, NOT A BACKGROUND ON THE PAGE WRAPPER. `.stats-page`
         and `.cat-page` are centred columns with a max-width, so a background on either
         would be a stripe down the middle of the screen, not a veil.
      >> SCOPED BY `:not(:has(.bp-bg))` - the exact selector the backdrop itself uses, so
         it lands on the two inner pages and can never reach the home page, whose hero
         sequence owns its own picture.
      Both layers sit at z-index -1 and `::after` is defined after `::before`, so the veil
      paints over the picture and under every piece of content. */
/* >> wd-s30 (owner): BOTH INNER PAGES NOW MATCH HOW IT WORKS - 0.50 -> 0.19. His words: "i
   want the bg image and the tinting / brightness to be the same, look the same as how it
   works." How it works is the site plate at 1.6 with a `rgba(0,0,0,0.19)` band over it -
   `.how` in the section-band block - and nothing else. So this is that value, copied off
   `.how`, not re-derived. Services went first; the stats page followed in the same session.
   >> ONE RULE FOR BOTH PAGES, NOT AN OVERRIDE PER PAGE. Services briefly had its own
   `:has(.cat-page)` rule while the stats page was still at 0.50; once both wanted the same
   value, two rules would just be two places to change it.
   >> A PORTFOLIO-STYLE brightness(1.45) LIFT WAS BUILT HERE FIRST AND REMOVED. It made the
   page visibly BRIGHTER than How it works, because How it works has no lift at all - it is
   darkened slightly, not brightened. The two were being matched against the wrong reference.
   >> THE STATS CARDS WERE NOT TOUCHED, BUT THEY ARE SEE-THROUGH. Their fill is .78-.88
   alpha, so a lighter ground behind them lets a little more of it through - a small shift in
   how they read, from the ground, not from any change to the cards. */
html:not(:has(.bp-bg))::after{
  content:"";
  position:fixed; inset:0; z-index:-1; pointer-events:none;
  background:rgba(0,0,0,0.19);
}

/* 2. "CHANGES GO LIVE WITHIN 2 BUSINESS DAYS" - RED TO WHITE.
      >> IT IS A FACT, NOT AN ACCENT. At .9rem, uppercase and letter-spaced, brand red sits
         almost opposite the backdrop's cyan on the colour wheel, so the two vibrate instead
         of separating - and no amount of size or weight fixes a colour clash. The page also
         already spends its red on the headline and the button; a third use dilutes both.
         Colour + weight + shadow, the full house treatment, since it sits on the picture. */
.cat-turn{ color:var(--white); }

/* 3. THE PRIMARY BUTTON GOES OPAQUE.
      >> ITS FILL WAS 50-60% TRANSPARENT, which composited to solid-looking red on a
         near-black page and to a dusty maroon once the backdrop was lit - the cyan behind
         it was mixing straight into the red. That is what "washed out" was.
      >> THE SHARED GLASS-BUTTON STANDARD ALREADY SAYS THIS: the primary red CTA keeps its
         SOLID fill and its glow and only GAINS the glass insets, because a transparent
         primary loses the strongest click signal on the page. The insets, the edge
         highlights and the hover lift are all untouched - only the alpha changes. */
.btn-liquid.btn-primary::before{
  background:linear-gradient(180deg, rgb(226,26,26) 0%,
                                     rgb(178,0,0)  52%,
                                     rgb(122,0,0) 100%);
}


/* ==================================================================================
   THE PROSPECT PAGE - /start/ (wd-s34, rebuilt after the owner's QC)

   >> THE MODEL IS THE AGENCY'S /agents/ PAGE, and it is read before anything here changes:
   7Star-RealEstate-VideoMarketing/marketing/agent-outreach/launch-teaser/p1-sms-agents-index.html
   That page is ONE narrow column, a minimal nav, proof cards, then a single offer panel
   carrying the prices, one button and a quiet reply line. The first build of this page
   assembled home-page sections instead and was rejected on screen - the home page sells to
   someone comparing quotes, this page is read on a phone by someone who just met us.

   >> NOTHING HERE IS A NEW LOOK. The stack is Veloura's, the card fill is the stats page's,
   the button and the pill are the house ones, the quiet link is the shared rule.
   ================================================================================== */
.start-page{max-width:60rem;margin:0 auto;padding:9rem 1.5rem 0;position:relative;z-index:1;}
.start-head{text-align:center;max-width:none;margin:0 auto;}   /* the stat row runs the full column; the copy inside keeps its own max-widths */
.start-head .kicker,
.start-price .kicker{margin-top:0;}   /* the kicker's own top margin was adding 22px under one separator */
.start-head h1{
  max-width:52rem;margin-left:auto;margin-right:auto;
  font-family:'Barlow Condensed',sans-serif;font-weight:800;
  font-size:clamp(2.1rem,5.4vw,3.4rem);line-height:1.05;text-transform:uppercase;
  margin-top:.6rem;
}
.start-head h1 .accent{color:var(--red);text-shadow:0 3px 26px rgba(204,0,0,0.35);}
.start-intro{margin:1.3rem auto 2rem;max-width:46ch;line-height:1.65;text-wrap:balance;}
/* >> ONE HELPER SETTING FOR THE WHOLE PAGE. Every line that sits under a heading is the same
   size, weight and colour - they were four different settings, which is the one-look-per-
   element rule broken four times on one page. `.start-sub` is the reference. */
.start-intro,
.start-stats-line,
.start-sub,
.start-note,
.start-reply{color:#c9c9c9;font-weight:600;font-size:.98rem;}

/* ---------- THE STAT ROW (wd-s34) ----------
   The agents page runs three tiles under its hero; this runs four. The tile is this site's
   own dark card - the same fill as the step cards, the example cards and the offer panel -
   rather than the agents page's own glass, because on THIS backdrop a .04 fill is see-through
   and washes out. Source sits inside the caption, in italics, so a number is never shown
   without one. */
.start-stats{display:grid;grid-template-columns:1fr 1fr;gap:.8rem;margin:0 auto 1.4rem;}
.start-stat{
  padding:1rem .9rem;border-radius:14px;text-align:center;
  background:linear-gradient(180deg, rgba(32,32,37,.80), rgba(15,15,18,.88));
  backdrop-filter:blur(16px) saturate(130%);
  -webkit-backdrop-filter:blur(16px) saturate(130%);
  border:1px solid rgba(255,255,255,.15);
  box-shadow:0 8px 32px rgba(0,0,0,.45),0 2px 8px rgba(0,0,0,.35),
    inset 0 1px 0 rgba(255,255,255,.14),inset 0 -2px 0 rgba(0,0,0,.32);
}
.start-stat b{display:block;font-family:'Barlow Condensed',sans-serif;font-weight:700;
  font-size:1.9rem;line-height:1.1;color:var(--red);}
.start-stat span{display:block;margin-top:.35rem;font-size:.78rem;line-height:1.4;
  color:#c9c9c9;font-weight:600;}
.start-stat i{display:block;margin-top:.25rem;font-size:.72rem;color:var(--gray-dim);
  font-style:italic;font-weight:400;}
.start-stats-line{max-width:46ch;margin:0 auto 2rem;line-height:1.6;}

/* the two section heads on this page - same job, one rule */
.start-ex h2,
.start-price h2{
  font-family:'Barlow Condensed',sans-serif;font-weight:800;
  font-size:clamp(1.9rem,3.8vw,2.7rem);line-height:1.1;margin-bottom:.6rem;
}
/* >> SECTION SEPARATOR AND SPACING, MODELLED ON THE AGENTS PAGE (owner, wd-s34). That page
   gives every section `padding:34px 0` and a `border-top`, so the gap above the line and the
   gap below it are the same number - which is what was wrong here, where sections carried a
   bottom margin and nothing else.
   >> THE LINE IS THE 7 STAR SECTION HAIRLINE, not the agents page's own rgba white: the house
   standard is `.5px solid #1a1a1a` at content width, and a translucent white line picks up
   whatever sits behind it, so on this lit backdrop it would change weight down the page.
   wd-s35: 3.4rem -> 6rem on every boundary (owner) - at 3.4rem an anchor jump put the heading
   under the top bar (bar 66px laptop / 56px phone). Keep this and `.start-head` equal. */
.start-ex,
.start-price,
.start-page .how{
  position:relative;text-align:center;margin:0;padding:6rem 0;
}
/* >> A LIGHT HAIRLINE, AND THE BRIEF WAS "subtle, goes with the background" (owner, wd-s34).
   Two versions were wrong before this one: the house `.5px solid #1a1a1a` hairline, which is
   written for a near-black page and reads as a hard dark mark drawn across a lit photograph,
   and a frosted darkening bar, which was still a dark line. A light translucent rule sits with
   the backdrop rather than on it. Content width, because a full-bleed line divides the PAGE
   and a content-width one separates two sections of it - that half of the hairline standard
   still holds. Recorded as a scoped deviation from the section-hairline standard: it applies
   to this page, whose ground is a lit picture rather than near-black. */
.start-ex::before,
.start-price::before,
.start-page .how::before{
  content:"";position:absolute;top:0;left:0;right:0;height:1px;
  background:rgba(255,255,255,.30);pointer-events:none;
}
.start-head{padding-bottom:6rem;}
.start-sub{margin:0 auto 2.4rem;max-width:46ch;}

/* ---------- THE EXAMPLES, AS A STICKY STACK ----------
   Donor: sites/example/veloura/site/assets/css/style.css, `.stack-list` / `.scard`.
   The card's fill is the stats page's `.stat`, value for value, so every stacked card on
   this site is the same object. The tail that lets the last card finish is in the page. */
.ex-stack{list-style:none;margin:0;padding:0;text-align:left;}
/* >> THE GAP BETWEEN CARDS *IS* THE HOLD (owner, wd-s34). In a sticky stack a card is
   covered by the next one only once that one has travelled up to the same line, so the
   scroll distance between two cards is `card height + this margin`. At 2.4rem that was
   723px on a laptop - less than one screen - so a card was covered almost as soon as it
   pinned and the caption had no chance to be read. At 55vh it is card height + 495px,
   about 1180px, so each card holds alone for roughly half a screen of scrolling before the
   next one starts to cover it.
   >> IT IS A vh, NOT A FIXED PIXEL VALUE, BECAUSE THE HOLD IS A READING TIME, not a
   distance - a tall screen scrolls further per turn of the wheel. The tail script below the
   list reads this value back out of the computed style, so the two cannot drift. */
.ex-card{
  position:sticky;top:6.5rem;margin-bottom:55vh;
  padding:clamp(1rem,2.4vw,1.6rem);border-radius:18px;
  background:linear-gradient(180deg, rgba(32,32,37,.80), rgba(15,15,18,.88));
  backdrop-filter:blur(16px) saturate(130%);
  -webkit-backdrop-filter:blur(16px) saturate(130%);
  border:1px solid rgba(255,255,255,.15);
  box-shadow:
    0 -18px 40px rgba(0,0,0,.55),
    0 26px 60px rgba(0,0,0,.62),
    inset 0 1px 0 rgba(255,255,255,.14),
    inset 0 -2px 0 rgba(0,0,0,.34);
}
/* the home page's card, out of its deck: the deck script absolutely positions `.work-item`
   and sizes it off --cardw, and there is no deck here */
.ex-card .work-item{position:relative;top:auto;left:auto;transform:none;
  width:100%;margin:0;filter:none;opacity:1;will-change:auto;}
.ex-card .work-tag{margin-top:1rem;}
/* the tail the script appends - an empty item so the last card has something to travel
   against; see the note on the script at the foot of start.html */
.ex-tail{list-style:none;}

/* ---------- THE OFFER PANEL ----------
   Shape copied from the agents page: the list of what you get, then two compact price
   tiles, then ONE button, then the terms note and the reply line. */
/* >> THE PANEL IS THE PAGE'S DARK CARD, NOT THE HOUSE .glass (owner, wd-s34 - "no words can
   describe how not correct this is"). It was built with `.glass`, whose fill is .04, and on a
   backdrop lit at 1.6 that is see-through: the picture read straight through the panel, the
   struck prices vanished and the whole block sat pale against the dark cards above it. The
   shared frosted-panel standard names this exact exception - a panel carrying body copy over
   another surface needs a heavier fill - and every other card on this page already uses it. */
.start-offer{
  padding:clamp(1.4rem,3vw,2.2rem);text-align:left;border-radius:18px;
  background:linear-gradient(180deg, rgba(32,32,37,.80), rgba(15,15,18,.88));
  backdrop-filter:blur(16px) saturate(130%);
  -webkit-backdrop-filter:blur(16px) saturate(130%);
  border:1px solid rgba(255,255,255,.15);
  box-shadow:0 8px 32px rgba(0,0,0,.45),0 2px 8px rgba(0,0,0,.35),
    inset 0 1px 0 rgba(255,255,255,.14),inset 0 -2px 0 rgba(0,0,0,.32);
}
/* the list is the home page's `.offer-list`, marker and all. `display:contents` on the plan
   block is what keeps the laptop layout as it was - lists on the first row, tiles on the
   second - while the phone shows each tile with its own items. */
.start-plans{display:grid;grid-template-columns:1fr;gap:0 2.4rem;margin-bottom:1.6rem;}
.start-plans .offer-list{margin:0 0 1.2rem;max-width:none;align-content:start;}


.start-tile{background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.16);
  border-radius:12px;padding:1rem 1.1rem;}
.start-tile .pname{font-size:.8rem;letter-spacing:.04em;text-transform:uppercase;
  font-weight:600;line-height:1.35;color:var(--gray);}
.start-tile .pamt{font-family:'Barlow Condensed',sans-serif;font-weight:700;
  font-size:2.2rem;line-height:1.1;margin-top:.4rem;}
.start-tile .pwas{font-size:.8rem;color:var(--gray);font-weight:500;margin-top:.2rem;}
/* >> THE STRUCK-THROUGH PRICE STAYS RECESSIVE, THE LINE AROUND IT DOES NOT. The shared
   text-on-a-background standard excludes struck-through old prices deliberately, and that
   still holds - but at --gray-dim the whole line was unreadable on a lit glass tile, so the
   line takes the panel treatment (colour and weight, no shadow) and only the <s> stays dim. */
.start-tile .pwas s{color:var(--gray-dim);font-weight:400;}

.start-cta{text-align:center;margin:.4rem 0 1.4rem;}
.start-note{line-height:1.55;}
.start-reply{margin-top:1.1rem;padding-top:1rem;border-top:1px solid rgba(255,255,255,0.07);}


/* >> THE SECTION BAND COMES OFF INSIDE A CENTRED COLUMN. `.how` carries rgba(0,0,0,0.19) as a
   full-bleed band on the home page; on a column it paints a stripe down the middle instead.
   This page already carries that exact value as its own page veil (the html::after rule). */
.start-page .how{background:transparent;}
.start-page .how-inner{max-width:100%;}

@media (min-width:660px){
  .start-plans{grid-template-columns:1fr 1fr;}
  .start-plan{display:contents;}
  .start-plan .offer-list{grid-row:1;}
  .start-plan .start-tile{grid-row:2;}
  .start-plan:nth-child(1) > *{grid-column:1;}
  .start-plan:nth-child(2) > *{grid-column:2;}
  .start-stats{grid-template-columns:repeat(4,1fr);}
}
@media (max-width:900px){
  /* phone hero spacing, moved up twice at 24px each (owner): 9.5rem -> 8rem -> 6.5rem */
  .start-page{padding:6.5rem 1.2rem 0;}
  .ex-card{top:5.5rem;}
  /* >> THE OFFER PANEL HAS TO FIT ONE PHONE SCREEN, and at the laptop's spacing it measured
     903px against an 844px viewport. Tightened rather than shortened: the six things you get
     are the six things you get, and cutting one to win 40px would be a copy decision made by
     a stylesheet. Measured after: it fits with room to spare. */
  .start-offer{padding:1.1rem 1.1rem 1.3rem;}
  .start-plans{margin-bottom:1.1rem;}
  .start-plans .offer-list{gap:.5rem;margin:.9rem 0 0;}
  /* >> NO DIVIDER LINE (owner, wd-s34): the tile sitting above its own items is the break. */
  .start-plan + .start-plan{margin-top:1.4rem;}
  .start-price-row{gap:.7rem;margin-bottom:1.1rem;}
  .start-tile{padding:.8rem .9rem;}
  .start-tile .pamt{font-size:1.9rem;}
  .start-cta{margin:.2rem 0 1rem;}
  .start-note{line-height:1.5;}
  .start-reply{margin-top:.9rem;padding-top:.8rem;}
  /* >> PHONE ONLY: THE WHOLE CARD IS THE LINK (owner, wd-s34). The picture and the "See what
     we can build for you" line are already inside the anchor; the caption paragraph is not.
     A thumb lands anywhere on the card, so the anchor is stretched over the whole card with
     a transparent overlay instead of the markup being rearranged - the caption stays outside
     the link for screen readers and the honesty line keeps its own place in the source. */
  .ex-card .work-link::after{
    content:"";position:absolute;inset:0;z-index:1;
  }
  .ex-card .work-tag{position:relative;}   /* text stays selectable above the overlay */

  /* >> RESERVE FIVE LINES OF CAPTION, AND IT IS THE SAME FIX VELOURA'S STACK NEEDED (wd-s30).
     The tail below the list is derived on the assumption that every card is the same height:
     a sticky card is released when the list's bottom rises past `top + ITS OWN height`, so a
     taller card is let go earlier and slides out from behind the stack while the others are
     still pinned. Measured at 390px the captions run 4, 4 and 5 lines - 86, 86 and 108px -
     so the cards came out 361, 361 and 383. Reserving the longest makes them equal again.
     RESERVE, DO NOT CLAMP: a line-clamp would equalise them by cutting a word off the end of
     the third caption, which is a copy change made by a stylesheet. If a caption is ever
     rewritten longer, this number goes up with it. */
  .ex-card .work-tag{min-height:calc(5 * 1.5em);}
}

/* the nav's quiet domain link takes the bar's own size, not the .95rem of a link in a
   sentence - same reason the contact-form links take theirs (wd-s12). */
.nav-domain{font-size:.9rem;font-weight:500;}
