/* ============================================================
   EVORRAH V15 — palette system
   Linen is the world. Onyx is the structure. Bordeaux is the
   signature. Deep Crimson is the spark. Alabaster is the depth.
   Black Cherry is tonal only.
   Scoped to / — does not touch brand.css tokens.
   ============================================================ */

/* ── metric-matched fallbacks · the home page's 0.128 CLS ────────────────────
   Both families come from Google Fonts with `display=swap`, so a first-time
   visitor is shown the fallback and then the real face swaps in. Measured on
   / at 1440x900: the swap lands at ~290ms and `.rc__sub` — the hero lede —
   REFLOWS FROM THREE LINES TO TWO, 113px to 75px, which moved the whole
   vertically-centred hero and scored 0.0888 of a 0.128 CLS. The <h1> is immune
   only because each of its lines is wrapped in its own .rv-line span; ordinary
   flowing copy is not.

   ⚠ IT IS A WIDTH PROBLEM, NOT A LINE-HEIGHT ONE, which is why measuring the
   h1 said nothing was wrong — its height never changed. Times New Roman sets
   the lede's own sentences about 1% wider than Crimson Pro, and that 1% was
   enough to push the line past the plate and wrap it. So the numbers below are
   taken from THE ACTUAL STRINGS THAT REFLOW ("From invitation to arrival," and
   "Evorrah handles the work behind your event.", the lede until 2026-09-15) rather than from a pangram:
   an A-Z/0-9 sample gives the serif 0.922 instead of 0.991, and matching the
   fallback to that would leave the prose still wrapping wrong.

   size-adjust makes the fallback occupy the width the real face will; the
   ascent/descent overrides are the font's own metrics divided by that same
   figure, so the line box does not move either. Nothing about the loaded
   typography changes — this only shapes the ~290ms before it arrives. */
@font-face{
  font-family:"Crimson Pro fallback";
  src:local("Times New Roman"),local("Times");
  size-adjust:99.10%;ascent-override:90.82%;descent-override:21.19%;line-gap-override:0%;
}
@font-face{
  font-family:"Inter fallback";
  src:local("Arial"),local("Helvetica");
  size-adjust:106.53%;ascent-override:91.06%;descent-override:22.53%;line-gap-override:0%;
}

:root{
  --linen:     #F8F1E7;
  --onyx:      #141414;
  --bordeaux:  #4C061D;
  /* ⚠ THE ACCENT IS TWO STOPS ON ONE FAMILY, NOT ONE COLOUR — 2026-08-31.
     Step 1 fixed the HUE. #AA1515 sat at hue 0deg while --bordeaux sits at
     340deg: a 40deg gap at high saturation reads as a near-miss rather than a
     contrast, which is what "the red doesn't match the burgundy" was. Both
     stops below sit in the 353-356deg band — on top of Black Cherry
     (#6B0410 = 353) and within sixteen of bordeaux — one family.

     Step 2 fixed the CONTRAST, which one stop cannot do. Measured against the
     three grounds, a single mid red is always failing one end:
         #CC242F   linen 4.85  alabaster 4.26  onyx 3.39
     The accent is not decoration — it carries "Most chosen", the part
     counts and the companion prices at --fs-label (11px), where AA wants
     4.5:1. So the light grounds take the deep stop and onyx takes the
     lifted one, and each clears 4.5:1 on the ground it is actually used on:
         --mahogany    #9D0617   linen 7.58 · alabaster 6.65
         --accent-lift #EB3743   onyx 4.53

     Step 3 fixed the SATURATION of the lifted stop, and it is why that stop
     is 82% and not the 70% it was first drawn at. 4.5:1 on a #141414 ground
     can only be bought with LIGHTNESS, and a light red reads ROSE unless
     saturation comes up with it — 82% sits inside the band the rest of the
     family occupies (bordeaux 85%, cherry 93%) and stops short of the vivid
     range where small red text on near-black starts to buzz.

     Step 4 replaced the DEEP stop: #C81420 -> #9D0617, owner, 2026-08-31.
     A wine red at 353deg/93%/32% instead of a mahogany at 356/82/43 — the
     same hue and saturation as Black Cherry, two stops lighter, so on the
     light grounds the accent now reads as part of the bordeaux/cherry family
     rather than beside it. It buys contrast instead of spending it: 7.58 on
     linen and 6.65 on alabaster, AA at 11px with room over. The cost is all
     at the dark end — 2.17:1 on onyx, 1.83:1 on bordeaux — which is exactly
     why the ground rebinds below exist and why no rule may read --mahogany
     directly. NOTE the two stops are no longer one hue at two exposures:
     353/93 deep against 356/82 lifted. They are close enough to read as one
     accent and they never appear on the same ground, so nothing compares
     them side by side.

     Rebound per ground below; every rule reads --accent, never a stop
     directly, so a ground change can never strand a colour on the wrong one.
     Comparison rig: app/red-9d0617-test.html. */
  --mahogany:  #9D0617;
  --accent-lift: #EB3743;
  --alabaster: #E7E3DD;
  --cherry:    #6B0410;

  /* portal-only accents (live inside the device frame, nowhere else) */
  --portal-green: #3D7A2E;
  --portal-red:   #C0281C;

  --serif: "Crimson Pro", "Crimson Pro fallback", "Times New Roman", serif;
  --sans:  "Inter", "Inter fallback", -apple-system, "Helvetica Neue", Helvetica, Arial, sans-serif;

  /* ---------- type tokens ----------
     Serif = Crimson Pro, the voice. Sans = Inter, the infrastructure.
     Display steps above 36px are one-off moments and each keeps its own
     job. Everything from 36px down is on the scale: three serif steps,
     three sans steps, one label. The portal mock inside the device frame
     runs its own 4.6-11px micro-system and is deliberately exempt. */
  --fs-d1:    clamp(44px,7.2vw,118px);  /* the one headline per page */
  --fs-mega:  clamp(38px,6.4vw,104px);  /* full-bleed statements */
  --fs-plan:  clamp(34px,5.4vw,84px);   /* plan names, the "handled" line */
  --fs-d2:    clamp(34px,5vw,80px);
  --fs-stat:  clamp(40px,4.6vw,72px);   /* counted numbers */
  --fs-d3:    clamp(26px,3.2vw,48px);
  --fs-d4:    clamp(24px,2.6vw,36px);
  --fs-d5:    clamp(21px,2.2vw,28px);
  --fs-lede:  clamp(18px,1.6vw,23px);

  --fs-read:  16px;  /* long-form prose pages only */
  --fs-body:  15px;  /* every running paragraph */
  --fs-cap:   13px;  /* list rows, meta, secondary */

  --fs-label:    11px;
  --ls-label:    .2em;
  --fs-label-lg: clamp(11px,1.2vw,14px);  /* the two display labels */
  --ls-label-lg: .28em;

  /* ---- weight (owner, 2026-08-31: "where you feel things could be bolder,
     make it bolder") -------------------------------------------------------
     The serif is untouched. Crimson Pro 300 above 20px IS the Evorrah voice and
     it only loads 200/300/400, so there is nothing to gain there. What was
     genuinely too faint is the SANS layer: 11px uppercase at .2em tracking,
     often at 56-66% opacity, rendered at the same 400 as the paragraph next
     to it. Two knobs, both real Inter faces already in every page's font
     request — nothing here costs a byte more to load.
       --fw-label  the micro-label idiom (--fs-label + --ls-label): eyebrows,
                   plan specs, buttons, nav, column heads, figcaptions.
       --fw-emph   inline <b>/<strong> inside running prose. The site carries
                   emphasis in ACCENT COLOUR rather than weight and that stays
                   true; 500 just stops the marked words dissolving back into
                   the sentence, which at 15px/400 they did.
     500 is the ceiling on purpose: "Most chosen" is the one 600 on the site
     (§plans) and outranking every label is its whole job. */
  --fw-label: 500;
  --fw-emph:  500;

  --gutter: clamp(20px, 5vw, 72px);
  --maxw: 1480px;

  /* the accent as used: rebound per ground, never referenced as a stop */
  --accent: var(--mahogany);
}

*{margin:0;padding:0;box-sizing:border-box}
html{scroll-behavior:smooth}
html,body{background:var(--linen)}
body{
  font-family:var(--sans);
  color:var(--onyx);
  font-size:var(--fs-body);
  line-height:1.6;
  -webkit-font-smoothing:antialiased;
  overflow-x:hidden;
}
img{max-width:100%;display:block}
a{color:inherit;text-decoration:none}
ul{list-style:none}
button{font:inherit;background:none;border:none;cursor:pointer;color:inherit}

/* ---------- placeholders ----------
   ⚠ AN UNSTYLED PLACEHOLDER IS NOT NEUTRAL, IT IS #757575 — Chrome's own grey,
   which measures 4.11 on --linen and fails AA at every size these forms use.
   It is also the one piece of text on the site no ordinary sweep reaches, being
   a pseudo-element on an element that has no text node of its own; it surfaced
   only when the rig grew a ::placeholder pass (contact's four fields, begin's
   "Tell us the occasion").
   currentColor rather than a fixed grey: the same declaration then holds on the
   dark grounds, where the input's own colour is --linen and a hard-coded grey
   would be the identical mistake pointing the other way. 64% measures 5.29 on
   linen, 5.02 on alabaster, 7.21 for a linen input on onyx.
   The explicit opacity:1 is for Firefox, which dims placeholders to .54 by
   default and would take 5.29 back down to about 3. */
::placeholder{color:color-mix(in srgb,currentColor 64%,transparent);opacity:1}

/* ---------- selection ----------
   ⚠ ::selection IS NOT INHERITED FROM AN ANCESTOR'S RULE. It is matched against
   the element the selected text lives in, so a dark ground needs its own
   DESCENDANT rule — rebinding a token on .g-bordeaux does nothing here.
   Without the two below, a drag across the bordeaux section painted bordeaux
   on bordeaux and left the text linen: the words really were selected and
   copied, and nothing on screen said so, which reads as "I cannot select this".
   Every dark ground therefore inverts: linen highlight, ground-coloured text. */
::selection{background:var(--bordeaux);color:var(--linen)}
.g-bordeaux::selection,.g-bordeaux ::selection,
.mnav::selection,.mnav ::selection{background:var(--linen);color:var(--bordeaux)}
.g-onyx::selection,.g-onyx ::selection{background:var(--linen);color:var(--onyx)}
/* The footer used to be listed on the line above. It is alabaster now (see
   §footer), so it takes the DEFAULT bordeaux-on-linen selection like every
   other light ground; the one exception inside it is .capture__go, which is
   still a dark plate and inverts for itself at .capture__go::selection. */

.wrap{max-width:var(--maxw);margin:0 auto;padding-left:var(--gutter);padding-right:var(--gutter)}

/* ---------- type scale ----------
   Serif runs weight 300 above 20px and weight 400 below it. Crimson Pro
   Light was drawn for display; at text size, faded against linen, it
   turns to grey mush. Anything serif under 20px carries .srf-sm. */
.d1{font-family:var(--serif);font-weight:300;font-size:var(--fs-d1);line-height:1.02;letter-spacing:-0.015em}
.d2{font-family:var(--serif);font-weight:300;font-size:var(--fs-d2);line-height:1.05;letter-spacing:-0.01em}
.d3{font-family:var(--serif);font-weight:300;font-size:var(--fs-d3);line-height:1.12}
.d4{font-family:var(--serif);font-weight:300;font-size:var(--fs-d4);line-height:1.15}
.d5{font-family:var(--serif);font-weight:300;font-size:var(--fs-d5);line-height:1.2}
.eyebrow{
  font-family:var(--sans);font-size:var(--fs-label);font-weight:var(--fw-label);
  letter-spacing:var(--ls-label);text-transform:uppercase;
  display:flex;align-items:center;gap:10px;
}
.eyebrow::before{content:"";width:22px;height:1px;background:var(--accent)}
.lede{font-family:var(--serif);font-weight:300;font-size:var(--fs-lede);line-height:1.5}
.small{font-size:var(--fs-cap);letter-spacing:.02em}
em.mh{font-style:normal;color:var(--accent)}
.srf{font-family:var(--serif);font-weight:300}
.srf-sm{font-family:var(--serif);font-weight:400}  /* serif below 20px */

/* ---------- nav ---------- */
.nav{
  position:fixed;inset:0 0 auto 0;z-index:200;
  display:flex;align-items:center;justify-content:space-between;
  padding:0 var(--gutter);height:76px;
  /* ⚠ THE BAR IS BLACK CHERRY, WHICH MAKES IT A DARK GROUND — owner,
     2026-08-31, picked in app/hero-ground-lab.html. IT WAS --bordeaux
     UNTIL THAT DATE and the reasoning below is unchanged by the swap, because
     cherry (353deg) and bordeaux (340deg) are the same family at two depths.
     Everything inked on it flips with it, and --accent is the one that is not
     obvious: the link underline is 1px of --mahogany, which measures 1.6:1 on
     the burgundy plate, i.e. an underline nobody can see. It goes linen here for exactly
     the reason .g-bordeaux does further down the file — any red on bordeaux is
     a near-miss rather than a contrast.
     94%, NOT THE 86% THE LINEN PLATE CARRIED. A pale plate can be that sheer
     because whatever scrolls under it is darker and reads as depth; a dark
     plate at the same transparency takes the colour of the section beneath it,
     so the bar shifts from burgundy to plum to near-black as the page moves.
     94% keeps the blur and stops the bar from changing its mind.
     ⚠ CHERRY IS THE LIGHTER OF THE TWO, SO EVERY RATIO ON THIS BAR DROPPED —
     measured over linen, the worst case, at the real 94% blend (#73121d
     against bordeaux's #561429): linen text 12.26 → 10.22, and .nav__login's
     72% mix 6.68 → 5.69. Both still clear AA at 11px with room, which is why
     nothing below needed re-tuning. Anything NEW inked on this bar must be
     measured against #73121d, not against the bordeaux figure. */
  background:color-mix(in srgb, var(--cherry) 94%, transparent);
  color:var(--linen);--accent:var(--linen);
  backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);
  transition:background .4s, transform .45s cubic-bezier(.4,0,.2,1);
}
.nav.is-hidden{transform:translateY(-100%)}

/* ⚠ THE MERGED NAV. Past the first screen the bar does not slide away — it
   COLLAPSES INTO THE MARK: the strip loses its linen plate, the links and the
   CTA fade out, the wordmark closes leftward, and the seven-circle mark
   (brand/EVORRAH_BRAND_SYSTEM_V15.html §01) rises inside a round pill sitting
   exactly where the E of EVORRAH was. Hovering it — or tabbing into the bar —
   plays the whole thing backwards, so the navigation is one gesture away and
   never actually leaves.
   Four constraints hold this together and none of them is decoration:
   1. NO MARKUP CHANGED. The header is hand-copied into ~75 pages, so the mark
      is a CSS mask on .nav__mark::after, not a second <img> per page. The SVG
      paints currentColor, which an <img> cannot inherit — a mask can.
   2. ⚠ THE PILL CLOSES, NOT THE IMAGE. max-width on the <img> looks equivalent
      and is not: the image carries height:20px with width:auto, so clamping its
      width SQUASHES the letterforms — EVORRAH condensing as it goes, which is
      the wordmark drawn wrong. So the ANCHOR's max-width closes (280px → 52px)
      while the image keeps its true width behind overflow:hidden, and the
      letters fade out well before the closing edge could reach them.
   3. LEFT-ALIGNED, NOT CENTRED. justify-content:flex-start pins the wordmark's
      left edge to the pill's, so the pill closes onto the E rather than sliding
      inward from both sides — the logo does not move on the page, it just
      loses its word.
   4. THE STRIP IS pointer-events:none WHEN MERGED, so a transparent 76px band
      does not eat clicks across the top of every page. The mark and the burger
      opt back in, and :hover restores the strip itself so the cursor can travel
      from the pill to the links without falling through the gap between them.
   TIMING. A transition reads the timing of the state it moves TO, so closing
   times live on .is-merged and opening times on the base and :hover rules —
   which is what makes the two directions differently paced rather than one
   played in rewind. Closing: letters go first (.22s), pill shuts under them
   (.5s), mark arrives last (.26s at +.2s). Opening: mark goes first, then the
   pill opens, then the letters land — always the outgoing thing before the
   incoming one, so the two never overlap in the same 52px. */
.nav__mark{
  position:relative;display:flex;align-items:center;justify-content:flex-start;
  flex:0 0 auto;  /* the wordmark must never be flex-shrunk into the clip */
  /* ⚠ border-radius:0 AT REST, 50% ONLY WHEN MERGED (2026-09-10, owner: "this is cut still").
     At rest this box is the wordmark's own 207x52, and 50% of a 207x52 box is an ELLIPSE:
     with overflow:hidden it cut the top and bottom off the E's stem and the H's right leg
     on every page (hit-tested on evorrah.com). EVR-087 measured only the merged 52x52
     seal, where 50% is a true circle — so the 50% lives on .nav.is-merged .nav__mark. */
  height:52px;min-width:52px;max-width:280px;border-radius:0;overflow:hidden;
  transition:max-width .5s cubic-bezier(.22,.61,.36,1) .06s,
             background .4s,box-shadow .4s;
}
.nav__mark img{
  /* ⚠ max-width:none UNDOES THE GLOBAL img{max-width:100%} ON LINE 99, which
     otherwise re-clamps the wordmark to the closing pill and SQUASHES the
     letterforms as it shuts (measured: 207.4px → 48.9px). flex:0 0 auto keeps
     flexbox from doing the same thing by a different route. The wordmark must
     keep its true width and be CLIPPED — a condensed EVORRAH is the wordmark
     drawn wrong, and it would be drawn wrong on every scroll. */
  height:20px;width:auto;max-width:none;flex:0 0 auto;transform-origin:left center;
  /* The wordmark SVG paints currentColor, and an <img> resolves that against
     its OWN document — black — not ours, so it cannot be recoloured by
     inheritance. Invert the black to white and let brightness hold it there.
     Safe to apply unconditionally — the only state that puts this image on a
     pale plate is the merged pill, where it is already opacity:0 and the
     ::after mask is the visible mark. (This used to point at .foot__mark img
     as the same case. It is the OPPOSITE case now: the footer is alabaster
     since 2026-08-31 and carries no filter at all.) */
  filter:invert(1) brightness(2);
  transition:opacity .3s cubic-bezier(.22,.61,.36,1) .22s,
             transform .45s cubic-bezier(.22,.61,.36,1) .18s;
}
.nav__mark::after{
  content:"";position:absolute;left:26px;top:50%;width:21px;height:23px;
  transform:translate(-50%,-50%) scale(.5) rotate(-25deg);opacity:0;pointer-events:none;
  background:var(--onyx);
  -webkit-mask:url(/assets/brand/evorrah-mark.svg) center/contain no-repeat;
  mask:url(/assets/brand/evorrah-mark.svg) center/contain no-repeat;
  transition:opacity .2s,transform .3s cubic-bezier(.4,0,1,1);
}
.nav.is-merged{background:transparent;backdrop-filter:none;-webkit-backdrop-filter:none;pointer-events:none}
.nav.is-merged .nav__mark,
.nav.is-merged .nav__burger{pointer-events:auto}
.nav.is-merged .nav__mark{
  max-width:52px;border-radius:50%;
  background:color-mix(in srgb, var(--linen) 92%, transparent);
  backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);
  box-shadow:0 2px 20px color-mix(in srgb, var(--onyx) 12%, transparent);
  transition:max-width .5s cubic-bezier(.65,0,.35,1) .04s,
             background .4s .1s,box-shadow .4s .1s;
}
.nav.is-merged .nav__mark img{
  opacity:0;transform:translateX(-10px) scale(.94);
  transition:opacity .22s cubic-bezier(.4,0,1,1),transform .4s cubic-bezier(.65,0,.35,1);
}
.nav.is-merged .nav__mark::after{
  opacity:1;transform:translate(-50%,-50%) scale(1) rotate(0deg);
  transition:opacity .26s .2s,transform .5s cubic-bezier(.34,1.28,.64,1) .18s;
}
/* ⚠ THE BOOKING BUTTON DOES NOT MERGE AWAY — it is the only thing on the bar
   that earns money, so it survives the collapse as a round arrow at the right
   edge. Hence the links fade INDIVIDUALLY (a:not(.btn-cta)) rather than the
   .nav__links row fading as a block: a faded parent cannot hold a solid child.
   The row's gap closes to 0 with them — on the timing set out below — or the
   collapsed links would leave six 30px holes and push the arrow off the gutter.
   font-size:0 is what removes "Start Your Event": the label is a bare text node
   inside the anchor, so it cannot be selected — but .arr is an element and can
   take its size back. Nothing is display:none anywhere here, because a link
   that is not in the layout cannot animate back into it. */
/* ⚠ THE FADE LEADS THE GEOMETRY, AND UNTIL 2026-09-04 IT DID NOT. Both the
   labels and the row's gap used to run on the base rule below — opacity .35s
   after a .12s delay — while `gap:0` carried no transition at all and so snapped
   on the first frame of the merge. Measured at 1440px: for the first ~220ms the
   row sat at gap 0 with every label still full width and fully opaque, which
   ran "How It Works What We've Handled Pricing About" together into one
   unbroken line of words before any of it began to fade. The labels now go out
   FIRST (.22s, no delay — the same beat the wordmark keeps three rules up), and
   the widths and the gap close under them from .18s, by which point there is
   nothing left to read. The gap is transitioned rather than snapped for the
   reason the pill closes rather than jumps; a browser that will not interpolate
   `gap` still gets the fix, because the fade is over before the snap. */
.nav.is-merged .nav__links{gap:0;transition:gap .45s cubic-bezier(.65,0,.35,1) .18s}
.nav.is-merged .nav__links a:not(.btn-cta){
  opacity:0;transform:translateY(-8px);max-width:0;pointer-events:none;overflow:hidden;
  transition:opacity .22s cubic-bezier(.4,0,1,1),
             transform .45s cubic-bezier(.65,0,.35,1) .18s,
             max-width .45s cubic-bezier(.65,0,.35,1) .18s;
}
.nav__links a:not(.btn-cta){max-width:220px;transition:opacity .35s .12s,transform .45s cubic-bezier(.22,.61,.36,1) .12s,max-width .45s cubic-bezier(.22,.61,.36,1) .12s}
.nav__links .btn-cta{
  overflow:hidden;
  transition:background .35s,color .35s,border-color .35s,
             width .5s cubic-bezier(.22,.61,.36,1) .06s,
             padding .5s cubic-bezier(.22,.61,.36,1) .06s,
             font-size .3s cubic-bezier(.22,.61,.36,1) .1s,
             box-shadow .4s;
}
.nav.is-merged .nav__links .btn-cta{
  width:52px;height:52px;padding:0;gap:0;font-size:0;
  background:var(--onyx);border-color:var(--onyx);color:var(--linen);
  box-shadow:0 2px 20px color-mix(in srgb, var(--onyx) 16%, transparent);
  pointer-events:auto;
  transition:background .35s,color .35s,border-color .35s,
             width .5s cubic-bezier(.65,0,.35,1) .04s,
             padding .5s cubic-bezier(.65,0,.35,1) .04s,
             font-size .22s cubic-bezier(.4,0,1,1),
             box-shadow .4s .1s;
}
.nav.is-merged .nav__links .btn-cta .arr{font-size:var(--fs-label);line-height:1}
.nav.is-merged .nav__links .btn-cta:hover{background:var(--cherry);border-color:var(--cherry)}
.nav.is-merged .nav__burger{
  background:color-mix(in srgb, var(--linen) 92%, transparent);
  backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);
  box-shadow:0 2px 20px color-mix(in srgb, var(--onyx) 12%, transparent);
  /* 15px, not 14: the three 1.5px bars plus two 5px gaps measure 14.5px, so
     14px padding gives a 43px pill — under the 44px minimum touch target, and
     this is the ONLY way to open the menu on a phone once the bar has merged. */
  border-radius:50%;padding:15px 14px;
  transition:background .4s .1s,box-shadow .4s .1s,
             padding .5s cubic-bezier(.65,0,.35,1) .04s;
}
.nav__links{transition:opacity .35s .12s,transform .45s cubic-bezier(.22,.61,.36,1) .12s,gap .45s cubic-bezier(.22,.61,.36,1) .12s}
/* ⚠ THE HOVER RE-OPEN IS DESKTOP-ONLY, BEHIND @media(hover:hover). A phone has
   no hover — it has a tap that STICKS as :hover on whatever was last touched,
   which would leave the bar jammed open over the page after any tap near it.
   Touch re-opens the bar the way it always has: the burger, which survives the
   merge as its own pill. :focus-within is deliberately NOT gated — it is how a
   keyboard reaches the bar, and it works the same on both. */
@media(hover:hover){
  /* Unmerge on hover / keyboard focus — the bar comes back exactly as it was.
     ⚠ "EXACTLY AS IT WAS" IS LITERAL: this blend must stay identical to the
     one on .nav itself, or the bar changes colour the moment you hover it.
     Both moved bordeaux → cherry together on 2026-08-31; change them together
     or not at all. */
  .nav.is-merged:hover,
  .nav.is-merged:focus-within{
    background:color-mix(in srgb, var(--cherry) 94%, transparent);
    backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);
    pointer-events:auto;
  }
  .nav.is-merged:hover .nav__mark,
  .nav.is-merged:focus-within .nav__mark{
    max-width:280px;border-radius:0;background:transparent;box-shadow:none;  /* square again, or the ellipse clips the returning wordmark */
    transition:max-width .5s cubic-bezier(.22,.61,.36,1) .06s,background .35s,box-shadow .35s;
  }
  .nav.is-merged:hover .nav__mark img,
  .nav.is-merged:focus-within .nav__mark img{
    opacity:1;transform:none;
    transition:opacity .3s cubic-bezier(.22,.61,.36,1) .22s,
               transform .45s cubic-bezier(.22,.61,.36,1) .18s;
  }
  .nav.is-merged:hover .nav__mark::after,
  .nav.is-merged:focus-within .nav__mark::after{
    opacity:0;transform:translate(-50%,-50%) scale(.5) rotate(-25deg);
    transition:opacity .18s,transform .3s cubic-bezier(.4,0,1,1);
  }
  /* ⚠ THE OPENING TIMING IS RESTATED ON BOTH OF THESE, and deleting it does
     not restore the base rule — it hands the re-open to the CLOSING one. A
     transition is read from the state being moved TO, and in the hovered state
     the .nav.is-merged rules above still match and out-specify plain
     .nav__links, so the numbers they carry would run the bar backwards out of
     the pill: labels back at .22s, before the row has the width to hold them.
     The values below are the base rule's, unchanged — keep the three in step. */
  .nav.is-merged:hover .nav__links,
  .nav.is-merged:focus-within .nav__links{
    gap:clamp(13px,1.9vw,30px);
    transition:opacity .35s .12s,transform .45s cubic-bezier(.22,.61,.36,1) .12s,
               gap .45s cubic-bezier(.22,.61,.36,1) .12s;
  }
  .nav.is-merged:hover .nav__links a:not(.btn-cta),
  .nav.is-merged:focus-within .nav__links a:not(.btn-cta){
    opacity:1;transform:none;max-width:220px;pointer-events:auto;
    transition:opacity .35s .12s,transform .45s cubic-bezier(.22,.61,.36,1) .12s,
               max-width .45s cubic-bezier(.22,.61,.36,1) .12s;
  }
  .nav.is-merged:hover .nav__links .btn-cta,
  .nav.is-merged:focus-within .nav__links .btn-cta{
    width:auto;height:auto;padding:14px 34px 14px 36px;gap:14px;font-size:var(--fs-label);
    background:transparent;border-color:var(--linen);color:var(--linen);box-shadow:none;
  }
  /* Fill can no longer be --bordeaux: the bar it sits on IS bordeaux, so the
     hover state would erase the button and leave floating linen text. It
     inverts instead, which is what .btn-cta--linen already does everywhere
     else on a dark ground. */
  .nav.is-merged:hover .nav__links .btn-cta:hover,
  .nav.is-merged:focus-within .nav__links .btn-cta:hover{background:var(--linen);border-color:var(--linen);color:var(--onyx)}
  /* The opening timing again, restated for the reason it is restated on
     .nav__links above: .nav.is-merged .nav__burger out-specifies the base rule
     and would otherwise run the re-open on the closing numbers. */
  .nav.is-merged:hover .nav__burger,
  .nav.is-merged:focus-within .nav__burger{
    background:transparent;box-shadow:none;padding:8px;
    transition:background .4s,box-shadow .4s,padding .5s cubic-bezier(.22,.61,.36,1) .06s;
  }
  /* ⚠ AND THE BARS COME BACK LINEN EVEN WITH NO GROUND RESOLVED. The stand-down
     further down the file is written as [data-ground], but motion.js only sets
     that attribute once it has found a section under the bar — before it does,
     and on any page with no ground section beneath the strip, the reopened
     burgundy bar was wearing the merged pill's onyx bars. Measured at (0,4,1),
     which clears the (0,3,1) restore next to the base rule and loses to the
     (0,5,1) ground rules, so the tinted pills still win where they should. */
  .nav.is-merged:hover .nav__burger i,
  .nav.is-merged:focus-within .nav__burger i{background:var(--linen)}
}
/* ⚠ !important IS LOAD-BEARING HERE, NOT SLOPPINESS. Without it this block is
   DEAD: every state rule above is a compound selector — .nav.is-merged
   .nav__mark is (0,3,0), .nav.is-merged:hover .nav__mark is (0,4,0) — so a
   plain .nav__mark{transition:none} at (0,1,0) loses on specificity no matter
   where it sits in the file. Measured before the fix: with the rule appended
   LAST, .nav.is-merged .nav__mark still reported 0.5s, 0.4s, 0.4s — a visitor
   who has asked their system for less motion was getting the whole animation.
   The same pattern at the .rv block below holds for the same reason. */
@media(prefers-reduced-motion:reduce){
  .nav,.nav__mark,.nav__mark img,.nav__mark::after,
  .nav__links,.nav__links a,.nav__links .btn-cta,.nav__burger{transition:none!important}
}

/* ⚠ THE MERGED PILL WEARS THE GROUND IT IS ON. nav[data-ground] is written by
   motion.js from the section actually behind the pill (.g-linen / .g-alab /
   .g-onyx / .g-bordeaux). The pill is then that ground's own colour with the
   mark flipped for contrast — so on the bordeaux acts the logo IS burgundy,
   and on the onyx acts it is black, instead of a cream disc riding over both.
   Two rules that are not style choices:
   · THE MARK ALWAYS FLIPS WITH THE PILL. A linen mark on a linen pill is an
     invisible logo, and this is the only navigation on screen once merged.
   · THE BUTTON FLIPS TOO, IN THE OPPOSITE DIRECTION. The collapsed CTA is onyx
     by default and would vanish into an onyx section; on both dark grounds it
     goes linen with an onyx arrow. It is the buy path — it can never be the
     thing that disappears.
   These apply ONLY while merged: the full bar carries its own linen plate, and
   the wordmark on it is already onyx-on-linen at every scroll position. */
.nav.is-merged[data-ground="alab"] .nav__mark{background:color-mix(in srgb, var(--alabaster) 92%, transparent)}
.nav.is-merged[data-ground="bordeaux"] .nav__mark,
.nav.is-merged[data-ground="bordeaux"] .nav__burger{
  background:color-mix(in srgb, var(--bordeaux) 92%, transparent);
  box-shadow:0 2px 20px color-mix(in srgb, var(--onyx) 26%, transparent);
}
.nav.is-merged[data-ground="onyx"] .nav__mark,
.nav.is-merged[data-ground="onyx"] .nav__burger{
  background:color-mix(in srgb, var(--onyx) 92%, transparent);
  box-shadow:0 2px 20px color-mix(in srgb, var(--onyx) 34%, transparent);
}
.nav.is-merged[data-ground="bordeaux"] .nav__mark::after,
.nav.is-merged[data-ground="onyx"] .nav__mark::after{background:var(--linen)}
.nav.is-merged[data-ground="bordeaux"] .nav__burger i,
.nav.is-merged[data-ground="onyx"] .nav__burger i{background:var(--linen)}
.nav.is-merged[data-ground="bordeaux"] .nav__links .btn-cta,
.nav.is-merged[data-ground="onyx"] .nav__links .btn-cta{
  background:var(--linen);border-color:var(--linen);color:var(--onyx);
  box-shadow:0 2px 20px color-mix(in srgb, var(--onyx) 30%, transparent);
}
.nav.is-merged[data-ground="bordeaux"] .nav__links .btn-cta:hover,
.nav.is-merged[data-ground="onyx"] .nav__links .btn-cta:hover{
  background:var(--cherry);border-color:var(--cherry);color:var(--linen);
}
/* ⚠ ≤1000px THE MERGED NAV KEEPS A PLATE, because below that width the two
   floating discs stop clearing the content column and start covering it.
   Measured on the live page at the Fully Handled row (2026-09-04, owner asked
   for the fix), pill x-range against the row it sits over:
       1280   mark 64–116, title at 121  → clear; only .prow__num is grazed
        768   mark 38–90,  title at  82  → title covered 9×30, and the BURGER
                                           takes 50×26 of it plus 50×9 of the price
        375   mark 20–72,  title at  53  → title covered 19×22 by the mark and
                                           50×18 by the burger, plus 50×8 of the price
   THE DESIGN IS RIGHT AT DESKTOP and wrong only where the gutter is too narrow
   to hold the pill outside the column, so the fix is scoped to those widths and
   nothing above 1000px changes. It is also why this reads as a rendering bug
   rather than a floating logo: the pill is deliberately the ground's OWN colour
   (see the [data-ground] block above), so a reader does not see a disc over the
   words — they see the words cut in half, with nothing to explain the cut.
   The plate wears that same ground, so "merged" still means merged INTO the
   section rather than a bar bolted onto it; and the discs give up their own
   plates here, because a disc on a plate of the identical colour is only a
   shadow. THE PLATE IS SOLID, not the pill's 92%-plus-blur: at 52px that reads
   as frosted glass, but across the full width the 8% that comes through is a
   legible smear of the words underneath — measured on the first cut of this
   fix. Solid ground is also the only value that makes the bar vanish into the
   section, which is the whole point of calling it merged. The mark glyph and the burger bars keep the contrast flips the
   [data-ground] rules already give them, so nothing needs re-checking there.
   ⚠ [data-ground] IS IN THE LAST SELECTOR TO MATCH SPECIFICITY, NOT FOR EFFECT.
   The tint rules above are (0,4,0) compound selectors, so a plain
   .nav.is-merged .nav__mark at (0,3,0) would lose to them wherever it sat in
   the file — the same trap the prefers-reduced-motion block documents. */
@media (max-width:1000px){
  .nav.is-merged{background:var(--linen)}
  .nav.is-merged[data-ground="alab"]{background:var(--alabaster)}
  .nav.is-merged[data-ground="bordeaux"]{background:var(--bordeaux)}
  .nav.is-merged[data-ground="onyx"]{background:var(--onyx)}
  .nav.is-merged[data-ground] .nav__mark,
  .nav.is-merged[data-ground] .nav__burger,
  .nav.is-merged .nav__mark,
  .nav.is-merged .nav__burger{background:transparent;box-shadow:none}
}

/* Re-opening the bar brings its own linen plate back, so the ground tints must
   stand down — a bordeaux pill would then be sitting on linen. Split in two on
   purpose: the :hover half is gated to hover-capable pointers for the same
   reason as the block above (a sticky tap-hover would otherwise clear the pill
   colour and leave a dark mark on a dark section, i.e. no visible logo on a
   phone), the :focus-within half is not, because a keyboard has no pointer. */
@media(hover:hover){
  .nav.is-merged[data-ground]:hover .nav__mark,
  .nav.is-merged[data-ground]:hover .nav__burger{background:transparent;box-shadow:none}
  .nav.is-merged[data-ground]:hover .nav__mark::after{background:var(--onyx)}
  .nav.is-merged[data-ground]:hover .nav__burger i{background:var(--linen)}
  /* ⚠ AND THE BUTTON HAS TO BE SAID TWICE. The ground rule and the hover rule
     both weigh (0,5,0), so the later one — the ground — would win and leave a
     linen button with a linen border on the reopened linen bar, i.e. an
     invisible Start Your Event. Naming both in one selector outweighs both. */
  .nav.is-merged[data-ground]:hover .nav__links .btn-cta{
    background:transparent;border-color:var(--linen);color:var(--linen);box-shadow:none;
  }
  .nav.is-merged[data-ground]:hover .nav__links .btn-cta:hover{
    background:var(--linen);border-color:var(--linen);color:var(--onyx);
  }
}
.nav.is-merged[data-ground]:focus-within .nav__mark,
.nav.is-merged[data-ground]:focus-within .nav__burger{background:transparent;box-shadow:none}
.nav.is-merged[data-ground]:focus-within .nav__mark::after{background:var(--onyx)}
.nav.is-merged[data-ground]:focus-within .nav__burger i{background:var(--onyx)}
.nav.is-merged[data-ground]:focus-within .nav__links .btn-cta{
  background:transparent;border-color:var(--onyx);color:var(--onyx);box-shadow:none;
}
/* ⚠ AND THE FOCUS HALF FLIPS ONLY WHERE THE BAR ACTUALLY REOPENS. The three
   rules above are deliberately ungated (a keyboard has no pointer), but the
   reopen they are standing down FOR lives inside @media(hover:hover) — so on a
   touch device focusing the merged nav clears the ground tint without ever
   bringing the burgundy plate back. Flipping their ink to linen up there would
   put a linen border on a transparent button over a LINEN section: an
   invisible buy path on a phone. So the flip is added here, gated, at equal
   specificity and later in the file, and the ungated onyx stays correct for
   the state it actually describes. */
@media(hover:hover){
  .nav.is-merged[data-ground]:focus-within .nav__burger i{background:var(--linen)}
  .nav.is-merged[data-ground]:focus-within .nav__links .btn-cta{
    background:transparent;border-color:var(--linen);color:var(--linen);box-shadow:none;
  }
}
/* ⚠ THE GAP AND THE BREAKPOINT ARE SIZED TO THE LINK COUNT, AND THE COUNT
   CHANGED. The bar carried four links; Pricing and Client login make six, and
   at the old clamp(18px,3vw,44px) the six links measured 951px against a 1280px
   viewport — the row began at x=265 and the wordmark ended at x=265, i.e. they
   met at exactly zero gap, with "What We Solve" (renamed "Why Evorrah") and "Events, Handled" wrapping
   to two lines to fit. Hence a tighter gap, nowrap so a label can never break
   mid-phrase, an explicit floor between mark and links, and a handoff to the
   burger at 1080px instead of 900px — the range where six links stop fitting
   is above 900, so the old breakpoint left the crowded state on screen. */
/* ⚠ THE BAR IS SPLIT, NOT ONE RIGHT-HAND CLUSTER (owner, 2026-08-31: "it
   doesn’t make sense that About is on the right side"). The four site links
   ride with the wordmark on the LEFT; only the account pair — Login and
   the booking CTA — sits at the right gutter, so "who we are" is no longer
   shoulder-to-shoulder with the buy button. flex:1 here + margin-left:auto on
   .nav__login (below) is the whole mechanism: NO MARKUP CHANGED, because the
   merged-nav machinery above addresses .nav__links as one element and the
   header is stamped into 80+ pages by scripts/v15-shell.mjs. */
.nav__links{display:flex;align-items:center;gap:clamp(13px,1.9vw,30px);margin-left:36px;flex:1 1 auto}
.nav__links a{white-space:nowrap}
/* The bar's CTA is an outline button and the outline was --onyx: 1.4:1 on
   burgundy, a button you can only find by hovering it. On the bar it wears the
   .btn-cta--linen treatment instead — linen outline, inverting to a linen fill
   with onyx text. Left at (0,3,0) on purpose: every .nav.is-merged rule above
   is (0,4,0) or higher, so the collapsed arrow keeps its own onyx/ground
   colours and only the OPEN bar is restyled here. */
.nav__links .btn-cta{border-color:var(--linen);color:var(--linen)}
.nav__links .btn-cta:hover{background:var(--linen);border-color:var(--linen);color:var(--onyx)}
.nav__links a:not(.btn-cta){
  font-size:var(--fs-label);font-weight:var(--fw-label);letter-spacing:var(--ls-label);text-transform:uppercase;
  position:relative;padding:6px 0;
}
.nav__links a:not(.btn-cta)::after{
  content:"";position:absolute;left:0;bottom:0;height:1px;width:0;
  background:var(--accent);transition:width .35s cubic-bezier(.4,0,.2,1);
}
.nav__links a:not(.btn-cta):hover::after,
.nav__links a:not(.btn-cta)[aria-current]::after{width:100%}

/* ⚠ THREE RADII EXIST — 0, 2px, 50% — AND NOTHING ELSE (EVR-087, brand book
   §"Rounded corners", brand.json tokens.radius). Until 2026-09-09 thirteen
   rules in this file drew a 999px stadium; the homepage alone rendered seven
   of them as buttons. Engraving has square corners, so the rule now reads:
   ·  A TEXT BUTTON OR CHIP IS SQUARE — radius 0, the book's default. Swept:
      .btn-cta (here), .chaos__words span, .ecard__plan, .hcard__plan,
      .mod-tier, .mod-inc, .pf-chip, .made span, .ck__btn, .efm__go,
      .coll__ex a. 2px is the INPUT radius and is deliberately NOT used on a
      button — a 2px button and a 2px field would read as the same control.
   ·  A CIRCULAR CONTROL IS 50% — the merged .nav__mark (52x52, a seal
      carrying the E) and the merged .nav__burger, the two discs the comment
      above already calls "one gesture". 50% is the only radius that keeps
      being a disc when the box changes size, which both of those do.
   The portal DRAWING further down (.p*, the mock inside .device__screen) is
   left alone on purpose: it depicts software on a screen, not stationery. */
.btn-cta{
  display:inline-flex;align-items:center;justify-content:center;gap:14px;
  font-size:var(--fs-label);font-weight:var(--fw-label);letter-spacing:var(--ls-label);text-transform:uppercase;
  border:1px solid var(--onyx);border-radius:0;
  padding:14px 34px 14px 36px;color:var(--onyx);
  transition:background .35s,color .35s,border-color .35s;
  white-space:nowrap;line-height:1;
  /* WCAG 1.4.10 · nowrap plus 70px of horizontal padding makes the longest CTA
     ("Start with Fully Handled →") 300px wide, which overran a 280px column at
     320px and had its arrow CLIPPED by the plan panel's overflow:hidden.
     max-width lets the button shrink instead; the narrow-screen rule at the end
     of this file lifts the nowrap and trims the padding once even that is not
     enough. */
  max-width:100%;
}
.btn-cta .arr{letter-spacing:0}
.btn-cta .arr{transition:transform .35s}
.btn-cta:hover{background:var(--bordeaux);border-color:var(--bordeaux);color:var(--linen)}
.btn-cta:hover .arr{transform:translateX(4px)}
.btn-cta--solid{background:var(--onyx);color:var(--linen)}
.btn-cta--solid:hover{background:var(--cherry);border-color:var(--cherry)}
.btn-cta--linen{border-color:var(--linen);color:var(--linen)}
.btn-cta--linen:hover{background:var(--linen);border-color:var(--linen);color:var(--onyx)}

/* mobile nav */
.nav__burger{display:none;flex-direction:column;gap:5px;padding:8px;
  /* The merged pill below computes its padding to clear 44px explicitly. The
     UNMERGED burger did not, and measured 38x31 — the same control, the same
     job, under the Evorrah minimum on every phone before the bar merges. The
     min-* pair holds the floor without touching the padding, so the merged
     rule and the :hover rule that resets padding to 8px both stay at 44. */
  min-width:44px;min-height:44px;align-items:center;justify-content:center;box-sizing:border-box;
  /* ⚠ THE BURGER IS HALF OF A PAIR AND HAS TO MOVE LIKE IT. It carried no
     transition of its own until 2026-09-04 — only its three bars did — so every
     merge jumped it in a single frame while the mark beside it eased: the box
     8px → 15px/14px (44 → 50 wide, measured), and between 1001 and 1080px the
     plate and the shadow arrived at full strength on frame one while the mark's
     plate faded in over .4s. Two discs meant to read as one gesture, one of
     them popping. These are the mark's OPENING numbers; the closing ones sit on
     .nav.is-merged with the rest of the closing timing. Below 1000px the discs
     are transparent (the ground rules further down), so only the padding is
     doing anything there — which is the frame the finger is resting on. */
  transition:background .4s,box-shadow .4s,padding .5s cubic-bezier(.22,.61,.36,1) .06s}
/* Linen on the burgundy bar; back to onyx once the bar has merged, because the
   burger is then a pale pill on the page rather than three bars on the plate.
   The [data-ground] rules for the bordeaux and onyx acts are (0,4,1) and still
   win over the restore, which is what keeps the pill's bars flipping with it. */
.nav__burger i{display:block;width:22px;height:1.5px;background:var(--linen);transition:transform .3s,opacity .3s}
.nav.is-merged .nav__burger i{background:var(--onyx)}
/* ⚠ THE OVERLAY FOLLOWS THE BAR, bordeaux → cherry with it on 2026-08-31.
   It is not a separate colour decision: .mnav IS the bar on a phone, and the
   rule below drops the bar's plate on the stated grounds that "both are now
   the same" colour. Leave one bordeaux and the other cherry and that sentence
   becomes false, the bar and its own menu become two near-miss reds, and the
   seam the rule exists to remove comes back as a colour step instead. */
.mnav{
  position:fixed;inset:0;z-index:190;background:var(--cherry);color:var(--linen);
  display:flex;flex-direction:column;justify-content:center;padding:0 var(--gutter);
  opacity:0;pointer-events:none;transition:opacity .4s;
}
.mnav.open{opacity:1;pointer-events:auto}
/* ⚠ THE BAR DROPS ITS PLATE WHILE THE MENU IS OPEN. The bar sits at z-index 200
   over the overlay's 190, and both are now the same burgundy — so the bar's own
   94% plate and its backdrop-filter buy nothing and cost a faint seam across
   the top of the menu, where the blur samples past the top edge. This mattered
   the other way round when the bar was linen: a pale strip over a bordeaux
   overlay was a deliberate divider. It is not one any more. */
body.mnav-open .nav{background:transparent;backdrop-filter:none;-webkit-backdrop-filter:none}
.mnav a{font-family:var(--serif);font-weight:300;font-size:clamp(34px,9vw,56px);line-height:1.3;border-bottom:1px solid color-mix(in srgb,var(--linen) 16%, transparent)}
.mnav a:last-of-type{border-bottom:none}
/* ⚠ THE MENU MUST FIT, OR SCROLL, ON A PHONE HELD SIDEWAYS — measured 2026-09-10, Launch Hub
   check 02-04 (scripts/launch-readiness/ws02-04-device-matrix.mjs). `justify-content:center` in
   a fixed box that does not scroll centred six 56px links (9vw of a 667–838px-wide screen hits
   the 56px cap) in a 320–390px-tall screen: the list overflowed BOTH ends, the first link sat
   26–61px above the screen under the bar and the last below it, and nothing could scroll to
   either. Three to five of the six links were unreachable on every phone measured on its side
   (iPhone SE, 16, 16 Pro Max; Galaxy S9+, S24; Pixel 7); every upright phone and every tablet
   was fine. Three moves, each needed:
   · auto margins on the first and last link centre the list exactly as before when it fits
     (so an upright phone and a tablet render unchanged) and collapse to zero when it does not,
     so an overflowing list starts at the top of the box instead of above it;
   · the box scrolls (overscroll contained, so the page behind does not move);
   · a short screen reserves the 76px the bar (z-index 200) covers, and the type steps down to
     8.5vh (27–40px) — six links then fit a 320px screen with room to spare, and the scroll is
     only the safety net for a larger text setting. */
/* ⚠ AND IT SCROLLS ABOVE THE CONSENT BAR (owner, 2026-09-10): the tap that opens the menu is
   also the tap that shows #cookieBanner (site.js), and on a phone on its side the bar sat over
   the last two or three links until answered. site.js publishes the bar's measured height as
   --ck-h while it is up (0 once answered); the menu pads its bottom by it, so the list centres
   in, and scrolls within, the space above the bar. */
/* ⚠ justify-content MUST BE flex-start WHERE THE LIST CAN OVERFLOW — and ONLY there.
   Corrected 2026-09-11 (02-04), then SCOPED the same day (WS02 close), and both halves matter.

   The correction: the first version of the comment above was wrong about why the auto margins
   were enough. With free space the auto margins centre the list and justify-content is ignored;
   with NEGATIVE free space the auto margins resolve to 0 and justify-content applies again, so
   `center` split the overflow and put the first link ABOVE the scroll origin, where no scroll
   reaches. The consent-bar padding made the list overflow on a 360px-tall Android held sideways
   and "How It Works" sat at 20-62px, under the bar, on the Galaxy S24, S9+ and Pixel 7.

   The scoping: that correction was written as an UNCONDITIONAL declaration on .mnav, which also
   changed an upright phone — where it renders identically (the auto margins take the free space)
   but the computed value is no longer `center`, which is what
   test/v15-mobile-menu-short-screen.test.mjs case 2 measures, and it went red on the merge.
   Deleting the peer's fix would put the 360px-tall Android back, so the declaration MOVED into a
   height query instead of going away. 620px is measured, not guessed: six links cap at 56px above
   ~622px of width and stack to 442px, and the real consent bar measures 75px on a wide screen and
   132-152px on a narrow one (app/live served through Playwright, 2026-09-11), so the list can
   overflow up to ~574px of height. 620px clears that with room for a larger text setting, and
   every upright phone (667px and up) keeps `center`. */
.mnav{overflow-y:auto;overscroll-behavior:contain;padding-bottom:var(--ck-h,0px)}
.mnav a:first-of-type{margin-top:auto}
.mnav a:last-of-type{margin-bottom:auto}
@media (max-height:620px){
  .mnav{justify-content:flex-start}
}
/* ⚠ A PHONE ON ITS SIDE IS 390px TALL, AND THE MENU IS TALLER THAN THAT (2026-09-10, check 02-03).
   Six links at clamp(34px,9vw,56px) × 1.3 stack to ~444px. Centred by justify-content:center in a
   fixed overlay that could not scroll, the overflow spilled off BOTH ends: "How It Works" sat at
   y=-26, under the 76px bar, and could not be reached — measured in Chrome, WebKit and Firefox at
   844×390 (scripts/launch-readiness/ws02-2c-device-browser-matrix.mjs). On a short screen the list
   now starts below the bar and the overlay scrolls; a tall screen still centres it (the first link
   of an upright 390×844 phone measures 283px with or without this block).

   ⚠ ORDER IS THE RESOLUTION (WS02 close, 2026-09-11). Checks 02-03 and 02-04 found the SAME defect
   and fixed it twice. This 520px block is kept for the 501–520px band that 02-04 never measured —
   there it is the whole fix, and the menu scrolls from under the 88px it reserves. At 500px and
   below the 02-04 block underneath SUPERSEDES it: it was retested 66/66 on six phones on their
   side, and it reserves the bar's MEASURED 76px (pinned to `.nav`'s own height by
   test/website-phone-menu-scrolls.test.mjs) and steps the type to 8.5vh so the six links FIT
   instead of only scrolling. Padding is not doubled — the later block overrides the same two
   declarations — and `justify-content:flex-start` here now restates what the 620px block above
   already sets (it is the same declaration, not a second one): it is kept because this block is
   the one 02-03 measured, and a reader who opens it should see the value it was measured with.
   ⚠ It is NOT inert, which an earlier version of this sentence claimed: the auto margins take
   the free space only when there IS free space, and this whole block exists for screens where
   there is none. Keep the 02-04 block LAST. */
@media (max-height:520px){
  .mnav{justify-content:flex-start;overflow-y:auto;overscroll-behavior:contain;padding-top:88px;padding-bottom:24px}
}
@media (max-height:500px){
  .mnav{padding-top:76px;padding-bottom:max(16px,var(--ck-h,0px))}
  .mnav a{font-size:clamp(26px,8.5vh,40px)}
}
@media(max-width:1080px){
  .nav__links{display:none}
  .nav__burger{display:flex}
  body.mnav-open{overflow:hidden}
  body.mnav-open .nav__burger i:nth-child(1){transform:translateY(6.5px) rotate(45deg);background:var(--linen)}
  body.mnav-open .nav__burger i:nth-child(2){opacity:0}
  body.mnav-open .nav__burger i:nth-child(3){transform:translateY(-6.5px) rotate(-45deg);background:var(--linen)}
}

/* ---------- section grounds ---------- */
/* ⚠ THE GROUND DECIDES WHICH STOP OF THE ACCENT IS LEGIBLE ON IT.
   onyx takes the lifted stop (--accent-lift, 4.53:1 — the base is 3.15 there);
   bordeaux takes linen outright, because ANY red on bordeaux is 2.9:1 or
   worse and a 1px accent hairline on it is invisible rather than subtle. */
.g-onyx{--accent:var(--accent-lift)}
.g-bordeaux{--accent:var(--linen)}
.g-linen{background:var(--linen);color:var(--onyx)}
.g-alab{background:var(--alabaster);color:var(--onyx)}
.g-onyx{background:var(--onyx);color:var(--linen)}
.g-bordeaux{background:var(--bordeaux);color:var(--linen)}
section{position:relative}
.sec-pad{padding-top:clamp(80px,11vh,150px);padding-bottom:clamp(80px,11vh,150px)}

/* ---------- reveal motion ---------- */
.rv{opacity:0;transform:translateY(34px);transition:opacity .9s cubic-bezier(.22,.6,.2,1),transform .9s cubic-bezier(.22,.6,.2,1)}
.rv.in{opacity:1;transform:none}
.rv-d1{transition-delay:.12s}.rv-d2{transition-delay:.24s}.rv-d3{transition-delay:.36s}.rv-d4{transition-delay:.48s}
.rv-line{display:block;overflow:hidden}
.rv-line span{display:block;transform:translateY(110%);transition:transform 1.1s cubic-bezier(.22,.6,.2,1)}
.in .rv-line span{transform:none}
.rv-line:nth-child(2) span{transition-delay:.1s}
.rv-line:nth-child(3) span{transition-delay:.2s}
@media(prefers-reduced-motion:reduce){
  .rv,.rv-line span{opacity:1!important;transform:none!important;transition:none!important}
  html{scroll-behavior:auto}
}

/* ---------- footer ---------- */
/* ============================================================
   THE FOOTER IS ALABASTER — 2026-08-31 (owner: "linen or grey
   background … no black background"). It was onyx from v15's
   first commit, which meant every page ended in a black slab.

   This is the SAME migration #plans took (style.css §#plans.g-alab)
   and it obeys the same two rules:

   1. ⚠ THE ALPHA STOPS ARE NOT THE OLD NUMBERS INVERTED. On onyx a
      linen mix at 56% measures 7.4:1; 56% of onyx over #E7E3DD is
      3.92:1 and FAILS AA at --fs-label (11px), which is what the
      column headings, the newsletter label, the consent line and
      the legal row are all set in. 62% is the 4.5:1 floor on this
      ground, so the muted stop is 64% (5.00) and the body stops sit
      at 86-88% (10.2-10.8). Re-measure before lowering any of them.
      Hairlines follow the light-ground convention: 14% for rules,
      50% for the one control border that owes 3:1 (1.4.11).
   2. THE GROUND REBINDS THE ACCENT, so --accent is no longer forced
      to the lifted stop here: :root's --mahogany is 6.65:1 on
      alabaster, and --accent-lift would be 2.9:1. The consent bar
      is still onyx and keeps the rebind for itself.

   The <footer> also carries g-alab in the markup now. That class is
   what motion.js:40 probes to colour the merged nav pill; with no
   ground class the pill fell through to '' and wore linen over the
   footer at the bottom of a short page.
   ============================================================ */
.ck{--accent:var(--accent-lift)}
.foot{background:var(--alabaster);color:var(--onyx);padding:clamp(60px,8vh,110px) 0 40px}
/* ⚠ FOUR CHILDREN, FOUR TRACKS — recounted 2026-09-02, when the "The platform"
   column (the hub plus the twelve service pages) was removed from the footer
   site-wide. The brand block plus THREE link columns (Site, Plans, Contact).
   The track count is written here by hand and nothing derives it from the DOM,
   so it has to move in the same change as the column: left alone, `repeat(4,…)`
   would have parked an empty fourth track on the right and squeezed the real
   content into 4/5 of the container. That is the same failure this note
   recorded on 2026-08-31, when it read `1.4fr repeat(5,1fr)` for four columns.
   Count the children in scripts/v15-shell.mjs's footer(), not in this comment.
   The brand track takes 1.9fr because it carries the wordmark, the tagline AND
   the newsletter form, which needs ~420px before the input and button stop
   sharing a line. */
.foot__grid{
  display:grid;grid-template-columns:1.9fr repeat(3,minmax(0,1fr));
  gap:clamp(28px,3.4vw,56px);margin-bottom:clamp(56px,7vh,78px);
}
/* The footer columns are h2, not h4 — see scripts/v15-shell.mjs. They used to
   be h4 and produced an h2→h4 jump on 76 of 80 pages (WCAG 1.3.1). The class
   carries the look so the level is free to be whatever the outline needs. */
.foot h4,.foot .foot__h{font-size:var(--fs-label);letter-spacing:var(--ls-label);text-transform:uppercase;font-weight:var(--fw-label);color:color-mix(in srgb,var(--onyx) 66%,transparent);margin-bottom:18px}
.foot li{margin-bottom:11px}
/* AA: `opacity:.85` here multiplied against .foot__base's own mixed colour and
   put the legal row (Terms · Privacy · Text messages · Security · Reliability)
   at 3.36:1 on onyx. The dimming is now a colour rather than an alpha, so it
   composes once instead of twice and can be measured where it is written.
   Same stop, mixed the other way for the alabaster ground: 10.79:1. */
.foot a{font-size:var(--fs-cap);color:color-mix(in srgb,var(--onyx) 88%,var(--alabaster));transition:opacity .3s,color .3s}
.foot a:hover{opacity:1;color:var(--accent)}
/* NO FILTER. The wordmark SVG paints currentColor and an <img> resolves that
   against its OWN document — black — so on the onyx footer this carried
   `filter:invert(1) brightness(2)` to turn it white (the note at .nav__mark img
   points here for the explanation). On alabaster the untouched black is the
   right mark and the filter would paint white on cream. */
/* ⚠ display:inline HERE MEANS NO FOCUS RING AT ALL, and the computed style
   does not admit it. This anchor wraps nothing but the wordmark <img>; as an
   inline box Chromium reports outline:2px solid #9d0617 on :focus-visible and
   paints none of it. Proved by forcing a 4px lime ring on the focused link at
   1440x900: still not one pixel, and it appeared the instant display became
   inline-block. So the site's own home link — the last stop before the end of
   the footer — took keyboard focus invisibly. That is 2.4.7 Focus Visible, a
   Level A failure, and no checker that reads outlineColor off the DOM can see
   it; scripts/v15-focus-check.mjs measures the pixels for this reason.
   line-height:0 keeps the box exactly the image's 30px, so inline-block does
   not add the baseline gap under the mark. */
.foot__mark{display:inline-block;line-height:0}
.foot__mark img{height:30px}
.foot__tag{font-family:var(--serif);font-weight:400;font-size:18px;margin-top:16px;max-width:300px;line-height:1.4}
.foot__base{display:flex;justify-content:space-between;align-items:center;border-top:1px solid color-mix(in srgb,var(--onyx) 14%,transparent);padding-top:28px;font-size:var(--fs-label);letter-spacing:.06em;color:color-mix(in srgb,var(--onyx) 66%,transparent);flex-wrap:wrap;gap:12px}
/* ── the legal row (© · Terms · Privacy · Text messages · Security · Reliability) ──
   Six items on one line at 1280 and five lines' worth of words at 390, so it is
   a wrapping flex row rather than a paragraph. The middot is a ::before on the
   item's WRAPPER — never inside the <a>, which would put it in the link's name
   and its 44px target box — so it can be switched off where the row wraps.
   See the note in scripts/v15-shell.mjs for what the old inline separators did
   to a phone (dots stranded at the end of every wrapped line). */
.foot__legal{display:flex;flex-wrap:wrap;align-items:center}
/* ⚠ NO COLOUR OF ITS OWN — it inherits .foot__base's 66% onyx. A quieter dot
   (40% was written first) is a text colour below the AA floor and
   v15-contrast-floor fails on it, correctly: the aa:decorative exemption is
   for aria-hidden display type, not for a separator the reader is looking at.
   Inheriting is also what the old inline "&middot;" did, so nothing moved. */
.foot__legal>span+span::before{content:"\00B7";padding:0 12px}
/* ⚠ 2.5.8 — EACH LEGAL LINK NOW OWNS ITS BOX, because splitting the row into
   one span per item took away the exception it used to lean on. As one run of
   text these anchors were "a link inside a sentence", which 2.5.8 exempts and
   which is what scripts/v15-mobile-sweep.mjs tests for (a parent with ten more
   characters than the link). Each one is alone in its wrapper now, so the
   exemption is gone and the 16px box has to become 24px on its own. Padding
   with an equal negative margin: the target grows, the row does not move — the
   50px .foot__base is unchanged, measured. Below 900px the tap-target block's
   44px inline-flex rule already wins and this must not reach it, or the two
   wrapped rows would be pulled 8px closer and lose the spacing exception the
   footer lists pass on. Hence min-width, not an unscoped rule. */
@media(min-width:901px){
  .foot__legal a{display:inline-block;padding:4px 0;margin:-4px 0}
}
/* Below 640 the row cannot hold on one line at any size the brand would set,
   so it stops pretending to be a sentence: the copyright takes its own line
   and the five policy links sit under it as an evenly-spaced row. The dots go
   with the single line they punctuated — a separator that only ever appears
   mid-line is noise once the line has become a block. Column gap 18px, and the
   44px anchors from the tap-target block supply the vertical rhythm. */
@media(max-width:640px){
  .foot__base{justify-content:flex-start}
  .foot__legal{column-gap:18px;row-gap:0}
  .foot__legal>span:first-child{flex:0 0 100%}
  .foot__legal>span+span::before{content:none}
}
@media(max-width:1080px){.foot__grid{grid-template-columns:1.6fr repeat(2,minmax(0,1fr));row-gap:44px}}
/* ⚠ minmax(0,1fr), NOT 1fr — WCAG 1.4.10 Reflow. A `1fr` grid track has an
   implicit min-width of AUTO, so it can never shrink below its own min-content:
   the widest link in the column. At 320px that made the two footer columns
   demand 94px each plus gaps and padding, and the DOCUMENT came out 403px wide
   — every V15 page scrolled sideways at 320px, and the footer was the only
   thing doing it. minmax(0,1fr) lets a track go narrower than its content and
   wrap instead. The single column below 520px is the same fix from the other
   end: two columns of 130px is not a layout, it is a column of broken words. */
@media(max-width:800px){.foot__grid{grid-template-columns:repeat(2,minmax(0,1fr));row-gap:40px}}
@media(max-width:520px){.foot__grid{grid-template-columns:minmax(0,1fr);row-gap:36px}}

/* ============================================================
   HERO
   ============================================================ */
/* ⚠ THE HERO MUST FIT ONE VIEWPORT, INCLUDING ON A SHORT LAPTOP.
   min-height:100svh only sets a FLOOR — with a fixed 120px top pad, a 118px
   headline and four blocks under it, the content ran past the fold on any
   viewport under ~950px and the buttons + scroll cue landed inside section 2.
   Every vertical value below is therefore capped against vh as well as vw, so
   the whole block compresses with the window instead of overflowing it. */
.hero{position:relative;min-height:100svh;display:flex;flex-direction:column;justify-content:center;
  padding-top:clamp(92px,12vh,116px);padding-bottom:clamp(42px,6.4vh,74px);overflow:hidden}
/* ---------- the hero ground ----------
   Flat linen made the hero read as a slide rather than as a room: the
   headline had no light on it and the tablet's shadow fell on nothing.
   Two layers fix that and neither costs a pixel of height, which is the
   only reason they are safe under the fold warning above.
   ::before is the light — a soft warm-white wash behind the headline and a
   6% accent bloom under the device, so the frame sits IN the ground instead
   of ON it. It is inset past the edges and relies on .hero{overflow:hidden}
   to clip; do not remove that.
   ::after is the grain — one tile of desaturated feTurbulence at 5%. It kills
   the plastic flatness of a single flat hex across 100svh and is what makes
   the linen read as paper. Both are pointer-events:none and sit under the
   content, which is why .hero__grid carries a z-index below. */
.hero::before{
  content:"";position:absolute;inset:-18% -12% -18% -20%;pointer-events:none;z-index:0;
  background:
    radial-gradient(54% 58% at 24% 30%, rgba(255,252,247,.92) 0%, rgba(255,252,247,0) 66%),
    radial-gradient(50% 54% at 82% 74%, color-mix(in srgb,var(--accent) 7%,transparent) 0%, transparent 68%),
    radial-gradient(88% 78% at 50% 108%, color-mix(in srgb,var(--bordeaux) 6%,transparent) 0%, transparent 62%);
}
/* ⚠ NO mix-blend-mode HERE, deliberately. multiply is the obvious way to sit
   grain on linen, but it promotes this pseudo into its own compositing layer
   across a full 100svh section that already composites a fixed nav, a shadowed
   device and a hand animation — cost with no visible gain at 5%. Plain opacity
   over a desaturated tile reads identically on linen. The feColorMatrix
   saturate=0 is what keeps this grey grain rather than the colour static
   feTurbulence emits by default; without it the wash picks up a green cast. */
.hero::after{
  content:"";position:absolute;inset:0;pointer-events:none;z-index:1;
  opacity:.05;background-size:200px 200px;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23n)'/%3E%3C/svg%3E");
}
/* ⚠ width:100% IS LOAD-BEARING — do not drop it.
   .wrap carries margin:0 auto, and .hero is a COLUMN FLEX container. An auto
   margin on the cross axis overrides align-items:stretch, so the wrap stops
   filling the hero and becomes shrink-to-fit — sized by its own content. That
   was invisible while the headline was always the widest thing in it, but the
   moment the portal started switching screens the tablet resized with it
   (515px on Overview, 480px on Designs) and the whole hero shifted. width:100%
   restores the fill; max-width:1480 + auto margins still centre it above that.
   The min-width:0 chain below (grid items -> portal -> main -> views -> grids
   and lists) is the second half: fr tracks and flex items take their automatic
   minimum from min-content, so without it inner content feeds back out too. */
/* ⚠ align-items:STRETCH, AND IT IS CARRYING TWO ALIGNMENTS AT ONCE — 2026-08-31,
   owner. The hero owes two anchors that a single alignment keyword cannot hold
   together:
       TOP     the film's frame starts on the headline's first line
       BOTTOM  "Show me more" sits on the same rule as "See events we've handled"
   end/start/center each satisfy one and strand the other, because the right
   column (film + link) is ~96px shorter than the left (headline + lede + CTA
   row) and every one-keyword alignment pins the short column at one end and
   lets the other end fall where it lands. This cycled through all three before
   arriving here.
   STRETCH makes the right column as tall as the row instead, and .hero__film
   is then a space-between flex column: frame pinned to its top, link pinned to
   its bottom, the slack absorbed by the gap between them. Both anchors hold at
   once, and they hold at every width because both offsets are constants.
   ⚠ .hero__film's padding is the other half of this — top pays back the
   headline's margin-top, bottom pays back the CTA pill's overhang. Changing
   this keyword without that padding, or the reverse, breaks both alignments
   silently. Read them as one rule. */
.hero__grid{display:grid;grid-template-columns:1.25fr .9fr;gap:clamp(30px,4vw,80px);align-items:stretch;width:100%;position:relative;z-index:2}
.hero__grid>*{min-width:0}
.hero__kicker{margin-bottom:26px}
/* Hero typography, owner pick 2026-08-31 (lab variant 02 "Weighted"): the
   headline gains a step, the lede grows onto the display scale, and the
   running paragraph steps down to caption size. The hierarchy is carried by
   the gaps between the three voices, not by layout — the grid, the device and
   the colours are untouched.
   ⚠ WEIGHT WAS 200 UNTIL 2026-08-31, WHEN THE OWNER PICKED 300 in
   app/hero-ground-lab.html (the "Your pick" frame, 200 and 300 shown side
   by side). It is written out rather than deleted so the value is visible at
   the rule instead of two hundred lines away, but 300 is exactly what .d1
   already carries — so this line no longer disagrees with the display scale,
   and it no longer disagrees with /about/, whose .vhero h1 sets no weight
   and has therefore always rendered at 300 beside a 200 homepage.
   The Crimson Pro 200 face is still requested in the font links and must
   STAY: hero-lab.html and both lab-flow-*.html pages still set weight 200,
   and dropping it from the link would silently fall those pages back to 300. */
.hero h1{font-size:min(var(--fs-d1),9.1svh);font-weight:300;letter-spacing:-.03em;margin-top:clamp(16px,2.4vh,26px)!important;margin-bottom:clamp(16px,2.6vh,34px)}
/* --fs-d5 (clamp(21px,2.2vw,28px)) rather than a one-off 29px: the lede is
   now display-scale and the scale already has that step. Full onyx at 16.4:1.
   ⚠ 34ch, NOT the 23ch this shipped at. The lede is authored as TWO lines
   (a <br> after "arrival,") so the accented clause lands as one unbroken line
   instead of splitting "the work / behind your event". Measured in Crimson Pro
   300, that clause is 31.7ch wide, so 34ch clears it at every step of the
   clamp — the ratio is fixed because ch scales with the font-size. 23ch cut it
   and forced the three-line rag. Re-measure before lowering this.
   The three blocks below now narrow monotonically — 720 / 492 / 454 at 1440 —
   which is the whole point of the widths: one right edge stepping inward, not
   a zigzag. Changing any one of the three measures breaks that. */
/* ⚠ margin-bottom is the RUNNING PARAGRAPH'S, inherited on 2026-08-31 when
   .hero__body was removed from the homepage hero (owner). The lede is now the
   last text before the CTA row, and at its own clamp(10px,1.6vh,18px) the
   buttons crowded it; this is the value .hero__body held above the row. The
   lab pages that still carry a .hero__body get a slightly wider gap under the
   lede as a result, which is the cheaper of the two prices. */
.hero__sub{font-size:var(--fs-d5);line-height:1.32;max-width:34ch;margin-bottom:clamp(20px,3.4vh,38px);color:var(--onyx);text-wrap:pretty}
/* ⚠ 60% onyx, not the 50% the variant was drawn at. 50% measures 3.40:1 on
   linen — an AA fail. 60% is the nearest stop that clears it (4.66:1) and
   keeps the recession. Re-measure if the ground ever changes.
   ⚠ --fs-body (15px), NOT the --fs-cap (13px) variant 02 drew this at.
   --fs-cap is documented as "list rows, meta, secondary" — the smallest text
   on the site — and under a 90px headline it read as fine print rather than
   as the hero's supporting sentence. The 90 / 28 / 13 ladder had no bottom
   rung; 90 / 28 / 15 does. 48ch rather than 52ch keeps this narrower than the
   lede's longest line (31.7ch of a larger step) so the stack still steps
   inward — at 52ch the two blocks measure the same and the step disappears. */
/* text-wrap:pretty on both: below 1001px the max-widths stop binding (the
   column is the viewport) and the accented clause dropped a one-word widow
   — "event." alone on line 3 at 375px. pretty only pulls a word down to
   avoid that; it does not restructure the authored two-line desktop rag. */
.hero__body{max-width:48ch;margin-bottom:clamp(20px,3.4vh,38px);font-size:var(--fs-body);line-height:1.85;color:color-mix(in srgb,var(--onyx) 66%,var(--linen));text-wrap:pretty}
.hero__actions{display:flex;gap:16px;align-items:center;flex-wrap:wrap}
.hero__ghost{font-size:var(--fs-label);font-weight:var(--fw-label);letter-spacing:var(--ls-label);text-transform:uppercase;border-bottom:1px solid var(--onyx);padding-bottom:4px;transition:color .3s,border-color .3s}
.hero__ghost:hover{color:var(--accent);border-color:var(--accent)}
/* The hero's scroll cue (.hero__scroll, @keyframes heroBob) was removed
   entirely on 2026-08-31, markup and CSS, on the homepage and in
   hero-lab.html / burgundy-test.html. A hero that is visibly cut off by
   the fold does not need a label telling people to scroll, and the cue
   carried two accessibility defects of its own (a .45 opacity that
   measured 2.91:1, and a bob with no reduced-motion escape). Do not
   reintroduce it. */

@media(max-width:1000px){.hero__grid{grid-template-columns:1fr}.hero{padding-bottom:90px}}
/* ⚠ THE HEADLINE IS AUTHORED AS THREE LINES (three .rv-line spans) AND MUST
   STAY THREE. Above 1000px it lives in the 1.25fr column — 732px once .wrap
   saturates at 1480. Measured in Crimson Pro 300, the longest authored line
   ("Your event shouldn't") needs 7.81x its own font-size in width, so anything
   over ~92px re-wraps it into five ragged lines and pushes the buttons down
   into section 2. That is exactly what was happening at 1920.
   6.6vw tracks the column as it narrows; 90px caps it once .wrap stops growing;
   10svh keeps it inside a short laptop viewport. Whichever is smallest wins.
   ⚠ 90px IS the ceiling the 7.81x rule allows here — it is not a round number
   picked by eye. The 2026-08-31 pass tried 100px and lines 1 and 2 both
   re-wrapped at 1920. Raise this only by widening the column first. */
@media(min-width:1001px){.hero h1{font-size:min(6.6vw,90px,10svh)}}

/* ---------- the hero device + THE TAP FILM ----------
   The warm backdrop panel is gone (owner, 2026-08-31): the tablet now stands
   free on the linen and a hand comes in from off-stage right to make the two
   decisions a client actually makes — approve, or send back. Every step of the
   loop is driven from motion.js, which measures the real button rectangles and
   places the hand against them, so the sequence stays in register at any width.
   ⚠ The frame sits square (owner, 2026-08-31 — the -2.2deg tilt is gone). Do
   NOT put a transform on .device that changes between frames: the hand is
   positioned from getBoundingClientRect and a moving target desynchronises the
   tap from the button. ---------- */
.device-scene{
  justify-self:end;width:min(620px,100%);min-width:0;position:relative;
  display:flex;justify-content:flex-end;
  padding:0 0 clamp(52px,7vh,100px);
}
.device{
  width:min(544px,100%);
  background:#0b0b0b;border-radius:22px;padding:12px;
  box-shadow:0 60px 100px -38px rgba(30,11,14,.42), 0 22px 44px -22px rgba(30,11,14,.26);
}
.device__screen{background:#F4EFE7;border-radius:12px;overflow:hidden;aspect-ratio:4/3.1;position:relative;
  /* the portal inside is sized entirely from this box (see --pu), which is
     what makes it scale as one object instead of breaking up as it shrinks */
  container-type:inline-size;container-name:screen}
@media(max-width:1000px){.device-scene{justify-self:start;margin-top:10px}}

/* ---------- hero: THE FILM ----------
   It took the tablet-and-hand scene's grid column on 2026-08-31 and inherits
   that column's rules deliberately: same justify-self:end, same
   width:min(620px,100%), same min-width:0 (the .hero__grid comment above
   explains why the whole chain needs it — an fr track takes its automatic
   minimum from min-content, and a video is happy to report a very large one).
   .hero__grid stretches this column to the left column's height and it
   distributes space-between, so BOTH ends are anchored: the film's top edge on
   the headline's first line, and "Show me more" on the same rule as "See
   events we've handled". The slack sits between the film and the link.
   The .film base (16/9, black, radius:8px, overflow:hidden) is shared with the
   platform page; --hero only swaps in the device's shadow, which is tuned for
   the linen ground rather than the onyx band. */
.hero__film{
  justify-self:end;width:min(620px,100%);min-width:0;
  /* ⚠ space-between IS THE ALIGNMENT, not a spacing preference. The grid
     stretches this column to the left column's full height (see .hero__grid),
     and space-between is what then pins the film to the top of it and the link
     to the bottom. The gap below is a MINIMUM under that, not the gap you will
     measure — the column's slack lands between the two children. Switch this
     to flex-start and the link floats up under the film, losing its rule. */
  display:flex;flex-direction:column;align-items:flex-start;
  justify-content:space-between;
  gap:clamp(18px,2.4vh,26px);
  /* TWO MEASURED CONSTANTS, ONE PER ANCHOR. Neither is a chosen number and
     neither is a ratio — both elements they answer to are fixed-height, so
     these do not move with the viewport.
       TOP  clamp(16px,2.4vh,26px) — .hero h1's own margin-top, repeated. The
            grid stretches both columns from the same line, and the h1 then
            pushes its text down by that clamp inside the left column (a grid
            item establishes its own formatting context, so the margin cannot
            collapse out). Repaying it here starts the film's frame on the
            headline's first line rather than 26px above it.
       BOT  7px — the CTA row's lowest edge is the solid pill, which sits 6.7px
            below the baseline rule of the ghost link beside it. 7px lands
            "Show me more" on that same rule, so the two quiet links read as
            one line across the hero.
     ⚠ Keep the top value in step with .hero h1's margin-top. */
  padding:clamp(16px,2.4vh,26px) 0 7px;
}
/* ⚠ WRITTEN AS `.hero__film .film`, AND THE DESCENDANT IS THE POINT. The base
   .film block lives ~1000 lines BELOW this one, so a bare `.film--hero` — same
   one-class specificity — loses the cascade to it on source order and silently
   keeps the onyx-band radius and shadow. Two classes beat one wherever the
   rule sits. The class stays on the element as the name of the variant. */
.hero__film .film{
  width:100%;
  border-radius:12px;
  /* the tablet's own shadow, kept: bordeaux-tinted black, because this frame
     sits on linen rather than on the onyx band the base shadow is tuned for */
  box-shadow:0 60px 100px -38px rgba(30,11,14,.42), 0 22px 44px -22px rgba(30,11,14,.26);
}
/* the label under the film. .hero__ghost carries the look and the 44px-target
   padding-top rule further down; .hero__more only places it. */
.hero__more{align-self:flex-start}
.hero__more .arr{display:inline-block;transition:transform .35s}
.hero__more:hover .arr{transform:translateX(4px)}
@media(prefers-reduced-motion:reduce){
  .btn-cta .arr,.hero__more .arr{transition:none}
  .btn-cta:hover .arr,.hero__more:hover .arr{transform:none}
}

/* ⚠ THE HERO POSTER IS ARTWORK + PLAY BUTTON, NOTHING ELSE — owner,
   2026-08-31, picked in app/hero-ground-lab.html as "Art + play only".
   THE PROBLEM IT SOLVES IS A DOUBLE PRINT, not a preference. The poster JPEG
   already has type set into it: it reads "EVORRAH · THE CLIENT PORTAL" over
   "Your event, handled." The .film__poster furniture then printed
   .film__mark ("EVORRAH") and .film__sub ("THE CLIENT PORTAL · ONE EVENT, END
   TO END") on top of that same artwork, both centred, so the frame carried the
   word EVORRAH twice and THE CLIENT PORTAL twice, overlapping. The artwork
   wins because it is the better setting of the same words.
   ⚠ SCOPED TO .hero__film ON PURPOSE, and the scoping is defensive rather
   than currently load-bearing — say so rather than overstate it. Measured
   2026-08-31: the homepage is the ONLY shipped page carrying a .film frame
   (/platform/ merely links to it, "Watch the film · 2½ min"). But the base
   .film block is written to be reusable and its furniture is the only type on
   a frame whose poster is NOT already typeset — drop that furniture globally
   and the next reuse gets a bare rectangle. Two classes beat one, so this also
   survives the base rules below it wherever the file order puts them.
   ⚠ THE RUNNING TIME IS THE ONE REAL COST, and it is a deliberate trade, not
   an oversight. .film__len ("WATCH · 2½ MIN") answers the question a viewer
   actually has before pressing play, and the container query further down
   keeps it even at 335px for exactly that reason. It goes here because it
   centres under the play button on top of the artwork's own rule. The
   accessible name on the <button> still carries "two and a half minutes", so
   the fact is not lost to a screen reader — only to the eye. Put it back by
   deleting it from this selector, NOT by re-showing .film__mark/.film__sub.
   ⚠ 64px, NOT the base 84px. With no type around it an 84px circle sits on
   the artwork's headline; 64 clears it and still beats the 44px target. */
.hero__film .film__mark,
.hero__film .film__sub,
.hero__film .film__len{display:none}
.hero__film .film__play{width:64px;height:64px}

@media(max-width:1000px){.hero__film{justify-self:start;margin-top:18px;width:100%}}

/* ---------- the hand ----------
   It moves at three HEIGHTS above the glass, not on one plane: it lifts to
   travel, settles just above its target, then presses. Scale and shadow carry
   that depth — a hand far from the glass is fractionally larger and throws a
   big soft shadow; on the glass it is smaller with a tight dark one. This is
   the whole difference between a cursor sliding around and a hand.
   ⚠ transform-origin sits ON the fingertip, so scaling never moves the contact
   point. Keep it in step with motion.js TIP_X/TIP_Y. */
.hand{
  position:absolute;left:0;top:0;z-index:6;
  width:clamp(200px,58%,310px);aspect-ratio:585/1817;
  --hs:1.05;
  transform:translate(var(--hx,60px),var(--hy,420px)) rotate(var(--hr,7deg)) scale(var(--hs));
  transform-origin:41.97% 0.77%;
  opacity:0;pointer-events:none;
  filter:drop-shadow(16px 30px 30px rgba(30,11,14,.19));
  transition:transform .62s cubic-bezier(.33,.02,.2,1),filter .42s ease,opacity .55s ease;
  will-change:transform,filter;
}
/* a live hand is never perfectly still, even while it hovers */
.hand img{width:100%;height:auto;display:block;animation:handIdle 5.4s ease-in-out infinite}
@keyframes handIdle{
  0%,100%{transform:translate(0,0) rotate(0deg)}
  34%{transform:translate(-1px,.8px) rotate(.26deg)}
  67%{transform:translate(.8px,-.6px) rotate(-.2deg)}
}
[data-film].film-on .hand{opacity:1}
/* travelling, high off the glass */
.hand.is-air{--hs:1.05;filter:drop-shadow(16px 32px 32px rgba(30,11,14,.18))}
/* settled just above the target — the beat before the press */
.hand.is-near{--hs:1.016;filter:drop-shadow(7px 15px 16px rgba(30,11,14,.28));
  transition-duration:.30s,.32s,.55s}
/* on the glass */
.hand.is-press{--hs:1;filter:drop-shadow(3px 6px 7px rgba(30,11,14,.36));
  transition-duration:.13s,.16s,.55s;
  transition-timing-function:cubic-bezier(.4,0,.6,1),ease,ease}
.hand.is-drag{--hs:1;filter:drop-shadow(3px 6px 7px rgba(30,11,14,.36));
  transition-duration:.78s,.3s,.55s}
@media(prefers-reduced-motion:reduce){.hand img{animation:none}}

/* the contact ripple, drawn on the glass under the fingertip: a soft bloom
   under the pad, not an outline */
.ptap{
  position:absolute;z-index:5;width:46px;height:46px;margin:-23px 0 0 -23px;
  border-radius:50%;opacity:0;pointer-events:none;
  background:radial-gradient(circle,rgba(255,255,255,.62) 0%,rgba(255,255,255,.16) 46%,rgba(255,255,255,0) 64%);
  box-shadow:inset 0 0 0 1.1px rgba(255,255,255,.8);
}
.ptap.go{animation:ptapGo .58s cubic-bezier(.2,.72,.25,1)}
@keyframes ptapGo{0%{opacity:1;transform:scale(.10)}55%{opacity:.52}100%{opacity:0;transform:scale(1)}}

/* the confirmation that lands after each decision */
.ptoast{
  position:absolute;right:9px;top:9px;z-index:7;max-width:64%;
  display:flex;align-items:center;gap:5px;
  background:#131110;color:#F4EFE7;border-radius:5px;padding:5px 8px;
  font-family:var(--sans);font-size:5.6px;line-height:1.35;
  box-shadow:0 10px 22px -10px rgba(0,0,0,.55);
  opacity:0;transform:translateY(-7px) scale(.96);pointer-events:none;
  transition:opacity .3s ease,transform .46s cubic-bezier(.22,.6,.2,1);
}
.ptoast.show{opacity:1;transform:none}
.ptoast i{width:7px;height:7px;border-radius:50%;flex:none;background:var(--portal-green)}
.ptoast.is-no i{background:var(--portal-red)}

/* the line under the film */
.scene__cap{
  position:absolute;left:2px;bottom:clamp(14px,2.2vh,30px);z-index:8;margin:0;
  font-family:var(--serif);font-weight:300;font-size:var(--fs-lede);
  color:color-mix(in srgb,var(--onyx) 66%,var(--linen));
  opacity:0;transition:opacity .8s ease;pointer-events:none;
}
.scene__cap span{display:inline-block;transform:translateY(9px);transition:transform .8s cubic-bezier(.22,.6,.2,1)}
[data-film].cap-on .scene__cap{opacity:1}
[data-film].cap-on .scene__cap span{transform:none}

@media(max-width:620px){.hand,.scene__cap{display:none}.device-scene{padding-bottom:0}}
@media(prefers-reduced-motion:reduce){
  .hand,.scene__cap,.ptap{display:none}
  .ptoast{transition:none}
}

/* ---------- portal UI (inside the device only) ----------
   SCALE. Everything in the portal is authored in DESIGN PIXELS — the sizes this
   UI has when the tablet is at its full 544px, i.e. a 520px screen — and each
   one is multiplied by --pu to get there. --pu is one design pixel expressed
   against the width of the screen itself (container units), so the whole portal
   shrinks as one object instead of a 4px caption sitting inside a box that
   halved. That is what lets the mock-up survive a phone. Write new rules the
   same way: `font-size:calc(5.5*var(--pu))`, never a bare px. Hairlines, radii
   and shadows are the exception and stay in real px, so a border never renders
   at 0.4px. --pk is the per-breakpoint fudge, set in the media queries at the
   end of this block.
   ⚠ The container is .device__screen (container-type:inline-size, set with the
   device). Nothing in here may be sized from the viewport. */
.portal{
  --pu:calc(0.19231cqi * var(--pk,1));
  display:grid;grid-template-columns:calc(118*var(--pu)) 1fr;height:100%;
  font-size:calc(6.5*var(--pu));font-family:var(--sans);
}
.portal__side{background:#131110;color:#EFE9DF;padding:calc(10*var(--pu)) calc(9*var(--pu));display:flex;flex-direction:column;gap:calc(2*var(--pu));min-width:0}
.portal__logo{font-family:var(--serif);letter-spacing:.24em;font-size:calc(9*var(--pu));margin-bottom:calc(9*var(--pu))}
.portal__user{display:flex;align-items:center;gap:calc(5*var(--pu));margin-bottom:calc(9*var(--pu));min-width:0}
.portal__avatar{width:calc(13*var(--pu));height:calc(13*var(--pu));border-radius:50%;background:var(--portal-green);display:grid;place-items:center;font-size:calc(5*var(--pu));color:#fff;flex:none}
.portal__user>span{min-width:0}
.portal__user b{display:block;font-size:calc(6*var(--pu));font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.portal__user span{font-size:calc(5*var(--pu));opacity:.55;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* the square belongs to the state, not to the markup: whichever item is .on is
   the filled one, so nine items and one class stay in step for ever */
.portal__item{padding:calc(4.5*var(--pu)) calc(7*var(--pu));border-radius:4px;opacity:.62;display:flex;align-items:center;gap:calc(5*var(--pu));white-space:nowrap}
.portal__item::before{content:"\25A1";font-size:calc(6*var(--pu));line-height:1;flex:none}
.portal__item.on::before{content:"\25A0"}
.portal__item.on{background:#F4EFE7;color:#131110;opacity:1;font-weight:700}
.portal__main{padding:calc(10*var(--pu)) calc(12*var(--pu));overflow:hidden;min-width:0;display:flex;flex-direction:column;gap:calc(7*var(--pu))}
.portal__hi{display:flex;justify-content:space-between;align-items:flex-start;gap:calc(8*var(--pu))}
/* The device mock's labels are <b>, not <h5> — see index.html. They are text
   inside a picture of the product, and nine h5s under the page's h1 put the
   product's own navigation into this page's outline (WCAG 1.3.1). Both
   selectors are kept so the rule survives either markup. */
.portal__hi h5,.portal__hi>div>b{font-family:var(--serif);font-weight:400;font-size:calc(11*var(--pu));line-height:1.1}
.portal__hi p{font-size:calc(5.5*var(--pu));opacity:.6}
.portal__date{text-align:right;font-size:calc(5*var(--pu));letter-spacing:.1em;text-transform:uppercase;opacity:.6;flex:none}
.portal__date b{display:block;font-size:calc(6.5*var(--pu));letter-spacing:0;opacity:1}
.portal__cards{display:grid;grid-template-columns:repeat(4,1fr);gap:calc(5*var(--pu));min-width:0}
.pcard{background:#fff;border:1px solid #E5DFD4;border-radius:5px;padding:calc(6*var(--pu));min-width:0;overflow:hidden}
.pcard__ic{width:calc(9*var(--pu));height:calc(9*var(--pu));border-radius:50%;margin-bottom:calc(4*var(--pu))}
.pcard__ic--g{background:var(--portal-green)}
.pcard__ic--r{background:var(--portal-red)}
.pcard__ic--b{background:var(--bordeaux)}
.pcard b{font-size:calc(11*var(--pu));font-weight:700;display:block;line-height:1.1}
.pcard i{font-style:normal;font-family:var(--serif);font-size:calc(8*var(--pu));display:block;line-height:1.15}
.pcard span{font-size:calc(5*var(--pu));opacity:.6;display:block;margin-top:calc(1.5*var(--pu))}
.pcard .plink{font-size:calc(4.8*var(--pu));letter-spacing:.08em;text-transform:uppercase;margin-top:calc(4*var(--pu));display:block;font-weight:700}
.portal__prog{background:#fff;border:1px solid #E5DFD4;border-radius:5px;padding:calc(7*var(--pu)) calc(9*var(--pu));display:flex;align-items:center;gap:calc(9*var(--pu))}
@property --p{syntax:'<number>';inherits:false;initial-value:76}
.pring{width:calc(26*var(--pu));height:calc(26*var(--pu));border-radius:50%;background:conic-gradient(var(--portal-green) 0 calc(var(--p,76) * 1%), #E5DFD4 0 100%);display:grid;place-items:center;flex:none;transition:--p 1.1s cubic-bezier(.22,.6,.2,1)}
.pring i{width:calc(18*var(--pu));height:calc(18*var(--pu));border-radius:50%;background:#fff;display:grid;place-items:center;font-style:normal;font-size:calc(5.5*var(--pu));font-weight:700}
.psteps{display:flex;align-items:center;gap:0;flex:1;min-width:0}
.pstep{display:flex;flex-direction:column;align-items:center;gap:calc(2*var(--pu));flex:1;position:relative;min-width:0}
.pstep::before{content:"";position:absolute;top:calc(4*var(--pu));left:-50%;width:100%;height:1px;background:#DDD6C9}
.pstep:first-child::before{display:none}
.pstep i{width:calc(8*var(--pu));height:calc(8*var(--pu));border-radius:50%;background:var(--portal-green);z-index:1;font-style:normal}
.pstep.off i{background:#fff;border:1px solid #CFC7B8}
.pstep span{font-size:calc(4.6*var(--pu));opacity:.65;white-space:nowrap}

/* ---------- the portal's nine screens ----------
   The film navigates: tapping a sidebar item swaps the view under a decision
   strip that never moves. Keeping the strip outside .pviews is deliberate —
   it is the one control the hand returns to, so it must not shift between
   screens, and motion.js can hold a single [data-pact] reference. Every screen
   is flex:1 + min-height:0, so nine different layouts all end at the same line
   above that strip whatever the tablet is scaled to. */
.pviews{flex:1;min-height:0;min-width:0;display:flex;flex-direction:column}
.pview{display:none;flex:1;min-height:0;min-width:0;flex-direction:column;gap:calc(7*var(--pu))}
.pview.on{display:flex}
/* a screen assembles itself — header, then contents — instead of arriving whole */
.pview.on>*,.pview.on .ptile,.pview.on .pgrow,.pview.on .ptable{animation:pviewIn .46s cubic-bezier(.22,.6,.2,1) backwards}
.pview.on>*:nth-child(1){animation-delay:.02s}
.pview.on>*:nth-child(2){animation-delay:.07s}
.pview.on>*:nth-child(3){animation-delay:.12s}
.pview.on>*:nth-child(4){animation-delay:.17s}
.pview.on .ptile:nth-child(1){animation-delay:.10s}
.pview.on .ptile:nth-child(2){animation-delay:.16s}
.pview.on .ptile:nth-child(3){animation-delay:.22s}
.pview.on .ptile:nth-child(4){animation-delay:.28s}
.pview.on .pgrow:nth-child(1){animation-delay:0.060s}
.pview.on .pgrow:nth-child(2){animation-delay:0.095s}
.pview.on .pgrow:nth-child(3){animation-delay:0.130s}
.pview.on .pgrow:nth-child(4){animation-delay:0.165s}
.pview.on .pgrow:nth-child(5){animation-delay:0.200s}
.pview.on .pgrow:nth-child(6){animation-delay:0.235s}
.pview.on .pgrow:nth-child(7){animation-delay:0.270s}
.pview.on .ptable:nth-child(n){animation-delay:.06s}
.pview.on .ptable:nth-child(n+4){animation-delay:.10s}
.pview.on .ptable:nth-child(n+7){animation-delay:.14s}
.pview.on .ptable:nth-child(n+10){animation-delay:.18s}
@keyframes pviewIn{0%{opacity:0;transform:translateY(6px)}100%{opacity:1;transform:none}}

/* the activity feed — it fills the dead space under the tracker and gives the
   overview something that visibly changes when a decision is made */
.pfeed{background:#fff;border:1px solid #E5DFD4;border-radius:5px;padding:calc(6*var(--pu)) calc(8*var(--pu));flex:1;min-height:0;overflow:hidden}
.pfeed__h{font-size:calc(4.6*var(--pu));letter-spacing:.14em;text-transform:uppercase;opacity:.5;font-weight:700;margin-bottom:calc(4*var(--pu))}
.pfr{display:flex;align-items:center;gap:calc(5*var(--pu));padding:calc(2.6*var(--pu)) 0;font-size:calc(5.2*var(--pu));border-top:1px solid #F0EBE1}
.pfr:first-child{border-top:0}
.pfr i{width:calc(5*var(--pu));height:calc(5*var(--pu));border-radius:50%;background:var(--portal-green);flex:none}
.pfr span{flex:1;min-width:0;opacity:.72;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pfr em{font-style:normal;font-size:calc(4.6*var(--pu));opacity:.4}
.pfr.fresh{animation:pfrIn .55s cubic-bezier(.22,.6,.2,1)}
@keyframes pfrIn{0%{opacity:0;transform:translateY(-4px)}100%{opacity:1;transform:none}}

/* designs */
.pgrid{display:grid;grid-template-columns:repeat(4,1fr);gap:calc(6*var(--pu));flex:1;min-height:0;min-width:0}
.ptile{background:#fff;border:1px solid #E5DFD4;border-radius:5px;padding:calc(5*var(--pu));display:flex;flex-direction:column;position:relative;min-width:0;
  transition:transform .25s cubic-bezier(.22,.6,.2,1),box-shadow .25s ease,border-color .25s ease}
.ptile__art{flex:1;min-height:0;border-radius:3px;margin-bottom:calc(4*var(--pu))}
.ptile__art--a{background:linear-gradient(150deg,#F3EDE3,#E3D8C6)}
.ptile__art--b{background:linear-gradient(150deg,#EDE7E4,#D8C9C4)}
.ptile__art--c{background:linear-gradient(150deg,#F1EEE6,#DCDACB)}
.ptile__art--d{background:linear-gradient(150deg,#EFEAE6,#D5CCC2)}
.ptile b{font-family:var(--serif);font-weight:400;font-size:calc(7*var(--pu));line-height:1.15;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.ptile span{font-size:calc(4.8*var(--pu));opacity:.55;margin-top:calc(1*var(--pu))}
.ptile.is-armed{transform:translateY(-1.5px);box-shadow:0 4px 8px -4px rgba(20,10,10,.28);border-color:#CFC5B4}
.ptile.is-hit{transform:translateY(0) scale(.975)}
.ptile.is-picked{border-color:var(--bordeaux);box-shadow:0 0 0 1px var(--bordeaux)}
.ptile.is-done::after{content:"\2713";position:absolute;top:calc(5*var(--pu));right:calc(5*var(--pu));width:calc(9*var(--pu));height:calc(9*var(--pu));border-radius:50%;
  background:var(--portal-green);color:#fff;font-size:calc(5*var(--pu));display:grid;place-items:center;animation:pfrIn .4s ease}
.ptile.is-done .ptile__art{opacity:.5}

/* guests */
.plist{flex:1;min-height:0;background:#fff;border:1px solid #E5DFD4;border-radius:5px;overflow:hidden;position:relative}
.plist__track{transition:transform .9s cubic-bezier(.22,.6,.2,1);will-change:transform}
.plist__track.is-drag{transition:transform .1s linear}
.plist .pgrow{display:flex;align-items:center;gap:calc(5*var(--pu));padding:calc(3.6*var(--pu)) calc(8*var(--pu));font-size:calc(5.2*var(--pu));border-top:1px solid #F0EBE1}
.plist .pgrow:first-child{border-top:0}
.pav{width:calc(10*var(--pu));height:calc(10*var(--pu));border-radius:50%;background:#E7E0D3;display:grid;place-items:center;font-size:calc(4.2*var(--pu));font-weight:700;opacity:.75;flex:none}
.plist .pgrow b{font-size:calc(5.4*var(--pu));font-weight:700;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.plist .pgrow>span{font-size:calc(4.8*var(--pu));opacity:.5;width:calc(34*var(--pu))}
.pyes,.pwait{font-style:normal;font-size:calc(4.6*var(--pu));letter-spacing:.06em;text-transform:uppercase;font-weight:700}
.pyes{color:var(--portal-green)}
.pwait{color:color-mix(in srgb,var(--portal-red) 70%,#8a8378)}

/* ---------- event details ----------
   A fact and its Change control. Change never edits in place: it loads the
   decision strip, because confirming is the only thing this portal asks of a
   client and there is exactly one place where that happens. */
.pdet{flex:1;min-height:0;background:#fff;border:1px solid #E5DFD4;border-radius:5px;overflow:hidden;display:flex;flex-direction:column}
.pdet__r{display:flex;align-items:center;gap:calc(7*var(--pu));padding:0 calc(8*var(--pu));flex:1;min-height:0;border-top:1px solid #F0EBE1;transition:background .25s ease}
.pdet__r:first-child{border-top:0}
.pdet__r>span{font-size:calc(4.8*var(--pu));letter-spacing:.1em;text-transform:uppercase;opacity:.45;width:calc(48*var(--pu));flex:none}
.pdet__r b{font-size:calc(5.6*var(--pu));font-weight:400;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pdet__r em{font-style:normal;font-size:calc(4.6*var(--pu));letter-spacing:.1em;text-transform:uppercase;font-weight:700;color:var(--bordeaux);opacity:.45;flex:none;transition:opacity .25s ease}
.pdet__r:hover,.pdet__r.is-armed{background:#FBF8F3}
.pdet__r:hover em,.pdet__r.is-armed em{opacity:1}
.pdet__r.is-picked{background:#FBF6F1;box-shadow:inset 2px 0 0 var(--bordeaux)}
.pdet__r.is-picked b{font-weight:700}

/* ---------- communications ----------
   Unread carries a bordeaux dot and full-strength ink; opening a thread is the
   click that empties the counter on the overview. */
.pthreads{flex:1;min-height:0;background:#fff;border:1px solid #E5DFD4;border-radius:5px;overflow:hidden;display:flex;flex-direction:column}
.pthread{display:flex;align-items:center;gap:calc(5*var(--pu));padding:0 calc(8*var(--pu));flex:1;min-height:0;border-top:1px solid #F0EBE1;transition:background .25s ease}
.pthread:first-child{border-top:0}
.pthread b{font-size:calc(5.4*var(--pu));font-weight:700;flex:none;max-width:calc(96*var(--pu));overflow:hidden;text-overflow:ellipsis;white-space:nowrap;opacity:.5}
.pthread>span:not(.pav){font-size:calc(5*var(--pu));opacity:.4;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pthread em{font-style:normal;font-size:calc(4.6*var(--pu));opacity:.35;flex:none}
.pthread.is-new b,.pthread.is-new>span:not(.pav){opacity:.85}
.pthread.is-new .pav{background:color-mix(in srgb,var(--bordeaux) 16%,#E7E0D3);color:var(--bordeaux);opacity:1}
.pthread.is-new::after{content:"";width:calc(4.4*var(--pu));height:calc(4.4*var(--pu));border-radius:50%;background:var(--bordeaux);flex:none}
.pthread:hover,.pthread.is-armed{background:#FBF8F3}
.pthread.is-read{animation:pfrIn .45s ease}

/* ---------- seating ----------
   Tables are circles because that is what they are. Picking one and then a
   second swaps them, and the swap is written to the feed on the overview. */
/* the room is the panel and the tables are laid out in it. The circles are
   sized from the COLUMN, never from the row: an auto row collapses to its
   content and a height:100% circle then renders at 11px. */
.pplan{flex:1;min-height:0;overflow:hidden;background:#fff;border:1px solid #E5DFD4;border-radius:5px;
  padding:calc(9*var(--pu)) calc(11*var(--pu));
  display:grid;grid-template-columns:repeat(6,1fr);grid-auto-rows:1fr;
  gap:calc(6*var(--pu)) calc(5*var(--pu));align-content:center;justify-items:center}
.ptable{width:min(100%,calc(58*var(--pu)));aspect-ratio:1;height:auto;align-self:center;
  border-radius:50%;background:#FCFAF6;border:1px solid #E5DFD4;
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:calc(1*var(--pu));
  transition:transform .25s cubic-bezier(.22,.6,.2,1),border-color .25s ease,box-shadow .25s ease,background .25s ease}
.ptable i{font-style:normal;font-size:calc(6.5*var(--pu));font-weight:700;line-height:1}
.ptable b{font-weight:400;font-size:calc(4.8*var(--pu));opacity:.5;line-height:1;letter-spacing:.06em}
.ptable:hover,.ptable.is-armed{border-color:#CFC5B4;transform:translateY(-1.5px);box-shadow:0 4px 8px -4px rgba(20,10,10,.24)}
.ptable.is-picked{border-color:var(--bordeaux);box-shadow:0 0 0 1px var(--bordeaux);background:#FBF6F1}
.ptable.is-swapped{animation:pSwap .62s cubic-bezier(.22,.6,.2,1)}
@keyframes pSwap{
  0%{transform:scale(.84);border-color:var(--portal-green);box-shadow:0 0 0 2px color-mix(in srgb,var(--portal-green) 35%,transparent)}
  60%{transform:scale(1.06);border-color:var(--portal-green);box-shadow:0 0 0 1px color-mix(in srgb,var(--portal-green) 55%,transparent)}
  100%{transform:none;border-color:#E5DFD4;box-shadow:none}}
.pnote{font-size:calc(4.6*var(--pu));opacity:.4;text-align:center;flex:none}

/* ---------- timeline ----------
   Every step says who holds it. Only the ones marked Yours can be ticked —
   which is the whole argument of the page, stated by an interface. */
.pmiles{flex:1;min-height:0;background:#fff;border:1px solid #E5DFD4;border-radius:5px;overflow:hidden;display:flex;flex-direction:column}
.pmile{display:flex;align-items:center;gap:calc(6*var(--pu));padding:0 calc(8*var(--pu));flex:1;min-height:0;border-top:1px solid #F0EBE1;transition:background .25s ease}
.pmile:first-child{border-top:0}
.pmile i{width:calc(7.5*var(--pu));height:calc(7.5*var(--pu));border-radius:50%;border:1px solid #CFC7B8;background:#fff;flex:none;display:grid;place-items:center;
  font-style:normal;font-size:calc(4.4*var(--pu));color:#fff;line-height:1;transition:background .3s ease,border-color .3s ease}
.pmile b{font-size:calc(5.4*var(--pu));font-weight:400;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pmile>span{font-size:calc(4.6*var(--pu));letter-spacing:.1em;text-transform:uppercase;font-weight:700;opacity:.4;flex:none}
.pmile em{font-style:normal;font-size:calc(4.8*var(--pu));opacity:.4;flex:none;width:calc(30*var(--pu));text-align:right}
.pmile.is-done i{background:var(--portal-green);border-color:var(--portal-green)}
.pmile.is-done i::after{content:"\2713"}
.pmile.is-done b{opacity:.45}
.pmile.is-wait i{border-color:#DDD6C9;background:#F4EFE7}
.pmile[data-mile] i{border-color:var(--bordeaux)}
.pmile[data-mile]>span{color:var(--bordeaux);opacity:.75}
.pmile[data-mile]:hover,.pmile.is-armed{background:#FBF8F3}
.pmile.is-ticked{animation:pfrIn .5s ease}

/* ---------- website ----------
   A site inside a site. The tabs move the preview and nothing else: the event
   website is the one screen where a client is a visitor, not a decider. */
.pweb{flex:1;min-height:0;background:#fff;border:1px solid #E5DFD4;border-radius:5px;overflow:hidden;display:flex;flex-direction:column}
.pweb__bar{display:flex;gap:calc(2*var(--pu));padding:calc(5*var(--pu)) calc(6*var(--pu)) 0;border-bottom:1px solid #F0EBE1;flex:none}
.pweb__tab{font-size:calc(4.8*var(--pu));letter-spacing:.08em;text-transform:uppercase;font-weight:700;opacity:.4;
  padding:calc(3*var(--pu)) calc(6*var(--pu));border-radius:4px 4px 0 0;white-space:nowrap;transition:opacity .25s ease,background .25s ease}
.pweb__tab:hover,.pweb__tab.is-armed{opacity:.7;background:#FBF8F3}
.pweb__tab.on{opacity:1;background:#F7F2EA;box-shadow:inset 0 -1.5px 0 var(--bordeaux)}
.pweb__view{flex:1;min-height:0;padding:calc(8*var(--pu)) calc(11*var(--pu));background:linear-gradient(160deg,#FCFAF6,#F5F0E7)}
.pwp{display:none;height:100%;flex-direction:column}
.pwp.on{display:flex;animation:pviewIn .42s cubic-bezier(.22,.6,.2,1)}
.pwp[data-webview="home"]{align-items:center;justify-content:center;gap:calc(3*var(--pu));text-align:center}
.pwp__k{font-size:calc(4.6*var(--pu));letter-spacing:.24em;text-transform:uppercase;opacity:.45}
.pwp__t{font-family:var(--serif);font-weight:300;font-size:calc(14*var(--pu));line-height:1.05}
.pwp__s{font-size:calc(5*var(--pu));opacity:.55}
.pwp__btn{margin-top:calc(4*var(--pu));font-size:calc(4.6*var(--pu));letter-spacing:.14em;text-transform:uppercase;font-weight:700;color:#fff;
  background:var(--bordeaux);border-radius:3px;padding:calc(3.4*var(--pu)) calc(10*var(--pu))}
.pwp__row{display:flex;align-items:center;gap:calc(7*var(--pu));flex:1;min-height:0;border-top:1px solid rgba(20,10,10,.07)}
.pwp__row:first-child{border-top:0}
.pwp__row em{font-style:normal;font-size:calc(4.8*var(--pu));letter-spacing:.08em;text-transform:uppercase;opacity:.4;width:calc(28*var(--pu));flex:none}
.pwp__row b{font-family:var(--serif);font-weight:400;font-size:calc(6.5*var(--pu));min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pwp[data-webview="rsvp"].on{display:grid;grid-template-columns:1fr 1fr;gap:calc(5*var(--pu));place-content:center}
.pwp__stat{display:flex;flex-direction:column;align-items:center;gap:calc(1*var(--pu))}
.pwp__stat b{font-family:var(--serif);font-weight:300;font-size:calc(15*var(--pu));line-height:1}
.pwp__stat em{font-style:normal;font-size:calc(4.6*var(--pu));letter-spacing:.1em;text-transform:uppercase;opacity:.45}

/* ---------- reports ----------
   One chart, three datasets. Bars are drawn from --v, so switching dataset is a
   transition rather than a redraw: the chart moves, it never jumps. */
.prep{flex:1;min-height:0;background:#fff;border:1px solid #E5DFD4;border-radius:5px;padding:calc(7*var(--pu)) calc(9*var(--pu)) calc(6*var(--pu));display:flex;flex-direction:column;gap:calc(6*var(--pu))}
.prep__tabs{display:flex;gap:calc(4*var(--pu));flex:none}
.prep__tab{font-size:calc(4.6*var(--pu));letter-spacing:.1em;text-transform:uppercase;font-weight:700;opacity:.4;white-space:nowrap;
  padding:calc(2.4*var(--pu)) calc(6*var(--pu));border-radius:99px;border:1px solid transparent;transition:opacity .25s ease,background .25s ease,color .25s ease,border-color .25s ease}
.prep__tab:hover,.prep__tab.is-armed{opacity:.7;background:#FBF8F3}
.prep__tab.on{opacity:1;color:var(--bordeaux);border-color:color-mix(in srgb,var(--bordeaux) 30%,transparent);background:#FBF6F1}
.prep__chart{flex:1;min-height:0;display:flex;align-items:flex-end;gap:calc(5*var(--pu))}
.prep__bar{flex:1;min-width:0;border-radius:2px 2px 0 0;height:calc(var(--v,50) * 1%);
  background:linear-gradient(180deg,var(--portal-green),color-mix(in srgb,var(--portal-green) 55%,#E9E3D7));
  transition:height .62s cubic-bezier(.22,.6,.2,1),background .4s ease}
.prep.is-opens .prep__bar{background:linear-gradient(180deg,var(--bordeaux),color-mix(in srgb,var(--bordeaux) 45%,#E9E3D7))}
.prep.is-spend .prep__bar{background:linear-gradient(180deg,#8C8578,color-mix(in srgb,#8C8578 45%,#E9E3D7))}
.prep__foot{display:flex;justify-content:space-between;align-items:baseline;gap:calc(8*var(--pu));font-size:calc(4.8*var(--pu));opacity:.5;border-top:1px solid #F0EBE1;padding-top:calc(5*var(--pu));flex:none}
.prep__foot span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.prep__foot em{font-style:normal;letter-spacing:.1em;text-transform:uppercase;opacity:.7;flex:none}

/* ---------- the portal is operable, not a picture ----------
   The hand clicks these for real (el.click()), and so can a visitor — same
   handler either way. So they need genuine affordances: a pointer, a hover
   state, and a focus ring for anyone arriving on the keyboard. */
.portal [data-nav],.portal [data-tile],.portal [data-pact],.portal .plink,
.portal [data-det],.portal [data-thread],.portal [data-table],.portal [data-mile],
.portal [data-web],.portal [data-rep]{cursor:pointer}
.portal [data-pact]{user-select:none}
.pcard{transition:border-color .25s ease,box-shadow .25s ease,transform .25s cubic-bezier(.22,.6,.2,1)}
.pcard:hover{border-color:#CFC5B4;box-shadow:0 4px 10px -6px rgba(20,10,10,.22);transform:translateY(-1px)}
.portal__item:hover{background:color-mix(in srgb,#F4EFE7 18%,transparent)}
.portal [data-tile]:hover{border-color:#CFC5B4;transform:translateY(-1.5px);box-shadow:0 4px 8px -4px rgba(20,10,10,.24)}
.pact:hover{filter:brightness(1.07)}
.portal :focus-visible{outline:1.5px solid var(--bordeaux);outline-offset:1px;border-radius:3px}
.portal__side :focus-visible{outline-color:#F4EFE7}

/* the control the hand is about to hit, then hits — one pair of states shared
   by every screen, so a new beat in the film needs no new CSS */
.portal__item{transition:background .25s ease,opacity .25s ease}
.portal__item.is-armed{background:color-mix(in srgb,#F4EFE7 22%,transparent);opacity:.92}
.portal__item.is-hit{background:color-mix(in srgb,#F4EFE7 34%,transparent)}
.pcard.is-armed{background:#FDFBF7;border-color:#CFC5B4;transform:translateY(-1px)}
.pdet__r.is-hit,.pthread.is-hit,.pmile.is-hit,.pweb__tab.is-hit,.prep__tab.is-hit{background:#F4EFE7}
.ptable.is-hit{transform:scale(.93)}

.portal__decide{display:flex;flex-direction:column;gap:calc(5*var(--pu))}
.pdec{display:flex;align-items:center;justify-content:space-between;gap:calc(6*var(--pu));
  background:#fff;border:1px solid #E5DFD4;border-radius:5px;padding:calc(5*var(--pu)) calc(8*var(--pu))}
.pdec__tag{font-size:calc(4.6*var(--pu));letter-spacing:.14em;text-transform:uppercase;opacity:.5;font-weight:700;flex:none}
.pdec b{font-family:var(--serif);font-weight:400;font-size:calc(8*var(--pu));text-align:right;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pdec b.swap{animation:pdecSwap .52s cubic-bezier(.22,.6,.2,1)}
@keyframes pdecSwap{0%{opacity:0;transform:translateY(5px)}100%{opacity:1;transform:none}}
.portal__actions{display:grid;grid-template-columns:1fr 1fr;gap:calc(5*var(--pu))}
.pact{border-radius:4px;padding:calc(5*var(--pu));text-align:center;font-size:calc(5.5*var(--pu));font-weight:700;letter-spacing:.06em;
  transition:transform .17s cubic-bezier(.4,0,.5,1),filter .17s ease,box-shadow .3s ease}
.pact--ok{background:var(--portal-green);color:#fff}
.pact--no{background:var(--portal-red);color:#fff}
.pact.is-armed{box-shadow:0 0 0 1.4px rgba(255,255,255,.75),0 0 0 3.2px rgba(19,17,16,.13)}
.pact.is-hit{transform:scale(.955);filter:brightness(.88);box-shadow:inset 0 1px 2.5px rgba(0,0,0,.34)}
[data-pv].bump,[data-pn].bump,[data-rq].bump{animation:pBump .62s ease}
@keyframes pBump{0%{opacity:.22;transform:translateY(3px)}100%{opacity:1;transform:none}}

/* ---------- the portal on smaller glass ----------
   Nothing here is hidden: all nine screens stay reachable on a phone, because
   the whole promise of this hero is that the sidebar is real. What changes is
   the proportion — the tablet grows taller once the hand is gone (there is no
   longer a wrist to leave room for), the UI is scaled up against it so 4.6
   design px is still readable ink, and the two four-across grids drop to two.
   --pk is the only lever; every rule above rides on it. */
@media(max-width:900px){
  .portal{--pk:1.06}
}
@media(max-width:620px){
  .device__screen{aspect-ratio:4/4.3}
  .portal{--pk:1.5;grid-template-columns:calc(104*var(--pu)) 1fr}
  .portal__cards,.pgrid{grid-template-columns:1fr 1fr}
  /* fewer columns means more rows, and the room would grow past its panel —
     so the plan shows fewer tables instead of scrolling or clipping */
  .pplan{grid-template-columns:repeat(4,1fr)}
  .ptable{width:min(100%,calc(50*var(--pu)))}
  .ptable:nth-child(n+13){display:none}
  .psteps .pstep span{display:none}
  .portal__hi p{display:none}
  .pdet__r>span{width:calc(44*var(--pu))}
  .pthread b{max-width:calc(70*var(--pu))}
  /* the feed is the one panel that would rather clip than shrink: two rows
     is what fits once the tablet is phone-sized */
  .pfeed .pfr:nth-child(n+3){display:none}
}
@media(max-width:420px){
  .device__screen{aspect-ratio:4/4.9}
  .portal{--pk:1.85;grid-template-columns:calc(96*var(--pu)) 1fr}
  .pcard .plink,.pmile em,.pthread em{display:none}
  .pplan{grid-template-columns:repeat(3,1fr)}
  .ptable{width:min(100%,calc(44*var(--pu)))}
  .ptable:nth-child(n+10){display:none}
  .pwp[data-webview="rsvp"].on{grid-template-columns:1fr 1fr}
}

/* ============================================================
   WHAT WE SOLVE  (home section)
   ============================================================ */
.solve__head{max-width:900px;margin-bottom:clamp(40px,6vh,80px)}
.solve__head .eyebrow{margin-bottom:26px}
.stats{display:grid;grid-template-columns:repeat(3,1fr);gap:1px;background:color-mix(in srgb,var(--onyx) 12%,transparent);border:1px solid color-mix(in srgb,var(--onyx) 12%,transparent);margin-bottom:14px}
.stat{background:var(--linen);padding:clamp(24px,3vw,44px)}
.stat b{font-family:var(--serif);font-weight:300;font-size:var(--fs-stat);line-height:1;display:block;margin-bottom:12px}
.stat b sup{font-size:.4em;color:var(--accent)}
.stat p{font-size:var(--fs-body);max-width:230px;color:color-mix(in srgb,var(--onyx) 75%,var(--linen))}
/* The sources footnote is the smallest type on the site — owner, 2026-08-31:
   "much smaller". It sits below the label step on purpose, so it reads as a
   citation rather than as copy. Dropping from 11px to 9.5px costs contrast at
   38% onyx, so the ink steps up to 48% to hold legibility at the smaller size. */
.solve__src{font-size:9.5px;letter-spacing:.03em;color:color-mix(in srgb,var(--onyx) 66%,var(--linen));max-width:760px;line-height:1.65}
.solve__src a{border-bottom:1px dotted currentColor}
.chaos{margin-top:clamp(50px,8vh,100px);display:grid;grid-template-columns:1fr 1fr;gap:clamp(30px,4vw,90px);align-items:center}
.chaos__words{display:flex;flex-wrap:wrap;gap:10px 14px}
.chaos__words span{
  font-family:var(--serif);font-weight:300;font-size:var(--fs-d5);
  border:1px solid color-mix(in srgb,var(--onyx) 22%,transparent);
  border-radius:0;padding:8px 20px;white-space:nowrap;
}
@media(max-width:900px){.stats{grid-template-columns:1fr}.chaos{grid-template-columns:1fr}}

/* ---------- 99 → 1 pinned scene (Bordeaux) ---------- */
.shift{height:340vh}
.shift__pin{position:sticky;top:0;height:100svh;overflow:hidden;display:flex;align-items:center;justify-content:center}
/* The numeral and its caption live in ONE flow column, not stacked absolutely.
   They used to overlap: the numeral was centred in the pin and the caption was
   pinned to bottom:12vh, so on a short/wide viewport the 99 sat straight on top
   of "Without Evorrah". A column with a gap makes that collision impossible. */
.shift__stage{
  position:relative;z-index:2;height:100%;width:100%;box-sizing:border-box;
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:clamp(10px,2.5vh,34px);padding:8vh 5vw 7vh;
}
.shift__num{
  font-family:var(--serif);font-weight:300;line-height:.85;
  font-size:min(56vw,46vh);letter-spacing:-.04em;
  flex:0 0 auto;will-change:transform,opacity;
}
.shift__pct{font-size:.22em;vertical-align:super;color:var(--accent)}
/* AA: 60% predicted 5.57:1 against flat --bordeaux and MEASURED 3.14:1,
   because the stage behind this is a gradient, not the flat colour the mix
   names. Measuring rather than computing is the whole reason the audit rig
   drives a browser. 88% measures 5.1:1 in place. */
.g-bordeaux .shift__pct{color:color-mix(in srgb,var(--linen) 88%,var(--bordeaux))}
.shift__cap{text-align:center;width:min(90vw,700px);flex:0 0 auto}
.shift__cap .eyebrow{justify-content:center}
.shift__cap .eyebrow::before{background:var(--linen)}
.shift__cap h3{font-family:var(--serif);font-weight:300;font-size:var(--fs-d4);margin-top:14px}
.shift__orbit{position:absolute;inset:0;z-index:1;pointer-events:none}
.shift__orbit span{
  position:absolute;font-family:var(--serif);font-weight:300;
  font-size:var(--fs-lede);opacity:.55;white-space:nowrap;
  will-change:transform,opacity;
}
.shift__handled{
  position:absolute;inset:0;z-index:2;display:flex;flex-direction:column;
  align-items:center;justify-content:center;text-align:center;
  opacity:0;will-change:opacity,transform;pointer-events:none;
}
.shift__handled .hb{font-family:var(--sans);font-size:var(--fs-label-lg);font-weight:var(--fw-label);letter-spacing:var(--ls-label-lg);text-transform:uppercase;margin-bottom:clamp(14px,2vh,24px);opacity:.7}
/* the numeral the 99 became — its own line between the label and the sentence */
.shift__one{
  font-family:var(--serif);font-weight:300;line-height:.82;letter-spacing:-.04em;
  font-size:min(30vw,26vh);margin-bottom:clamp(12px,2.2vh,28px);
}
.shift__handled h3{font-family:var(--serif);font-weight:300;font-size:var(--fs-plan);line-height:1.06}
.shift__handled h3 em{font-style:italic}
.shift__final{
  position:absolute;inset:0;z-index:4;display:flex;flex-direction:column;
  align-items:center;justify-content:center;text-align:center;
  gap:clamp(14px,2.4vh,26px);padding:8vh var(--gutter);box-sizing:border-box;
  opacity:0;will-change:opacity;pointer-events:none;
}
.shift__final.pe{pointer-events:auto}
/* owner 2026-09-01: this eyebrow reads centred over the bordeaux ground with
   nothing else on its line, so the 22px accent rule before it hangs off the
   left of the label rather than anchoring it. Dash removed here only. */
.shift__final .eyebrow::before{display:none}
.shift__final p{font-family:var(--serif);font-weight:300;font-size:var(--fs-d5);max-width:640px}
/* the close of the scene — the sentence that was a single small line is now the
   headline of the frame, with the trade-off set out as a two-column ledger */
.flip__t{
  font-family:var(--serif);font-weight:300;line-height:1.02;letter-spacing:-.02em;
  font-size:var(--fs-mega);
}
.flip{
  display:grid;grid-template-columns:1fr 1px 1fr;align-items:start;
  gap:clamp(18px,4vw,64px);width:min(100%,820px);
  margin-top:clamp(6px,1.4vh,16px);
}
.flip::before{
  content:"";grid-column:2;align-self:stretch;
  background:color-mix(in srgb,var(--linen) 22%,transparent);
}
.flip__col{grid-row:1;text-align:center}
.flip__col--less{grid-column:1}
.flip__col--more{grid-column:3}
.flip__col h4{
  font-family:var(--sans);font-size:var(--fs-label-lg);font-weight:var(--fw-label);
  letter-spacing:var(--ls-label-lg);text-transform:uppercase;
  color:color-mix(in srgb,var(--linen) 56%,transparent);margin-bottom:clamp(8px,1.4vh,16px);
}
.flip__col li{
  font-family:var(--serif);font-weight:300;line-height:1.25;
  font-size:var(--fs-d4);
  padding:clamp(5px,1vh,11px) 0;
}
.flip__col--less li{
  color:color-mix(in srgb,var(--linen) 56%,var(--bordeaux));
  text-decoration:line-through;text-decoration-thickness:1px;
  text-decoration-color:color-mix(in srgb,var(--linen) 34%,transparent);
}
.flip__col--more li em{font-style:italic;color:var(--linen)}
@media(max-width:640px){
  .flip{grid-template-columns:1fr;gap:clamp(14px,3vh,26px)}
  .flip::before{display:none}
  .flip__col{grid-row:auto;grid-column:1 !important}
}

/* ============================================================
   EVENTS WE'VE HANDLED (home section)
   ============================================================ */
.evh__head{display:flex;justify-content:space-between;align-items:flex-end;gap:30px;margin-bottom:clamp(36px,5vh,64px);flex-wrap:wrap}
.evh__head .eyebrow{margin-bottom:24px}
.evh__count{font-family:var(--serif);font-weight:400;font-size:var(--fs-body);color:color-mix(in srgb,var(--onyx) 66%,var(--linen))}
.evh__count b{font-weight:var(--fw-emph);color:var(--accent);font-size:var(--fs-lede)}
/* The rail carries all 53 now, so it needs the one line of instruction a
   53-card drag surface earns. Caption size, sans, quieter than the sentence
   above it — 66% onyx on linen is 5.7:1, the same value the sentence uses. */
.evh__note{display:block;margin-top:10px;font-family:var(--sans);
  font-size:var(--fs-cap);color:color-mix(in srgb,var(--onyx) 66%,var(--linen))}
.deck{position:relative}
.deck__rail{
  display:flex;gap:18px;overflow-x:auto;scroll-snap-type:x mandatory;
  padding-bottom:26px;scrollbar-width:none;
  mask-image:linear-gradient(90deg,transparent 0,#000 var(--gutter),#000 calc(100% - var(--gutter)),transparent 100%);
  -webkit-mask-image:linear-gradient(90deg,transparent 0,#000 var(--gutter),#000 calc(100% - var(--gutter)),transparent 100%);
  padding-left:var(--gutter);padding-right:var(--gutter);
  /* ⚠ THE SNAPPORT IS THE PADDING BOX, NOT THE PADDING EDGE — without this the
     rail loads ALREADY SCROLLED BY ONE GUTTER (measured on production
     2026-09-03: scrollLeft 64 on a 64px gutter). `scroll-snap-type:x mandatory`
     snaps the first card's start edge to the start of the SNAPPORT, and the
     snapport is inset by scroll-padding — which defaults to auto (0), NOT by
     this element's own padding-left. So the browser pulled the rail 64px on
     load, the first card sat flush at x=0, and the mask's transparent→#000
     fade over that same first 64px ate its left edge: the first commission
     looked cropped and half-faded while every other card was whole.
     scroll-padding-inline restates the gutter in the scroll coordinate system,
     where the padding does not reach. Both values must move together. */
  scroll-padding-inline:var(--gutter);
}
.deck__rail::-webkit-scrollbar{display:none}
/* ⚠ NO SCRIM. THE ARTWORK IS THE PRODUCT — 2026-08-31 (owner: "remove the black
   cover black contrast in the front pages/covers of the designs").
   This card used to be a single 3/3.6 box with the title laid over the cover and
   a linear-gradient from rgba(15,8,8,0) to .88 under it. The gradient existed
   for a real reason (linen type over 53 unvetted covers, several of them nearly
   white) but it darkened the bottom HALF of every commission's artwork to buy
   that contrast — and the covers are square paintings, so the half it dimmed was
   half the painting.
   The fix is structural, not cosmetic: the type moved OFF the image. The cover
   now sits in its own square frame with nothing over it, and the occasion, the
   title, the venue and the link sit below it on the section's linen ground,
   flowing straight into .edet (which was already there, already on linen). So
   the contrast problem is solved by not creating it — every line below is onyx
   on #F8F1E7 and nothing depends on what the photograph happens to be.
   DO NOT reintroduce ::after here. If type ever goes back over the image, the
   scrim comes back with it and so does this. */
.ecard{
  flex:0 0 min(380px,78vw);scroll-snap-align:start;
  position:relative;display:block;
  transition:transform .6s cubic-bezier(.22,.6,.2,1);
}
.ecard:hover{transform:translateY(-8px)}
/* The frame, not the card, carries the crop and the clipping. 1/1 because the
   covers ARE square (24 of the 25 files are 1100x1100; the odd one is 1200x900),
   so a square frame shows each painting as it was made instead of cropping ~9%
   off its sides for a portrait box. */
.ecard__fig{
  position:relative;display:block;aspect-ratio:1/1;
  border-radius:6px;overflow:hidden;background:var(--alabaster);
}
.ecard img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;transition:transform 1.2s cubic-bezier(.22,.6,.2,1)}
.ecard:hover img{transform:scale(1.045)}
/* Onyx on linen, measured: the title at full onyx is 17.4:1, and the two muted
   lines at onyx 66% are 5.7:1 — both clear AA for body text with margin. The
   opacity:.8 / .75 the old lines carried is GONE on purpose: dimming was there
   to soften linen against a photograph, and against linen it only costs
   contrast (the note the scrim block used to carry). */
.ecard__body{padding:16px 2px 0;color:var(--onyx)}
.ecard__occ{font-size:var(--fs-label);font-weight:var(--fw-label);letter-spacing:var(--ls-label);text-transform:uppercase;
  color:color-mix(in srgb,var(--onyx) 66%,var(--linen));margin-bottom:8px;display:block}
.ecard__body h3,.ecard__body .ecard__t{font-family:var(--serif);font-weight:300;font-size:var(--fs-d5);line-height:1.15;margin-bottom:6px;color:var(--onyx)}
.ecard__meta{font-size:var(--fs-cap);color:color-mix(in srgb,var(--onyx) 66%,var(--linen))}
/* The plan chip moved off the cover and into the body with everything else. As
   an outlined pill on linen it needs no wash at all; the 55% border is the same
   value .filters uses, and for the same reason (WCAG 1.4.11 wants 3:1 on a
   control's boundary — 55% measures 3.1). */
.ecard__plan{
  display:inline-block;vertical-align:middle;margin-right:9px;
  font-size:var(--fs-label);font-weight:var(--fw-label);letter-spacing:var(--ls-label);text-transform:uppercase;
  border:1px solid color-mix(in srgb,var(--onyx) 55%,transparent);border-radius:0;
  padding:3px 11px;color:color-mix(in srgb,var(--onyx) 78%,var(--linen));
}
/* Solid bordeaux, not a translucent black wash with a blur behind it. It is a
   38px button that appears on hover, so it reads as a control rather than as
   something dimming the picture. */
.ecard__go{
  position:absolute;top:18px;right:18px;z-index:2;
  width:38px;height:38px;border-radius:50%;border:1px solid var(--bordeaux);
  background:var(--bordeaux);
  display:grid;place-items:center;color:var(--linen);font-size:15px;
  opacity:0;transform:translateY(-6px);transition:opacity .4s,transform .4s,background .3s,border-color .3s;
}
.ecard:hover .ecard__go{opacity:1;transform:none}
.ecard__go:hover{background:var(--accent);border-color:var(--accent)}

.evh__all{margin-top:14px;display:flex;justify-content:center}

/* ── THE HOME EVENT CARD (.hcard) ─────────────────────────────────────────
   The deck used to carry a photo with a name on it, which read as a mood
   board rather than a record of work. The card now states the same six facts
   the event's own page states — venue, guests, designs, how long
   it ran, and the palette that was actually delivered — so a stranger can
   tell these are commissions and not styling. Facts come from events-data.js,
   transcribed from the linked page; nothing is composed in the template.
   .ecard is left untouched: /events/ still uses it. */
.hcard{
  flex:0 0 min(420px,84vw);scroll-snap-align:start;
  display:flex;flex-direction:column;background:var(--linen);
  border:1px solid color-mix(in srgb,var(--onyx) 12%,transparent);
  border-radius:6px;overflow:hidden;
  transition:transform .6s cubic-bezier(.22,.6,.2,1),
             box-shadow .6s cubic-bezier(.22,.6,.2,1),
             border-color .4s;
}
.hcard:hover{transform:translateY(-8px);border-color:color-mix(in srgb,var(--onyx) 22%,transparent);
  box-shadow:0 26px 50px -34px rgba(20,10,10,.55)}
.hcard__shot{position:relative;display:block;aspect-ratio:3/2.05;overflow:hidden;background:var(--alabaster)}
.hcard__shot img{width:100%;height:100%;object-fit:cover;transition:transform 1.2s cubic-bezier(.22,.6,.2,1)}
.hcard:hover .hcard__shot img{transform:scale(1.045)}
/* ⚠ A SCRIM OVER PHOTOGRAPHY IS SIZED FOR THE BRIGHTEST PHOTO, NOT THE ONE ON
   SCREEN — 2026-08-31. Both chips carry --linen text over whatever cover image
   the event happens to have, so the only backdrop worth measuring is white:
   a bride in daylight, a white marquee, an overexposed table setting. At the
   .52 and .42 these were drawn at, linen measured 3.55 and 2.58 over white —
   the label was already at 4.28 over the alabaster placeholder BEFORE any
   image loaded, which is what put it on the sweep. .62 is the point where
   linen clears 4.5 against a pure white backdrop (5.06), and it costs almost
   nothing on a dark cover, where the chip was already legible.
   The blur is decoration: backdrop-filter redistributes luminance, it does not
   lower it, so it can never be the thing that buys the contrast. */
.hcard__plan{
  position:absolute;left:14px;top:14px;z-index:2;
  font-size:var(--fs-label);font-weight:var(--fw-label);letter-spacing:var(--ls-label);text-transform:uppercase;
  color:var(--linen);background:rgba(15,8,8,.62);
  -webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);
  border:1px solid color-mix(in srgb,var(--linen) 30%,transparent);
  border-radius:0;padding:6px 13px;
}
.hcard__go{
  position:absolute;top:12px;right:12px;z-index:2;
  width:36px;height:36px;border-radius:50%;
  border:1px solid color-mix(in srgb,var(--linen) 55%,transparent);
  background:rgba(15,8,8,.62);-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);
  display:grid;place-items:center;color:var(--linen);font-size:15px;
  opacity:0;transform:translateY(-6px);transition:opacity .4s,transform .4s,background .3s;
}
.hcard:hover .hcard__go{opacity:1;transform:none}
.hcard__body{display:flex;flex-direction:column;flex:1;padding:20px 22px 22px}
.hcard__occ{
  font-size:var(--fs-label);font-weight:var(--fw-label);letter-spacing:var(--ls-label);text-transform:uppercase;
  color:var(--accent);margin-bottom:9px;
}
.hcard__body h3{
  font-family:var(--serif);font-weight:300;font-size:var(--fs-d5);
  line-height:1.14;margin-bottom:7px;
}
.hcard__where{
  display:flex;gap:8px;align-items:baseline;
  font-size:var(--fs-cap);color:color-mix(in srgb,var(--onyx) 66%,var(--linen));
}
.hcard__where::before{
  content:"";flex:none;width:5px;height:5px;border-radius:50%;
  background:var(--bordeaux);transform:translateY(-2px);
}
.hcard__facts{
  display:grid;grid-template-columns:1fr 1fr;gap:13px 18px;
  margin-top:17px;padding-top:15px;
  border-top:1px solid color-mix(in srgb,var(--onyx) 13%,transparent);
}
.hcard__facts > span{display:flex;flex-direction:column;gap:3px;min-width:0}
.hcard__facts i{
  font-style:normal;font-size:var(--fs-label);font-weight:var(--fw-label);letter-spacing:var(--ls-label);text-transform:uppercase;
  color:color-mix(in srgb,var(--onyx) 66%,var(--linen));
}
.hcard__facts b{
  font-weight:400;font-size:var(--fs-cap);line-height:1.34;
  color:color-mix(in srgb,var(--onyx) 88%,var(--linen));
}
.hcard__sw{display:flex;gap:6px;margin-top:16px}
.hcard__sw i{
  width:15px;height:15px;border-radius:50%;display:block;
  box-shadow:inset 0 0 0 1px rgba(20,20,20,.14);
}
.hcard__note{
  font-family:var(--serif);font-style:italic;font-weight:400;
  font-size:var(--fs-body);line-height:1.5;margin-top:14px;
  color:color-mix(in srgb,var(--onyx) 72%,var(--linen));
}
/* The plain card's closing block, standing where .hcard__note stands on the
   ten authored ones. Sans and caption-sized rather than serif italic, because
   it is a list of deliverables and not a sentence — the two must not be
   mistaken for each other at a glance down the rail. 78% onyx on linen is
   7.9:1, the label above it 5.7:1. */
.hcard__made{margin-top:15px;font-size:var(--fs-cap);line-height:1.5;
  color:color-mix(in srgb,var(--onyx) 78%,var(--linen))}
.hcard__made i{display:block;font-style:normal;margin-bottom:6px;
  font-size:var(--fs-label);font-weight:var(--fw-label);letter-spacing:var(--ls-label);text-transform:uppercase;
  color:color-mix(in srgb,var(--onyx) 66%,var(--linen))}
.hcard__more{
  margin-top:auto;padding-top:18px;display:flex;align-items:center;gap:9px;
  font-size:var(--fs-label);font-weight:var(--fw-label);letter-spacing:var(--ls-label);text-transform:uppercase;
  color:color-mix(in srgb,var(--onyx) 66%,var(--linen));transition:color .3s;
}
.hcard:hover .hcard__more{color:var(--bordeaux)}
.hcard__more .arr{transition:transform .4s cubic-bezier(.22,.6,.2,1)}
.hcard:hover .hcard__more .arr{transform:translateX(5px)}
@media(max-width:620px){
  .hcard__facts{gap:12px 14px}
}
@media(prefers-reduced-motion:reduce){
  .hcard,.hcard__shot img,.hcard__more .arr{transition:none}
  .hcard:hover{transform:none}
  .hcard:hover .hcard__shot img{transform:none}
}

/* ============================================================
   OUR PROCESS (home section, Bordeaux, sticky left)
   ============================================================ */
.proc{display:grid;grid-template-columns:.9fr 1.4fr;gap:clamp(36px,5vw,110px)}
.proc__left{position:sticky;top:110px;align-self:start}
.proc__left .eyebrow{margin-bottom:26px}
.proc__left h2{margin-bottom:30px}
.proc__idx{display:flex;flex-direction:column;gap:2px;margin-top:8px}
.proc__idx button{
  text-align:left;font-size:var(--fs-label);font-weight:var(--fw-label);letter-spacing:var(--ls-label);text-transform:uppercase;
  padding:9px 0;color:color-mix(in srgb,var(--onyx) 66%,var(--alabaster));
  display:flex;gap:14px;align-items:baseline;transition:color .3s;
}
.proc__idx button b{font-weight:var(--fw-emph);font-size:var(--fs-label);color:var(--accent);opacity:0;transition:opacity .3s}
.proc__idx button.on{color:var(--onyx)}
.proc__idx button.on b{opacity:1}
@media(prefers-reduced-motion:reduce){.proc__idx button,.proc__idx button b{transition:none}}
.pstepx{padding:clamp(30px,4vh,52px) 0;border-top:1px solid color-mix(in srgb,var(--onyx) 14%,transparent)}
.pstepx:last-child{border-bottom:1px solid color-mix(in srgb,var(--onyx) 14%,transparent)}
.pstepx__num{font-size:var(--fs-label);font-weight:var(--fw-label);letter-spacing:var(--ls-label);color:var(--accent);margin-bottom:14px;display:block}
.pstepx h3{font-family:var(--serif);font-weight:300;font-size:var(--fs-d4);margin-bottom:14px}
.pstepx p{max-width:560px;color:color-mix(in srgb,var(--onyx) 76%,var(--alabaster))}
.proc__coda{margin-top:clamp(40px,6vh,70px);font-family:var(--serif);font-weight:300;font-size:var(--fs-d5);max-width:680px;line-height:1.4}
.proc__coda em{font-style:italic;color:var(--bordeaux)}
@media(max-width:900px){.proc{grid-template-columns:1fr}.proc__left{position:static}.proc__idx{display:none}}

/* ---------- the section on BORDEAUX (2026-08-31) ----------
   #process moved from the alabaster ground to bordeaux, so every colour
   above that was mixed toward --onyx / --alabaster is now mixing toward a
   ground it is no longer sitting on. Rebound below.

   ⚠ ON BORDEAUX THE SEPARATION IS EXPOSURE, NOT HUE. The 01-06 numerals
   and the eyebrow hairline follow --accent, which .g-bordeaux already
   rebinds to linen (any red on #4C061D measures 2.9:1 or worse), and
   .proc__coda em reads --accent too -- so the coda's emphasis only reads as
   emphasis if the LINE around it steps down. Measured against #4C061D:
       linen 100%  13.86:1   headings, numerals, coda emphasis
       linen  78%   8.71:1   step paragraphs
       linen  72%   7.56:1   the coda line
       linen  62%   5.88:1   the 11px index, off state (AA floor is 4.5)
   The hairlines have to be made of linen as well: an onyx 14% rule is
   invisible on bordeaux, and a red one is worse than invisible. */
.g-bordeaux .proc__idx button{color:color-mix(in srgb,var(--linen) 62%,var(--bordeaux))}
.g-bordeaux .proc__idx button.on{color:var(--linen)}
.g-bordeaux .pstepx{border-top-color:color-mix(in srgb,var(--linen) 18%,transparent)}
.g-bordeaux .pstepx:last-of-type{border-bottom:1px solid color-mix(in srgb,var(--linen) 18%,transparent)}
.g-bordeaux .pstepx p{color:color-mix(in srgb,var(--linen) 78%,var(--bordeaux))}
.g-bordeaux .proc__coda{color:color-mix(in srgb,var(--linen) 72%,var(--bordeaux))}

/* ============================================================
   WHAT WE HANDLE FOR YOU (home section, Onyx, huge type rows)
   ============================================================ */
.plans__head{display:flex;justify-content:space-between;align-items:flex-end;gap:30px;margin-bottom:clamp(40px,6vh,80px);flex-wrap:wrap}
.plans__head .eyebrow{margin-bottom:24px}
.plans__head .eyebrow::before{background:var(--accent)}
.plans__hint{font-size:var(--fs-cap);color:color-mix(in srgb,var(--linen) 56%,transparent);max-width:300px}
.prow{border-top:1px solid color-mix(in srgb,var(--linen) 14%,transparent)}
.prow:last-of-type{border-bottom:1px solid color-mix(in srgb,var(--linen) 14%,transparent)}
.prow__bar{
  width:100%;display:grid;grid-template-columns:auto 1fr auto auto;gap:clamp(16px,3vw,50px);
  align-items:center;padding:clamp(26px,4vh,44px) 0;text-align:left;
  transition:padding .45s cubic-bezier(.4,0,.2,1);
}
.prow__num{font-size:var(--fs-label);font-weight:var(--fw-label);letter-spacing:var(--ls-label);color:color-mix(in srgb,var(--linen) 56%,transparent);transition:color .4s}
.prow__name{
  font-family:var(--serif);font-weight:300;
  font-size:var(--fs-plan);line-height:1;
  transition:color .5s cubic-bezier(.4,0,.2,1), transform .5s cubic-bezier(.4,0,.2,1);
  transform-origin:left center;
}
.prow__spec{font-size:var(--fs-label);font-weight:var(--fw-label);letter-spacing:var(--ls-label);text-transform:uppercase;color:color-mix(in srgb,var(--linen) 56%,transparent);white-space:nowrap}
.prow__price{font-family:var(--serif);font-weight:300;font-size:var(--fs-d5);white-space:nowrap}
.prow:hover .prow__name{color:var(--accent)}
.prow.open .prow__num{color:var(--accent)}
.prow.open .prow__name{color:var(--accent)}
.prow__body{
  display:grid;grid-template-rows:0fr;transition:grid-template-rows .65s cubic-bezier(.4,0,.2,1);
}
.prow.open .prow__body{grid-template-rows:1fr}
.prow__inner{overflow:hidden}
/* The open panel is one grid, not a stack. The three fact blocks (add-ons,
   support, design work) sit SIDE BY SIDE across the full width — a plan panel
   that ran them one under the other left the right half empty and read as a
   very tall left column. Placement is automatic: the lede, the at-a-glance
   line, the columns, the toggle and the foot all claim whole rows, so the fact
   blocks flow into the row between the columns and the toggle in DOM order.
   Add a fact block and it takes a track; add a fifth and it wraps, which is the
   reason nothing here is pinned to a column number.
   ⚠ THE FACT BLOCKS MOVED BELOW .prow__cols on 2026-09-01 (owner). They used to
   sit ABOVE it, in the row .prow__at vacated on open, which meant opening a
   panel replaced one line with three blocks and pushed the columns down the
   screen. They are appended below the columns now; keep them there. */
/* ONE TRACK. This was four, then three, because the three fact blocks used to sit
   side by side in a row of this grid. They live inside .prow__side now, so every
   remaining child — the lede, the at-a-glance line, .prow__cols, the toggle and
   the foot — spans the full width, and a multi-track grid here would only be a
   trap for whoever next adds a child expecting it to be full-bleed.
   minmax(0,1fr), not 1fr: the same WCAG 1.4.10 overflow trap as .prow__cols. */
.prow__pad{
  padding:0 0 clamp(36px,5vh,60px);
  display:grid;grid-template-columns:minmax(0,1fr);
  column-gap:clamp(24px,3vw,60px);row-gap:clamp(26px,3.5vh,40px);
  align-items:start;
}
.prow__pos{grid-column:1/-1;font-family:var(--serif);font-weight:300;font-size:var(--fs-lede);max-width:640px;margin-bottom:0;color:color-mix(in srgb,var(--linen) 85%,transparent)}
/* The two paid add-ons, stated on the row itself.
   ⚠ THE EVENT WEBSITE AND THE ANIMATED OPENING ARE **NOT** INCLUDED WITH SUITE —
   PACKAGE_ENTITLEMENTS in packages/contracts/prices.js is what the checkout bills
   on, and it says false for both on `suite`, true on `whole_event` and
   `fully_handled`. The Suite row listed "Event Website" as one of its four parts
   until 2026-08-31; a Suite buyer read "included" and would then have been charged
   $99. Keep every claim on these rows derived from that contract, not from memory. */
.prow__addons{
  font-size:var(--fs-cap);line-height:1.65;max-width:none;margin-bottom:0;
  padding:clamp(20px,2.6vh,30px) 0 0;border-top:1px solid color-mix(in srgb,var(--linen) 12%,transparent);
  color:color-mix(in srgb,var(--linen) 62%,transparent);
}
.prow__addons b{font-weight:var(--fw-emph);color:var(--accent)}
/* ⚠ THE SELECTOR COVERS THE HANDLED AND DESIGN LISTS TOO, and it did not before.
   The three plan rows carry links in two places — the labelled lines
   (.prow__addons and friends) and the <ul>s inside .prow__col. Only the first
   had this rule, so the "Evorrah Design Collection" link added to each designs
   column rendered with NO underline and no colour shift: measured, borderBottom
   0px against the addon link's 1px, on an onyx ground where nothing else marks
   it. A link nobody can see is not a link. */
.prow__addons a,
.prow__design a,
.prow__col ul a{border-bottom:1px solid color-mix(in srgb,var(--linen) 28%,transparent);transition:color .3s,border-color .3s}
.prow__addons a:hover,
.prow__design a:hover,
.prow__col ul a:hover{color:var(--linen);border-color:var(--linen)}
/* ⚠ THE RETIRED LANGUAGES ROW IS GONE FROM THIS FILE (2026-09-03), not merely unused.
   Its rules were kept on 2026-09-01 for a colour lab, and that reason expired: the lab
   carries none of them any more, so the only file in the repository still naming that class
   was this stylesheet describing itself. A shipped stylesheet is a client surface, dead
   rules and comments included — which is why the brand gate reads them, and why deleting
   is the fix rather than an exception. The row is not sold: English only, owner 2026-09-01.
   ⚠ Do not reintroduce the class name here, even in prose: scripts/v15-brand-check.mjs
   matches the identifier itself, and a comment explaining a retirement must not restate
   the retired vocabulary to do it. */
.prow__addons i{
  display:block;margin-bottom:9px;font-style:normal;
  font-size:var(--fs-label);font-weight:var(--fw-label);letter-spacing:var(--ls-label);text-transform:uppercase;
  color:color-mix(in srgb,var(--linen) 56%,transparent);
}
/* SUPPORT — who answers you, and how fast. It answers the question every row in
   this band answers: what does THIS plan give you that the one below does not.
   ⚠ THE PROMISES ARE QUOTED VERBATIM FROM PACKAGE_SUPPORT in
   packages/contracts/prices.js — "2 business days", "1 business day", "same
   business day" (tightened by the owner 2026-09-01, from 3 / 2 / same-day). That
   constant carries its own warning that it is customer-facing prose rather than a
   number; these three strings are the promise Evorrah made, so re-word the
   sentence around them if you must, never the promise itself.
   ⚠ ITS SIBLING IS GONE. This block used to point at a retired row above — the row that
   sold a per-plan count of them, removed from the plan cards on 2026-09-01 when Evorrah
   stopped selling that ladder (English only, owner). Its rules were kept that day for a
   colour lab and deleted on 2026-09-03 once the lab stopped rendering them; see the note
   above this block. */
.prow__support{
  font-size:var(--fs-cap);line-height:1.65;max-width:none;margin-bottom:0;
  padding:clamp(20px,2.6vh,30px) 0 0;border-top:1px solid color-mix(in srgb,var(--linen) 12%,transparent);
  color:color-mix(in srgb,var(--linen) 62%,transparent);
}
.prow__support b{font-weight:var(--fw-emph);color:var(--accent)}
.prow__support i{
  display:block;margin-bottom:9px;font-style:normal;
  font-size:var(--fs-label);font-weight:var(--fw-label);letter-spacing:var(--ls-label);text-transform:uppercase;
  color:color-mix(in srgb,var(--linen) 56%,transparent);
}

/* THE DESIGN WORK — creative directions, revision rounds and the three design
   add-ons, on a rule of their own directly under the support row.
   ⚠ IT IS SCOPED ON PURPOSE. Directions (PACKAGE_DIRECTIONS 2/2/3) and revision
   rounds (PACKAGE_REVISION_ROUNDS 2/3/4) govern the ARTWORK ONLY — they are not
   event terms, and a reader who takes "2 revision rounds" for a limit on the
   event has been misled by the layout. The .prow__scope line says so in words
   rather than relying on the label alone.
   ⚠ THE EXTRA REVISION ROUND CARRIES NO NUMBER HERE, DELIBERATELY. It is priced
   off events.piece_count by revisionRoundCents() — ADDON_REVISION_ROUND_CENTS_BY_SIZE
   has keys for 3/6/9/12/16 and NOTHING ELSE, so any plan count not in that map
   falls back to the flat $75. Do not quote a figure on this line until the
   plan's design count and that map agree. $125/$245 are flat and safe to state. */
.prow__design{
  font-size:var(--fs-cap);line-height:1.65;max-width:none;margin-bottom:0;
  padding:clamp(20px,2.6vh,30px) 0 0;border-top:1px solid color-mix(in srgb,var(--linen) 12%,transparent);
  color:color-mix(in srgb,var(--linen) 62%,transparent);
}
.prow__design b{font-weight:var(--fw-emph);color:var(--accent)}
.prow__design i{
  display:block;margin-bottom:9px;font-style:normal;
  font-size:var(--fs-label);font-weight:var(--fw-label);letter-spacing:var(--ls-label);text-transform:uppercase;
  color:color-mix(in srgb,var(--linen) 56%,transparent);
}
.prow__design em{font-style:italic;color:color-mix(in srgb,var(--linen) 85%,transparent)}
.prow__scope{
  display:block;margin-top:11px;
  font-size:var(--fs-label);font-weight:var(--fw-label);letter-spacing:var(--ls-label);text-transform:uppercase;
  color:color-mix(in srgb,var(--linen) 56%,transparent);
}
/* The band's air is the pad grid's row-gap now, not a margin on the last block
   in it — the three blocks are siblings in one row and must end level. */
/* "Moving up costs the difference" — stated ONCE under the three rows rather
   than three times inside them, because it is the answer to a question about
   the SET (which do I pick?), not about any one plan. */
/* Left-aligned and one notch quieter (owner, 2026-08-31). Centred at full lede
   width it read as a closing statement competing with the plans above it; ranged
   left and smaller it reads as the footnote it actually is. */
/* Pulled up against the table it annotates (owner, 2026-08-31: "too much
   space"). At 44px above and .more's full 96px below, a single 26px line sat
   marooned in a 166px band — attached to neither the plans nor the section
   after it. The fix shortened BOTH sides but not equally: ~18px above and ~50px
   below, on the theory that a caption should hang off the rows it annotates.
   ⚠ THAT ASYMMETRY IS REVERSED (owner, 2026-08-31, looking at the rendered
   band: "this text needs to be centralized vertically"). Read on screen it did
   not look attached to the plans, it looked like a line that had slipped off
   the bottom of the table: 18px of air above it and 50px below, inside a 91px
   band bounded by two hairlines — the last .prow's border-bottom above and
   .more--events' border-top below. Both gaps are now ONE token, so the line
   sits on the band's centre line and cannot drift off it again by an edit to
   one side. The band's TOTAL height is unchanged (18+50 = 68; 34+34 = 68), so
   this moved the sentence without reopening the space the earlier pass closed.
   ⚠ Change --upgrade-air, never either margin on its own — the moment the two
   sides can hold different values, this comes back. */
.plans__upgrade,.plans__upgrade + .more{--upgrade-air:clamp(28px,3.8vh,44px)}
.plans__upgrade{
  /* ONE line (owner, 2026-09-08: "make this 1 line"), superseding the 2026-09-01
     two-line reading that set max-width:700px. Every word stays; the measure gives
     way entirely. At the 12.5px ceiling the sentence needs ~1000px, which the
     .wrap (1480px minus gutters) has from ~1100px viewports up; below that it
     wraps to two, and nowrap is not used because an overflowing line is worse
     than a second one. */
  margin:var(--upgrade-air) 0 0;max-width:none;text-align:left;
  font-size:clamp(11px,.78vw,12.5px);line-height:1.75;
  color:color-mix(in srgb,var(--linen) 56%,transparent);
}
.plans__upgrade b{font-weight:var(--fw-emph);color:color-mix(in srgb,var(--linen) 82%,transparent)}
/* The footnote already carries the plans block's closing air, so the section
   rule beneath it does not need .more's full opening gap on top of it. Scoped
   to the adjacency: every other .more on the site keeps clamp(56px,8vh,96px).
   This is the LOWER half of the footnote's band and must stay identical to the
   upper half above — hence the shared token rather than a second clamp. */
.plans__upgrade + .more{margin-top:var(--upgrade-air)}

/* ---------- PROGRESSIVE DISCLOSURE INSIDE AN OPEN PLAN PANEL ----------
   A plan panel opens on the essentials only: who it is for, the guest cap, the
   NAMES of everything that plan has us handle (4, 8 or 12 of them — the list is
   cumulative, so each plan restates the ones below it), the design count, one
   at-a-glance line, and the CTA.
   Everything that explains, qualifies or compares is marked .xtra and held
   behind one "Show more" per plan.

   ONE class does all of it. .xtra is display:none closed and display:REVERT
   open, so the same class works on a <span> that continues a sentence, a <p>
   fact block, an <li>, a whole .prow__col and an <a> in the foot — each reverts
   to its own natural display rather than being forced to one. Do not swap
   revert for a literal value; it would flatten the list items and the columns.

   ⚠ NOTHING PRICED OR PROMISED MAY HIDE ALONE. A number a buyer could be
   charged ($125, $245, the revision round) may sit inside .xtra only while the
   thing it prices is also inside it. The plan prices, the guest caps and the
   lead times live outside the panel body; the support SLA, the
   direction and revision counts and the two add-ons are all restated on
   .prow__at, which is what stands in for the four fact blocks when closed. */
/* The class is DOUBLED on purpose. .prow__see sets display:inline-block at
   §2468, later in this file and at the same specificity, so a single .xtra
   loses the cascade to it and the "See the whole Design Collection" link
   stayed visible in the closed panel. .xtra.xtra outranks any one-class rule
   an element it is put on already carries. Do not simplify it away. */
.xtra.xtra,.xtra-li.xtra-li{display:none}
/* THE TWO DISCLOSURE CONTAINERS. A .prow__pad (one plan) and .plans__upgrade
   (the footnote) each take .xopen on their own toggle. .xtra-li is an alias for
   .xtra; both names behave identically, and it is kept because whole bullets
   read better marked with the list-shaped name.
   .more ("more than one event") is the THIRD container, restored 2026-08-31 at
   the owner's request after one earlier revert.
   Widened the same day (owner: only the eyebrow, the headline and the lede are
   to be "displayed", "for all the additional text, it's show me more"), so what
   the toggle holds is no longer a handful of qualifying lines inside the cards
   — it is the whole card grid, and in the planner note the whole roles list and
   both lines of fine print. The closed state of each block is now exactly the
   three things the owner named.
   ⚠ CARD 01 MAY ONLY HIDE WHOLE. That card carries the discounted prices, the
   sentence that justifies them and the fine print that says a related event is
   never free — a number and its justification must be in the same state as each
   other, which is why .xtra sits on .more__grid and never on anything inside
   card 01. Closed, nothing priced is on screen at all; the lede's "costs less"
   is a promise with its proof one click away, which is weaker than the open
   state, never rosier. That is the test everything hidden on this page passes.
   ⚠ NOTHING NEWLY HIDDEN MAY CARRY .rv. .rv is opacity:0 until an
   IntersectionObserver adds .in, and a display:none element never intersects,
   so an .rv element revealed by this toggle would open invisible. The three
   .more__c cards, the planner's roles list and its first fine-print line all
   lost their .rv/.rv-d* when they went behind the toggle — as every other
   .xtra on the page already had none. */
.prow__pad.xopen .xtra,.prow__pad.xopen .xtra-li,
.plans__upgrade.xopen .xtra,.plans__upgrade.xopen .xtra-li,
.more.xopen .xtra,.more.xopen .xtra-li,
.rcp.xopen .xtra,.rcp.xopen .xtra-li{display:revert}
/* THE ONE ELEMENT THAT CANNOT RIDE display:revert. .more__grid is display:grid
   from the author sheet, and `revert` rolls an AUTHOR declaration back to the
   USER-AGENT value — block — so the plain rule above would open the three cards
   as three stacked full-width blocks. It wears .xtra for the closed state and is
   put back to grid explicitly here, later in the file and at equal specificity.
   ⚠ Any future .xtra on an element whose display comes from CSS rather than from
   its tag needs the same companion line. */
.more.xopen .more__grid{display:grid}
/* Same trap, same fix: .rcp__x is display:grid from the author sheet, so the
   plain revert above would open the named files as a stacked block list with
   no row gap. Added with the BY RECIPIENT band, 2026-09-01. */
.rcp.xopen .rcp__x{display:grid}

/* AT A GLANCE — the fact blocks compressed to one line. Every claim on it is a
   restatement of one of those blocks — change them together or the panel will
   contradict itself between its two states.
   ⚠ IT NO LONGER DISAPPEARS ON OPEN (owner, 2026-09-01: "Show more" must "just
   add text below what is already there, not open a whole new vision"). It used
   to be display:none under .xopen while the three fact blocks took over from it
   ABOVE the columns — so one line vanished, three blocks appeared in its place
   and everything below jumped. The blocks now sit BELOW .prow__cols (see
   index.html) and this line stays exactly where the reader left it, standing as
   the summary the blocks expand. Restoring the hide reintroduces the jump. */
.prow__at{
  grid-column:1/-1;margin-bottom:0;
  padding:clamp(20px,2.6vh,30px) 0 0;border-top:1px solid color-mix(in srgb,var(--linen) 12%,transparent);
  font-size:var(--fs-cap);line-height:1.7;
  color:color-mix(in srgb,var(--linen) 62%,transparent);
}
.prow__at b{font-weight:var(--fw-emph);color:var(--accent)}
.prow__at i{font-style:normal;color:color-mix(in srgb,var(--linen) 56%,transparent)}
.prow__at span{padding:0 6px;color:color-mix(in srgb,var(--linen) 56%,transparent)}

.prow__toggle{
  grid-column:1/-1;justify-self:start;
  display:inline-flex;align-items:center;gap:8px;
  background:none;border:0;padding:0 0 4px;cursor:pointer;
  font-family:var(--sans);font-size:var(--fs-label);font-weight:var(--fw-label);letter-spacing:var(--ls-label);
  text-transform:uppercase;color:color-mix(in srgb,var(--linen) 80%,transparent);
  border-bottom:1px solid color-mix(in srgb,var(--linen) 40%,transparent);
  transition:color .3s,border-color .3s;
}
.prow__toggle::after{content:"+";font-size:1.15em;line-height:1;transition:color .3s}
.prow__pad.xopen .prow__toggle::after,
.plans__upgrade.xopen .prow__toggle::after,
.more.xopen .prow__toggle::after,
.rcp.xopen .prow__toggle::after{content:"\2212"}
.prow__toggle:hover{color:var(--accent);border-color:var(--accent)}
.prow__toggle:focus-visible{outline:2px solid var(--accent);outline-offset:4px}
/* The footnote's toggle is not in the panel grid, so it drops the grid-column
   and rides the end of its own sentence at the footnote's smaller size. */
.prow__toggle--note{
  grid-column:auto;margin-left:10px;vertical-align:baseline;
  /* was clamp(10px,.7vw,11px) — .7vw only reaches 11px above a ~1571px
     viewport, so in practice this floored at 10px on every real screen. It is
     a BUTTON, and 10px is under --fs-label (11px), the smallest type the site
     defines. Matched to the .prow__toggle it is a variant of. */
  font-size:var(--fs-label);
  color:color-mix(in srgb,var(--linen) 70%,transparent);
}
/* The "more than one event" toggle sits under the three cards rather than in a
   panel grid, so it drops the grid placement and takes a rule of its own. It
   keeps .prow__toggle's size, case and +/− affordance on purpose: the reader has
   already met this control four times higher up the same section. */
/* ⚠ DEAD SINCE 2026-09-01 — .rcp and both .more blocks lost their own "Show
   more" when they became the bodies of the .dsc questions (see §.dsc). Kept
   because the .xtra markup they governed is still there and still opens; if a
   toggle is ever put back inside one of those blocks, this is its spacing. */
.more__toggle{grid-column:auto;margin-top:clamp(22px,3vh,32px)}

/* the tier a plan's first four parts belong to — Communicate / Coordinate / Operate.
   Retired from /index.html when that vocabulary went (2026-08-31); still
   worn by the burgundy colour rig, so the rule stays. */
.mod-tier{
  display:inline-block;margin-left:10px;font-family:var(--sans);font-size:9.5px;
  letter-spacing:.2em;text-transform:uppercase;font-weight:400;
  border:1px solid color-mix(in srgb,var(--linen) 26%,transparent);border-radius:0;
  padding:3px 11px;color:color-mix(in srgb,var(--linen) 60%,transparent);vertical-align:2px;
}
.prow__col li b{font-weight:var(--fw-emph);color:var(--linen);letter-spacing:.01em}
.mod-inc{
  display:inline-block;margin-left:8px;font-size:var(--fs-label);font-weight:var(--fw-label);letter-spacing:var(--ls-label);text-transform:uppercase;
  border:1px solid color-mix(in srgb,var(--accent) 55%,transparent);border-radius:0;padding:2px 9px;
  color:var(--accent);white-space:nowrap;vertical-align:1px;
}
/* the "Best for ..." qualifier under each plan position — sans body, not a
   shrunken em of the serif lede above it */
.prow__pos span{display:block;margin-top:10px;font-family:var(--sans);
  font-size:var(--fs-body);line-height:1.65;letter-spacing:0}
.prow__pos br{display:none}
/* TWO TRACKS: the long "what we handle" list, and .prow__side beside it — the
   designs column, "You vs. Evorrah" and the three fact blocks, stacked.
   ⚠ THE STACK IS THE POINT (owner, 2026-09-01). These were three equal columns in
   one row, so the designs column — a heading and one or two lines — sat next to a
   list of eight or twelve and left roughly 390px of dead space beneath itself,
   while "You vs. Evorrah" and the fact blocks were pushed onto rows BELOW the long
   list: a screen further down than the column they belong to.
   ⚠ AND THE TRACK COUNT NEVER CHANGES BETWEEN STATES. It was 1fr 1fr 1fr, with a
   `.prow__pad:not(.xopen) .prow__cols{1fr 1fr}` override gated above 900px so
   the closed panel did not leave a dead third track where the hidden "You vs.
   Evorrah" column would go. That meant opening the panel RE-SPLIT the grid —
   two columns of text became three and every line re-wrapped mid-read. The
   track count is now fixed, and the third column simply wraps onto its own row
   underneath when it appears. Do not reintroduce a state-dependent track count
   here; if a fourth column is ever added, give it a row, not a track. */
.prow__cols{grid-column:1/-1;display:grid;grid-template-columns:1fr 1fr;gap:clamp(24px,3vw,60px);align-items:start}
/* align-content:start so a short stack sits at the top of its track instead of
   spreading its gaps to match the long list beside it. */
.prow__side{display:grid;gap:clamp(34px,4.4vh,52px);align-content:start;min-width:0}
.prow__col h4,.prow__col h3{font-size:var(--fs-label);letter-spacing:var(--ls-label);text-transform:uppercase;font-weight:var(--fw-label);color:color-mix(in srgb,var(--linen) 56%,transparent);margin-bottom:20px;display:flex;gap:8px;align-items:center}
.prow__col h4 b,.prow__col h3 b{color:var(--accent);font-weight:var(--fw-emph)}
.prow__col li{font-size:var(--fs-cap);padding:11px 0;border-bottom:1px solid color-mix(in srgb,var(--linen) 9%,transparent);color:color-mix(in srgb,var(--linen) 85%,transparent)}
.prow__col li.inherit{color:color-mix(in srgb,var(--linen) 56%,transparent)}
.vs{display:grid;grid-template-columns:1fr 1fr;gap:0;border:1px solid color-mix(in srgb,var(--linen) 14%,transparent);border-radius:6px;overflow:hidden}
.vs > div{padding:24px 24px}
.vs__you{background:color-mix(in srgb,var(--linen) 6%,transparent)}
.vs h5,.vs h4{font-size:var(--fs-label);letter-spacing:var(--ls-label);text-transform:uppercase;font-weight:var(--fw-label);margin-bottom:12px}
.vs__you h5,.vs__you h4{color:var(--accent)}
.vs__us h5,.vs__us h4{color:color-mix(in srgb,var(--linen) 56%,transparent)}
.vs li{font-size:var(--fs-cap);padding:8px 0;color:color-mix(in srgb,var(--linen) 80%,transparent)}
.prow__foot{grid-column:1/-1;display:flex;gap:22px;align-items:center;margin-top:8px;flex-wrap:wrap}
.prow__see{font-size:var(--fs-label);font-weight:var(--fw-label);letter-spacing:var(--ls-label);text-transform:uppercase;border-bottom:1px solid color-mix(in srgb,var(--linen) 40%,transparent);padding-bottom:4px;color:color-mix(in srgb,var(--linen) 80%,transparent);transition:color .3s,border-color .3s}
.prow__see:hover{color:var(--accent);border-color:var(--accent)}
/* "Most chosen" sits beside the PLAN NAME, not beside the spec (owner, 2026-08-31).
   On the spec it read as a fourth item in "8 services · 8 designs · $1,495" —
   a property of the row rather than a recommendation, and it was the width of
   the bar away from the thing it recommends. It is the same treatment as
   before (accent, label size, uppercase) and only the anchor moved.
   ⚠ It is a pseudo-element on purpose: the name is inside the accordion's
   <button>, and a real element there would be read out as part of the button's
   own label. `speak:never` is not reliable, so keep this decorative.
   Weight 600 (owner, 2026-08-31, "make it bolder"): the only 600 on the page,
   so index.html's Google Fonts request loads that Inter weight — without it
   the browser fakes the bold and the letterspaced caps smear. */
.prow--reco .prow__name::after{
  content:"Most chosen";
  display:inline-block;vertical-align:middle;white-space:nowrap;
  margin-left:clamp(12px,1.4vw,22px);
  transform:translateY(-.12em);
  font-family:var(--sans);font-weight:600;
  font-size:var(--fs-label);letter-spacing:var(--ls-label);text-transform:uppercase;
  color:var(--accent);
}
@media(max-width:560px){
  /* At 34px the name already fills the row; the badge drops under it rather
     than pushing "Whole Event" into a two-line wrap. */
  .prow--reco .prow__name::after{display:block;margin:10px 0 0;transform:none}
}
/* Moved above the three plan rows (owner, 2026-08-31): it is a legend for
   the ladder you are about to read, not a closing line. Ranged left to sit
   under the section head, with a hairline tying it to the rows below. */
.plans__ladder{margin:clamp(30px,4.2vh,52px) 0 clamp(18px,2.4vh,30px);padding-top:clamp(18px,2.4vh,28px);border-top:1px solid color-mix(in srgb,var(--linen) 14%,transparent);text-align:left;font-family:var(--serif);font-weight:300;font-size:var(--fs-lede);color:color-mix(in srgb,var(--linen) 70%,transparent)}
.plans__ladder em{font-style:italic;color:var(--linen)}
/* Below ~1180px four fact blocks stop fitting across: two rows of two. */
@media(max-width:1180px){
  .prow__pad{grid-template-columns:1fr 1fr}
}
@media(max-width:900px){
  .prow__bar{grid-template-columns:auto minmax(0,1fr) auto;grid-template-rows:auto auto;row-gap:10px;column-gap:clamp(14px,3vw,24px)}
  /* ⚠ white-space:normal — the desktop bar keeps the spec on one line because
     it is one cell of a four-column grid. Here it is alone on row 2 inside
     minmax(0,1fr), where nowrap cannot shrink and simply overruns: at 320px
     "We handle 12 services · 12 designs" measures 267px in a 246px track and
     ran under the price. It wraps to two lines instead (owner, 2026-08-31,
     when "services" was added to the spec and made it 72px longer). */
  /* ⚠ THE PRICE SHARES THE SPEC'S ROW, RANGED RIGHT — it does not get a row of
     its own. It used to be `grid-column:1/-1`, which dropped "$495" onto a
     third row as a 350px-wide box starting in the number gutter at x=20:
     ranged left under an indented spec, touching nothing, reading as a stray
     line of the row below rather than the price of the plan above it. A bar
     is a name and a price; keeping them in the same two-line block is what
     makes it one bar and not three stacked fragments. The spec keeps the
     flexible track and the price takes only the width of "$1,495". */
  .prow__num{grid-column:1;grid-row:1}
  .prow__name{grid-column:2/-1;grid-row:1}
  .prow__spec{grid-column:2;grid-row:2;white-space:normal}
  .prow__price{grid-column:3;grid-row:2;justify-self:end;align-self:center;line-height:1}
  /* minmax(0,1fr) — WCAG 1.4.10, same trap as .foot__grid. With a plain 1fr
     these single-column tracks still could not go below their own min-content,
     so at 320px the open plan panels ran 5-20px past .prow__inner's
     overflow:hidden and the ends of real sentences ("Best for weddings,
     multi-day celebrations, …") were CLIPPED, not scrolled to. */
  .prow__pad{grid-template-columns:minmax(0,1fr);row-gap:clamp(22px,3vh,30px)}
  .prow__cols{grid-template-columns:minmax(0,1fr)}
  .vs{grid-template-columns:minmax(0,1fr)}
}

/* ============================================================
   THE PLATFORM (the /platform/ capability band) — the film +
   the eight capability tiles.
   ⚠ NO LONGER ONYX. The band is .g-alab as of 2026-08-31 (owner: no
   black backgrounds on / — style.css §'THE SEVENTEEN FORMER ONYX
   BANDS'). burgundy-test.html still renders this furniture on a
   .g-onyx section, which is why the linen eyebrow below is kept and
   scoped to that ground rather than deleted.
   ============================================================ */
.plat__head{max-width:880px;margin-bottom:clamp(40px,6vh,70px)}
.g-onyx .plat__head .eyebrow{color:var(--linen)}
/* container-type:inline-size so the poster furniture can answer to the FRAME
   rather than to the viewport. The film is 940px wide in a wide hero column
   and 335px on a phone, and a media query cannot tell those apart once the
   film can be dropped into more than one column width. */
.film{position:relative;border-radius:8px;overflow:hidden;background:#000;aspect-ratio:16/9;box-shadow:0 50px 100px -40px rgba(0,0,0,.7);
  container-type:inline-size;container-name:film}
.film video{width:100%;height:100%;object-fit:cover;display:block}
.film__poster{
  /* The horizontal padding is not decoration. .film__sub is one uppercase,
     letter-spaced line — "THE CLIENT PORTAL · ONE EVENT, END TO END" — and in
     the hero column at 375px the frame is 335px wide, which is almost exactly
     that line's length: measured before this, it ran edge to edge with nothing
     either side and read as a label printed off the plate. text-align:center is
     the pair to it, for the width where the line does wrap. */
  position:absolute;inset:0;z-index:2;cursor:pointer;border:none;
  padding:0 clamp(16px,5%,32px);text-align:center;
  background:linear-gradient(160deg, rgba(20,20,20,.55), rgba(76,6,29,.45)), url('/assets/demo/evorrah-portal-demo-poster.jpg') center/cover;
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:20px;
  color:var(--linen);transition:opacity .6s;width:100%;
}
.film__poster.hide{opacity:0;pointer-events:none}
.film__play{
  width:84px;height:84px;border-radius:50%;border:1px solid color-mix(in srgb,var(--linen) 55%,transparent);
  display:grid;place-items:center;transition:background .35s,border-color .35s,transform .35s;
  /* ⚠ flex:none, OR THE CIRCLE IS NOT A CIRCLE. .film__poster is a column flex
     box inside a 16/9 frame, so on a 335px-wide phone the frame is 188px tall
     and the four children want more than that. The default flex-shrink:1 takes
     it out of the tallest child — this one — and the play button rendered as a
     visibly wide ellipse. Height is not negotiable here; the copy below is what
     gives way, in the container query. */
  flex:none;
}
.film__play::before{content:"";border-style:solid;border-width:11px 0 11px 19px;border-color:transparent transparent transparent var(--linen);margin-left:5px}
.film__poster:hover .film__play{background:var(--accent);border-color:var(--accent);transform:scale(1.06)}
@media(prefers-reduced-motion:reduce){
  .film__poster,.film__play{transition:none}
  .film__poster:hover .film__play{transform:none}
}
.film__mark{font-family:var(--serif);font-weight:400;letter-spacing:.3em;font-size:var(--fs-body)}
.film__sub{font-size:var(--fs-label);font-weight:var(--fw-label);letter-spacing:var(--ls-label);text-transform:uppercase;opacity:.75}
.film__len{font-size:var(--fs-label);font-weight:var(--fw-label);letter-spacing:var(--ls-label);text-transform:uppercase;opacity:.55}
/* ── the film in a narrow frame (a phone, or any column under ~460px) ──
   Measured at 375px before this: "THE CLIENT PORTAL · ONE EVENT, END TO END"
   is very nearly the full width of the frame, so it wrapped to two lines and
   the second landed on top of the wordmark printed in the poster art itself.
   It is also the one line here that says nothing new — the poster image
   already reads EVORRAH · THE CLIENT PORTAL — so it is what goes. The mark,
   the play button and the running time all stay: "how long is this?" is the
   question a viewer actually has before pressing play. */
@container film (max-width:460px){
  .film__poster{gap:14px}
  .film__sub{display:none}
  .film__play{width:58px;height:58px}
  .film__play::before{border-width:8px 0 8px 14px;margin-left:4px}
}
.plat__caps{display:grid;grid-template-columns:repeat(4,1fr);gap:1px;background:color-mix(in srgb,var(--linen) 12%,transparent);border:1px solid color-mix(in srgb,var(--linen) 12%,transparent);margin-top:clamp(36px,5vh,60px)}
.pcap{background:var(--onyx);padding:clamp(22px,2.6vw,38px);display:flex;flex-direction:column;gap:12px}
.pcap b{font-size:var(--fs-label);letter-spacing:var(--ls-label);color:var(--accent);font-weight:var(--fw-label)}
.pcap h3{font-family:var(--serif);font-weight:300;font-size:var(--fs-d5);line-height:1.2}
.pcap p{font-size:var(--fs-cap);color:color-mix(in srgb,var(--linen) 62%,transparent);line-height:1.65}
.pcap a{margin-top:auto;font-size:var(--fs-label);font-weight:var(--fw-label);letter-spacing:var(--ls-label);text-transform:uppercase;color:color-mix(in srgb,var(--linen) 75%,transparent);border-bottom:1px solid color-mix(in srgb,var(--linen) 25%,transparent);align-self:flex-start;padding-bottom:4px;transition:color .3s,border-color .3s}
.pcap a:hover{color:var(--accent);border-color:var(--accent)}
@media(max-width:1000px){.plat__caps{grid-template-columns:repeat(2,1fr)}}
@media(max-width:560px){.plat__caps{grid-template-columns:1fr}}


/* ============================================================
   FAQ
   ============================================================ */
.faq{display:grid;grid-template-columns:.8fr 1.4fr;gap:clamp(36px,5vw,110px)}
.faq__left{position:sticky;top:110px;align-self:start}
.faq__left .eyebrow{margin-bottom:26px}
.fq{border-top:1px solid color-mix(in srgb,var(--onyx) 14%,transparent);background:transparent;transition:background .4s}
.fq:last-child{border-bottom:1px solid color-mix(in srgb,var(--onyx) 14%,transparent)}
.fq__q{width:100%;display:flex;justify-content:space-between;align-items:center;gap:20px;text-align:left;padding:24px 0;font-family:var(--serif);font-weight:300;font-size:var(--fs-lede)}
/* THE PLUS-IN-A-CIRCLE IS SHARED WITH THE THREE QUESTIONS (owner, 2026-09-01
   — see §.dsc, which used to draw a 9px chevron here instead). It is defined
   once, under both names, rather than twinned: the FAQ and the .dsc stack are
   the same gesture on the same page, and a reader who learns the control in
   one stack must not have to learn it again in the other.
   ⚠ THE RING IS NOT THE INDICATOR — the mark inside it is. The border is onyx
   25%, which measures ~1.7:1 on either ground; that is deliberate, and it is
   why the two bars take currentColor (full --onyx from the button, 14.6:1 on
   alabaster) rather than a muted value. Muting the bars to match the ring
   would leave the control with no legible state at all.
   ⚠ OPEN IS --bordeaux, NOT --accent. The disc reads 12:1 against alabaster
   and carries its bars at 13.9:1, so the open row is the loudest mark in
   either stack — which is the whole job: telling the reader at a glance which
   row they left open. */
.fq__x,.dsc__x{flex:none;width:30px;height:30px;border-radius:50%;border:1px solid color-mix(in srgb,var(--onyx) 25%,transparent);display:grid;place-items:center;position:relative;transition:background .35s,border-color .35s,transform .45s}
.fq__x::before,.fq__x::after,.dsc__x::before,.dsc__x::after{content:"";position:absolute;background:currentColor;width:10px;height:1px}
.fq__x::after,.dsc__x::after{transform:rotate(90deg);transition:transform .35s}
.fq.open .fq__x,.dsc__row.dopen .dsc__x{background:var(--bordeaux);border-color:var(--bordeaux);color:var(--linen);transform:rotate(45deg)}
.fq__a{display:grid;grid-template-rows:0fr;transition:grid-template-rows .5s cubic-bezier(.4,0,.2,1)}
.fq.open .fq__a{grid-template-rows:1fr}
.fq__a>div{overflow:hidden}
.fq__a p{padding:0 44px 26px 0;max-width:620px;color:color-mix(in srgb,var(--onyx) 74%,var(--linen));font-size:var(--fs-body)}
@media(max-width:900px){.faq{grid-template-columns:1fr}.faq__left{position:static}}

/* ============================================================
   BRAND STATEMENT + FINAL CTA
   ============================================================ */
.statement{min-height:82svh;display:flex;align-items:center}
.statement h2{font-family:var(--serif);font-weight:300;font-size:var(--fs-mega);line-height:1.08;max-width:1150px}
.statement h2 em{font-style:italic}
.statement h2 .dim{color:color-mix(in srgb,var(--linen) 56%,var(--bordeaux))}
.cta-final{text-align:center;padding-top:clamp(90px,14vh,180px);padding-bottom:clamp(90px,14vh,180px)}
.cta-final .eyebrow{justify-content:center;margin-bottom:30px}
.cta-final h2{margin-bottom:22px}
.cta-final p{font-family:var(--serif);font-weight:300;font-size:var(--fs-lede);color:color-mix(in srgb,var(--linen) 65%,transparent);margin-bottom:44px}

/* ------------------------------------------------------------
   THE CLOSE — the home page only, and NOT a variant of .cta-final.
   One bordeaux band carrying the statement, the sign-off and the button,
   replacing the old .statement + .g-onyx .cta-final pair (index.html only;
   /contact, /collection and /events still use .cta-final unchanged).

   Two things here are load-bearing:

   1. .close__sign has its OWN clamp instead of taking --fs-stat or --fs-d2.
      It has to stay below --fs-mega at every width or it stops reading as a
      second tier and starts competing with the statement. --fs-stat
      (clamp 40→72) overtakes --fs-mega (clamp 38→104) below roughly 870px,
      which is exactly where two big serif lines in one band turn to mush.

   2. .close__rule is structural. The statement is left-aligned (see the
      centred-statement note above — the home statement is deliberately not
      centred) while the tail is centred, as .cta-final was. Without the
      hairline that axis change reads as a mis-set band rather than as a
      deliberate turn. Do not delete it while the two alignments differ.
   ------------------------------------------------------------ */
.close{padding-top:clamp(74px,11vh,150px);padding-bottom:clamp(74px,11vh,150px)}
.close__stat{font-family:var(--serif);font-weight:300;font-size:var(--fs-mega);
  line-height:1.08;max-width:1150px;margin-bottom:clamp(38px,6vh,74px)}
.close__stat em{font-style:italic}
.close__stat .dim{color:color-mix(in srgb,var(--linen) 56%,var(--bordeaux))}
.close__rule{border:0;border-top:1px solid color-mix(in srgb,var(--linen) 18%,transparent);
  margin:0 0 clamp(38px,6vh,68px)}
.close__tail{text-align:center}
.close__sign{font-family:var(--serif);font-weight:300;font-size:clamp(28px,3.4vw,58px);
  line-height:1.08;margin-bottom:20px}
.close__sign em{font-style:italic}
.close__lede{font-family:var(--serif);font-weight:300;font-size:var(--fs-lede);
  color:color-mix(in srgb,var(--linen) 68%,transparent);margin-bottom:40px}

/* ============================================================
   Shared page-hero (interior pages)
   ============================================================ */
.phero{padding-top:clamp(150px,22vh,230px);padding-bottom:clamp(50px,8vh,100px)}
.phero .eyebrow{margin-bottom:28px}
.phero h1{max-width:1100px}
.phero__sub{margin-top:30px;max-width:560px;color:color-mix(in srgb,var(--onyx) 72%,var(--linen))}

/* prose blocks (about page) */
.prose{max-width:680px}
.prose p{font-size:var(--fs-read);line-height:1.85;margin-bottom:26px;color:color-mix(in srgb,var(--onyx) 82%,var(--linen))}
.prose p.big{font-family:var(--serif);font-weight:300;font-size:var(--fs-d5);line-height:1.5;color:var(--onyx)}
.g-bordeaux .prose p,.g-onyx .prose p{color:color-mix(in srgb,var(--linen) 82%,transparent)}
.g-bordeaux .prose p.big,.g-onyx .prose p.big{color:var(--linen)}

/* ============================================================
   ITALIC EMPHASIS CARRIES THE PALETTE
   An <em> is not only a slant — it takes the signature accent of
   the site. That accent is --mahogany everywhere it can be read:
   on linen, on alabaster and on onyx. Only on the bordeaux ground
   does it step aside for linen, because a red on bordeaux would
   disappear. Bordeaux is a GROUND here, not an emphasis colour —
   it used to be the light-ground italic and read as a second,
   muddier red beside the mahogany in the nav rule and the eyebrows.
   Set as a token so that changing --mahogany moves every italic
   with it, and nothing has to be re-found by hand.
   Contrast: the alabaster figure recorded here was 4.63:1, which was
   never measured — #CC242F was 4.26:1 there and FAILED the 4.5:1 floor it
   claimed to clear. The accent is now two stops (see :root) and the number
   holds on every ground: linen 5.24, alabaster 4.59, onyx 4.53.
   em.mh keeps its own separate job: an UPRIGHT mahogany accent.
   ============================================================ */
/* --em was a second per-ground colour system running beside --accent and
   resolving to the same value on every ground. Two systems that must agree
   are one drift waiting to happen, so italics now read --accent directly and
   the ground rebinding lives in exactly one place (.g-onyx / .g-bordeaux,
   above). --em is kept as an alias only because three inline styles in the
   page markup still name it. */
:root{--em:var(--accent)}
em{color:var(--accent)}
em.mh{color:var(--accent)}
.proc__coda em,.plans__ladder em{color:var(--accent)}

/* ============================================================
   MORE THAN ONE EVENT — companion pricing, linked events, the reused list
   Lives at the foot of the onyx plans section, after the three rows,
   because it is an answer to "which plan", not a fourth plan.
   ============================================================ */
.prow__add{font-size:var(--fs-label);font-weight:var(--fw-label);letter-spacing:var(--ls-label);text-transform:uppercase;color:color-mix(in srgb,var(--linen) 56%,transparent)}
.prow__add b{font-weight:var(--fw-emph);color:var(--accent)}

.more{margin-top:clamp(56px,8vh,96px);border-top:1px solid color-mix(in srgb,var(--linen) 14%,transparent);padding-top:clamp(44px,6vh,72px)}
.more__head{max-width:900px;margin-bottom:clamp(34px,5vh,56px)}
.more__head .eyebrow{margin-bottom:20px}
.more__head h3{margin-bottom:20px}
.more__lede{font-family:var(--serif);font-weight:300;font-size:var(--fs-lede);line-height:1.55;max-width:620px;color:color-mix(in srgb,var(--linen) 72%,transparent)}
.more__grid{display:grid;grid-template-columns:repeat(3,1fr);gap:clamp(24px,3vw,48px)}
.more__c{position:relative;padding-top:26px;border-top:1px solid color-mix(in srgb,var(--linen) 12%,transparent)}
.more__n{position:absolute;top:26px;left:0;font-size:var(--fs-label);font-weight:var(--fw-label);letter-spacing:var(--ls-label);color:color-mix(in srgb,var(--linen) 56%,transparent)}
.more__c h4{font-family:var(--serif);font-weight:300;font-size:var(--fs-d5);line-height:1.2;margin:0 0 16px 42px}
.more__c p{font-size:var(--fs-body);line-height:1.72;color:color-mix(in srgb,var(--linen) 76%,transparent);margin:0 0 14px 42px}
.more__c p strong{font-weight:var(--fw-emph);color:var(--linen)}
.more__price{margin:20px 0 0 42px;border-top:1px solid color-mix(in srgb,var(--linen) 12%,transparent)}
.more__price li{display:grid;grid-template-columns:minmax(0,1fr) auto auto;gap:10px;align-items:baseline;
  padding:11px 0;border-bottom:1px solid color-mix(in srgb,var(--linen) 9%,transparent);font-size:var(--fs-cap)}
.more__price span{color:color-mix(in srgb,var(--linen) 82%,transparent)}
.more__price b{font-weight:var(--fw-emph);color:var(--accent);letter-spacing:.06em}
.more__price i{font-style:normal;font-family:var(--serif);font-size:var(--fs-cap);color:color-mix(in srgb,var(--linen) 56%,transparent);white-space:nowrap}
.more__list{margin:0 0 0 42px}
.more__list li{font-size:var(--fs-cap);line-height:1.6;padding:8px 0;border-bottom:1px solid color-mix(in srgb,var(--linen) 9%,transparent);color:color-mix(in srgb,var(--linen) 78%,transparent)}
.more__fine{font-size:var(--fs-label) !important;line-height:1.6;color:color-mix(in srgb,var(--linen) 56%,transparent) !important;margin-top:16px !important}
@media(max-width:1100px){.more__grid{grid-template-columns:1fr;gap:34px}}

/* ---------- HEAD BESIDE THE CONTENT — worn by BOTH blocks ----------
   A reassurance, not a fourth promise: the heading rides beside the content
   instead of above it, which halves the block's height without losing a word.
   The eyebrow + heading column is deliberately narrow — the two-line heading
   wants to break where the <br> puts it.
   ⚠ These four rules are keyed on .more__note, NOT on .more--note. The planner
   note owned the layout until 2026-08-31, when the owner asked for "more than
   one event" to become "the same size more or less as Working with someone";
   the answer was to give that block the same wrapper rather than to cut another
   six words out of it. Anything scoped to one block only belongs below. */
.more--note{margin-top:clamp(36px,5vh,60px);padding-top:clamp(30px,4vh,44px)}
.more__note{display:grid;grid-template-columns:minmax(0,.34fr) minmax(0,1fr);gap:clamp(24px,3.7vw,64px);align-items:start}
.more__note .more__head{margin-bottom:0;max-width:none}
.more__note .more__head h3{margin-bottom:0}
.more__note .more__lede{max-width:640px;margin-bottom:clamp(20px,2.6vh,28px)}
/* ONE stacking point for both blocks. They stack where the harder of the two
   stops working: the events grid, whose three cards starve long before the
   note's single body column would. A pair that stacks at two different widths
   is not a pair between them. */
@media(max-width:1240px){.more__note{grid-template-columns:1fr;gap:clamp(18px,2.4vh,26px)}}
@media(max-width:1000px){.more--note .more__list--wide{columns:1}}

/* ---------- "MORE THAN ONE EVENT" AT NOTE SCALE ----------
   Same wrapper AND the same column as the planner note, so what is left here
   is only what having three cards instead of one body column costs:
   · a SMALLER number gutter and headline. 42px of indent and --fs-d5 were
     drawn for a full-width three-column grid; in the narrower column they read
     as a headline with a paragraph tucked under its arm. 34px and --fs-lede is
     the same relationship at the smaller size.
   ⚙ The head column used to be .3fr here against the note's .4fr, and the two
   blocks stacked at different widths (1240px / 1000px) — which meant the pair
   the copy calls matched started its lede ~125px further left than the note
   below it. Both now share one column (.34fr) and one stacking point (1240px).
   .34fr is the meeting point, and it was measured rather than guessed (a
   playwright pass over the OPEN grid at 1600/1440/1366/1280/1241): meeting at
   the note's .4fr puts the three cards at 274px @1440 and wraps "Fully
   Handled" from 1366 down, both labels from 1280; .34fr holds them at 288px
   and wraps nothing until 1280 — where the old .3fr wrapped that same label
   too. The row's own column gap went 14px → 10px to pay for the ~11px the
   cards lost against .3fr.
   ⚠ If this column is ever widened again, check .more__price first: "Fully
   Handled / −50% / $2,995 → $1,495", whose price is white-space:nowrap, is the
   tightest line on the page. The label is minmax(0,1fr), so what a squeeze
   costs is a two-line label, never an overflow. */
.more--events .more__grid{gap:clamp(20px,2.2vw,34px)}
.more--events .more__c h4{font-size:var(--fs-lede);margin-left:34px}
.more--events .more__c p{margin-left:34px}
.more--events .more__price{margin-left:34px}
.more--events .more__list{margin-left:34px}
/* the planner note stands full width, not as a card in the grid — its list
   runs in two columns so four short lines do not become a tall thin stack */
.more__list--wide{margin-left:0;columns:2;column-gap:clamp(24px,3vw,48px)}
.more__list--wide li{break-inside:avoid}
/* the three role rows behind the note's "Show more" lead with the portal's own
   label. Not <strong>: this is a name at the head of a line, not emphasis, and
   the site's --sans has no weight above 400 in play here — the label separates
   by COLOUR (full linen against the row's 78%), the way .more__c p strong does
   one block above. */
.more__list li b{font-weight:var(--fw-emph);color:var(--linen)}
@media(max-width:800px){.more__list--wide{columns:1}}

/* "WHAT CAN HANG OFF IT" — the full-width strip that closes the #more-events
   block, under the three cards. It reuses .more__list--wide wholesale, so
   every colour it needs already has its alabaster override at the
   "#plans.g-alab .more__list" rules below; the only thing this block owns is
   the rule that separates it from the cards.
   ⚠ .more--events .more__list{margin-left:34px} (two rules up) is 0,2,0 and
   outranks .more__list--wide{margin-left:0} at 0,1,0, so the wide list inside
   this block needs the indent cancelled at equal weight or it hangs under the
   card gutter it is not in. */
.more__also{margin-top:clamp(30px,4vh,50px);padding-top:26px;
  border-top:1px solid color-mix(in srgb,var(--linen) 12%,transparent)}
.more__also .eyebrow{margin-bottom:20px}
.more--events .more__list--wide{margin-left:0}

/* ------------------------------------------------------------
   A CENTRED STATEMENT MUST CENTRE ITS HEADLINE BOX, NOT ONLY ITS TEXT.
   .statement h2 is capped at max-width:1150px inside a 1480px wrap. With
   text-align:center alone the TEXT centres inside a box that is still flush
   left, so the headline sat ~55px left of the buttons under it — visibly
   off-axis on /what-we-solve, /about and /manifesto. margin-inline:auto
   centres the box itself. The home page statement stays left-aligned and
   is deliberately NOT given this class.
   ------------------------------------------------------------ */
.statement--center{text-align:center;padding:clamp(64px,9vh,110px) 0}
.statement--center .wrap{width:100%}
.statement--center h2{margin-inline:auto}
.statement__actions{margin-top:clamp(32px,5vh,48px);display:flex;gap:18px;justify-content:center;flex-wrap:wrap}

/* ============================================================
   PLATFORM PAGES (/platform/*)
   The four capabilities the home page's platform section opens onto.
   These used to send visitors out to /v13; the content lives here now,
   in this stylesheet's own language. One shared vocabulary, so a new
   capability page is markup only.
   ============================================================ */
.pf-chips{display:flex;flex-wrap:wrap;gap:10px;margin-top:clamp(26px,4vh,40px)}
.pf-chip{
  font-size:var(--fs-label);font-weight:var(--fw-label);letter-spacing:var(--ls-label);text-transform:uppercase;
  border:1px solid color-mix(in srgb,var(--onyx) 22%,transparent);
  border-radius:0;padding:7px 16px;
  color:color-mix(in srgb,var(--onyx) 66%,var(--linen));white-space:nowrap;
  /* WCAG 1.4.10 · the longest chip ("Included with Whole Event and above") is
     313px of nowrap text, wider than a 320px screen's content box, and it made
     eleven platform pages scroll sideways. It keeps its single line wherever
     there is room; the rule at the end of this file lifts the nowrap when
     there is not. */
  max-width:100%;
}
.pf-chip--on{border-color:var(--bordeaux);color:var(--bordeaux)}

/* numbered section head, shared by every block on these pages */
.pf-h{display:flex;align-items:baseline;gap:clamp(16px,3vw,40px);margin-bottom:clamp(30px,5vh,54px);flex-wrap:wrap}
.pf-h__n{font-size:var(--fs-label);font-weight:var(--fw-label);letter-spacing:var(--ls-label);color:var(--accent);flex:0 0 auto;padding-top:8px}
.g-onyx .pf-h__n,.g-bordeaux .pf-h__n{color:color-mix(in srgb,var(--linen) 56%,transparent)}
.pf-h__t{max-width:900px}
.pf-h__t p{margin-top:16px;font-size:var(--fs-body);line-height:1.7;max-width:640px;color:color-mix(in srgb,var(--onyx) 68%,var(--linen))}
.g-onyx .pf-h__t p,.g-bordeaux .pf-h__t p{color:color-mix(in srgb,var(--linen) 70%,transparent)}

/* the numbered steps grid (01–04) */
.pf-steps{display:grid;grid-template-columns:repeat(4,1fr);gap:clamp(20px,2.6vw,44px)}
.pf-steps--3{grid-template-columns:repeat(3,1fr)}
.pf-step{border-top:1px solid color-mix(in srgb,var(--onyx) 16%,transparent);padding-top:22px}
.g-onyx .pf-step,.g-bordeaux .pf-step{border-top-color:color-mix(in srgb,var(--linen) 16%,transparent)}
.pf-step b{display:block;font-size:var(--fs-label);letter-spacing:var(--ls-label);font-weight:var(--fw-label);color:var(--accent);margin-bottom:14px}
.g-onyx .pf-step b,.g-bordeaux .pf-step b{color:color-mix(in srgb,var(--linen) 56%,transparent)}
.pf-step h3{font-family:var(--serif);font-weight:300;font-size:var(--fs-d5);line-height:1.2;margin-bottom:12px}
.pf-step p{font-size:var(--fs-body);line-height:1.72;color:color-mix(in srgb,var(--onyx) 70%,var(--linen))}
.g-onyx .pf-step p,.g-bordeaux .pf-step p{color:color-mix(in srgb,var(--linen) 72%,transparent)}
@media(max-width:1000px){.pf-steps,.pf-steps--3{grid-template-columns:repeat(2,1fr)}}
@media(max-width:620px){.pf-steps,.pf-steps--3{grid-template-columns:1fr}}

/* without / with */
.pf-wo{display:grid;grid-template-columns:1fr 1fr;gap:clamp(20px,3vw,44px)}
.pf-wo__c{border-radius:6px;padding:clamp(26px,3.4vw,46px);display:flex;flex-direction:column;gap:16px}
.pf-wo__c--without{background:var(--alabaster);color:var(--onyx)}
.pf-wo__c--with{background:var(--bordeaux);color:var(--linen)}
.pf-wo__c h3{font-size:var(--fs-label);letter-spacing:var(--ls-label);text-transform:uppercase;font-weight:var(--fw-label);opacity:.62}
.pf-wo__c p{font-family:var(--serif);font-weight:300;font-size:var(--fs-lede);line-height:1.55}
@media(max-width:800px){.pf-wo{grid-template-columns:1fr}}

/* delivered-piece tiles */
.pf-tiles{display:grid;grid-template-columns:repeat(6,1fr);gap:clamp(12px,1.6vw,22px)}
.pf-tile figcaption{margin-top:12px;font-size:var(--fs-label);font-weight:var(--fw-label);letter-spacing:var(--ls-label);text-transform:uppercase;color:color-mix(in srgb,var(--onyx) 66%,var(--linen))}
.g-onyx .pf-tile figcaption,.g-bordeaux .pf-tile figcaption{color:color-mix(in srgb,var(--linen) 56%,transparent)}
.pf-tile img{width:100%;height:auto;border-radius:3px;background:var(--alabaster)}
.pf-cap{margin-top:clamp(18px,2.6vh,28px);font-size:var(--fs-label);letter-spacing:.04em;color:color-mix(in srgb,var(--onyx) 66%,var(--linen))}
.g-onyx .pf-cap,.g-bordeaux .pf-cap{color:color-mix(in srgb,var(--linen) 56%,transparent)}
@media(max-width:1000px){.pf-tiles{grid-template-columns:repeat(3,1fr)}}
@media(max-width:560px){.pf-tiles{grid-template-columns:repeat(2,1fr)}}

/* a prose pair — two columns of running text under a section head */
.pf-say{display:grid;grid-template-columns:1fr 1fr;gap:clamp(24px,4vw,80px)}
.pf-say p{font-size:var(--fs-body);line-height:1.78;margin-bottom:16px;color:color-mix(in srgb,var(--onyx) 74%,var(--linen))}
.g-onyx .pf-say p,.g-bordeaux .pf-say p{color:color-mix(in srgb,var(--linen) 76%,transparent)}
.pf-say p.big{font-family:var(--serif);font-weight:300;font-size:var(--fs-d5);line-height:1.42;color:inherit}
.pf-say strong{font-weight:var(--fw-emph);color:inherit}
@media(max-width:800px){.pf-say{grid-template-columns:1fr}}

/* rules list — "what it will not do" */
.pf-rules{display:grid;grid-template-columns:repeat(2,1fr);gap:0 clamp(24px,4vw,70px)}
.pf-rules li{
  font-family:var(--serif);font-weight:300;font-size:var(--fs-lede);line-height:1.35;
  padding:clamp(13px,1.8vh,20px) 0;border-bottom:1px solid color-mix(in srgb,var(--linen) 13%,transparent);
}
@media(max-width:800px){.pf-rules{grid-template-columns:1fr}}

/* the other three capabilities, at the foot of each page */
.pf-next{display:grid;grid-template-columns:repeat(3,1fr);gap:clamp(20px,3vw,46px)}
.pf-next a{display:block;border-top:1px solid color-mix(in srgb,var(--onyx) 18%,transparent);padding-top:22px;transition:border-color .4s}
.pf-next a:hover{border-color:var(--bordeaux)}
.pf-next b{display:block;font-size:var(--fs-label);letter-spacing:var(--ls-label);font-weight:var(--fw-label);color:var(--accent);margin-bottom:12px}
.pf-next h3{font-family:var(--serif);font-weight:300;font-size:var(--fs-d5);line-height:1.18;margin-bottom:10px}
.pf-next p{font-size:var(--fs-body);line-height:1.7;color:color-mix(in srgb,var(--onyx) 66%,var(--linen));margin-bottom:16px}
.pf-next span{font-size:var(--fs-label);font-weight:var(--fw-label);letter-spacing:var(--ls-label);text-transform:uppercase;border-bottom:1px solid currentColor;padding-bottom:3px;transition:color .3s}
.pf-next a:hover span{color:var(--bordeaux)}
@media(max-width:900px){.pf-next{grid-template-columns:1fr;gap:30px}}

/* the home page's four capability cards now open onto /platform/* */
.pcap a .arr{display:inline-block;transition:transform .35s}
.pcap a:hover .arr{transform:translateX(4px)}

/* ============================================================
   TYPE · SMALL SCREENS
   The only place the scale is re-tuned. Three things happen here:
   1. The h1 gets a hard cap so the three authored .rv-line spans
      stay three lines instead of ragging into five. The longest
      authored line ("Your event shouldn't") measures 7.81x its own
      font-size in Crimson Pro 300, so it fits while
      font-size <= (100vw - 2*gutter) / 7.81. Gutter is 20px below
      400px wide, so 10.6vw clears 375px and 320px both.
   2. Label tracking comes in. .2em is tuned for a wide column; on a
      375px screen it forces uppercase labels to wrap.
   3. Serif ledes stop scaling off vw and sit at their floor, so a
      lede never lands smaller than the body copy under it.
   ============================================================ */
@media(max-width:600px){
  :root{
    --ls-label: .16em;
    --ls-label-lg: .22em;
  }
  .hero h1{font-size:min(10.6vw,40px)}
  .hero__body{font-size:var(--fs-body);line-height:1.7}
  .statement h2,.flip__t{letter-spacing:-.01em}
}

/* ============================================================
   THE WORK ITSELF — /events/<slug>/
   Every commission page ends by showing what was actually made:
   each piece rendered live from its own file under /portfolio/,
   not a photograph of one. The tile IS the artwork.

   Lives here rather than in a page-local <style> block because 53
   pages use it, and 53 copies of a rule is 53 chances to drift.
   ============================================================ */
.work{margin-top:clamp(46px,7vh,80px)}
.work__grid{
  display:grid;grid-template-columns:repeat(auto-fill,minmax(232px,1fr));
  gap:clamp(14px,1.6vw,24px);margin-top:clamp(26px,4vh,44px);
}
.work__grid--doc{grid-template-columns:repeat(auto-fill,minmax(340px,1fr))}
/* THE PLATFORM GRID, on /events/<slug>/#the-platform. It carries two
   shapes at once — a phone or an arrival pass, which are tall, and a laptop
   screen, which is not — so it is a fixed four-column track a screen spans two
   of, rather than the auto-fill the stationery uses. `dense` matters: a Suite
   event has four tiles spanning five columns, and without it the leftover
   column stays empty next to a tall phone instead of pulling the next tile up.
   ⚠ A WIDE TILE IS NOT A `wtile--doc`. A doc is top-pinned and deliberately
   runs under the tile's bottom edge, which is how a twelve-page client deck
   should preview and exactly wrong for a screen that has to be seen whole.
   ⚠ NOT `grid-auto-flow:dense`, THOUGH IT PACKS BETTER. Dense backfills an
   earlier gap with a later tile that fits, and every tile here is NUMBERED —
   on a Fully Handled event it lifted 09 into the hole beside 05 and the strip
   read 04, 05, 09, 06, 07, 08. The ladder IS the plan, so the four empty cells
   a Fully Handled layout leaves are the cheaper price. */
.work__grid--plat{grid-template-columns:repeat(4,1fr)}
.work__grid--plat .wtile--wide{grid-column:span 2}
.work__grid--plat .wtile--wide .wtile__f{aspect-ratio:16/10}
/* 4/5 is not a taste call: a wide tile is two columns plus a gap at 16/10, and
   4/5 is the narrow aspect that lands a one-column tile at the SAME height. Any
   other value leaves a phone hanging below its row's screens, or a screen
   floating above its row's phones. The device artwork is sized to suit it —
   every phone file lands between 560 and 650 logical pixels tall so it fills
   this tile rather than shrinking to fit an over-tall one. */
.work__grid--plat .wtile:not(.wtile--wide) .wtile__f{aspect-ratio:4/5}
@media(max-width:900px){
  .work__grid--plat{grid-template-columns:repeat(2,1fr)}
}
@media(max-width:520px){
  .work__grid{grid-template-columns:repeat(2,1fr);gap:10px}
  .work__grid--doc{grid-template-columns:1fr}
  /* Two columns is already narrow; a wide tile spanning both is the only way a
     screenshot of a screen stays legible on a phone. */
  .work__grid--plat{grid-template-columns:repeat(2,1fr);gap:10px}
  .work__grid--plat .wtile--wide{grid-column:span 2}
}
.wtile{display:block;position:relative;border-radius:4px;overflow:hidden;
  background:var(--alabaster);
  border:1px solid color-mix(in srgb,var(--onyx) 11%,transparent);
  transition:transform .5s cubic-bezier(.22,.6,.2,1),box-shadow .5s,border-color .5s}
.wtile:hover{transform:translateY(-5px);border-color:color-mix(in srgb,var(--onyx) 26%,transparent);
  box-shadow:0 16px 40px rgba(20,10,10,.13)}
/* display:block is load-bearing, not tidiness: the tile is built from <span>s
   so the whole card can be one <a>, and an inline box ignores aspect-ratio —
   the frame collapsed to 0x0 and every tile rendered as a 48px caption strip. */
.wtile__f{display:block;position:relative;width:100%;aspect-ratio:3/4;overflow:hidden;background:var(--alabaster)}
.work__grid--doc .wtile__f{aspect-ratio:16/10}
/* The piece files are self-fitting: each sizes its sheet to BOTH axes of its
   viewport, so an iframe of any shape renders the whole artwork with no
   transform. Sizing the frame to the tile is the entire trick. */
/* The frame is NOT sized to the tile. It is given a large fixed logical
   viewport so the artwork inside lays out at its true proportion, and work.js
   then transforms it so the artwork alone lands in the tile. Sizing the frame
   to the tile is what cropped a 4x9in menu to its top third. */
.wtile__f iframe{position:absolute;top:0;left:0;border:0;transform-origin:0 0;
  opacity:0;transition:opacity .5s ease;pointer-events:none;background:transparent}
.wtile__f iframe.on{opacity:1}
/* Until the frame mounts, the tile shows the piece's own name on its own
   ground — never a blank rectangle. */
.wtile__ph{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
  font-size:var(--fs-label);font-weight:var(--fw-label);letter-spacing:var(--ls-label);text-transform:uppercase;
  color:color-mix(in srgb,var(--onyx) 66%,var(--alabaster))}
.wtile__c{display:flex;align-items:baseline;justify-content:space-between;gap:10px;
  padding:13px 15px 14px;border-top:1px solid color-mix(in srgb,var(--onyx) 10%,transparent)}
.wtile__c b{font-weight:var(--fw-emph);font-size:var(--fs-cap);line-height:1.3;
  color:color-mix(in srgb,var(--onyx) 88%,var(--linen))}
.wtile__c i{font-style:normal;font-size:var(--fs-label);font-weight:var(--fw-label);letter-spacing:var(--ls-label);
  text-transform:uppercase;color:color-mix(in srgb,var(--onyx) 66%,var(--linen));
  flex:none;transition:color .4s}
.wtile:hover .wtile__c i{color:var(--accent)}
/* A pure-CSS hit layer over the iframe: the frame has pointer-events:none so
   the whole tile stays one link, and a click never lands inside the artwork. */
.wtile__hit{position:absolute;inset:0;z-index:2}
/* The link's label exists for assistive tech only — the caption below the
   tile is the visible one, and is aria-hidden so the name is not read twice. */
.vh{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;
  clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0}
.wtile:focus-within{outline:2px solid var(--accent);outline-offset:3px;
  border-color:color-mix(in srgb,var(--onyx) 26%,transparent)}
.wtile:focus-within .wtile__c i{color:var(--accent)}
.wtile__hit:focus{outline:none}

.work__head{display:flex;align-items:baseline;justify-content:space-between;gap:20px;flex-wrap:wrap}
.work__n{font-size:var(--fs-label);font-weight:var(--fw-label);letter-spacing:var(--ls-label);text-transform:uppercase;
  color:color-mix(in srgb,var(--onyx) 66%,var(--linen))}
.g-onyx .work__n,.g-bordeaux .work__n{color:color-mix(in srgb,var(--linen) 56%,transparent)}
.g-alab .wtile{background:var(--linen)}
.g-alab .wtile__f,.g-alab .wtile__ph{background:var(--linen)}

/* The made-list: every design named in the commission, whether or not a file
   for it sits in the grid above. */
.made{display:flex;flex-wrap:wrap;gap:9px;margin-top:clamp(22px,3vh,34px)}
.made span{font-size:var(--fs-label);font-weight:var(--fw-label);letter-spacing:var(--ls-label);text-transform:uppercase;
  border:1px solid color-mix(in srgb,var(--onyx) 18%,transparent);border-radius:0;padding:9px 17px;
  color:color-mix(in srgb,var(--onyx) 72%,var(--linen))}
.g-onyx .made span,.g-bordeaux .made span{border-color:color-mix(in srgb,var(--linen) 22%,transparent);
  color:color-mix(in srgb,var(--linen) 80%,transparent)}


/* ============================================================
   THE SHELL'S OWN FURNITURE
   Nav login · footer capture · consent banner · forms
   ------------------------------------------------------------
   ⚠ THESE LIVE HERE, NOT IN A PAGE'S <style> BLOCK. The shell is stamped onto
   75 pages by scripts/v15-shell.mjs; styling it per-page would put 75 copies of
   the same rules on disk and guarantee the drift the stamper exists to end.
   ============================================================ */

/* A returning client is not a prospect. The login sits in the primary nav but
   never competes with the CTA beside it: no underline sweep, lower contrast,
   and it drops off the desktop bar with the rest of .nav__links on mobile —
   where the mobile nav carries it as a full row instead. */
.nav__login{
  /* The split point of the bar: this auto margin is what opens the gap between
     the site links and the account pair. */
  margin-left:auto;
  font-size:var(--fs-label);font-weight:var(--fw-label);letter-spacing:var(--ls-label);text-transform:uppercase;
  /* Same job, other direction: dimmed TOWARD the plate instead of toward
     linen, so it still reads quieter than the CTA beside it while clearing AA
     at 11px. ⚠ THE SECOND STOP MUST BE WHATEVER .nav's PLATE IS — the point
     of the mix is that this text recedes into the bar, so it followed the bar
     bordeaux → cherry on 2026-08-31. Re-measured at 72% over the real 94%
     cherry blend (#73121d): 5.69:1 against the bar's own 10.22:1 — quieter
     than the CTA, still AA at 11px. It was 6.68 against 12.26 on bordeaux;
     both ends came down and the relationship is unchanged. 62% is the floor
     (4.45:1, an AA fail) — do not dim this further. */
  color:color-mix(in srgb,var(--linen) 72%,var(--cherry));padding:6px 0;
  transition:color .3s;
}
.nav__login:hover{color:var(--linen)}
.nav__links a.nav__login::after{display:none}
/* The lab builds (proposed.html, hero-lab-v2.html) carry no login link; the
   auto margin moves to the CTA there so it still parks at the gutter. */
.nav__links .btn-cta{margin-left:auto}
.nav__login ~ .btn-cta{margin-left:0}

/* ── the footer capture ──────────────────────────────────────
   The one place on the site that catches a visitor who is interested and not
   ready. It posts to /api/track-lead with source:'newsletter' — NOT
   'checkout_modal', which is the value that opts a person into the
   abandoned-cart drip (see the VALID_SOURCES note in api/track-lead.js). A
   reader who asked for occasion ideas must never receive "everything you
   selected is still saved" about a cart they never had. */
.capture{margin-top:24px;max-width:400px}
.capture__lab{display:block;font-size:var(--fs-label);font-weight:var(--fw-label);letter-spacing:var(--ls-label);
  text-transform:uppercase;color:color-mix(in srgb,var(--onyx) 66%,transparent);margin-bottom:10px}
.capture__row{display:flex;gap:8px;margin-top:12px}
/* Off-screen rather than display:none — a hidden input a browser refuses to
   render is also one an autofiller may skip, and the point is that a BOT fills
   it while a person never sees it. */
.capture__pot{position:absolute;left:-9999px;width:1px;height:1px;opacity:0}
.capture__in{
  /* Same 1.4.11 point as .efm: a transparent fill, so the hairline IS the
     control and owes 3:1 on its own. The stop moved with the ground — an onyx
     hairline on alabaster measures 2.37 at the old 38% and 3.28 at 50% (rig:
     __sweepControls, contrast-sweep.js). */
  flex:1 1 auto;min-width:0;background:transparent;color:var(--onyx);
  border:1px solid color-mix(in srgb,var(--onyx) 50%,transparent);border-radius:2px;
  font-family:var(--sans);font-size:var(--fs-cap);padding:11px 12px;
  transition:border-color .3s;
}
.capture__in::placeholder{color:color-mix(in srgb,var(--onyx) 66%,transparent)}
.capture__in:hover{border-color:color-mix(in srgb,var(--onyx) 64%,transparent)}
.capture__go{
  /* Inverted with the ground: this was the one LINEN plate on an onyx footer
     and is now the one ONYX plate on an alabaster one. Linen on onyx, 16.43. */
  flex:0 0 auto;background:var(--onyx);color:var(--linen);border:1px solid var(--onyx);
  border-radius:2px;font-family:var(--sans);font-size:var(--fs-label);font-weight:var(--fw-label);
  letter-spacing:var(--ls-label);text-transform:uppercase;padding:11px 16px;cursor:pointer;
  transition:opacity .3s;
}
/* The one DARK plate on this light ground, so it is the one place in the
   footer that still needs the dark-ground selection pair: the page default
   (bordeaux on linen) would put bordeaux on near-black inside it. */
.capture__go::selection{background:var(--linen);color:var(--onyx)}
.capture__go:hover{opacity:.82}
.capture__go[disabled]{opacity:.5;cursor:default}
.capture__note{font-size:11px;line-height:1.6;margin-top:10px;
  color:color-mix(in srgb,var(--onyx) 66%,transparent)}
.capture__note a{font-size:11px;text-decoration:underline}
/* Empty by default and only ever filled by script, so it occupies no space
   until it has something to say. */
.capture__msg{font-size:var(--fs-cap);line-height:1.6;margin-top:10px;color:var(--onyx)}
.capture__msg:empty{display:none}
/* #F2A0A8 — a rose light enough to read on onyx — measures 1.6:1 on alabaster.
   The accent is the footer's red now, and on this ground that is --mahogany at
   6.65:1. It is the only red in the footer, so nothing sits beside it to be
   confused with. */
.capture__msg[data-bad]{color:var(--accent)}
.capture[data-done] .capture__row,.capture[data-done] .capture__note{display:none}

/* ── consent banner ──────────────────────────────────────────
   Hidden until .ck--on. It is shown on first scroll or tap rather than on load
   (see site.js): Consent Mode defaults to denied until a choice is made, so
   waiting costs nothing privacy-wise and does not bury a phone's first screen. */
.ck{
  position:fixed;left:0;right:0;bottom:0;z-index:240;
  display:none;gap:clamp(14px,3vw,40px);align-items:center;justify-content:space-between;
  flex-wrap:wrap;
  background:var(--onyx);color:var(--linen);
  padding:16px clamp(16px,4vw,56px);
  border-top:1px solid color-mix(in srgb,var(--linen) 16%,transparent);
}
.ck--on{display:flex}
/* While the banner is up (site.js sets data-ck-on and --ck-h), Google's fixed reCAPTCHA badge is
   lifted above it: it is a keyboard stop, and under the banner its focus is hidden (WCAG 2.2 2.4.11,
   WS02 check 02-05, 2026-09-10). Google writes bottom:14px inline, hence !important. */
html[data-ck-on] .grecaptcha-badge{bottom:calc(var(--ck-h,0px) + 14px)!important}
/* ⚠ GOOGLE'S BADGE IS A TAB STOP THAT DRAWS NO FOCUS (Launch Hub 01-05, 2026-09-13). Tab lands on its
   anchor iframe, and inside that cross-origin document focus sits on a body with outline:none: no ring,
   and the badge stays parked at Google's inline right:-186px, so 186 of its 256px are off screen. The
   ring inside the frame is not ours to draw, but the wrapper div lives in OUR document, so while it
   holds focus it slides fully on screen and wears a two-tone ring: a linen halo under an accent outline,
   so the ring clears 3:1 on the linen ground AND over an onyx band (WCAG 2.4.7 / 2.4.13). Google writes
   right and its 0.3s slide inline, hence !important; the slide is cut so the ring is there on the Tab,
   not 300ms later. Chromium matches NEITHER :focus-within NOR :has(iframe:focus) for a focused
   cross-origin frame (measured), so site.js flags the wrapper .grecaptcha-badge--focus on the window
   blur that Tab into the frame causes; :focus-within stays for engines that do report it. */
.grecaptcha-badge:focus-within,.grecaptcha-badge.grecaptcha-badge--focus{right:14px!important;transition:none!important;outline:3px solid var(--accent);outline-offset:3px;box-shadow:0 0 0 3px var(--linen)!important}
.ck p{font-size:var(--fs-cap);line-height:1.6;max-width:74ch;margin:0}
.ck p a{text-decoration:underline}
.ck__btns{display:flex;gap:10px;flex:0 0 auto}
.ck__btn{
  font-family:var(--sans);font-size:var(--fs-label);font-weight:var(--fw-label);letter-spacing:var(--ls-label);
  text-transform:uppercase;padding:11px 22px;border-radius:0;cursor:pointer;
  background:transparent;color:var(--linen);
  border:1px solid color-mix(in srgb,var(--linen) 40%,transparent);
  transition:background .3s,color .3s,border-color .3s;
}
.ck__btn:hover{border-color:var(--linen)}
.ck__btn--yes{background:var(--linen);color:var(--onyx);border-color:var(--linen)}
.ck__btn--yes:hover{opacity:.85}
@media(max-width:700px){.ck{flex-direction:column;align-items:flex-start}.ck__btns{width:100%}.ck__btn{flex:1}}

/* ── the enquiry form ────────────────────────────────────────
   /contact. Posts to /api/send-contact, the same endpoint the 264 V13
   landing pages use, so an enquiry from here lands in the same place as every
   other enquiry rather than in a second inbox nobody watches. */
.efm{max-width:620px;margin-top:clamp(26px,3.4vw,42px)}
.efm__row{display:grid;grid-template-columns:1fr 1fr;gap:18px}
@media(max-width:640px){.efm__row{grid-template-columns:1fr}}
.efm__f{margin-bottom:18px}
.efm label{display:block;font-size:var(--fs-label);font-weight:var(--fw-label);letter-spacing:var(--ls-label);
  text-transform:uppercase;color:color-mix(in srgb,var(--onyx) 66%,var(--linen));margin-bottom:9px}
/* ⚠ THE BORDER IS THE FIELD — WCAG 1.4.11, 2026-08-31. These inputs are filled
   --linen and sit on a --linen ground: the fill measures 1.14 against the page,
   which is to say it is invisible, so the 1px border is the ONLY thing on
   screen that says a field is there. At 20% it measured 1.34 and the form read
   as four paragraphs of label with nothing to type into. 48% is where the line
   clears 3:1 wherever a field lands, and it is still a hairline, not a box. The
   stop is set by the DEEPEST of those grounds, not the lightest: 48% measures
   3.16 on linen but only 2.80 on the alabaster the enquiry form sits on, which
   is the same "the alpha stops do not invert" trap the #plans re-ground hit at
   the other end of the file. 55% measures 3.95 / 3.46. Every other control
   boundary on the site — begin's fields, gift's fields and chips, the events
   filters, pricing's disclosure button — is now the same 55%. Hover moves in
   the same direction it always did. */
.efm input,.efm textarea,.efm select{
  width:100%;font-family:var(--sans);font-size:var(--fs-body);color:var(--onyx);
  background:var(--linen);border:1px solid color-mix(in srgb,var(--onyx) 55%,transparent);
  border-radius:2px;padding:13px 14px;transition:border-color .3s;
}
.efm textarea{min-height:150px;line-height:1.7;resize:vertical}
.efm input:hover,.efm textarea:hover,.efm select:hover{border-color:color-mix(in srgb,var(--onyx) 70%,transparent)}
.efm [aria-invalid="true"]{border-color:var(--mahogany)}
.efm__err{font-size:var(--fs-cap);line-height:1.6;color:var(--mahogany);margin-top:7px}
.efm__go{
  font-family:var(--sans);font-size:var(--fs-label);font-weight:var(--fw-label);letter-spacing:var(--ls-label);
  text-transform:uppercase;padding:15px 38px;border-radius:0;cursor:pointer;
  background:var(--onyx);color:var(--linen);border:1px solid var(--onyx);
  transition:opacity .3s;
}
.efm__go:hover{opacity:.85}
.efm__go[disabled]{opacity:.5;cursor:default}
.efm__fine{font-size:var(--fs-cap);line-height:1.7;margin-top:16px;max-width:60ch;
  color:color-mix(in srgb,var(--onyx) 66%,var(--linen))}
.efm__fine a{text-decoration:underline}
/* The sent state replaces the form in place, so the answer appears where the
   question was asked rather than scrolling somewhere else. */
.efm__sent{display:none;border:1px solid color-mix(in srgb,var(--onyx) 18%,transparent);
  border-radius:3px;padding:clamp(26px,3.4vw,42px);max-width:620px;
  margin-top:clamp(26px,3.4vw,42px)}
.efm__sent[data-on]{display:block}
.efm__sent h3{font-family:var(--serif);font-weight:300;font-size:var(--fs-d5);line-height:1.25}
.efm__sent p{font-size:var(--fs-body);line-height:1.75;margin-top:12px;
  color:color-mix(in srgb,var(--onyx) 72%,var(--linen))}

/* ⚠ A SITE-WIDE FOCUS RING, ADDED WITH THE FIRST FORMS. Before this the only
   :focus-visible rule on V15 was scoped to .portal, so a keyboard visitor
   tabbing the new inputs, the consent buttons and the nav had no visible
   position at all. :focus-visible (not :focus) keeps it off mouse clicks. */
:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:2px}
.g-onyx :focus-visible,.g-bordeaux :focus-visible,.ck :focus-visible{
  outline-color:var(--linen)}

/* A REAL CLIENT QUOTE, WHEN THERE IS ONE.
   Chapter III is empty on purpose today, so nothing below renders. It is
   written anyway because the generator emits <figure class="pr-quote"> the
   moment a permissioned quote is added to proof-content.json — and the first
   real testimonial Evorrah ever prints must not arrive unstyled on the
   night somebody finally earns it. Sits on the alabaster ground of #evidence. */
.pr-quote{margin:0 0 clamp(20px,2.4vw,32px);padding:clamp(24px,3vw,40px);
  border-left:2px solid var(--accent);
  background:color-mix(in srgb,var(--linen) 60%,transparent)}
.pr-quote blockquote{margin:0;font-family:var(--serif);font-weight:300;
  font-size:var(--fs-d5);line-height:1.45;color:var(--onyx)}
.pr-quote figcaption{margin-top:16px;font-family:var(--sans);font-size:var(--fs-label);font-weight:var(--fw-label);
  letter-spacing:var(--ls-label);text-transform:uppercase;
  color:color-mix(in srgb,var(--onyx) 66%,var(--alabaster))}

/* ============================================================
   THE DESIGN COLLECTION  ·  /collection
   Generated by scripts/v15-build-collection.mjs
   ============================================================ */
.coll__g{margin-top:clamp(56px,8vh,104px)}
.coll__g:first-child{margin-top:clamp(30px,4vh,54px)}
.coll__gh{border-top:1px solid color-mix(in srgb,var(--onyx) 16%,transparent);
  padding-top:clamp(22px,3vh,34px);max-width:760px}
.coll__gn{font-size:var(--fs-label);font-weight:var(--fw-label);letter-spacing:var(--ls-label);text-transform:uppercase;
  color:var(--accent);display:block;margin-bottom:12px}
.coll__gh h2{font-family:var(--serif);font-weight:300;font-size:var(--fs-d4);line-height:1.14}
.coll__gh p{font-size:var(--fs-body);line-height:1.8;margin-top:14px;max-width:62ch;
  color:color-mix(in srgb,var(--onyx) 72%,var(--linen))}

/* ⚠ auto-fill, NOT auto-fit. The last group holds two pieces; auto-fit would
   collapse the empty tracks and stretch those two across the full width, so
   one group would render at a different card size from the other four for no
   reason a reader could see. auto-fill keeps the column rhythm identical down
   the whole page. */
.coll__ps{display:grid;grid-template-columns:repeat(auto-fill,minmax(268px,1fr));
  gap:clamp(18px,2.2vw,30px);margin-top:clamp(26px,3.4vh,42px)}
.coll__p{border:1px solid color-mix(in srgb,var(--onyx) 13%,transparent);border-radius:3px;
  padding:clamp(20px,2.2vw,28px);display:flex;flex-direction:column}
.coll__p h3{font-family:var(--serif);font-weight:300;font-size:var(--fs-d5);line-height:1.2}
.coll__b{font-size:var(--fs-cap);line-height:1.72;margin-top:10px;
  color:color-mix(in srgb,var(--onyx) 74%,var(--linen))}
/* ⚠ NO LONGER PUSHED TO THE FOOT OF THE CARD, and the reason is that the thing
   it was aligning changed. It carried margin-top:auto so the example rows lined
   up across a row of cards whose DESCRIPTIONS differ in length — which worked
   while every card showed at most four examples. Raising the cap to twelve made
   example count, not description length, the dominant variance: a card with 12
   pills and a card with 4 sit in the same grid row, so the auto-push stopped
   aligning anything and simply opened a hole above the short one (visible on
   "Details Card" beside "Envelope"). Natural flow reads better than a false
   alignment. */
.coll__reach{font-size:var(--fs-label);letter-spacing:.08em;text-transform:uppercase;
  margin-top:18px;color:color-mix(in srgb,var(--onyx) 66%,var(--linen))}
.coll__ex{display:flex;flex-wrap:wrap;gap:6px;margin-top:10px}
.coll__ex li{margin:0}
.coll__ex a{display:inline-block;font-size:11px;line-height:1.2;padding:6px 10px;
  border:1px solid color-mix(in srgb,var(--onyx) 18%,transparent);border-radius:0;
  color:color-mix(in srgb,var(--onyx) 78%,var(--linen));transition:border-color .3s,color .3s}
.coll__ex a:hover{border-color:var(--accent);color:var(--accent)}
.coll__none{font-size:11px;line-height:1.6;margin-top:10px;
  color:color-mix(in srgb,var(--onyx) 66%,var(--linen))}

.coll__rules{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));
  gap:1px;margin-top:clamp(28px,3.6vh,44px);
  background:color-mix(in srgb,var(--onyx) 12%,transparent);
  border:1px solid color-mix(in srgb,var(--onyx) 12%,transparent)}
.coll__rules div{background:var(--alabaster);padding:20px 22px;display:flex;
  flex-direction:column;gap:6px}
.coll__rules b{font-family:var(--serif);font-weight:300;font-size:var(--fs-d5);line-height:1.2}
/* The price is its own line, so the three panels agree no matter how long the
   plan name is. As "Suite &middot; $495" the two longer names wrapped and Suite
   did not, which left the row ragged at most widths. */
.coll__rules em{font-family:var(--serif);font-style:normal;font-size:var(--fs-d5);
  line-height:1.2;color:color-mix(in srgb,var(--onyx) 72%,var(--alabaster))}
.coll__rules span{font-size:var(--fs-cap);color:color-mix(in srgb,var(--onyx) 66%,var(--alabaster))}
/* The piece count is the fact this band exists to deliver, so it is the one
   caption in the panel that carries weight rather than receding. It sits above
   the link, not beside it, so it never competes for the same line. */
.coll__n{font-weight:500;color:color-mix(in srgb,var(--onyx) 84%,var(--alabaster))}
.coll__lnk{margin-top:auto;padding-top:4px}

/* ADD-ONS — deliberately NOT the plans' treatment. These are what you buy after
   a plan, so they get no panel and no border box: a single hairline above them
   and the same caption tone as the fine print. If this ever grows a background
   or a border it starts reading as two more tiers again, which is the misread
   splitting the block was meant to end. */
.coll__adds{display:flex;flex-wrap:wrap;gap:8px 40px;margin-top:18px;padding-top:16px;
  border-top:1px solid color-mix(in srgb,var(--onyx) 12%,transparent)}
.coll__adds div{display:flex;align-items:baseline;gap:8px}
.coll__adds b{font-weight:500;font-size:var(--fs-cap);
  color:color-mix(in srgb,var(--onyx) 82%,var(--alabaster))}
.coll__adds span{font-size:var(--fs-cap);color:color-mix(in srgb,var(--onyx) 66%,var(--alabaster))}
.coll__fine{font-size:var(--fs-cap);line-height:1.75;margin-top:20px;max-width:64ch;
  color:color-mix(in srgb,var(--onyx) 68%,var(--alabaster))}
.coll__fine a{text-decoration:underline}

/* ============================================================
   ACCESSIBILITY — WCAG 2.1 AA
   ────────────────────────────────────────────────────────────
   Added 2026-08-31. This block holds the rules that exist for
   conformance rather than for looks, kept together so that a
   later visual pass can see what it is about to undo. The
   audit rig that produced it is scripts/v15-a11y-audit.mjs
   (`npm run a11y:v15`), which fails the build on a regression.

   Contrast repairs are NOT here — they belong beside the token
   or the component they correct, and are marked `AA:` inline.
   ============================================================ */

/* ── 2.4.1 Bypass Blocks (A) ──────────────────────────────────
   Every page opens with the same header, the same six-link nav
   and, on a phone, the mobile menu — measured at 8 to 14 tab
   stops before the first word of the page. The skip link is the
   documented way past that. It is visible ONLY on focus, so it
   costs the design nothing, and it is the first thing in the
   DOM so it is the first thing Tab reaches. */
.skip{
  position:fixed;top:0;left:0;z-index:400;
  transform:translateY(-120%);
  display:inline-block;padding:14px 22px;
  background:var(--bordeaux);color:var(--linen);
  font-family:var(--sans);font-size:var(--fs-cap);
  letter-spacing:.06em;text-decoration:none;
  border-bottom-right-radius:3px;
  transition:transform .18s ease-out;
}
.skip:focus{transform:translateY(0)}
/* The <main> it lands on takes focus programmatically (tabindex="-1"),
   which would otherwise paint a focus ring around the entire page. */
main:focus{outline:none}
main:focus-visible{outline:none}
@media(prefers-reduced-motion:reduce){.skip{transition:none}}

/* ── 2.4.3 Focus Order (A) · the closed mobile menu ───────────
   `opacity:0;pointer-events:none` hides a thing from the mouse
   and from nobody else. Before this, tab stops 2 through 7 on
   every page at 375px landed inside the closed menu: the focus
   ring was drawn off-screen over an invisible link, and six
   presses of Tab appeared to do nothing at all.
   `visibility:hidden` is what takes them out of the tab order
   AND out of the accessibility tree; the `inert` attribute in
   the markup does the same for browsers that support it, and
   motion.js keeps the two in step. The visibility transition is
   delayed by the full fade on close so the menu does not vanish
   mid-animation, and is instant on open. */
.mnav{visibility:hidden;transition:opacity .4s,visibility 0s linear .4s}
.mnav.open{visibility:visible;transition:opacity .4s,visibility 0s}
@media(prefers-reduced-motion:reduce){
  .mnav{transition:none}
  .mnav.open{transition:none}
}

/* ── 2.4.3 Focus Order (A) · collapsed accordion panels ───────
   The plan rows and the FAQ collapse with grid-template-rows
   0fr and `overflow:hidden` on the inner element. That hides
   the pixels; it leaves every link inside reachable by Tab and
   readable by a screen reader. Measured on the home page: seven
   focusable links inside collapsed plan panels, one inside each
   collapsed FAQ answer. Same visibility pattern as the menu —
   instant on open, delayed to the end of the animation on
   close, so nothing is lost visually. */
.prow__body{visibility:hidden;transition:grid-template-rows .65s cubic-bezier(.4,0,.2,1),visibility 0s linear .65s}
.prow.open .prow__body{visibility:visible;transition:grid-template-rows .65s cubic-bezier(.4,0,.2,1),visibility 0s}
.fq__a{visibility:hidden;transition:grid-template-rows .5s cubic-bezier(.4,0,.2,1),visibility 0s linear .5s}
.fq.open .fq__a{visibility:visible;transition:grid-template-rows .5s cubic-bezier(.4,0,.2,1),visibility 0s}
@media(prefers-reduced-motion:reduce){
  .prow__body,.prow.open .prow__body,.fq__a,.fq.open .fq__a{transition:none}
}

/* ── 1.4.13 Content on Hover or Focus (AA) · reveal-on-scroll ──
   `.rv{opacity:0}` is animation, and animation that is the only
   thing standing between a reader and the words is a content
   failure, not a motion one. The IntersectionObserver in
   motion.js is what sets `.in`; if the script does not run —
   scripts blocked, a CSP that does not expect us, a CDN blip —
   the whole page reads as blank. `html:not(.js)` restores it.
   site.js is deferred, so the stamp goes on as early as the
   document element exists (see the inline stamp in the shell). */
html:not(.js) .rv,
html:not(.js) .rv-line span{opacity:1;transform:none;transition:none}

/* ── 1.4.11 Non-text Contrast (AA) · the focus indicator ──────
   The global :focus-visible ring is --accent, which is a red on
   a light ground. Against a bordeaux panel that red is nearly
   invisible (1.7:1), so the ring is rebound on the two coloured
   grounds it can land on. Linen on bordeaux is 12.0:1. */
.g-bordeaux :focus-visible,.mnav :focus-visible{outline-color:var(--linen)}

/* The same rule, for the one ground that is not a ground. .film paints its own
   #000 (it is a video frame, not a section), so it never carries .g-onyx and
   --accent is never rebound to the lifted stop inside it — it inherits the
   mahogany of the .g-linen section the film sits in. That put the ring for the
   video and its play button at 2.47:1 on black, under the 3:1 floor, on the
   most prominent interactive element on the homepage. Linen on #000 is 19.4:1.
   Scoped to the focus ring rather than fixed by adding .g-onyx to .film,
   because rebinding the ground would re-tint everything else in the frame. */
.film :focus-visible,.film:focus-visible{outline-color:var(--linen)}

/* ...AND THE RING THAT COLOUR FIXED WAS BEING CLIPPED AWAY UNSEEN. .film is
   overflow:hidden (it has to be — an 8px radius on a video frame), and both
   the <video> and .film__poster fill it exactly: same top, same bottom, same
   edges, measured 85/814 against the figure's 85/814. The global ring is
   drawn OUTSIDE the border box and pushed 2px further out by outline-offset,
   so every pixel of it landed outside the figure and was clipped. Linen on
   #000 is 19.4:1 and none of it was on screen. Drawing it inside instead
   puts the whole ring within the clip, on the film's own black. */
.film video:focus-visible,.film .film__poster:focus-visible{outline-offset:-4px}

/* ── 2.5.8 Target Size (AA, WCAG 2.2) ────────────────────────
   Standalone "See …" and "View all …" links measured 22-23px
   tall against a 24px floor. They are not inline in a sentence,
   so the inline exception does not apply. Padding rather than a
   font change, so nothing reflows. */
/* padding-top only: these links draw their own underline with border-bottom,
   and padding-bottom would push that rule away from the words it belongs to.
   The extra height goes above the text, where nothing is drawn. */
.prow__see,.edet__go,.hero__ghost,.coll__ex a,.pcap a,.ecard__deep,.hcard__more{
  display:inline-block;padding-top:5px}

/* ── 1.4.10 Reflow (AA) · the long CTAs, below 380px ──────────
   Above this the pill button stays on one line, which is what
   it is for. Below it, a clipped call to action is worse than
   a two-line one. */
@media(max-width:380px){
  .btn-cta{white-space:normal;padding-inline:22px;text-align:center}
}

/* ── 1.4.10 Reflow (AA) · plan chips, below 400px ────────────
   A pill that has to wrap is a small ugliness; a page that
   scrolls sideways is a conformance failure. */
@media(max-width:400px){
  .pf-chip{white-space:normal;text-align:center}
}

/* ── 1.4.10 Reflow (AA) · .wrap as a flex item ───────────────
   Several pages centre their hero with an inline
   `display:flex;align-items:center` on the section, which makes
   .wrap a FLEX ITEM — and a flex item's min-width is `auto`, so
   it cannot shrink below its own content however narrow the
   viewport gets. On /404 that produced a 765px document on
   a 320px screen: four link cards refusing to stack because the
   box holding them was not allowed to narrow. `min-width:0` is
   the standard release for that, and it changes nothing
   anywhere the wrap is already a block. */
.wrap{min-width:0}

/* ── 1.4.12 Text Spacing (AA) · the line mask ────────────────
   See the transitionend handler in motion.js. Reduced motion
   and no-JS never run that handler, and never run the animation
   either, so they get the mask lifted from the start. */
.rv-line--done{overflow:visible}
@media(prefers-reduced-motion:reduce){.rv-line{overflow:visible}}
html:not(.js) .rv-line{overflow:visible}

/* ── 2.4.7 Focus Visible (A) · focus into an unrevealed block ─
   Tabbing to a link inside a .rv block that has not been
   revealed scrolls it into view, which trips the observer — but
   the focus ring is drawn during the 0.9s fade, i.e. on
   something between invisible and faint. Measured at 0.88
   opacity on the home page's second CTA. Focus reveals its own
   block at once, so the ring is always drawn on something the
   visitor can see. */
.rv:focus-within{opacity:1;transform:none}
.rv:focus-within .rv-line span{transform:none}

/* ── 2.5.8 Target Size (AA, WCAG 2.2) · the checkboxes ───────
   The consent and option boxes rendered at the UA default 13px.
   Their wrapping <label> is the real target, so this is not the
   conformance failure it looks like — but 13px is a hard thing
   to hit with a thumb, and the boxes read as an afterthought
   beside 16px type. 18px, and the label row is given enough
   height that the whole row clears 24px. */
label.check input[type=checkbox],
label.check input[type=radio]{width:18px;height:18px;flex:none;accent-color:var(--bordeaux)}
label.check{display:flex;align-items:center;gap:10px;min-height:24px}

/* ============================================================
   2.4.7 Focus Visible (A) · 1.4.11 Non-text Contrast (AA)
   ────────────────────────────────────────────────────────────
   Added on the re-check pass, 2026-08-31. The global
   :focus-visible ring is `--accent`, and a single colour cannot
   be visible on every ground this site has. Measured by tabbing
   through eight pages for real — `:focus-visible` only matches
   KEYBOARD focus, so reading outlineColor off an unfocused
   element (which the first pass did) returns the default and
   tells you nothing.
   ============================================================ */

/* The skip link is MY OWN regression: it sits on a bordeaux
   panel, so it inherited the mahogany ring at 1.83:1 — the one
   control on the site that exists purely for keyboard users had
   the worst focus ring on the site. Linen on bordeaux is 13.9:1. */
.skip:focus-visible{outline-color:var(--linen)}

/* This used to force an onyx ring on the newsletter button, because the
   button was linen and `.foot :focus-visible` painted the ring linen too —
   1.0:1. Both halves of that are gone: the footer is alabaster and no longer
   in the linen-ring list above, so the button takes the default --accent ring,
   which the ground binds to --mahogany. outline-offset:2px draws it clear of
   the dark plate, on the alabaster behind it: 6.65:1. */
.ck .ck__btn--yes:focus-visible{outline-color:var(--onyx)}

/* ⚠ THE PORTAL MOCK IS OPERABLE, NOT DECORATION — motion.js:442
   binds keydown to it, and its rows are role="button"
   tabindex="0". So its ring has to work, and it has to work on
   BOTH of the device's own grounds: a near-black sidebar and a
   near-white content pane. One colour cannot. A DUAL ring does:
   the onyx line is what you see on the pale pane, the linen halo
   is what you see on the dark sidebar, and one of the two is
   always in contrast whatever is behind it.
   The selector is doubled because `.g-onyx :focus-visible` sits
   later in this file at equal specificity and was winning — that
   is how a light ring ended up on a light pane at 1.0:1. */
.portal :focus-visible,
.g-onyx .portal :focus-visible,
.g-bordeaux .portal :focus-visible{
  outline:1.5px solid var(--onyx);
  outline-offset:1px;
  box-shadow:0 0 0 3px color-mix(in srgb,var(--linen) 92%,transparent);
  border-radius:2px;
}
/* The approve / reject pair are the exception inside that
   exception: they sit on saturated green and red, where onyx is
   2.9:1 and 2.6:1. Linen clears both. */
.portal .pact:focus-visible{
  outline-color:var(--linen);
  box-shadow:0 0 0 3px color-mix(in srgb,var(--onyx) 85%,transparent);
}

/* The film had NO ring at all. A <video controls> is focusable,
   and the visitor who tabs to it is exactly the one who cannot
   see where they are. */
video:focus-visible{outline:2px solid var(--accent);outline-offset:3px}
.g-onyx video:focus-visible{outline-color:var(--linen)}

/* ── 3.3.1 Error Identification (A) · the form's error summary ─
   Written by site.js when a submit is refused. It is a live
   region first and a visible line second: it exists so that the
   failure is ANNOUNCED, not only shown next to whichever field
   is about to take focus. Kept in the accent so it reads as an
   error, and spaced so it does not sit on the first label. */
.efm__err--sum{
  margin:0 0 18px;font-weight:500;
  border-left:2px solid var(--accent);padding-left:12px;
}
.efm__err--sum:empty{display:none}

/* ============================================================
   THE SEVENTEEN FORMER ONYX BANDS, RE-GROUNDED — 2026-08-31 (owner:
   "we do not use black backgrounds" on /).

   ⚠ THIS IS A RULE, NOT A ONE-OFF. / has FOUR grounds and only
   three of them are grounds any more: linen, alabaster, bordeaux.
   .g-onyx survives in this stylesheet for the lab rigs alone
   (burgundy-test.html, red-test.html, hero-lab*.html), which exist
   to compare colours side by side and are not pages. No SECTION on a
   real / page may carry it. `npm run ground:v15:check` fails the
   build if one does — add the ground, do not add an exception.

   Seventeen bands moved g-onyx -> g-alab: the eight /platform/*
   capability bands, /platform/ itself, about, what-we-solve,
   contact, collection, events, reliability, pricing, guests. This is
   the third migration of this shape (the footer and #plans were the
   first two) and it obeys their two rules:

   1. ⚠ THE ALPHA STOPS ARE NOT THE OLD NUMBERS INVERTED. A linen mix
      at 56% measures 7.4:1 on #141414; 56% of onyx over #E7E3DD is
      3.92:1 and FAILS AA. 62% is the bare 4.5:1 floor on this
      ground (4.71), but the muted stop here is 66% (5.35), which is
      the floor scripts/v15-contrast-floor.mjs enforces — deliberately
      two points above the minimum so a rule that ALSO renders on
      linen keeps headroom. (#plans' 64% stops predate that script.)
      Body stops sit at 70-74% (6.09-6.88) and strong text at 86%
      (10.17). Hairlines are 11-14%, chips and borders 24-30%.
   2. PANELS LIGHTEN ON THIS GROUND, they do not darken — the same
      correction #plans' "You do" panel needed. .pcap was a black
      tile on a black band; on alabaster it becomes a LINEN tile, so
      the card still reads as lifted out of its ground rather than
      pressed into it.

   Measured with app/contrast-sweep.js across all seventeen pages
   at 1280x900: 0 failures, minimum ratio 4.96.
   ============================================================ */

/* ---- the closing CTA bands (.cta-final, .close-band) ----
   ⚠ .cta-final p ALSO CATCHES THE EYEBROW, which is authored as
   <p class="eyebrow">. That is how the eyebrow inverted along with the
   body copy, and why re-grounding the paragraph rule fixes both.
   ⚠ BOTH A COMPOUND AND A DESCENDANT SELECTOR, and the compound is the
   one that does the work. The ground and the band are the SAME element
   — <section class="g-alab cta-final"> — so `.g-alab .cta-final p`
   matches nothing here. Written descendant-only this rule silently did
   nothing on /events/ while appearing to fix the identical band on
   /contact/, which has no <p> in it to expose the mistake. */
.g-alab.cta-final p,
.g-alab .cta-final p{color:color-mix(in srgb,var(--onyx) 70%,var(--alabaster))}

/* ⚠ THE LINEN BUTTON CANNOT STAY LINEN ON A LIGHT GROUND — it measured
   1.14:1, i.e. a cream pill on a cream band. This is the same swap
   #plans.g-alab made for its three "Start with ..." CTAs; that rule is
   now redundant against this one but is left where it is, scoped to the
   id, because it is documented in place. Onyx outline at rest, bordeaux
   fill on hover: bordeaux is 12.16:1 on alabaster and carries linen text
   at 8.9:1, so both states clear AA at the 11px these are set in. */
.g-alab .btn-cta--linen{border-color:var(--onyx);color:var(--onyx)}
.g-alab .btn-cta--linen:hover{background:var(--bordeaux);border-color:var(--bordeaux);color:var(--linen)}

/* ---- /platform/ · the eight capability tiles ----
   The grid's 1px gaps show the CONTAINER through, so the container is
   the hairline and the tiles are the surface. On onyx that was a linen
   12% lattice under black tiles; here it is an onyx 14% lattice (2.5:1,
   structural rather than informational) under linen tiles. */
.g-alab .plat__caps{
  background:color-mix(in srgb,var(--onyx) 14%,transparent);
  border-color:color-mix(in srgb,var(--onyx) 14%,transparent);
}
.g-alab .pcap{background:var(--linen)}
/* stops below are measured against #F8F1E7, the TILE, not #E7E3DD, the
   band — the tile is what the text actually sits on. */
.g-alab .pcap p{color:color-mix(in srgb,var(--onyx) 72%,var(--linen))}
.g-alab .pcap a{
  color:color-mix(in srgb,var(--onyx) 66%,var(--linen));
  border-bottom-color:color-mix(in srgb,var(--onyx) 26%,transparent);
}

/* ---- one hairline the ground change made invisible ----
   .more (style.css:~2193) draws a linen 14% rule above itself. That is the
   plans page's "more than one event" component, but the class name is generic
   enough that /what-we-solve/'s "More" column picks the whole rule up —
   its 72px offset from the "Less" column beside it comes from there too. That
   collision is older than this migration and is left alone; what IS this
   migration's doing is that the rule went from drawn-on-black to invisible-on-
   alabaster, so the offset lost the line that explained it. #plans.g-alab
   already re-grounded the same hairline for itself (style.css §#plans); this
   generalises it to the ground so any .more on alabaster keeps its rule. */
.g-alab .more{border-top-color:color-mix(in srgb,var(--onyx) 14%,transparent)}

/* ============================================================
   #plans.g-alab ON A LIGHT GROUND — 2026-08-31 (owner)
   The band moved from .g-onyx to .g-alab (#E7E3DD, --alabaster).
   Every colour in the "WHAT WE HANDLE FOR YOU" block above was
   written as a mix of --linen against a near-black ground, so on
   alabaster the whole band inverted into near-white-on-cream.
   This block re-grounds it, and every selector is scoped on the
   GROUND and not the id alone: #plans.g-alab. The same .prow /
   .plans / .more classes still render on onyx in red-test.html, and
   burgundy-test.html carries a SECOND `id="plans"` on a .g-onyx
   section — scoped `#plans` alone, this block would invert that rig
   into dark-on-dark. Nothing above this block was edited.

   ⚠ THE ALPHA STOPS ARE NOT THE SAME NUMBERS INVERTED. On #141414
   a linen mix at 56% measures 7.4:1; the same 56% of onyx over
   #E7E3DD measures 3.9:1 and fails AA at --fs-label (11px), which
   is the size most of these rules are used at. 62% is the floor
   for 4.5:1 on this ground, so the muted stop is 64% and the body
   stops sit at 70-86%. Re-measure before lowering any of them.
   Hairlines follow the light-ground convention already used by
   .pstepx and .fq (13-16% for rules, 22-26% for chips).
   ============================================================ */

/* The section before this one (#process) is ALSO .g-alab, so the
   two now share one continuous field where the onyx ground used
   to separate them. One hairline keeps the boundary the ground
   was carrying. */
.sec-pad--ruled{border-top:1px solid color-mix(in srgb,var(--onyx) 12%,transparent)}

/* ---- head, ladder, footnote ---- */
#plans.g-alab .plans__hint{color:color-mix(in srgb,var(--onyx) 66%,transparent)}
#plans.g-alab .plans__ladder{
  border-top-color:color-mix(in srgb,var(--onyx) 14%,transparent);
  color:color-mix(in srgb,var(--onyx) 74%,transparent);
}
#plans.g-alab .plans__ladder em{color:var(--accent)}
#plans.g-alab .plans__upgrade{color:color-mix(in srgb,var(--onyx) 66%,transparent)}
#plans.g-alab .plans__upgrade b{color:color-mix(in srgb,var(--onyx) 86%,transparent)}

/* ---- the three plan rows ---- */
#plans.g-alab .prow{border-top-color:color-mix(in srgb,var(--onyx) 14%,transparent)}
#plans.g-alab .prow:last-of-type{border-bottom-color:color-mix(in srgb,var(--onyx) 14%,transparent)}
#plans.g-alab .prow__num,
#plans.g-alab .prow__spec{color:color-mix(in srgb,var(--onyx) 66%,transparent)}
#plans.g-alab .prow__pos{color:color-mix(in srgb,var(--onyx) 86%,transparent)}

/* the labelled lines (add-ons · support · design) */
#plans.g-alab .prow__addons,
#plans.g-alab .prow__support,
#plans.g-alab .prow__design{
  border-top-color:color-mix(in srgb,var(--onyx) 12%,transparent);
  color:color-mix(in srgb,var(--onyx) 70%,transparent);
}
#plans.g-alab .prow__addons i,
#plans.g-alab .prow__support i,
#plans.g-alab .prow__design i,
#plans.g-alab .prow__scope{color:color-mix(in srgb,var(--onyx) 66%,transparent)}
#plans.g-alab .prow__design em{color:color-mix(in srgb,var(--onyx) 86%,transparent)}

/* links: the underline is the only thing marking them here */
#plans.g-alab .prow__addons a,
#plans.g-alab .prow__design a,
#plans.g-alab .prow__col ul a{border-bottom-color:color-mix(in srgb,var(--onyx) 30%,transparent)}
#plans.g-alab .prow__addons a:hover,
#plans.g-alab .prow__design a:hover,
#plans.g-alab .prow__col ul a:hover{color:var(--onyx);border-color:var(--onyx)}

/* ---- handled / design columns ---- */
#plans.g-alab .prow__col h3,
#plans.g-alab .prow__col h4{color:color-mix(in srgb,var(--onyx) 66%,transparent)}
#plans.g-alab .prow__col li{
  border-bottom-color:color-mix(in srgb,var(--onyx) 11%,transparent);
  color:color-mix(in srgb,var(--onyx) 86%,transparent);
}
#plans.g-alab .prow__col li.inherit{color:color-mix(in srgb,var(--onyx) 66%,transparent)}
#plans.g-alab .prow__col li b{color:var(--onyx)}
#plans.g-alab .mod-tier{
  border-color:color-mix(in srgb,var(--onyx) 24%,transparent);
  color:color-mix(in srgb,var(--onyx) 68%,transparent);
}

/* ---- you vs. us ---- */
#plans.g-alab .vs{border-color:color-mix(in srgb,var(--onyx) 14%,transparent)}
/* ⚠ THE "YOU DO" PANEL LIGHTENS, IT DOES NOT DARKEN — 2026-08-31.
   On onyx it was a 6% LINEN lift: the panel reads lighter than the ground it
   sits in. Re-grounding it as a 5% ONYX wash inverted that relationship, and
   the inversion is not just wrong by eye — it takes the accent down with it.
   The "You do" heading is --accent (= --mahogany, 6.65 on bare #E7E3DD), and
   a 5% darkening of the ground under it costs 0.62 and lands it at 6.03 —
   still clear of the floor, so with the 2026-08-31 wine-red accent the panel
   direction is a relationship decision rather than a contrast one. It was a
   contrast one when the accent was #C81420: 4.59 bare, 4.17 under a 5% wash,
   and even 1% only just held 4.51. Lifting toward linen keeps the original
   relationship either way, and still buys contrast rather than spending it
   (7.15). */
#plans.g-alab .vs__you{background:color-mix(in srgb,var(--linen) 55%,transparent)}
#plans.g-alab .vs__us h4,#plans.g-alab .vs__us h5{color:color-mix(in srgb,var(--onyx) 66%,transparent)}
#plans.g-alab .vs li{color:color-mix(in srgb,var(--onyx) 84%,transparent)}

/* ---- show more · see-also · lead time ---- */
#plans.g-alab .prow__toggle{
  color:color-mix(in srgb,var(--onyx) 84%,transparent);
  border-bottom-color:color-mix(in srgb,var(--onyx) 32%,transparent);
}
#plans.g-alab .prow__toggle--note{color:color-mix(in srgb,var(--onyx) 74%,transparent)}
#plans.g-alab .prow__see{
  border-bottom-color:color-mix(in srgb,var(--onyx) 32%,transparent);
  color:color-mix(in srgb,var(--onyx) 84%,transparent);
}
#plans.g-alab .prow__add{color:color-mix(in srgb,var(--onyx) 66%,transparent)}

/* ---- "More than one event" and "Working with someone" ---- */
#plans.g-alab .more{border-top-color:color-mix(in srgb,var(--onyx) 14%,transparent)}
#plans.g-alab .more__lede{color:color-mix(in srgb,var(--onyx) 76%,transparent)}
#plans.g-alab .more__c{border-top-color:color-mix(in srgb,var(--onyx) 12%,transparent)}
#plans.g-alab .more__n{color:color-mix(in srgb,var(--onyx) 66%,transparent)}
#plans.g-alab .more__c p{color:color-mix(in srgb,var(--onyx) 80%,transparent)}
#plans.g-alab .more__c p strong{color:var(--onyx)}
#plans.g-alab .more__price{border-top-color:color-mix(in srgb,var(--onyx) 12%,transparent)}
#plans.g-alab .more__price li{border-bottom-color:color-mix(in srgb,var(--onyx) 11%,transparent)}
#plans.g-alab .more__price span{color:color-mix(in srgb,var(--onyx) 86%,transparent)}
#plans.g-alab .more__price i{color:color-mix(in srgb,var(--onyx) 66%,transparent)}
#plans.g-alab .more__list li{
  border-bottom-color:color-mix(in srgb,var(--onyx) 11%,transparent);
  color:color-mix(in srgb,var(--onyx) 82%,transparent);
}
#plans.g-alab .more__list li b{color:var(--onyx)}
#plans.g-alab .more__fine{color:color-mix(in srgb,var(--onyx) 66%,transparent) !important}
#plans.g-alab .more__also{border-top-color:color-mix(in srgb,var(--onyx) 12%,transparent)}

/* ⚠ THE "YOU DO" HEADING LIVES INSIDE .prow__col, so the rule above reaches it
   as a descendant and outranks .vs__you h4 (which only ever beat .prow__col h4
   on source order). Restated here or the accent half of the split goes grey. */
#plans.g-alab .vs__you h4,#plans.g-alab .vs__you h5{color:var(--accent)}

/* FOUR THINGS THE FIRST RE-GROUND MISSED — 2026-08-31.
   All four live in the ONE state the certifying sweep never measured: row open,
   "Show more" still closed. Closed, .prow__body hides everything; open with the
   extras out, .prow__pad.xopen hides .prow__at and the fact blocks take over.
   The sweep measured exactly those two states.

   1. .prow__at — the at-a-glance line, drawn in a 62% mix of --linen, which over
      #E7E3DD lands lighter than the ground. Its <b> is --accent and survived the
      ground change; nothing around it did, which is why the line read as
      floating red numbers on off-white. Stops: 74% = 6.8 (the onyx original
      measured 6.83), 64% = 5.0 for the (artwork) parenthetical, lifted from its
      original 3.79 to clear the floor at --fs-cap.
      The middot separators are the one stop that does NOT match its original:
      26% linen measured 2.19 on onyx and the faithful translation (34%) measures
      2.12 here, so they sit at the 62% floor instead. They still read lighter
      than the 74% around them, and the line stops carrying nine sub-AA glyphs
      into the only state it is visible in.
   2. .btn-cta--linen — the three "Start with …" CTAs, drawn in --linen outright,
      so they were an invisible pill with an invisible label. The modifier exists
      to invert the button for dark grounds; on a light one it must fall back to
      what plain .btn-cta already does. Scoped to the ground, so the same
      modifier keeps working at index.html lines 216 and 611, still dark.

   3 and 4 were ground-specific INLINE styles left in the markup, which no
   override could ever have reached — both removed from index.html rather than
   fought with !important:
   3. style="color:var(--linen)" on the section eyebrow ("What we handle for
      you") — 1.14:1. It inherits --onyx now like every other eyebrow.
   4. style="opacity:.65" on the "Best for …" qualifier inside each .prow__pos —
      the alpha trap compounded. .65 on top of the 86% .prow__pos stop composites
      to 56% onyx = 3.9:1, and a CSSOM colour sweep reports it PASSING, because
      the failure lives in `opacity` and no computed `color` shows it. Any future
      sweep over this band has to multiply the opacity chain in. */
#plans.g-alab .prow__at{
  border-top-color:color-mix(in srgb,var(--onyx) 12%,transparent);
  color:color-mix(in srgb,var(--onyx) 74%,transparent);
}
#plans.g-alab .prow__at i{color:color-mix(in srgb,var(--onyx) 66%,transparent)}
#plans.g-alab .prow__at span{color:color-mix(in srgb,var(--onyx) 66%,transparent)}
#plans.g-alab .prow__pos span{color:color-mix(in srgb,var(--onyx) 66%,transparent)}
#plans.g-alab .btn-cta--linen{border-color:var(--onyx);color:var(--onyx)}
#plans.g-alab .btn-cta--linen:hover{background:var(--bordeaux);border-color:var(--bordeaux);color:var(--linen)}

/* =========================================================
   THE ROLL CALL — the hero, owner pick 2026-08-31
   Ported from variant 09 of app/hero-lab-v2.html.

   Two counter-running bands of the actual work, at display
   scale, above and below a headline that does not move. The
   scale of the job is the message; the still line in the
   middle is the relief. Pauses on hover, stops entirely
   under prefers-reduced-motion.

   This block sits at the END of style.css deliberately: it
   overrides .hero h1 and .hero__actions, both of which are
   ~200 lines above, and one-class selectors there would
   otherwise win on source order.
   ========================================================= */

/* .hero is a COLUMN FLEX box and .wrap carries margin:0 auto — an auto margin
   on the cross axis kills align-items:stretch, so without width:100% the wrap
   shrinks to its content and the bands stop being full-bleed. This is the same
   trap documented at .hero__grid; .rc inherits it because it replaces that
   element as the hero's only child. */
.hero--rc .rc{width:100%;position:relative;z-index:2}

/* ⚠ z-index:2 ON .rc IS LOAD-BEARING. .hero::before (the warm wash) and
   .hero::after (the grain) are POSITIONED pseudo-elements at z-index 0 and 1,
   and a positioned element paints above static block content — so an
   un-layered hero body comes out as blank linen. .hero__grid carried this
   before; .rc carries it now. */

/* the bands negate .wrap's gutter so the type runs off both edges, and the
   mask fades it out before it reaches them — type that is CUT at the viewport
   edge reads as broken; type that FADES reads as continuing. */
.hero--rc .rc__band{position:absolute;left:calc(var(--gutter) * -1);right:calc(var(--gutter) * -1);
  overflow:hidden;pointer-events:auto;z-index:0;
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 12%,#000 88%,transparent);
          mask-image:linear-gradient(90deg,transparent,#000 12%,#000 88%,transparent)}
/* asymmetric by design: the upper band sits further from the centre line than
   the lower one, because the headline is three lines and its optical centre is
   above its box centre. */
.hero--rc .rc__band--a{top:calc(50% - clamp(150px,22vh,240px))}
.hero--rc .rc__band--b{top:calc(50% + clamp(96px,16vh,180px))}
.hero--rc .rc__run{display:flex;width:max-content;will-change:transform;
  font-family:var(--serif);font-weight:200;letter-spacing:-.02em;
  font-size:clamp(30px,4.6vw,72px);line-height:1.16;white-space:nowrap;
  color:color-mix(in srgb,var(--onyx) 15%,var(--linen))  /* aa:decorative */;
  animation:rcRun var(--d,52s) linear infinite}
/* different speed AND lighter tint on the lower band: two bands at one speed
   read as a single mechanism scrolling, which is a slideshow. Two speeds read
   as a volume of work. The lower band is the fainter of the two so the eye
   settles upward, onto the headline. */
.hero--rc .rc__band--b .rc__run{animation-name:rcRunBack;--d:64s;
  color:color-mix(in srgb,var(--onyx) 11%,var(--linen))  /* aa:decorative */}
/* pause on hover is NOT a flourish — it is how a visitor who wants to READ the
   work can. The band is pointer-events:auto for exactly this. */
.hero--rc .rc__band:hover .rc__run{animation-play-state:paused}
.hero--rc .rc__run span{padding:0 .34em}
.hero--rc .rc__run i{font-style:normal;color:var(--accent);opacity:.5}
@keyframes rcRun{from{transform:translateX(0)}to{transform:translateX(-50%)}}
@keyframes rcRunBack{from{transform:translateX(-50%)}to{transform:translateX(0)}}

.hero--rc .rc__mid{position:relative;z-index:2;text-align:center;padding:0}

/* ⚠⚠ THE PLATE IS THE VARIANT. Do not "simplify" it away.
   It is the opaque ground the bands pass BEHIND, and it is the single piece
   that both standalone extractions of this hero (hero-09.html and
   hero-09-centered.html) dropped — with the result that "the invitations ·
   the replies" runs straight through "Your event shouldn't" and a second
   band cuts across the Start Your Event pill. Verified at 1440x900.
   The radial stop is the shape of the fix: fully opaque linen out to 30%,
   still 72% at 62%, gone by the edge — so the headline sits on solid ground
   and the plate itself has no visible border to give it away. */
/* ⚠⚠ THE PLATE IS A BLUR, NOT A FILL — AND THAT IS THE WHOLE POINT.
   READ THIS BEFORE PASTING THE LAB'S VALUES BACK IN.

   The lab paints an opaque linen radial behind the headline:
       background:radial-gradient(64% 108% at 50% 50%, linen 30%, 72% 62%, 0 100%)
   That works in hero-lab-v2.html because the lab's hero ground is a FLAT
   --linen — a linen patch on linen is invisible, so only the bands disappear.

   PRODUCTION'S HERO GROUND IS NOT FLAT. .hero::before paints a warm-white wash
   at 24%/30%, an accent bloom at 82%/74% and a bordeaux bloom along the bottom;
   .hero::after lays grain over all of it. Any OPAQUE patch replaces that
   gradient with a flat colour, so it reads as a pale rectangle floating in the
   middle of the hero no matter how softly its own edges are feathered — the
   feather controls the EDGE, and the problem is the FILL. Owner saw it
   immediately and called it "the apparent white square"; two rounds of
   retuning the radius and the stops did not remove it, because they could not.

   So the plate stops adding a ground and starts destroying what is behind it:
     backdrop-filter:blur  takes the moving words apart WITHOUT contributing any
        colour of its own, so .hero::before's wash and blooms pass through
        completely unchanged. This is what kills the square.
     the 64% linen veil  is the contrast the blur alone does not buy. Blurred
        grey type still carries luminance, and at blur-only the band was legible
        through "SEE EVENTS WE'VE HANDLED". 64% is the measured floor at which
        it is not. It is ALSO the no-backdrop-filter fallback: where the filter
        is unsupported the veil still runs, and 64% is light enough that the
        patch it leaves is far softer than the 100% fill it replaces.
     the mask  feathers both to nothing before the box ends, so there is no
        edge to find.

   ⚠ THE MASK IS ON THE PSEUDO-ELEMENT, NOT ON .rc__plate. mask-image masks an
   element AND ITS CHILDREN — putting it on the plate itself faded the ends of
   "Your event shouldn't" and ghosted the Start Your Event pill. The pseudo is
   why the headline stays crisp while its backdrop does not. .rc__plate>*
   z-index:1 is the other half: it keeps the content above the pseudo.

   ⚠ inset IS NEGATIVE. The blur must start dissolving the bands OUTSIDE the
   text box, or the haze stops exactly where the letters start and draws its own
   outline. -8%/-4% is what puts the falloff in clear ground.
   ⚠ IF YOU CHANGE THE PLATE'S PADDING, RE-CHECK THIS. inset and the mask are
   percentages of a box sized by its content plus that padding. */
.hero--rc .rc__plate{display:inline-block;position:relative;
  padding:clamp(26px,4vw,54px) clamp(34px,6vw,90px)}
.hero--rc .rc__plate>*{position:relative;z-index:1}
.hero--rc .rc__plate::before{content:"";position:absolute;inset:-8% -4%;z-index:0;
  pointer-events:none;
  background:color-mix(in srgb,var(--linen) 64%,transparent);
  -webkit-backdrop-filter:blur(24px);backdrop-filter:blur(24px);
  -webkit-mask-image:radial-gradient(50% 50% at 50% 50%,#000 0 56%,transparent 100%);
          mask-image:radial-gradient(50% 50% at 50% 50%,#000 0 56%,transparent 100%)}

/* .hero h1 sets margin-top/bottom to pay back a two-column grid that no longer
   exists here, and the !important on its margin-top means only !important can
   answer it. The plate's own padding is the spacing now. */
.hero--rc h1{margin:0!important;letter-spacing:-.032em;line-height:1}
.hero--rc h1 em{font-style:normal;color:var(--bordeaux)}

/* the lede keeps .lede's display serif — it is the promise half of the Evorrah
   message — but centres, and drops .hero__sub's left-aligned max-width. */
/* ⚠ 18.86em, NOT 34ch, AND THAT UNIT IS THE WHOLE CLS BUG. `ch` is the advance
   of the "0" glyph, so a ch-measure is re-computed the moment the real font
   arrives — and Crimson Pro's zero is 12% wider than Times New Roman's. This
   max-width therefore jumped 471.58px -> 527.99px at ~290ms, the lede reflowed
   from three lines to two, and the vertically-centred hero moved: 0.0888 of
   the page's 0.128 CLS, from a rule that never mentions height.
   18.86em is the same measure — 34ch as Crimson Pro actually sets it, at the
   settled 528px — in a unit that depends only on font-size, so it is identical
   before and after the swap. The other 19 ch-measures in this file are all
   below the fold when the swap lands and cost nothing; if one ever surfaces,
   this is the fix. */
.hero--rc .rc__sub{max-width:18.86em;margin:clamp(16px,2.2vh,26px) auto 0;
  font-size:min(var(--fs-d5),3.6svh);line-height:1.34;color:var(--onyx);text-wrap:pretty}
.hero--rc .rc__act{display:flex;justify-content:center;gap:18px;align-items:center;
  flex-wrap:wrap;margin-top:clamp(22px,3vh,32px)}

/* ⚠ THE BANDS ARE CSS ANIMATIONS, so the site-wide reduced-motion block at
   .rv does NOT reach them — that one only zeroes .rv and .rv-line. The lab
   stopped them with `.v *{animation:none}`; this is the same stop, scoped.
   Under reduce the bands hold still and stay legible, which is the point:
   the breadth is still shown, it just stops moving. */
@media(prefers-reduced-motion:reduce){
  .hero--rc .rc__run{animation:none!important}
  /* ⚠ A STOPPED BAND IS A FRAME, SO CHOOSE THE FRAME. With the animation off
     both runs sit at translateX(0): the same first word under the same mask
     fade on both rows, which reads as a reset rather than a pause. These
     offsets are a caught mid-loop frame — one row leads the other and neither
     begins at a list boundary. Any value between 0 and -50% is safe: each list
     is printed twice and the run is wider than any viewport at either offset. */
  .hero--rc .rc__band--a .rc__run{transform:translateX(-18%)}
  .hero--rc .rc__band--b .rc__run{transform:translateX(-37%)}
}
/* On a phone the bands sit BEHIND the plate (§narrow screens below: "at this
   width they are background") and only their ends show either side of the
   headline. They were hidden under reduce for a day on the theory that two
   half-words reads as a print fault; the owner ruled 2026-09-04: "Keep the
   phone bands, just still." So they stay, on the same held frame as the
   desktop, and the offsets above are what keep that frame from being the
   list's first word on both rows. */

/* ---------- narrow screens ----------
   ⚠ THE LAB SHIPPED NOTHING HERE — hero-lab-v2.html has no media query for
   variant 09 at any width, because the lab was a desktop decision environment
   and every variant in it was judged at desktop. Ported as-is, the phone gets
   the desktop band: the clamp floor pins the running type at 30px while the
   headline goes from three lines to FIVE, so the two bands sit inside the
   headline block instead of above and below it, and "the invitations · the
   replies" crosses between "Your event" and "shouldn't". Measured at 375x812.
   The bands stay — the breadth is the message and it survives at any size —
   but at this width they are background, so they take background weight: type
   down to the body-copy range and the tint pulled back so the headline wins
   the contrast outright. */
@media(max-width:640px){
  .hero--rc .rc__run{font-size:clamp(21px,6.4vw,28px)}
  /* ⚠ 18%/14%, RAISED FROM 11%/8% — owner, 2026-09-02: on a phone "the effect
     that we have of text running along doesn't show too much". The tints below
     were correct in isolation and wrong in combination: see the stacking note
     under the short-viewport block. The headline still wins outright — it is
     near-black on linen with the plate's blur between them, against words that
     are still under a fifth of the ink. */
  .hero--rc .rc__band--a .rc__run{color:color-mix(in srgb,var(--onyx) 18%,var(--linen))  /* aa:decorative */}
  .hero--rc .rc__band--b .rc__run{color:color-mix(in srgb,var(--onyx) 14%,var(--linen))  /* aa:decorative */}
  /* ⚠ BACK TO 10%, FROM 22% — owner, 2026-09-02, and the earlier reasoning is
     kept because it is still true as far as it goes: a 12% edge mask on a 375px
     viewport is 45px, narrower than one word, so a word can be cut rather than
     faded. What that reasoning missed is that widening the fade to 22% left a
     clear window of only 250px on a 390px screen — and the headline's blur
     plate covers 350px of the same band, so between them there was almost no
     clear ground for the words to appear in at all. A slightly harder edge is
     the price of the band existing on a phone; rendered and chosen at 390x664
     against the 22% version. */
  .hero--rc .rc__band{
    -webkit-mask-image:linear-gradient(90deg,transparent,#000 10%,#000 90%,transparent);
            mask-image:linear-gradient(90deg,transparent,#000 10%,#000 90%,transparent)}
}

/* ---------- short viewports ----------
   ⚠ MEASURED, NOT GUESSED. The band offsets are clamps whose FLOOR wins on a
   short screen: at 900px tall, 22vh = 198px so band A sits at 50% - 198px; at
   600px tall, 22vh = 132px falls under the 150px floor, so the band stops
   climbing while the headline keeps growing — and the two collide. Below 700px
   of height the plate's ground is doing all the work and the bands have to
   give way, so they move out to the edges and fade back. */
@media(max-height:700px){
  .hero--rc .rc__band--a{top:clamp(84px,13vh,132px)}
  .hero--rc .rc__band--b{bottom:clamp(84px,13vh,132px);top:auto}
  .hero--rc .rc__run{font-size:clamp(26px,3.4vw,44px);opacity:.66}
}

/* ---------- the two fades were multiplying on a real phone ----------
   ⚠ THIS IS THE ONE THAT MADE THE BAND ALL BUT INVISIBLE, and neither block
   above is wrong on its own. Safari's chrome leaves ~664px of page on an
   iPhone 13, so a phone is BOTH narrow and short and takes both rules: the
   narrow rule's pulled-back tint, and then this block's .66 on top of it.
   Measured on the live site 2026-09-02 at 390x664 — 11% ink multiplied by
   .66, inside a 250px clear window, against a plate covering 350px of the
   430px band. The words survived only as fragments at the two screen edges,
   which is exactly what the owner reported seeing.
   The .66 exists for a SHORT DESKTOP WINDOW, where the bands are still at
   full desktop size and tint and are crowding a headline that has nowhere to
   go. On a phone the narrow rule has already done that job — so it is not
   done twice. Everything else in the block above still applies here. */
@media (max-width:640px) and (max-height:700px){
  .hero--rc .rc__run{opacity:1}
}

/* =========================================================
   TAKE A LOOK AROUND — the film, in its own section
   ========================================================= */
/* ⚠ NOT CENTRED, AND NOT ITS OWN TYPE SCALE — owner, "make this the same
   format/size/type as the rest of the website". The first pass centred the
   head and set the subhead on the display scale (--fs-d5), which made it the
   only section head on the page that did not look like the other four. It now
   carries .solve__head in the markup and adds only what is local:
   .d2 for the heading and .lede for the sentence under it are the classes
   every other linen section already uses, so neither size is restated here. */
.tour__head h2{margin-bottom:clamp(14px,2vh,22px)}
.tour__lede{max-width:56ch;color:color-mix(in srgb,var(--onyx) 74%,var(--linen))}

/* the browser chrome. It is furniture, and it is the reason the film reads as
   SOFTWARE rather than as a video: the bar is what says "this is a screen".
   background is a hair off --linen so the chrome separates from the section
   ground without introducing a new colour. */
/* full wrap width, like .stats and the event rail — a capped, centred frame
   under a left-aligned head reads as a different section's furniture. */
.brw{border-radius:14px;overflow:hidden;
  background:color-mix(in srgb,var(--linen) 62%,#fff);
  box-shadow:0 60px 100px -38px rgba(30,11,14,.42), 0 22px 44px -22px rgba(30,11,14,.26)}
.brw__bar{display:flex;align-items:center;gap:8px;
  padding:clamp(10px,1.4vw,14px) clamp(14px,1.8vw,20px);
  border-bottom:1px solid color-mix(in srgb,var(--onyx) 8%,transparent)}
.brw__bar span{width:11px;height:11px;border-radius:50%;
  background:color-mix(in srgb,var(--onyx) 16%,transparent)}

/* ⚠ TWO CLASSES, DELIBERATELY — same trap .hero__film documents. The base
   .film block sits ~900 lines above this one, so a bare .film--tour (one
   class) loses the cascade to it on source order and silently keeps the
   onyx-band radius and shadow. The shadow lives on .brw here anyway; the
   frame itself must go flat and square so it meets the chrome bar cleanly. */
.tour__frame .film{width:100%;border-radius:0;box-shadow:none}

/* ⚠ THE DOUBLE PRINT, AGAIN. The poster JPEG has "EVORRAH · THE CLIENT PORTAL"
   over "Your event, handled." typeset INTO the artwork. The base
   .film furniture then prints .film__mark ("EVORRAH") and .film__sub ("THE
   CLIENT PORTAL · ONE EVENT, END TO END") centred on top of it, so the frame
   carries both phrases twice, overlapping. These three rules existed already
   — scoped to .hero__film. Moving the figure out of that container brought
   the double print straight back, so the scope moves with it.
   .film__len ("WATCH · 2½ MIN") STAYS: it answers the question a viewer
   actually has before pressing play. The accessible name on the button still
   carries "two and a half minutes" either way.
   64px play button, not the base 84px: with no type around it an 84px circle
   sits on the artwork's own headline. */
.tour__frame .film__mark,
.tour__frame .film__sub{display:none}
.tour__frame .film__play{width:64px;height:64px}

/* ⚠ THE POSTER GROUP IS PUSHED OUT OF THE FRAME'S CENTRE, ON PURPOSE.
   .film__poster is a centred column flex box, and the poster ARTWORK has its
   own headline — "Your event, handled." — set across the middle of
   the image. Centred furniture therefore lands exactly on it: the play circle
   sits on the word "beautifully" and "WATCH · 2½ MIN" prints across the line
   under it. Three layers of type and shape in one place.
   The hero carried this same collision and it was tolerable at 620px wide. It
   is not tolerable here: this frame is 1120px and it is the whole point of the
   section, so the artwork's type is nearly twice the size and the overlap is
   the first thing the eye lands on.
   flex-end + a padding-bottom in PERCENT moves the play button into the clear
   band below the artwork's rule mark. Percent rather than px because the
   padding of an absolutely-positioned box resolves against its own WIDTH, and
   the frame is locked to aspect-ratio 16/9 — so one value tracks every width
   without a second clamp to keep in step. */
.tour__frame .film__poster{justify-content:flex-end;padding-bottom:5.5%;gap:14px}

/* "Show me more" kept its .hero__ghost look and its 44px target from the hero;
   .tour__foot only re-places it, centred, under the frame. The <p> wrapper is
   what lets an inline-block link centre without the flex column the hero used. */
.tour__foot{margin-top:clamp(26px,3.6vh,40px)}
.tour__foot .hero__ghost{display:inline-block}

@media(max-width:900px){
  .brw__bar{padding:9px 12px}
  .brw__bar span{width:9px;height:9px}
}

/* ═══ INCLUDED WITH EVERY EVENT · BY RECIPIENT ════════════════════════════════
   The deliverables band under the three plan cards (index.html #included).
   Replaced the four-column SEND · PRINT · SHARE · KEEP accordion on
   2026-09-01; that block is kept verbatim in
   _archive/v15_included_band_20260901/style.css.fragment.

   ⚠ IT LIVES ON #plans, WHICH IS .g-alab. Every colour below is stated against
   #E7E3DD, not against onyx, and every muted value is >= 66% onyx — the floor
   the 2026-08-31 contrast sweep set for this ground. That floor is why the
   values here are copied from the retired .dlv rules rather than re-picked by
   eye: they are already measured against this ground, in both states.

   ⚠ BOTH STATES MUST BE SWEPT. The open state is the one nothing measures by
   default — .rcp__p and .rcp__x are display:none until the toggle fires, so a
   sweep that does not click first reports a band that is half unmeasured.
   contrast-sweep.js clicks every .prow__toggle, which now includes this one.

   ⚠ THE COUNT IS THE CLOSED STATE'S JOB. .rcp__cnt is the only thing that
   holds the arithmetic when shut, so it is stated at full onyx, not muted. */
/* ⚠ TIGHTER THAN THE BAND IT REPLACED, DELIBERATELY. The retired .dlv opened
   with margin-top clamp(56,7vh,96) + padding-top clamp(40,5vh,64) — 160px of
   the 425px it measured at 1440 was the gap above it, not the band. Measured
   old-vs-new on the live page 2026-09-01: the four-column accordion and this
   were within 20px of each other at every width, so the compaction the owner
   asked for had to come out of the spacing and the notes below, not out of the
   content.

   ⚠ NO border-top HERE — THE LADDER'S CLOSING RULE IS THE SEPARATOR
   (owner, 2026-09-04: "there should only be 1 line here"). This band had its
   own hairline while `.prow:last-of-type` (§2282 area) already draws a
   border-bottom to close the ladder directly above it, so the page painted TWO
   hairlines 40px apart with nothing between them — an empty band a reader
   reads as a missing element. Only one of the two could go, and it is this
   one: the ladder's rule closes the last plan row and has to stay, and it sits
   flush under that row, where a separator belongs; ours floated 40px clear of
   everything. The margin/padding above are UNCHANGED, so the gap from the
   remaining rule down to .rcp__h is the same 68–106px it always was. Do not
   put a border-top back on .rcp without first removing the ladder's. */
.rcp{
  margin-top:clamp(40px,4.6vh,64px);
  padding-top:clamp(28px,3.4vh,42px);
}
.rcp__n{
  display:block;font-size:var(--fs-label);letter-spacing:var(--ls-label);
  text-transform:uppercase;font-weight:var(--fw-label);
  color:color-mix(in srgb,var(--onyx) 66%,transparent);
}
/* ⚠ .rcp__h AND .rcp__lede ARE LIVE AGAIN (2026-09-01, owner picked redesign 01
   of app/disclosure-stack-labs.html). They were dead for one evening, while
   this band was the body of a closed .dsc row whose header — "Check what's
   included with every event" — did their job. That row was a category error
   (the full reasoning is in index.html at the band); the band is a stated band
   under the ladder again, so it needs its own head back.
   ⚠ .rcp__h LOST ITS margin-top. It had 14px to clear the .rcp__n eyebrow that
   used to sit above it; the eyebrow has not come back (the band's own hairline
   and its position under the ladder are the label now), so that value was
   pushing the headline off .rcp's own padding-top. */
.rcp__head{max-width:70ch}
.rcp__h{
  font-family:var(--serif);font-weight:300;font-size:var(--fs-d3);
  line-height:1.12;color:var(--onyx);text-wrap:pretty;
}
.rcp__lede{
  max-width:64ch;margin-top:14px;font-size:var(--fs-body);line-height:1.8;
  color:color-mix(in srgb,var(--onyx) 78%,transparent);text-wrap:pretty;
}
.rcp__lede b{color:var(--onyx);font-weight:var(--fw-emph)}

/* Four columns, no cell background and no 1px grid: the retired band needed
   the boxes because each cell was a button. These are not clickable, so a rule
   over each column is enough furniture — and it is what keeps the closed state
   two lines tall instead of four boxes tall. */
.rcp__grid{
  list-style:none;margin:clamp(22px,2.8vh,32px) 0 0;padding:0;
  display:grid;grid-template-columns:repeat(4,1fr);gap:clamp(14px,1.8vw,24px);
}
.rcp__c{
  border-top:2px solid color-mix(in srgb,var(--onyx) 88%,transparent);
  padding-top:16px;
}
/* The buyer is a recipient too, and the one a reader forgets. It gets the
   accent rule; the other three do not. */
.rcp__c--you{border-top-color:var(--accent)}
.rcp__who{
  font-family:var(--serif);font-weight:300;font-size:var(--fs-d5,1.35rem);
  line-height:1.1;color:var(--onyx);
}
.rcp__m{
  margin-top:10px;display:flex;justify-content:space-between;align-items:baseline;gap:10px;
}
.rcp__verb{
  font-size:var(--fs-label);letter-spacing:var(--ls-label);text-transform:uppercase;
  font-weight:var(--fw-label);color:var(--accent);
}
.rcp__cnt{font-size:var(--fs-cap);color:var(--onyx);font-variant-numeric:tabular-nums}
.rcp__p{
  margin-top:12px;font-size:var(--fs-cap);line-height:1.7;
  color:color-mix(in srgb,var(--onyx) 76%,transparent);text-wrap:pretty;
}
.rcp__x{list-style:none;margin:14px 0 0;padding:12px 0 0;display:grid;gap:11px;
  border-top:1px solid color-mix(in srgb,var(--onyx) 12%,transparent)}
.rcp__x li{
  font-size:var(--fs-cap);line-height:1.7;
  color:color-mix(in srgb,var(--onyx) 78%,transparent);
}
.rcp__x li b{display:block;color:var(--onyx);font-weight:var(--fw-emph)}

/* .rcp__foot IS GONE — removed 2026-09-01 (owner: "just seven deliverables").
   It carried "1 + 2 + 1 + 3 = seven deliverables…", a restatement of .rcp__lede
   as a sum. The reasoning is written out at its old position in index.html; do
   not re-add the rules here without reading it first.

   ⚠ ITS margin-top IS NOT LOST — it moved onto .rcp__toggle below, which is why
   that value is a clamp rather than the flat 16px it was. The foot sat between
   the four cards and the "Show more" button and was the only thing separating
   them; deleting the element without carrying its space over leaves the button
   against the bottom edge of the grid. */
/* ⚠ LIVE AGAIN SINCE 2026-09-01 (redesign 01) — it was dead for one evening,
   while the band was the body of a .dsc question and everything shipped .xopen.
   The band is stated under the ladder again and closes to its head, the four
   recipients and the counts, so the button is back and this is its spacing.
   ⚠ IT NEEDS ITS OWN COLOURS AND THAT IS THE TRAP. Every other .prow__toggle on
   this page lives inside a dark plan panel, so the base rule at §.prow__toggle
   paints it linen-80% on a linen-40% border. This one sits on #plans.g-alab
   (#E7E3DD): linen on alabaster measures 1.06:1 — the button would be there,
   focusable, tabbable and INVISIBLE. Onyx-mixed values below, matched to the
   .plans__upgrade pair directly under it (66%/86% onyx), which the 2026-08-31
   contrast sweep set the 62% floor for on this ground.
   ⚠ A ground change invalidates these two lines. If #plans ever leaves g-alab,
   re-derive them; do not assume they travel. */
.rcp__toggle{grid-column:auto;margin-top:clamp(20px,2.6vh,30px)}
#plans.g-alab .rcp__toggle{
  color:color-mix(in srgb,var(--onyx) 78%,transparent);
  /* ⚠ 52%, NOT the 34% that mirrors the dark panels' linen-40% border. This
     underline is the only edge the button has — a non-text boundary, so it owes
     3:1, not the 4.5:1 the label owes. 34% onyx measured 2.14:1 on #E7E3DD and
     46% still only reaches 2.91; 52% is 3.54. Measured on the live page, not
     derived. Anything lighter puts a control on the page with no visible edge. */
  border-bottom-color:color-mix(in srgb,var(--onyx) 52%,transparent);
}
#plans.g-alab .rcp__toggle:hover{color:var(--accent);border-bottom-color:var(--accent)}

/* Two columns before the four names start truncating, one below the phone
   breakpoint. The <940px trap the retired band hit applies unchanged: the plan
   rows already collapse there, so a four-across band is the only wide thing
   left on the section and it overflows the body rather than itself. */
@media (max-width:940px){
  .rcp__grid{grid-template-columns:repeat(2,1fr)}
  /* ⚠ THE ACCENT LEAVES THE RULE AND GOES ON THE COUNT BELOW THIS BREAKPOINT,
     because the rule stops meaning what it means at four columns (owner asked
     2026-09-04: "why is the you hand over with a red line?").
     .rcp__c--you takes the accent on its top rule to say the BUYER is the
     fourth recipient — see the comment at that rule. At four across, the four
     rules sit on one line (measured on the live page at 1280: all four at
     y=1171, the red one last at x=945) and it plainly heads its own card.
     At two across the same rule drops to the second row — 375: red rule at
     x=195 y=1185, with the "Your print shop / YOU HAND OVER" card sitting at
     x=195 y=1094 — so it lands 91px under a DIFFERENT card with nothing in
     between, and reads as a red underline on the print shop. The colour was
     never wrong; the adjacency is, and adjacency is what a stacked grid changes.
     So here the card's own rule goes back to onyx like the other three, and the
     accent moves inside the card onto .rcp__cnt, where it cannot be read as
     belonging to anything else. The COUNT and not the heading: the count is
     already the closed state's load-bearing element (see the note at .rcp__cnt),
     and the verbs are all var(--accent) at every width, so recolouring "YOU
     KEEP" would have marked nothing. Contrast holds — #9D0617 on #E7E3DD is
     6.65:1, the pair the verbs already use on this ground. */
  .rcp__c--you{border-top-color:color-mix(in srgb,var(--onyx) 88%,transparent)}
  .rcp__c--you .rcp__cnt{color:var(--accent)}
}
/* ⚠ TWO COLUMNS ON A PHONE, NOT ONE. Measured 2026-09-01: four stacked
   recipients came to 777px at 390 against the accordion's 757 — the one place
   the new band was WORSE. The four names are two or three short words, so they
   hold a 165px column; the sentences behind the toggle are what needs width,
   and they get the full row when opened. */
@media (max-width:560px){
  .rcp__grid{grid-template-columns:1fr 1fr;gap:18px clamp(12px,4vw,18px)}
  .rcp.xopen .rcp__grid{grid-template-columns:1fr}
}

/* ── .dsc · THE THREE QUESTIONS ─────────────────────────────────────────────
   2026-09-01 (owner). Replaces the #beyond section AND the run-on band under
   the ladder. Everything that used to keep going after .plans__upgrade — the
   "Included with every event" band and the two notes that #beyond held — is
   three closed questions under one hairline. The rules #beyond owned (its
   first-block border reset, and the min-width:760px pair that put its two
   notes in side-by-side columns) went with it: two full-width blocks that are
   closed by default do not need pairing to be compact, and a disclosure body
   that opens into half a screen starves the events note's price table — the
   exact failure the 760px block's own comment warned about at 1240.

   ⚠ THE FIFTEEN `#beyond.g-alab .more*` TWINS ARE GONE TOO, merged back into
   the single `#plans.g-alab` rule each was copied from (above, §"More than one
   event"). That merge is what lets both notes move onto this ground unchanged:
   they are inside #plans now, which is the selector the colours were tuned on.

   ⚠ GROUND IS ALABASTER (#E7E3DD), so every value here is onyx-based and
   >= 66% for anything muted — the floor the 2026-08-31 contrast sweep set. The
   question text is the loudest thing in the closed state and is stated at full
   onyx. The mark beside it is .dsc__x, whose ring is furniture at onyx 25%
   (~1.7:1) and whose two bars are content at full onyx (14.6:1) — do not
   "fix" the ring to the muted floor by dragging the bars down with it.
   OPEN, the disc fills --bordeaux: 12:1 against this ground. It is the one
   filled mark in the stack, and a sweep will measure it as linen-on-bordeaux
   (13.9:1), NOT as accent-on-alabaster, which is what the chevron used to be.

   ⚠ BOTH STATES MUST BE SWEPT. A closed row measures three words. Open it —
   `document.querySelectorAll('.dsc__q').forEach(b=>b.click())` — before running
   contrast-sweep.js, or the three blocks inside are entirely unmeasured. */
/* THE STACK'S LABEL. Added 2026-09-01 with redesign 01, when the included band
   left this stack and what remained was two CONDITIONS rather than a fact and
   two conditions. It sits ABOVE .dsc's own top hairline, so the rule reads as
   the label's underline rather than as a divider with nothing to divide.
   ⚠ The margin below is small on purpose: .dsc keeps its own margin-top, and
   the two together are the gap. Do not add a third value here. */
.dsc__lab{margin-bottom:2px}
.dsc{
  margin-top:clamp(30px,4vh,46px);
  border-top:1px solid color-mix(in srgb,var(--onyx) 14%,transparent);
}
.dsc__row{border-bottom:1px solid color-mix(in srgb,var(--onyx) 14%,transparent)}
.dsc__qh{margin:0}
.dsc__q{
  display:flex;align-items:center;justify-content:space-between;gap:20px;width:100%;
  padding:clamp(20px,2.8vh,28px) 0;
  background:none;border:0;text-align:left;cursor:pointer;color:var(--onyx);
  font:inherit;-webkit-appearance:none;appearance:none;
}
.dsc__qt{
  display:block;min-width:0;font-family:var(--serif);font-weight:300;
  font-size:var(--fs-lede);line-height:1.35;text-wrap:pretty;
}
/* THE MARK IS RIGHT-ALIGNED, ON THE ROW'S OWN EDGE (owner, 2026-09-01). It has
   moved twice in one day: off its own line under the question, to beside the
   question, to here. justify-content:space-between + gap:20px is the .fq__q
   rule verbatim, because the mark is now .fq__x's twin (below) and the two
   stacks have to line their controls up on the same edge or the page reads as
   two different components. ⚠ THE gap IS A FLOOR, NOT THE SPACING — it only
   does anything once a question grows long enough to reach the disc, which is
   why .dsc__qt keeps its min-width:0.
   ⚠ IT IS NO LONGER A CHEVRON. The 9px two-border chevron that used to be
   drawn here was replaced (owner) by the plus-in-a-circle the FAQ already
   uses — .dsc__x, defined with .fq__x under §FAQ, because it is one control
   under two names and not a copy. It needs a real element, so .dsc__q now
   carries <span class="dsc__x" aria-hidden="true"> after .dsc__qt; the button
   is still what announces the state, via aria-expanded.
   ⚠ THE ELEMENT IS DECORATIVE AND MUST STAY aria-hidden. It draws a + that
   rotates into an ×, which a screen reader would otherwise read as stray
   punctuation on top of an already-labelled button.
   ⚠ THE OPTICAL translateY IS GONE WITH THE CHEVRON. A rotated square hung its
   corner below the text's centre line and needed correcting; a circle is
   symmetrical about its own centre, so align-items:center on .dsc__q is now
   the whole of the vertical alignment. Do not reintroduce an offset.
   ⚠ HOVER NO LONGER PREVIEWS THE OPEN COLOUR. The chevron went --accent on
   hover because a hairline needed the help; a 30px disc that fills bordeaux on
   open does not, and previewing the filled state on hover made the closed rows
   flicker under a mouse crossing the stack. */
.dsc__q:focus-visible{outline:1px solid var(--accent);outline-offset:4px}

/* Closed is the state the page ships in, and the state it keeps with JS off —
   same contract as the .xtra toggles above. */
.dsc__body{display:none}
.dsc__row.dopen .dsc__body{display:block;padding-bottom:clamp(34px,5vh,60px)}

/* The blocks inside each carry their own top hairline + top margin, drawn back
   when they hung off the bottom of something. The row's own border is that line
   now, so a body that kept them would print a rule 8px under the one the reader
   just clicked.
   ⚠ .rcp IS NO LONGER A .dsc__body CHILD (2026-09-01, redesign 01) — it is a
   stated band above .plans__upgrade and its own hairline is what separates it
   from the ladder, so this rule must not reach it. The selector is kept for
   .more and left listing .rcp because the suppression is still correct for any
   future block put in a body; nothing matches the .rcp half today. */
.dsc__body > .more,
.dsc__body > .rcp{margin-top:0;border-top:none;padding-top:0}
@media (prefers-reduced-motion:reduce){.dsc__x,.dsc__x::after{transition:none}}

/* ═══ SETS — curated selections, on /collection ═══════════════════════════
   ⚠ ON .g-alab (#E7E3DD), like the plans band. Muted values stay >= 66% onyx,
   the floor the 2026-08-31 contrast sweep set for this ground. */
.sets{
  margin-top:clamp(44px,6vh,72px);
  padding-top:clamp(36px,4.5vh,56px);
  border-top:1px solid color-mix(in srgb,var(--onyx) 14%,transparent);
}
.sets__n{
  display:block;font-size:var(--fs-label);letter-spacing:var(--ls-label);
  text-transform:uppercase;font-weight:var(--fw-label);
  color:color-mix(in srgb,var(--onyx) 66%,transparent);
}
.sets__h{
  font-family:var(--serif);font-weight:300;font-size:var(--fs-d4);
  line-height:1.15;margin-top:14px;color:var(--onyx);
}
.sets__lede{
  max-width:62ch;margin-top:16px;font-size:var(--fs-body);line-height:1.8;
  color:color-mix(in srgb,var(--onyx) 78%,transparent);text-wrap:pretty;
}
.sets__grid{
  list-style:none;margin:clamp(26px,3.5vh,40px) 0 0;padding:0;
  display:grid;grid-template-columns:repeat(3,1fr);gap:1px;
  background:color-mix(in srgb,var(--onyx) 12%,transparent);
  border:1px solid color-mix(in srgb,var(--onyx) 12%,transparent);
}
/* Flex column so the slot-cost line pins to the bottom: the sets hold 3–5
   pieces, so without this the cost sits at a different height in every card of
   a row and the one number a reader must not miss is the one that never lands
   in the same place twice. */
.sets__c{background:var(--alabaster);padding:24px 22px;display:flex;flex-direction:column}
.sets__c h3{
  font-family:var(--serif);font-weight:300;font-size:var(--fs-d5,1.35rem);
  line-height:1.2;color:var(--onyx);
}
.sets__b{
  margin-top:8px;font-size:var(--fs-cap);line-height:1.7;
  color:color-mix(in srgb,var(--onyx) 74%,transparent);text-wrap:pretty;
}
.sets__p{list-style:none;margin:16px 0 0;padding:0}
.sets__p li{
  font-size:var(--fs-cap);line-height:1.6;padding:7px 0;
  border-top:1px solid color-mix(in srgb,var(--onyx) 11%,transparent);
  color:color-mix(in srgb,var(--onyx) 80%,transparent);
}
/* The slot cost. Deliberately the loudest thing in the card after the name —
   a reader who thinks a set is a bundle deal has misread the offer. */
.sets__c .sets__n{
  margin-top:auto;padding-top:12px;
  border-top:1px solid color-mix(in srgb,var(--onyx) 20%,transparent);
  color:color-mix(in srgb,var(--onyx) 84%,transparent);text-transform:none;
  letter-spacing:.02em;font-size:var(--fs-cap);
}
.sets__foot{
  margin-top:22px;font-size:var(--fs-cap);line-height:1.7;
  color:color-mix(in srgb,var(--onyx) 70%,transparent);max-width:70ch;
}
@media (max-width:940px){ .sets__grid{grid-template-columns:repeat(2,1fr)} }
@media (max-width:560px){ .sets__grid{grid-template-columns:1fr} .sets__c{padding:20px 18px} }

/* ══════════════════════════════════════════════════════════════════════════
   THE MOBILE LAYER — phones first, then small tablets
   ──────────────────────────────────────────────────────────────────────────
   Everything above this line was drawn at desktop width and narrowed with
   per-component media queries. That got the site to ZERO horizontal overflow
   at 320px (verified across all 79 pages), which is the hard part — but a page
   that merely fits a phone is not a page designed for one. This block is the
   phone's own layer: its type scale, its target sizes, its safe areas, and the
   handful of scenes that were built for a mouse and a wide viewport.

   ⚠ WHY IT IS LAST IN THE FILE. Almost every page carries its own <style>
   block in <head>, which the browser reads AFTER this stylesheet — so a
   page-level rule beats an equal-specificity rule here no matter where in
   style.css it sits. Where this layer has to overrule one of those (the events
   filters, the event-page back link, gift's chips, begin's fields) the selector
   is prefixed with `body` to win on specificity rather than order, and the
   comment says so. Everything else here only ever overrides style.css itself,
   where being last is enough.

   ⚠ WIDTH, NOT `pointer:coarse`. The rest of this file switches on width, the
   audit rig drives widths, and a narrow desktop window deserves the same
   layout a phone gets. A hover-capable device that is 375px wide is still a
   375px-wide reading experience.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── the phone type scale ────────────────────────────────────
   The display sizes (--fs-d1 … --fs-lede) are already clamps with a vw term,
   so they scale themselves. The four SMALL tokens are flat pixel values and
   did not: 15px body / 13px caption / 11px label were chosen at arm's length
   on a monitor and are read at 30cm on a phone.

   --fs-body 15 → 16 is also the iOS zoom threshold (see the form block below),
   so the body copy and the fields it sits beside now agree on a size.

   ⚠ EVERY ONE OF THESE MOVES UP, NEVER DOWN. The contrast stops recorded all
   over this file were measured against the SMALL end of each token, and WCAG's
   thresholds only ever loosen as text grows — so a bigger token cannot
   invalidate a measured ratio. Shrinking one could, and would need the sweep
   re-run (app/contrast-sweep.js). */
@media (max-width:900px){
  :root{
    --fs-read:  17px;
    --fs-body:  16px;
    --fs-cap:   14px;
    --fs-label: 12px;
    --fs-label-lg: clamp(12px,1.2vw,14px);
  }
  /* The two flat 11px values that are NOT tokens, and one 9.5px source line.
     .ptoast (5.6px) and .mod-tier (9.5px) are deliberately left alone: the
     first is type inside a miniature device mockup, where it is a picture of
     an interface rather than text to read, and the second is only worn by the
     burgundy colour rig. */
  .capture__note,.capture__note a,.coll__none{font-size:12px}
  .coll__ex a{font-size:12px}
  .solve__src{font-size:11px}
  /* ⚠ A vw CLAMP RUNS THE WRONG WAY FOR A PHONE. `clamp(11px,.78vw,12.5px)`
     hands the SMALLEST screen the SMALLEST text — the opposite of what the
     viewport term is normally reached for — and this is a running sentence of
     real copy under the plan ladder ("Not sure which? Start lower. Moving up
     costs exactly the difference."), not a label. It is the only font-size on
     the site with a floor under 12px that a visitor is meant to read; the rest
     are type inside the miniature portal mockups. */
  .plans__upgrade{font-size:12.5px}
}

/* ── 2.5.5 Target Size (Enhanced) · the thumb ─────────────────
   The site already cleared WCAG 2.2's 24px AA floor (see the .prow__see rule
   above). 24px is a conformance minimum, not a comfortable phone target: Apple
   asks for 44pt and Material for 48dp, and the measured reality here was a
   41px primary CTA, a 42px newsletter row, a 36px disclosure and a 16px column
   of footer links on all 78 pages.

   min-height rather than padding wherever the control is already a flex box
   that centres its own text, so nothing reflows and no baseline moves. */
@media (max-width:900px){

  /* the primary pill, everywhere it appears */
  .btn-cta{min-height:48px}

  /* the newsletter row in the footer of all 78 pages */
  .capture__in,.capture__go{min-height:48px}

  /* Standalone "See …" / "The full event →" links. These draw their own
     underline with border-bottom, so the height has to come from padding-top
     alone (the rule above this block explains why) — but padding-top on an
     inline-block also pushes the NEXT block down by the same amount, so the
     margin is pulled back by what the padding added. The link grows; the page
     does not move. */
  .prow__see,.edet__go,.hero__ghost,.pcap a,.ecard__deep,.hcard__more{
    padding-top:20px;margin-top:-15px;
  }
  /* Same treatment, its own number: this link's line box is 17px where the
     family above is 24px (it is set at caption size on a tight leading inside
     the plan strip), so the shared 20px would land it at 37. The value has to
     be measured per line box, not shared — 27 + 17 = 44. */
  .coll__lnk a{padding-top:27px;margin-top:-23px}

  /* .coll__ex's links are BORDERED PILLS, not underlined links, so they take
     the height the ordinary way — centred in a box — rather than borrowing it
     from padding above the text, which would have drawn the pill's own border
     20px clear of the words inside it. */
  .coll__ex a{display:inline-flex;align-items:center;min-height:44px}

  /* Disclosure toggles: "Show more" on the plan rows, and the per-event
     "What we handle" row on /events. */
  .prow__toggle{min-height:44px;align-items:center}

  /* The footer's four link columns. The links were 16px tall with 11px of
     dead space between them — the pitch already reserved the room, the target
     just never claimed it. Making the anchor a block and moving the row gap
     INTO it turns 16px of target and 11px of gap into a 44px target with the
     same visual rhythm. */
  .foot li{margin-bottom:0}
  .foot li a{display:flex;align-items:center;min-height:44px}
  .foot__mark{display:inline-flex;align-items:center;min-height:44px}

  /* The legal row under the footer rule (Terms · Privacy · Text messages …) */
  .foot__base a{display:inline-flex;align-items:center;min-height:44px}

  /* Consent bar. 11px of padding either side of a 12px label is 42px. */
  .ck__btn{min-height:48px}

  /* ⚠ `body`-prefixed: these four selectors are defined in page-level <style>
     blocks, which the browser reads after this file. Specificity, not order,
     is what wins here. */
  body .ehero__back{display:inline-flex;align-items:center;min-height:44px}
  body .filters button{min-height:44px}
  body .edet__t{min-height:44px}
  body .chips .chip{min-height:44px}
  body .bk-form .btn-cta,body .bk-su{min-height:48px}
}

/* ── the checkboxes ──────────────────────────────────────────
   18px was set as "hard to hit with a thumb, but the label is the real
   target". On a phone it is worth making both true: the box grows to 22px and
   the label row it lives in is given a full 44px to be tapped anywhere along.
   ⚠ NOT the radio inputs inside .bk-plan — those are the 1px visually-hidden
   inputs behind begin's plan cards, and sizing them would drag them on screen.
   `.bk-terms input` and `label.check input` name the real ones. */
@media (max-width:900px){
  label.check input[type=checkbox],
  label.check input[type=radio],
  body .bk-terms input[type=checkbox]{width:22px;height:22px}
  label.check{min-height:44px}
  body .bk-terms{align-items:flex-start;min-height:44px}
}

/* ── the fields · iOS zoom-on-focus ──────────────────────────
   ⚠ THIS IS THE ONE RULE IN THIS FILE THAT IS NOT ABOUT LOOKS. Mobile Safari
   zooms the whole page in when you focus a field whose font-size is under
   16px, and does not zoom back out when you leave it — the visitor is left on
   a page that now scrolls sideways, mid-form. Every field on the site tripped
   it: the enquiry form at 15px, begin's date and occasion at 15px, and the
   newsletter input at 13px on all 78 pages.

   16px is a threshold, not a preference, so this is a flat value rather than a
   token — --fs-body happens to be 16px on phones too, but a later change to
   that token must not quietly reintroduce the zoom.

   The :not() chain excludes the controls where font-size means nothing
   (checkboxes, radios, hidden inputs). `body` prefix: begin's fields are
   styled in that page's own <style> block. */
@media (max-width:900px){
  body input:not([type=checkbox]):not([type=radio]):not([type=hidden]),
  body textarea,
  body select{font-size:16px}
  /* The field boxes grow with their text; the enquiry form's four inputs were
     47px and land at 50px, which is where they should have been. */
  body .efm input,body .efm select,body .bk-form input,body .bk-form select{min-height:50px}
}

/* ── safe areas · notches and home indicators ────────────────
   Three fixed / full-bleed surfaces sit against the edges of the screen and
   none of them knew about an iPhone's insets: the nav bar and the consent bar
   against the home indicator, and .wrap against the notch in landscape.
   max() rather than an added value — the inset is 0 on every device that does
   not have one, and `max()` then resolves to the gutter that was already
   there, so nothing changes anywhere else. */
.wrap{
  padding-left:max(var(--gutter),env(safe-area-inset-left));
  padding-right:max(var(--gutter),env(safe-area-inset-right));
}
.nav,.mnav{
  padding-left:max(var(--gutter),env(safe-area-inset-left));
  padding-right:max(var(--gutter),env(safe-area-inset-right));
}
.ck{
  padding-bottom:max(16px,env(safe-area-inset-bottom));
  padding-left:max(clamp(16px,4vw,56px),env(safe-area-inset-left));
  padding-right:max(clamp(16px,4vw,56px),env(safe-area-inset-right));
}

/* ── the consent bar on a phone ──────────────────────────────
   Three lines of copy plus a button row took 160px off an 812px screen — a
   fifth of the first view, over the top of whatever the page opened with, and
   on /begin directly over "Continue to secure payment". The bar cannot
   move (it is the first thing that must be answered) so it gets smaller: the
   copy runs at caption size on a tighter leading and the buttons sit on one
   row, which is what the ≤700px rule already intended. */
@media (max-width:700px){
  .ck{gap:12px;padding-top:14px}
  .ck p{font-size:var(--fs-cap);line-height:1.45}
}

/* ── 1.4.10 Reflow · the platform chips, re-measured ─────────
   .pf-chip keeps `white-space:nowrap` and lifts it below 400px, because the
   longest chip ("Included with Whole Event and above") measured 313px at the
   11px label — wider than a 320px content box, but comfortable at 375 and up.
   The phone label is 12px now, and 12px plus .2em of tracking took that same
   chip to 404px: it began overflowing its own `max-width:100%` box at 414px
   too, on /platform/personalisation and /platform/animation.

   ⚠ `max-width` CANNOT SAVE A nowrap BOX. It caps the element, and the text
   then runs out of it — which is why this surfaced as a CLIPPED element rather
   than as page overflow (body{overflow-x:hidden} hid the rest). The nowrap has
   to be lifted, not the width capped.

   Lifted across the whole small-screen range rather than at a re-measured
   pixel, so the next change to --fs-label cannot walk it off the end again. */
@media (max-width:900px){
  .pf-chip{white-space:normal;text-align:center}
}

/* ── 1.4.10 Reflow · the work-tile caption at 320px ──────────
   .wtile__c is a flex row of a name (b) and a label (i, flex:none). The name
   had no `min-width:0`, so it refused to shrink under its own longest word and
   pushed the label out of the tile — the one element the 320px sweep still
   reported clipped, on 53 event pages. */
.wtile__c b{min-width:0;overflow-wrap:anywhere}

/* ══════════════════════════════════════════════════════════════════════════
   THE 99 → 1 PINNED SCENE, ON A PHONE AND WITH REDUCED MOTION
   ──────────────────────────────────────────────────────────────────────────
   ⚠ THIS SECTION WAS BROKEN, NOT MERELY CRAMPED, FOR EVERY REDUCED-MOTION
   VISITOR. motion.js's reduced branch sets `shift.style.height='auto'` and
   promotes .shift__final to `position:static` so the payoff frame is readable
   without scroll-driving it. But .shift__pin is `display:flex` with the
   default `flex-direction:row` — so the promoted frame did not stack under the
   99, it became its SIBLING IN A ROW. Measured at 375px: .shift__stage sat at
   x=-81 and .shift__final was squeezed to 161px starting at x=294, both of
   them sliced off by the pin's own overflow:hidden. Half a numeral on the left,
   a column of two-letter word fragments on the right.

   The fix is CSS, not JS, because JS is what writes those inline styles and an
   inline style cannot be overruled by the rule that has to coexist with it.
   Turning the pin into a normal block in this state makes the promoted frame
   what it was always meant to be: the next thing down the page.
   ══════════════════════════════════════════════════════════════════════════ */
@media (prefers-reduced-motion:reduce){
  /* !important because the phone block just below re-sets 240vh at equal
     specificity and later in the file; motion.js writes height:auto inline in
     its reduced branch, but the page must read right with the script off too. */
  .shift{height:auto!important}
  /* ⚠ ONE COLUMN, THREE FRAMES, TWO CONNECTORS — not three slabs. The scene's
     argument is a sequence (99 → 1 → the ratio flipped) and the animation
     carried the "→" by shrinking the numeral under the reader's scroll. With
     it gone the frames were stacked with 10–12vh of bare ground between each,
     and the sequence read as three unrelated bordeaux sections (measured
     2026-09-04: 1,654px of scene at 375px, 1,800px at 1280px). The connector
     is the flip ledger's own hairline (22% linen) stood on end between the
     frames — the mark the closing frame already uses to separate LESS from
     MORE, so it reads as "then" rather than as a new device. */
  .shift__pin{position:static;height:auto;overflow:visible;display:block;
    padding:clamp(72px,11vh,130px) 0 clamp(64px,10vh,120px)}
  .shift__stage{height:auto;padding:0 5vw;gap:clamp(16px,2.5vh,28px)}
  /* The payoff frame and the closing frame both become ordinary blocks. JS
     sets opacity/position inline on .shift__final only, so .shift__handled
     needs both here or it stays at the opacity:0 the animation parks it at —
     and a reduced-motion visitor would scroll from the problem straight to the
     CTA, with the answer in between never shown. */
  .shift__handled,.shift__final{
    position:static;opacity:1;transform:none;pointer-events:auto;
    inset:auto;padding:0 var(--gutter);margin-top:clamp(40px,6vh,72px);
  }
  .shift__handled::before,.shift__final::before{
    content:"";display:block;width:1px;height:clamp(40px,7vh,72px);
    margin:0 auto clamp(20px,3vh,32px);
    background:color-mix(in srgb,var(--linen) 22%,transparent);
  }
  /* .shift__final is a column with its own gap, which already spaces the
     hairline from the eyebrow under it. */
  .shift__final::before{margin-bottom:clamp(6px,1vh,10px)}
  /* Decorative only: the same words are set as real content in .chaos__words
     two sections up. Without the animation they are twelve absolutely-placed
     phrases scattered over the frame at 55% opacity, overlapping the numeral
     and each other. */
  .shift__orbit{display:none}
}

/* On a phone WITH motion, the scene is not broken — it is just long. 340vh is
   3.4 full flicks of a thumb spent inside one scene, most of them on a screen
   that is holding still. The progress is derived from the section's own height
   in motion.js (`-r.top / (r.height - innerHeight)`), so shortening it here
   re-times the whole scene automatically and needs no change to the script. */
@media (max-width:700px){
  .shift{height:240vh}
  /* The numeral is sized against the viewport's SHORT side on a phone
     (min(56vw,46vh) resolves to 46vh in portrait, ~210px) which reads small
     for the one number the scene is about. On a portrait phone the width is
     the constraint worth using. */
  .shift__num{font-size:min(62vw,38vh)}
  /* The chaos words are set at --fs-lede, which is a reading size on a desktop
     and a headline size beside a 240px numeral on a phone: "communications"
     measured 119px of a 390px screen, so two words on the same band touched.
     Stepped down one size and given a ceiling, so the longest of the twelve
     ("communications", 14 characters) cannot reach the gutter however narrow
     the screen gets. Paired with the right-anchoring in motion.js — the seed
     decides where a word sits, this decides how wide it is allowed to be.
     16px, not the 13px caption size: these words carry the whole "chaos"
     half of the scene and a caption size reads as a footnote next to a
     240px numeral. */
  .shift__orbit span{font-size:var(--fs-read);max-width:calc(100vw - 40px)}
}

/* ── three cards never render as two-plus-one ────────────────
   A 3-up card grid stepping down to 2 columns leaves ONE card on the second
   row and a hole the size of the card beside it. On a 768px tablet that hole
   is half the block — the single clearest tell that a layout was drawn wide
   and squeezed rather than designed for the screen it is on. Measured on
   /security, /gift, /pricing, /guests (the retention cards,
   the two gift trios, the plan cards) and on the platform steps.

   Three columns → one column, skipping the two-column step entirely. An odd
   card at the end of a NINE-card gallery is just how a gallery ends; an odd
   card out of three is a broken row.

   ⚠ `body`-prefixed: .cards--3 and its ≤1000px step are declared in each
   page's own <style> block, which the browser reads after this file, so an
   equal-specificity rule here would lose on order however late it sits.
   .pf-steps--3 and .coll__rules are style.css's own and need no boost. */
@media (max-width:1000px){
  body .cards--3{grid-template-columns:minmax(0,1fr)}
  .pf-steps--3{grid-template-columns:minmax(0,1fr)}
}
/* auto-fit with a 180px floor put three rules into two columns at 414px. The
   floor is what decides the column count, so it moves rather than the rule. */
@media (max-width:620px){
  .coll__rules{grid-template-columns:minmax(0,1fr)}
}

/* ── the one standalone link the target sweep still names ────
   /begin's "What each one includes, in full →" is the only text of its
   paragraph, so the inline-link exception to 2.5.8 does not cover it the way
   it covers "Privacy Policy" inside the consent sentences. Same padding-above
   treatment as the .hero__ghost family, for the same reason: the link draws
   its own underline and the height cannot come from below it. */
@media (max-width:900px){
  .bk-help a{display:inline-block;padding-top:20px;margin-top:-15px}
}

/* ── targets that are the only thing on their line (WCAG 2.5.8, all widths) ──
   A standalone link or button draws its own box, so its height must reach 24px
   itself: the inline-link exception covers "Privacy Policy" inside a sentence,
   not a label-sized link sitting alone. Measured 2026-09-02 with a Playwright
   sweep of every shipping page at 320–1280: the suite pages' "← Events, Handled"
   was 18px, the home band's "Show more" 23px. Padding, not font size — the type
   system is the brand's and stays. Footer and nav lists pass through the
   spacing exception (no other target within a 24px circle), measured the same
   day, and are not touched. */
body .ehero__back{padding:4px 0;margin-bottom:24px}
.rcp__toggle{min-height:24px}

/* The two links that are the only text of their line at EVERY width — the
   collection's "What it includes →" and the checkout's "What each one includes,
   in full →" — get the same treatment here unscoped: 8px of padding above with
   the same negative margin, so the box reaches 24px and nothing moves. The
   larger phone-only values further down still win below 900px. */
.coll__lnk a,.bk-help a{display:inline-block;padding-top:8px;margin-top:-8px}

/* ── a tile whose frame painted nothing keeps its name ──────────
   work.js adds .wtile--still when the sandboxed frame drew no pixel (the
   animated invitations and previews: their card is drawn by script, and the
   sandbox runs none). The placeholder stays, and says the piece opens in full. */
.wtile--still .wtile__ph{opacity:1!important;flex-direction:column;gap:8px;padding:18px;text-align:center}
.wtile--still .wtile__ph::after{content:"Opens in full";font-size:var(--fs-label);letter-spacing:var(--ls-label);text-transform:uppercase;opacity:.62}

/* ── tile captions on a phone ─────────────────────────────────────
   Two tiles across a 375px screen leave each caption ~120px beside its
   "Open →" label, and overflow-wrap:anywhere then split "Envelope" and
   "Animated Invitation" mid-word (measured 2026-09-02). Below 600px the label
   drops under the name, so the name has the whole tile to wrap at a word. */
@media (max-width:600px){
  .wtile__c{flex-direction:column;align-items:flex-start;gap:6px}
  .wtile__c b{overflow-wrap:break-word}
}

/* ── one desktop line break ───────────────────────────────────────
   "50+ events — weddings, milestone birthdays, galas,<br>baptisms…" balances
   two lines on a desktop and strands "galas," alone on a phone. */
@media (max-width:700px){
  .evh__count br{display:none}
}

/* ── the three plan cards between 1001 and 1080px ──────────────
   `.cards--3{repeat(3,1fr)}` with three cards whose CTA is one no-wrap line
   ("Begin Fully Handled →", 264px) plus 28px of padding a side: at 1024px the
   1fr tracks cannot shrink below that and the third card ran to 1062px on a
   1024px screen — the one page-level overflow the sweep found. Below 1000px the
   cards already stack (the three-never-two-plus-one rule above); this closes
   the 80px gap up to the navigation breakpoint. `body`-prefixed for the same
   reason as that rule. */
@media (max-width:1080px){
  body .cards--3{grid-template-columns:minmax(0,1fr)}
}

/* ── the hero headline on a phone ────────────────────────────
   Five lines of a centred serif headline wrapping wherever the box happened to
   end put "second" alone on line four. `balance` is what this property is for
   and Safari, Chrome and Firefox all ship it; anything that does not simply
   wraps the way it does today. */
@media (max-width:700px){
  .hero h1{text-wrap:balance}
}

/* ── the roll call, top and bottom, on a phone ────────────────
   Measured 2026-09-02 at 390x664 — an iPhone 13 in Safari, the screen the
   owner photographed. The hero centres its content inside its own padding
   box, and below 1000px that box was 92px of top padding against 90px of
   bottom, which looks balanced and is not: 76px of the top padding is spent
   under the fixed nav bar (.nav{height:76px}, which paints OVER the hero), so
   the air the eye actually sees was 22px above the headline and 96px under
   the CTA. The block sat jammed against the burgundy bar with a seventh of
   the screen empty beneath it.

   Symmetry has to count the bar. With padding-top = nav + G and
   padding-bottom = G the two visible gaps come out equal for ANY G, because
   justify-content:center splits whatever slack is left between them; G is
   only the FLOOR — what survives when the content is taller than the screen
   and the hero grows past 100svh, which it still does at 320x568.

   Measured, before -> after (air above the plate / below it):
     320x568    16 / 90  ->  24 / 24   (fold overrun 76px -> 18px)
     375x635    16 / 90  ->  45 / 45   (overrun 15px -> none)
     390x664    22 / 96  ->  51 / 51
     430x745   102 / 176 -> 139 / 139
     768x1024  257 / 307 -> 282 / 282

   ⚠ THE BANDS NEED NO RE-AIMING, and that is worth knowing before anyone
   re-tunes them to match. .rc__band is absolute inside .rc, which IS the
   content block, so both bands travel with the plate: their distance from
   the headline and from the CTA pill is the same pixel value after this as
   before, at every size above.

   The two inner gaps move for the same reason the band offsets do — they are
   clamps whose vh term falls under its floor on a phone (2.2vh = 14.6px and
   3vh = 19.9px at 664), so the screen carrying the TALLEST headline (five
   lines here against three on a desktop) was being given the site's SMALLEST
   separation under it. Floors raised only; the ceilings are untouched, so
   nothing above this breakpoint moves. */
@media (max-width:1000px){
  .hero--rc{
    --rc-air:clamp(24px,4vh,40px);
    padding-top:calc(76px + var(--rc-air));
    padding-bottom:var(--rc-air);
  }
  .hero--rc .rc__sub{margin-top:clamp(24px,2.2vh,26px)}
  .hero--rc .rc__act{margin-top:clamp(30px,3vh,32px)}
}
