/* ============================================================
   ROMAN WEST — design stylesheet
   Tokens live in theme.json. This file carries everything theme.json
   cannot express: the thorn compositing, the rough edge, scroll-snap,
   the fluid clamps and the responsive bands.

   THE DESCENT is a body class, not an attribute — WordPress has no
   per-page equivalent of <html data-theme>. functions.php adds
   .rw-bone or .rw-dark via the body_class filter.

   DO NOT hardcode a colour to fix contrast. Every mode-dependent value
   goes through a token or it breaks in the other mode.
   ============================================================ */

/* Chosen type system: Remboy display, Auria Sans UI, Karrik body.
   Auria replaced Basteleur on 2026-08-12, once Ari supplied the licensed
   commercial files. Basteleur was only ever the stand-in — Auria Sans is
   the face Ari named for nav and form back in the 2026-08-01 round, and it
   could not be used because nothing had been sent.

   ⚠️ THE FALLBACK STACK CHANGED FROM SERIF TO SANS, and that is not
   cosmetic. Basteleur is a serif and fell back to Georgia. Auria is a
   sans, so leaving the old fallback would have rendered a serif in the
   window before the webfont lands, and permanently for anyone it fails to
   reach. The whole UI layer would silently look like the wrong typeface.

   Shipped as the VARIABLE font rather than static cuts. One 49.9KB file
   covers weights 100-950, against 92.8KB for the two fixed Basteleur cuts
   it replaces — smaller and strictly more capable. It also removes the
   synthesized-bold risk that Remboy carries: any weight the CSS asks for
   is a real instance, so nothing is faked. */
:root{
  --body-font: var(--wp--preset--font-family--karrik, 'Karrik', Helvetica, sans-serif);
  --ui-font:   var(--wp--preset--font-family--auria, 'Auria Sans 2.0', Helvetica, Arial, sans-serif);

  /* Regular. A variable so the whole UI layer retunes from one line, which
     is worth having on a face this new to the build. Went 500 -> 400 on
     2026-08-13 at Ross's call, after the 1.75x size increase had landed —
     larger type needs less weight to hold its own.

     Either value is a real instance rather than a synthesized one, because
     Auria ships variable across 100-950. That is NOT true of Remboy, which
     is Regular only; .mode pins its own literal font-weight:400 for exactly
     that reason and is unaffected by this line. */
  --ui-weight: 400;

  /* ⚠️ MULTIPLIES EVERY UI FONT-SIZE. 28 declarations across the file are
     written as calc(<base>rem * var(--ui-scale)), so this one number moves
     the whole Auria layer. Ross asked for 2.5x on 2026-08-12.

     It is a multiplier rather than 28 rewritten literals for two reasons.
     Several of these selectors are declared more than once — .rw-arrow-label
     five times, .dial .of three, .step label twice — and scaling all copies
     by the same factor keeps whichever one currently wins still winning,
     just larger. And a face this new to the build will get retuned; the
     previous pass had to be corrected twice because edits at source were
     silently overridden by later duplicates.

     2.5 -> 1.75 on 2026-08-12. Ross: "way too big. Let's cut that increase
     in half." Halving the INCREASE, not the scale: 1.0 -> 2.5 is a rise of
     1.5, so half of it lands at 1.75. Nav 13px -> 23px rather than 33px.
     Whole exercise is now one number, which is the point of the calc(). */
  --ui-scale: 1.75;
}



/* ---------- BONE (light) — Home, Lore ---------- */
:root,
body.rw-bone{
  --bg:#E9E4DA;
  --fg:#17140F;
  --accent:#7B2116;          /* oxblood */
  --muted:rgba(23,20,15,.55);
  --rule:#17140F;
  --card-bg:#E9E4DA;
  --card-bd:#17140F;
  --scrim:rgba(233,228,218,.94);   /* quiet zone behind reading matter */

  /* thorns are now a real alpha cutout — no blend mode, no filter hacks.
     Composites cleanly on any colour and keeps its true maroon/olive. */
  /* NOTE: the alpha cutout is full-strength maroon with no grey ground to
     soften it, so it needs FAR lower opacity than the old blended version.
     Raising these buries the copy — see the quiet-zone rule. */
  --thorn-filter:none;
  --thorn-op-1:.62;
  --thorn-op-2:.44;

  --hero-blend:normal;
  --hero-shadow:13px 16px 0 rgba(23,20,15,.9);
  --glow:transparent;
}

/* ---------- DARK (underworld) — Offerings, Submit ---------- */
body.rw-dark{
  --bg:#0B0A09;
  --fg:#E6E0D4;
  --accent:#BF7F7D;          /* dusty rose — the thorn plate's own hue family
                                (sampled: hue 0-20deg, L 5-14%) lifted to 62%
                                lightness so it survives on near-black. Ari's
                                reference #5E4241 is the right colour at the
                                wrong lightness: it scores 2.20:1 and vanishes.
                                This is 6.19:1 on the page and 5.89:1 on a
                                card, past the 4.5 floor, which matters because
                                the accent carries the rates and the pricing
                                legibility rule is not negotiable. */
  --muted:rgba(230,224,212,.6);
  --rule:#E6E0D4;
  /* Near-solid. Slightly translucent so the card sits IN the page rather
     than on top of it — but kept high enough that thorns never read through
     the pricing. Do not drop this below ~.85. */
  --card-bg:rgba(19,17,15,.88);
  --card-bd:rgba(230,224,212,.3);
  --scrim:rgba(11,10,9,.93);       /* quiet zone behind reading matter */

  /* alpha cutout keeps the thorns' real maroon/olive on black — moodier and
     more botanical than the old inverted-to-pale trick. Lifted so it reads. */
  /* DARK: invert the cutout to pale. The brown reads too dark on black and
     the background-removal cut lines show; inverting hides them and gives
     back the bone-white thorns. invert() flips RGB but leaves alpha alone,
     so the clean edges of the cutout survive — no blend mode needed. */
  --thorn-filter:invert(1) hue-rotate(178deg) saturate(1.25) brightness(1.02);
  --thorn-op-1:.6;
  --thorn-op-2:.42;

  --hero-blend:screen;
  --hero-shadow:none;
  --glow:rgba(240,138,18,.3);
}

/* ============================================================ */

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

html{background:var(--bg)}
body{
  background:var(--bg);color:var(--fg);
  font-family:var(--body-font);
  min-height:100vh;position:relative;overflow-x:hidden;
  transition:background .45s ease,color .45s ease;
}

/* ---------- thorns ---------- */
.thorn{
  position:absolute;pointer-events:none;z-index:1;
  filter:var(--thorn-filter);
  transition:filter .45s ease;
}
.t1{width:1150px;top:-300px;left:-330px;transform:rotate(-16deg);opacity:var(--thorn-op-1)}
.t2{width:900px;bottom:-390px;right:-230px;transform:rotate(158deg) scaleX(-1);opacity:var(--thorn-op-2)}

.glow{
  position:absolute;z-index:2;width:820px;height:820px;border-radius:50%;
  background:radial-gradient(circle,var(--glow),transparent 66%);
  top:-130px;right:-130px;pointer-events:none;
}

/* ---------- nav ---------- */
nav{
  position:relative;z-index:40;display:flex;align-items:flex-start;
  gap:2.4rem;padding:2.1rem 3.2rem;
}
.mark{
  font-family:'Remboy';font-size:2.05rem;line-height:.9;
  color:var(--fg);text-decoration:none;margin-right:auto;
}
/* ============================================================
   UI LAYER GOES SENTENCE CASE, MEDIUM WEIGHT — 2026-08-12
   Ross: "all instances of Auria are uppercase. Let's try normal
   typecase and use the medium weight."

   ⚠️ THE TRACKING HAD TO COME DOWN WITH THE CASE. Every value
   here was set for caps, where wide tracking is what makes a
   short all-caps run legible: .34em on nav, .3em on the footer,
   .26em, .24em, .16em elsewhere. Those same numbers on lowercase
   read as broken spacing, because lowercase already carries its
   own rhythm from ascenders and descenders. Roughly a fifth of
   the cap value is the equivalent optical texture, so .34 → .05,
   .3/.26/.24 → .04, .16 → .03.

   Dropping text-transform alone would have looked like a bug.
   ============================================================ */
nav a.lnk{
  font-family:var(--ui-font);font-weight:var(--ui-weight);
  font-size:calc(.82rem * var(--ui-scale));letter-spacing:.05em;
  color:var(--fg);text-decoration:none;padding-bottom:3px;
  border-bottom:1px solid transparent;
}
nav a.lnk:hover{border-bottom-color:var(--accent)}
nav a.lnk[aria-current="page"]{color:var(--accent);border-bottom-color:var(--accent)}
.n1{margin-top:.15rem}
.n2{margin-top:1.4rem}    /* deliberate broken baseline */
/* ⚠️ The accent used to be hardcoded here and on .rw-nav .n3 a, which made
   Submit look permanently selected on every page. It was styling the CTA,
   not the current page. Highlighting is now driven by aria-current below. */
.n3{margin-top:.5rem}

/* ---------- PLUMBING FOR OPTION 1 ----------
   Visitor toggle. Present and functional now; hide with
   display:none for launch and nothing else changes.        */
.mode{
  margin-left:1.6rem;margin-top:.1rem;background:none;cursor:pointer;
  border:1px solid var(--fg);color:var(--fg);
  font:inherit;font-size:calc(.62rem * var(--ui-scale));letter-spacing:.05em;
  padding:.42rem .7rem;border-radius:0;
}
.mode:hover{background:var(--fg);color:var(--bg)}

/* ---------- type ---------- */
/* Fluid display type. clamp() scales between phone and desktop with no
   breakpoint jump — the chaotic size relationships hold at every width
   because they're ratios, not fixed pixels. */
h1{font-family:'Remboy';font-weight:400;line-height:.84}
.l1{font-size:clamp(2.7rem, 10.5vw, 7.4rem)}
.l2{font-size:clamp(1.75rem, 6.4vw, 4.4rem);color:var(--accent);
    margin-left:clamp(1.4rem, 9vw, 6.4rem);margin-top:-.35rem}
.l3{font-size:clamp(2.9rem, 11.6vw, 8.2rem);
    margin-left:clamp(.4rem, 2.2vw, 1.6rem);margin-top:-.7rem}

.stage{position:relative;z-index:30;padding:3.4rem 3.2rem 0}
.rule{width:180px;height:2px;background:var(--rule);margin-bottom:1.1rem;transform:rotate(-1.2deg)}

.sub{max-width:335px;font-size:1rem;line-height:1.64}
.sub em,.sub b{color:var(--accent);font-style:normal;font-weight:400}

.foot{
  position:absolute;z-index:30;left:3.2rem;bottom:1.7rem;font-family:var(--ui-font);
  font-weight:var(--ui-weight);
  font-size:calc(.72rem * var(--ui-scale));letter-spacing:.04em;color:var(--muted);
}
/* ---- Descend / Ascend is Remboy, not the UI face ----
   Ross, 2026-08-12: "it's not quite a menu item. I want it to feel
   distinct." It sits in the nav bar but does something categorically
   different from the three links beside it — it moves you between the
   light and dark halves of the descent rather than to a page — so it
   takes the display face to say so.

   ⚠️ font-weight:400 IS LOAD-BEARING, not tidying. --ui-weight is 500 and
   Remboy ships Regular only, so inheriting it would make the browser
   synthesize a fake bold on a display face. That is the exact failure the
   REMBOY IS REGULAR-ONLY block further down this file exists to prevent,
   and it would have applied silently — a synthesized weight throws no
   error and just looks slightly wrong. Any future rule that puts Remboy on
   an element needs the same pairing. */
.mode{
  font-family:var(--wp--preset--font-family--remboy),'Remboy',Georgia,serif;
  font-weight:400;
}

/* ---------- hero ---------- */
.hero{
  position:absolute;z-index:35;width:400px;
  mix-blend-mode:var(--hero-blend);box-shadow:var(--hero-shadow);
}
.hero img{width:100%;display:block}

/* ---------- LIVE TYPE OVER IMAGERY ----------
   RULE: never use a photo with text burned into it. Every image is a clean
   plate; captions are real HTML set in Remboy on top. That keeps them
   editable in the Site Editor, readable by screen readers, sharp at any
   size, and restylable — none of which a flattened JPEG can do.        */
.hero-wrap{position:relative;display:block}
.hero-cap{
  position:absolute;left:0;right:0;top:38%;z-index:2;
  font-family:'Remboy';font-weight:400;
  display:flex;justify-content:center;gap:1.1em;
  font-size:1.42rem;line-height:1;color:#17140F;
  padding:0 .8rem;pointer-events:none;
  /* the plate is bright amber, so a soft light-side shadow keeps the type
     readable without a box or scrim sitting on the photograph */
  text-shadow:0 1px 0 rgba(255,225,180,.55);
}
.hero-cap span{white-space:nowrap}

/* ---------- rates card: the functional floor ----------
   Solid ground, never under the thorns, never rotated far
   enough to hurt reading. Chaos lives around it, not on it. */
/* ---------- ROUGH EDGE ----------
   The card's ground and border are drawn by a pseudo-element which gets an
   SVG turbulence displacement, so the edge wobbles like torn paper or a bad
   photocopy. The card's own content is NOT filtered, so text stays crisp.
   Ties to her "underground slow media" zine — misprint, not UI chrome.
   Requires the <svg id="rw-rough"> filter block to be present on the page. */
.rw-rough{position:relative;background:transparent;border:0}
.rw-rough::before{
  content:'';position:absolute;inset:-7px;z-index:-1;pointer-events:none;
  background:var(--card-bg);
  border:1.5px solid var(--card-bd);
  filter:url(#rw-roughedge);
}

.rates{
  position:relative;z-index:30;padding:1.6rem 1.8rem 1.7rem;max-width:460px;
  transform:rotate(-1.2deg);
}
.rates dl{display:grid;grid-template-columns:1fr auto;gap:.72rem 1.1rem;
  align-items:baseline;font-size:.95rem;line-height:1.3}

/* label + a drawn leader running out to the price */
.rates dt{color:var(--fg);display:flex;align-items:baseline;gap:.55rem;
  font-family:var(--ui-font);letter-spacing:.02em}
.rates dt::after{
  content:'';flex:1;border-bottom:1px dotted var(--card-bd);
  transform:translateY(-.3em);opacity:.75;
}
/* rows drift — chaos that costs nothing in legibility */
.rates dt:nth-of-type(2n){padding-left:1rem}
.rates dt:nth-of-type(3n){padding-left:1.9rem}

/* PRICES IN REMBOY. The display face doing the one job on the page where
   the number should feel like a pronouncement rather than a line item. */
.rates dd{
  text-align:right;color:var(--accent);white-space:nowrap;
  font-family:'Remboy';font-size:1.42rem;line-height:1;
}
.rates dd:nth-of-type(2n){transform:rotate(-1.4deg)}
.rates dd:nth-of-type(3n){transform:rotate(1.1deg)}

.rates .terms{
  margin-top:1.3rem;padding-top:.95rem;border-top:1px solid var(--card-bd);
  font-size:.79rem;line-height:1.65;color:var(--muted);
  transform:rotate(.4deg);
}
/* font-size lives in the type scale at the end of this file (--t-body). */
.prose{position:relative;z-index:30;max-width:520px;line-height:1.68}
.prose p+p{margin-top:1.05rem}

/* ---------- QUIET ZONE ----------
   Reading matter never sits raw on the thorns. A soft radial scrim
   fades out at the edges, so the page still feels layered and chaotic
   but the words always win. Cheaper and less rigid than a hard box. */
.prose::before,.sub::before,.lede::before,.deeper::before{
  content:'';position:absolute;inset:-2rem -2.6rem;z-index:-1;pointer-events:none;
  background:radial-gradient(ellipse at 45% 50%,var(--scrim) 42%,transparent 76%);
}

/* ============================================================
   TABLET  —  max-width: 1100px
   The desktop composition places heroes absolutely and lets them overlap
   the type. That needs real width. Below ~1100 they collide with the
   headline (Home lost "Offer yourself."; Lore lost its heading entirely).
   Heroes drop into the flow here, but everything else keeps desktop
   generosity — this is NOT the phone layout stretched upward.
   ============================================================ */
@media (max-width:1100px){
  /* cap by HEIGHT as well as width — a 560px-wide portrait is ~840px tall and
     filled the entire tablet viewport before any type was reachable. */
  .hero{position:relative !important;top:auto !important;right:auto !important;
        left:auto !important;margin:1.6rem 0 0 !important;z-index:30 !important;
        width:fit-content !important;max-width:min(480px,58%) !important}
  .hero img{max-height:56vh;width:auto;object-fit:contain}
  .stage{padding:2.6rem 2.2rem 0}
  .foot{position:relative;left:0;bottom:auto;padding:2.4rem 2.2rem 1.6rem}
  .sub{padding-left:2.2rem;padding-right:2.2rem}
  .prose{max-width:min(100%,520px)}
  .lede{max-width:min(100%,620px)}
  .t1{width:880px;top:-210px;left:-280px}
  .t2{width:700px;bottom:-260px;right:-200px}
  .glow{width:620px;height:620px}
}

/* ============================================================
   MOBILE  —  max-width: 780px
   The desktop composition leans on absolute positioning and overlap.
   On a phone that buries content, so layout goes linear. Everything that
   carries the CHARACTER stays: rotation, thorns, rough edges, drift,
   Remboy prices. Only the positioning changes.
   Per-page hero placement is overridden inside each page's own <style>,
   because those rules come after this file in the cascade.
   ============================================================ */
@media (max-width:780px){

  /* ---- nav: Submit and the mode toggle were off-screen entirely ---- */
  nav{flex-wrap:wrap;gap:.9rem 1.1rem;padding:1.2rem 1.2rem .9rem;align-items:center}
  .mark{font-size:1.6rem;margin-right:0;flex:1 0 100%;margin-bottom:.2rem}
  nav a.lnk{font-size:calc(.72rem * var(--ui-scale));letter-spacing:.04em}
  /* kill the broken-baseline offsets — on one wrapped row they just misalign */
  .n1,.n2,.n3{margin-top:0}
  /* 44px minimum tap target — the desktop button measured 26px high */
  .mode{margin-left:auto;margin-top:0;font-size:calc(.58rem * var(--ui-scale));padding:.7rem .8rem;min-height:44px}
  nav a.lnk{padding-top:.78rem;padding-bottom:.78rem}

  /* ---- global rhythm ---- */
  .stage{padding:1.6rem 1.2rem 0}
  .foot{position:relative;left:0;bottom:auto;padding:2rem 1.2rem 1.4rem;
        font-size:calc(.62rem * var(--ui-scale));letter-spacing:.04em}
  /* Clamp to the viewport WITHOUT widening: .prose already caps at 520px in
     the base rules. An earlier 66ch cap here computed to ~660px in Karrik and
     silently made the line LONGER (87 chars). min() against the existing cap
     is the safe form. */
  .sub{max-width:min(100%,335px)}
  .prose{max-width:min(100%,520px)}
  .lede{max-width:min(100%,620px)}

  /* ---- thorns: pull them back so they frame rather than swamp ---- */
  .t1{width:660px;top:-130px;left:-230px}
  .t2{width:520px;bottom:-190px;right:-170px}
  .glow{width:460px;height:460px;top:-90px;right:-120px}

  /* ---- heroes go into the flow, never absolute ---- */
  .hero{position:relative !important;top:auto !important;right:auto !important;
        left:auto !important;width:auto !important;max-width:100% !important;
        margin:1.4rem 0 0 !important;z-index:30 !important}
  .hero-cap{font-size:clamp(.95rem, 4.4vw, 1.42rem);gap:.7em}

  /* ---- rates: still rough, still drifting, just narrower ---- */
  .rates{padding:1.2rem 1.1rem 1.2rem;max-width:100%;transform:rotate(-.7deg)}
  .rates dl{gap:.6rem .7rem;font-size:.86rem}
  .rates dd{font-size:1.15rem}
  /* ⚠️ A FOURTH DRIFT DEVICE, found during Phase 4 and flattened for the same
     reason as the other three (2026-08-14). These indent the price LABELS in a
     staggered pattern — measured at 390 the four sat at 0 / 8 / 15.2 / 8px,
     which on a wide ledger reads as drift and in a narrow column reads as
     labels that failed to line up.

     This one matters more than the other three: the ledger is the most
     functional thing on the site, and "pricing legibility is not negotiable" is
     a standing rule here. Desktop keeps the stagger at lines 307-308. */
  .rates dt:nth-of-type(2n),
  .rates dt:nth-of-type(3n){padding-left:0}
  .rw-rough::before{inset:-5px}

  /* ---- forms stack ---- */
  .two{grid-template-columns:1fr}
}

/* very small phones */
@media (max-width:400px){
  nav{padding:1rem 1rem .8rem}
  .stage{padding:1.4rem 1rem 0}
  .foot{padding:1.8rem 1rem 1.2rem}
  .rates dd{font-size:1.05rem}
}

/* ============================================================
   WORDPRESS BLOCK INTEGRATION
   rw.css was authored against hand-written markup. WordPress wraps
   every block in its own containers, so these rules bridge the two.
   Anything here exists ONLY because of WP's markup — the design
   rules above stay untouched.
   ============================================================ */

/* The positioning context for thorns. Every template's <main> gets this;
   .thorn is position:absolute and needs a positioned ancestor. */
.rw-shell{
  position:relative;
  overflow:hidden;
  min-height:72vh;
  padding-bottom:3rem;
}

/* ---- nav ---- */
.rw-nav{
  position:relative;z-index:40;
  display:flex;align-items:flex-start;gap:2.4rem;
  padding:var(--rw-nav-pad-y) var(--rw-nav-pad-x);
}
/* site-title block renders <h1 class="wp-block-site-title mark"><a> */
.rw-nav .mark{
  font-family:var(--wp--preset--font-family--remboy),'Remboy',Georgia,serif;
  font-size:2.05rem;line-height:.9;margin:0 auto 0 0;font-weight:400;
}
.rw-nav .mark a{color:var(--fg);text-decoration:none}
/* nav links are paragraph blocks so Ari can edit them in the editor */
.rw-nav .rw-link{margin:0}
.rw-nav .rw-link a{
  font-family:var(--ui-font);font-weight:var(--ui-weight);
  font-size:calc(.82rem * var(--ui-scale));letter-spacing:.05em;
  color:var(--fg);text-decoration:none;padding-bottom:3px;
  border-bottom:1px solid transparent;
}
.rw-nav .rw-link a:hover{border-bottom-color:var(--accent)}
.rw-nav .n1{margin-top:.15rem}
.rw-nav .n2{margin-top:1.4rem}   /* deliberate broken baseline */
.rw-nav .n3{margin-top:.5rem}
/* see the note on .n3 above — accent removed, aria-current drives it now */
.rw-nav .rw-link a[aria-current="page"],
nav a.lnk[aria-current="page"]{
  color:var(--accent);
  border-bottom-color:var(--accent);
}

/* current page marker, set by WP's body/nav classes */
.rw-nav .rw-link.is-current a{color:var(--accent);border-bottom-color:var(--accent)}

/* ---- WP strips margins oddly on grouped headings ---- */
.rw-shell .stage h1,
.rw-shell .stage h2{margin:0}

/* ---- footer ---- */
.rw-shell + footer .foot,
footer .foot{
  position:relative;left:auto;bottom:auto;
  padding:2.4rem 3.2rem 1.8rem;margin:0;
  font-family:var(--ui-font);font-weight:var(--ui-weight);
  font-size:calc(.72rem * var(--ui-scale));letter-spacing:.04em;color:var(--muted);
}

/* ---- images inside patterns shouldn't inherit WP's constrained width ---- */
.rw-shell .hero img,
.rw-shell .lore-hero img,
.rw-shell .strip img{max-width:100%;height:auto;display:block}

@media (max-width:1100px){
  .rw-nav{padding:var(--rw-nav-pad-y) var(--rw-nav-pad-x)}
  footer .foot{padding:2.2rem 2.2rem 1.6rem}
}
@media (max-width:780px){
  .rw-nav{flex-wrap:wrap;gap:.9rem 1.1rem;
          padding:var(--rw-nav-pad-y) var(--rw-nav-pad-x) .9rem;align-items:center}
  .rw-nav .mark{flex:1 0 100%;font-size:1.6rem;margin-bottom:.2rem}
  .rw-nav .rw-link a{font-size:calc(.72rem * var(--ui-scale));letter-spacing:.04em;
                     padding-top:.78rem;padding-bottom:.78rem;display:inline-block}
  .rw-nav .n1,.rw-nav .n2,.rw-nav .n3{margin-top:0}
  footer .foot{padding:1.8rem 1.2rem 1.4rem;font-size:calc(.62rem * var(--ui-scale));letter-spacing:.04em}
}

/* ============================================================
   PER-PAGE LAYOUT
   In the mockups each page carried its own <style> block. Those
   never made it into the shared stylesheet, which is why heroes
   fell back to flow position on first deploy. Scoped here by the
   rw-page-{slug} body class that functions.php adds.
   ============================================================ */

/* ---------------- HOME ---------------- */
.rw-page-home .rw-shell{min-height:900px}
.rw-page-home .hero{
  position:absolute;z-index:35;top:150px;right:60px;width:400px;
  transform:rotate(3deg);box-shadow:var(--hero-shadow);
  mix-blend-mode:var(--hero-blend);
}
.rw-page-home .sub{
  position:absolute;z-index:30;left:3.4rem;bottom:5.4rem;max-width:335px;
}
.rw-page-home .stage{padding-top:3.4rem}

/* ---------------- LORE ---------------- */
.rw-page-lore .lore-hero{
  position:absolute;z-index:35;top:96px;right:40px;width:620px;
  transform:rotate(-2.4deg);box-shadow:14px 16px 0 rgba(23,20,15,.14);
}
.rw-page-lore .lore-hero img{width:100%;display:block}
.rw-page-lore .lede{max-width:620px;padding:1rem 3.2rem 0;line-height:1.7}
.rw-page-lore .lede p+p{margin-top:1rem}

/* ---------------- OFFERINGS ---------------- */
.rw-page-offerings .cols{
  position:relative;z-index:30;display:flex;gap:3rem;
  padding:1.4rem 3.2rem 0;align-items:flex-start;flex-wrap:wrap;
}
.rw-page-offerings .left{flex:1 1 520px;max-width:620px;display:flex;flex-direction:column}
.rw-page-offerings .rates{margin-top:2.2rem;max-width:100%}
.rw-page-offerings .hero{
  position:relative;flex:1 1 380px;max-width:470px;width:auto;
  transform:rotate(2deg);margin-top:-1rem;
}
.rw-page-offerings .closer{
  position:relative;z-index:30;padding:2.6rem 3.2rem 0;
  font-family:'Remboy',Georgia,serif;font-size:2.5rem;line-height:1.1;max-width:760px;
}
.rw-page-offerings .closer span{color:var(--accent)}
.rw-page-offerings .l1{font-size:clamp(2.6rem,9vw,6.2rem)}

@media (max-width:1100px){
  .rw-page-offerings .cols{gap:2rem;padding:1.4rem 2.2rem 0}
  .rw-page-offerings .left{flex:1 1 400px}
  .rw-page-offerings .hero{flex:1 1 280px;max-width:360px;margin-top:0}
  .rw-page-offerings .closer{padding:2.4rem 2.2rem 0;font-size:2.1rem}
}
@media (max-width:900px){
  .rw-page-offerings .cols{flex-direction:column;align-items:stretch;gap:0}
  .rw-page-offerings .left{flex:1 1 auto}
  .rw-page-offerings .hero{flex:1 1 auto;max-width:min(460px,100%);margin-top:2rem}
}
@media (max-width:780px){
  .rw-page-offerings .cols{padding:1.2rem 1.2rem 0}
  .rw-page-offerings .hero{max-width:100%;transform:rotate(1.4deg)}
  .rw-page-offerings .closer{padding:2rem 1.2rem 0;font-size:1.7rem}
}

/* ---------------- SUBMIT ---------------- */
.rw-page-submit .l1{font-size:clamp(2.5rem,8vw,5.6rem)}
.rw-page-submit .l2b{
  font-family:'Remboy',Georgia,serif;font-size:clamp(1.7rem,5vw,3.4rem);
  margin-left:4.2rem;margin-top:-.45rem;color:var(--accent);
  line-height:.85;transform:rotate(-1.4deg);
}
.rw-page-submit .cols2{
  position:relative;z-index:30;display:flex;flex-direction:column;gap:2.4rem;
  padding:2rem 3.2rem 0;align-items:flex-start;
}
/* The process text runs the full width up to the marquee (.cols2 carries
   padding-right for it), and the form sits underneath. */
.rw-page-submit .process{flex:0 0 auto;width:100%;max-width:none;padding-top:.6rem;position:relative}
.rw-page-submit .process h2{font-size:2.3rem;margin-bottom:1.1rem;transform:rotate(-1deg)}
.rw-page-submit .steps{
  counter-reset:s;list-style:none;margin:0;padding:0;
  display:grid;grid-template-columns:repeat(auto-fit,minmax(215px,1fr));
  gap:1.5rem 2.6rem;
}
.rw-page-submit .steps li{
  position:relative;padding-left:3rem;margin-bottom:0;
  font-size:1rem;line-height:1.58;
}
.rw-page-submit .steps li::before{
  counter-increment:s;content:counter(s,upper-roman);
  position:absolute;left:0;top:-.35rem;
  font-family:'Remboy',Georgia,serif;font-size:1.9rem;color:var(--accent);line-height:1;
}
.rw-page-submit .steps li:nth-child(2n){transform:translateY(.75rem)}
.rw-page-submit .steps li:nth-child(3n){transform:translateY(-.4rem)}
.rw-page-submit .terms2{
  margin-top:1.5rem;padding-top:1rem;border-top:1px solid var(--card-bd);max-width:44rem;
  font-size:.86rem;line-height:1.7;color:var(--muted);transform:rotate(-.5deg);
}
.rw-page-submit .terms2 b{color:var(--accent);font-weight:400}

.rw-page-submit form{
  flex:0 0 auto;width:100%;max-width:740px;position:relative;z-index:30;
  padding:1.6rem 1.8rem 1.8rem;transform:rotate(-.8deg);
}
.rw-page-submit .stepctl{position:absolute;opacity:0;pointer-events:none}
.rw-page-submit .dial{
  display:flex;align-items:baseline;gap:.9rem;margin-bottom:1.2rem;
  border-bottom:1px solid var(--card-bd);padding-bottom:.7rem;
}
.rw-page-submit .dial label{
  font-family:'Remboy',Georgia,serif;font-size:1.65rem;line-height:1;margin:0;
  color:var(--muted);cursor:pointer;transition:color .25s ease;
}
.rw-page-submit .dial .of{
  margin-left:auto;font-family:var(--ui-font);font-weight:var(--ui-weight);
  font-size:calc(.62rem * var(--ui-scale));letter-spacing:.04em;color:var(--muted);
}
.rw-page-submit form:has(#st1:checked) .dial label[for=st1],
.rw-page-submit form:has(#st2:checked) .dial label[for=st2],
.rw-page-submit form:has(#st3:checked) .dial label[for=st3],
.rw-page-submit form:has(#st4:checked) .dial label[for=st4]{color:var(--accent);font-size:2.1rem}

.rw-page-submit .step{display:none}
.rw-page-submit form:has(#st1:checked) .s1,
.rw-page-submit form:has(#st2:checked) .s2,
.rw-page-submit form:has(#st3:checked) .s3,
.rw-page-submit form:has(#st4:checked) .s4{display:block}
.rw-page-submit .step h3{font-size:1.9rem;margin-bottom:1rem;transform:rotate(-.8deg)}

.rw-page-submit label{
  display:block;font-size:.8rem;line-height:1.4;margin-bottom:.24rem;
  font-family:var(--ui-font);letter-spacing:.04em;
}
.rw-page-submit .req{color:var(--accent)}
.rw-page-submit input[type=text],
.rw-page-submit input[type=email],
.rw-page-submit input[type=tel],
.rw-page-submit select,
.rw-page-submit textarea{
  width:100%;background:transparent;color:var(--fg);
  border:0;border-bottom:1px solid var(--card-bd);
  font:inherit;font-size:.95rem;padding:.4rem 0 .44rem;margin-bottom:.95rem;
  border-radius:0;font-family:var(--body-font);
}
.rw-page-submit textarea{resize:vertical;min-height:4.2rem;line-height:1.55}
.rw-page-submit :is(input,select,textarea):focus{outline:0;border-bottom-color:var(--accent)}
.rw-page-submit select{appearance:none;cursor:pointer}
.rw-page-submit select option{background:#13110F;color:#E6E0D4}
.rw-page-submit .two{display:grid;grid-template-columns:1fr 1fr;gap:0 1.1rem}
.rw-page-submit .drift{margin-left:1.3rem}
.rw-page-submit .hint{font-size:.73rem;line-height:1.5;color:var(--muted);margin:-.62rem 0 1rem}
.rw-page-submit .radios{display:flex;gap:1.3rem;margin-bottom:1rem;flex-wrap:wrap}
.rw-page-submit .radios label{
  display:flex;align-items:center;gap:.4rem;margin:0;font-size:.88rem;
  cursor:pointer;font-family:var(--body-font);letter-spacing:0;
}
.rw-page-submit :is(input[type=radio],input[type=checkbox]){accent-color:var(--accent);cursor:pointer;width:auto}
.rw-page-submit .returning-only,
.rw-page-submit .new-only{display:none}
.rw-page-submit form:has(#c-new:checked) .new-only{display:block}
.rw-page-submit form:has(#c-returning:checked) .returning-only{display:block}

.rw-page-submit .move{display:flex;align-items:center;gap:.9rem;margin-top:1.1rem;flex-wrap:wrap}
.rw-page-submit :is(.next,.back,button[type=submit]){
  font-family:var(--ui-font);font-weight:var(--ui-weight);
  font-size:calc(.72rem * var(--ui-scale));letter-spacing:.04em;
  cursor:pointer;margin:0;display:inline-block;
  padding:.72rem 1.4rem;border-radius:0;line-height:1;width:auto;
}
.rw-page-submit .next{background:var(--accent);color:#0B0A09;transform:rotate(-1deg)}
.rw-page-submit .back{
  background:transparent;color:var(--muted);
  border:1px solid var(--card-bd);transform:rotate(.8deg);
}
.rw-page-submit button[type=submit]{
  background:var(--accent);color:#0B0A09;border:0;
  transform:rotate(-1.2deg);font-size:.78rem;padding:.9rem 1.7rem;
}
.rw-page-submit .consent{
  font-size:.78rem;line-height:1.5;color:var(--muted);
  display:flex;gap:.55rem;align-items:flex-start;margin:.4rem 0 1rem;
}
.rw-page-submit .consent label{font-family:var(--body-font);letter-spacing:0;font-size:.78rem}

@media (max-width:1100px){
  .rw-page-submit .cols2{gap:2rem;padding:1.6rem 2.2rem 0}
  .rw-page-submit .process{max-width:none}
  .rw-page-submit form{max-width:100%}
}
@media (max-width:900px){
  .rw-page-submit .cols2{align-items:stretch}
  .rw-page-submit .process{max-width:640px}
  .rw-page-submit form{max-width:600px}
  .rw-page-submit .steps{grid-template-columns:repeat(auto-fit,minmax(200px,1fr))}
}
@media (max-width:780px){
  .rw-page-submit .cols2{padding:1.4rem 1.2rem 0}
  .rw-page-submit .two{grid-template-columns:1fr}
  .rw-page-submit .l2b{margin-left:1.4rem}
  .rw-page-submit form{padding:1.3rem 1.2rem 1.4rem;transform:rotate(-.5deg)}
  /* ⚠️ DRIFT DEVICES ARE FLATTENED AT THIS WIDTH — 2026-08-14, Ross.
     The chaos is deliberate on a wide canvas and it stays there. In a single
     narrow column it stops reading as intent and starts reading as broken
     alignment, which is the opposite of what it is for.

     .drift indents alternate field wrappers. At 390 that put "Pronouns" at
     left 29 while "Legal name" and "Preferred name" sat at 19 — a 10px step
     with nothing to explain it, since there is no second column for it to
     relate to. Desktop keeps its 1.3rem at line 659. */
  .rw-page-submit .drift{margin-left:0}
  .rw-page-submit .steps li{padding-left:2.2rem}

  /* ⚠️ transform:none IS THE POINT HERE; margin-left:0 was already correct.
     translateY(.75rem) on 2n and -.4rem on 3n stagger the numerals ACROSS a
     four-column row. Stacked into one column that is not a stagger at all,
     just uneven vertical space: the four steps measured 31 / 1 / 38px apart,
     with II and III effectively touching. Same values, opposite effect, purely
     because the axis they were drawn for no longer exists here. */
  .rw-page-submit .steps li:nth-child(2n),
  .rw-page-submit .steps li:nth-child(3n){margin-left:0;transform:none}
  .rw-page-submit .process::before{
    content:'';position:absolute;inset:-1.2rem -1.2rem -.6rem;z-index:-1;
    background:radial-gradient(ellipse at 45% 48%,var(--scrim) 78%,transparent 100%);
  }
  .rw-page-submit :is(input[type=text],input[type=email],input[type=tel],select){
    padding:.72rem 0 .74rem;min-height:44px;
  }
  .rw-page-submit :is(.next,.back,button[type=submit]){padding:.85rem 1.3rem;font-size:calc(.7rem * var(--ui-scale))}
}

/* Home + Lore responsive: heroes leave absolute placement below 1100
   (they collide with the headline), then go full-flow on phones. */
@media (max-width:1100px){
  .rw-page-home .rw-shell{min-height:0}
  .rw-page-home .hero,
  .rw-page-lore .lore-hero{
    position:relative;top:auto;right:auto;left:auto;
    width:fit-content;max-width:min(480px,58%);
    margin:1.6rem 2.2rem 0;z-index:30;
  }
  .rw-page-lore .lore-hero{max-width:min(820px,78%);transform:rotate(-1.8deg)}
  .rw-page-home .hero img{max-height:50.4vh;width:auto}   /* 56 less 10% */
  .rw-page-home .sub{position:relative;left:auto;bottom:auto;padding:1.8rem 2.2rem 0}
  .rw-page-lore .lede{padding:1.6rem 2.2rem 0}
}
@media (max-width:780px){
  .rw-page-home .hero,
  .rw-page-lore .lore-hero{max-width:100%;margin:1.2rem 1.2rem 0}
  .rw-page-home .sub{padding:1.6rem 1.2rem 0}
  .rw-page-lore .lede{padding:1.4rem 1.2rem 0}
}

/* ---------------- LORE: strip + deeper ----------------
   MISSED ON FIRST PORT. These lived in the mockup's lore.html <style>
   block. Without them the strip figures render as full-size stacked
   images and the page runs thousands of pixels tall. */
.rw-page-lore .striplab{
  position:relative;z-index:30;padding:0 3.2rem;font-family:var(--ui-font);
  font-weight:var(--ui-weight);
  font-size:calc(.72rem * var(--ui-scale));letter-spacing:.05em;color:var(--muted);
}
.rw-page-lore .strip{
  position:relative;z-index:30;margin:2.6rem 0 0;
  display:flex;gap:1.6rem;align-items:center;
  overflow-x:auto;overflow-y:hidden;
  scroll-snap-type:x proximity;-webkit-overflow-scrolling:touch;
  padding:2.6rem 3.2rem 3.2rem;scrollbar-width:none;
}
.rw-page-lore .strip::-webkit-scrollbar{display:none}
.rw-page-lore .strip figure{
  flex:0 0 auto;scroll-snap-align:center;position:relative;margin:0;
  background:var(--card-bg);box-shadow:10px 12px 0 rgba(23,20,15,.16);
  transform:rotate(var(--rot)) scale(1);
  transition:transform .38s cubic-bezier(.2,.7,.3,1),box-shadow .38s ease;
}
.rw-page-lore .strip img{display:block;height:100%;width:auto;object-fit:cover;max-width:none}
.rw-page-lore .strip figure:nth-child(5n+1){--rot:-2.2deg;height:264px}
.rw-page-lore .strip figure:nth-child(5n+2){--rot:1.6deg;height:356px}
.rw-page-lore .strip figure:nth-child(5n+3){--rot:3deg;height:226px}
.rw-page-lore .strip figure:nth-child(5n+4){--rot:-1.2deg;height:404px}
.rw-page-lore .strip figure:nth-child(5n+5){--rot:2.4deg;height:296px}
.rw-page-lore .strip figure:first-child{margin-left:-1.4rem}
.rw-page-lore .strip figure:last-child{margin-right:2rem}
.rw-page-lore .strip figure:hover{
  transform:rotate(var(--rot)) scale(1.09);
  box-shadow:14px 18px 0 rgba(23,20,15,.22);z-index:5;
}
@media (prefers-reduced-motion:reduce){.rw-page-lore .strip figure{transition:none}}

/* thorns crossing OVER the strip — hide-and-seek, done literally */
.rw-page-lore .strip-veil{
  position:absolute;left:0;right:0;z-index:34;pointer-events:none;
  height:560px;overflow:hidden;
}
.rw-page-lore .strip-veil img{
  position:absolute;width:820px;opacity:.26;filter:var(--thorn-filter);max-width:none;
}
.rw-page-lore .sv1{top:-190px;left:-90px;transform:rotate(-9deg)}
.rw-page-lore .sv2{top:-60px;right:-160px;transform:rotate(148deg) scaleX(-1);opacity:.18}

.rw-page-lore .deeper{position:relative;z-index:30;padding:2.4rem 3.2rem 0;max-width:900px}
.rw-page-lore .deeper h2{font-size:3.4rem;line-height:1;margin-bottom:1.4rem}
.rw-page-lore .deeper p{line-height:1.62;margin:0 0 .7rem;max-width:640px}
.rw-page-lore .deeper p:nth-of-type(2n){margin-left:3.4rem}
.rw-page-lore .deeper p:nth-of-type(3n){margin-left:6.2rem}
.rw-page-lore .deeper b{color:var(--accent);font-weight:400}

@media (max-width:1100px){
  .rw-page-lore .striplab{padding:0 2.2rem}
  .rw-page-lore .strip{padding:2rem 2.2rem 2.4rem}
  .rw-page-lore .strip figure:nth-child(5n+1){height:232px}
  .rw-page-lore .strip figure:nth-child(5n+2){height:308px}
  .rw-page-lore .strip figure:nth-child(5n+3){height:200px}
  .rw-page-lore .strip figure:nth-child(5n+4){height:346px}
  .rw-page-lore .strip figure:nth-child(5n+5){height:260px}
  .rw-page-lore .deeper{padding:2.2rem 2.2rem 0}
}
@media (max-width:780px){
  .rw-page-lore .striplab{padding:0 1.2rem}
  .rw-page-lore .strip{gap:1rem;padding:1.6rem 1.2rem 2rem}
  .rw-page-lore .strip figure:nth-child(5n+1){height:186px}
  .rw-page-lore .strip figure:nth-child(5n+2){height:246px}
  .rw-page-lore .strip figure:nth-child(5n+3){height:162px}
  .rw-page-lore .strip figure:nth-child(5n+4){height:278px}
  .rw-page-lore .strip figure:nth-child(5n+5){height:208px}
  .rw-page-lore .strip figure:first-child{margin-left:0}
  .rw-page-lore .strip-veil{height:330px}
  .rw-page-lore .strip-veil img{width:460px}
  .rw-page-lore .deeper{padding:1.8rem 1.2rem 0}
  .rw-page-lore .deeper h2{font-size:2.3rem}
  /* .deeper p had a third font-size here, .98rem. Removed 2026-08-13, not
     migrated: the body tier now steps to --t-body 1.06rem at this same
     breakpoint, so the deeper list matches .lede on mobile instead of
     sitting 1px under it. Leaving it would have been worse than either
     choice — the token block is unmediated and later, so it already
     outranked this at every width. A dead rule inside a media query reads
     as live responsive behaviour and is the reason edits here did nothing. */
  /* ⚠️ FLATTENED 2026-08-14 — was 1rem and 1.9rem here, 3.4/6.2rem on desktop.
     The "a deeper sense" list drifts rightward on purpose and that is some of
     the best texture on the site, so DESKTOP KEEPS IT (lines 796-797).

     At 390 it did two things it does not do on a wide screen. Measured, the six
     items sat at four different left edges — 22 / 38 / 53 / 38 / 22 / 53 — with
     no column structure to read them against, so it looked like broken
     indentation rather than drift. And the indent eats measure that a 390px
     column cannot spare, pushing items to three uneven lines. */
  .rw-page-lore .deeper p:nth-of-type(2n),
  .rw-page-lore .deeper p:nth-of-type(3n){margin-left:0}
}

/* Clips the thorn overhang. Without this, t2's negative bottom offset
   extends the document and leaves dead space below the footer. */
.rw-chrome{
  position:absolute;inset:0;overflow:hidden;
  pointer-events:none;z-index:1;
}

/* ============================================================
   REMBOY IS REGULAR-ONLY — never let anything synthesize a bold.
   The display lines are h1/h2, and browser + WP heading defaults
   apply font-weight:700. With no bold file the browser fakes one,
   which looks rough on a display face. Measured: "does not" (h1)
   rendered 400 while "conquer." and "Offer yourself." (h2) rendered
   700 — visibly heavier strokes on the same typeface.
   ============================================================ */
.l1,.l2,.l3,.l2b,
.stage h1,.stage h2,.stage h3,
.mark,.mark a,
.rw-page-lore .deeper h2,
.rw-page-offerings .closer,
.rw-page-submit .process h2,
.rw-page-submit .step h3,
.rw-page-submit .dial label,
.offer h2,
.rates dd{
  font-weight:400;
}

/* ⚠️ .offer h2 added 2026-08-13, and it had been shipping broken since the
   cards were built two days earlier. "lust letters" and "underground slow
   media" rendered Remboy at 700 — synthesized, exactly what this block
   exists to stop.

   The reason it slipped: every other heading on the site is a WP block and
   arrives with .wp-block-heading, whereas the offerings cards are raw <h2>
   in the pattern, so none of the selectors above happened to catch them.
   THIS LIST IS AN ALLOWLIST, so any new heading outside the block editor is
   broken by default until it is named here.

   Found by a sweep for the opposite thing — checking Auria had gone to 400
   — which is the argument for asserting on the whole type system rather
   than only the property you just changed. Nothing errors; a fake bold just
   looks slightly wrong. */

/* ---------------- HOME: spacing + hero ----------------
   Body copy was landing at the very bottom of a fixed 900px shell,
   pushing it under the fold on shorter viewports. Shell now tracks
   the viewport, and the hero runs larger and sits further left so
   the page balances rather than hugging the right edge. */
.rw-page-home .rw-shell{min-height:min(880px,94vh)}
/* lifted: the rotated hero's bounding box was overrunning the shell and
   getting clipped by overflow:hidden at the bottom edge */
.rw-page-home .hero{top:56px;right:132px;width:474px}
.rw-page-home .sub{bottom:3.4rem}
.rw-page-home .stage{padding-top:2.4rem}

@media (max-width:1280px){
  .rw-page-home .hero{right:64px;width:430px}
}

/* ============================================================
   SUBMIT — rebuilt as a document, not a UI panel
   The first pass read as a web form dropped on the page: filled
   amber button, boxed panel, numerals floating like a HUD. The
   rest of the site works because everything reads as an ARTIFACT.
   This borrows the rates ledger's logic — dotted leaders, editorial
   labels, rules instead of boxes, no filled buttons anywhere.
   ============================================================ */

/* tighten the display pair — heading line-height plus stage padding
   were stacking and opening a gap between the two lines */
.rw-page-submit .stage{padding-bottom:0}
.rw-page-submit .l1{line-height:.82;margin-bottom:0}
.rw-page-submit .l2b{margin-top:-1.1rem;margin-left:3.4rem}

/* the form is no longer a card — the panel is what made it feel like UI */
.rw-page-submit form.rw-rough::before{display:none}
.rw-page-submit form{
  padding:0 0 1rem;transform:none;
  border-top:1px solid var(--card-bd);padding-top:1.5rem;
}

/* step markers: big Remboy numerals sitting in the margin like a
   manuscript, not chips in a toolbar */
.rw-page-submit .dial{
  border-bottom:0;padding-bottom:0;margin-bottom:1.6rem;
  gap:1.4rem;align-items:baseline;
}
.rw-page-submit .dial label{
  font-size:1.5rem;color:var(--muted);opacity:.5;
  transition:opacity .25s ease,color .25s ease;
}
.rw-page-submit form:has(#st1:checked) .dial label[for=st1],
.rw-page-submit form:has(#st2:checked) .dial label[for=st2],
.rw-page-submit form:has(#st3:checked) .dial label[for=st3],
.rw-page-submit form:has(#st4:checked) .dial label[for=st4]{
  color:var(--fg);opacity:1;font-size:2.4rem;
}
.rw-page-submit .dial .of{
  font-size:calc(.58rem * var(--ui-scale));letter-spacing:.04em;opacity:.6;
}

/* section headings sit inline with a rule running out from them */
.rw-page-submit .step h3{
  display:flex;align-items:baseline;gap:.9rem;
  font-size:2.1rem;margin-bottom:1.4rem;transform:none;
}
.rw-page-submit .step h3::after{
  content:'';flex:1;border-bottom:1px solid var(--card-bd);
  transform:translateY(-.4em);opacity:.6;
}

/* LABELS + LEADERS — the ledger move, applied to a form.
   Label, a drawn leader running out, then the field rule beneath. */
.rw-page-submit .step label:not(.next):not(.back){
  display:flex;align-items:baseline;gap:.6rem;
  font-family:var(--ui-font);font-weight:var(--ui-weight);
  font-size:calc(.76rem * var(--ui-scale));letter-spacing:.03em;
  color:var(--muted);margin-bottom:.35rem;
}
.rw-page-submit .step > label::after,
.rw-page-submit .step .drift > label::after,
.rw-page-submit .step .two label::after{
  content:'';flex:1;border-bottom:1px dotted var(--card-bd);
  transform:translateY(-.2em);opacity:.55;
}
.rw-page-submit .radios label::after{display:none}

/* fields: no boxes, a single rule, and the text sits ON it */
.rw-page-submit :is(input[type=text],input[type=email],input[type=tel],select,textarea){
  border-bottom-width:1px;padding:.3rem 0 .5rem;margin-bottom:1.3rem;
  font-size:1.02rem;
}
.rw-page-submit :is(input,select,textarea):focus{
  border-bottom-color:var(--accent);
}

/* NO FILLED BUTTONS. Continue/Back are typeset, not chrome. */
.rw-page-submit .move{
  margin-top:1.8rem;padding-top:1.1rem;
  border-top:1px solid var(--card-bd);gap:1.6rem;
}
.rw-page-submit :is(.next,.back,button[type=submit]){
  background:none;border:0;padding:0;transform:none;
  font-family:'Remboy',Georgia,serif;font-weight:400;
  font-size:calc(1.5rem * var(--ui-scale));letter-spacing:0;text-transform:none;
  color:var(--fg);line-height:1;
}
.rw-page-submit .next::after,
.rw-page-submit button[type=submit]::after{content:' \2192'}
.rw-page-submit .back::before{content:'\2190 '}
.rw-page-submit .back{color:var(--muted);font-size:1.15rem}
.rw-page-submit :is(.next,button[type=submit]):hover{color:var(--accent)}
.rw-page-submit .back:hover{color:var(--fg)}
.rw-page-submit button[type=submit]{font-size:1.9rem;color:var(--accent)}

/* hints read as marginalia */
.rw-page-submit .hint{
  font-size:.76rem;font-style:italic;margin:-1rem 0 1.2rem;opacity:.8;
}
.rw-page-submit .consent{
  border-top:1px solid var(--card-bd);padding-top:1.1rem;margin-top:1.4rem;
}
.rw-page-submit .consent label{text-transform:none;letter-spacing:0}
.rw-page-submit .consent label::after{display:none}

@media (max-width:780px){
  .rw-page-submit .l2b{margin-left:1.2rem;margin-top:-.7rem}
  .rw-page-submit :is(.next,button[type=submit]){font-size:1.3rem}
}

/* QUIET ZONE for the form. Removing the card to kill the "UI panel" feel
   also removed the protective ground and the thorns ran straight through
   the fields. A soft scrim restores legibility without reinstating a box —
   same mechanism as .prose, same rule: chaos around it, never on it. */
.rw-page-submit form{position:relative}
.rw-page-submit form::after{
  content:'';position:absolute;inset:-1.5rem -2rem;z-index:-1;pointer-events:none;
  background:radial-gradient(ellipse at 45% 45%,var(--scrim) 68%,transparent 96%);
}
/* thorns are heavy behind a long form — ease them on this page only */
.rw-page-submit .t1{opacity:.34}
.rw-page-submit .t2{opacity:.22}

/* title pair: WP still leaves margin on the h1 */
.rw-page-submit .stage h1{margin-bottom:0;padding-bottom:0}
.rw-page-submit .l2b{margin-top:-1.9rem}

/* ============================================================
   HERO CLIPPING FIX
   .rw-shell had overflow:hidden (to clip thorns) AND a viewport-linked
   min-height. The hero is absolutely positioned, so it adds nothing to
   the shell's height — on any short window the shell shrank below the
   hero and sliced it. At 900px tall it cleared by 67px, which is why
   it looked fine locally and wrong on a laptop.

   The thorns now clip themselves via .rw-chrome, so the shell doesn't
   need overflow:hidden at all. Height is a fixed floor, not a vh ratio.
   ============================================================ */
.rw-shell{overflow:visible}
.rw-page-home .rw-shell{min-height:880px}

/* ============================================================
   SUBMIT — vertical marquee + document-style rates
   ============================================================ */

/* infinite vertical scroller, right edge. Pure CSS: the image list is
   rendered twice and the track travels exactly -50%, so the seam lands
   on an identical frame and the loop is invisible. No JS. */
.rw-page-submit .rw-marquee{
  position:absolute;top:0;right:0;bottom:0;width:300px;
  overflow:hidden;z-index:20;pointer-events:none;
  -webkit-mask-image:linear-gradient(to bottom,transparent,#000 12%,#000 88%,transparent);
          mask-image:linear-gradient(to bottom,transparent,#000 12%,#000 88%,transparent);
}
.rw-page-submit .rw-marquee-track{
  display:flex;flex-direction:column;gap:1.1rem;
  animation:rw-scroll 48s linear infinite;
  will-change:transform;
}
.rw-page-submit .rw-marquee figure{margin:0;flex:0 0 auto}
.rw-page-submit .rw-marquee img{
  display:block;width:100%;height:auto;
  opacity:.8;filter:saturate(.9);
}
@keyframes rw-scroll{
  from{transform:translateY(0)}
  to{transform:translateY(calc(-50% - .55rem))}   /* half the track + half a gap */
}
@media (prefers-reduced-motion:reduce){
  .rw-page-submit .rw-marquee-track{animation:none}
}

/* the form column keeps clear of the marquee */
.rw-page-submit .cols2{padding-right:340px}

/* form sits higher on the page */
.rw-page-submit .stage{padding-top:1.6rem}
.rw-page-submit .cols2{padding-top:1rem}
.rw-page-submit .process{padding-top:0}

/* ---- RATES: same treatment as the form — document, not panel ---- */
.rates.rw-rough::before{display:none}
.rates{
  position:relative;transform:none;padding:1.4rem 0 1.2rem;
  border-top:1px solid var(--card-bd);border-bottom:1px solid var(--card-bd);
}
.rates::after{
  content:'';position:absolute;inset:-1rem -1.6rem;z-index:-1;pointer-events:none;
  background:radial-gradient(ellipse at 45% 50%,var(--scrim) 70%,transparent 96%);
}
/* dt inherits Auria rather than declaring it, so the weight is set here
   explicitly — a replace on "font-family:var(--ui-font)" would have missed
   this rule entirely and left the ledger labels at 400 among 500s. */
.rates dt{
  font-weight:var(--ui-weight);
  font-size:calc(.76rem * var(--ui-scale));letter-spacing:.03em;color:var(--muted);
}
.rates dd{font-size:1.5rem}
.rates .terms{border-top:0;padding-top:.8rem;font-style:italic}

@media (max-width:1100px){
  .rw-page-submit .rw-marquee{width:220px}
  .rw-page-submit .cols2{padding-right:250px}
}
@media (max-width:900px){
  .rw-page-submit .rw-marquee{display:none}
  .rw-page-submit .cols2{padding-right:2.2rem}
}

/* Home body copy was pinned to the SHELL bottom (bottom:3.4rem). Once the
   shell became a fixed 880px floor, that pushed the copy below the fold on
   shorter windows. Anchoring from the top instead keeps it in the first
   screen at any viewport height, still low-left for balance. */
.rw-page-home .sub{top:392px;bottom:auto}
@media (max-width:1100px){
  .rw-page-home .sub{top:auto}   /* back in flow once the hero stacks */
}

/* Hero runs larger and higher — deliberately tight to the nav, and the
   headline is allowed to bleed across it. z-index keeps the type on top. */
.rw-page-home .hero{top:-18px;right:96px;width:582px}
.rw-page-home .stage{position:relative;z-index:36}
.rw-page-home .rw-shell{min-height:848px}   /* ⚠️ WAS 940, CUT WITH THE HERO.
   This number exists only to reserve flow space for the absolutely-positioned
   hero, so it is not independent of the hero's height and must move with it.
   The 10% top crop took the hero 918 -> 826 and, left at 940, this held the
   page at exactly its old 1165px — the crop bought nothing on desktop and the
   page looked unchanged. 848 is 940 less the same 92px. If the hero is ever
   resized again, this line is the second half of that change. */

/* ⚠️ MEDIATED ON PURPOSE. This was written unmediated, which silently undid
   the "top:auto" reset at ~992 above: identical specificity, later in the
   file, so it won at EVERY width including phones. Measured at 390 before
   the fix — .sub sat at flow position 727 but rendered at 1183, displaced
   456px, floating detached below the arrow.

   It also broke the page invisibly. A relatively positioned element is
   offset visually but keeps its ORIGINAL flow box, so nothing grew to
   contain it: .entry-content measured 882 tall while its content reached
   1314, and that shortfall propagated all the way to body, whose box came
   out 244px shorter than the document. Any full-page background layer
   anchored to body therefore stopped short of the footer, which is how this
   was finally found.

   Same trap as the Lore hero, and the second time it has cost real time in
   this file. An unmediated rule placed after a media query is not a default,
   it is an override of that query. */
@media (min-width:1101px){
  .rw-page-home .sub{top:432px}
}
@media (max-width:1280px){
  .rw-page-home .hero{right:48px;width:520px;top:0}
}

/* ============================================================
   THORNS BLEEDING THROUGH IMAGERY — sitewide
   The Lore strip's veil reads well, so the same idea is applied to
   every photograph: a thorn layer passing lightly OVER the image.

   Per-image rather than one continuous sheet, with the background
   position and rotation varied so no two show the same crop —
   otherwise it reads as a repeated stamp instead of a texture.

   Inherits --thorn-filter, so it stays maroon on bone pages and
   inverts to pale on dark ones, same as the page thorns.
   ============================================================ */
.hero-wrap,
.rw-page-lore .lore-hero,
.rw-page-offerings .hero,
.rw-page-submit .rw-marquee figure{
  position:relative;
}
.hero-wrap::after,
.rw-page-lore .lore-hero::after,
.rw-page-offerings .hero::after,
.rw-page-submit .rw-marquee figure::after{
  content:'';
  position:absolute;inset:0;z-index:2;pointer-events:none;
  background-image:url('../img/thorns.png');
  background-repeat:no-repeat;
  background-size:190% auto;
  filter:var(--thorn-filter);
  opacity:.19;
}

/* vary the crop so each image gets different growth */
.hero-wrap::after{background-position:22% 28%;transform:rotate(4deg) scale(1.15)}
.rw-page-lore .lore-hero::after{background-position:68% 40%;background-size:150% auto;
                                transform:rotate(-6deg) scale(1.1);opacity:.16}
.rw-page-offerings .hero::after{background-position:34% 62%;transform:rotate(-3deg) scale(1.12)}
.rw-page-submit .rw-marquee figure::after{background-size:230% auto;opacity:.22}
.rw-page-submit .rw-marquee figure:nth-child(2n)::after{background-position:70% 20%;transform:rotate(5deg)}
.rw-page-submit .rw-marquee figure:nth-child(3n)::after{background-position:15% 75%;transform:rotate(-7deg)}
.rw-page-submit .rw-marquee figure:nth-child(5n)::after{background-position:90% 50%;transform:rotate(2deg)}

/* the caption must stay above the veil */
.hero-cap{z-index:4}

/* Veil was reading as thorns sitting ON the photograph rather than growing
   through it. Lower opacity and a larger crop — fewer, bigger forms cross
   the subject, so it registers as texture rather than as objects. */
.hero-wrap::after,
.rw-page-lore .lore-hero::after,
.rw-page-offerings .hero::after{
  opacity:.12;background-size:300% auto;
}
.hero-wrap::after{background-position:30% 34%;transform:rotate(4deg) scale(1.05)}
.rw-page-lore .lore-hero::after{background-position:62% 44%;opacity:.11}
.rw-page-offerings .hero::after{background-position:40% 58%;opacity:.13}
.rw-page-submit .rw-marquee figure::after{opacity:.15;background-size:320% auto}

/* ============================================================
   VEIL CORRECTIONS
   1. The position:relative above clobbered .lore-hero's absolute
      placement and collapsed the Lore layout. Absolutely positioned
      elements are ALREADY a containing block — it was never needed.
   2. Zooming to 300% isolated a single thorn, which read as one stray
      shape hanging over the photo. Using the whole plate instead:
      overlapped, tangled, filling the frame.
   ============================================================ */
.rw-page-lore .lore-hero{position:absolute}

.hero-wrap::after,
.rw-page-lore .lore-hero::after,
.rw-page-offerings .hero::after,
.rw-page-submit .rw-marquee figure::after{
  background-size:cover;
  background-position:center;
  transform:none;
  opacity:.16;
}
.rw-page-lore .lore-hero::after{background-position:center 35%;opacity:.14}
.rw-page-offerings .hero::after{opacity:.17}
.rw-page-submit .rw-marquee figure::after{opacity:.2}
/* vary which part of the tangle each frame shows, without zooming in */
.rw-page-submit .rw-marquee figure:nth-child(2n)::after{background-position:center 20%}
.rw-page-submit .rw-marquee figure:nth-child(3n)::after{background-position:center 80%;transform:scaleX(-1)}

/* ============================================================
   VEIL: ONE CONTINUOUS FIELD, NOT PER-IMAGE
   Sizing the veil to each element made the thorns start and stop at
   the image edges — they read as bound to the photo. background-attachment
   :fixed anchors the plate to the VIEWPORT instead, so every image reveals
   a slice of the same underlying field. The image becomes translucent to
   thorns that were already there, which is the intent.
   Per-element positions are dropped: they would break the continuity.
   ============================================================ */
.hero-wrap::after,
.rw-page-lore .lore-hero::after,
.rw-page-offerings .hero::after,
.rw-page-submit .rw-marquee figure::after{
  background-attachment:fixed;
  background-size:cover;
  background-position:center;
  transform:none;
  opacity:.15;
}
.rw-page-submit .rw-marquee figure:nth-child(2n)::after,
.rw-page-submit .rw-marquee figure:nth-child(3n)::after,
.rw-page-submit .rw-marquee figure:nth-child(5n)::after{
  background-position:center;transform:none;
}
/* iOS ignores fixed attachment; the veil still shows, just per-element */

/* ============================================================
   THE VEIL, DONE PROPERLY
   background-attachment:fixed could never work here — any ancestor
   with a transform becomes the containing block for fixed backgrounds,
   and every image on this site is rotated. It silently fell back to
   element-relative, which is precisely the "bound to the image" look.

   Correct approach: the page's OWN thorn plate is drawn a second time
   above the imagery. Not a copy of the look — literally the same field,
   same position, same scale. Images sit below it, text above it.
   ============================================================ */

/* kill the per-image overlays entirely
   ⚠️ background-image:none IS NOT REDUNDANT ALONGSIDE content:none, and this
   cost 1.27MB per page load until 2026-08-16.

   content:none means the pseudo-element is never generated, so it paints
   nothing — verified, all four report content:"none" and render:false. But
   Chrome STILL FETCHES the background-image declared on them. Measured in a
   clean browser context with no HTML reference to the file anywhere: thorns.png
   downloading at 1272KB on all four pages, requested by nothing that renders.

   So "the rule is dead" and "the rule costs nothing" are different claims. A
   dead rule that names an asset still pays for it. Nulling the image here is
   what actually stops the request.

   The rest of the crop rules above are left intact so restoring the per-image
   veil stays a small change, matching how the retired Lore strip was handled. */
.hero-wrap::after,
.rw-page-lore .lore-hero::after,
.rw-page-offerings .hero::after,
.rw-page-submit .rw-marquee figure::after{content:none;background-image:none}

.rw-chrome-over{z-index:36}
.rw-chrome-over .thorn{opacity:.17}
.rw-page-submit .rw-chrome-over .thorn{opacity:.13}

/* text must clear the over-layer; imagery deliberately does not */
.stage,.sub,.prose,.lede,.deeper,.closer,.rates,.striplab,
.rw-page-submit .process,.rw-page-submit form,
nav,.rw-nav,footer,.foot,.proto{
  position:relative;z-index:40;
}
.rw-nav{z-index:44}

/* ============================================================
   THE DESCENT ARROW
   One-way path: home → lore → offerings → submit. Submit is the end,
   so it has none. Form is an archer's arrow, from Ari's own Lore copy
   ("as an archer... you will be my target") — barbed head and slender
   shaft share a language with the thorns instead of being UI chrome.
   ============================================================ */
.rw-arrow{
  position:absolute;z-index:42;
  display:flex;align-items:center;gap:1rem;
  text-decoration:none;color:var(--fg);
  transition:transform .5s cubic-bezier(.2,.7,.3,1),color .3s ease;
}
.rw-arrow svg{
  width:132px;height:40px;overflow:visible;
  stroke:currentColor;fill:none;
  stroke-width:1.4;stroke-linecap:round;stroke-linejoin:round;
  vector-effect:non-scaling-stroke;
}
.rw-arrow-label{
  font-family:'Remboy',Georgia,serif;font-size:calc(1.5rem * var(--ui-scale));line-height:1;
  white-space:nowrap;opacity:0;transform:translateX(-10px);
  transition:opacity .4s ease,transform .4s ease;
}
.rw-arrow:hover{transform:translateX(14px);color:var(--accent)}
.rw-arrow:hover .rw-arrow-label{opacity:1;transform:translateX(0)}
.rw-arrow:focus-visible{outline:1px solid var(--accent);outline-offset:8px}


/* ---- placement, per Ross ---- */
.rw-page-home .rw-arrow{left:3.4rem;top:600px}          /* below the body copy */
.rw-page-lore .rw-arrow{right:9%;top:1180px}            /* beside "a deeper sense" */
.rw-page-offerings .rw-arrow{left:52%;top:46%}          /* left of the image, centred */

@media (max-width:1100px){
  .rw-arrow{position:relative;left:auto!important;right:auto!important;
            top:auto!important;margin:2.4rem 2.2rem 0}
  .rw-arrow-label{opacity:1;transform:none}
}
@media (max-width:780px){
  .rw-arrow{margin:2rem 1.2rem 0}
  .rw-arrow svg{width:104px;height:26px}
  .rw-arrow-label{font-size:calc(1.2rem * var(--ui-scale))}
}

/* ============================================================
   CROSS-DOCUMENT SLIDE
   Native CSS view transitions — no JavaScript. Chrome 126+ and
   Safari 18.2+ animate; everything else navigates normally, which
   is a clean degradation rather than a broken state.
   ============================================================ */
@view-transition{ navigation: auto; }

@keyframes rw-slide-out-left{
  to{transform:translateX(-14%);opacity:0}
}
@keyframes rw-slide-in-right{
  from{transform:translateX(100%)}
  to{transform:translateX(0)}
}
::view-transition-old(root){
  animation:rw-slide-out-left .58s cubic-bezier(.4,0,.2,1) both;
}
::view-transition-new(root){
  animation:rw-slide-in-right .58s cubic-bezier(.4,0,.2,1) both;
}
/* the thorn field stays put while pages travel across it — it reads as
   one continuous world rather than four separate documents */
.rw-chrome{view-transition-name:rw-thorns}
::view-transition-group(rw-thorns){animation-duration:.58s}

@media (prefers-reduced-motion:reduce){
  ::view-transition-old(root),
  ::view-transition-new(root){animation:none}
}

/* Arrow placement corrections — measured against the live pages.
   Home's arrow was landing inside .sub (572–677) rather than below it. */
/* ⚠️ 712 -> 812. This number is tied to where .sub ENDS, which nothing in
   CSS enforces, so it has to be re-measured whenever either moves.

   .sub is absolutely positioned at top:432px and its box bottom measures
   768px, so an arrow anchored at 712 overlapped it by 56px. That overlap
   PRE-DATES the 2.5x UI scale — .sub is Karrik and did not scale, and the
   arrow's top is a fixed value — but at .82rem the label's glyphs were
   small enough to sit in the gap and nobody saw it. At 2.05rem they land
   on the last line of the body copy.

   A latent collision that only becomes visible when something nearby grows
   is exactly the failure mode the fixed-value note in the runbook warns
   about. Desktop only: at <=1100 the arrow drops to position:relative and
   flows, which is why the overlap never appeared on phones. */
.rw-page-home .rw-arrow{top:812px;left:3.4rem}
.rw-page-lore .rw-arrow{right:11%;top:1210px}
.rw-page-offerings .rw-arrow{left:auto;right:38%;top:44%}

/* a touch more presence, and the destination readable without hovering */
.rw-arrow svg{width:150px;height:37px;stroke-width:1.6}
.rw-arrow-label{opacity:.45;transform:none;font-size:calc(1.35rem * var(--ui-scale))}
.rw-arrow:hover .rw-arrow-label{opacity:1;transform:translateX(4px)}

/* ============================================================
   ARROW v2 — botanical whiplash, not a technical diagram
   Superseded 2026-08-03: the arrow is no longer drawn here at all. It is
   traced from assets/images/arrow1.png, which Ari supplied — a thorn-cane
   shaft with a barbed head and two leaf nodes. Earlier attempts (geometric
   chevrons, then a hand-drawn whiplash curve) are gone, along with the
   .rw-stem / .rw-barb / .rw-leaf rules that styled them.
   The drawing is 4.01:1, so every box that holds it uses that aspect.
   ============================================================ */


/* ---- placement: bottom-left on Lore and Offerings ---- */
.rw-page-lore .rw-arrow,
.rw-page-offerings .rw-arrow{
  left:3.4rem;right:auto;top:auto;bottom:2.6rem;
}

/* ============================================================
   TRUE SLIDE — the whole page travels as one plane
   v1 kept a cross-fade (opacity in the keyframes) and gave the thorns
   their own transition name, so pages dissolved instead of moving.
   Killing the default blend and sliding a full 100% makes it read as a
   deck: one surface moving, not two documents swapping.
   ============================================================ */
::view-transition-image-pair(root){isolation:auto}
::view-transition-old(root),
::view-transition-new(root){mix-blend-mode:normal;display:block}

@keyframes rw-slide-out-left{to{transform:translateX(-100%)}}
@keyframes rw-slide-in-right{from{transform:translateX(100%)}}

::view-transition-old(root){
  animation:rw-slide-out-left .62s cubic-bezier(.65,0,.35,1) both;
}
::view-transition-new(root){
  animation:rw-slide-in-right .62s cubic-bezier(.65,0,.35,1) both;
}
/* thorns travel WITH the page — one continuous surface, not a fixed
   backdrop the content slides over */
.rw-chrome{view-transition-name:none}

/* ============================================================
   ARROW — Ari's own drawing, traced
   THIS BLOCK WINS. Three earlier rules also set .rw-arrow svg size
   further up the file; this one is last, so it is the one that decides.
   If the arrow renders at the wrong proportions, look here first.
   Filled, never stroked: uniform line weight read as clipart, and the
   drawing is a silhouette anyway. 4.01:1, the traced aspect.
   ============================================================ */
.rw-arrow svg{width:176px;height:44px;stroke:none;fill:currentColor}
.rw-arrow .rw-cane{fill:currentColor;stroke:none}

/* grows from the tail — a clip sweep works on fills, unlike stroke-dash */
@keyframes rw-unfurl{from{clip-path:inset(0 100% 0 0)}to{clip-path:inset(0 0 0 0)}}
.rw-arrow svg{animation:rw-unfurl 1.15s cubic-bezier(.3,.7,.3,1) both .2s}
@media (prefers-reduced-motion:reduce){.rw-arrow svg{animation:none}}

/* ============================================================
   THORNS FILL THE WHOLE PAGE
   t1 (top-left) and t2 (bottom-right) left the top-right corner
   bare on every page. Not deliberate — just an unfinished field.
   t3 fills the top-right, t4 the lower-left.
   ============================================================ */
.t3{width:1000px;top:-260px;right:-300px;transform:rotate(64deg);
    opacity:var(--thorn-op-2)}
.t4{width:820px;bottom:-200px;left:-260px;transform:rotate(-120deg) scaleX(-1);
    opacity:var(--thorn-op-2)}
.rw-chrome-over .t3{opacity:.13}

@media (max-width:1100px){
  .t3{width:700px;top:-170px;right:-220px}
  .t4{width:560px;bottom:-140px;left:-190px}
}
@media (max-width:780px){
  .t3{width:480px;top:-110px;right:-160px}
  .t4{display:none}
}

/* ============================================================
   THE TICK — every arrow on the site is the same drawing
   Same two paths as .rw-arrow, as a mask so it inherits currentColor
   and can live inside ::after. Used on the strip label and the form's
   Continue / Back, which were character glyphs before.
   ============================================================ */
.rw-tick,
.rw-page-submit .next::after,
.rw-page-submit button[type=submit]::after,
.rw-page-submit .back::before{
  content:'';display:inline-block;
  width:3.8em;height:.95em;vertical-align:-.06em;   /* 4.01:1. Wide, because at
     this aspect a tick sized for height alone renders ~7px tall and the leaf
     nodes disappear entirely. */
  background-color:currentColor;
  -webkit-mask:var(--rw-tick) no-repeat center/contain;
          mask:var(--rw-tick) no-repeat center/contain;
}
:root{
  --rw-tick:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 49.9'%3E%3Cpath d='M97.1 0.0C99.6 -0.3 100.7 -0.1 102.3 0.0C103.9 0.1 105.6 0.3 107.5 0.8C109.4 1.4 112.8 2.5 114.6 3.5C116.4 4.6 118.0 5.8 119.2 7.4C120.5 9.0 121.7 12.4 122.8 13.9C123.9 15.5 125.0 16.5 126.3 17.5C127.6 18.4 129.4 19.4 131.2 20.2C133.1 21.0 132.1 22.2 138.6 22.6C145.2 23.1 168.2 22.9 173.8 22.9C179.4 22.9 174.9 22.6 174.9 22.6C174.9 22.6 175.6 22.6 175.2 21.6C174.7 20.5 171.9 16.1 171.9 16.1C171.9 16.1 170.6 15.2 172.4 15.8C174.3 16.4 179.9 18.6 184.2 19.9C188.4 21.2 200.0 24.3 200.0 24.3C200.0 24.3 188.7 26.9 184.4 28.1C180.2 29.3 174.2 31.6 172.2 32.2C170.1 32.8 171.1 31.9 171.1 31.9C171.1 31.9 174.1 27.7 174.6 26.7C175.2 25.7 174.6 25.4 174.6 25.4C174.6 25.4 99.1 25.0 84.3 25.4C69.5 25.8 79.8 27.1 78.3 27.8C76.8 28.6 75.8 29.4 74.8 30.3C73.8 31.1 72.8 31.9 71.8 33.3C70.8 34.6 69.1 37.8 68.2 39.0C67.3 40.2 66.8 40.6 65.8 41.2C64.7 41.8 63.0 42.5 61.1 42.8C59.2 43.1 55.0 43.0 53.5 43.1C51.9 43.2 51.8 43.3 51.0 43.7C50.2 44.0 49.4 44.3 48.3 45.3C47.2 46.3 43.9 49.9 43.9 49.9C43.9 49.9 43.9 48.3 44.5 46.9C45.0 45.5 46.6 42.5 47.5 40.9C48.4 39.4 49.3 38.0 50.2 36.8C51.1 35.7 51.8 34.7 53.5 33.6C55.1 32.4 59.0 30.3 60.8 29.5C62.7 28.6 62.9 28.6 65.2 28.1C67.6 27.6 76.1 25.9 76.1 25.9C76.1 25.9 76.1 25.4 76.1 25.4C76.1 25.4 59.3 25.2 55.4 25.4C51.4 25.6 51.8 26.2 50.5 26.7C49.1 27.2 48.8 27.3 46.7 28.6C44.5 30.0 38.5 34.5 36.6 35.7C34.6 37.0 34.5 36.2 33.8 36.6C33.1 36.9 32.7 37.6 31.9 37.9C31.2 38.3 30.1 38.6 28.9 38.7C27.8 38.9 25.8 38.9 24.6 38.7C23.3 38.6 22.3 38.5 21.0 37.9C19.7 37.4 17.8 36.4 16.4 35.2C15.0 34.0 13.4 31.5 12.0 30.0C10.6 28.5 8.4 26.3 7.1 25.4C5.8 24.5 4.6 24.5 3.5 24.3C2.5 24.1 0.0 24.0 0.0 24.0C0.0 24.0 0.0 23.5 0.0 23.5C0.0 23.5 2.1 23.5 3.3 23.2C4.5 22.9 5.4 23.3 7.6 21.6C9.9 19.8 15.6 13.8 17.7 12.0C19.8 10.2 20.2 10.6 21.3 10.1C22.4 9.6 23.5 9.1 24.8 9.0C26.1 8.9 28.1 8.9 29.7 9.3C31.4 9.6 33.0 9.7 35.5 11.2C37.9 12.7 43.6 17.3 45.8 18.8C48.1 20.3 48.7 20.4 50.2 21.0C51.7 21.6 42.8 22.4 55.4 22.6C68.0 22.9 131.8 22.6 131.8 22.6C131.8 22.6 131.8 22.1 131.8 22.1C131.8 22.1 124.2 18.8 121.4 18.0C118.6 17.3 115.2 17.6 113.5 17.2C111.8 16.8 112.2 16.8 110.2 15.3C108.2 13.8 103.0 9.3 100.4 7.6C97.8 6.0 96.0 5.3 93.6 4.6C91.2 4.0 86.8 3.6 84.9 3.5C82.9 3.5 82.0 4.0 81.0 4.4C80.1 4.7 78.6 5.7 78.6 5.7C78.6 5.7 79.0 4.7 80.2 4.1C81.4 3.5 83.9 2.3 86.5 1.6C89.1 1.0 94.7 0.3 97.1 0.0Z'/%3E%3C/svg%3E");
}
.rw-page-submit .back::before{transform:scaleX(-1);margin-right:.3em}
.rw-page-submit .next::after,
.rw-page-submit button[type=submit]::after{margin-left:.35em}

/* strip label sits right on top of the scroller now */
.rw-page-lore .striplab{
  display:flex;align-items:center;gap:.7rem;
  margin-top:2.2rem;margin-bottom:-2.2rem;position:relative;z-index:40;
}
.rw-page-lore .striplab .rw-tick{width:4.6em;height:1.15em;opacity:.8}
.rw-page-lore .strip{margin-top:.6rem}

/* ---- arrows to the BOTTOM RIGHT on Lore and Offerings ---- */
.rw-page-lore .rw-arrow,
.rw-page-offerings .rw-arrow{
  left:auto;right:3.4rem;top:auto;bottom:2.6rem;
  flex-direction:row-reverse;
}
.rw-page-lore .rw-arrow .rw-arrow-label,
.rw-page-offerings .rw-arrow .rw-arrow-label{text-align:right}

/* Strip label hugs the top edge of the scroller. Pushed down with a
   positive margin-top and pulled back by an equal negative margin-bottom,
   so the label moves without shifting the strip; the strip's own top
   padding comes in to close the remaining gap. */
.rw-page-lore .striplab{margin-top:4.6rem;margin-bottom:-4.6rem}
.rw-page-lore .strip{padding-top:1.5rem;margin-top:0}

/* 4.6rem overshot — the label landed 29px inside the tallest figure.
   2.5rem sits it just clear of the top edge. */
.rw-page-lore .striplab{margin-top:2.5rem;margin-bottom:-2.5rem}

/* Overlap restored — Ross wants the label sitting INTO the top of the
   scroller, not clearing it. 29px of overlap was the version he liked. */
.rw-page-lore .striplab{margin-top:4.6rem;margin-bottom:-4.6rem}

/* ============================================================
   FORM LEGIBILITY
   Dotted leaders after each label ran horizontally, and the inputs'
   solid underlines ran horizontally too — two different line types
   doing different jobs at the same weight. You couldn't tell which
   line you were meant to write on.

   Leaders stay on the rates ledger, where they connect a label to a
   price and carry meaning. In the form they are noise: dropped.
   Labels now read as labels (small, letterspaced, muted) and only
   the writing lines are lines.
   ============================================================ */
.rw-page-submit .step > label::after,
.rw-page-submit .step .drift > label::after,
.rw-page-submit .step .two label::after{content:none}

.rw-page-submit .step label:not(.next):not(.back){
  display:block;font-size:calc(.72rem * var(--ui-scale));letter-spacing:.03em;
  color:var(--muted);margin-bottom:.5rem;
}
/* the writing line is the only horizontal rule in a field group,
   and it is brighter than any divider so it reads as "write here" */
.rw-page-submit :is(input[type=text],input[type=email],input[type=tel],select,textarea){
  border-bottom:1px solid var(--fg);
  opacity:1;margin-bottom:1.6rem;
}
.rw-page-submit :is(input,select,textarea):hover{border-bottom-color:var(--accent)}
/* section rules are dimmer than writing lines, so the hierarchy is obvious */
.rw-page-submit .step h3::after{opacity:.3}
.rw-page-submit .move{border-top-color:var(--card-bd)}

/* ============================================================
   OFFERINGS HERO — larger, underlapping the copy
   ============================================================ */
.rw-page-offerings .hero{
  flex:1 1 560px;max-width:680px;margin-left:-140px;z-index:18;
}
.rw-page-offerings .left{flex:1 1 460px;max-width:560px}
@media (max-width:1100px){
  .rw-page-offerings .hero{flex:1 1 380px;max-width:460px;margin-left:-60px}
}
@media (max-width:900px){
  .rw-page-offerings .hero{margin-left:0;max-width:min(520px,100%)}
}

/* Offerings hero: ~20% smaller and shifted right. The -140px pull was
   what held it left, so easing that moves it over. */
.rw-page-offerings .hero{flex:1 1 470px;max-width:545px;margin-left:16px}
.rw-page-offerings .left{flex:1 1 500px;max-width:600px}
@media (max-width:1100px){
  .rw-page-offerings .hero{flex:1 1 340px;max-width:400px;margin-left:0}
}

/* Lore hero: up and left, slightly larger, to close the gap it left
   between the lede and the top of the strip. */
.rw-page-lore .lore-hero{top:60px;right:84px;width:664px}

/* Arrow label matches the nav, not Remboy — same face, tracking and
   case as the header links, so it reads as navigation rather than display. */
.rw-arrow-label{
  font-family:var(--ui-font);font-weight:var(--ui-weight);
  font-size:calc(.82rem * var(--ui-scale));letter-spacing:.05em;
}
@media (max-width:780px){
  .rw-arrow-label{font-size:calc(.72rem * var(--ui-scale));letter-spacing:.04em}
}

/* ============================================================
   THORNS EASED — 5% smaller, and lighter
   Seven plates per page plus the over-layer had tipped into busy,
   especially behind the ledger on the dark pages.
   ============================================================ */
.t1{width:1092px}
.t2{width:855px}
.t3{width:950px}
.t4{width:779px}

body.rw-bone{--thorn-op-1:.53;--thorn-op-2:.37}
body.rw-dark{--thorn-op-1:.50;--thorn-op-2:.35}
.rw-chrome-over .thorn{opacity:.14}
.rw-chrome-over .t3{opacity:.11}
.rw-page-submit .rw-chrome-over .thorn{opacity:.10}

@media (max-width:1100px){
  .t1{width:836px}
  .t2{width:665px}
  .t3{width:665px}
  .t4{width:532px}
}
@media (max-width:780px){
  .t1{width:627px}
  .t2{width:494px}
  .t3{width:456px}
}

/* ============================================================
   HOME OVERTURE — "Roman West" completes the sentence, then
   becomes the logo.

   Her line is "Roman West does not conquer. Offer yourself." The page
   was starting mid-sentence, which is why the headline felt like it
   hung. All four lines are legible from the first frame:

       Roman West
       does not
       conquer.
       Offer yourself.

   Then "Roman West" travels up into the nav and becomes the wordmark —
   the sentence resolves and the logo is explained in one gesture.
   No fade on the headline; it is readable immediately, it just shifts.

   NOTHING HERE IS A MEASURED PIXEL VALUE. Every earlier attempt was
   calibrated at 1440 and drifted everywhere else — the scale factor,
   the vertical offset, and the resting position each broke separately.
   Three rules keep it honest:

   1. The overture rests at the nav mark's exact position by consuming
      the same padding variables the nav does, so it cannot drift when
      the nav reflows at a breakpoint.
   2. It animates font-size, not scale. The element sizes from its own
      top-left, so the landing is exact by construction.
   3. The headline offset derives from the overture's line box, which
      derives from the same clamp() the headline uses. Both track width
      together.

   The geometry: the overture starts one line box above the shifted
   headline, and the headline shifts by (line box − the gap between
   them). Those cancel, so the overture's start position holds steady
   at any width while both track the display size.
   ============================================================ */

:root{
  /* the nav's own padding, so the overture can land on the mark
     without restating the numbers */
  --rw-nav-pad-y: 2.1rem;
  --rw-nav-pad-x: 3.2rem;

  /* ⚠️ NOW EQUAL TO --rw-headline-rest, and that is forced, not a choice.
     Ross asked for the settled headline +35%, which lands at ~125px. The
     opening cannot start SMALLER than where the headline settles or the
     headline would grow on its way in rather than shrink. And during the
     hold the wordmark and headline have to be the same size or they stop
     reading as one sentence.

     So the settled size sets the opening size. 6.2 -> 7.8rem. This is
     larger than the 7.4rem Ross called "a little too large" last round,
     but that complaint was compound — "too large AND still overlaps" — and
     the overlap is now fixed by --rw-headline-gap independently.

     Only the WORDMARK still shrinks, 125px down to the nav's 33px, which
     is the gesture Ari said they loved. */
  --rw-display: clamp(2.6rem, 9vw, 6.2rem);   /* == --t-title */
  --rw-overture-line: calc(var(--rw-display) * .9);

  /* ⚠️ NEGATIVE ON PURPOSE, and it is the fix for the overlap.
     Measured at 2000px before the change: the overture ran y140-276 while
     the headline started at y211, tangling them by 65px.

     The algebra is worth keeping because it is not obvious. With
     L = --rw-overture-line and G = this gap:

       overture bottom = O_rest + L + O_height
       headline top    = S_rest + L - G

     L appears on BOTH sides and cancels, so how far the wordmark travels
     has nothing to do with whether the two collide. Separation depends
     only on G, and clearance requires G <= S_rest - O_rest - O_height.
     Substituting the measured 34, 143 and 136 gives G <= -27px, so any
     positive value overlaps no matter what else is tuned.

     The old +2.44rem was described as "stage padding above l1", which was
     true when the headline stayed at rest size and only the wordmark was
     large. Once both animate from display size that reasoning stopped
     holding.

     ⚠️ RE-DERIVE THIS WHENEVER THE HEADLINE SIZE CHANGES. O_height scales
     with the type, so a bigger headline eats the clearance. -2.6rem gave
     +37px at a 99px opening and -11px at 125px — it went straight back to
     overlapping. -4.9rem restored roughly +25px at 125px.

     2026-08-16: headline 125px -> 112px at Ari's request, and this had to move
     WITH it. The failure runs both ways — a smaller headline has a shorter
     O_height, so the same gap over-corrects and pulls the wordmark too far from
     its own sentence. Measured across the animation at 112px: -4.9rem gave 40px
     of clearance, well past the ~25px the sentence wants, and it read as two
     separate lines rather than one. Values sampled at 112px were -4.2 -> 29px,
     -3.8 -> 22px, -3.4 -> 16px, so -4.0rem lands on ~25px and restores the
     original relationship. Zero collisions at every value tested; this is about
     how the sentence READS, not about overlap.

     2026-08-16, AGAIN: headline 112px -> 99px so it matches --t-title and the
     other three page titles, and the gap moved a THIRD time. -4rem then gave
     40px — the identical over-correction, one size down. Sampled at 99px:
     -3.4 -> 31px, -3.0 -> 24px, -2.6 -> 18px. -3rem holds the ~25px.

     Three size changes, three re-derivations, and every one of them was a
     number that looked right until it was measured. If the headline moves
     again, measure this. Do not carry the old value forward. */
  --rw-headline-gap: -3rem;

  /* ⚠️ ONE DURATION FOR THREE ANIMATIONS. The overture, the headline
     settle and the sub fade all read as a single gesture, so they have to
     share a clock — three separate literals is how they drift apart the
     first time one gets retuned. 1.55s -> 3s on 2026-08-13, because Ari
     said the old opening was "taking place too quickly to seem
     intentional". Retune here, not in the keyframes. */
  --rw-overture-dur: 3s;
}
@media (max-width:1100px){
  :root{--rw-nav-pad-y:1.8rem;--rw-nav-pad-x:2.2rem}
}
@media (max-width:780px){
  :root{--rw-nav-pad-y:1.2rem;--rw-nav-pad-x:1.2rem}
}

.rw-page-home .rw-overture{
  position:absolute;z-index:45;
  left:var(--rw-nav-pad-x);top:var(--rw-nav-pad-y);
  font-family:'Remboy',Georgia,serif;font-weight:400;
  font-size:2.05rem;line-height:.9;color:var(--fg);
  transform-origin:left top;white-space:nowrap;
  pointer-events:none;
  animation:rw-overture var(--rw-overture-dur) cubic-bezier(.6,0,.2,1) both .55s;
}

/* ⚠️ THE HOLD AT 0-32% IS THE POINT OF THIS ANIMATION, not padding.
   Ari: "its taking place too quickly to seem intentional... 'roman west'
   text should have a proper featured moment."

   The old keyframes went straight from 0% into the move, so the wordmark
   was already travelling on the first frame a visitor could see it. There
   was no moment to read — the mechanic they said they loved was over
   before it registered as deliberate.

   Now it sits still at display size while the eye lands on it, THEN goes.
   Because fill-mode is `both`, the 0% state also holds through the .55s
   delay, so the real featured beat is .55 + (.32 x 3s) = 1.51s before
   anything moves, against roughly zero before.

   The travel is slower too: 44% of 3s is 1.32s against 70% of 1.55s, so
   1.08s. And it now rests at nav size from 76-88% before fading, so the
   arrival reads as an arrival rather than a handoff caught mid-stride. */
@keyframes rw-overture{
  0%,32% {transform:translateY(var(--rw-overture-line));font-size:var(--rw-display)}
  76%    {transform:translateY(0);font-size:2.05rem;opacity:1}
  88%    {transform:translateY(0);font-size:2.05rem;opacity:1}
  100%   {transform:translateY(0);font-size:2.05rem;opacity:0}
}

/* The real wordmark is a link, so the overture cannot be it. The link
   fades in underneath at identical position and size as the overture
   fades out, which makes the handoff invisible. */
/* Delay tracks the overture's fade window (88-100% of 3s, plus the .55s
   delay), so the two still cross over exactly. Hand-computed, and it has to
   be recomputed if --rw-overture-dur changes: .55 + (3 x .88) = 3.19s. */
.rw-page-home .rw-nav .mark{animation:rw-mark-in .36s ease both 3.19s}
@keyframes rw-mark-in{from{opacity:0}to{opacity:1}}

/* The whole stage moves, not .stage h1 — "conquer." and "Offer
   yourself." are h2s, so animating the h1 alone dropped the first line
   into the others. */
/* Holds to 32% to match the overture exactly. If these two diverge the
   headline starts drifting while the wordmark is still holding, which
   reads as two unrelated animations rather than one gesture. */
.rw-page-home .stage{
  animation:rw-settle var(--rw-overture-dur) cubic-bezier(.6,0,.2,1) both .55s;
}
@keyframes rw-settle{
  0%,32% {transform:translateY(calc(var(--rw-overture-line) - var(--rw-headline-gap)))}
  100%   {transform:translateY(0)}
}
.rw-page-home .sub{
  animation:rw-settle-sub var(--rw-overture-dur) cubic-bezier(.6,0,.2,1) both .55s;
}
@keyframes rw-settle-sub{
  0%,32% {transform:translateY(calc(var(--rw-overture-line) - var(--rw-headline-gap)));opacity:0}
  58%    {opacity:0}
  100%   {transform:translateY(0);opacity:1}
}

/* Below 1100 the shell drops from grid to block, so the hero stacks
   above the headline and pushes it ~700px down the page. The overture
   would sit alone at the top with half a screen between it and "does
   not" — not a sentence, just two unrelated things. The effect only
   means anything when the two are adjacent, so it is desktop-only. */
@media (max-width:1100px){
  .rw-page-home .rw-overture{display:none}
  /* ⚠️ h1 IS IN THIS LIST ON PURPOSE — 2026-08-17. It used to be excluded and
     given `animation:rw-line-in` instead, a 38px rise meant as a small
     substitute for the overture the line above kills. It was a bug, and an
     invisible one to whoever wrote it: .stage h1 is ONLY "does not". The two
     lines that finish the sentence, "conquer." and "Offer yourself.", are h2,
     so they sat still while their own first line slid up under them. The
     headline is one sentence broken across three elements and animating a
     third of it reads as a glitch, which is exactly how Ross found it —
     "'does not' has an animation on it, rest of text doesn't."

     ⚠️ AND THE LINE ABOVE IT DOES NOT WORK. `.rw-page-home .rw-overture
     {display:none}` here is DEAD — overridden by the wordmark-mask group
     around line 2949, which lists .rw-page-home .rw-overture among the
     elements it sets to display:inline-block. Same specificity (0,2,0),
     later in the file, so it wins. THE OVERTURE RUNS AT EVERY WIDTH.
     Measured at 390: visible and cream at t800, travelling 19,57 243x48 ->
     19,19 192x38 between ~2.0s and ~3.4s, then opacity 0 at 4.6s as the nav
     wordmark fades in on its 4.07s delay. The handoff is clean, so this has
     been shipping as a working effect rather than a visible bug, which is
     why nobody caught the dead rule.

     That is left ALONE deliberately. Ross has reviewed mobile Home from a
     real phone repeatedly and never flagged it; deleting the site's signature
     opening from mobile is a design change nobody asked for. Do not "fix" the
     display:none by raising its specificity without asking first.

     It does mean the h1 entrance was never a substitute for an absent
     overture — it was a second animation competing with a running one. */
  .rw-page-home .stage,
  .rw-page-home .stage h1,
  .rw-page-home .sub{animation:none}
  .rw-page-home .rw-nav .mark{animation:none}
}

@media (prefers-reduced-motion:reduce){
  .rw-page-home .rw-overture{display:none}
  .rw-page-home .rw-nav .mark,
  .rw-page-home .stage,
  .rw-page-home .stage h1,
  .rw-page-home .sub{animation:none}
}


/* ============================================================
   THE CROW CURSOR
   Traced from assets/images/newcrow.png — a cut metal plate, so the
   shape is genuinely all straight lines. Segmented on the blue of the
   metal, boundary-traced, then Ramer-Douglas-Peucker'd down to 59
   points. The reduction is the tell: a hard-edged plate collapses to a
   small polygon, where a photographed bird would not.

   Two artefacts of the photograph were removed, not drawn around:
     - the rock occludes the lower edge, so rows below the cut are
       discarded rather than traced as if they were metal;
     - the plate is shot at a slight angle, so the right half is
       mirrored onto the left. The sculpture is symmetric; the
       photograph of it is not.
   The fan tail ends flat because that is where the rock begins. How
   much further it runs is not visible in the photograph, so it is not
   invented here.

   THE SPIRE TIP IS THE HOTSPOT — topmost point, dead centre, so the
   click lands where the eye reads the point. Hotspot is derived from
   the traced viewBox, never measured on screen.
   ============================================================ */
:root:has(body.rw-bone),body.rw-bone{
  cursor:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 119' width='30' height='18'%3E%3Cpath fill='%2317140F' d='M100.0 0.0 L103.0 2.0 L107.9 16.8 L115.3 29.2 L128.7 41.6 L157.9 52.0 L169.3 58.4 L183.7 58.9 L190.1 64.9 L198.0 77.7 L200.0 119.3 L187.1 118.3 L165.8 81.7 L164.9 83.7 L167.8 97.0 L160.9 94.1 L153.0 86.1 L153.5 97.0 L153.0 100.5 L151.0 101.0 L142.6 90.6 L134.7 84.2 L131.7 84.7 L134.7 91.6 L131.2 94.1 L113.4 77.7 L112.9 81.7 L118.3 97.0 L125.7 110.4 L128.2 117.8 L127.2 119.3 L72.8 118.3 L82.7 97.0 L88.1 81.7 L87.6 77.7 L69.8 94.1 L66.3 91.6 L69.3 84.7 L66.3 84.2 L58.4 90.6 L50.0 101.0 L48.0 100.5 L47.5 97.0 L48.0 86.1 L40.1 94.1 L33.2 97.0 L36.1 83.7 L35.1 81.7 L13.9 118.3 L0.0 118.3 L3.0 77.7 L10.9 64.9 L17.3 58.9 L31.7 58.4 L43.1 52.0 L72.3 41.6 L77.7 37.6 L88.6 24.8 L100.0 0.0 Z'/%3E%3C/svg%3E") 15 0,default;
}
:root:has(body.rw-dark),body.rw-dark{
  cursor:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 119' width='30' height='18'%3E%3Cpath fill='%23E6E0D4' d='M100.0 0.0 L103.0 2.0 L107.9 16.8 L115.3 29.2 L128.7 41.6 L157.9 52.0 L169.3 58.4 L183.7 58.9 L190.1 64.9 L198.0 77.7 L200.0 119.3 L187.1 118.3 L165.8 81.7 L164.9 83.7 L167.8 97.0 L160.9 94.1 L153.0 86.1 L153.5 97.0 L153.0 100.5 L151.0 101.0 L142.6 90.6 L134.7 84.2 L131.7 84.7 L134.7 91.6 L131.2 94.1 L113.4 77.7 L112.9 81.7 L118.3 97.0 L125.7 110.4 L128.2 117.8 L127.2 119.3 L72.8 118.3 L82.7 97.0 L88.1 81.7 L87.6 77.7 L69.8 94.1 L66.3 91.6 L69.3 84.7 L66.3 84.2 L58.4 90.6 L50.0 101.0 L48.0 100.5 L47.5 97.0 L48.0 86.1 L40.1 94.1 L33.2 97.0 L36.1 83.7 L35.1 81.7 L13.9 118.3 L0.0 118.3 L3.0 77.7 L10.9 64.9 L17.3 58.9 L31.7 58.4 L43.1 52.0 L72.3 41.6 L77.7 37.6 L88.6 24.8 L100.0 0.0 Z'/%3E%3C/svg%3E") 15 0,default;
}

/* clickable: oxblood ascending, amber descending */
body.rw-bone a,body.rw-bone button,body.rw-bone summary,body.rw-bone label,
body.rw-bone select,body.rw-bone [role="button"],body.rw-bone input[type="submit"],
body.rw-bone input[type="checkbox"],body.rw-bone input[type="radio"],
body.rw-bone input[type="file"]{
  cursor:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 119' width='30' height='18'%3E%3Cpath fill='%237B2116' d='M100.0 0.0 L103.0 2.0 L107.9 16.8 L115.3 29.2 L128.7 41.6 L157.9 52.0 L169.3 58.4 L183.7 58.9 L190.1 64.9 L198.0 77.7 L200.0 119.3 L187.1 118.3 L165.8 81.7 L164.9 83.7 L167.8 97.0 L160.9 94.1 L153.0 86.1 L153.5 97.0 L153.0 100.5 L151.0 101.0 L142.6 90.6 L134.7 84.2 L131.7 84.7 L134.7 91.6 L131.2 94.1 L113.4 77.7 L112.9 81.7 L118.3 97.0 L125.7 110.4 L128.2 117.8 L127.2 119.3 L72.8 118.3 L82.7 97.0 L88.1 81.7 L87.6 77.7 L69.8 94.1 L66.3 91.6 L69.3 84.7 L66.3 84.2 L58.4 90.6 L50.0 101.0 L48.0 100.5 L47.5 97.0 L48.0 86.1 L40.1 94.1 L33.2 97.0 L36.1 83.7 L35.1 81.7 L13.9 118.3 L0.0 118.3 L3.0 77.7 L10.9 64.9 L17.3 58.9 L31.7 58.4 L43.1 52.0 L72.3 41.6 L77.7 37.6 L88.6 24.8 L100.0 0.0 Z'/%3E%3C/svg%3E") 15 0,pointer;
}
body.rw-dark a,body.rw-dark button,body.rw-dark summary,body.rw-dark label,
body.rw-dark select,body.rw-dark [role="button"],body.rw-dark input[type="submit"],
body.rw-dark input[type="checkbox"],body.rw-dark input[type="radio"],
body.rw-dark input[type="file"]{
  /* Follows --accent in descend mode. The colour is baked into the data URI,
     so it does NOT move when the variable changes — it was missed on the
     2026-08-10 accent swap and hovering a link showed an orange crow against
     a dusty-rose page. If --accent changes again, change this too. */
  cursor:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 119' width='30' height='18'%3E%3Cpath fill='%23BF7F7D' d='M100.0 0.0 L103.0 2.0 L107.9 16.8 L115.3 29.2 L128.7 41.6 L157.9 52.0 L169.3 58.4 L183.7 58.9 L190.1 64.9 L198.0 77.7 L200.0 119.3 L187.1 118.3 L165.8 81.7 L164.9 83.7 L167.8 97.0 L160.9 94.1 L153.0 86.1 L153.5 97.0 L153.0 100.5 L151.0 101.0 L142.6 90.6 L134.7 84.2 L131.7 84.7 L134.7 91.6 L131.2 94.1 L113.4 77.7 L112.9 81.7 L118.3 97.0 L125.7 110.4 L128.2 117.8 L127.2 119.3 L72.8 118.3 L82.7 97.0 L88.1 81.7 L87.6 77.7 L69.8 94.1 L66.3 91.6 L69.3 84.7 L66.3 84.2 L58.4 90.6 L50.0 101.0 L48.0 100.5 L47.5 97.0 L48.0 86.1 L40.1 94.1 L33.2 97.0 L36.1 83.7 L35.1 81.7 L13.9 118.3 L0.0 118.3 L3.0 77.7 L10.9 64.9 L17.3 58.9 L31.7 58.4 L43.1 52.0 L72.3 41.6 L77.7 37.6 L88.6 24.8 L100.0 0.0 Z'/%3E%3C/svg%3E") 15 0,pointer;
}

/* Typing needs an I-beam. A crow cannot show a caret position. */
body.rw-bone input[type="text"],body.rw-bone input[type="email"],
body.rw-bone input[type="tel"],body.rw-bone input[type="url"],
body.rw-bone input[type="number"],body.rw-bone input[type="search"],
body.rw-bone input[type="password"],body.rw-bone textarea,
body.rw-dark input[type="text"],body.rw-dark input[type="email"],
body.rw-dark input[type="tel"],body.rw-dark input[type="url"],
body.rw-dark input[type="number"],body.rw-dark input[type="search"],
body.rw-dark input[type="password"],body.rw-dark textarea{cursor:text}

/* ============================================================
   SUBMIT — usability pass, 2026-08-03
   THIS BLOCK IS LAST AND THEREFORE WINS. Earlier .next / form-width /
   .dial rules are superseded here; edit this block, not those.

   Measured before changing anything, at 1512x982:
     - "Continue" sat at y=973-997, i.e. clipped by the fold
     - "FOUR PARTS" rendered at 1.91:1 — 0.6 colour alpha multiplied by
       a further 0.6 element opacity, which looks unintentional
     - the terms line dropped to 3.60:1 where a thorn passed behind it
     - the form used 740px of 1161px available, leaving a 421px void

   The aesthetic is untouched: thorns, rotation, Remboy, broken
   baselines, no filled buttons. Only contrast, width and weight move.
   ============================================================ */

/* ---- 1. the form fills the same band as the text above it ----
   Ross's layout is text-wide-on-top, form beneath. The form was capped
   at 740px while the text ran 1121px, so the two bands did not read as
   one column and the page sat bottom-left-heavy. */
.rw-page-submit form{max-width:none}
/* Two equal columns spanning the band, so a field row actually fills the
   width. auto-fill was tried first: it made 3 tracks and left the third
   empty, which read as an unfinished row. */
.rw-page-submit .two{grid-template-columns:1fr 1fr;gap:0 2.4rem}
/* Single fields keep a readable cap and sit short on purpose — .drift is
   already the deliberate-asymmetry hook on this page. */
.rw-page-submit .step > .drift :is(input[type=text],input[type=email],input[type=tel],select){
  max-width:34rem;
}
.rw-page-submit .step textarea{max-width:46rem}
.rw-page-submit .consent{max-width:46rem}

/* ---- 2. the action carries weight ----
   Still typeset, not chrome — no fill, no chip. Accent, size, and a rule
   under it. It is the only accent-coloured interactive thing below the
   fold, so it reads as the one thing to do. */
.rw-page-submit :is(.next,button[type=submit]){
  display:inline-flex;align-items:center;
  font-size:2.15rem;color:var(--accent);
  padding:.45rem 0 .4rem;min-height:44px;
  border-bottom:1px solid var(--accent);
  transition:color .25s ease,border-color .25s ease,
             transform .35s cubic-bezier(.2,.7,.3,1);
}
.rw-page-submit :is(.next,button[type=submit]):hover{
  color:var(--fg);border-bottom-color:var(--fg);transform:translateX(6px);
}
.rw-page-submit :is(.next,button[type=submit]):focus-visible{
  outline:1px solid var(--accent);outline-offset:6px;
}
/* Back stays deliberately quiet — one action per screen */
.rw-page-submit .back{
  font-size:1.15rem;color:var(--muted);border-bottom:0;min-height:44px;
  display:inline-flex;align-items:center;
}

/* ---- 3. the money terms get the scrim the standing rule requires ----
   "Reading matter never sits raw on the thorns; pricing legibility is
   not negotiable." The form already had this. This block did not, and
   it is the block carrying the deposit and cancellation terms. */
.rw-page-submit .terms2{position:relative}
.rw-page-submit .terms2::before{
  content:'';position:absolute;inset:-.9rem -1.7rem -1.1rem;z-index:-1;
  pointer-events:none;
  background:radial-gradient(ellipse at 38% 50%,var(--scrim) 60%,transparent 92%);
}

/* ---- 4. the step counter becomes legible ----
   .of carried opacity .6 on top of a .6 colour alpha, so it rendered at
   0.36 effective. The dial is the only thing telling an applicant this
   has four parts and an end, which matters on a form this long. */
/* ⚠️ THIS IS THE THIRD DECLARATION OF THIS SELECTOR (573, 859, here) and
   the last one wins. The sentence-case pass on 2026-08-12 set .04em at 573
   and the change did nothing, because both later copies still carried cap
   tracking. Same for .step label, declared at 875 and 1448.

   Editing a value "at source" means nothing in this file until you have
   grepped for every other copy of the selector. Measure the COMPUTED value
   afterwards, never assume the edit landed — that is how this was caught. */
.rw-page-submit .dial .of{
  font-size:calc(.66rem * var(--ui-scale));letter-spacing:.04em;opacity:1;color:var(--muted);
}
.rw-page-submit .dial label{opacity:.72}

/* ---- 5. tighten the top band so the first field and the CTA clear the fold ---- */
.rw-page-submit .stage{padding-top:1.1rem}
.rw-page-submit .cols2{gap:1.7rem}
.rw-page-submit .process h2{margin-bottom:.75rem}
.rw-page-submit .terms2{margin-top:1.1rem;padding-top:.9rem}
.rw-page-submit .steps{gap:1.2rem 2.6rem}
.rw-page-submit .dial{margin-bottom:1.1rem}
.rw-page-submit .step h3{margin-bottom:1.1rem}

/* Responsive repair for the block above. Because it is last in the file it
   silently overrode two earlier responsive rules: the ≤900 form cap (the form
   rendered WIDER than the text band it is meant to align with) and the ≤780
   single-column field rule (fields stayed 2-up at 336px, ~168px each). Any
   later addition to the submit form has to re-state these. */
@media (max-width:900px){
  .rw-page-submit form{max-width:640px}      /* == .process at this band */
}
@media (max-width:780px){
  .rw-page-submit .two{grid-template-columns:1fr;gap:0}
  .rw-page-submit :is(.next,button[type=submit]){font-size:1.7rem}
}

/* ============================================================
   HORIZONTAL OVERFLOW — the decorative layers were growing the document
   Found 2026-08-03 by sweeping 4 pages x 11 widths. Home, Lore and
   Offerings all scrolled sideways on narrow viewports; Submit did not.

   Two unrelated causes, one fix:

   1. THE SCRIMS. Every quiet-zone pseudo uses negative horizontal insets
      (-2.6rem / -1.6rem) so the soft ground extends past its text. That
      is correct on a wide screen and lethal on a narrow one, because a
      pseudo-element still grows the scroll area even though it never
      appears in its parent's bounding box — which is why the usual
      "find the too-wide element" hunt turns up nothing.
      Offenders: .lede::before, .deeper::before, .prose::before, .rates::after
   2. THE ROTATED HERO. A rotated box's bounding rect is wider than its
      layout width: .hero measured 414px inside a 390px viewport.

   overflow-x:clip, not hidden. `clip` does not create a scroll container,
   so it cannot break position:sticky, and it composes with overflow-y:
   visible where hidden would silently force auto. The thorns are injected
   at body level, outside .rw-shell, so they still bleed as intended.
   ============================================================ */
.rw-shell{overflow-x:clip}

/* Tap targets were width-gated at <=780, so a tablet in portrait at 834
   still got 19px nav links. Pointer type is the honest test, not width. */
@media (pointer:coarse){
  .rw-nav .rw-link a{padding-top:.78rem;padding-bottom:.78rem;display:inline-block}
  .rw-page-submit .step label:not(.next):not(.back){min-height:32px;align-items:center}
}

/* ============================================================
   SUBMIT — form backend, 2026-08-04
   THIS BLOCK IS NOW LAST AND THEREFORE WINS. It sits after the
   2026-08-03 usability pass deliberately. File order has bitten
   this page twice; anything added later must go BELOW this.

   Purely additive. Every selector here is a class that did not
   exist before today, so nothing above is overridden and the
   hard-won layout is untouched.
   ============================================================ */

/* ---- honeypot ----
   Off-screen rather than display:none. A bot that parses CSS skips
   display:none fields, so hiding it that way defeats the point. It
   keeps a real visible label so that if the stylesheet ever fails
   to load, a human sees an instruction rather than a mystery box. */
.rw-page-submit .rw-hp{
  position:absolute;left:-9999px;top:auto;
  width:1px;height:1px;overflow:hidden;
}

/* ---- radio-group heading ----
   Was a bare <label> with no `for`, which associated with nothing and
   was invalid. Now a <span> inside role="radiogroup", carrying the
   exact styling the old label had so nothing moves by a pixel. */
.rw-page-submit .rw-grouplabel{
  display:block;font-size:calc(.8rem * var(--ui-scale));line-height:1.4;margin-bottom:.24rem;
  font-family:var(--ui-font);letter-spacing:.04em;
}

/* ---- field errors ----
   Sits directly under its input, in the accent, pulled up into the
   margin the input already reserves so adding an error does not
   shove the rest of the step downward. */
.rw-page-submit .rw-err{
  font-size:calc(.74rem * var(--ui-scale));line-height:1.45;color:var(--accent);
  font-family:var(--ui-font);letter-spacing:.03em;
  margin:-.72rem 0 .9rem;
}

/* ---- the summary above the form ----
   Card-backed, because reading matter never sits raw on the thorns. */
.rw-page-submit .rw-errsum{
  position:relative;z-index:1;
  border-left:2px solid var(--accent);
  background:var(--scrim);
  padding:.85rem 1.1rem;margin:0 0 1.6rem;
  transform:rotate(-.35deg);
}
.rw-page-submit .rw-errsum h4,
.rw-page-submit .rw-errsum p{
  margin:0;font-family:var(--ui-font);
  font-size:calc(.82rem * var(--ui-scale));line-height:1.5;letter-spacing:.03em;
  color:var(--fg);
}

/* ---- the confirmation that replaces the form ----
   Inherits .rw-rough, so the torn edge stays and the page reads as
   finished rather than empty. */
.rw-page-submit .rw-confirm{
  align-self:start;
  padding:2.4rem 2.2rem 2.6rem;
}
.rw-page-submit .rw-confirm h3{
  font-size:2.4rem;line-height:1;margin:0 0 .9rem;
  transform:rotate(-.8deg);
}
.rw-page-submit .rw-confirm p{
  margin:0;max-width:34rem;
  font-size:1rem;line-height:1.6;color:var(--muted);
}

/* ---- keyboard route through the wizard ----
   The .stepctl radios are opacity:0 but are the ONLY keyboard path
   through the steps, because .next/.back/.dial are <label>s and
   labels are not focusable. Do NOT give them tabindex="-1": that
   makes the form keyboard-inoperable. What was missing is a visible
   focus indicator, so a keyboard user can see where they are. */
.rw-page-submit form:has(#st1:focus-visible) .dial label[for=st1],
.rw-page-submit form:has(#st2:focus-visible) .dial label[for=st2],
.rw-page-submit form:has(#st3:focus-visible) .dial label[for=st3],
.rw-page-submit form:has(#st4:focus-visible) .dial label[for=st4]{
  outline:2px solid var(--accent);outline-offset:3px;
}

@media (max-width:780px){
  .rw-page-submit .rw-confirm{padding:1.8rem 1.4rem 2rem}
  .rw-page-submit .rw-confirm h3{font-size:1.9rem}
  .rw-page-submit .rw-errsum{margin-bottom:1.2rem}
}

/* ============================================================
   ARI'S FEEDBACK PASS — 2026-08-10
   THIS BLOCK IS NOW LAST AND THEREFORE WINS. It sits after the
   2026-08-04 form-backend block deliberately. Anything added
   later must go BELOW this. File order has bitten this page
   three times now.

   Ari's note: "go a bit more sleek and understated, create more
   visual space and legibility", and specifically "square all
   photos and remove the drop shadow". "Square" means de-rotate,
   NOT crop to 1:1 — confirmed with Ross.

   Written as overrides rather than edits at source on purpose:
   the hero rotations are re-stated inside two media queries, so
   editing the base rules alone would leave them tilted below
   1100px. Equal specificity, later in the file, so this wins at
   every width.
   ============================================================ */

/* ---- photos sit flat and calm ----
   ⚠️ SUPERSEDED 2026-08-14, and the original wording is kept below so the
   reversal is legible rather than looking like this block was always
   half-applied.

   This used to read: "Type and UI rotation stays. Ari asked to square the
   PHOTOS, not to flatten the design — the rates cards, step headings,
   buttons and the 'Received.' confirmation all keep their tilt."

   That was a correct reading of Ari's note and held for three days. Ross
   then asked for straight lines across the board, so type is de-rotated too
   now. See the TEXT SITS STRAIGHT block at the very end of this file, which
   is where the de-rotation lives and which explains why it is an explicit
   list rather than a blanket reset. This block still owns the PHOTOS. */
.rw-page-home .hero,
.rw-page-lore .lore-hero,
.rw-page-offerings .hero{
  transform:none;
  box-shadow:none;
}

/* ---- Lore's photograph loses its thorn overlay ----
   Ari: "replace with a singular large photo WITHOUT transparency."
   Offerings keeps its overlay — they called that one out as
   working. The overlay is re-declared across eight rules for the
   various widths and modes, so it is switched off once here. */
.rw-page-lore .lore-hero::after{display:none}

/* ---- and gets bigger ----
   PROVISIONAL. Ari is supplying the real photograph ("it will be
   one of these, once edited"), so this is sized for the current
   placeholder and will want revisiting once the actual image and
   its crop arrive. */
@media (min-width:1101px){
  .rw-page-lore .lore-hero{width:min(780px,52vw)}
}

/* ---- Lore hero: put it back in the flow on narrow screens ----
   ⚠️ PRE-EXISTING BREAK, found by rendering at 430 on 2026-08-11. Not
   caused by this round, but this round changed what it looked like.

   The @media (max-width:1100px) block at ~639 sets the hero to
   position:relative so it drops into the flow. Two later rules, both
   OUTSIDE any media query, were overriding it:

     line ~1071  .rw-page-lore .lore-hero{position:absolute}
     line ~1422  .rw-page-lore .lore-hero{top:60px;right:84px;width:664px}

   The first has a comment explaining itself — a group selector during the
   thorn-overlay work had clobbered the hero's absolute positioning and this
   restored it — but restoring it unmediated also silently undid the
   responsive behaviour. Since then the hero has been absolutely positioned
   at every width.

   It was invisible before because at 430 a 664px-wide absolute box sits
   almost entirely off-screen left and overflow-x:clip hides it. Narrowing
   the hero this round brought it back on-screen, where it landed on top of
   the lede and covered the photograph.

   THE LESSON IS THE ONE THIS FILE KEEPS TEACHING: an unmediated rule placed
   after a media query wins at every width, so "fixing" a desktop regression
   below the responsive block quietly breaks every smaller screen. */
@media (max-width:1100px){
  .rw-page-lore .lore-hero{
    position:relative;
    top:auto;right:auto;left:auto;
    width:auto;max-width:min(820px,78%);
    margin:1.6rem 2.2rem 0;
    z-index:30;
  }
}
@media (max-width:780px){
  .rw-page-lore .lore-hero{max-width:100%;margin:1.2rem 1.2rem 0}
}

/* ---- breathing room where the strip used to be ----
   "create more negative space between text and photos" */
.rw-page-lore .lede{padding-top:2.2rem}
.rw-page-lore .deeper{padding-top:4.6rem}

/* ---- UNUSED as of 2026-08-10 ----
   .striplab, .strip, .strip-veil and .sv1/.sv2 above are retained
   but no longer rendered; the markup came out of patterns/lore.php.
   Kept so restoring the strip is a markup change alone. Delete
   both together if Ari confirms at review. */

/* ---- legibility: Lore and Offerings body copy ----
   Ari, on Lore: "the paragraph text feels like its getting lost. increase
   font size? or add a more distinct background? not as bad in descend."
   On Offerings: "the paragraph text needs background to make it distinct
   from the thorns." And, pointing at the rates card: "this outline and
   opacity overlay is awesome! I wonder if we can repeat this style
   elsewhere where text needs more background."

   THE GROUND ALREADY EXISTED AND WAS TOO WEAK. .prose, .lede and .deeper
   all carry a ::before scrim, but it runs solid to only 42% of its ellipse
   and is fully transparent by 76%, so the text edges sit on half-covered
   ground and the thorns read through. The rates card — the block Ari
   likes — runs 70% to 96%. That gap is the whole complaint. Matched here.

   "Not as bad in descend" tracks: bone carries heavier thorns
   (--thorn-op-1 .53 vs .50) and a lighter scrim to hide them against.

   NOT DONE HERE: the border half of the rates treatment. Ari raised it as
   a question rather than a direction ("I wonder if we can"), and ruled
   lines across every text block is a bigger visual move than they may
   have meant. Worth showing them as an option rather than assuming. */
/* CORRECTED after Ross identified the actual screenshot. Ari was pointing at
   the "Received." confirmation panel, which carries .rw-rough: a near-solid
   --card-bg panel, a 1.5px --card-bd outline, and the #rw-roughedge filter
   that tears the border into a drawn line. That is what "outline and opacity
   overlay" means here, not a soft scrim.

   Worth knowing: .rates carried this treatment once and had it switched off
   in the 2026-08-03 pass (".rates.rw-rough::before{display:none}", commented
   "document, not panel"), so the rates card on Offerings is NOT what they
   saw. Only the Received panel still has it.

   Applied by converting each block's existing scrim pseudo into the panel,
   rather than adding .rw-rough to the markup — both use ::before, so they
   cannot coexist on the same element.

   ⚠️ THE INSETS ARE GEOMETRY-SENSITIVE AND UNVERIFIED. A soft scrim fades
   out and forgives a wrong inset; a hard-edged torn panel does not. .prose
   carries no padding of its own, while .lede and .deeper carry 3.2rem
   horizontal page padding, so the panel has to pull INWARD on those two or
   it bleeds off the left edge. These values are derived from the padding,
   not measured. They need a look. */
.prose::before{
  inset:-1.5rem -1.3rem;
  background:var(--card-bg);
  border:1.5px solid var(--card-bd);
  filter:url(#rw-roughedge);
}
.rw-page-lore .lede::before{
  inset:-0.9rem 1.8rem;
  background:var(--card-bg);
  border:1.5px solid var(--card-bd);
  filter:url(#rw-roughedge);
}
@media (max-width:1100px){
  .rw-page-lore .lede::before{inset:-0.9rem 0.8rem}
}
@media (max-width:780px){
  .prose::before{inset:-1rem -0.7rem}
  .rw-page-lore .lede::before{inset:-0.8rem 0.2rem}
}

/* ---- "a deeper sense" gets the panel after all ----
   REVERSES an earlier call in this same block. I first left this as a soft
   scrim, arguing the geometry was wrong: the block runs 900px while the
   widest inked line stops at 715, so a block-sized panel leaves 185px of
   empty ground, and pinning it with a fixed right inset would be a measured
   pixel beside fluid text.

   The geometry objection was real. The conclusion was not. The fix is to
   constrain the BLOCK so it hugs its own content, rather than to pin the
   panel — 790px brings the gap after the widest ink down to 56px, which
   reads as a margin instead of emptiness, and it stays correct if Ari edits
   a line because nothing is pinned to today's text.

   Ross read Ari's "I wonder if we can repeat this style elsewhere" as
   covering this section, and their note that it "needs its own defining
   moment" is answered better by a panel than by a slightly stronger fade.

   Rendered before committing: the two Lore panels differ in width and the
   photograph cuts across the corner, so it does not read as two stacked
   boxes — which was the other half of my original objection.

   ⚠️ The 790px cap slightly changes where the indented lines wrap. That is
   a consequence, not a bug, but it is worth knowing if the copy changes. */
.rw-page-lore .deeper{max-width:790px}
.rw-page-lore .deeper::before{
  inset:-1.6rem 1.2rem -1.8rem 1.6rem;
  background:var(--card-bg);
  border:1.5px solid var(--card-bd);
  filter:url(#rw-roughedge);
}
@media (max-width:1100px){
  .rw-page-lore .deeper::before{inset:-1.4rem 0.6rem -1.6rem 0.9rem}
}
@media (max-width:780px){
  .rw-page-lore .deeper::before{inset:-1.1rem -0.4rem -1.3rem -0.2rem}
}

/* ---- the rates ledger gets it too ----
   ⚠️ THIS REVERSES A DELIBERATE DECISION, at Ross's instruction. The
   2026-08-03 pass switched this panel off with
   ".rates.rw-rough::before{display:none}" and rebuilt the block as ruled
   lines, commented "document, not panel" — the same reasoning that stripped
   the form's panel.

   Ari's "this outline and opacity overlay is awesome! I wonder if we can
   repeat this style elsewhere where text needs more background" reads as an
   invitation to put it back, and Ross made the call.

   The markup never changed: patterns/offerings.php has always carried
   class="rates rw-rough". Only the CSS was suppressing it.

   The ::after radial scrim that replaced it is switched off here, since the
   panel now supplies the ground and stacking both would double-darken the
   area behind the pricing.

   Note this is quieter in descend than it looks on the bone pages:
   --card-bg is rgba(19,17,15,.88) against a #0B0A09 background, so the fill
   barely separates and the torn border does most of the work. */
.rates.rw-rough::before{
  display:block;
  inset:-1.4rem -1.6rem;
  background:var(--card-bg);
  border:1.5px solid var(--card-bd);
  filter:url(#rw-roughedge);
}
.rates::after{display:none}
@media (max-width:780px){
  .rates.rw-rough::before{inset:-1.1rem -0.9rem}
}

/* Ari's own first suggestion for Lore, applied alongside the ground rather
   than instead of it.

   ⚠️ SUPERSEDED 2026-08-13, and the old text is kept here only so the change
   reads as deliberate: this comment used to end "Offerings is left at its
   current size: they asked for background there, not scale." That was true
   when written and is not any more. Ross, seeing the three pages measured
   side by side at 21/19/16px: "Keep 21px on home. And then standardize to
   19px on Lore and Offerings."

   Every font-size that was here is now in the type scale at the end of this
   file as --t-body. The ≤780 pair went with it — it set 1.06rem on .lede and
   1.04rem on .deeper p, a 1px split that was drift rather than a decision,
   and the token now steps both together. Line-heights stay here because they
   are not tokenised and differ per role by design. */
.rw-page-lore .lede{line-height:1.72}
.rw-page-lore .deeper p{line-height:1.66}

/* ---- Offerings: the prices read as a document, not a flourish ----
   Ari, pointing at the split-screen screenshot: "the remboy is too playful
   here - should remain straight forward."

   The Remboy in that frame is the prices. First attempt moved them to
   Basteleur to match the labels, but rendering it showed that only went
   half way: Basteleur is still a characterful serif and "case by case" kept
   a swash. Ross compared both on the live page and chose the body face.

   The row now reads label in the house serif, number in plain sans, which
   is a real hierarchy rather than one face doing both jobs. It also suits
   Ari's wider direction this round, "a bit more sleek and understated".

   Size drops 1.5 -> 1.35rem: Karrik carries more visual weight than Remboy
   at the same nominal size. Accent colour and right alignment stay, since
   Ari asked about the typeface and not the emphasis. */
.rates dd{
  font-family:var(--body-font);
  letter-spacing:0;
  font-size:1.35rem;
}

/* ---- Submit: the form floats once you start ----
   Ari: "this is too chaotic - form should float in an opaque way one it's
   started." Ross confirmed this means the whole form panel going opaque on
   focus, and that it belongs to Submit rather than Offerings, where the note
   was filed.

   This does NOT undo the 2026-08-03 decision. That pass killed the panel
   deliberately — "the form is no longer a card, the panel is what made it
   feel like UI" — and replaced it with a single rule line. Both readings
   survive here: a document at rest, a panel once you engage. The panel is
   restored but starts invisible, so nothing changes until someone actually
   begins.

   The .rw-rough::before was display:none, which cannot transition. Swapped
   for opacity so it fades rather than snaps.

   "Started" is two conditions, not one. :focus-within covers the moment of
   typing, but it drops the instant focus leaves, which would flicker the
   panel away mid-application. So any step past the first also holds it open
   — once you have pressed Continue you are committed, and the ground should
   stay under you. */
.rw-page-submit form.rw-rough::before{
  display:block;
  inset:-1.5rem -1.8rem;
  opacity:0;
  transition:opacity .42s cubic-bezier(.2,.7,.3,1);
}
.rw-page-submit form.rw-rough:focus-within::before,
.rw-page-submit form.rw-rough:has(#st2:checked)::before,
.rw-page-submit form.rw-rough:has(#st3:checked)::before,
.rw-page-submit form.rw-rough:has(#st4:checked)::before{
  opacity:1;
}
@media (prefers-reduced-motion:reduce){
  .rw-page-submit form.rw-rough::before{transition:none}
}
@media (max-width:780px){
  .rw-page-submit form.rw-rough::before{inset:-1rem -0.9rem}
}

/* ---- photographs scale up, sitewide ----
   Ari: "i think the photos can scale up", alongside cutting to one strong
   image per page. Ross confirmed sitewide on 2026-08-11.

   Two halves to this.

   PHONE AND TABLET: full bleed on every page. Home already did this via the
   .hero !important blocks (margin:1.4rem 0 0, max-width:100%). Lore did not,
   because its element is .lore-hero and those blocks never selected it — see
   the runbook note. Offerings did not either, because its hero sits inside
   .cols, which carries the page padding, so it needs negative margins to
   reach the edge rather than just a wider max-width.

   DESKTOP: each hero grows by roughly a sixth. Kept in vw where the
   neighbour is fluid, per the standing rule that a measured pixel value
   sitting next to a fluid one is a latent bug. */

@media (min-width:1101px){
  .rw-page-lore .lore-hero{width:min(880px,58vw)}
  .rw-page-home .hero{width:min(680px,46vw)}
  .rw-page-offerings .hero{flex:1 1 540px;max-width:min(640px,44vw)}
}

/* tablet and phone: edge to edge, no side gutter.

   ⚠️ THE !important HERE IS NOT LAZINESS. The .hero blocks at ~265 and ~318
   already carry `margin:… 0 0 !important`, which zeroes the side margins to
   force heroes into the flow. Offerings' hero IS a .hero, so a plain
   negative margin loses to that and the image stays inside .cols' padding —
   measured at 430, it sat 19px in from each edge instead of bleeding.

   Matching !important with a more specific selector is the honest fix.
   Rewriting those older blocks to drop !important would be the cleaner one,
   but they are load-bearing for Home and this is not the round to find out
   what else depends on them.

   Lore needs no !important: .lore-hero is a different class and was never
   selected by those blocks, which is the same asymmetry that broke it at
   430 in the first place. */
@media (max-width:1100px){
  .rw-page-lore .lore-hero{
    max-width:100%;width:auto;margin:1.6rem 0 0;
  }
  .rw-page-offerings .hero{
    max-width:none !important;width:auto !important;
    margin:1.6rem -2.2rem 0 !important;        /* cancels .cols padding */
  }
}

@media (max-width:780px){
  .rw-page-lore .lore-hero{margin:1.4rem 0 0}
  .rw-page-offerings .hero{
    margin:1.4rem -1.2rem 0 !important;        /* .cols padding drops to 1.2rem */
  }
}

/* ---- panels: even breathing room, consistent layering ----
   Ross, seeing them in place: "the boxes are too tight, they need breathing
   room. Also, one overlapping the photo and the other underneath is strange."

   BOTH TRUE, AND THE FIRST WAS NOT REALLY TIGHTNESS — IT WAS ASYMMETRY.
   Measured on Lore before touching anything:

     .lede    left 22  right 33  top 54  bottom 20
     .deeper  left 25  right 56  top 93  bottom 35

   Bottom was a third of top on both. The cause is structural rather than a
   bad number: the panel is inset from the element box, but the element
   carries lopsided internal padding (.lede was 2.2rem top, 0 bottom), so a
   symmetric inset yields an asymmetric gap. Tuning the insets would work
   until someone edits the padding.

   So the padding now does the work and the panel sits near-flush. The gap is
   the padding, which means it stays even by construction:

     .lede    40 / 51 / 47 / 48
     .deeper  40 / 64 / 36 / 59

   .deeper's top-heavy 4.6rem became margin, so the separation between the
   two sections survives without inflating the panel.

   LAYERING. The photo is z-index 35. .lede was 40 (above it) and .deeper was
   30 (below), so one panel crossed the photograph and the other slid under
   and had its border stop mid-air.

   ⚠️ .lede's z-index 40 IS LOAD-BEARING, not arbitrary. Two of its lines
   physically overlap the photo by 37px and 20px, and are legible only
   because the panel sits above it. Dropping both panels below the photo —
   which was one of the options on the table — would have hidden real text.
   Measured before choosing, which is the only reason it was not chosen.

   So .deeper rises to 40 instead. Both panels now cross the photograph the
   same way, which also matches Ari's own instinct: "finding attachment
   points between text and photo rather than text and background". */
.rw-page-lore .lede{padding:2.6rem 3.2rem 2.6rem}
.rw-page-lore .lede::before{inset:0 0.7rem}

.rw-page-lore .deeper{padding:2.6rem 3.2rem 2.6rem;margin-top:2.4rem;z-index:40}
.rw-page-lore .deeper::before{inset:0 0.7rem}

@media (max-width:1100px){
  .rw-page-lore .lede{padding:2.2rem 2.2rem}
  .rw-page-lore .deeper{padding:2.2rem 2.2rem;margin-top:2rem}
  .rw-page-lore .lede::before,
  .rw-page-lore .deeper::before{inset:0 0.4rem}
}
@media (max-width:780px){
  .rw-page-lore .lede{padding:1.8rem 1.4rem}
  .rw-page-lore .deeper{padding:1.8rem 1.4rem;margin-top:1.6rem}
  .rw-page-lore .lede::before,
  .rw-page-lore .deeper::before{inset:0 -0.2rem}
}

/* Offerings was already near-even (21/21/28/29 on .prose, 25/25/45/48 on
   .rates) and only wanted more room. .prose carries no padding of its own —
   it sits inside .cols — so the inset does the work there rather than
   padding, which would narrow the measure. */
.prose::before{inset:-2.4rem -2.5rem}
.rates.rw-rough::before{inset:-1.4rem -2.5rem}
@media (max-width:780px){
  .prose::before{inset:-1.4rem -1rem}
  .rates.rw-rough::before{inset:-1.1rem -1rem}
}

/* ---- the rates card was colliding with the prose card ----
   Ross: "Pricing card still sitting too tightly on offerings page."

   The internal gaps were already fine (40/40/45/48). The crowding was
   BETWEEN the two panels, and they were not merely close — they overlapped
   by 26px:

     .rates margin-top            35.2px
     .prose panel extends down    38.4px  (inset -2.4rem)
     .rates panel extends up      22.4px  (inset -1.4rem)
     35.2 - 60.8                  = -25.6

   Negative insets expand a panel past its element box, so two stacked
   panels need a flow gap LARGER than the sum of the insets facing each
   other, or they collide. Easy to miss, because each element's own spacing
   looks perfectly reasonable in isolation.

   Two smaller crowding problems went with it: the panel sat 11px from the
   viewport edge and 24px from the photograph, because a -2.5rem side inset
   was eating most of .cols' 3.2rem page padding.

   Fixed the same way as Lore: horizontal padding on the element does the
   internal spacing, the inset stays small, and the margin carries the gap
   between cards.

     internal L/R   25.6 padding + 14.4 inset = 40
     to viewport    51.2 page padding - 14.4  = 37
     between cards  89.6 margin - 38.4 - 22.4 = 29

   The margin is on .rw-page-offerings .rates, not .rates. The first attempt
   used the bare class and did nothing at all, because the two-class rule at
   ~456 wins on SPECIFICITY no matter how far down the file the single-class
   rule sits. Source order only breaks ties between equally specific
   selectors, a different failure from the unmediated-rule trap documented
   in the runbook, and one this file is equally prone to.

   NOTE, and this cost an hour: the paragraph above was originally written
   AFTER a stray closing marker, so it sat outside the comment as live CSS.
   The parser scanned it for a selector, hit the brace inside a quoted rule
   it contained, and swallowed the .rates padding rule below into a second
   invalid selector. Both were silently discarded. margin-top still applied,
   so three of the four measurements came out correct and the failure looked
   like a cascade problem for hours. Never quote a CSS rule inside a CSS
   comment, and never let a comment carry more than one closing marker. */
.rates{padding:1.4rem 1.6rem 1.2rem}
.rw-page-offerings .rates{margin-top:5.6rem}
.rates.rw-rough::before{inset:-1.4rem -0.9rem}

@media (max-width:780px){
  .rates{padding:1.2rem 1rem}
  .rw-page-offerings .rates{margin-top:4.2rem}
  .rates.rw-rough::before{inset:-1.1rem -0.7rem}
}

/* ============================================================
   THE THORN FIELD IS NOW A TILED MESH, NOT PLACED PLATES
   2026-08-11. Ross chose this from nine measured variations.

   The brief was "thinner, lighter, evenly spaced across the
   entire frame, less dense, and not showing through the photos."
   Thinner is the constraint that decided the mechanism: the
   plate is a photograph, so strokes only get thinner by drawing
   it SMALLER, which means far too many to place by hand. So it
   stops being four positioned images and becomes one repeating
   background.

   Measured against a render of the same page with thorns off,
   which is opacity-robust in a way that eyeballing is not:

                       coverage   ink    over photo
     placed plates       27.0%    27.5     18.9%
     this mesh           21.0%    15.1      0.3%

   Ink is mean darkening, which is what "heavy" actually means.
   It falls 45% while coverage stays comparable and even, and
   the photographs come out clean, which is what Ari asked for
   in "replace with a singular large photo WITHOUT transparency".

   WHY A VARIABLE TILE SIZE. 640px is tuned for desktop. Held
   constant it would show barely one cell across a phone, so the
   texture would read as a few huge arcs rather than a thicket.
   It steps down with the viewport to keep the density constant.

   WHY NOT position:fixed. It would sidestep all height concerns,
   but a uniform texture that does not move while content scrolls
   reads as a screen in front of the page. The thorns are meant to
   be the ground the page travels across, per the view-transition
   note above.

   ⚠️ THIS DEPENDS ON body's BOX MATCHING ITS CONTENT. .rw-chrome
   is absolute with inset:0, so it resolves against body's padding
   box, not its scroll height. A displaced-but-not-contained child
   anywhere makes this mesh stop short of the footer. That exact
   bug existed on Home at ≤1100 and is fixed near line 1000; if
   the mesh ever stops early on a page, look there first, not here.

   The plates are hidden rather than deleted so this is one
   revert if Ari prefers the old field. Their markup still ships
   from roman_west_thorns() in functions.php.
   ============================================================ */
.thorn{display:none}
.rw-chrome-over{display:none}

.rw-chrome::before{
  content:'';
  position:absolute;
  inset:0;
  pointer-events:none;
  /* ⚠️ thorns-tile.webp, NOT thorns.png — the two are NOT interchangeable.
     This is the seamless MIRRORED plate built from Ari's newer thorn artwork
     (IMG_3839, supplied 2026-08-15). thorns.png stays in the project and is
     still the source for line 1202, where it is cropped at four hand-tuned
     positions over the heroes. Point THAT rule at this file and all four crops
     land somewhere else, at half scale, silently. Two assets, two jobs. */
  background:url('../img/thorns-tile.webp') repeat;
  background-size:var(--mesh-tile) auto;
  filter:var(--thorn-filter);   /* inherits the dark-mode invert */
  opacity:var(--mesh-op);
}

/* ⚠️ ALL THREE --mesh-tile VALUES DOUBLE, and there are three, not two.
   The plate holds a 2x2 mirror, so a quadrant is half the tile. Tiling it at
   the old 640/520/400 would render Ari's thorns at HALF the intended size and
   look like a deliberate design choice rather than a mistake. 1280/1040/800
   keeps each quadrant at the old tile width.

   The 1040 is the one that gets missed: it lives in a @media (max-width:1100px)
   further down, so setting only "desktop and mobile" leaves the 781-1100 band
   silently halved. */
:root{--mesh-tile:3200px;--mesh-op:.20}

/* The form is long and the mesh sits behind every field, so it
   eases here for the same reason the old plates did. */
.rw-page-submit{--mesh-op:.14}

/* ============================================================
   OFFERINGS — the two sectioned-out offerings
   Built 2026-08-11. Ari asked for these on 2026-08-01 and they
   were parked as "blocked" on a misreading; a placeholder was
   always the deliverable. See patterns/offerings.php.

   They take the torn-card treatment because that is exactly
   what Ari asked for: "this outline and opacity overlay is
   awesome! I wonder if we can repeat this style elsewhere where
   text needs more background." Two cards below the ledger is
   the last place on the page where prose needed a ground.
   ============================================================ */
/* ⚠️ NEITHER THE GAP NOR THE BOTTOM MARGIN IS DECORATIVE. Both are sized
   against the panel overhang, and both were wrong on the first pass.

   .offer.rw-rough::before insets -1.3rem vertically and -1.5rem
   horizontally, so every panel's visual edge sits 20.8px below and 24px
   either side of its element box. Two consequences, and I hit both:

     bottom   no margin at all → 24px element gap to .closer, about 3px of
              real clearance, torn edge nearly touching "Submit if you seek
              liberation."
     between  gap:3.2rem = 51.2px, minus 24px from each neighbouring panel
              = 48px consumed, leaving 3.2px. The two cards visually
              touched in the middle.

   THE RULE: a gap between two rw-rough panels must exceed the SUM of the
   insets facing each other before it buys any visible space. Third time
   this exact trap has cost a pass on this build, after the rates card
   colliding with the prose card. Each element's own spacing reads as
   perfectly reasonable in isolation, which is precisely why it only shows
   up when measured between two of them — and why the fix must be measured
   from the ::before box, never the element box. */
/* Moved INSIDE .left on 2026-08-11, directly under the ledger, so they
   inherit the column width and read as smaller cards hanging off the
   pricing rather than a second full-width band. Ross's call. Everything
   below is sized for that narrower column, not the old full width. */
.offers{
  display:grid;
  grid-template-columns:1fr 1fr;
  gap:3.4rem;                 /* 54.4px - 32px of facing insets = ~22px clear */
  margin:4.4rem 0 0;          /* the ledger already carries its own spacing */
  position:relative;
  z-index:30;                 /* above the mesh, same as .prose */

  /* Cards match heights. This was briefly align-items:start, because with
     the zine image cropped to a band its one-line placeholder left ~150px
     of dead space and uneven bottoms looked deliberate. Running the zine
     image at full height removed the imbalance at its source, so the two
     cards are now close enough that stretching them to match reads as
     intended rather than as one card padded out. Ross's call. */
  align-items:stretch;
}

.offer{padding:1.5rem 1.6rem 1.3rem}

/* Ari's own reference images, supplied 2026-08-09 and named by Ross:
   a wax-sealed envelope for lust letters, a risograph zine spread for
   underground slow media. Converted from 1.8MB and 1.5MB PNG screenshots
   to 106KB and 310KB JPEGs; originals kept in assets/images/.

   The negative margins pull each image to the card's box edge, so it sits
   flush inside the torn panel rather than floating in the padding. They
   MUST stay equal to .offer's padding — change one and change the other.

   ⚠️ THE TWO IMAGES ARE TREATED DIFFERENTLY ON PURPOSE, and it is the
   thing that makes this row work. The sources disagree badly: the envelope
   is 1.44 landscape, the zine 0.79 portrait.

   The zine runs at its NATURAL height. It is a page of printed matter, so
   cropping it to a band threw away most of the artefact and cut the
   "CRY FOR ME" line mid-stroke. Full height also does useful layout work:
   the tall image pushes the zine's one-line placeholder well down the
   card, which brings a card with very little copy close to the height of
   one carrying Ari's full draft. The imbalance solves itself.

   The envelope stays cropped to a 16/10 band, aimed at the wax seal. Run
   at natural height it would be the shorter image AND sit above the longer
   text, which pushes the two cards back out of balance the other way. */
.offer-img{margin:-1.5rem -1.6rem 1.1rem;overflow:hidden}
/* ⚠️ object-fit:cover is DEFENSIVE, not cosmetic — it changes nothing today.
   The zine image had no object-fit at all, so it inherited the `fill` default.
   That renders correctly right now only because height is auto, which makes the
   box match the file's own 867x1100 ratio so there is nothing to distort. The
   moment anything constrains the height — a max-height, an aspect-ratio, a
   flex or grid stretch — `fill` squashes Ari's artwork instead of cropping it,
   and it does so silently. cover fails gracefully in every one of those cases.
   The letter image already sets cover explicitly a few lines below. */
.offer-img img{display:block;width:100%;height:auto;object-fit:cover}

.offer:nth-child(1) .offer-img img{
  aspect-ratio:16/10;
  object-fit:cover;
  object-position:center 56%;
}

/* Opposed half-degree tilts. Enough to belong to a page where
   nothing sits square, small enough that the bounding box does
   not grow past .rw-shell's overflow-x:clip and start a
   sideways scroll — the exact bug the 2026-08-03 sweep found on
   three pages. Measured after, not assumed. */
.offer:nth-child(1){transform:rotate(-.45deg)}
.offer:nth-child(2){transform:rotate(.35deg)}

/* Tighter than the full-width version: 16px each side rather than 24, so
   two cards still clear each other inside the column. */
.offer.rw-rough::before{inset:-0.9rem -1rem}

.offer h2{font-size:clamp(1.2rem,1.9vw,1.5rem);margin:0 0 .4rem;line-height:1.02}
.offer p{font-size:.88rem;line-height:1.6;margin:0}

.offer-meta{
  color:var(--accent);
  font-size:.76rem;letter-spacing:.03em;
  margin-bottom:.85rem !important;
}

/* Deliberately plain. This is a build marker, not Ari's voice,
   and it should not read as finished copy. */
.offer-todo{color:var(--muted);font-style:italic}

/* .offer-flag's rule lived here and is DELETED, not orphaned — 2026-08-17,
   with the two markers it styled. Dead selectors have already cost this
   build real time once: four "hand-tuned hero crops" were argued about at
   length before anyone noticed every one of them carried content:none and
   had been painting nothing for weeks. A rule with no markup left is not
   free documentation, it is a trap for the next reader. The history is in
   patterns/offerings.php where the markup was. */

/* Stacked. The facing insets become the VERTICAL pair, 14.4px each, so the
   gap is sized against 28.8px rather than the 32px that applies side by
   side. First pass under-sized both of these — measured 15px between
   panels and 15px to the ledger, which is collision territory again. */
@media (max-width:900px){
  .offers{grid-template-columns:1fr;gap:3.6rem;margin-top:4.2rem}
}
@media (max-width:780px){
  /* ⚠️ The horizontal inset is cut to -0.45rem here, not for looks. The
     column's own page padding is only 1.2rem at this width, so a -0.7rem
     inset left the torn edge 7px off the viewport and reading as clipped.
     A panel that bleeds outward has to be sized against the page padding
     it sits inside, not just against its neighbours. */
  .offers{margin-top:3.8rem;gap:3.4rem}
  .offer{padding:1.4rem 1.4rem 1.2rem}
  .offer.rw-rough::before{inset:-0.8rem -0.45rem}
  /* kept equal to .offer's padding above */
  .offer-img{margin:-1.4rem -1.4rem 1rem}
}

@media (max-width:1100px){
  :root{--mesh-tile:2600px}   /* 781-1100 band — the one that gets missed */
}
@media (max-width:780px){
  /* ⚠️ MOBILE DOES NOT SCALE PROPORTIONALLY WITH THE OTHER TWO, and that is
     deliberate. Desktop and tablet went to 3200/2600 for bigger, sparser
     thorns. Carrying the same ratio down gave mobile 2000 — and at 390 that is
     only 20% of one tile across, so the crop landed on an empty region and the
     thorn field effectively vanished from the page. Rendered full-page to
     confirm rather than judging from one viewport.

     1500 keeps the thorns clearly larger than they were while leaving enough
     of the plate on screen to still read as a field. The narrower the
     viewport, the smaller the tile has to stay to show anything at all. */
  :root{--mesh-tile:1500px}
}

/* ============================================================
   THE WORDMARK BECOMES ARI'S SCRIPT — 2026-08-13
   Ross: "grab that new RomanWest script image and remove the
   background and replace any instance of 'Roman West' with
   that png file."

   Source was a 2048x2048 JPEG, black beaded script on white.
   Processed to alpha (ink = opaque, paper = transparent),
   cropped to the ink's bounding box at 1832x360, output 1600px
   wide. Original kept in assets/images/.

   ⚠️ IT IS A MASK, NOT AN <img>, and that is the whole design.
   background-color:currentColor painted through the mask means
   the wordmark inherits whatever colour its context already
   has — --fg in the nav, --muted in the footer — so it flips
   correctly between bone and dark pages with no second file and
   no filter. Same mechanism as the descent arrow.

   ⚠️ SIZED IN em, NOT px, and that is not a style preference.
   The Home overture ANIMATES font-size from --rw-display down
   to 2.05rem as the wordmark travels up into the nav. A fixed
   box would have frozen mid-flight and broken the one piece of
   this build the runbook says has already broken three times.
   In em the mask box tracks font-size, so that animation and
   every existing size rule keep working untouched.

   Text stays in the DOM and is pushed off-box rather than
   removed, so the site title still reads to screen readers.
   That preserves most of what the no-text-in-images rule is
   protecting: readable by assistive tech, restylable, and one
   drawing rather than a per-mode export.
   ============================================================ */
.rw-nav .mark a,
.mark a,
.foot,
.rw-page-home .rw-overture{
  display:inline-block;
  height:1.15em;
  aspect-ratio:1832 / 360;      /* the cropped source, so width follows height */
  background-color:currentColor;
  -webkit-mask:url('../img/wordmark.png') no-repeat left center;
  -webkit-mask-size:contain;
  mask:url('../img/wordmark.png') no-repeat left center;
  mask-size:contain;
  /* keeps the accessible name, takes the glyphs out of the masked box */
  text-indent:-9999px;
  overflow:hidden;
  white-space:nowrap;
}

/* ⚠️ THE FOOTER'S PADDING HAD TO MOVE OFF THE MASKED ELEMENT.
   .foot carried padding:2.4rem 3.2rem 1.8rem — 67.2px vertical. A border
   box cannot be shorter than its own padding, so height:1.15em (23px) was
   ignored and the wordmark rendered at 342x67 instead of 118x23, nearly
   three times too big.

   Nothing errors here: the box just silently takes the larger value, then
   aspect-ratio derives a width from it, and the result looks like a
   deliberate size choice. Measuring against the expected 1.15em is what
   caught it.

   The padding was always spacing the footer FROM the page, not the text
   from its own box, so it belongs on <footer>. Same three breakpoints,
   same values, just moved up one level. */
/* ⚠️ THE OVERRIDE MUST REPEAT ".rw-shell + footer .foot", and the first
   attempt at this did not. The original padding is declared on a SELECTOR
   LIST at ~473:

     .rw-shell + footer .foot,     <- (0,2,1)
     footer .foot{ padding: ... }  <- (0,1,1)

   A plain "footer .foot{padding:0}" is (0,1,1) and loses to the sibling
   selector in that list no matter how far down the file it sits, because
   specificity is evaluated per selector, not per rule. The override looked
   correct, deployed cleanly, and changed nothing — the footer stayed
   342x67 across all sixteen checks.

   Third variant of the same trap in this file, after the unmediated rule
   and the duplicated selector. When an override does no work, compare the
   specificity of EVERY selector in the original list, not just the one
   that reads like the match. */
footer{padding:2.4rem 3.2rem 1.8rem}
.rw-shell + footer .foot,
footer .foot{padding:0}
@media (max-width:1100px){
  footer{padding:2.2rem 2.2rem 1.6rem}
  .rw-shell + footer .foot,
  footer .foot{padding:0}
}
@media (max-width:780px){
  footer{padding:1.8rem 1.2rem 1.4rem}
  .rw-shell + footer .foot,
  footer .foot{padding:0}
}

/* ============================================================
   HOME OPENING, REBUILT — 2026-08-13
   Ross: "start it with entire text large, the way that it was,
   'Roman West does not conquer. Offer yourself.' and make it
   all move together and shrink into the top left corner. Slow
   that timing down so it feels intentional. As it happens, the
   body text becomes Auria Sans, slightly larger. We get rid of
   the line above it and tighten the Lore CTA to it."

   THIS BLOCK IS LAST AND THEREFORE WINS. Anything added later
   must go below it.

   What changed conceptually. The old opening SPLIT: "Roman West"
   detached and flew to the corner while the headline stayed put
   at full size. Now the whole sentence is one object — it starts
   large, and every part of it shrinks and travels together into
   the top-left. The headline animates font-size for the first
   time; previously only the overture did, which is exactly why
   the two read as separate gestures.

   ⚠️ The headline shrink and the overture MUST share keyframe
   percentages and the same duration variable. They are one
   movement to the eye, so any divergence reads as two things
   happening near each other.
   ============================================================ */

:root{
  /* Where the headline LANDS, against --rw-display where it starts.
     Revised 2026-08-13. The first pass took this down to ~2.3rem and Ross
     called it: "that didn't do what I want. There's too much blank space."

     Shrinking the headline did not remove blank space, it relocated it —
     everything crowded into the top third and left a dead lower half. So
     the headline stays large and the whole column moves up instead. */
  /* +35%, 5.8 -> 7.8rem. Ross: "pretty large, so it even overlaps the image
     a little bit." At his 1512px window "Offer yourself." now runs past the
     hero's left edge at 735px, which is the overlap he is after. Matches
     --rw-display exactly, so the headline holds one size throughout and
     only travels. */
  --rw-headline-rest: clamp(2.6rem, 9vw, 6.2rem);   /* == --t-title */

  /* Matches the nav's own left padding, so the headline shares the
     wordmark's left edge rather than hanging off it. Ross: "so the smaller
     Roman West sits right on top of it" — on top means directly above and
     aligned, not indented beneath. Derived from the nav variable rather
     than restated, so it cannot drift when the nav reflows. */
  --rw-headline-indent: var(--rw-nav-pad-x);
}

/* 3s -> 4s. Second slow-down; Ari called the original "too quick to seem
   intentional" and Ross asked for more again after the first pass. */
:root{--rw-overture-dur:4s}
/* recomputed: .55 + (4 x .88) = 4.07s */
.rw-page-home .rw-nav .mark{animation:rw-mark-in .4s ease both 4.07s}

/* ⚠️ DESKTOP ONLY, and the first pass got this wrong. Written unmediated,
   these leaked into the <=1100 layout: at 390 the headline still animated
   from display size, the body copy sat at a 102px indent on a 390px screen,
   and it ran flush to the right edge.

   The whole nested-under-the-wordmark idea depends on the wordmark being
   IN the nav beside it. Below 1100 the shell drops from grid to block, the
   overture is meant to be display:none (⚠️ it is NOT — that rule is dead,
   see the <=1100 block near rw-settle-sub for the measurement), and the hero
   stacks above the headline — there is no wordmark IN THE NAV to nest under
   while the overture is still travelling, so nesting is meaningless there.

   Mobile's headline no longer has its own entrance: it briefly carried
   rw-line-in, removed 2026-08-17 for animating only "does not" and leaving
   the two lines that finish the sentence behind.

   Same trap as .sub's top:432px and the Lore hero before it. An unmediated
   rule is not a default, it is an override of every breakpoint below. */
@media (min-width:1101px){
  /* ⚠️ margin-left:0, NOT the indent. .stage already carries
     padding:3.4rem 3.2rem 0, so its 3.2rem left padding IS the page
     margin. Adding the indent on top DOUBLED it: the stage box sat at 51
     with the wordmark, but its children rendered at 102, so the headline
     was inset half an inch from the mark above it.

     The element boxes measured as aligned, which is why this passed the
     last check — the offset was inside the box. Measuring the INKED left
     edge with a Range instead of the element rect is what exposed it.
     .sub has no padding of its own, which is why it aligned correctly at
     51 while the headline did not; the two need different values and
     cannot share one variable.

     margin-top -3.4 -> -1.4rem. At -3.4 the headline's ascenders ran into
     the wordmark's descenders — Ross: "Roman west and does not conquer are
     getting tangled." The box gap read as +2px, so nothing overlapped
     numerically; the collision was between glyphs, not boxes. */
  /* margin-top -1.4 -> -2.6rem. Ross: "tighten to the top, to Roman West, a
     little bit more." The larger headline closes some of this on its own —
     taller ascenders reach up into the gap — so this is the remainder. */
  .rw-page-home .stage{
    margin-left:0;
    margin-top:-2.6rem;
  }
  /* ⚠️ OPTICAL, NOT GEOMETRIC. Every box already aligns at exactly 51px —
     that was verified — but sampling the rendered PIXELS showed the
     wordmark inking at 51 while the headline inked at 54.

     The 3px is real type behaviour, not a bug: the script's capital R opens
     with a flourish that reaches past its own glyph origin, while Remboy's
     "d" and "O" carry an ordinary left side bearing. Boxes aligned, ink did
     not, and the eye reads ink.

     In em so it tracks the headline size rather than breaking the next time
     --rw-headline-rest is retuned. -0.032em is 3px at the current 93px. */
  .rw-page-home .stage h1,
  .rw-page-home .stage h2{margin-left:-0.032em}
  .rw-page-home .stage h1,
  .rw-page-home .stage h2{
    font-size:var(--rw-headline-rest);
    animation:rw-headline-shrink var(--rw-overture-dur) cubic-bezier(.6,0,.2,1) both .55s;
  }
}
@keyframes rw-headline-shrink{
  0%,32%   {font-size:var(--rw-display)}
  76%,100% {font-size:var(--rw-headline-rest)}
}
/* ⚠️ NO translateX. It used to be translate(-indent, …) to cancel a 6.4rem
   nesting offset back when the headline sat indented beneath the wordmark.
   That layout is gone — .stage is margin-left:0 now and shares the
   wordmark's left edge — but the cancel was left behind, so it dragged the
   headline 51px LEFT for the whole opening. Measured during the hold: the
   wordmark at 51, the headline box at -3.

   That is why the settled state kept measuring as aligned while the opening
   frame did not. Two different positions, and only one of them was being
   checked. Vertical travel only now. */
@keyframes rw-settle{
  0%,32%   {transform:translateY(calc(var(--rw-overture-line) - var(--rw-headline-gap)))}
  76%,100% {transform:translateY(0)}
}

/* ---- body copy: Auria, larger, and back in flow ----
   It carried top:432px, which offset it visually while leaving its flow box
   behind — the bug that made body's box 244px shorter than the document.
   With the headline now small there is no reason to displace it at all, so
   the offset goes and it simply follows the headline. */
/* The TYPE change is sitewide — Ross asked for the body copy to become
   Auria, slightly larger, and that is true at every width. Only the
   position and indent are desktop-only. */
/* 1.16 -> 1.34rem, Ross: "bump up the Auria Sans font a bit to take up some
   more room". Measure widens with it so the copy still breaks over three
   lines rather than running long and undoing the gain. */
.rw-page-home .sub{
  font-family:var(--ui-font);font-weight:var(--ui-weight);
  line-height:1.58;
}
@media (min-width:1101px){
  /* margin-top 2.6 -> 5.2rem. Ross: "drop the Auria sans font down slightly
     towards the middle of the page." The extra space also does real work on
     the dead-space problem — it sits the gap BETWEEN headline and copy,
     where it reads as a breath, rather than trailing below the whole
     column where it read as a shortfall.

     The left margin stays at the nav padding: .sub has no padding of its
     own, unlike .stage, so it needs the offset to reach the same 51px. */
  .rw-page-home .sub{
    position:static;top:auto;bottom:auto;
    max-width:min(100%,490px);
    /* ⚠️ vh, NOT a fixed rem. Ross: "body text and lore arrow should go a
       little lower on the page otherwise, too much blank space." The blank
       space is worst on TALL windows — his is ~1300px against the 950 I had
       been testing — so a fixed margin cannot answer it: whatever value
       looks right at 950 leaves a hole at 1300, and whatever fills 1300
       pushes the copy off the fold at 950.

       Tying it to viewport height makes the gap grow with the space it is
       meant to occupy. Clamped at both ends so it never collapses onto the
       headline or runs the arrow past the fold.

       13vh -> 22vh on the second pass: 13 moved the copy only 86px and left
       41% of a 1300px window empty below the column, which was still
       reading as a hole. 22vh is 286px at 1300 and 209px at 950, against
       the flat 83px it replaced. */
    margin:clamp(5.2rem, 22vh, 18rem) 0 0 var(--rw-nav-pad-x);
    padding:0;
    animation:rw-sub-fade var(--rw-overture-dur) ease both .55s;
  }
}
@keyframes rw-sub-fade{0%,58%{opacity:0}100%{opacity:1}}

/* the 180px rule above the body copy — Ross: "get rid of the line" */
.rw-page-home .rule{display:none}

/* ---- the Lore CTA tightens to the body copy ----
   It was stranded: the headline shrank from 118px to 37px and the body copy
   came back into flow, so .sub now ends at 421 while the arrow sat at its
   old 812 — a 391px hole. Aligned to the same indent as the headline and
   body so the three read as one column rather than three stacked things.

   ⚠️ 466 IS TIED TO WHERE .sub ENDS and nothing in CSS enforces that. The
   arrow is injected on wp_footer, so it lives at the end of <body> and
   cannot simply flow after .sub — absolute positioning is the only option
   and the number has to be re-measured whenever the copy or the headline
   size changes. Third time a hardcoded top has needed moving on this
   element; if it moves again, consider whether the arrow should be emitted
   inside the content instead. */
/* ✅ THE HARDCODED OFFSET IS GONE. This was top:600 -> 712 -> 812 -> 466 ->
   563 across one session, each time standing in for "just below wherever
   .sub ends", and wrong again the moment the copy moved.

   roman_west_arrow_inline() now appends the arrow to Home's content, so it
   flows after the body copy and its position is whatever the flow says it
   is. Margin, not coordinates. Nothing here needs re-measuring when the
   headline or body type changes again.

   The left margin matches .sub's, since .sub carries no padding of its own
   and both need the offset to reach the wordmark's 51px. */
@media (min-width:1101px){
  .rw-page-home .rw-arrow{
    position:relative;
    top:auto;left:auto;right:auto;
    /* -3px is the same optical nudge the headline gets: the arrow's SVG
       carries its own left bearing, so it inked at 54 against the
       wordmark's 51. Fixed px rather than em because the SVG is a fixed
       size, not type. */
    margin:2.6rem 0 0 calc(var(--rw-nav-pad-x) - 3px);
    /* Ross: "Lore arrow should appear same time as the body text." Shares
       rw-sub-fade with the same duration and delay as .sub, so the two are
       literally the same curve rather than two timings that happen to look
       close. Previously it had no entrance at all and was simply present
       from the first frame, which read as arriving early. */
    animation:rw-sub-fade var(--rw-overture-dur) ease both .55s;
  }
}

/* ============================================================
   HOME'S HERO KEEPS FULL OPACITY IN DESCEND MODE — 2026-08-13
   Ross: "make sure home hero image is full opacity, even on
   Descend mode."

   Dark mode sets --hero-blend:screen, and screen against a
   near-black ground lifts the image's own dark values toward
   transparent — the photograph visibly thins out. That is the
   right effect for the dark pages, where the imagery is meant
   to sit INSIDE the murk, but Home is the first thing anyone
   sees and its hero should read as a photograph either way.

   The mode toggle is what exposes this: Home is a bone page by
   default, so nobody sees it until a visitor presses Descend.
   Scoped to Home only; Lore, Offerings and Submit keep the
   blend.
   ============================================================ */
.rw-page-home .hero,
.rw-page-home .hero-wrap,
.rw-page-home .hero img{
  mix-blend-mode:normal;
  opacity:1;
}

/* ============================================================
   TYPE HIERARCHY — established 2026-08-13
   Ross: "let's establish a very clear font hierarchy because I
   don't think we have one and I'm worried it's going to get
   chaotic."

   He is right, and an audit of every rendered text element
   across the four pages showed why. Remboy alone was doing
   ELEVEN jobs at ten sizes, from 17px ("Descend") to 125px —
   display, subheads, buttons, captions and step numerals all on
   one face. Body copy was split across TWO faces for the same
   role: Karrik on Lore, Auria on Home and Offerings.

   The rule from here: a face is chosen by ROLE, never by how a
   given line happens to look. Three faces, three jobs.

     Remboy   display only. Page titles and section headings.
              Nothing below --t-heading. Regular weight only,
              so nothing may ask it for a bold.
     Auria    everything the reader operates or scans: nav,
              labels, form, buttons, body copy.
     Karrik   long-form reading and figures — Lore's prose and
              the price column, where an italic and tabular
              numerals are actually needed.

   Sizes are named by role, not by number, so a change is made
   once here rather than hunted through the file.
   ============================================================ */
:root{
  /* ⚠️ THE DISPLAY TOKENS WERE DEFINED TWICE and are now defined once, in
     THE TYPE SCALE block near the end of this file. (--t-display is
       named here as HISTORY ONLY — it was retired 2026-08-16 when the Home
       headline moved onto --t-title. Do not go looking for it.)
       --t-display, --t-title
     and --t-heading were declared here first and redeclared there; the
     later definition won, so these three were dead the moment the scale
     landed. --t-title in particular said 7vw here against 9vw there.

     Two definitions of one token is how the --t-heading collision happened
     — same name, two meanings, and the wrong value looks deliberate. One
     definition per token, in one block, is the whole point of a scale. */

  /* interface — Auria */
  /* ⚠️ ONLY TOKENS THAT DESCRIBE SOMETHING REAL. --t-body and --t-micro
     were removed here: --t-body said 1.16rem when the only body copy it
     could have meant was Karrik at 1.34rem, and --t-micro matched nothing
     left on screen after "four parts" came out. Both were defined and
     referenced by nothing.

     A token that describes nothing is worse than a literal, because the
     next person wires a rule to it and inherits a value nobody checked.
     Same failure as the --t-heading collision, one step earlier.

     ⚠️ --t-body IS BACK, later in this file, and deliberately. Read that
     definition rather than this paragraph. It returned on 2026-08-13 when
     Ross standardised Lore and Offerings to 19px, which gave the name a
     real referent for the first time — 1.16rem is now correct AND wired to
     three selectors. Home's 21px lives in a separate --t-body-lead so the
     two cannot collide the way --t-heading once did. This note stays only
     so the round trip is legible; it is history, not current state. */
  --t-nav:      calc(.82rem * var(--ui-scale));   /* 23px  nav + arrow label */
  --t-label:    calc(.76rem * var(--ui-scale));   /* 21px  ledger labels     */

  /* weights. Auria is variable 100-950, so every one of these is
     a real instance. Remboy is Regular-only and must never be
     given anything but 400.

     700 -> 400, Ross 2026-08-14: "Turn all instances of Auria down to 400
     weight again." The nav and the descent-arrow label were the last two
     things still bold, so this token and the --ui-weight override further
     down both return to 400, and Auria is uniform across the site again.

     Kept as a token rather than inlined at the selector: the nav's weight
     has now changed twice in two days, so the next change should stay a
     one-line edit rather than a hunt.

     ⚠️ SETTLED AT 400. The weight moved four times in two days — 700, 400,
     200, and back to 400 — so the useful thing to record is not the value
     but the fact that BOTH directions were tried against the real site and
     rejected. 700 was too heavy under the display headline; 200 was tried
     on 2026-08-14 and Ross's call was "put it back to 400, I like that
     better."

     Do not re-propose either without a new reason. This is exactly why the
     weight is a token: the next experiment stays a two-line edit. */
  --w-nav:      400;

  /* --w-ui and --w-display were defined here and REFERENCED BY NOTHING.
     Removed 2026-08-14 while changing the weight above, under this file's
     own rule that a token describing nothing is worse than a literal,
     because the next person wires a rule to it and inherits a value nobody
     checked. --w-ui read `var(--ui-weight)`, which made it look like the
     live control for the UI weight when the real control is --ui-weight
     itself — exactly the confusion that rule exists to prevent. */
}

/* ⚠️ REVERTED 2026-08-14. This block read `--ui-weight: 700` and was the
   single line that took ALL of Auria bold on 2026-08-13 ("let's do the same
   to Auria everywhere"). Ross, the next day: "Turn all instances of Auria
   down to 400 weight again." So it goes back.

   Set to 400 here rather than deleting the block, deliberately. The base
   definition near the top of the file is already 400, so deleting this
   would produce the same rendering today — but it would also erase the
   record that the weight was tried at 700 and taken back, and this token
   has now moved twice. A future "should the UI be bolder?" is better met
   with a line saying it was, and was reverted, than with silence.

   --ui-weight feeds every rule that sets the UI face, so nav, labels, the
   footer, form fields, the step counter and the ledger labels all move
   together — which is the reason a sitewide weight change is one line.

   Remboy is untouched and MUST stay untouched: it ships Regular only, so
   .mode keeps its own literal font-weight:400 and the REGULAR-ONLY
   allowlist still pins the headings. That protection is no longer being
   tested now the value is 400, but it stays — it is what stopped a
   synthesized fake bold across every display element yesterday, and the
   next person to raise the weight will need it again. */
/* ⚠️ 400 IS THE SETTLED VALUE. Both directions have now been tried on the
   live site and rejected, and this comment exists so neither gets proposed
   again as if it were a fresh idea.

   700, 2026-08-13 → 400, 2026-08-14. Too heavy under a 125px display
   headline; it competed rather than supported.

   200 ExtraLight, 2026-08-14 → 400, same day. Ross: "put it back to 400, I
   like that better." It was a genuine cut, not a clamp — the shipped
   AuriaSans2.0VF fvar table carries a wght axis of 100-950 with a named
   ExtraLight instance — and it looked good on the bone pages. What it cost
   was on the dark pages, where two dimmings stack: the Offerings ledger
   labels are already rgba(...,0.6), and 200 thinned them again. Measured on
   rendered pixels rather than CSS values, the typical antialiased stroke
   pixel fell to 4.93:1 against the ground, versus 5.75:1 at the glyph core.
   Still above the 4.5:1 AA floor, but the colour-only figure of 5.76:1 holds
   only at the thickest point of the stroke — WCAG's formula is weight-blind
   and reads optimistic on thin type. Pricing legibility is a standing
   non-negotiable here.

   If a lighter UI is ever wanted again, the finding worth reusing is that
   the weight was not really the problem: the ledger labels compound weight
   with alpha. Raising that 0.6 would buy the headroom without touching the
   weight anywhere else. */
:root{
  --ui-weight: 400;
}

/* ---- 1. nav and the arrow label share a weight ----
   ⚠️ This heading read "nav goes to 700" until 2026-08-14, when Ross took
   all of Auria back to 400. --w-nav is 400 now, so the rule below no longer
   changes anything visually — it is kept because the GROUPING is the point,
   not the value.

   The descent arrow's label belongs with the nav links. It is the same role
   — a link that moves you to another page — so it takes the same weight
   rather than becoming a fourth thing. That is the hierarchy doing its job:
   the question is "what is this element FOR", not "where does it sit". If
   the nav is ever re-weighted, the arrow label should move with it, and it
   will, because they read the same token. */
nav a.lnk,
.rw-nav .rw-link a,
.rw-arrow-label{font-weight:var(--w-nav)}

/* ---- 2. "Can you take direction?" moves to Auria ----
   Ross: "I also feel like 'Can you take direction?' on submit page should
   also be Auria sans." It is a question put to the applicant, not a page
   title — it belongs with the form it introduces rather than with the
   display face. Sits at --t-heading so it still outranks the field labels
   beneath it.

   It was in the REMBOY IS REGULAR-ONLY allowlist, which pinned it to 400 to
   stop a synthesized bold. On Auria that pin is no longer load-bearing —
   the face is variable, so any weight is a real instance — but the rule
   states it explicitly rather than leaving the value to whichever guard
   happens to still match.

   ⚠️ NOTE, CORRECTED 2026-08-14. This used to read "at --ui-weight it
   renders 400, which IS lighter than the nav's 700 sitting above it", and
   explained why that was fine. The nav is 400 now too, so the asymmetry
   it described is gone and the note no longer applies.

   The underlying point survives and is worth keeping: this heading carries
   its hierarchy by SIZE, 30px against the nav's 23px, not by weight. That
   was true when the nav was bolder than it and is true now that they match.
   Anyone tempted to re-bold one of them to "fix" the relationship should
   know the relationship was never resting on weight. */
.rw-page-submit .process h2{
  font-family:var(--ui-font);
  font-weight:var(--ui-weight);
  font-size:var(--t-heading);
  letter-spacing:.01em;
}

/* ---- 3. Home's body copy returns to Karrik ----
   Ross: "let's change that body font back to Karrik but keep it the same
   size as the Auria is now."

   This is the "except the home page" half of the same instruction. Auria
   had gone to 700 sitewide, and 700 was wrong under a 125px display
   headline — it competed rather than supported. Karrik at the same nominal
   size reads as prose again.

   ⚠️ THIS PARAGRAPH IS HISTORY, NOT CURRENT STATE — corrected 2026-08-14.
   Auria went back to 400 everywhere on Ross's instruction, so the "700 is
   wrong here" argument no longer describes anything live. The DECISION it
   produced stands on its own merits and is unaffected: Home's body copy is
   Karrik because body copy should be one face sitewide, not because Auria
   was briefly bold.

   ⚠️ THE SIZE IS NO LONGER SET HERE EITHER. This block used to say "SIZE
   STAYS 1.16rem" and was immediately followed by a comment insisting on
   1.34rem — two adjacent comments contradicting each other on the same
   number, which is worse than either being wrong alone. Both are gone: the
   size moved into --t-body-lead (1.34rem / 21px) in the type scale on
   2026-08-13. Change it there, not here.

   The weight below stays pinned to 400 and is still worth keeping. Karrik
   ships Regular and Italic only, so a two-weight face asked for a bold gets
   a synthesized one. That pin is not being tested today, since --ui-weight
   is 400 again, but it is what would catch the next sitewide weight change.
   Same trap as Remboy, different font.

   It also resolves the split the type audit flagged: body copy was using
   two faces for one role, Karrik on Lore and Auria on Home. It is Karrik
   in both places now, and Lore's real italic — "transformation" — is the
   reason that face owns long-form reading. */
.rw-page-home .sub{
  font-family:var(--body-font);
  font-weight:400;
}

/* ============================================================
   THE TYPE SCALE — single source of truth, 2026-08-13
   THIS BLOCK IS LAST AND THEREFORE WINS. Anything added later
   must go below it, and any new type rule belongs IN it.

   Step 2 of the tidy-up. The audit found 105 font-size
   declarations for what is really about ten roles, and 104 of
   them were literals — the same selector redeclared at three or
   four breakpoints, each a separate chance to drift. Remboy
   alone ran twelve sizes from 17px to 125px.

   ⚠️ WHY AN OVERRIDE BLOCK RATHER THAN 104 EDITS IN PLACE.
   Rewriting every literal would touch four responsive copies of
   most selectors, and this file has already produced three
   separate bugs from exactly that kind of scattered editing —
   a duplicated selector, an unmediated rule, and a specificity
   list. One authoritative block at the end is the pattern this
   file already uses for the arrow and the rough edge, and it
   puts every display size where one person can read it.

   ⚠️ VALUES HERE MATCH WHAT IS ALREADY ON SCREEN. Step 2 is
   deliberately invisible: it moves the numbers into one place
   without changing any of them. The three marked OUTLIER are
   the consolidation Ross still has to rule on, and naming them
   is the point — step 3 is deleting those three lines and
   pointing their rules at the standard step.
   ============================================================ */
:root{
  /* display — Remboy */
  /* ✅ STEP 3 APPLIED 2026-08-13. The three OUTLIER tokens are gone and
     their rules point at the standard steps. Lore 118 -> 99, Submit 90 ->
     99, and the closer 40 -> 34. Three page titles now share one size,
     with Home's 125 deliberately above them because it is the overture
     rather than a title.

     The display tier is four steps now, down from the twelve sizes the
     audit found: display, title, heading, subhead, plus card and caption
     for the two component roles. */
  /* ⚠️ --t-display IS GONE, 2026-08-16, and this is a real simplification
     rather than housekeeping. Ari asked for the Home headline to match the
     other pages' titles, which is --t-title at 99px. Keeping a --t-display
     that held an identical clamp would have been two names for one step —
     precisely the duplication that caused the --t-heading collision recorded
     above, where one name meant 38px in one block and 54px in another.

     The display tier is five steps now, not six, and Home's headline shares
     --t-title with every other page title. If a distinct Home size is ever
     wanted again, add the token back rather than widening --t-title, which
     three other pages depend on. */
  --t-title:        clamp(2.6rem,  9vw,   6.2rem);  /*  99px  page titles     */
  --t-heading:      3.4rem;                         /*  54px  section heads   */
  --t-subhead:      2.1rem;                         /*  34px  step headings   */
  --t-card:         1.5rem;                         /*  24px  card headings   */
  --t-caption:      1.42rem;                        /*  23px  hero caption    */

  /* ---- body tier, added 2026-08-13 ----
     The fourth tier, and the last untokenised one. Measuring the three pages
     together found body copy at 21px on Home, 19px AND 18px on Lore, and 16px
     on Offerings — four sizes for one role, which is how the display tier
     looked before it was fixed. Ross: "Keep 21px on home. And then
     standardize to 19px on Lore and Offerings."

     Two tokens rather than one, because Home's 21px is a real decision and
     not drift: Ross set it when the Home body moved from Auria back to
     Karrik, asking to keep the size the Auria had reached. Folding it into
     --t-body would quietly undo that. Naming it --t-body-lead says the
     difference is intentional, so nobody "fixes" the inconsistency later.

     ⚠️ The 1.16rem below is the same number this file already got wrong once,
     in the opposite direction — see the stale-value warning on .rw-page-home
     .sub above, where 1.16 was written for a rule that needed 1.34 and the
     Home body silently shrank to 19px. It is correct HERE and wrong THERE.
     The two live in different tokens now precisely so that cannot recur. */
  --t-body:         1.16rem;                        /*  19px  Lore, Offerings */
  --t-body-lead:    1.34rem;                        /*  21px  Home only       */

  /* ⚠️ SEPARATE NAME, NOT --t-heading, and the collision is why. The
     earlier hierarchy block defined --t-heading as the UI heading at 38px
     and pointed "Can you take direction?" at it. This block redefined
     --t-heading as the DISPLAY section head at 54px. Same name, two
     meanings, later definition wins — so that Submit heading silently grew
     from 38px to 54px on a change that had nothing to do with it.

     Nothing errored. Diffing every rendered size against the audit taken
     before the block went in is the only reason it surfaced. A scale whose
     names are ambiguous across tiers is worse than no scale, because the
     collision looks like a deliberate value. */
  /* 2.4 -> 1.9rem cap, 38px -> 30px. Ross: "let's take 'Can you take
     direction?' down in size as well." It now sits clearly BELOW the
     display subhead at 34px, which is the right relationship — it is an
     interface heading introducing a form, not a display element — and
     clearly above the nav at 23px. */
  --t-ui-heading:   clamp(1.35rem, 2.6vw, 1.9rem);  /*  30px  Auria heading   */
}

/* ---- interface tier, all Auria ---- */
.rw-page-submit .process h2  {font-size:var(--t-ui-heading)}
nav a.lnk,
.rw-nav .rw-link a,
.rw-arrow-label              {font-size:var(--t-nav)}
.rates dt                    {font-size:var(--t-label)}

/* ---- display tier, all Remboy ---- */
.rw-page-home .stage h1,
.rw-page-home .stage h2      {font-size:var(--t-title)}
.rw-page-lore .l1,
.rw-page-offerings .l1,
.rw-page-submit .l1          {font-size:var(--t-title)}
.rw-page-lore .deeper h2     {font-size:var(--t-heading)}
.rw-page-offerings .closer   {font-size:var(--t-subhead)}
.rw-page-submit .step h3     {font-size:var(--t-subhead)}
.offer h2                    {font-size:var(--t-card)}
.hero-cap                    {font-size:var(--t-caption)}

/* ---- body tier, all Karrik ----
   These four selectors were each declaring their own size further up the
   file — .lede and .deeper p twice apiece, .sub twice — so an edit at the
   first declaration did nothing. They are declared once, here, now.

   .rw-page-offerings .prose rather than bare .prose: the base rule is (0,1,0)
   and this needs to outrank it. .prose appears only in offerings.php, so the
   page scope costs nothing and states where it applies. */
.rw-page-lore .lede,
.rw-page-lore .deeper p,
.rw-page-offerings .prose    {font-size:var(--t-body)}
.rw-page-home .sub           {font-size:var(--t-body-lead)}

/* ⚠️ EXCEPTIONS, deliberate and documented. The rule is that Remboy is the
   display and editorial voice and Auria is anything the reader OPERATES.

   .mode — "Descend" at 17px is the only Remboy below the display range.
   Ross put it there on purpose: "it's not quite a menu item. I want it to
   feel distinct." It is an exception, not drift, and it stays.

   .hero-cap — I proposed moving this to Auria as step 1, then talked myself
   out of it while writing this block, and the reasoning is worth keeping.
   "sadistic / sensual / surreal" is editorial voice sitting on a
   photograph, not something anyone operates. Being small does not make it
   UI. The rule needed sharpening, not the caption moving.

   So the display tier has exactly one exception, and it is on the record.
   Everything else on Remboy sits at 24px or above. */

/* ---- responsive: the tier scales as one, rather than each selector
   carrying its own breakpoint copy ---- */
/* --t-closer is gone from both of these. Step 3 pointed the closer at
   --t-subhead, so the token fed nothing — and a token nothing reads is the
   exact drift this block exists to prevent. */
@media (max-width:1100px){
  :root{
    --t-heading: 2.6rem;
    --t-subhead: 1.9rem;
  }
}
@media (max-width:780px){
  :root{
    --t-heading: 2.3rem;
    --t-subhead: 1.7rem;
    --t-card:    1.32rem;
    --t-caption: clamp(.95rem, 4.4vw, 1.42rem);
    /* Lore already stepped its body down here, as 1.06rem on .lede and
       1.04rem on .deeper p. Both become one value. Offerings picks up a
       narrow-width step it never had — it sat flat at 16px everywhere —
       which is the point of the role owning a size rather than each page.
       --t-body-lead is deliberately absent: Home's copy carried no
       breakpoint of its own before this and gains none now. */
    --t-body:    1.06rem;
  }
}

/* ============================================================
   THE GUARD'S FIRST-RUN FINDINGS — 2026-08-13
   Seven violations from notes/type-guard.js, worked through.
   Anything added later must go below this block.
   ============================================================ */

/* ---- .l2b "to submit." ----
   Was clamp(1.7rem,5vw,3.4rem), which happened to equal --t-heading at 1512
   and diverged everywhere else: 54px against the token's 42px at 1100. It
   passed the width I test most and failed the two I do not. Now the token
   itself, so it tracks by construction rather than by coincidence. */
.rw-page-submit .l2b{font-size:var(--t-heading)}

/* ---- form field labels, and .req which inherits from them ----
   Sat at .72rem x scale = 20px while --t-label is .76rem x scale = 21px.
   One pixel, three selectors, every width — small enough to never be
   noticed and exactly the kind of near-miss that makes a scale meaningless.
   A step you are close to is not a step you are on. */
.rw-page-submit .step label:not(.next):not(.back){font-size:var(--t-label)}

/* ---- the primary CTA ----
   ⚠️ THE FACE IS A DELIBERATE EXCEPTION, THE SIZE IS NOT.

   "Continue" and "Submit to submit" render in Remboy, which contradicts the
   rule that Auria owns anything the reader operates. Kept on Remboy: the
   rule above it reads "still typeset, not chrome — no fill, no chip", and it
   is the same decision as .mode, an interactive element deliberately given
   the display voice rather than button chrome. Moving it to Auria would
   change the look of the page's primary action, which nobody asked for.
   Recorded in the guard as an exception with that reasoning.

   The SIZE was a genuine bug. 2.15rem = 34px, which equalled --t-subhead at
   1512 by coincidence and broke at 1100 when the token dropped to 30px.
   That is the finding that justified resolving tokens at runtime instead of
   hardcoding pixels — a fixed allowlist would have called it clean forever. */
.rw-page-submit :is(.next,button[type=submit]){font-size:var(--t-subhead)}

/* ============================================================
   TEXT SITS STRAIGHT — 2026-08-14
   THIS BLOCK IS LAST AND THEREFORE WINS.
   ============================================================

   Ross: "There's still a few places where text is slightly rotated. Let's
   clean all that up. Straight lines across the board, except for the header
   nav, those can stay the way they are."

   ⚠️ THIS REVERSES A DELIBERATE EARLIER DECISION, which is why it is stated
   rather than quietly applied. The photo-squaring block further up carries
   the line "Type and UI rotation stays. Ari asked to square the PHOTOS, not
   to flatten the design." That was correct then and is superseded now. The
   comment up there has been amended to point here.

   THE NAV NEEDS NO EXCEPTION, and this is worth knowing before anyone goes
   looking for one. The header nav is not rotated and never was — measured
   across four pages and three widths, zero rotation inside .rw-nav. Its
   staggered look comes from vertical offsets on the links, not tilt. So
   Ross's carve-out costs nothing here; nothing below touches the nav, and
   the stagger is left exactly as it is.

   WRITTEN AS AN EXPLICIT LIST, not a blanket `* { transform: none }`.
   Transforms do real structural work on this site — the four thorn plates,
   the marquee frames, the scroll-strip figures and the Home overture are all
   transform-driven, and a global reset would take the site apart. Every
   selector below was found by measuring rendered rotation, not by reading
   the source, because ~40 rotate() declarations exist in this file and only
   the ones here survive the cascade.

   Some of these are ALREADY straight, overridden by an earlier pass, and are
   named anyway. That is deliberate: they still carry a live rotate() at
   their source rule, so they are one override-removal away from tilting
   again. Naming them here makes the intent survive that. */

/* --- actually tilted before this block --- */
.rates dd:nth-of-type(2n),
.rates dd:nth-of-type(3n),          /* prices, -1.4deg and 1.1deg */
.rates .terms,                      /* deposit + cancellation, 0.4deg */
.offer:nth-child(1),
.offer:nth-child(2),                /* the two cards, -0.45deg and 0.35deg */
.rw-page-submit .l2b,               /* "to submit." -1.4deg */
.rw-page-submit .process h2,        /* "Can you take direction?" -1deg */

/* --- not in the DOM at page load, so invisible to a straight sweep, but
       they carry a rotate() and would arrive tilted. .rw-errsum appears only
       on a validation failure; .rw-confirm h3 only after a successful
       submit. Both are text a real applicant sees. --- */
.rw-page-submit .rw-errsum,
.rw-page-submit .rw-confirm h3,

/* --- already straight via an earlier override, pinned here so a future
       edit at source cannot bring the tilt back --- */
.rates,
.rw-page-submit form,
.rw-page-submit .step h3,
.rw-page-submit .next,
.rw-page-submit .back,
.rw-page-submit button[type=submit]{
  transform:none;
}

/* ---- the process steps need room before the form panel ----
   Ross, 2026-08-14, with a screenshot: "the directions body text is getting
   cut off by the top of the form on submit page. Let's put a little pad
   there."

   ⚠️ NOT CAUSED BY THE DE-ROTATION ABOVE, though it surfaced right after it,
   and the distinction matters for anyone reading this later. Measured: the
   de-rotation commit added no layout property of any kind. What it changed
   is how visible this was — a tilted block hides a 9px collision and a
   straight one does not. Pre-existing bug, newly obvious.

   TWO CAUSES STACK, and fixing only the visible one would leave the real
   one armed:

   1. .steps li:nth-child(2n) carries transform:translateY(.75rem) to
      stagger the numerals. TRANSFORMS DO NOT AFFECT LAYOUT, so ol.steps
      computes its height as if nothing moved — the even items hang 12px
      below their own container's bottom edge. This is the same class of
      trap as relatively-positioned children, and it has now cost time on
      this project more than once.

   2. form.rw-rough draws its torn panel from a ::before at top:-24px, so
      the panel paints 24px ABOVE the form's element box. Panel insets
      expanding past the element box is the recurring bug on this site —
      this is the fourth time. Any gap here must clear the inset, not just
      the boxes, which is why measuring element rects alone reported the
      layout as fine.

   Net before this rule: painted panel top sat 9px INSIDE the deepest step
   text, identically at 1512, 1280 and 1100.

   So: .steps gains padding-bottom matching its own maximum downward
   translate, which makes the container actually contain what it renders and
   stops this recurring if the copy or the stagger changes. .process then
   adds the real breathing room on top of a container that is finally
   honest about its height. */
.rw-page-submit .steps{
  padding-bottom:.75rem;   /* == the largest translateY on the li stagger */
}
.rw-page-submit .process{
  padding-bottom:2rem;
}

/* ---- mobile: hero images sit centred, not hard against the left edge ----
   Ross, 2026-08-14: "on mobile fix all left aligned images. Make them
   centered."

   WHY THE IMAGE AND THE HOLDER BOTH NEED IT, which measuring made obvious
   and reading the CSS would not have:

   Below 780 the holder is full width and the IMAGE is narrower than it —
   Home's is 336px in a 390px holder, Offerings' is 286px — because both
   images are sized by max-height with width:auto, so their width falls out
   of their height. A block image narrower than its container sits left.

   Between 781 and 1100 the situation inverts on Home: the holder itself
   shrinks to the image's 336px and then sits left in the page, so centring
   the image inside it does nothing. That band needs the HOLDER centred.
   Hence auto margins on both, rather than one guessed fix.

   ⚠️ THE UPPER BOUNDS ARE DIFFERENT ON PURPOSE, AND ARE NOT A TYPO.
   Home stops at 1100 and Offerings at 900, because that is where each
   page's desktop composition takes over and puts the hero deliberately to
   the RIGHT — Home by absolute positioning, Offerings as the second column.
   Measured: at 1024 the Offerings hero already sits at left 608 of 1024.
   Centring either above its bound would break the intended layout, so the
   rules stop exactly where the stacked layout does. */
/* ⚠️ !important IS REQUIRED HERE AND IS NOT LAZINESS. A bare `.hero` rule
   sets `margin:1.6rem 0 0 !important` inside the same 1100 media query, and
   it is declared TWICE — once around line 348 and again around line 401,
   with different top values. Being last in the file does not beat
   !important, so the first version of this fix deployed cleanly, changed
   nothing, and measured identically. Confirmed by forcing !important inline
   in the browser: the hero jumped from left 0 to left 247 at 830px.

   Overriding page-scoped rather than editing that bare rule, which is the
   same reasoning the photo-squaring block records. `.hero` is shared by Home
   and Offerings, and between 901 and 1100 the Offerings hero is a FLEX ITEM
   in the two-column layout — giving it auto inline margins would pull it to
   the centre of its flex line and break the composition. .rw-page-home .hero
   is (0,2,0) against the bare rule's (0,1,0), so it wins on specificity and
   touches only the page that needs it. */
@media (max-width:1100px){
  .rw-page-home .hero{
    margin-left:auto !important;
    margin-right:auto !important;
  }
  .rw-page-home .hero img{
    display:block;
    margin-left:auto;
    margin-right:auto;
  }
}
@media (max-width:900px){
  .rw-page-offerings .hero img{
    display:block;
    margin-left:auto;
    margin-right:auto;
  }
}

/* ============================================================
   THE MOBILE MENU — 2026-08-14
   THIS BLOCK IS LAST AND THEREFORE WINS.
   ============================================================

   Ross: "can we make a hamburger style menu on mobile. Or something that feels
   a little more appropriate for mobile that stays in line with the aesthetic
   for the site? Right now having the header nav on the home page doesn't look
   right."

   It is also the most on-brand interaction available. Ari's brief asks for
   "hide and seek of info throughout" — a menu that conceals and reveals reads
   that literally rather than as a concession to mobile convention.

   ⚠️ ARCHITECTURE: <details> IS A STATE HOLDER, NOT A CONTAINER, AND THAT IS
   THE WHOLE DESIGN. The obvious build puts the links inside the <details>.
   Do not do that. It was tested and rejected for a specific reason:

   Desktop needs those links visible while the element is CLOSED, and the only
   CSS that can reveal closed <details> content is
   `::details-content { content-visibility: visible }` — verified in a browser
   against three alternatives, of which `details:not([open]) > *{display:block}`
   and `details{display:contents}` both failed. That pseudo-element is Chrome
   131+. Building the desktop nav on it means the nav links VANISH ENTIRELY in
   any browser that lacks it. Unacceptable failure mode for a nav.

   So the <details> here holds only the trigger. The links stay exactly where
   they were, as siblings, and `:has()` reads the open state off the element and
   restyles the nav around it. Consequences, all good:

     - Desktop has ZERO dependency on <details>. If it were unsupported, the
       trigger simply never shows and the nav is what it always was.
     - The three links stay individual wp:paragraph blocks, so ARI KEEPS
       BLOCK-EDITOR EDITABILITY. The plan had written that off as a necessary
       loss; it is not necessary.
     - Keyboard support is still native: <summary> is focusable and toggles on
       Enter and Space with no JavaScript.

   :has() carries this, and there are already 23 :has() rules in this file, so
   it is not a new dependency.

   ⚠️ TWO NO-JS GAPS, NAMED RATHER THAN DISCOVERED LATER:
     - Escape does not close it. Nothing in CSS can listen for a key. The
       trigger stays visible while open and doubles as the close control.
     - Focus is NOT trapped. Tabbing past the last link walks into the page
       underneath. Trapping focus is impossible without script. The panel is
       therefore a takeover, not a modal, and should not be described as one.
   ============================================================ */

/* Desktop and tablet: the trigger does not exist. */
.rw-menu{display:none}

@media (max-width:780px){

  /* ---- the trigger ---- */
  .rw-menu{
    display:block;
    margin-left:auto;      /* pushes it to the right of the wordmark */
    align-self:flex-start;
  }
  /* Both resets are needed: the modern one plus the WebKit pseudo-element,
     or a disclosure triangle sits next to the thorn. */
  .rw-menu summary{
    list-style:none;
    display:flex;
    align-items:center;
    justify-content:center;
    /* 44px minimum hit area — this is the one control a visitor must find
       before any of the panel's links exist. */
    width:44px;
    height:44px;
    margin:-.4rem -.4rem 0 0;
    cursor:pointer;
    -webkit-tap-highlight-color:transparent;
  }
  .rw-menu summary::-webkit-details-marker{display:none}
  .rw-menu summary:focus-visible{outline:2px solid var(--accent);outline-offset:3px}

  /* The mark itself. Same mask technique as the wordmark and the arrow tick,
     so it inherits currentColor and one file serves bone and dark. */
  .rw-menu-mark{
    display:block;
    width:28px;
    height:22px;
    background-color:currentColor;
    -webkit-mask:url('../img/menu-thorn.svg') no-repeat center/contain;
            mask:url('../img/menu-thorn.svg') no-repeat center/contain;
    transition:transform .18s ease, opacity .18s ease;
  }
  .rw-menu[open] .rw-menu-mark{transform:rotate(90deg) scaleX(1.05);opacity:.85}

  /* ---- closed: the nav is just the wordmark and the trigger ---- */
  .rw-nav .rw-link,
  .rw-nav .mode{display:none}

  /* ⚠️ The wordmark carries flex:1 0 100% at this width (see .rw-nav .mark in
     the ≤780 block further up), which was correct when three links had to wrap
     beneath it. With the links gone it forces its own full-width row and the
     trigger wraps below, making the nav 119px when the whole point is ~65px.
     Measured before and after: 119 → 67. Overriding here rather than editing
     the original, because that rule is still right for the open state, where
     the wordmark is taken out of flow entirely. */
  .rw-nav .mark{flex:0 1 auto;margin-bottom:0}

  /* ---- open: the nav becomes the takeover ----
     Coverage and obstruction, per Ari's brief, and it makes every link a large
     target by construction. */
  .rw-nav:has(.rw-menu[open]){
    position:fixed;
    inset:0;
    z-index:60;
    display:flex;
    flex-direction:column;
    justify-content:center;
    align-items:flex-start;
    gap:1.5rem;
    padding:1.2rem;
    background:var(--bg);
  }

  /* the thorn mesh reads through the panel, so it is a curtain of the site's
     own material rather than a flat sheet of background colour */
  .rw-nav:has(.rw-menu[open])::before{
    content:'';
    position:absolute;
    inset:0;
    pointer-events:none;
    background:url('../img/thorns-tile.webp') repeat;
    background-size:var(--mesh-tile) auto;
    filter:var(--thorn-filter);
    opacity:.26;
  }

  /* wordmark stays where it was, top left, so the page never loses its name */
  .rw-nav:has(.rw-menu[open]) .mark{
    position:absolute;
    top:1.2rem;
    left:1.2rem;
    flex:none;
    margin:0;
  }
  /* trigger stays put and doubles as the close control, since Escape cannot */
  .rw-nav:has(.rw-menu[open]) .rw-menu{
    position:absolute;
    top:1.2rem;
    right:1.2rem;
    margin:0;
  }

  .rw-nav:has(.rw-menu[open]) .rw-link{
    display:block;
    margin:0;
    position:relative;
    z-index:2;
  }
  /* ⚠️ font-weight:400 IS LOAD-BEARING, not tidying, and the same warning sits
     on .mode further up this file. Remboy ships Regular only — any weight above
     400 gets a synthesized bold from the browser, which throws nothing and
     merely looks wrong. The nav links inherit --ui-weight elsewhere, so this
     has to be stated. There is no --display-font variable; Remboy is reached
     through the WordPress preset, matching the pattern at .mode. */
  /* ⚠️ MATCHES THE DESKTOP NAV EXACTLY — face, weight, size and tracking are
     the same four values as .rw-nav .rw-link a. Ross, 2026-08-17: "make the
     menu font on mobile same as desktop."

     It used to be Remboy at --t-title, i.e. the 99px display face, because a
     full-bleed takeover felt like it wanted display type. That made the mobile
     menu the only place on the site where a nav item was set in the display
     face, so the menu read as a different design rather than the same nav
     revealed. Same reasoning that keeps .mode on its own exception: the nav is
     interface, and interface is Auria.

     Small in a full-screen panel is intentional now, not an oversight. The tap
     targets come from the row padding below, not from the type size. */
  .rw-nav:has(.rw-menu[open]) .rw-link a{
    font-family:var(--ui-font);
    font-weight:var(--ui-weight);
    font-size:var(--t-nav);
    letter-spacing:.05em;
    line-height:1.02;
    border-bottom:0;
    letter-spacing:0;
    display:block;
    padding:.1em 0;
  }
  .rw-nav:has(.rw-menu[open]) .mode{
    display:block;
    position:absolute;
    left:1.2rem;
    bottom:2.2rem;
    z-index:2;
  }

  /* stop the page scrolling behind the takeover. Not possible with <details>
     alone — this is the :has() doing it. */
  body:has(.rw-menu[open]){overflow:hidden}
}

/* ---- Home: clear the overture's travel path on mobile ----
   ⚠️ THIS COLLISION WAS CREATED BY PHASES 1 AND 2 TOGETHER, and neither one
   causes it alone. It was predicted in review and then confirmed by sampling
   the animation frame by frame.

   Phase 1 moved the photograph out from under the nav, so the headline rose
   into the space it vacated. Phase 2 shrank the nav from 211px to 71px, so the
   headline rose further, to top 110. The overture wordmark's box bottom sits at
   124 through the first half of its travel. Result: 11 painted frames overlap,
   worst case 15px, from t554 to t2405 of a 4s animation — about two seconds of
   the wordmark sitting on top of "does not".

   ⚠️ A CHECK AT REST WOULD HAVE PASSED THIS. At t0 the clearance is -23px and
   by t2953 it is -45px; the collision exists only in the middle. This file has
   already shipped one bug that was correct at rest and wrong mid-animation, so
   the frame sampling is not optional when this area changes.

   Fixed by pushing .stage down rather than by moving the overture: the overture
   geometry is derived from --rw-overture-line and every number in it is
   load-bearing (see the fixed-values note in production-build.md), whereas the
   stage's top padding is inert. 2.4rem -> 4.05rem gives the headline top 136
   against the overture's 124, a 12px clearance.

   Cost, stated: the wordmark-to-sentence gap widens from 56px to about 82,
   still inside the <90 gate and still against 25px on desktop. */
@media (max-width:780px){
  .rw-page-home .stage{padding-top:4.05rem}
}

/* ---- the steps container no longer needs its overflow allowance on mobile ----
   .rw-page-submit .steps carries padding-bottom:.75rem, added in 0812a8e and
   sized to exactly match the largest translateY on the li stagger, so the
   container would contain children that transforms had moved outside its
   computed height.

   The stagger is switched off at this width now, so nothing hangs below the
   container and that allowance is 12px of dead space between the last step and
   the form. It goes with the thing it existed for.

   ⚠️ THIS CANNOT BE EDITED AT SOURCE, and that is why it is here rather than in
   the ≤780 block with the rest of Phase 3. The padding is declared UNMEDIATED
   at ~line 3669, later in the file than that media query, at equal specificity
   — so a rule up there loses the cascade and would have looked applied while
   doing nothing. This file has sprung that trap repeatedly; the fix is position
   in the file, not specificity.

   ⚠️ The allowance must come BACK if the stagger ever returns at this width.
   The two belong together. */
@media (max-width:780px){
  .rw-page-submit .steps{padding-bottom:0}
}

/* ---- touch targets: the step dial and the consent box ----
   Phase 5 of the mobile audit. Gated on pointer:coarse, matching the block at
   line 2030 rather than adding a width breakpoint — the project moved off
   width-gating on 2026-08-02 after a tablet in portrait at 834px got 19px nav
   links. A finger is a finger at any viewport size.

   ⚠️ THESE RULES ARE NOW ACTUALLY VERIFIABLE, WHICH THEY WERE NOT BEFORE.
   Playwright's default context reports pointer:fine with maxTouchPoints 0, so
   every previous check of the 2026-08-02 tap-target work silently measured the
   MOUSE branch and proved nothing about touch. A context created with
   hasTouch/isMobile reports pointer:coarse, fine:false, hover:false — verify
   this block that way or it is untested.

   Measured under a real coarse pointer before this block: the step dial ran
   10x38, 12x24, 19x24 and 24x24, and the consent checkbox was 13x13. The text
   inputs (336x44) and Continue (209x44) already passed, so the 2026-08-02 work
   holds up where it applies — it simply never covered the form's own controls,
   which were built later.

   The dial fits: four 44px targets plus the existing 22.4px gaps needs 244px of
   the 336px available, so nothing overlaps. Overlapping hit areas would be
   worse than small ones, since a tap near a boundary would select the wrong
   step.

   align-items moves baseline -> center because a 44px box cannot sit on a
   shared baseline with a 24px one and still centre its numeral. The active
   step still grows to 2.1rem inside its box, so the current-step cue survives.

   ⚠️ NOT TOUCHED, AND DELIBERATELY: the nav wordmark link, 150x29. It clears
   WCAG 2.5.8 AA (24x24) and fails AAA (44x44) on height only. Padding it would
   enlarge the hit area AND the artwork, because the wordmark is a CSS mask
   sized with mask-size:contain against the padding box — the mark would grow
   with the padding. mask-origin:content-box can decouple them, but that is a
   real risk to the site's identity on every page to fix an AAA gap on a 150px
   wide target. Flagged rather than fixed. */
@media (pointer:coarse){
  .rw-page-submit .dial{align-items:center}
  .rw-page-submit .dial label{
    min-width:44px;
    min-height:44px;
    display:inline-flex;
    align-items:center;
    justify-content:center;
  }
  /* 13x13 -> 24x24. Not 44: the box sits beside a 314px label that toggles it,
     so the effective target is already large, and a 44px checkbox beside 12px
     text reads as a mistake. 24 is the WCAG 2.5.8 AA minimum and is honest
     about what this control is. */
  .rw-page-submit .consent input[type=checkbox]{
    width:24px;
    height:24px;
    flex:none;
  }
}

/* ---- Lore: move the mirror axis off the bare footer ----
   Ross: "the mirror effect on Lore looks really strong and present, where it
   doesn't on Offerings and Submit. Can we make Lore look like those two?"

   ⚠️ THE CAUSE IS GEOMETRY, NOT OPACITY, and opacity was tried first and
   rejected. A mirrored 2x2 plate has reflection axes at every HALF tile, both
   vertically and horizontally, and the design only reads as a field where no
   axis is exposed. Measured at 1512, tile 3200, so the axis period is 1112px:

     Lore        page 1294 — axis at 1112 sits 86% down, in the bare footer
     Offerings   page 1971 — axis at 1112 sits behind the offer cards
     Submit      page 1111 — page ENDS one pixel before the axis, so it never
                             appears at all

   So Lore was not doing anything different. It is simply the one page whose
   height puts a reflection line in its emptiest region, and the eye reads a
   horizontal mirror instantly once nothing covers it.

   Dropping --mesh-op to .15 was rendered and did not work: it dims the line
   without removing it, and it makes Lore's field weaker than the Offerings
   look Ross wants to match. The line has to move, not fade.

   HOW THE OFFSET WAS CHOSEN, so it can be re-derived rather than guessed:
   for each candidate offset, score how much of the axis line is covered across
   the viewport width. Current 1112 scores 52% — only .deeper, which is a
   left-hand panel, so nearly half the line runs through open background.
   Anything in 313-636 scores 94%, because .lede (0-620) and .lore-hero
   (551-1428) overlap there and between them span almost the full width.

   475 is the CENTRE of that band, not an edge, deliberately: it leaves ~160px
   of tolerance in both directions, so ordinary copy edits by Ari shift the
   layout without re-exposing the axis. 475 + 1112 = 1587, past the 1294 page
   bottom, so this is also the only axis on the page.

   ⚠️ Desktop only. The tile, the page height and the layout all change at
   narrower widths, so the same number would land somewhere arbitrary. Checked
   separately below 1101 — see the verification note in the commit. */
@media (min-width:1101px){
  .rw-page-lore .rw-chrome::before{background-position:0 475px}
}

/* ---- Home: move the mirror axis off the footer, and trim the hero ----
   Ross: "The mirror effect is also happening on the homepage, let's clean that
   up too. Also, let's cut the height of the hero image down just a tad on home,
   so that we reduce some of the unnecessary scroll. maybe 5% on top and
   bottom?"

   1. THE MIRROR AXIS — same cause as Lore, different fix, and the difference
   matters. On Lore the axis could be hidden BEHIND content, because .lede and
   .lore-hero together span nearly the full width at the same height. Home has
   no such band: the only thing that genuinely covers mesh is the hero
   photograph, which occupies x736-1416, about 45% of the width, so no vertical
   position hides an axis there.

   ⚠️ The offset-scoring metric used on Lore GAVE A FALSE 100% HERE and must not
   be trusted on this page. It counted .stage as cover — .stage is the headline
   container, transparent text with no panel behind it, so the mesh reads
   straight through the letters. A metric that cannot tell a panel from a text
   box will overrate any page whose content is mostly type.

   What works instead is arithmetic on the page height. Home is 1165 tall and
   the axis period is 1112, so axes sit at 0 and 1112 — and 1112 lands 96% down,
   in the footer, which is exactly what Ross saw. Any offset above ~53 pushes
   the second axis past the page bottom entirely, leaving ONE axis instead of
   two. 70 puts that single axis at y=70, up in the nav band where the wordmark
   and links sit and the eye is on the headline, and leaves the footer clean.

   2. THE HERO — cropped, not scaled. aspect-ratio 933/1260 is the natural
   933x1400 with 10% taken off the height, and object-fit:cover with the default
   centre position removes that as 5% off the top and 5% off the bottom, which
   is what was asked for. The subject keeps its size; only the edges go.

   ⚠️ object-fit MUST change from fill to cover here. fill is the current value
   and it renders correctly today only because the box matches the file's own
   ratio. Constrain the height while leaving fill in place and it squashes Ari's
   photograph rather than cropping it — silently, since nothing errors.

   Both scoped to ≥1101. Below that the hero is already capped by max-height and
   the page heights differ, so the offset arithmetic does not hold — see the
   separate mobile handling if it is ever wanted there. */
@media (min-width:1101px){
  /* ⚠️ REVISED — the 70px offset below was not enough and is replaced.
     Moving the single axis to y=70 put it in the nav band on the reasoning that
     the eye is on the headline there. Wrong: the top-left of Home is bare bone
     with nothing over it, so the reflection read just as strongly as it had in
     the footer, only higher up. Ross saw it immediately.

     THE FIX IS TO REMOVE THE AXIS, NOT RELOCATE IT — which is what makes Submit
     work, by accident: its page ends one pixel before its axis. Home can be
     given the same property on purpose. A mirrored quadrant is
     tile x 0.3475 tall, so a tile of 3500 gives a 1216px quadrant, and Home's
     document is 1165-1166 across every width this rule covers (measured at
     1101/1200/1280/1440/1512/1800). The whole page therefore sits INSIDE one
     quadrant and crosses no reflection line at all.

     The remaining axis at y=0 is the page's own top edge. Nothing above it is
     visible, so there is no reflection to perceive.

     ⚠️ THE ~50px MARGIN IS THE WHOLE SAFETY BUDGET. If Home's content grows
     past 1216px — roughly one and a half added lines of the standfirst — an
     axis reappears at the very bottom. If that happens, raise this tile rather
     than reintroducing an offset. Home's mesh is 9% larger than the other
     pages as a result, which is deliberate and is not worth matching. */
  .rw-page-home{--mesh-tile:3500px}
  .rw-page-home .rw-chrome::before{background-position:0 0}
  .rw-page-home .hero img{
    aspect-ratio:933 / 1134;
    object-fit:cover;
    height:auto;
  }
}

/* ---- mobile pass, 2026-08-16, from Ross's device screenshots ----
   Four fixes, three of them layout and one of them not a layout bug at all. */

/* 1. THE THORN GAP AT THE BOTTOM IS OVERSCROLL, NOT A SIZING BUG.
   ⚠️ Do not "fix" this by making .rw-chrome taller — it is already exactly the
   document height on every page, measured at 402px on all four, gap 0. The bare
   band Ross photographed is iOS rubber-banding: pull past the end and the
   browser exposes the ROOT background, which the mesh does not cover because
   the mesh is an absolutely positioned child of the document, not the root.

   Extending .rw-chrome below the document would only add scrollable height.
   Stopping the bounce is the actual fix. Confirmed with Ross that the band
   appears only when scrolling past the end. */
html{
  overscroll-behavior-y:none;
  background:var(--bg);   /* if a browser bounces anyway, it bounces on the page's own ground, not white */
}

@media (max-width:780px){

  /* 2. LORE comes in to the same column as Offerings.
     Lore's hero, lede and deeper all ran the full 390 while Offerings' prose,
     rates and offers sit at 352 with 19px either side. Ross: "too wide for
     viewfinder, need to come in to match what's on offerings." The 1.2rem here
     is the same page padding those Offerings blocks resolve to. */
  .rw-page-lore .lore-hero{
    margin-left:1.2rem !important;
    margin-right:1.2rem !important;
    max-width:calc(100% - 2.4rem) !important;
  }
  .rw-page-lore .lede,
  .rw-page-lore .deeper{
    margin-left:1.2rem;
    margin-right:1.2rem;
  }

  /* 3. OFFERINGS: the hero moves to the top and fills its column.
     .cols is flex-column at this width and .hero is a direct child of it, so
     `order` genuinely applies here — unlike the Home case, where the hero's
     parent was not a flex container and `order` would have done nothing. It
     lands directly under the page title, above the prose.

     The image was 268px inside a 390px holder — sized by max-height with
     width:auto, so its width fell out of its height and it read as a small
     centred plate rather than a hero. width:100% with a capped height and
     object-fit:cover makes it fill the column and crop instead. */
  /* ⚠️ margin-inline is 0, NOT 1.2rem, and the first attempt got this wrong.
     .cols already carries 1.2rem of page padding, so adding another 1.2rem
     here doubled it — the hero came out 325px at 38px insets while .prose,
     .rates and .offers sat at 364px inset 19. Matching those means inheriting
     the column's padding rather than adding to it. */
  .rw-page-offerings .hero{
    order:-1;
    margin:0 0 1.6rem !important;
    max-width:100% !important;
  }
  .rw-page-offerings .hero img{
    width:100%;
    height:auto;
    max-height:56vh;
    object-fit:cover;
  }
}

/* ---- four corrections, 2026-08-16 ---- */

/* 1. ⚠️ .back WAS ORPHANED BY AN EARLIER FIX, which is why it ended up LARGER
   than the primary action it sits beside. The type-scale block pinned the CTA
   sizes with `:is(.next, button[type=submit]) { font-size: var(--t-subhead) }`
   and left .back out of the list — so Continue moved onto the token while Back
   stayed on the older `calc(1.5rem * var(--ui-scale))`, which resolves to 42px
   at every width. Measured: Continue 34px at 1512 and 27px at 402, Back 42px at
   both. Ross: "back should not be larger than continue."

   --t-card rather than matching --t-subhead: Back is the secondary action and
   already muted, so a smaller step states the hierarchy rather than leaving two
   equal-weight buttons. 24px desktop, 21px below 780, both real steps on the
   scale so the guard stays green. */
/* ⚠️ label.back, NOT .back, and the extra element selector is doing real work.
   The first attempt used `.rw-page-submit .back` and changed nothing, because
   :is() TAKES THE SPECIFICITY OF ITS MOST SPECIFIC ARGUMENT. The rule holding
   Back at 42px is
     .rw-page-submit :is(.next, .back, button[type="submit"])
   and that argument list contains button[type="submit"] at (0,1,1), so the
   whole selector scores (0,2,1) — beating a plain (0,2,0) no matter how much
   later it appears. Qualifying with the element name brings this to (0,2,1)
   and the later position then wins. Diagnosed by listing every matching rule
   in cascade order and finding mine already last and still losing. */
.rw-page-submit label.back{font-size:var(--t-card)}

/* 2. The descent arrow sits lower.
   ⚠️ THIS IS A CONSEQUENCE OF UNIFYING THE RENDER PATH, not a separate bug.
   The arrow used to draw on wp_footer for Lore and Offerings, which put it
   after the footer; moving every page onto the_content fixed the watermark
   order but pulled the arrow up tight under the content, leaving a gap beneath
   it. Ross: "arrows are now sitting too high, need to be lowered to fill that
   blank space better." Spacing it down is the other half of that change. */
.rw-arrow{margin-top:4.4rem}

@media (max-width:780px){
  /* 3. Offerings hero gets room before the prose. 1.6 -> 2.6rem. */
  .rw-page-offerings .hero{margin-bottom:2.6rem !important}

  /* 4. Home hero fills the width.
     It was 326px inside a 402px column — sized by max-height with width:auto,
     so its width fell out of its height and it read as a centred plate against
     left-aligned type. Ross: "looks weird to have centered image with left
     aligned text."

     ⚠️ object-fit moves contain -> cover, and that is required rather than
     tidy. Setting width:100% while leaving `contain` would letterbox the photo
     inside the wider box instead of filling it. cover crops to the box. */
  /* ⚠️ WIDTH MATCHES OFFERINGS, it is not full bleed. First attempt used
     width:100% and Ross came back with "a little too wide now, should be same
     width as image on offerings". Offerings' hero measures 364 inset 19 at
     402 — it inherits .cols's 1.2rem page padding. Home's .hero has no such
     parent padding, so the same inset has to be stated here as a width of
     calc(100% - 2.4rem). The auto margins from the ≤1100 centring rule then
     place it at 19 either side, which is how it lines up with Offerings
     rather than merely being close to it. */
  .rw-page-home .hero,
  .rw-page-home .hero-wrap{width:calc(100% - 2.4rem);max-width:calc(100% - 2.4rem)}
  /* ⚠️ .hero-wrap needs its OWN auto margins. The width above lands on the
     wrap but NOT on .hero, because a bare `.hero{width:auto !important}` rule
     higher in this file wins there — so .hero stays full width and the 364px
     wrap sat flush LEFT inside it, gap all on the right. The auto margins on
     .hero from the ≤1100 centring block centre the wrong box. Caught by
     screenshot: the measurement said 364px wide, which was true and not the
     question — the left edge was. */
  .rw-page-home .hero-wrap{margin-left:auto;margin-right:auto}
  .rw-page-home .hero img{
    width:100%;
    height:auto;
    max-height:55.8vh;
    object-fit:cover;
  }
}


/* ============================================================
   THE GATE  —  2026-08-17
   The entrance. Ari's iron gate, the wordmark on it, and one link.
   Scoped entirely to .rw-page-gate plus the view-transition names, so
   nothing here can reach any other page.
   ============================================================ */

.rw-page-gate .rw-shell-gate{padding:0;max-width:none;overflow:visible}

/* The stage. 100svh not 100vh: on iOS vh is the LARGEST viewport, so with the
   browser chrome showing, a 100vh gate is taller than the screen and the
   bottom rail sits under the toolbar. svh is the smallest, which is the one
   that is always actually visible. Same reasoning as overscroll-behavior on
   html — the gate is the one screen that must fit exactly. */
.gate{
  position:relative;
  width:100%;height:100svh;min-height:32rem;
  display:grid;place-items:center;
  /* ⚠️ grid-template PINS THE CELL TO THE CONTAINER, and leaving it off is what
     put the wordmark at the bottom of the screen. With no explicit template the
     single implicit row is sized `auto`, meaning CONTENT height — so when the
     doors grew taller than the viewport the row grew with them, to 1441px
     inside a 900px box. `place-items:center` then faithfully centred the
     wordmark in the 1441px ROW, which lands near the bottom of the 900px you
     can actually see. The centring was never wrong; it was centring in the
     wrong box. */
  grid-template:100% / 100%;
  overflow:hidden;
}
/* Doors and wordmark occupy the SAME grid cell, so the mark sits on the gate
   without either being taken out of flow. DOM order does the layering. */
.gate > *{grid-area:1/1}

/* ⚠️ THE TWO LEAVES ARE NOT EQUAL HALVES, and assuming they were is what broke
   the first build. They window a shared coordinate space at viewBox
   "0 0 153 264" and "153 0 124 264" — the seam sits at the central knot, which
   is at x=153 of 277, not at 138.5. Laying each out in a 50%-wide column left
   the drawings letterboxed inside boxes of the wrong ratio and opened a 117px
   gap down the middle of a gate that measured as having none: seamGapPx read 0
   because the SVG BOXES touched. preserveAspectRatio had centred the artwork
   inside them, and the box is not the drawing.
   So nothing here states a width. The doors carry the whole gate's ratio, each
   leaf is full height, and each SVG takes the width its own viewBox implies —
   the two then sum to exactly the container and meet by construction. */
/* ⚠️ TWO APPROACHES ARE ALREADY DEAD HERE. Do not resurrect either.

   THE FENCE EXTENSION (tried 2026-08-17, rejected): the gate was extended
   sideways with a tiled picket run cut from its own left-hand railings, so the
   ornate panel could keep its scale while the page filled. It measured
   perfectly — 0.0% seam, bar weight matched to the pixel — and it looked wrong.
   Ross: "really disjointed and messy on desktop." The repeated railing read as
   a different object bolted onto Ari's drawing, which no amount of seam
   accuracy fixes. assets/img/gate-picket.svg was deleted with it.

   COVER (tried twice, rejected twice): scaling until both axes fill throws 541px
   of ironwork off screen at 1512x900 — the spike tips and the whole bottom rail.
   "Full gate not showing."

   SO THE GATE STRETCHES. The drawing is 277/264 and a desktop viewport is not,
   and after contain, cover and extension had each been rejected, the only lever
   left was the aspect ratio itself. It works far better than it has any right
   to, and the reason is that this is whiplash Art Nouveau ironwork rather than
   geometry: there is no circle whose distortion the eye can check, the bars
   already wander, and a 1.6x widening at 1512x900 reads as a WIDER GATE instead
   of a squashed one. Ari's spirals become slightly oval, and Guimard drew oval
   spirals anyway.

   Whole gate, all four edges, no crop and nothing added. Ross: "I really just
   want the gate full screen."

   ⚠️ THE STRETCH ONLY EVER WIDENS. It is gated behind min-aspect-ratio:277/264,
   the drawing's own ratio, so it engages only once the viewport is wider than
   the gate. Below that — every phone in portrait — the default rule fits the
   width and derives the height, because stretching a 277/264 drawing into a
   390x844 hole would squash it to less than half its width. A media query on
   the ARTWORK'S ratio rather than a device breakpoint is what keeps that
   automatic. */
.gate-doors{
  display:flex;align-items:stretch;justify-content:center;
  width:100%;
  aspect-ratio:277 / 264;      /* portrait: fit the width, height follows */
  perspective:1400px;          /* the swing needs this on the LEAVES' parent */
  perspective-origin:50% 46%;
}
.gate-leaf{
  height:100%;
  color:var(--fg);
  transform-style:preserve-3d;
  will-change:transform;
}
.gate-leaf svg{height:100%;width:auto;display:block}
.gate-l{transform-origin:left  center}
.gate-r{transform-origin:right center}

@media (min-aspect-ratio: 277 / 264){
  .gate-doors{height:100svh;aspect-ratio:auto}
  /* ⚠️ THE SEAM IS PINNED TO THE CENTRE OF THE SCREEN, and getting there is not
     as simple as giving each leaf 50%. Ross, 2026-08-17: "gate seam should sit
     center of the page. It's offset to the right."

     It was offset because the halves are NOT equal — 153 units against 124 —
     and the previous rule sized each leaf by its own viewbox width, which puts
     the seam at 153/277, i.e. 55.2% across. Correct proportions, wrong place.

     ⚠️ BUT PLAIN 50/50 IS WRONG TOO, and worse. It would squeeze 153 units into
     the same width as 124, so the left half would render at 0.81x the right
     half's horizontal scale. Both halves still touch, so nothing looks broken
     at a glance — the bar SPACING just jumps 23% as you cross the seam, right
     where the bars bunch tightest and the discontinuity is most readable.

     So: each leaf takes exactly half, the right half fills its half at scale s,
     and the left half is drawn at the SAME s — which makes it 153/124 = 123.387%
     of its leaf. It overflows to the left and .gate's overflow:hidden trims it.
     Uniform scale across the seam, seam dead centre, screen still filled, and
     the only thing lost is a strip of plain vertical bars at the far left. */
  .gate-l,.gate-r{flex:0 0 50%}
  /* ⚠️ NEGATIVE MARGIN, NOT justify-content:flex-end. The obvious version was
     to right-align the oversized half inside its leaf and let it spill left.
     It does not spill left: an item that OVERFLOWS its flex container ignores
     justify-content and runs off the END edge, so the left half overflowed
     RIGHT and lapped 177px over its neighbour at 1512x900. Measured, not
     guessed — seamGap came back -177.
     The margin states the offset directly. -23.387% of the leaf plus a width of
     123.387% puts the right edge on exactly 100%, which is the screen centre. */
  .gate-l svg{width:123.387%;margin-left:-23.387%;height:100%}
  .gate-r svg{width:100%;height:100%}
}

/* The wordmark. Same mask technique as the nav and footer marks, so it is one
   drawing that inherits ink colour and needs no dark-mode export. */
.gate-mark{
  position:relative;z-index:3;
  justify-self:center;align-self:center;
  display:block;
  /* ⚠️ SIZED UP 2026-08-17 (was min(46vw,30rem), i.e. 480px capped on desktop).
     Ross asked for it to feel more impactful and balanced. At 480px it sat in
     the middle of a full-bleed gate looking like a caption on the ironwork
     rather than the thing the page is for.
     The cap does the work on desktop and the vw does it on phones, which is why
     both moved: 42rem lands it at 672px at 1512, and 58vw keeps it proportional
     once the viewport is narrow. The scrim under it is sized in PERCENTAGES of
     this box, so it scales with the mark and needs no second edit — which is
     why those insets were written as % in the first place. */
  width:min(58vw, 42rem);
  padding:1.4rem;
  border-bottom:0;
  color:var(--fg);
}
.gate-mark-ink{
  display:block;width:100%;
  aspect-ratio:1600 / 314;
  background-color:currentColor;
  -webkit-mask:url('../img/wordmark.png') no-repeat center/contain;
          mask:url('../img/wordmark.png') no-repeat center/contain;
}

/* ⚠️ THE FIRST SCRIM WAS FAR TOO WEAK AND THE WORDMARK READ AS SPECKLED MUSH.
   It went inset:-8% -14% with the solid stop ending at 0%, so almost none of
   the ground was actually opaque and the gate's bars showed straight through
   the script's strokes. Remboy's wordmark is a fine-stroked script — around
   1-2px at this size — and it lands on the densest part of the ironwork, the
   central knot where the bars bunch.

   This is the SAME failure the rates ledger had, and the project already has a
   standing rule from it: reading matter never sits raw on the thorns, and the
   ground under it is solid, never alpha. The quiet zone protects the reading;
   opacity cannot do two jobs at once. So the centre here is fully opaque --bg
   out to 40%, and only then falls away. */
/* ⚠️ `closest-side` IS THE WHOLE FIX FOR THE VISIBLE RECTANGLE. Ross, 2026-08-17:
   "the lines are too hard. You can see that it's a rectangular shape."

   A radial-gradient defaults to farthest-CORNER, so its 100% stop sits at the
   corner distance. Along the horizontal and vertical axes the box edge is
   NEARER than that, so the gradient is still partly opaque when the element
   box clips it — and a partly-opaque fill ending at a straight boundary is
   exactly a visible rectangle. Nothing was drawing a rectangle; the falloff
   simply had not finished by the time the box ran out.

   closest-side puts the ellipse's 100% stop exactly ON the nearest edges, so
   alpha reaches zero before any clipping happens and there is no boundary left
   to see. The corners fall outside the ellipse and are transparent anyway.

   The box is also squarer than before (was -46%/-20%, a 2.7:1 letterbox that
   made the haze read as a bar). Now about 1.6:1, so the falloff is rounder. */
.gate-mark::before{
  content:'';position:absolute;inset:-140% -34%;
  background:radial-gradient(ellipse closest-side at center,
    var(--bg) 0%, var(--bg) 34%,
    color-mix(in srgb, var(--bg) 80%, transparent) 56%,
    color-mix(in srgb, var(--bg) 34%, transparent) 78%,
    transparent 100%);
  z-index:-1;
}

.gate-mark:hover .gate-mark-ink,
.gate-mark:focus-visible .gate-mark-ink{color:var(--accent)}
.gate-mark:focus-visible{outline:2px solid var(--accent);outline-offset:.4rem}

/* Landscape phones: the gate is nearly square, so at 844x390 a height-fitted
   gate leaves the wordmark sitting on the busiest bars. Pull the leaves down
   in scale and let the ground do more work. */
@media (max-height:520px){
  .gate-leaf svg{max-height:92%}
  .gate-mark{width:min(48vw,28rem)}
}

/* ============================================================
   THE OPENING
   No JavaScript. The site already declares
   `@view-transition { navigation: auto }`, so navigating from the gate to Home
   is a cross-document view transition, and the gate's exit animation IS the
   gate opening. The leaves belong only to the OLD page, so they animate out
   while Home is already painted underneath them.
   ============================================================ */

.gate-l{view-transition-name:rw-gate-l}
.gate-r{view-transition-name:rw-gate-r}

/* ⚠️ THIS IS THE HANDOFF ROSS ASKED FOR: "gate wordmark becomes the overture."
   The same view-transition-name on the gate's wordmark and on Home's overture
   makes the browser morph one into the other across the two documents, so the
   wordmark you click is the wordmark that travels up into the nav. It is one
   gesture across a page load, which is why this is a NAME and not an
   animation — there is nothing to keyframe, the browser interpolates the two
   real positions. The pairing lives in rw.css rather than either page because
   both ends have to agree, and a name that only one document declares silently
   does nothing. */
.gate-mark{view-transition-name:rw-mark}
.rw-page-home .rw-overture{view-transition-name:rw-mark}

@keyframes rw-gate-swing-l{
  to{transform:rotateY(-88deg) translateZ(2rem);opacity:.85}
}
@keyframes rw-gate-swing-r{
  to{transform:rotateY(88deg) translateZ(2rem);opacity:.85}
}

::view-transition-old(rw-gate-l){
  animation:rw-gate-swing-l 1.15s cubic-bezier(.42,0,.3,1) both;
  transform-origin:left center;
}
::view-transition-old(rw-gate-r){
  animation:rw-gate-swing-r 1.15s cubic-bezier(.42,0,.3,1) both;
  transform-origin:right center;
}
/* The leaves have no counterpart on Home, so without this the browser has
   nothing to pair them with and falls back to a cross-fade. */
::view-transition-new(rw-gate-l),
::view-transition-new(rw-gate-r){animation:none;opacity:0}

/* The wordmark's morph gets the same duration as the swing so the two read as
   one movement. Leave the site's own .58s page transition alone — that governs
   the root cross-fade, not these named pairs. */
::view-transition-group(rw-mark){animation-duration:1.15s}
::view-transition-group(rw-gate-l),
::view-transition-group(rw-gate-r){animation-duration:1.15s}

@media (prefers-reduced-motion:reduce){
  ::view-transition-old(rw-gate-l),
  ::view-transition-old(rw-gate-r){animation:none;opacity:0}
  ::view-transition-group(rw-mark){animation-duration:.01s}
}


/* ============================================================
   HOME — SCROLL RECLAIM, 2026-08-17

   ⚠️ THE PLAN FOR THIS WAS WRONG AND THE MEASUREMENT CORRECTED IT. The agreed
   fix was "overlay on desktop": move .sub and the arrow up into the hero's
   band, since the hero is already absolutely positioned there. That reclaims
   NOTHING. Measured at 1512x900: the flow content ends at 757px, but
   .rw-shell carries min-height:940px purely to reserve space for the hero,
   which is 918px tall and out of flow. The page is 1165px because of the
   PHOTOGRAPH, not because of the text under it. Moving text into a band whose
   height is set by something else changes no heights at all.

   So this block only takes the genuinely dead space — padding and margins that
   buy nothing. It REDUCES the scroll; it cannot remove it. Removing it needs
   the hero to get shorter, which is Ari's photograph and Ross's call.
   ============================================================ */

/* Desktop: the only slack not owned by the hero. */
.rw-page-home .rw-shell{padding-bottom:1.4rem}

@media (max-width:780px){
  /* Mobile is a plain stack, so rhythm is worth real pixels here — measured
     contributions were stage padding-top 64.8, sub margin 24 + padding 25.6,
     arrow margin-top 70.4 and shell padding-bottom 48. */
  .rw-page-home .stage{padding-top:2.4rem}
  .rw-page-home .sub{margin-top:.6rem;padding-top:.6rem}
  /* ⚠️ 4.4rem was set sitewide on 2026-08-17 when Ross asked for the arrows to
     sit lower. That was about arrows sitting too HIGH on pages with short
     content; Home is the opposite case, so it is overridden here rather than
     changed globally. */
  .rw-page-home .rw-arrow{margin-top:2.2rem}
  .rw-page-home .rw-shell{padding-bottom:1rem}
}


/* ⚠️ THE CROP IS ANCHORED TO THE BOTTOM, SITEWIDE FOR HOME'S HERO, and it has to
   be stated separately from the three height rules above. Those decide how much
   height is lost at each breakpoint; object-fit:cover on its own then centres
   what remains and removes half the difference from the TOP and half from the
   BOTTOM. Ross asked for the top only, so the anchor is the load-bearing half
   of this change and the heights alone would have quietly done the wrong thing.
   One rule rather than three, because it is the same instruction at every width.

   ⚠️ 80%, NOT `bottom`. Ross, 2026-08-17: "add 2% back to the top and take 2%
   off the bottom." The TOTAL crop is unchanged at 10% — the heights above are
   untouched and the page is exactly as tall as before. Only where the 10% comes
   from moved: 8% off the top, 2% off the bottom.

   The percentage is a share of the OVERFLOW, not of the image. 10% of 1260 is
   126 units hidden; 80% of 126 is 100.8 off the top, leaving 25.2 off the
   bottom, which is 8% and 2% of the original. `bottom` is simply the 100% case,
   where the whole overflow comes off the top.

   So if the crop depth ever changes, this number needs re-deriving rather than
   keeping: it is a ratio between the two ends, not a fixed offset. */
.rw-page-home .hero img{object-position:center 80%}


/* ============================================================
   CORE IMAGE BLOCK — NEUTRALISING THE <figure>
   Added 2026-08-17 with the switch to media-library images.

   wp:image cannot emit a bare <img>; it always wraps one in
   <figure class="wp-block-image">. That figure is the ONLY new
   node the editability change introduces, and three things
   depend on it carrying no box of its own:

     1. .hero-cap is position:absolute at top:38% of .hero-wrap.
        A 1em figure margin makes .hero-wrap taller and the
        caption slides down the photograph.
     2. The thorn veils are ::after at inset:0 on .hero-wrap /
        .lore-hero / .hero. A taller parent means the veil covers
        empty space below the image.
     3. .offer-img's negative margins MUST stay equal to .offer's
        padding. A figure margin breaks that equality from the
        inside.

   Core ships :where(figure){margin:0 0 1em}. That is specificity
   zero, and rw.css's `*{margin:0}` at line 133 already beats it
   on source order — but that is a coincidence of enqueue order,
   not a guarantee. State it.

   ⚠️ APPENDED AT THE END, not before the type scale as first
   drafted. The "type scale is the last word" convention was
   already broken by the gate work earlier today, and these rules
   need to win against core anyway. Do not move them up.
   ============================================================ */
.hero-wrap > .wp-block-image,
.lore-hero > .wp-block-image,
.offer-img > .wp-block-image,
.rw-page-offerings .hero > .wp-block-image{
  margin:0;
  padding:0;
  max-width:none;
  line-height:0;   /* no descender gap if a future edit makes the img inline */
}

/* Core ships .wp-block-image img{height:auto;max-width:100%} at
   (0,2,0), which outranks .offer-img img at (0,1,1). Both agree
   today; restate what must hold so a core change cannot quietly
   win. */
.hero-wrap > .wp-block-image > img,
.lore-hero > .wp-block-image > img,
.offer-img > .wp-block-image > img,
.rw-page-offerings .hero > .wp-block-image > img{
  display:block;
  width:100%;
  max-width:100%;
}

/* ⚠️ CROP SAFETY — THIS IS A MECHANISM, NOT AN INSTRUCTION.
   Once Ari can swap photographs, a replacement of the wrong shape
   would otherwise change the layout rather than be cropped by it.
   The offer cards were already safe (object-fit:cover, called
   defensive rather than cosmetic where it is declared). The two
   remaining heroes were not: .lore-hero img had no object-fit and
   no ratio at all, and Offerings' hero relied on its own file's
   proportions.

   Each ratio is its CURRENT photograph's, so nothing moves today
   and a differently-shaped replacement is cropped to fit instead
   of resizing the page. This is what turns the guide's slot table
   from a rule Ari must obey into an explanation of what happened. */
.rw-page-lore .lore-hero img{
  aspect-ratio:2400 / 1598;
  object-fit:cover;
}
.rw-page-offerings .hero img{
  aspect-ratio:681 / 1200;
  object-fit:cover;
}

/* Captions. ⚠️ THE CLEAN FIX DOES NOT EXIST — core/image has no
   supports.caption flag to unregister (checked: its supports are
   __experimentalBorder, align, anchor, color, filter,
   interactivity, shadow, spacing). Caption is an ATTRIBUTE, so
   the field cannot be removed from the editor.

   This hides it; roman_west_strip_image_caption() in functions.php
   also strips it server-side so it can never reach the front end.
   Belt and braces, because neither half alone is a guarantee.
   The guide has to explain this one — it is the single place in
   this change where structure could not replace instruction. */
.hero-wrap figcaption,
.lore-hero figcaption,
.offer-img figcaption,
.rw-page-offerings .hero figcaption{display:none}

/* ⚠️ BLOCK GAP RESET — MEASURED, NOT PRECAUTIONARY.
   theme.json sets appearanceTools:true, which enables spacing.blockGap, so core
   emits `:where(.is-layout-flow) > *{margin-block-start:24px}`. Turning
   .hero-wrap into a wp:group made .hero-cap a flow child, and it moved DOWN BY
   EXACTLY 24px — from 38% of the wrap to 41%.

   The mechanism is worth keeping: .hero-cap is position:absolute with top:38%,
   and `top` positions an absolutely positioned element's MARGIN edge, so a
   margin-block-start shifts it even though margins are usually inert under
   absolute positioning. It read as "the caption drifted", not as "a margin
   appeared".

   ⚠️ SCOPED TO .hero-wrap ONLY, deliberately. The obvious version — resetting
   `.left > *`, `.cols > *` and friends too — would have destroyed
   `.offers{margin:4.4rem 0 0}`, because a blanket `margin-block-start:0` at the
   same specificity wins on source order. Reset only where a gap is provably
   unwanted, and let the diff prove it. */
.hero-wrap > *{
  margin-block-start:0;
  margin-block-end:0;
}

/* ⚠️ TWO MORE BLOCK-GAP RESETS, EACH ISOLATED BY EXPERIMENT rather than by
   reading the cascade. Offerings came out 48px taller than before the content
   change, and bisecting by injecting one reset at a time gave:

     baseline                1944
     + .offers > * reset     1920   (-24)
     + .closer reset         1896   (-24)
     + .entry-content reset  1872   (too far)

   ⚠️ AND THAT FIRST READING WAS WRONG — 1896 was reached by TWO errors
   cancelling. The real cause of most of the growth was core zeroing
   .offer-img's negative margin (see the block below); .closer had nothing to
   do with it. Resetting .closer removed a gap that existed BEFORE this change
   too, because .closer was ALREADY a wp:group and already a flow child. It
   only looked correct because it happened to cancel the offer-card growth.

   Landing on the right total by way of two compensating mistakes is the
   failure mode this whole verification exists to catch, and a screenshot diff
   would have passed it. Only fixing the actual cause and re-measuring exposed
   it.

   .offers keeps its reset: it is display:grid with gap:3.4rem, so a block gap
   on its items is redundant and additive, and its children were raw <section>
   inside a wp:html block before — never flow children, so they never had one.

   ⚠️ .entry-content KEEPS ITS 24px. Pre-existing. */
.offers > *{margin-block-start:0}

/* ⚠️ CORE'S FLOW LAYOUT DESTROYS THE .offer-img NEGATIVE MARGIN. This is the
   subtlest thing the block conversion broke and it took a bisect to find.

   `.offer-img{margin:-1.5rem -1.6rem 1.1rem}` pulls the photograph out over
   `.offer`'s own padding so the image spans the full card — the negative top
   margin MUST equal the card's padding-top or the image stops meeting the top
   edge. Turning `.offer` into a wp:group gave it `is-layout-flow`, and core
   ships `:where(.is-layout-flow) > :first-child{margin-block-start:0}`.
   `.offer-img` is the first child. Its -1.5rem became 0.

   Measured: every offer card grew by exactly the padding it should have been
   pulling over — 22.4px at 375, 24px above 900 — and nothing else on the page
   moved. Proven by stripping the wp-block-group / is-layout-flow classes in the
   live DOM, which restored the card to 445px and the document to 3030px and put
   margin-top back to -22.4px.

   ⚠️ THE PAIRS MUST STAY EQUAL. -1.5rem against .offer{padding:1.5rem …} and
   -1.4rem against .offer{padding:1.4rem …}. If either side is ever retuned, the
   other moves with it — that equality is what the verification asserts by
   checking .offer-img's left edge against .offer's. */
.offer > .offer-img{margin-block-start:-1.5rem}

@media (max-width:900px){
  .offer > .offer-img{margin-block-start:-1.4rem}
}

/* ⚠️ SAME BLOCK-GAP BUG AS .offers, ONE PAGE LATER. Converting .cols2 to a
   wp:group made the form a block sibling of .process, so core's
   `:where(.is-layout-flow) > *{margin-block-start:24px}` landed on it. .cols2 is
   flex with its own gap:1.7rem, so the 24px is added on top of a gap that was
   already correct — the form dropped 24px and the page grew by the same.

   Before the conversion nothing inside .cols2 was a block (it was all one
   wp:html), so blockGap never reached these children. It does now.
   Measured: form margin-block-start 24px, .process 0px. */
.cols2 > *{margin-block-start:0}
