/* The design tokens live in the package — @rainyseason/shell (packages/shell/
   tokens.css), served at /shell/tokens.css. One source. @import must precede
   all rules. */
@import url("/shell/tokens.css?v=2026-08-02a");
/* The button — .btn and every variant — is single-sourced in button.css and
   shared verbatim with the concierge (tools/sync-shared.sh). @import after
   tokens (it consumes them), before all rules. */
@import url("/shell/button.css");

/* ============================================================
   rainyseason.cr — shared shell
   Single source of truth for every surface's foundation:
   fonts, base reset, and the fixed menu bar. Tokens: the import above.

   A page gets the full house style + menu bar by doing just:
     <link rel="stylesheet" href="/shell/shell.css">
     ... content inside <div class="shell"> ...
     <script src="/shell/nav.js"></script>
     <script>RSSurfaces.mountShell();</script>

   Do NOT fork these styles into a page. Change them here so every
   surface stays identical. See AGENTS.md → "Scaffolding new pages".
   ============================================================ */

/* ============================================================
   THE HOUSE STYLE — law
   "Swiss grid, terminal ink, race-kit accents." Three layers; build
   everything new inside them:
   1. SWISS — the 8-segment grid, spacing in eights, hierarchy from
      size/weight/ink only, full-bleed grounds, no boxes, no decoration.
   2. TERMINAL — vanta ground, one ink at four opacities, 1px hairlines,
      Fira Code for anything the machine says, tracked caps for metadata.
   3. RACE KIT — small, hot hits of colour against the dark, like hi-vis
      on cycling kit: the six weather tokens for brand surfaces.
   The caps ladder proves the style: footer title 14 / row 12 / tag 8 —
   three ranks from size, weight and ink alone. The .foot-tag pill is the
   reference micro-pattern: 8px caps, 12% wash, 26% hairline, square
   edges, monochrome ink.
   ============================================================ */

/* --- Fonts --------------------------------------------------- */
@font-face { font-family:"Fira Code"; src:url("/fonts/FiraCode-Light.woff2") format("woff2"); font-weight:300; font-display:swap; }
@font-face { font-family:"Fira Code"; src:url("/fonts/FiraCode-Regular.woff2") format("woff2"); font-weight:400; font-display:swap; }
@font-face { font-family:"Fira Code"; src:url("/fonts/FiraCode-Medium.woff2") format("woff2"); font-weight:500; font-display:swap; }
@font-face { font-family:"Fira Code"; src:url("/fonts/FiraCode-SemiBold.woff2") format("woff2"); font-weight:600; font-display:swap; }
@font-face { font-family:"Fira Code"; src:url("/fonts/FiraCode-Bold.woff2") format("woff2"); font-weight:700; font-display:swap; }
@font-face { font-family:"Fira Mono"; src:url("/fonts/FiraMono-Regular.woff2") format("woff2"); font-weight:400; font-display:swap; }
@font-face { font-family:"Fira Mono"; src:url("/fonts/FiraMono-Medium.woff2") format("woff2"); font-weight:500; font-display:swap; }
@font-face { font-family:"Fira Mono"; src:url("/fonts/FiraMono-Bold.woff2") format("woff2"); font-weight:700; font-display:swap; }
@font-face {
  font-family: "Amarna";
  src: url("/fonts/Amarna-VariableFont_wght.ttf") format("truetype");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}
/* DSEG14 Classic Mini — triathlon.tech wordmark (and any brand-face--dseg).
   Face is already slanted; declare normal/normal so WebKit matches. */
@font-face {
  font-family: "DSEG14 Classic Mini";
  src:
    url("/fonts/DSEG14ClassicMini-BoldItalic.woff2") format("woff2"),
    url("/fonts/DSEG14ClassicMini-BoldItalic.woff") format("woff"),
    url("/fonts/DSEG14ClassicMini-BoldItalic.ttf") format("truetype");
  font-weight: 100 900;
  font-style: normal;
  font-display: block;
}
/* DSEG7 Modern Mini — the 7-segment LCD digits (concierge boot count, and the
   studio-seat register count). Slant is baked in; declare normal so WebKit matches. */
@font-face {
  font-family: "DSEG7 Modern Mini";
  src: url("/fonts/DSEG7ModernMini-BoldItalic.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: block;
}
/* DSEG14 Modern Mini — the same Modern LCD digits as DSEG7, but 14-segment so it
   carries a diagonal "/". Used where an LCD figure needs the slash (studio-seat
   card register: 001/333). Slant is baked in; declare normal so WebKit matches. */
@font-face {
  font-family: "DSEG14 Modern Mini";
  src:
    url("/fonts/DSEG14ModernMini-BoldItalic.woff2") format("woff2"),
    url("/fonts/DSEG14ModernMini-BoldItalic.ttf") format("truetype");
  font-weight: 100 900;
  font-style: normal;
  font-display: block;
}

/* --- Brand wordmarks (data-brand-name system) -------------------
   Single source: surfaces-nav.js BRAND_WORDMARKS. Pages mark slots with
   data-brand-name="triathlon" (etc.); mountBrandNames() fills text + face.
   Edit the registry once — every lockup, menu label face, and page title
   that uses the attribute stays in step. */
.brand-name {
  margin: 0;
  font-weight: 500;
  line-height: 1;
  color: var(--ink);
  white-space: nowrap;
}
/* Face classes win over page .brand-name font-family (higher specificity). */
.brand-name.brand-face--fira,
.brand-face--fira {
  font-family: "Fira Code", ui-monospace, monospace;
  letter-spacing: 0;
  text-transform: none;
}
.brand-name.brand-face--fira-caps,
.brand-face--fira-caps {
  font-family: "Fira Code", ui-monospace, monospace;
  letter-spacing: 0;
  text-transform: lowercase;
}
.brand-name.brand-face--amama,
.brand-face--amama {
  font-family: "Amarna", "EB Garamond", ui-serif, Georgia, serif;
  letter-spacing: var(--track-wordmark);
  text-transform: none;
}
.brand-name.brand-face--dseg14-classic-mini,
.brand-face--dseg14-classic-mini,
h2.brand-face--dseg14-classic-mini {
  font-family: "DSEG14 Classic Mini", "Fira Code", ui-monospace, monospace;
  font-style: normal;
  font-weight: 500;
  font-synthesis: none;
  letter-spacing: var(--track-title);
  text-transform: none;
  -webkit-font-smoothing: antialiased;
}

/* --- Tokens — LOCKED --------------------------------------------
   These values are the brand. Do NOT change, "correct", or re-derive any
   of them without explicit instruction.
   Mirrors (DESIGN.md, data/design-tokens.json, design skill) follow THIS
   block — sync them when a change is authorised, never the reverse. */
/* The vanta guard: on the light ground NO dark ink
   ever sits translucent. Text recedes ride the ink ladder (all-black in
   light, above); glyph hardware keeps its dim-at-rest language on the dark
   ground only — in light every mark and the receded nav mark pin solid. */
html.is-light .tabgrid-glyph,
html.is-light .tabgrid-cell svg[data-wx="all-segments"].tabgrid-glyph,
html.is-light .top.is-open .brand-btn,
html.is-light .top.is-open .brand-btn:hover {
  opacity: 1;
}
/* Type ramp + tracking: /shell/tokens.css (--t-*, --track-*). --t-body is
   also the live control the palette system adjusts (surfaces-nav.js fontBody). */

/* --- Base ---------------------------------------------------- */
* { box-sizing: border-box; }
html, body { margin: 0; height: 100%; }
html { scroll-behavior: smooth; }   /* lifted from pages; honours reduced-motion below */
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }
/* ── Browser scrollbar — a slim, moody dark-gradient thumb that recedes into the
   ground; the track stays invisible. Firefox gets the thin dark thumb (no
   gradient there). The swipe rails hide their own bars via more-specific rules,
   so this only dresses the page + panel scrollbars. (William, 2026-08-02) */
html { scrollbar-width: thin; scrollbar-color: color-mix(in srgb, var(--ink) 18%, var(--vanta)) transparent; }
::-webkit-scrollbar { width: 12px; height: 12px; }
::-webkit-scrollbar-track,
::-webkit-scrollbar-corner { background: transparent; }
::-webkit-scrollbar-thumb {
  border: 3px solid transparent;                 /* transparent border insets the thumb so it reads slim */
  background: linear-gradient(180deg, color-mix(in srgb, var(--ink) 22%, var(--vanta)), var(--unlit));
  background-clip: padding-box;
  border-radius: 999px;
}
::-webkit-scrollbar-thumb:hover {
  background: linear-gradient(180deg, color-mix(in srgb, var(--ink) 34%, var(--vanta)), color-mix(in srgb, var(--ink) 10%, var(--vanta)));
  background-clip: padding-box;
}
body {
  background: var(--g);
  color: var(--ink);
  font-family: "Fira Code", ui-monospace, monospace;
  font-size: var(--t-body);
  line-height: 1.55;
  word-spacing: -0.18em; /* page-wide: tight between words */
  -webkit-font-smoothing: antialiased;
}
/* Explicit on headings so titles pick it up even if a rule resets spacing */
h1, h2, h3, h4, .brand-word {
  word-spacing: -0.28em;
}
/* --- Heading system — the house hierarchy ---------------------------
   Semantic defaults: use the element and you get the system. Space
   between headings comes from layout gaps, never heading margins.
   H1 natural casing; H2–H4 all capitals. 500 is the house heavy —
   never 600/700 in UI. Pages may size a heading UP a rung with a
   token, never with raw px. */
h1 {
  margin: 0;
  font-size: var(--t-h1);
  font-weight: 500;
  letter-spacing: var(--track-tight);
  line-height: 1.25;
  color: var(--ink);
}
h2 {
  margin: 0;
  font-size: var(--t-h2);
  font-weight: 500;
  letter-spacing: var(--track-head);
  line-height: 1.3;
  text-transform: lowercase;
  color: var(--ink);
}
h3 {
  margin: 0;
  font-size: var(--t-h3);
  font-weight: 500;
  letter-spacing: var(--track-head);
  line-height: 1.35;
  text-transform: lowercase;
  color: var(--ink);
}
h4 {
  margin: 0;
  font-size: var(--t-h4);
  font-weight: 400;
  letter-spacing: var(--track-eyebrow);
  line-height: 1.4;
  text-transform: lowercase;
  color: var(--ink-3);
}
button, textarea, input { font: inherit; color: inherit; }
button { cursor: pointer; }
a { color: inherit; text-decoration: none; }
::selection { background: rgba(0, 157, 255, 0.38); color: var(--ink); }

/* --- The page grid — Swiss, 8 segments ------------------------
   The wordmark is built on module = cap/8; pages follow the same grammar.
   Horizontal: content aligns to 8 equal columns (.grid-8) inside the page
   measure — spans of 2/4/8 are the house rhythm (no thirds).
   Vertical posters: zone rows — grid-template-rows alternating 1fr (free
   band) and auto (fixed cluster), align-items center — so every floating
   line sits EXACTLY halfway between its fixed neighbours. Never position
   poster elements with magic offsets. */
.grid-8 {
  display: grid;
  grid-template-columns: repeat(8, 1fr);
  gap: 0 20px;
}

/* Shell — column: fixed menu bar (64px) sits above scrolling content.
   Pages that scroll internally should give their content a top offset of
   64px (the bar is position:fixed and does not take layout space). */
/* --- The frame — app-wide maximum-width clamp -------------------------
   The whole app lives in one centred frame. Past --frame the sides are
   the base ground (black), so a Studio Display gets a centred site, not
   a stretched one. Every full-bleed device (bands, code grounds, split
   modes, hairlines) clips at the frame edge: "full-bleed" means the
   width of the frame, and the frame is the page. */
/* Frame and rail: /shell/tokens.css (--frame, --rail). */
.shell {
  min-height: 100%;
  display: flex;
  flex-direction: column;
  max-width: var(--frame);
  margin: 0 auto;
  overflow-x: clip; /* 100vw devices stop at the frame, never beyond */
}

/* --- Menu bar (fixed top chrome) ----------------------------- */
/* Fixed, but clamped to the same frame as the page — the glyph and
   omnibox align with the content column; beyond the frame, black. */
.top {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  max-width: var(--frame);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  flex-shrink: 0;
  height: 64px;
  padding: 0;
  /* Transparent at the top of the page, solid once anything scrolls under.
     Because --bar-surface IS --g, this is pixel identical everywhere except
     over a full-bleed hero, where the chrome floats on the picture and the
     hero's own scrim carries legibility. is-scrolled already exists for the
     hairline; the ground rides the same state. Open menu always sits on
     solid ground. */
  background: transparent;
  transition: background-color 200ms ease;
  border-bottom: 0;
  z-index: 30;
  overflow-x: clip;
}
.top.is-scrolled,
.top.is-open {
  background: var(--bar-surface);
}
/* The bar's bottom hairline is the one line that IGNORES the frame clamp —
   it runs the full window width (fixed, so ancestor clips can't cut it),
   while the bar content stays aligned to the frame. It is absent at the
   very top of the page and fades in once the content scrolls under the bar
   (is-scrolled set by surfaces-nav.js), so the bar floats free on the hero
   and only earns its edge once there is something to divide. */
.top::after {
  content: "";
  position: fixed;
  top: 64px;
  left: 0;
  right: 0;
  height: 1px;
  background: var(--line);
  pointer-events: none;
  opacity: 0;
  transition: opacity 200ms ease;
}
.top.is-scrolled::after { opacity: 1; }
/* overflow-x:clip forces overflow-y clip — lift both when menu is open */
.top.is-open {
  overflow: visible;
}
/* Menu continues the chrome; list carries the bottom stroke */
.top.is-open::after { display: none; }
.top-bar {
  position: relative;
  /* Far left = product weather · far right = search. */
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  height: 64px;
  min-height: 64px;
  /* Left inset IS the rail (glyph, menu rows and content all begin here);
     right inset is the chrome inset the search/hamburger sit in (--menu-x-r). */
  padding: 0 16px 0 var(--rail);
  gap: 14px;
  background: transparent; /* wash comes from .top */
}
.top-bar > .top-left { justify-self: start; min-width: 0; }
.top-bar > .top-right { justify-self: end; }
/* Open: no hairline under the bar — list is continuous chrome */
.top.is-open .top-bar {
  border-bottom: 0;
}
.top-left {
  display: flex;
  align-items: center;
  gap: 12px;
  min-width: 0;
  flex: 0 0 auto;
  height: 100%;
}
.top-right {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-shrink: 0;
}
/* Language switch — dead-centre of the bar, independent of the left breadcrumb
   and right hamburger widths (the bar is position:relative). Bracketed codes in
   terminal ink; brackets are dim pseudo-elements so the code reads first. The
   lit one is the current language (EN at rest). Styling only — wiring to real
   translation lands later. */
.lang-switch {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  align-items: center;
  gap: 12px;
  z-index: 1;
  /* Shown state: the switch waits for the wordmark behind it to clear before it
     fades back in (see .lang-chosen below), so the two never overlap mid-swap. */
  transition: opacity 300ms ease 200ms;
}
.lang-opt {
  appearance: none;
  -webkit-appearance: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin: 0;
  min-width: var(--hit);
  min-height: var(--hit);
  padding: 4px 2px;
  border: 0;
  background: transparent;
  font-family: "Fira Code", ui-monospace, monospace;
  font-weight: 500;
  font-size: var(--t-lede);
  letter-spacing: var(--track-micro);
  line-height: 1;
  /* Brightness is carried by opacity on the ink, not a lighter token, so the
     whole row sits well below the bar chrome and still flips for light theme. */
  color: var(--ink);
  opacity: 0.62;
  cursor: pointer;
  user-select: none;
  -webkit-user-select: none;
  transition: opacity 140ms ease;
}
/* Brackets always render in the mono face at the button's size, so the three
   bracket-pairs align cleanly even though the JP code is a CJK glyph. They sit a
   touch dimmer than the code they wrap. */
.lang-opt::before,
.lang-opt::after {
  font-family: "Fira Code", ui-monospace, monospace;
  letter-spacing: 0;
  opacity: 0.6;
}
.lang-opt::before { content: "["; margin-right: 2px; }
.lang-opt::after  { content: "]"; margin-left: 2px; }
.lang-opt:hover,
.lang-opt:focus-visible { opacity: 1; outline: none; }
/* Current language: brighter than the resting codes, still muted (never full white). */
.lang-opt.is-on { opacity: 0.85; }
/* JP code renders in a sans face for the CJK glyph, a hair smaller for optical
   size; its brackets stay mono/aligned via the rule above. */
.lang-opt--jp {
  font-family: ui-sans-serif, system-ui, "Hiragino Sans", "Yu Gothic", sans-serif;
  font-size: var(--t-small);
  letter-spacing: 0;
}
/* Narrow bars: keep the codes off the breadcrumb by tightening the row. */
@media (max-width: 560px) {
  .lang-switch { gap: 8px; }
  .lang-opt { font-size: var(--t-small); }
  .lang-opt--jp { font-size: var(--t-caption); }
}
/* The bar's centre — the live Nicoya (Costa Rica) clock. A DSEG7 Modern LCD
   readout (the machine voice), seconds and all, centred on the bar's midline the
   same way the glyph and the hamburger are (absolute + translate), so all three
   share one line. The language switch that used to sit here now lives in the
   footer. mountBarClock() (nav.js) feeds the digits; the colons blink in CSS. */
.bar-clock {
  /* Centred by LAYOUT, not a transform: span the whole bar (inset 0) and let the
     grid place the digits dead centre. A transform would put the composited layer
     on a subpixel row, and WebKit/WKWebView re-rounds that row when the menu
     toggles the bar's overflow — the 1px up/down jump seen only in the app. With
     no transform the layer sits on the pixel grid and holds still. It is a
     readout, so pointer-events:none lets clicks reach the glyph and hamburger. */
  position: absolute;
  inset: 0;
  z-index: 0;
  display: grid;
  place-items: center;
  pointer-events: none;
  white-space: nowrap;
  font-family: "DSEG7 Modern Mini", "Fira Code", ui-monospace, monospace;
  font-style: normal;
  font-weight: 400;
  font-synthesis: none;
  font-size: clamp(18px, 4vw, 24px);
  letter-spacing: var(--track-wordmark);
  /* Headroom so the DSEG glyph ink is never clipped by the bar's overflow, top
     or bottom — the face sits tall in its em box. */
  line-height: 1.3;
  color: var(--ink);
  opacity: 0.92;
  -webkit-font-smoothing: antialiased;
}
.bar-clock-face { grid-area: 1 / 1; } /* the live digits */
/* The unlit-segment ghost — DSEG's signature. "88:88:88" is the all-segments
   state; stacked in the same grid cell so it centres by layout too (no transform)
   and sits pixel-aligned behind the lit digits, in the off-hardware tone. */
.bar-clock::before {
  content: "88:88:88";
  grid-area: 1 / 1;
  z-index: -1;
  color: color-mix(in srgb, var(--ink) 7%, transparent);
  letter-spacing: inherit;
  pointer-events: none;
}
/* The clock holds dead still through open and close — same size, same place,
   same brightness — so the bar centre never jumps when the menu takes over. */
/* The colons blink like a clock; the digits hold steady. */
.bar-clock .bw-cln { animation: bwBlink 1s steps(1, end) infinite; }
@keyframes bwBlink { 50% { opacity: 0.2; } }
@media (max-width: 560px) {
  .bar-clock { font-size: 17px; }
}
@media (prefers-reduced-motion: reduce) {
  .bar-clock .bw-cln { animation: none; }
}
/* The live Nicoya temperature, riding beside the weather mark in the same DSEG7
   Modern LCD face as the clock — digits, degree and C all in the segment font. A
   secondary readout, a step down in size and a touch dimmer than the clock. Sits
   in the top-left flow (its 12px gap comes from .top-left), centred on the mark. */
.bar-temp {
  display: inline-flex;
  align-items: center;
  white-space: nowrap;
  /* No reading yet → the readout takes no space (the class display would
     otherwise beat the [hidden] UA rule). mountBarTemp reveals it on data. */
}
.bar-temp[hidden] { display: none; }
.bar-temp {
  font-family: "DSEG7 Modern Mini", "Fira Code", ui-monospace, monospace;
  font-style: normal;
  font-weight: 400;
  font-synthesis: none;
  font-size: clamp(18px, 4vw, 24px); /* same size as the clock */
  letter-spacing: var(--track-wordmark);
  line-height: 1.3; /* headroom so the DSEG ink never clips against the bar */
  color: var(--ink);
  opacity: 0.82;
  -webkit-font-smoothing: antialiased;
}
/* A hair of air before the C. */
.bar-temp .bw-tu { margin-left: 0.06em; }
@media (max-width: 560px) {
  .bar-temp { font-size: 17px; } /* tracks the clock's small-screen size */
}
/* Very narrow bars can't hold the temperature AND the centred seconds-clock
   without them touching — drop the secondary readout there; the clock holds. */
@media (max-width: 420px) {
  .bar-temp { display: none; }
}
/* The hamburger — two lines that fold into an X when the menu is open.
   Sole open/close control. */
.menu-toggle {
  appearance: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 46px;
  min-width: 46px;
  height: 46px;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: var(--r);
  background: transparent;
  color: var(--ink-2);
  cursor: pointer;
  flex-shrink: 0;
  transition: color 140ms ease;
}
.menu-toggle:hover,
.menu-toggle:focus-visible { color: var(--ink); outline: none; }
.ham-bars { position: relative; display: block; width: 22px; height: 12px; }
.ham-bar {
  position: absolute;
  left: 0;
  width: 100%;
  height: 1.5px;
  background: currentColor;
  border-radius: 2px;
  transition: transform 200ms ease, opacity 200ms ease;
}
.ham-bar:nth-child(1) { top: 2px; }
.ham-bar:nth-child(2) { bottom: 2px; }
/* Open → the two lines cross into a centred X */
.top.is-open .ham-bar:nth-child(1) { transform: translateY(3.5px) rotate(45deg); }
.top.is-open .ham-bar:nth-child(2) { transform: translateY(-3.5px) rotate(-45deg); }
@media (prefers-reduced-motion: reduce) { .ham-bar { transition: none; } }

.brand-wrap {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  align-self: center;
  /* No nudge: the mark is tight-cropped (menu-weather-mark.svg), so its left edge sits on
     the rail by structure, not by a compensating offset. */
}
/* Permanent surface icon — larger than menu glyphs */
.brand-btn {
  appearance: none;
  display: inline-flex;
  align-items: center;
  /* Glyph hugs the button's left edge, which sits on the rail (top-bar inset),
     so the mark's left lands on --rail with no offset. The touch target keeps
     its 56px via min-width; the slack falls to the glyph's right. */
  justify-content: flex-start;
  background: transparent;
  border: 0;
  border-radius: var(--r);
  margin: 0;
  cursor: pointer;
  line-height: 0;
  width: auto;
  height: 56px;
  min-width: 56px;
  min-height: 56px;
  padding: 0;
  opacity: 1;
  transition: opacity 160ms ease, filter 160ms ease;
}
/* Permanent surface icon is ALWAYS lit — full colour and animating at rest.
   The unlit/hover-lit law governs the menu-list glyphs below. Hover lives on
   the glyph itself — never a box */
.brand-btn:hover,
.brand-btn:focus-visible { background: transparent; outline: none; filter: brightness(1.15); }
.brand-btn:active { filter: brightness(1); }
/* Open: the top-left mark stays at full brightness — it is the live preview of
   the hovered row's glyph, so it must read clearly. */
.top.is-open .brand-btn {
  opacity: 1;
  background: transparent;
}
.top.is-open .brand-btn:hover {
  opacity: 1;
  background: transparent;
}
.brand-btn .brand-mark {
  display: block;
  /* ONE weather component: the bar wears the square all-segments master, the
     SAME glyph (and SAME --wx-glyph size) as the footer status mark and the
     on-page weather marks. Square box, so it lines up with every other weather
     glyph; the current surface / live weather lights its segments (data-wx-light). */
  width: var(--wx-glyph, 52px);
  height: var(--wx-glyph, 52px);
  pointer-events: none;
  user-select: none;
  -webkit-user-select: none;
}
/* Cold-load flash guard: the glyph mounts as a raw <img>
   of the COLOUR master, then inlineGlyph swaps it for the recoloured, surface-
   lit <svg>. Hold that raw <img> invisible so its baked colours never flash on
   a hard refresh — only the <img> is hit (the inline <svg> isn't), and a failed
   inline re-reveals it (.is-fallback) so the mark can never vanish. */
.brand-btn img.brand-mark { opacity: 0; }
.brand-btn img.brand-mark.is-fallback { opacity: 1; }

/* Menu-bar title — path (e.g. /studio-archive/season-one/colours) on every surface but home.
   Flex row: each path segment is preceded by the hard-rain drop streak as its
   separator. Alignment is handled by align-items:center — no magic baselines. */
.brand-word {
  display: inline-flex;
  align-items: center;
  margin: 0;
  font-family: "Fira Code", ui-monospace, monospace;
  font-weight: 500;
  font-size: var(--t-label);
  letter-spacing: 0;
  text-transform: none; /* keep path casing exact */
  color: var(--ink);
  line-height: 1;
  white-space: nowrap;
  user-select: none;
  -webkit-user-select: none;
  flex-shrink: 0;
  /* Optical middle with weather mark */
  transform: translateY(0.5px);
}
/* Leading identity wordmark — the mark's companion in the menu-bar lockup.
   Sits in .top-left after the glyph; the route reads at the label size. */
.brand-lead {
  /* Holds the FULL route breadcrumb. Capped so it leaves room for the
     top-right search magnifier and can't run away on wide screens. */
  flex: 0 1 auto;
  min-width: 0;
  max-width: min(64vw, 44ch);
  overflow: hidden;
}
.top.is-open .brand-word { opacity: 1; }

/* Surface list — full takeover: the open menu owns everything below the
   bar; the page behind disappears entirely (opaque ground, no peek-through) */
.mode-menu {
  display: none;
  position: fixed;
  top: 64px;
  left: 0;
  right: 0;
  bottom: 0;
  width: auto;
  /* The takeover obeys the app frame — same width rules as the page. */
  max-width: var(--frame);
  margin: 0 auto;
  /* No horizontal inset — row washes run full-bleed edge to edge */
  padding: 8px 0 12px;
  background: var(--bar-surface);
  border: 0;
  border-radius: 0;
  box-shadow: none;
  box-sizing: border-box;
  overflow-y: auto;
  overscroll-behavior: contain;
  /* Row geometry (rail, right inset, indicator column) lives in the canonical
     door-menu.css (.rs-doors) — the single source both surfaces mount. */
}
.top.is-open .mode-menu {
  display: flex;
  flex-direction: column;
}
.mode-menu[hidden] { display: none !important; }
.mode-close {
  appearance: none;
  background: transparent;
  border: 0;
  color: var(--ink-3);
  width: 44px;
  height: 44px;
  min-width: 44px;
  min-height: 44px;
  display: none;
  align-items: center;
  justify-content: center;
  border-radius: var(--r);
  cursor: pointer;
  font-size: var(--t-title);
  font-weight: 300;
  line-height: 1;
  padding: 0;
}
.mode-close:not([hidden]) {
  display: inline-flex;
}
/* Glyph lights up — no box (icon-hover law) */
.mode-close:hover,
.mode-close:focus-visible {
  color: var(--ink);
  background: transparent;
  outline: none;
}
.mode-close:active { color: var(--ink-2); }

/* --- Menu bar: standalone search toggle + omnibox ------------
   The magnifier + status ring is its own control (like the glyph).
   The omnibox holds only the URL breadcrumb, which becomes the live
   search input; a copy button yanks the current path. */

/* Search toggle — standalone magnifier + status ring */
.rs-search-toggle {
  appearance: none;
  position: relative;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 46px;
  min-width: 46px;
  height: 46px;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: var(--r);
  background: transparent;
  /* Quiet at rest — search is an affordance, not a feature headline */
  color: var(--ink-3);
  cursor: pointer;
  flex-shrink: 0;
  transition: color 140ms ease, background 140ms ease;
}
.rs-search-toggle:hover,
.top-bar.is-searching .rs-search-toggle {
  color: var(--ink);
}
/* Icon crossfade + rotate: magnifier out, × in */
.rs-ico {
  position: absolute;
  width: 20px;
  height: 20px;
  transition: opacity 180ms ease, transform 220ms cubic-bezier(0.4, 0, 0.2, 1);
}
.rs-ico-search { opacity: 1; transform: rotate(0) scale(1); }
.rs-ico-close  { opacity: 0; transform: rotate(-90deg) scale(0.8); }
.top-bar.is-searching .rs-ico-search { opacity: 0; transform: rotate(90deg) scale(0.8); }
.top-bar.is-searching .rs-ico-close  { opacity: 1; transform: rotate(0) scale(1); }

/* Omnibox — only the URL / typed text (+ copy) */
.omnibox {
  display: flex;
  align-items: center;
  height: 38px;
  width: 100%; /* fills the centre grid column, hugging both side elements */
  min-width: 0;
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--g-elev);
  padding: 0 6px 0 14px;
  /* No clip here — it cut the copy button's corner. Text is clipped inside
     .omni-body instead; children fit within the border. */
  transition: background 180ms ease;
}
/* Inner wash — a faint lift of the fill on hover, held while the bar is in
   use (focused or actively searching). No border/edge highlight. */
.omnibox:hover,
.omnibox:focus-within,
.top-bar.is-searching .omnibox {
  background:
    linear-gradient(rgba(241, 241, 241, 0.035), rgba(241, 241, 241, 0.035)),
    var(--g-elev);
}

/* Top-right search: the omnibox is a search field only, collapsed to nothing
   at rest (the magnifier is the whole affordance) and expanding leftward
   into a field while searching. */
.omnibox--search {
  width: 0;
  border-color: transparent;
  background: transparent;
  padding: 0;
  overflow: hidden;
  transition: width 220ms cubic-bezier(0.4, 0, 0.2, 1), padding 200ms ease,
    border-color 180ms ease, background 180ms ease;
}
.top-bar.is-searching .omnibox--search {
  width: min(340px, 62vw);
  border-color: var(--line);
  padding: 0 12px;
}
/* While searching, the breadcrumb steps aside so the field owns the bar. */
.top-bar.is-searching .brand-lead { display: none; }

/* Body — holds the breadcrumb at rest, the search input while searching */
.omni-body {
  position: relative;
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  height: 100%;
  overflow: hidden;
}
.omni-input {
  flex: 1 1 auto;
  min-width: 0;
  width: 100%;
  appearance: none;
  border: 0;
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-size: var(--t-body);
  font-weight: 500; /* one step up from body 400 */
  letter-spacing: 0;
  outline: none;
  padding: 0;
  caret-color: transparent; /* native caret hidden — custom .omni-caret used */
}
.omni-input::placeholder { color: var(--ink-3); }
.omni-input[hidden] { display: none; }

/* Custom caret — slightly thicker than the native one (JS-positioned) */
.omni-caret {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 2px;
  height: 20px;
  background: var(--ink);
  border-radius: 1px;
  opacity: 0;
  pointer-events: none;
}
.omni-caret.is-visible { animation: omniCaretBlink 1.1s steps(1) infinite; }
@keyframes omniCaretBlink { 0%, 50% { opacity: 1; } 50.01%, 100% { opacity: 0; } }

/* Search results — navigation dropdown under the omnibox */
.omni-results {
  position: fixed;
  z-index: 40;
  max-height: min(60vh, 440px);
  overflow-y: auto;
  padding: 6px;
  background: var(--g-elev);
  border: 1px solid var(--line-strong);
  border-radius: var(--r);
  box-shadow: 0 18px 44px rgba(0, 0, 0, 0.5);
}
.omni-results[hidden] { display: none; }
.omni-result {
  display: flex;
  align-items: baseline;
  gap: 12px;
  padding: 10px 12px;
  border-radius: var(--r);
  color: var(--ink-2);
  text-decoration: none;
  cursor: pointer;
  min-height: var(--hit);
  box-sizing: border-box;
}
.omni-result.is-active { background: rgba(241, 241, 241, 0.05); color: var(--ink); }
.omni-result-label { flex: 0 0 auto; font-weight: 500; font-size: var(--t-body); color: inherit; }
.omni-result-hint {
  flex: 1 1 auto;
  min-width: 0;
  font-size: var(--t-small);
  color: var(--ink-3);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.omni-result-path { flex: 0 0 auto; font-size: var(--t-caption); letter-spacing: 0; color: var(--ink-3); }
.omni-empty { padding: 14px 12px; font-size: var(--t-lede); color: var(--ink-3); }

/* Copy-path action — icon swaps to a check on success */
.omni-copy {
  appearance: none;
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--hit);
  min-width: var(--hit);
  height: var(--hit);
  margin-left: 4px;
  padding: 0;
  border: 0;
  border-radius: var(--r);
  background: transparent;
  color: var(--ink-3);
  cursor: pointer;
  flex-shrink: 0;
  /* Hover lives on the glyph itself (colour), never a box around it */
  transition: color 140ms ease;
}
.omni-copy:hover,
.omni-copy:focus-visible {
  color: var(--ink); /* the SVG lights up — no background, no outline box */
  background: transparent;
  outline: none;
}
.omni-copy:active { color: var(--ink-2); }
.top-bar.is-searching .omni-copy { display: none; }
.omni-copy-ico,
.omni-copy-ok {
  position: absolute;
  width: 20px;   /* match the magnifier icon */
  height: 20px;
  transition: opacity 160ms ease, transform 160ms ease;
}
.omni-copy-ok { opacity: 0; transform: scale(0.7); color: var(--accent); }
.omni-copy.is-copied .omni-copy-ico { opacity: 0; transform: scale(0.7); }
.omni-copy.is-copied .omni-copy-ok { opacity: 1; transform: scale(1); }

/* --- Weather motion — hover-only, hardware-mechanical ---------
   All inlined weather glyphs (surfaces-nav.js gives each `svg.wx-glyph`)
   carry annotated segments (scripts/annotate-weather-marks.py). Real-world
   behaviour with magic, rendered like LCD hardware: steps() switching,
   opacity + transform only, nothing ever blurs, glows outward, or leaves
   the glyph bounds. Animations exist ONLY under :hover — clean at rest;
   crisp restart. One programme everywhere: menu rows animate on button
   hover, content-page marks (retreat hero, product marks, lockups) on
   their own hover.
   Ambient exceptions: the permanent top-left surface
   icon (.brand-btn) runs its programme continuously — always lit, always
   alive — and any mark given the `wx-live` class
   does the same. wx-live is the ONLY sanctioned way to run a content glyph
   at rest; add it to the <img>, the inliner carries it onto the svg.
   Master-icon exception: the top-left icon is
   ALWAYS the full master LCD (all-segments-colour) — dormant OFF tone, no
   ambient programme; pages light the element in view via data-wx-light
   (see the lighting rules below). */
.wx {
  transform-box: fill-box;
  transform-origin: center;
}

/* Snow — six stars, individually programmed: irregular LCD twinkles */
@keyframes wxStar1 { 0%, 34%, 62%, 100% { opacity: 1; } 35%, 48% { opacity: 0.12; } 63%, 70% { opacity: 0.5; } }
@keyframes wxStar2 { 0%, 18%, 80%, 100% { opacity: 1; } 19%, 30% { opacity: 0.2; } 55%, 60% { opacity: 0.45; } }
@keyframes wxStar3 { 0%, 55%, 100% { opacity: 1; } 8%, 16% { opacity: 0.3; } 56%, 68% { opacity: 0.1; } }
@keyframes wxStar4 { 0%, 40%, 100% { opacity: 1; } 41%, 46% { opacity: 0.15; } 47%, 52% { opacity: 1; } 74%, 82% { opacity: 0.4; } }
@keyframes wxStar5 { 0%, 26%, 90%, 100% { opacity: 1; } 27%, 38% { opacity: 0.25; } 64%, 69% { opacity: 0.1; } }
@keyframes wxStar6 { 0%, 12%, 72%, 100% { opacity: 1; } 13%, 20% { opacity: 0.5; } 44%, 58% { opacity: 0.15; } }
:is(.brand-btn, svg.wx-glyph:hover, svg.wx-live) .wx-star1 { animation: wxStar1 1.7s steps(1, end) infinite; }
:is(.brand-btn, svg.wx-glyph:hover, svg.wx-live) .wx-star2 { animation: wxStar2 1.3s steps(1, end) infinite 0.2s; }
:is(.brand-btn, svg.wx-glyph:hover, svg.wx-live) .wx-star3 { animation: wxStar3 2.1s steps(1, end) infinite 0.5s; }
:is(.brand-btn, svg.wx-glyph:hover, svg.wx-live) .wx-star4 { animation: wxStar4 1.1s steps(1, end) infinite 0.35s; }
:is(.brand-btn, svg.wx-glyph:hover, svg.wx-live) .wx-star5 { animation: wxStar5 1.9s steps(1, end) infinite 0.65s; }
:is(.brand-btn, svg.wx-glyph:hover, svg.wx-live) .wx-star6 { animation: wxStar6 1.5s steps(1, end) infinite 0.1s; }

/* Lightning — bolts always together; the sharpest double-strike */
@keyframes wxStrike {
  0%, 100% { opacity: 0.88; filter: brightness(1); }
  7%  { opacity: 1; filter: brightness(1.9); }
  10% { opacity: 0.3; filter: brightness(1); }
  12% { opacity: 1; filter: brightness(2.1); }
  17% { opacity: 0.88; filter: brightness(1); }
  54% { opacity: 0.88; filter: brightness(1); }
  56% { opacity: 1; filter: brightness(1.7); }
  60% { opacity: 0.88; filter: brightness(1); }
}
:is(.brand-btn, svg.wx-glyph:hover, svg.wx-live) .wx-bolts {
  animation: wxStrike 1.5s steps(1, end) infinite;
}

/* Lightning, full-disappear: the always-live top-left
   glyph and the 3D hero mark run THIS, not the busy hover strike above — the
   bolt is GONE between strikes, cracks into view as a sharp triple-strike, then
   vanishes for a calm gap. A 3s period, so a strike is ~0.27s of the loop.
   Percentages are that 0.27s window mapped onto 3s (0.05s ≈ 1.67%). */
@keyframes wxStrikeSolo {
  0%    { opacity: 1; filter: brightness(1.9); } /* first crack */
  1.67% { opacity: 0; filter: brightness(1); }   /* gap — gone */
  3%    { opacity: 1; filter: brightness(2.1); } /* main crack */
  5.33% { opacity: 0; filter: brightness(1); }   /* gap — gone */
  7.33% { opacity: 1; filter: brightness(1.7); } /* afterflash */
  9%    { opacity: 0; filter: brightness(1); }   /* gone… */
  100%  { opacity: 0; filter: brightness(1); }   /* …until the next strike */
}

/* Hard rain — strobing streaks, a running downpour */
@keyframes wxRainFlash { 0%, 100% { opacity: 1; } 50% { opacity: 0.18; } }
:is(.brand-btn, svg.wx-glyph:hover, svg.wx-live) .wx-rain1 { animation: wxRainFlash 0.34s steps(1, end) infinite; }
:is(.brand-btn, svg.wx-glyph:hover, svg.wx-live) .wx-rain2 { animation: wxRainFlash 0.34s steps(1, end) infinite 0.085s; }
:is(.brand-btn, svg.wx-glyph:hover, svg.wx-live) .wx-rain3 { animation: wxRainFlash 0.34s steps(1, end) infinite 0.17s; }
:is(.brand-btn, svg.wx-glyph:hover, svg.wx-live) .wx-rain4 { animation: wxRainFlash 0.34s steps(1, end) infinite 0.255s; }

/* Coffee cloud — bright PWM glow: pure opacity, crisp edges, never spreads */
@keyframes wxGlow { 0%, 100% { opacity: 0.62; } 50% { opacity: 1; } }
:is(.brand-btn) svg[data-wx="cloud-mark"] .wx-cloud,
svg.wx-glyph[data-wx="cloud-mark"]:hover .wx-cloud,
svg.wx-live[data-wx="cloud-mark"] .wx-cloud {
  animation: wxGlow 1.4s ease-in-out infinite;
}

/* Sun + cloud — rays shimmer in sequence around the core; cloud still */
@keyframes wxRay { 0%, 24%, 100% { opacity: 1; } 8%, 16% { opacity: 0.25; } }
:is(.brand-btn, svg.wx-glyph:hover, svg.wx-live) .wx-ray1 { animation: wxRay 1.6s steps(1, end) infinite; }
:is(.brand-btn, svg.wx-glyph:hover, svg.wx-live) .wx-ray2 { animation: wxRay 1.6s steps(1, end) infinite 0.2s; }
:is(.brand-btn, svg.wx-glyph:hover, svg.wx-live) .wx-ray3 { animation: wxRay 1.6s steps(1, end) infinite 0.4s; }
:is(.brand-btn, svg.wx-glyph:hover, svg.wx-live) .wx-ray4 { animation: wxRay 1.6s steps(1, end) infinite 0.6s; }
:is(.brand-btn, svg.wx-glyph:hover, svg.wx-live) .wx-ray5 { animation: wxRay 1.6s steps(1, end) infinite 0.8s; }
:is(.brand-btn, svg.wx-glyph:hover, svg.wx-live) .wx-ray6 { animation: wxRay 1.6s steps(1, end) infinite 1s; }
:is(.brand-btn, svg.wx-glyph:hover, svg.wx-live) .wx-ray7 { animation: wxRay 1.6s steps(1, end) infinite 1.2s; }
:is(.brand-btn, svg.wx-glyph:hover, svg.wx-live) .wx-ray8 { animation: wxRay 1.6s steps(1, end) infinite 1.4s; }

/* All segments — LCD calibration flicker: boot test, then hold */
@keyframes wxCal {
  0% { opacity: 0.5; } 6% { opacity: 1; } 12% { opacity: 0.55; }
  18% { opacity: 1; } 24% { opacity: 0.75; } 32% { opacity: 1; } 100% { opacity: 1; }
}
:is(.brand-btn, svg.wx-glyph:hover, svg.wx-live) .wx-all {
  animation: wxCal 2.4s steps(1, end) infinite;
}

/* The master LCD — ONE mirror: the top-left icon
   is ALWAYS the full all-segments-colour mark, every segment dormant in
   the OFF tone. It reflects the current context via data-wx-light on
   #topChrome (RSSurfaces.lightChrome): the page's element at rest, the
   asset in view while scrolling a collection, the hovered menu row as a
   preview. Lit segments take their true colours AND run their weather
   programme; a segment's animation selector lists EXACTLY the keys that
   fill it — an unlit segment must never animate (dark flicker). */
/* The chrome LCD's segment FILLS + OFF base now come from the shared unified
   light layer (see "Unified weather-glyph light layer" below) — #topChrome
   carries data-wx-light (RSSurfaces.lightChrome), so the same context-agnostic
   rules that serve the card badges + footer status light the top-left master
   here. Only the MOTION programmes + the retreat hibiscus star override stay
   chrome-local (below): "light a segment" is the shared contract; the LCD
   weather MOTION is a chrome behaviour. */
/* SCENT retreat — all six stars wear hibiscus, so the
   top-left LCD reads as one constellation with the retreat's hibiscus 3D hero.
   Fill only: the twinkle programmes below stay, so they still sparkle. */
body[data-surface="retreat"] #topChrome .brand-btn svg .wx-star1 path,
body[data-surface="retreat"] #topChrome .brand-btn svg .wx-star2 path,
body[data-surface="retreat"] #topChrome .brand-btn svg .wx-star3 path,
body[data-surface="retreat"] #topChrome .brand-btn svg .wx-star4 path,
body[data-surface="retreat"] #topChrome .brand-btn svg .wx-star5 path,
body[data-surface="retreat"] #topChrome .brand-btn svg .wx-star6 path { fill: var(--hibiscus); }
/* programmes — re-arm the lit segments (the master rests at animation: none) */
#topChrome:is([data-wx-light="brand"], [data-wx-light="rain"], [data-wx-light="triathlon"], [data-wx-light="all"]) .brand-btn svg[data-wx="all-segments-colour"] .wx-rain1 { animation: wxRainFlash 0.34s steps(1, end) infinite; }
#topChrome:is([data-wx-light="brand"], [data-wx-light="rain"], [data-wx-light="triathlon"], [data-wx-light="all"]) .brand-btn svg[data-wx="all-segments-colour"] .wx-rain2 { animation: wxRainFlash 0.34s steps(1, end) infinite 0.085s; }
#topChrome:is([data-wx-light="brand"], [data-wx-light="rain"], [data-wx-light="triathlon"], [data-wx-light="all"]) .brand-btn svg[data-wx="all-segments-colour"] .wx-rain3 { animation: wxRainFlash 0.34s steps(1, end) infinite 0.17s; }
#topChrome:is([data-wx-light="brand"], [data-wx-light="rain"], [data-wx-light="triathlon"], [data-wx-light="all"]) .brand-btn svg[data-wx="all-segments-colour"] .wx-rain4 { animation: wxRainFlash 0.34s steps(1, end) infinite 0.255s; }
#topChrome:is([data-wx-light="drizzle"], [data-wx-light="coffee"], [data-wx-light="all"]) .brand-btn svg[data-wx="all-segments-colour"] .wx-lrain1 { animation: wxRainFlash 0.34s steps(1, end) infinite; }
#topChrome:is([data-wx-light="drizzle"], [data-wx-light="coffee"], [data-wx-light="all"]) .brand-btn svg[data-wx="all-segments-colour"] .wx-lrain2 { animation: wxRainFlash 0.34s steps(1, end) infinite 0.085s; }
#topChrome:is([data-wx-light="drizzle"], [data-wx-light="coffee"], [data-wx-light="all"]) .brand-btn svg[data-wx="all-segments-colour"] .wx-lrain3 { animation: wxRainFlash 0.34s steps(1, end) infinite 0.17s; }
#topChrome:is([data-wx-light="brand"], [data-wx-light="storm"], [data-wx-light="bolt"], [data-wx-light="all"]) .brand-btn svg[data-wx="all-segments-colour"] .wx-bolts { animation: wxStrikeSolo 3s steps(1, end) infinite; }
#topChrome:is([data-wx-light="storm"], [data-wx-light="coffee"], [data-wx-light="sun"], [data-wx-light="cloud"], [data-wx-light="all"]) .brand-btn svg[data-wx="all-segments-colour"] .wx-cloud { animation: wxGlow 1.4s ease-in-out infinite; }
#topChrome:is([data-wx-light="greater"], [data-wx-light="sun"], [data-wx-light="all"]) .brand-btn svg[data-wx="all-segments-colour"] .wx-sun { animation: wxGlow 1.4s ease-in-out infinite; }
#topChrome:is([data-wx-light="scent"], [data-wx-light="all"]) .brand-btn svg[data-wx="all-segments-colour"] .wx-star1 { animation: wxStar1 1.7s steps(1, end) infinite; }
#topChrome:is([data-wx-light="scent"], [data-wx-light="all"]) .brand-btn svg[data-wx="all-segments-colour"] .wx-star2 { animation: wxStar2 1.3s steps(1, end) infinite 0.2s; }
#topChrome:is([data-wx-light="scent"], [data-wx-light="all"]) .brand-btn svg[data-wx="all-segments-colour"] .wx-star3 { animation: wxStar3 2.1s steps(1, end) infinite 0.5s; }
#topChrome:is([data-wx-light="scent"], [data-wx-light="all"]) .brand-btn svg[data-wx="all-segments-colour"] .wx-star4 { animation: wxStar4 1.1s steps(1, end) infinite 0.35s; }
#topChrome:is([data-wx-light="scent"], [data-wx-light="all"]) .brand-btn svg[data-wx="all-segments-colour"] .wx-star5 { animation: wxStar5 1.9s steps(1, end) infinite 0.65s; }
#topChrome:is([data-wx-light="scent"], [data-wx-light="all"]) .brand-btn svg[data-wx="all-segments-colour"] .wx-star6 { animation: wxStar6 1.5s steps(1, end) infinite 0.1s; }

@media (prefers-reduced-motion: reduce) {
  .wx { animation: none !important; }
}

/* --- Live-reload toast (dev only) ---------------------------- */
.rs-live-toast {
  position: fixed;
  bottom: 20px;
  left: 50%;
  transform: translate(-50%, 12px);
  z-index: 100;
  appearance: none;
  border: 1px solid var(--line-strong);
  border-radius: var(--r);
  background: var(--g-elev);
  color: var(--ink);
  font: inherit;
  font-size: var(--t-small);
  letter-spacing: var(--track-micro);
  padding: 9px 16px;
  cursor: pointer;
  opacity: 0;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
  transition: opacity 200ms ease, transform 200ms ease, border-color 140ms ease;
}
.rs-live-toast.is-in { opacity: 1; transform: translate(-50%, 0); }
.rs-live-toast:hover { border-color: var(--accent); }

/* Standard scrolling content region for scaffolded pages.
   Clears the fixed 64px bar; scrolls independently of the bar. */
.surface {
  flex: 1;
  /* Fill the viewport so a trailing .site-foot starts below the fold:
     the footer reveals at the end of the scroll, never pinned.
     (Agent surface keeps its own fixed product layout — no .surface.) */
  min-height: 100vh;
  min-height: 100dvh;
  padding-top: 64px;
}

/* --- The split (law) ------------------------------------------
   How every content section of the app is built: one section, two
   zones. The head rail — the section's mark, MUCH bigger than a row
   glyph, above a stacked display title — and the body beside it.
   Mobile stacks head over body; desktop puts the head in a left rail
   and the body right, one shared measure, no rules, no boxes.
   Reference implementation: the universal FAQ below. New sections:
     <section class="rs-split">
       <div class="rs-split-inner">
         <div class="rs-split-head">(mark, then h2.rs-split-title)</div>
         <div class="rs-split-body">…</div>
       </div>
     </section> */
.rs-split {
  width: 100%;
  padding: var(--section-pad);   /* the shared /network rhythm — one responsive scale */
  box-sizing: border-box;
}
.rs-split-inner {
  max-width: 640px;
  margin: 0 auto;
}
.rs-split-head {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 24px;
  margin: 0 0 var(--rhythm-lead);
  padding: 0 2px;
}
/* The section's mark: hero-scale (the same 120px as .page-hero-mark),
   crowning the title from above. */
.rs-split-mark {
  width: 120px;
  height: 120px;
}
/* A real <h2> lifted to the poster scale — the section is a landmark,
   not a label. The global h2 rule does weight/case/colour. */
.rs-split-title {
  margin: 0;
  font-size: var(--t-poster);
  letter-spacing: var(--track-title);
  line-height: 1.15;
}
/* Path-style section title — "/name": the home title system (an --accent
   leading slash + lowercase code at one size), promoted to shared so every
   surface's section titles read the same. Opt-in via .rs-path; a plain
   .rs-split-title keeps the poster scale. */
.rs-split-title.rs-path {
  font-family: var(--font-code);
  font-size: var(--t-path);
  font-weight: 500;
  letter-spacing: 0;
  text-transform: lowercase;
  line-height: 1.15;
}
.rs-path-slash { color: var(--ink); }   /* forward slashes read white site-wide, never a colour (William, 2026-07-31) */
/* The home offer title, quieted (2026-07-28): a calmer scale, tighter
   tracking, more open leading and a lighter weight — sophistication over
   volume. Home-only variant; other split titles keep the poster scale. */
.rs-split-title--offer {
  font-size: var(--t-display);
  letter-spacing: var(--track-micro);
  line-height: 1.35;
  font-weight: 400;
}
@media (min-width: 761px) {
  /* .rs-split padding is now the responsive var(--section-pad) — no desktop fork. */
  .rs-split-inner {
    max-width: 1080px;
    display: grid;
    grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
    gap: 64px;
    align-items: start;
  }
  .rs-split-head { margin: 0; }
}
/* Stacked variant: head above body at all widths — no side-by-side grid.
   Use for sections where the body needs full measure (carousels, 3D panels,
   product tabs). The split structure and mobile stack are unchanged; only
   the desktop grid is suppressed. */
@media (min-width: 761px) {
  .rs-split--stacked .rs-split-inner {
    display: flex;
    flex-direction: column;
    gap: var(--rhythm-lead);
  }
  .rs-split--stacked .rs-split-head { margin: 0; }
}
/* Placeholder: split section with no content yet — title in ghost ink. */
.rs-split--placeholder .rs-split-title { color: var(--ink-3); }

/* Between sibling sections — the one generous, tokenised gap, on top of each
   section's own --section-pad (William, 2026-07-31). */
.rs-split + .rs-split,
.rs-split + .rs-duo,
.rs-duo + .rs-split,
.rs-duo + .rs-duo { margin-top: var(--section-gap); }
/* Body-copy rhythm — one gap between a section body's stacked children. */
.rs-split-body > * + * { margin-top: var(--prose-gap); }

/* ── The section media — ONE image container for every section (title →
   IMAGE → body). A <figure> capped to the reading measure so picture and body
   copy share one width; a 4:3 frame, square corners, no stroke (the photo
   carries its own edge); an optional centred credit. Consolidates the old
   .ov-frame / .globe-card / one-offs (William, 2026-07-31). ── */
.rs-split-media {
  margin: 0;
  width: 100%;
  max-width: var(--read-w, 430px);
}
.rs-media-frame {
  width: 100%;
  aspect-ratio: 4 / 3;
  border-radius: 0;
  overflow: hidden;
  background: var(--g-elev);
}
.rs-media-frame--square { aspect-ratio: 1; }
.rs-media-frame > img,
.rs-media-frame > canvas,
.rs-media-frame > svg { display: block; width: 100%; height: 100%; object-fit: cover; }
.rs-split-media > figcaption {
  margin: var(--space-sm) 0 0;
  font-family: var(--font-code, "Fira Code", ui-monospace, monospace);
  font-size: var(--t-caption);
  letter-spacing: var(--track-eyebrow);
  color: var(--ink-3);
  text-align: center;
}
.rs-split-media > figcaption a { color: var(--ink-2); text-decoration: underline; text-underline-offset: 2px; }
.rs-split-media > figcaption a:hover,
.rs-split-media > figcaption a:focus-visible { color: var(--ink); }

/* --- Universal brand FAQ (law) -------------------------------
   Mounted by RSSurfaces.mountFaq(): under the hero on most surfaces;
   at the bottom of home (above the site footer). On home (.rs-faq--open)
   every answer sits open. Elsewhere rows arrive folded. Single source:
   copy lives in surfaces-nav.js. Box-free and ruleless: rows separate
   by spacing alone. Native <details>, clickable and keyboard-accessible. */
.rs-faq {
  width: 100%;
}
/* Mobile-first: an expandable accordion — ruleless, rows separate by
   spacing alone. */
.rs-faq-list {
  display: grid;
}
.rs-faq-item > summary {
  position: relative;
  display: block; /* no index column — the question leads the row */
  padding: 20px 40px 20px 2px; /* right gutter keeps text off the hamburger toggle */
  cursor: pointer;
  list-style: none;
  /* Readable questions: natural case, full ink, relaxed tracking. */
  font-size: var(--t-label);
  font-weight: 500;
  letter-spacing: var(--track-micro);
  word-spacing: 0.02em; /* undo the page-wide tight word-spacing on questions */
  text-transform: none;
  color: var(--ink);
  line-height: 1.45;
  transition: color 140ms ease;
}
.rs-faq-q {
  max-width: 42ch;
}
/* The image slot below each question: hidden while the row is folded,
   revealed between the question and its answer when the row opens — the
   question stays anchored under the tap. Left edge aligns to the question
   and answer text. A quiet tonal fill — no border, no card — holds the
   space until real art lands. */
.rs-faq-media {
  display: none;
  width: 100%;
  max-width: 280px;
  aspect-ratio: 4 / 3;
  margin: 0 0 16px 2px;
  background: color-mix(in srgb, var(--unlit) 93%, var(--ink));
}
.rs-faq-item[open] .rs-faq-media {
  display: block;
}
/* Placeholder rows — the dedicated FAQ page carries the full set of
   slots; the ones past the written questions hold their line in ghost
   ink until each question lands. Same row anatomy as a summary, not
   interactive. */
.rs-faq-ph {
  display: block;
  padding: 20px 24px 20px 2px;
  font-size: var(--t-label);
  font-weight: 500;
  letter-spacing: var(--track-micro);
  word-spacing: 0.02em;
  line-height: 1.45;
  color: var(--ink-3);
}
.rs-faq-item > summary::-webkit-details-marker { display: none; }
.rs-faq-item > summary:hover,
.rs-faq-item[open] > summary { color: var(--ink); }
.rs-faq-item > summary:focus-visible { outline: none; color: var(--ink); }
/* The weather-symbol fixture beside the title: ONE symbol — the full
   all-segments master — every segment always drawn. The symbol never
   swaps; the active question lights its own segments, LCD-style, via
   [data-lit] on the fixture (set by mountFaq). Two tones only:
   unlit segments hold the dark off base — no
   hover lift — so only the isolated lit items read. Closed FAQ,
   closed symbol: folded pages rest at data-lit="off" (no rule — all
   dark) until a question is hovered or opened; on home the FAQ
   arrives open and the master mark (storm) burns. */
.rs-faq-fixture {
  position: relative;
  flex: 0 0 auto; /* size comes from .rs-split-mark (120px, hero scale) */
}
.rs-faq-glyph {
  display: block;
  width: 100%;
  height: 100%;
}
.rs-faq-glyph path {
  /* darker than the standard 86% off tone */
  fill: color-mix(in srgb, var(--unlit) 93%, var(--ink));
  transition: fill 160ms ease;
}
/* Segment lighting — fills mirror GLYPH_FILL_VARS (surfaces-nav.js).
   These sit after the base rule so a lit segment wins the cascade. */
.rs-faq-fixture[data-lit="rain"] :is(.wx-rain1, .wx-rain2, .wx-rain3, .wx-rain4) path {
  fill: var(--accent); /* the hard rain streaks, not the drops */
}
.rs-faq-fixture[data-lit="cloud"] .wx-cloud path { fill: var(--lit); }
.rs-faq-fixture[data-lit="bolts"] .wx-bolts path { fill: var(--rain); }
.rs-faq-fixture[data-lit="storm"] :is(.wx-rain1, .wx-rain2, .wx-rain3, .wx-rain4) path {
  fill: var(--accent); /* the hard rain */
}
.rs-faq-fixture[data-lit="storm"] .wx-bolts path { fill: var(--rain); }
.rs-faq-fixture[data-lit="storm"] .wx-cloud path { fill: var(--lit); }
/* The FAQ toggle IS the menu hamburger: the same two-line
   .ham-bars icon as the top bar, pinned to the right of each question — folding
   into an X when the row opens, matching the menu's open state. */
.rs-faq-ham {
  position: absolute;
  top: 24px;
  right: 2px;
}
/* The 45° fold inscribes the 22px bars in a ~16.6px span, centred — which
   steps the X off the right rail the closed rows hold. The 2.7px nudge
   ((22 − 16.62) / 2) puts the X's right tip back on that rail, so open and
   closed rows keep one aligned column of toggles. FAQ-scoped: the top menu's
   X is centred in its own button, where centred is correct. */
.rs-faq-item[open] .rs-faq-ham .ham-bar:nth-child(1) { transform: translateX(2.7px) translateY(3.5px) rotate(45deg); }
.rs-faq-item[open] .rs-faq-ham .ham-bar:nth-child(2) { transform: translateX(2.7px) translateY(-3.5px) rotate(-45deg); }
/* FAQ spotlight: opening a question dims the whole page —
   chrome, sections, the other rows AND the footer — behind an 80% ground scrim,
   and lifts the open row above it, the one bright thing. --g means the dim
   tracks the mode (black on dark, white on light); 80% is the single dim knob. */
body::after {
  content: "";
  position: fixed;
  inset: 0;
  background: color-mix(in srgb, var(--g) 80%, transparent);
  z-index: 40;
  pointer-events: none;
  opacity: 0;
  transition: opacity 320ms ease;
}
body.faq-spotlight::after { opacity: 1; }
.rs-faq-item[open] { position: relative; z-index: 41; }
.rs-faq-a {
  margin: 0;
  padding: 0 2px 22px 2px; /* aligned under the question */
  font-size: var(--t-body);
  line-height: 1.7;
  color: var(--ink-3); /* muted at rest ON THE LADDER — never element
    opacity, so light mode stays vanta */
  max-width: 60ch;
  transition: color 160ms ease;
}
.rs-faq-item:hover .rs-faq-a { color: var(--ink-2); }
@media (prefers-reduced-motion: reduce) {
  .rs-faq-glyph,
  .rs-faq-glyph path,
  .rs-faq-a { transition: none; }
}
/* Desktop, home only: the split does the layout (see .rs-split above);
   home (.rs-faq--open) simply arrives with every answer open — but the
   toggle stays live so any row can be folded back.
   mountFaq() forces home's <details> open at this breakpoint. */
@media (min-width: 761px) {
  .rs-faq--open .rs-faq-item > summary { color: var(--ink); }
}
/* The About page's knowledge teaser: the top few answers folded, their
   questions set in caps, then the button through to the full knowledge
   base. Home-only — the full /faq keeps its natural case. */
#knowledge .rs-faq-q {
  text-transform: lowercase;
  letter-spacing: var(--track-wordmark);
}
/* Home only: the knowledge teaser sits at the foot above the footer, a
   "more if you want it" section, not a poster landmark — so its title takes
   the headline token (a rung under display) and stays flush-left. */
#knowledge .rs-split-title {
  font-size: var(--t-headline);
  text-align: left;
}
.rs-faq-more {
  margin-top: clamp(28px, 5vh, 44px);
  padding-left: 2px; /* align to the question/answer column */
}
/* The KNOWLEDGE BASE button is a plain recent-family CTA:
   ghost hairline stroke, no visible glyph. It carries the brand weather mark
   (thunder + hard rain — the Frequently Shared Knowledge master) so its border
   lights to the brand stroke on hover, like the offer-grid buttons. */

/* --- Glossary ------------------------------------------------
   Mounted by RSSurfaces.mountGlossary() into [data-glossary-host].
   Two accordion levels (native <details>). Type follows the site footer
   directory: leading /, uppercase tracked labels, body size for section
   titles, small for terms. Section blurb above the nested term list.
   Copy is single-source in surfaces-nav.js. No FAQ on this page. */
.rs-gloss {
  display: grid;
  gap: clamp(8px, 1.5vh, 14px);
}
.rs-gloss-section {
  margin: 0;
  padding: 0;
}
.rs-gloss-section-summary {
  position: relative;
  display: block;
  list-style: none;
  cursor: pointer;
  padding: 12px 28px 12px 0; /* right gutter keeps title off the chevron */
  margin: 0;
  color: var(--ink);
  transition: color 140ms ease;
}
.rs-gloss-section-summary::-webkit-details-marker { display: none; }
.rs-gloss-section-summary:hover,
.rs-gloss-section[open] > .rs-gloss-section-summary { color: var(--ink); }
.rs-gloss-section-summary:focus-visible {
  outline: none;
  color: var(--ink);
}
/* Section title = footer column h3: body size, caps, tracked, leading /. */
.rs-gloss-section-title {
  margin: 0;
  padding: 0;
  font-size: var(--t-body);
  font-weight: 500;
  letter-spacing: var(--track-head);
  text-transform: lowercase;
  color: inherit;
  line-height: 1.2;
}
.rs-gloss-section-title::before {
  content: "/";
  color: var(--hard-rain);
}
/* Section blurb — plain definition under the category, above the terms. */
.rs-gloss-section-def {
  margin: 0 0 8px;
  padding: 0 0 0 12px; /* nest under the slash title, like footer rows */
  font-size: var(--t-body);
  font-weight: 400;
  line-height: 1.65;
  color: var(--ink-3);
  max-width: 54ch;
  text-transform: none;
  letter-spacing: 0;
}
/* Chevron — same square-border mark as the FAQ; points down, flips up open.
   Scoped per open details so a section open does not flip every term chevron. */
.rs-gloss-chevron {
  position: absolute;
  top: 50%;
  right: 2px;
  width: 7px;
  height: 7px;
  margin-top: -5px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(45deg);
  transition: transform 240ms ease;
  pointer-events: none;
}
.rs-gloss-section[open] > .rs-gloss-section-summary > .rs-gloss-chevron,
.rs-gloss-item[open] > .rs-gloss-term-summary > .rs-gloss-chevron {
  margin-top: -2px;
  transform: rotate(-135deg);
}
.rs-gloss-list {
  display: grid;
  gap: 0;
  margin: 0;
  /* Nest under the category title like .foot-col ul. */
  padding: 0 0 10px 12px;
}
/* Term row — second accordion level; path style like .foot-link. */
.rs-gloss-item {
  margin: 0;
  padding: 0;
}
.rs-gloss-term-summary {
  position: relative;
  display: block;
  list-style: none;
  cursor: pointer;
  padding: 10px 28px 10px 0;
  margin: 0;
  color: var(--ink-3);
  transition: color 140ms ease;
}
.rs-gloss-term-summary::-webkit-details-marker { display: none; }
.rs-gloss-term-summary:hover,
.rs-gloss-item[open] > .rs-gloss-term-summary { color: var(--ink); }
.rs-gloss-term-summary:focus-visible {
  outline: none;
  color: var(--ink);
}
.rs-gloss-term {
  display: block;
  margin: 0;
  font-size: var(--t-small);
  font-weight: 400;
  letter-spacing: var(--track-title);
  text-transform: lowercase;
  color: inherit;
  line-height: 1.2;
  max-width: 48ch;
}
.rs-gloss-term::before {
  content: "/";
  color: var(--hard-rain);
}
.rs-gloss-def {
  margin: 0;
  padding: 0 0 12px 0;
  font-size: var(--t-body);
  font-weight: 400;
  line-height: 1.65;
  color: var(--ink-3);
  max-width: 54ch;
  text-transform: none;
  letter-spacing: 0;
}
@media (prefers-reduced-motion: reduce) {
  .rs-gloss-chevron,
  .rs-gloss-section-summary,
  .rs-gloss-term-summary { transition: none; }
}

/* --- The shared page hero (law) ------------------------------
   Every surface opens the same way: the product mark floating at the
   EXACT centre of the first screen, the product name directly below it,
   nothing else. All other content lives on the next screen (.page-next).
   Zone rows 1fr/auto/1fr centre the cluster between bar and fold —
   never magic offsets (the page grid law). */
.page-hero {
  position: relative;
  isolation: isolate; /* keep the backdrop behind the mark, not the page */
  overflow: hidden; /* the wash drifts inside the hero, never past it */
  min-height: calc(100vh - 64px);
  min-height: calc(100svh - 64px);
  display: grid;
  grid-template-rows: 1fr auto 1fr;
  justify-items: center;
  align-items: center;
  padding: 24px;
}
/* Video-placement wash behind every hero — DISABLED: the hero is plain
   black ground until the real video plays. The per-surface palette rules
   below are kept dormant (they only paint a pseudo that no longer renders);
   flip `content: none` back to `content: ""` here to bring the whole wash +
   grain system back at once. Intent: each surface paints ONLY its weather
   colour(s) from the locked six, film grain on top, slow drift, still under
   reduced-motion. */
.page-hero::before,
.page-hero::after {
  content: none; /* no pseudo generated → hero shows the bare ground */
  position: absolute;
  inset: -8%; /* overscan so the drifting layer never bares an edge */
  z-index: -1;
  pointer-events: none;
}
/* Fallback / brand (studio) — hard rain + thunder */
.page-hero::before {
  background:
    radial-gradient(58% 55% at 22% 28%, color-mix(in srgb, var(--accent) 26%, transparent), transparent 70%),
    radial-gradient(55% 50% at 82% 24%, color-mix(in srgb, var(--accent) 14%, transparent), transparent 72%),
    radial-gradient(66% 62% at 70% 84%, color-mix(in srgb, var(--spark) 16%, transparent), transparent 72%);
  animation: heroWashDrift 24s ease-in-out infinite alternate;
}
/* Home — light rain wash, almost gone */
body[data-surface="home"] .page-hero::before {
  background:
    radial-gradient(58% 55% at 22% 28%, color-mix(in srgb, var(--storm) 5%, transparent), transparent 72%),
    radial-gradient(55% 50% at 82% 24%, color-mix(in srgb, var(--storm) 3%, transparent), transparent 74%),
    radial-gradient(62% 58% at 68% 82%, color-mix(in srgb, var(--storm) 2.5%, transparent), transparent 72%);
}
/* Studio agent — hard rain */
body[data-surface="chat"] .page-hero::before {
  background:
    radial-gradient(58% 55% at 22% 28%, color-mix(in srgb, var(--accent) 28%, transparent), transparent 70%),
    radial-gradient(55% 50% at 82% 24%, color-mix(in srgb, var(--accent) 16%, transparent), transparent 72%),
    radial-gradient(62% 58% at 68% 82%, color-mix(in srgb, var(--accent) 12%, transparent), transparent 70%);
}
/* Studio 333 — the cloud alone (ink / lit white) */
body[data-surface="studio"] .page-hero::before {
  background:
    radial-gradient(58% 55% at 22% 28%, color-mix(in srgb, var(--lit) 22%, transparent), transparent 70%),
    radial-gradient(55% 50% at 82% 24%, color-mix(in srgb, var(--lit) 12%, transparent), transparent 72%),
    radial-gradient(62% 58% at 68% 82%, color-mix(in srgb, var(--lit) 10%, transparent), transparent 70%);
}
/* Studio services — lightning */
body[data-surface="clients"] .page-hero::before {
  background:
    radial-gradient(58% 55% at 22% 28%, color-mix(in srgb, var(--spark) 26%, transparent), transparent 70%),
    radial-gradient(55% 50% at 82% 24%, color-mix(in srgb, var(--spark) 14%, transparent), transparent 72%),
    radial-gradient(62% 58% at 68% 82%, color-mix(in srgb, var(--spark) 12%, transparent), transparent 70%);
}
/* Studio communities — rising suns */
body[data-surface="ventures"] .page-hero::before {
  background:
    radial-gradient(58% 55% at 22% 28%, color-mix(in srgb, var(--sun) 26%, transparent), transparent 70%),
    radial-gradient(55% 50% at 82% 24%, color-mix(in srgb, var(--sun) 14%, transparent), transparent 72%),
    radial-gradient(62% 58% at 68% 82%, color-mix(in srgb, var(--sun) 12%, transparent), transparent 70%);
}
/* Retreat / SCENT.TECH — snow gold */
body[data-surface="retreat"] .page-hero::before {
  background:
    radial-gradient(58% 55% at 22% 28%, color-mix(in srgb, var(--gold) 26%, transparent), transparent 70%),
    radial-gradient(55% 50% at 82% 24%, color-mix(in srgb, var(--gold) 14%, transparent), transparent 72%),
    radial-gradient(62% 58% at 68% 82%, color-mix(in srgb, var(--gold) 12%, transparent), transparent 70%);
}
/* Community Coffee — rain blue only (the light-rain drops, not hard rain) */
body[data-surface="coffee"] .page-hero::before {
  background:
    radial-gradient(58% 55% at 22% 28%, color-mix(in srgb, var(--storm) 28%, transparent), transparent 70%),
    radial-gradient(55% 50% at 82% 24%, color-mix(in srgb, var(--storm) 16%, transparent), transparent 72%),
    radial-gradient(62% 58% at 68% 82%, color-mix(in srgb, var(--storm) 12%, transparent), transparent 70%);
}
/* Studio archive — all segments, quiet multi from the six */
body[data-surface="system"] .page-hero::before {
  background:
    radial-gradient(50% 50% at 18% 30%, color-mix(in srgb, var(--accent) 14%, transparent), transparent 70%),
    radial-gradient(48% 48% at 78% 22%, color-mix(in srgb, var(--storm) 12%, transparent), transparent 72%),
    radial-gradient(52% 52% at 72% 78%, color-mix(in srgb, var(--spark) 12%, transparent), transparent 72%),
    radial-gradient(46% 46% at 20% 80%, color-mix(in srgb, var(--sun) 10%, transparent), transparent 70%),
    radial-gradient(44% 44% at 50% 50%, color-mix(in srgb, var(--gold) 10%, transparent), transparent 68%);
}
.page-hero::after {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 180px 180px;
  opacity: 0.06;
  mix-blend-mode: soft-light; /* grain settles into the colour, not on top of it */
}
@keyframes heroWashDrift {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to   { transform: translate3d(0, -2%, 0) scale(1.08); }
}
@media (prefers-reduced-motion: reduce) {
  .page-hero::before { animation: none; }
}
.page-hero-center {
  grid-row: 2; /* one child in three zone rows — pin it to the centre band */
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 24px;
  text-align: center;
}
.page-hero-mark {
  width: 120px;
  height: 120px;
  display: block;
  object-fit: contain;
  user-select: none;
  -webkit-user-select: none;
}
.page-hero-word {
  margin: 0;
  font-size: var(--t-hero);
  font-weight: 500;
  letter-spacing: var(--track-title);
  text-transform: lowercase; /* hero titles run lowercase paths (William, 2026-07-29) */
  line-height: 1.15;
  color: var(--ink);
}
/* THE ONE HERO BYLINE (William, 2026-07-30) — every "By rainyseason.cr"
   credit under a page word reads the SAME: mono (Fira Code), --t-caption,
   500, 0.14em, uppercase, quiet --ink-3. .page-hero-credit is the canonical
   class; .page-hero-byline (the old Amarna "By"+1.5em lockup), .hero-byline
   (the stage pages' grey #8A879A fork) and .hero-maker (scent's "by SCENT.TECH")
   all fold into it here, and their per-page forks are deleted so nothing
   drifts. Co-branded credits (scent's SCENT.TECH, endurance's triathlon.tech)
   keep their words but wear this one face. */
.page-hero-credit,
.page-hero-byline,
.hero-byline,
.hero-maker {
  margin: 0;
  font-family: var(--font-code, "Fira Code", ui-monospace, monospace);
  font-size: var(--t-caption);
  font-weight: 500;
  letter-spacing: var(--track-eyebrow);
  text-transform: lowercase;
  color: var(--ink-3);
  line-height: 1.2;
  text-align: center;
}
/* Any inner spans (the old byline-by/byline-name lockup, scent's maker-by/scent)
   inherit the one credit face — no serif step-up, no 1.5em name. */
.page-hero-byline .byline-by,
.page-hero-byline .byline-name,
.hero-maker .maker-by,
.hero-maker .scent {
  font-family: inherit;
  font-size: inherit;
  font-weight: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  color: inherit;
}
/* Statement — the hero's most important message, one line under the name.
   A hero display size, like .page-hero-word, so it sits off the --t-* ramp.
   Still nothing else on that screen. */
.page-hero-statement {
  margin: 0;
  font-size: 18px;
  color: var(--ink-2);
  line-height: 1.35;
  /* Held to the buttons-component width so it only ever wraps to two lines.
     Fira Code is monospace, so ~34ch ≈ the two-button row beneath it. */
  max-width: 34ch;
}
/* Hero cluster type — ONE source for every page hero (home, team, …) so the
   eyebrow and body lead can never drift apart.
   Page-specific placement (alignment, measure of the column, the stack gap)
   stays on each page; the type itself lives here. The stack rhythm is the
   home reference: an eyebrow, a mono body, two CTAs, the curl — 24px apart. */
.hero-eyebrow {
  margin: 0;
  font-size: var(--t-caption); /* 11px — small eyebrow */
  font-weight: 500;
  letter-spacing: var(--track-eyebrow);
  text-transform: lowercase;
  color: var(--ink); /* full white */
  line-height: 1.2;
}
/* The parent-brand credit as the hero eyebrow — the locked product hero opens
   "By rainyseason.cr" above the page word. Never
   uppercased: the domain is always the lowercase wordmark. Quiet ink so it
   reads as a credit, not a headline. */
.hero-eyebrow--credit {
  text-transform: none;
  letter-spacing: var(--track-head);
  color: var(--ink-3);
}
.hero-lead {
  margin: 0;
  font-family: "Fira Code", ui-monospace, monospace;
  font-size: var(--t-body);
  font-weight: 400;
  letter-spacing: 0;
  line-height: 1.7;
  text-transform: none;
  color: var(--ink-2);
  word-spacing: 0.02em;
  text-align: left;
  /* Full column width on mobile — a measure cap there only wraps short of the
     rest of the cluster. The 42ch reading measure kicks in from tablet up. */
}
@media (min-width: 761px) {
  .hero-lead { max-width: 42ch; }
}

/* --- The stage hero (one source) ---------------------------------------
   The front-door hero as a STAGE: the live 3D mark rides the upper 1fr row,
   centred in the gap between the menu bar and the copy; the copy cluster
   (eyebrow at h1 scale · mono lead · CTAs · curl) anchors bottom-left.
   Home and experiences mount this same grammar — placement, ground and type
   scale all live here, never per page.
   Markup:
     <header class="page-hero stage-hero">
       <div id="weatherMark3d" class="stage-hero-3d" aria-hidden="true"></div>
       <div class="page-hero-center"> p.hero-eyebrow · h1.hero-lead · .btn-row </div>
     </header> */
/* The stage starts flush with the viewport top — no 64px surface pad. The
   menu bar overlays, which is fine: the cluster anchors to the bottom so it
   never hides behind it. */
/* A stage-hero fills the viewport behind the fixed bar, so the surface drops
   its bar padding — whether the hero is a direct child or wrapped (e.g. scent's
   .page token wrapper). */
.surface:has(.stage-hero) { padding-top: 0; }
/* Bottom-anchored, left-aligned. Explicit height: 100dvh (not just
   min-height) so the containing block is definite for the parked rain
   video iframe's inset: 0 to resolve against — NOTE the parked markup in
   git history targets the old .home-stage class; re-target to .stage-hero
   on restore. Overrides .page-hero and .has-tabbar on every breakpoint. */
.page-hero.stage-hero,
.has-tabbar .page-hero.stage-hero {
  /* CENTRED — the concierge gate grammar (concierge.css .gate-hero): the 3D
     mark rides the top, the words float in the free air between mark and
     CTAs (auto margins), the CTAs hold the floor. One look on both origins. */
  display: flex;
  flex-direction: column;
  align-items: center;
  /* The bottom pad is the concierge floor (--btn-floor) so the CTA stack
     lands at the same screen height as the gate's buttons. */
  padding: 24px clamp(24px, 5vw, 48px) var(--btn-floor, 50px);
  height: 100dvh;
  min-height: 100dvh;
  /* Rain video parked: the stage holds solid black —
     pinned literal, not --g, so it stays dark in both themes and the
     inverse EXPLORE chip keeps its backdrop. Restore the video from git. */
  background: #000;
}
.stage-hero .page-hero-center {
  /* The base .page-hero-center is already a centred flex column (gap 24).
     Fill the air below the mark; auto margins on the eyebrow and the CTA row
     centre the words vertically, concierge-style. */
  flex: 1 1 auto;
  min-height: 0;
  width: 100%;
  max-width: 720px;
  /* Lift the copy cluster above the 3D mark layer below. */
  position: relative;
  z-index: 2;
}
.stage-hero .hero-eyebrow { margin-top: auto; }
.stage-hero .btn-row { margin-top: auto; }
/* The live 3D mark, vertically centred in the gap between the menu bar and
   the bottom copy. It's the upper grid row's item, centred there with
   align-self, so it self-adjusts as the copy grows or shrinks. Transparent
   canvas, so the black ground shows through behind the metallic mark.
   Auto-mounts via weather-mark-3d.js on #weatherMark3d. Height is the size
   knob (the mark scales with it), nudge to taste. */
.stage-hero-3d {
  /* App-wide standard: the mark hugs ~8px under the bar exactly as the
     concierge hero does. The stage sits full-bleed behind the fixed 64px bar,
     so clear it explicitly (hero pad-top 24 + 48 = 72 = bar 64 + 8 gap).
     Height is the ONE size knob — the --wx3d-h token (tokens.css). */
  flex: none;
  width: 100%;
  height: var(--wx3d-h, min(38vh, 320px));
  margin-top: 48px;
  z-index: 0;
  pointer-events: none;
}
/* The stage eyebrow is the page word, run at h1 scale. */
.stage-hero .hero-eyebrow { font-size: var(--t-h1); }
/* The stage lead carries hand-set line breaks, so drop the shared 42ch
   measure and let the 720px cluster bound it. Centred with the cluster. */
.stage-hero .hero-lead { max-width: none; text-align: center; }

/* --- The centred hero (William, 2026-07-29) — ROLLING OUT ------------------
   The one hero spec, every surface + the concierge gate:
     · the H1 sits at the EXACT vertical centre of the first screen
     · the 3D mark hugs directly ABOVE the H1
     · the body copy, then the curl, sit directly BELOW the H1
     · the concierge CTA is ALWAYS the bottom button, holding the floor
   Zone rows 1fr/auto/1fr put the H1 at true centre — no magic offsets (the
   page-grid law). Proven on /services first (the reference William eyeballs),
   then this folds into .stage-hero and rolls to every hero + the concierge
   gate, and the law is recorded. Markup: the H1 is a DIRECT child of the
   header (so it can own the centre row); everything below it stays in
   .page-hero-center; the mark is the stage-hero-3d as before.
     <header class="page-hero stage-hero stage-hero--center">
       <div class="stage-hero-3d" id="weatherMark3d"></div>   (row 1, hugs above)
       <h1 class="hero-eyebrow">TITLE</h1>                     (row 2, dead centre)
       <div class="page-hero-center"> byline · lead · curl · .btn-row </div>
     </header> */
.page-hero.stage-hero--center {
  display: grid;
  grid-template-rows: 1fr auto 1fr;
  justify-items: center;
  align-items: stretch;
}
.stage-hero--center .stage-hero-3d {
  grid-row: 1;
  align-self: end;   /* the mark hangs at the foot of the top zone — right above the H1 */
  margin-top: 0;     /* drop the bar-hug offset; the zone grid places it now */
  /* A touch shorter than the top-anchored stage mark so the cloud reads close
     to the title, not floating high. Tune via --wx3d-h. */
  height: var(--wx3d-h, min(30vh, 260px));
}
/* The title sits at the EXACT vertical centre — whatever its class (services'
   hero-eyebrow, scent's page-hero-word, home's hero-lead). It must be a DIRECT
   child of the header so it can own the centre row. Every product hero shares
   this, so every H1 lands on the same line and every cloud above it too. */
.page-hero.stage-hero--center > h1,
.page-hero.stage-hero--center > .hero-lockup {
  grid-row: 2;
  margin: 0;         /* clear the stage's margin-top:auto */
  align-self: center;
}
/* Stage-hero titles are the SAME title as .page-hero-word — one size, one
   kerning, one weight — so /services, /studio and /network never drift
   (William, 2026-07-29). Higher specificity clears any page-local hero-eyebrow.
   :not(.hero-lead) — home's H1 IS the studio description (.hero-lead), not a
   path title, so it keeps its body size (William, 2026-07-30). */
.page-hero.stage-hero--center h1:not(.hero-lead) {
  font-size: var(--t-hero);
  font-weight: 500;
  letter-spacing: var(--track-title);
  text-transform: lowercase;
}
/* A title block that carries a line ABOVE the H1 (home's eyebrow, scent's
   theme) rides at the centre as ONE lockup, so the H1 still lands on the
   shared centre line with its label hugging just above it. */
.page-hero.stage-hero--center > .hero-lockup {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
}
/* .page-hero prefix wins over any page-local .stage-hero .page-hero-center
   override (the services page still carries the old stage layout locally). */
.page-hero.stage-hero--center .page-hero-center {
  grid-row: 3;         /* everything under the H1 hangs in the bottom zone */
  align-self: stretch; /* FILL the bottom zone so the button row can floor at the base */
  justify-content: flex-start; /* body copy + curl hug the top, under the H1 */
  width: 100%;
  margin-top: 24px;    /* the cluster gap — body copy sits just under the H1 */
  padding-top: 0;      /* drop the old stage top-pad so the copy hugs the H1 */
}
/* The concierge CTA row holds the floor, so the curl (a sibling above it in
   .page-hero-center) stays directly under the body copy. */
.stage-hero--center .page-hero-center .btn-row { margin-top: auto; }

/* Base heroes (static mark) share the ONE vertical rhythm: mark · h1 · credit ·
   CURL · body · buttons (William, 2026-07-30). The DOM keeps the curl before the
   concierge (the census law); CSS order lifts it above the body so every hero,
   3D or static, reads the same top to bottom. */
.page-hero:not(.stage-hero) .page-hero-center > .btn--curl { order: 1; }
.page-hero:not(.stage-hero) .page-hero-center > .page-hero-statement { order: 2; }
.page-hero:not(.stage-hero) .page-hero-center > .btn-row { order: 3; }

/* The next screen — everything that is not the hero starts below the fold */
.page-next {
  min-height: calc(100vh - 64px);
  min-height: calc(100svh - 64px);
  padding: 48px 24px 72px;
  scroll-margin-top: 64px;
}

/* ===================================================================
   Image placeholder — the .ph block (endurance still carries a local copy
   pending the drift sweep): a quiet
   dashed frame on the elevated ground holding the slot until the real
   photo lands. Aspect via ::before — 62% base, .wide 48%, .square 100%.
   =================================================================== */
.ph {
  position: relative;
  display: block;
  width: 100%;
  max-width: var(--read-w, 430px);   /* section image/diagram — the shared reading measure */
  border: 1px dashed rgba(241, 241, 241, 0.28);
  border-radius: var(--r);
  background:
    linear-gradient(135deg, rgba(241, 241, 241,0.03) 0%, transparent 48%),
    var(--g-elev);
  overflow: hidden;
  min-height: 200px;
}
.ph::before {
  content: "";
  display: block;
  padding-top: 62%;
}
.ph.wide::before { padding-top: 48%; }
.ph.square::before { padding-top: 100%; }

/* ===================================================================
   The poster duo — rs-duo ("the classic grid"): ONE full screen, two equal
   columns on
   the house frame + rail — copy one side, media the other (.ph slot,
   rs-globe, img, canvas). Successive duos alternate sides with
   .rs-duo--flip (media leads, copy rides right). Mobile stacks to one
   column, COPY FIRST regardless of flip — the words lead, the image
   follows. The copy column carries the 20px stack rhythm and the body
   measure; pages dress faces (serif titles etc.), never layout.
   Markup:
     <section class="rs-duo [rs-duo--flip]">
       <div class="rs-duo-copy"> eyebrow · h2 · body lines </div>
       <div class="rs-duo-media"> .ph | .rs-globe | <img> </div>
     </section>
   =================================================================== */
.rs-duo {
  min-height: calc(100vh - 64px);
  min-height: calc(100svh - 64px);
  width: 100%;
  max-width: var(--frame);
  margin: 0 auto;
  padding: 24px clamp(24px, 5vw, 48px);
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  align-items: center;
  align-content: center;
  column-gap: clamp(24px, 5vw, 64px);
  row-gap: 20px;
  scroll-margin-top: 64px;
}
.rs-duo-copy {
  display: grid;
  gap: 20px;
  align-content: center;
}
.rs-duo-copy p {
  margin: 0;
  font-size: var(--t-body);
  line-height: 1.7;
  color: var(--ink-2);
}
.rs-duo-media { min-width: 0; }
/* The flip — the alternating grid's other stride. */
.rs-duo--flip .rs-duo-copy { order: 2; }
.rs-duo--flip .rs-duo-media { order: 1; }
/* The top rail — copy and media share a top edge instead of centring:
   the readout's first line meets the globe's crown. */
.rs-duo--top { align-items: start; }
@media (max-width: 900px) {
  .rs-duo { grid-template-columns: 1fr; }
  .rs-duo--flip .rs-duo-copy { order: 1; }
  .rs-duo--flip .rs-duo-media { order: 2; }
}
/* Location brick — the undisclosed-locations globe section (.rs-duo--flip--top).
   The title types out (mountClimb); the 3D snow-star + outline globe auto-mount
   via the section kit (star-3d.js, globe.js). Self-contained so the brick drops
   into any page with no local style block. */
.rs-duo h2.globe-title {
  margin: 0;
  font-family: "Fira Code", ui-monospace, monospace;
  font-size: var(--t-head);
  font-weight: 500;
  letter-spacing: var(--track-label);
  line-height: 1.6;
  text-transform: lowercase;
  color: var(--ink);
}
.globe-title-line { display: block; }
.loc-star-3d { width: 36px; height: 36px; pointer-events: none; }
.senses-globe { justify-self: center; width: min(100%, 76vh); aspect-ratio: 1; }
@media (min-width: 901px) { .rs-duo--top .senses-globe { margin-top: clamp(48px, 8vh, 96px); } }
.senses-globe canvas {
  outline: none;
  /* Vignette masks the map canvas only, so the pins and wires above stay crisp. */
  -webkit-mask-image: radial-gradient(circle at 50% 50%, #000 66%, transparent 100%);
          mask-image: radial-gradient(circle at 50% 50%, #000 66%, transparent 100%);
}

/* Static location brick — the native app (WKWebView) can't hold live map/3D
   WebGL contexts (maplibre won't sustain one), so the same section renders
   STATIC: a flat snow-star mark beside the title + a full-bleed SVG render of
   the Costa Rica globe (tools/render-globe.mjs). Shared here so home and the
   retreat page drop the same static brick in with no local style block. Same
   build as the site's cards — invisible 1px ring, overflow-clipped — with an
   inner vignette (::after) fading the edges into the page ground the way the
   live globe's mask did. (Home lays its own static hard-rain field over the map.) */
.loc-lockup { display: flex; align-items: center; gap: clamp(14px, 2.2vw, 24px); }
.loc-lockup .loc-mark { width: 48px; height: 48px; flex: 0 0 auto; }
.loc-lockup .globe-title { margin: 0; font-size: clamp(13px, 4.1vw, 16px); }
.loc-lockup .globe-title-line { white-space: nowrap; }
.globe-card {
  position: relative;
  width: 100%;
  /* Section images/diagrams span the --read-w reading measure, so a map/diagram
     lines up with the justified body column (NOT the narrower --card-w trading
     card). The one section-media width, app-wide. */
  max-width: var(--read-w, 430px);
  aspect-ratio: 1;
  margin-inline: auto;
  overflow: hidden;
  border: 1px solid transparent;   /* invisible stroke — no visible line */
  background: #030207;
}
.globe-card img { display: block; width: 100%; height: 100%; object-fit: cover; }
.globe-card::after {                 /* vignette inside, around the map */
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(circle at 50% 50%, transparent 52%, var(--g) 100%);
  pointer-events: none;
}

/* ===================================================================
   Weather CTA buttons — a reusable component
   ONE construction carrying a weather MARK, in two styles: the primary
   (--solid) wears the stroke in that glyph's OWN weather colours at rest
   and presses into the inverse (ink fill, ground text) — its third state;
   the ghost is a hairline that lights the same colours on hover. One
   colour → a solid stroke; more than one → a gradient across them (SCENT
   stars carry all six, so they paint the full spectrum — the home
   "experiences" button). The stroke is read straight off the mark
   (data-wx-mark) via :has(), so a button is just the mark you drop in —
   no extra colour class to keep in sync.

   Markup (use anywhere):
     <div class="btn-row">
       <a class="btn btn--ghost" href="…">
         <img class="btn__mark" data-wx-mark="brand" src="…" alt="" aria-hidden="true">
         <span class="btn__words"><span class="btn__lead">Join rainyseason.cr</span></span>
       </a>
       … a second button …
       <button class="btn btn--curl" type="button">…curl…</button>  (optional copy bar)
     </div>
   Weather → token: Sun --sun · Thunder --spark · Snow --gold · Hard rain
   --accent · Rain --storm · Cloud --lit. Wire the curl clipboard per page. */

/* The row — the concierge gate-step geometry, every screen size: ONE column
   of full-width rows capped at the concierge button width (--btn-w-full),
   --btn-gap apart. Buttons centre their label + mark as one group; the curl
   bar keeps its own left-code / right-icon layout. */
.btn-row {
  display: grid;
  grid-template-columns: 1fr;
  align-items: stretch;
  gap: var(--btn-gap, 12px);
  width: min(100%, var(--btn-w-full, 420px));
}
.btn-row .btn { width: 100%; }
.btn-row .btn:not(.btn--curl) { justify-content: center; }
.btn-row .btn:not(.btn--curl) .btn__words { flex: 0 1 auto; }
/* The copy bar now matches the button's FULL width (William, 2026-07-30): it
   sits directly under the one CTA at the same width. Height is unchanged — it
   inherits width:100% from .btn-row .btn; only the hug/centre is dropped. */
.btn-row .btn--curl { grid-column: 1 / -1; }

/* The cluster goes full-width on phones — it's a centred grid item that
   otherwise shrink-wraps to its widest child (a 92vw 3D mark, say). */
@media (max-width: 760px) {
  .page-hero-center { width: 100%; }
}

/* .btn and every variant live in packages/shell/button.css — one canonical
   source, shared verbatim with the concierge (tools/sync-shared.sh). */

/* Narrow screens — labels wrap rather than overflow. */
@media (max-width: 720px) {
  .btn .btn__lead { white-space: normal; }
}

/* ---- Globe pins — brand lockups riding the outline globe ----
   Square-edge pills carrying the menu-bar lockup grammar (mark + wordmark,
   filled by the declarative data-wx-mark + data-brand-name system). globe.js
   lifts each .rs-globe-pin into a MapLibre marker at its data-lng/lat: the
   MARKER owns geographic placement, while the inner .pin-body is the visible
   pill AND the drag handle — so a community can be flung and spring home
   without fighting MapLibre's transform (pin-wires.js). Each community
   carries a stroke in its own weather colour, and a 1pt wire runs from its
   nearest corner to the heart of Guanacaste. Hidden until mounted. */
.rs-globe-pin {
  display: none;
  align-items: center;
  pointer-events: none; /* the marker is inert — only .pin-body grabs */
  white-space: nowrap;
}
.rs-globe-pin.is-mounted { display: inline-flex; }
.pin-body {
  --pin-a: 5%; /* stroke opacity — barely there, so the LOCKUP is all you read */
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 5px 9px;
  background: var(--g); /* ground fill lays the lockup over the coastline ink */
  border: 1px solid var(--line-strong); /* overridden per community below */
  border-radius: 0; /* square edges — never the pill radius */
  white-space: nowrap;
  pointer-events: auto; /* the grab handle */
  cursor: grab;
  touch-action: pan-y; /* let the page scroll on touch */
  will-change: transform;
}
.pin-body.is-dragging { cursor: grabbing; }
/* Per-community stroke — EXACTLY the glyph's colours (pin-wires.js echoes the
   same set in the wire), each mixed down to --pin-a so the lockup carries the
   eye and the border stays a quiet accent. Single-weather glyphs are solid;
   multi-weather glyphs get a gradient of their colours. Square pills, so
   border-image sits flush. Greater = Sun. Coffee = Cloud + Rain. triathlon =
   Sun · Hard Rain · Thunder. SCENT carries all six. */
.rs-globe-pin:has([data-wx-mark="greater"]) .pin-body {
  border-color: color-mix(in srgb, var(--sun) var(--pin-a), transparent);
}
.rs-globe-pin:has([data-wx-mark="coffee"]) .pin-body {
  border-image: linear-gradient(90deg,
    color-mix(in srgb, var(--lit) var(--pin-a), transparent),
    color-mix(in srgb, var(--storm) var(--pin-a), transparent)) 1;
}
.rs-globe-pin:has([data-wx-mark="triathlon"]) .pin-body {
  border-image: linear-gradient(90deg,
    color-mix(in srgb, var(--accent) var(--pin-a), transparent),
    color-mix(in srgb, var(--sun) var(--pin-a), transparent),
    color-mix(in srgb, var(--spark) var(--pin-a), transparent)) 1;
}
.rs-globe-pin:has([data-wx-mark="scent"]) .pin-body {
  border-image: linear-gradient(90deg,
    color-mix(in srgb, var(--sun) var(--pin-a), transparent),
    color-mix(in srgb, var(--gold) var(--pin-a), transparent),
    color-mix(in srgb, var(--spark) var(--pin-a), transparent),
    color-mix(in srgb, var(--accent) var(--pin-a), transparent),
    color-mix(in srgb, var(--storm) var(--pin-a), transparent),
    color-mix(in srgb, var(--lit) var(--pin-a), transparent)) 1;
}
/* rainyseason.cr — the master mark (Cloud · Thunder · Hard Rain). */
.rs-globe-pin:has([data-wx-mark="brand"]) .pin-body {
  border-image: linear-gradient(90deg,
    color-mix(in srgb, var(--lit) var(--pin-a), transparent),
    color-mix(in srgb, var(--spark) var(--pin-a), transparent),
    color-mix(in srgb, var(--accent) var(--pin-a), transparent)) 1;
}
/* Light mode — the label frames drop the faint weather tint for a crisp VANTA
   BLACK stroke, riding the light-mode ink law (every stroke goes full black,
   hierarchy from size + weight). Wins over the per-community rules above by
   specificity; border-image reset so the solid colour shows. */
html.is-light .rs-globe-pin .pin-body {
  border-image: none;
  border-color: #000;
}
.rs-globe-pin .pin-mark { display: block; width: 18px; height: 18px; flex: 0 0 auto; }
.rs-globe-pin .brand-name { font-size: var(--t-caption); line-height: 1; color: var(--ink-2); }

/* The heart of Guanacaste — the geographic centre the communities point to.
   A sharp four-point spark-star (not a round dot), lifted by globe.js like any
   other pin so it rides the same coordinate space. Its concave points read as
   a spark/twinkle; a soft glow lifts it off the coastline. */
.rs-globe-heart { pointer-events: none; }
.rs-globe-heart .heart-dot {
  width: 13px; height: 13px;
  background: var(--ink);
  /* four-point sparkle — long thin points, pinched waist between them */
  clip-path: polygon(50% 0%, 57% 43%, 100% 50%, 57% 57%, 50% 100%, 43% 57%, 0% 50%, 43% 43%);
  filter: drop-shadow(0 0 3px color-mix(in srgb, var(--ink) 55%, transparent));
}

/* The wires — 1pt lines from each community's nearest corner to the heart,
   drawn into this overlay by pin-wires.js, coloured per community. */
.pin-wires { position: absolute; inset: 0; z-index: 1; overflow: visible; pointer-events: none; }

/* Snap-home — a flung pin springs back on the curl bar's EXACT speed profile:
   turbo out of the corner, a steady cruise, then a hard brake. The --sx/--sy
   the script parks on the pin are the release offset; the keyframes scale that
   vector home and the per-segment easings mirror @keyframes btnBeam
   one-for-one (accelerate · linear cruise · decelerate). */
@keyframes pinSnap {
  0%   { transform: translate(var(--sx, 0), var(--sy, 0)); animation-timing-function: cubic-bezier(0.5, 0, 1, 0.55); }
  22%  { transform: translate(calc(var(--sx, 0) * 0.928), calc(var(--sy, 0) * 0.928)); animation-timing-function: linear; }
  78%  { transform: translate(calc(var(--sx, 0) * 0.072), calc(var(--sy, 0) * 0.072)); animation-timing-function: cubic-bezier(0, 0.45, 0.5, 1); }
  100% { transform: translate(0, 0); }
}
/* MapLibre's own stylesheet hides overflow on the map box, which crops pills
   near the container edge. The globes are borderless ink on the bare ground,
   so letting them overflow is invisible — and the FULL lockups always render. */
.rs-globe.maplibregl-map { overflow: visible; }

/* --- Site footer (bottom chrome; single source) ---
   The studio's directory beneath the page — ONE source: home mounts it, the
   components page shows it live. A full-bleed ground (no boxes), columns of
   links. Column titles speak the house uppercase; the link rows stay
   sentence case one notch under at weight 400 — the quiet "h4" the
   directory asks for. */
.site-footer {
  width: 100%;
  background: var(--g);
  /* Horizontal pad on the shared 22px rail, so the
     directory's first column lands with the menu glyph, the offer cards and the
     knowledge teaser above it — not in a narrower centred block. */
  padding: clamp(48px, 8vh, 88px) var(--rail) clamp(16px, 3vh, 24px);
}
.site-footer-inner {
  width: 100%;
  max-width: none; /* fill the frame so the first column sits on the rail */
  margin: 0 auto;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: clamp(32px, 5vw, 56px) clamp(24px, 4vw, 40px);
}
.foot-col {
  display: flex;
  flex-direction: column;
  gap: 12px;
  text-align: left; /* every column, first row included, stays flush-left */
}
/* The column title — the house uppercase, tracked like every caps label;
   row weight (500), only the brighter ink lifts it above the list. */
.foot-col h3 { color: var(--ink); font-size: var(--t-body); font-weight: 500; text-transform: lowercase; letter-spacing: var(--track-head); }
/* Every column title is a link to that section's main page.
   The anchor inherits the title's ink; hover underlines it so the
   heading reads as a destination without disturbing the resting hierarchy. */
.foot-col h3 a { color: inherit; text-decoration: none; transition: color 140ms ease; }
.foot-col h3 a:hover,
.foot-col h3 a:focus-visible { text-decoration: underline; text-underline-offset: 3px; outline: none; }
.foot-col ul {
  list-style: none;
  margin: 0;
  /* Nudge the rows in under the title, so the directory reads as nested
     paths: title flush left, rows a touch indented. */
  padding: 0 0 0 12px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
/* The directory row — the house caps one size under the title:
   --t-small 400 over the title's --t-body 500, so rank rides
   on size, weight and ink together. Caps want positive tracking. */
.foot-link {
  display: inline-block;
  font-size: var(--t-small);
  font-weight: 400;
  text-transform: lowercase;
  letter-spacing: var(--track-title);
  line-height: 1.1;
  color: var(--ink-3);
  text-decoration: none;
  transition: color 140ms ease;
}
.foot-link:hover,
.foot-link:focus-visible { color: var(--ink); text-decoration: underline; text-underline-offset: 3px; }
/* URL-path directory — every column title and row
   wears a quiet leading slash, so the footer reads as the site's route map.
   The body is already Fira Code (monospace), so the rows look like paths;
   the slash is a dim path marker in --ink-4, the label keeps the ink. The
   baseline credit line is prose (.site-footer-baseline), so it opts out. */
.foot-col h3::before,
.foot-col .foot-link::before {
  content: "/";
  /* Forward slashes read white in the footer route map, matching the site-wide
     .rs-path-slash rule, not the blue path mark (William, 2026-08-02). */
  color: var(--ink);
}
/* The origin column title IS the full URL (https://rainyseason.cr), not a route
   under the site — so it drops the leading path slash. Its rows are real paths
   and keep theirs. */
.foot-col--origin h3::before { content: none; }
/* Footer label (component /004) — a small timing note beside a
   directory row: “Early 2027” after “The Studio”. A quiet pill:
   the eyebrow voice on a faint hard-rain wash, ringed by a
   lighter hairline of the same accent. All three tones derive from the
   locked tokens via color-mix — the ink mix lifts the accent toward white
   on dark ground and toward ink on light, so one rule serves both modes.
   Markup stays sentence case; the transform is the style's. */
.foot-tag {
  --tag-c: var(--ink); /* monochrome — no accent colour on any pill */
  display: inline-block;
  vertical-align: middle;
  position: relative;
  top: -2px; /* rides a touch above the row's centre */
  margin-left: 8px;
  padding: 2px 5px;
  border: 1px solid color-mix(in srgb, var(--tag-c) 26%, transparent);
  border-radius: 0; /* straight edges — no rounded corners */
  font-size: var(--t-tag); /* below the ramp's 10px floor */
  font-weight: 400;
  letter-spacing: var(--track-head);
  text-transform: lowercase;
  line-height: 1;
  color: color-mix(in srgb, var(--tag-c) var(--tag-mix, 45%), var(--ink));
  background: color-mix(in srgb, var(--tag-c) 12%, transparent);
  white-space: nowrap;
}
/* Pills are monochrome by default. Hibiscus is the one live accent — it marks an
   open, time-sensitive row (Scent, "Applications open"); volt stays a no-colour
   alias so older markup still resolves to the base pill. */
.foot-tag--volt { --tag-c: var(--ink); }
.foot-tag--hibiscus { --tag-c: var(--hibiscus); }
/* Not-yet-live directory entries render as inert text, not links, so nothing
   in the footer 404s before it ships. */
.foot-link--soon { opacity: 1; cursor: default; }
.foot-title--soon { cursor: default; }
/* Collectibles is all roadmap — nothing live yet. Its inert rows carry the
   full label ink (off-white on dark, black on light), not the half-dim soon
   grey, so the labels read plainly without any accent colour. */
.foot-col--roadmap .foot-link--soon { color: var(--ink); opacity: 1; }

/* PRIVATE BETA rows — a directory entry that is held back, dressed to read as
   deliberate exclusivity rather than a dead end. It looks and hovers like a
   live link (keeps the route slash and the ink lift on hover), carries a small
   padlock, and is named by a PRIVATE BETA pill. The click is refused in JS
   (nav.js bindBetaRejects) with the shake below — it never navigates, never
   404s. */
.foot-link--beta,
.foot-link--reject { color: var(--ink-3); cursor: pointer; }
.foot-link--beta:hover,
.foot-link--beta:focus-visible,
.foot-link--reject:hover,
.foot-link--reject:focus-visible { color: var(--ink); text-decoration: none; outline: none; }
.foot-lock { display: inline-flex; vertical-align: middle; margin-left: 6px; }
.foot-lock-ico { width: 11px; height: 11px; opacity: 0.6; }
.foot-link--beta:hover .foot-lock-ico,
.foot-link--beta:focus-visible .foot-lock-ico { opacity: 0.85; }
.foot-tag--beta { --tag-c: var(--ink); }
/* The refusal: a brief lateral shake, restarted by the JS reflow on each click.
   Motion-reduced visitors get the same rejection without the movement. */
.foot-link--beta.is-rejected,
.foot-link--reject.is-rejected { animation: foot-reject 320ms ease; }
@keyframes foot-reject {
  0%, 100% { transform: translateX(0); }
  20% { transform: translateX(-3px); }
  40% { transform: translateX(3px); }
  60% { transform: translateX(-2px); }
  80% { transform: translateX(2px); }
}
@media (prefers-reduced-motion: reduce) {
  .foot-link--beta.is-rejected,
  .foot-link--reject.is-rejected { animation: none; }
}

/* Offer card title. The title stands alone with no credit line. */
.shc-name--offer {
  margin: 0;
  font-size: var(--t-title);
  font-weight: 500;
  letter-spacing: var(--track-caps);
  text-transform: lowercase; /* card titles run lowercase (William, 2026-07-29) */
  line-height: 1.2;
  color: var(--ink);
  text-wrap: balance; /* two-line names split evenly, never an orphan word */
}
/* A transparent link can make the full card interactive. It sits below any
   visible card action. */
.seat-holder-card__link {
  position: absolute;
  inset: 0;
  z-index: 3;
}

/* --- Card rail ------------------------------------------------------------
   Every set of cards uses this horizontal rail. A card fills the available
   content width on a phone and stops at --card-w on wider screens. The rail
   owns width, spacing, scrolling, snapping and progress. Pages only provide
   card content. */
.card-rail {
  display: flex;
  align-items: stretch;
  gap: var(--rail-gap);
  width: 100%;
  min-width: 0;
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  scroll-padding-inline: 0;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;
  padding: 2px 0 var(--space-xl);
}
.card-rail--spaced { margin-top: var(--rhythm-lead); }
.card-rail::-webkit-scrollbar { display: none; }
.card-rail > :is(.seat-holder-card, .seat-card) {
  flex: 0 0 var(--rail-basis);
  width: var(--rail-basis);
  max-width: var(--card-w);
  scroll-snap-align: start;
  scroll-snap-stop: always;
}

/* --- Seat-holder card -----------------------------------------------------
   A top image, an optional weather badge, a text body and an optional action.
   The card keeps one minimum height but grows with its content. */
.seat-holder-card {
  position: relative;   /* host for .seat-holder-card__link */
  display: flex;
  flex-direction: column;
  overflow: hidden;
  min-width: 0;
  min-height: var(--card-min-h);
  border: 1px solid var(--line);
  background: transparent;
  transition: border-color 180ms ease;
  /* The hover-lit stroke colour. App-wide default is the brand accent; a
     context can override the token to light each card in its own colour —
     the scent senses set --card-lit to the sense colour (William, 2026-07-31). */
  --card-lit: var(--accent);
}
/* The border brightens on hover or when a link inside receives focus. */
.seat-holder-card:hover,
.seat-holder-card:focus-within { border-color: var(--card-lit); }
.shc-photo-wrap { position: relative; flex: none; }
/* Empty art plate — transparent until real art lands (no grey fill), with the
   shared AWAITING PHOTOGRAPHY marker below so a missing image reads as
   intentional, never broken. (William, 2026-08-02) */
.shc-photo {
  width: 100%;
  aspect-ratio: var(--card-photo);
  background: transparent;
  position: relative;
}
/* AWAITING PHOTOGRAPHY — the empty-image marker. Any plate with no real art shows
   it, centred and quiet; a plate carrying real art (or intentionally blank, like
   the press card) opts out below. --ph plates keep their own marker. */
.shc-photo:not(.shc-photo--ph)::after,
.seat-photo--empty::after {
  content: "AWAITING PHOTOGRAPHY";
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 14px;
  text-align: center;
  font-family: var(--font-code);
  font-size: var(--t-eyebrow);
  font-weight: 500;
  letter-spacing: var(--track-wide);
  text-transform: lowercase;
  color: var(--ink-3);   /* faintest READABLE text tone — ink-4 fails the a11y gate */
  pointer-events: none;
}
.news-card .shc-photo::after,
.shc-photo.shc-photo--3d::after,
.shc-photo.shc-photo--asia::after,
.shc-photo.shc-photo--scenic-drive::after,
.shc-photo.shc-photo--tamara::after { content: none; }   /* carry real art (3D mounts included) or are intentionally blank — two classes so this beats the marker's :not() specificity */
/* The round PFP badge straddles the photo's bottom line — 104px, half in the
   image, half in the body (bottom: -52px), left-aligned. */
.shc-badge {
  position: absolute;
  left: 16px;
  bottom: calc(-1 * var(--pfp-overhang));
  width: var(--pfp-badge);
  height: var(--pfp-badge);
  border-radius: 50%;
  background: var(--g);
  border: var(--pfp-ring) solid var(--g);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
.shc-badge img, .shc-badge svg { width: var(--pfp-glyph); height: var(--pfp-glyph); }
/* padding-top clears the badge's lower half: the overhang + a space step of air. */
.shc-body { flex: 1 0 auto; min-width: 0; display: flex; flex-direction: column; gap: 4px; padding: calc(var(--pfp-overhang) + var(--space-md)) 18px 20px; text-align: left; }
.shc-kind { font-family: "Fira Code", ui-monospace, monospace; font-size: var(--t-eyebrow); font-weight: 500; letter-spacing: var(--track-title); text-transform: lowercase; color: var(--ink-3); }
.shc-namerow { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.shc-name { margin: 0; min-width: 0; overflow-wrap: anywhere; }
/* The card's NFT number, set opposite the title on the .shc-namerow (/nnn-333):
   same slug grammar and same ink as the title, so the whole path reads ONE
   colour. flex:none + nowrap holds the number at the right edge while a long
   title wraps beneath it. */
.shc-nft { margin: 0; flex: none; white-space: nowrap; font-family: var(--font-code); font-size: var(--t-label); font-weight: 500; letter-spacing: 0; color: var(--ink); }
/* The catalogue number shows ONLY inside the collection (the /collection/<slug>
   card page). Everywhere a card is embedded the number stays in the markup +
   data-card-no for tokenising, but is not shown (William, 2026-08-03). */
.shc-nft { display: none; }
.card-solo .shc-nft { display: revert; }
.shc-line { margin: 12px 0 16px; font-size: var(--t-lede); line-height: 1.55; color: var(--ink-2); overflow-wrap: anywhere; }
/* A body action stays at full button height at the bottom of the card and sits
   above the full-card link. */
.shc-body .btn { display: flex; flex: 0 0 auto; width: 100%; min-height: var(--btn-h); justify-content: center; margin-top: auto; position: relative; z-index: 4; }

/* Plain cards use the same frame and rail contract when their content does not
   need the image, badge and body slots. */
.seat-holder-card--plain { gap: var(--card-gap); padding: var(--card-pad); }

/* Rail focus changes the image and stroke only. Text and actions stay readable
   on every card, including cards outside the centre position. The dim filter
   lands on the photo plate and on non-LCD badges — NEVER on an all-segments
   LCD badge. That badge's light is segment fills alone (the carousel gate,
   below at the Content PFP block), so its ALL OFF face reads the SAME grey on
   every card instead of a darker grey off-focus. */
.card-rail.is-overflowing > .seat-holder-card { border-color: var(--line); }
.card-rail.is-overflowing > .seat-holder-card.is-focus { border-color: var(--card-lit); }
.card-rail.is-overflowing > .seat-card.is-focus .seat-front { border-color: var(--accent); }
.card-rail.is-overflowing > .seat-holder-card .shc-photo-wrap > .shc-photo,
.card-rail.is-overflowing > .seat-holder-card--plain > .shc-media,
.card-rail.is-overflowing > .seat-card .seat-photo-wrap .seat-photo,
.card-rail.is-overflowing > .seat-holder-card .shc-photo-wrap > .shc-badge:not(:has([data-wx-mark="allsegments"], [src*="all-segments"])),
.card-rail.is-overflowing > .seat-card .seat-photo-wrap .seat-badge:not(:has([data-wx-mark="allsegments"], [src*="all-segments"])) {
  filter: grayscale(1) brightness(0.42);
  transition: filter var(--dur-base) var(--ease);
}
.card-rail.is-overflowing > .seat-holder-card.is-focus .shc-photo-wrap > .shc-photo,
.card-rail.is-overflowing > .seat-holder-card--plain.is-focus > .shc-media,
.card-rail.is-overflowing > .seat-card.is-focus .seat-photo-wrap .seat-photo,
.card-rail.is-overflowing > .seat-holder-card.is-focus .shc-photo-wrap > .shc-badge,
.card-rail.is-overflowing > .seat-card.is-focus .seat-photo-wrap .seat-badge { filter: none; }
/* Off-focus, EVERYTHING on a card powers down to the one OFF tone: the whole
   ink ladder, every weather colour, every hairline and the button ring resolve
   to --wx-off — so type, slashes, strokes and lit glyphs all read as unlit
   segments. Only the in-focus card runs white ink and colour. --g stays: the
   badge circle is chassis, not a lamp. */
.card-rail.is-overflowing > :is(.seat-holder-card, .seat-card):not(.is-focus) {
  --ink: var(--wx-off);
  --ink-2: var(--wx-off);
  --ink-3: var(--wx-off);
  --ink-4: var(--wx-off);
  --white: var(--wx-off);
  --line: var(--wx-off);
  --line-2: var(--wx-off);
  --line-strong: var(--wx-off);
  --btn-stroke: var(--wx-off);
  --accent: var(--wx-off);
  --lit: var(--wx-off);
  --sun: var(--wx-off);
  --rain: var(--wx-off);
  --storm: var(--wx-off);
  --gold: var(--wx-off);
  --spark: var(--wx-off);
  --thunder: var(--wx-off);
  --card-lit: var(--wx-off);
}
@media (prefers-reduced-motion: reduce) {
  .card-rail.is-overflowing > .seat-holder-card .shc-photo-wrap > .shc-photo,
  .card-rail.is-overflowing > .seat-holder-card--plain > .shc-media,
  .card-rail.is-overflowing > .seat-card .seat-photo-wrap .seat-photo,
  .card-rail.is-overflowing > .seat-holder-card .shc-photo-wrap > .shc-badge,
  .card-rail.is-overflowing > .seat-card .seat-photo-wrap .seat-badge { transition: none; }
}

/* ── Identity card — the unified .seat-holder-card as a person / seat card: the
   portrait fills the plate, a compact caption strip carries name → role → seat.
   ONE locked grammar (William, 2026-07-30); pages apply the classes, no local fork.
   Stand-alone (outside the swipe rail) it caps at --card-w like every card. ── */
.seat-holder-card--id { width: 100%; max-width: var(--card-w, 360px); }
/* Person/identity card: the SHARED 16:10 photo plate (like every other card),
   then a body that grows to fill. The old height:100% / aspect-ratio:auto
   collapsed a background-image photo to 0px — a % height with no definite parent
   height to resolve against — so the portrait never showed. */
.seat-holder-card--id .shc-body { gap: var(--space-xs); padding: var(--space-xl) 20px 22px; }
.seat-holder-card--id .shc-name {
  margin: 0 0 3px;
  font-size: var(--t-title);
  font-weight: 500;
  letter-spacing: var(--track-tight);
  line-height: 1.1;
  text-transform: none;
  color: var(--ink);
}
/* Role + seat share one caption scale — colour sets the tiers; the seat number
   wears the DSEG LCD face. */
.shc-role,
.shc-seat {
  margin: 0;
  font-size: var(--t-label);
  letter-spacing: var(--track-label);
  text-transform: lowercase;
  line-height: 1.4;
}
.shc-role { color: var(--ink-2); }
.shc-seat { color: var(--ink-3); }
.shc-seat .shc-num { color: var(--ink-2); }
.shc-num,
.shc-num * { font-family: "DSEG14 Modern Mini", "Fira Code", ui-monospace, monospace; }

/* --- News card system (shared) --------------------------------------------
   Press and network cards use the shared photo, badge and body grammar. An
   action may sit inside .shc-body or after the card in .news-slot. */
/* The body a news-slot sits in runs full width so the slot reaches the card
   width (home scopes this to #stories; shared here for the network end pages). */
.rs-split-body:has(> .news-slot) { width: 100%; padding: 0 2px; }
.news-slot { width: 100%; max-width: calc(var(--card-w, 360px) + 4px); padding: 2px; display: flex; flex-direction: column; gap: 16px; }
.news-card .shc-photo { background: transparent; }
.news-card .shc-body { gap: 10px; padding-bottom: 12px; }
.news-headline {
  margin: 0;
  font-family: "Fira Code", ui-monospace, monospace;
  font-size: var(--t-lede);
  font-weight: 500; line-height: 1.55; letter-spacing: var(--track-tight); text-transform: none;
  color: var(--ink);
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 4; overflow: hidden;
}
/* A news action fills its host. The shared .shc-body rule floors an in-card
   action; .news-slot stretches an action placed after the card. */
.news-cta { align-self: stretch; justify-content: center; }

/* ── THE plug-and-play type classes (William, 2026-07-30) ──────────────────
   One headline, one card title, one paragraph — so every section + every card
   (the printed card game runs to one spec) draws from the same source. Titles
   ride the file-path/terminal style; the leading slash goes accent. ── */
.h-hero {
  margin: 0;
  font-family: var(--font-code, "Fira Code", ui-monospace, monospace);
  font-size: var(--t-hero);
  font-weight: 500;
  letter-spacing: var(--track-title);
  text-transform: lowercase;
  line-height: var(--lh-display);
  color: var(--ink);
}
.h-card {
  margin: 0;
  font-family: var(--font-code, "Fira Code", ui-monospace, monospace);
  font-size: var(--t-card);
  font-weight: 500;
  letter-spacing: 0;
  text-transform: lowercase;
  line-height: var(--lh-title);
  color: var(--ink);
}
.prose { font-size: var(--t-body); line-height: var(--lh-prose); color: var(--ink-2); }

/* --- Plan card + product stage --------------------------------------------
   The product purchase card (.plan) and its 3D stage (.seat-stage), shared
   by the four product pages under /studio/seats — one grammar, no drift.
   Desktop: stage left, plan right (.product-duo), the plan sticky under
   the menu bar. */
.product-duo {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-xl);
}
.seat-stage {
  width: 100%;
  height: clamp(240px, 46vh, 400px);
}
@media (min-width: 720px) {
  .product-duo {
    grid-template-columns: 54% 1fr;
    column-gap: clamp(40px, 5vw, 64px);
    row-gap: var(--space-2xl);
    align-items: start;
  }
  .product-duo > .seat-stage { height: clamp(400px, 62vh, 640px); }
  .product-duo > .plan {
    max-width: none;
    position: sticky;
    top: 72px;
  }
}
/* The plan — one product · on-chain purchase. OPEN content, not a box:
   THE ONE CARD is the app's only bordered container, so the
   plan sits straight on the ground — its structure comes from the hairline
   rows (.plan-scarcity, .day), its action from the weather CTA. */
.plan {
  width: 100%;
  max-width: 480px;
  text-align: left;
  display: grid;
  gap: var(--space-xl);
}
.plan-scarcity {
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px 14px;
  font-size: var(--t-caption);
  letter-spacing: var(--track-caps);
  text-transform: lowercase;
  color: var(--ink-3);
  padding-bottom: var(--space-lg);
  border-bottom: 1px solid var(--line);
}
.plan-scarcity strong {
  color: var(--ink);
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  letter-spacing: var(--track-head);
}
.plan-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px 16px;
}
.plan-name {
  margin: 0;
  font-size: var(--t-caption);
  letter-spacing: var(--track-wide);
  text-transform: lowercase;
  color: var(--ink-3);
  font-weight: 500;
}
.plan-price {
  margin: 0;
  font-size: 28px;
  font-weight: 500;
  letter-spacing: var(--track-micro);
  color: var(--ink);
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.plan-price span {
  font-size: var(--t-small);
  letter-spacing: var(--track-label);
  text-transform: lowercase;
  color: var(--ink-3);
  font-weight: 400;
  margin-left: 6px;
}
.plan-desc {
  margin: 0;
  font-size: var(--t-body);
  line-height: 1.7;
  color: var(--ink-2);
}
.plan-section-label {
  margin: 0;
  font-size: var(--t-eyebrow);
  letter-spacing: var(--track-eyebrow);
  text-transform: lowercase;
  color: var(--ink-3);
}
/* The day — one concrete timeline */
.day {
  margin: 0;
  padding: 0;
  list-style: none;
  border-top: 1px solid var(--line);
}
.day li {
  display: grid;
  grid-template-columns: 6.5rem 1fr;
  gap: var(--space-md);
  align-items: baseline;
  padding: var(--space-lg) 10px;
  border-bottom: 1px solid var(--line);
  font-size: var(--t-lede);
  line-height: 1.45;
}
.day .when {
  font-size: var(--t-caption);
  letter-spacing: var(--track-caps);
  text-transform: lowercase;
  color: var(--ink-3);
  font-weight: 500;
}
.day .what {
  color: var(--ink-2);
}
/* Work sessions — the spine of the day */
.day li.work {
  background: rgba(241, 241, 241, 0.04);
}
.day li.work .when {
  color: var(--ink);
}
.day li.work .what {
  color: var(--ink);
}
.day .scent {
  font-family: "Amarna", "EB Garamond", ui-serif, Georgia, serif;
  letter-spacing: var(--track-wordmark);
  text-transform: none;
  color: var(--ink);
}
.day a {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px solid var(--line-strong);
  transition: border-color 140ms ease;
}
.day a:hover { border-bottom-color: var(--ink-2); }
.day-foot {
  margin: 0;
  font-size: var(--t-caption);
  line-height: 1.5;
  color: var(--ink-3);
  letter-spacing: var(--track-wordmark);
}
/* The plan's action is the house weather CTA (btn ghost) — no local
   button variant. It stretches to the plan measure and centres its label. */
.plan-actions {
  display: grid;
  gap: 10px;
}
.plan-actions .btn {
  width: 100%;
  justify-content: center;
}
.plan-actions .btn .btn__words { justify-content: center; }
.plan-note {
  margin: 0;
  font-size: var(--t-caption);
  line-height: 1.5;
  color: var(--ink-3);
  text-align: center;
  letter-spacing: var(--track-wordmark);
}
.plan-status {
  margin: 0;
  min-height: 1.2em;
  font-size: var(--t-small);
  color: var(--ink-3);
  text-align: center;
}
.plan-status.on {
  color: var(--ink-2);
}

/* --- Product directory list ------------------------------------------------
   The five products with their formats, each row a way in — the studio
   page's Products section and the every-product section on each product
   page share it. */
.res-list {
  margin: 0;
  padding: 0;
  list-style: none;
  border-top: 1px solid var(--line);
}
.res-link {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: baseline;
  column-gap: 16px;
  padding: 18px 10px;
  border-bottom: 1px solid var(--line);
  text-decoration: none;
  transition: background 140ms ease;
}
.res-link:hover,
.res-link:focus-visible {
  background: rgba(241, 241, 241, 0.03);
  outline: none;
}
.res-name {
  margin: 0;
  font-size: var(--t-lede);
  font-weight: 500;
  letter-spacing: var(--track-label);
  text-transform: lowercase;
  color: var(--ink);
  line-height: 1.4;
}
.res-format {
  margin-left: 14px;
  font-size: var(--t-caption);
  font-weight: 400;
  letter-spacing: var(--track-caps);
  text-transform: lowercase;
  color: var(--ink-3);
}
.res-go {
  font-size: var(--t-label);
  color: var(--ink-3);
  line-height: 1;
  transition: color 140ms ease, transform 160ms ease;
}
.res-link:hover .res-go,
.res-link:focus-visible .res-go {
  color: var(--ink-2);
  transform: translateX(3px);
}
@media (prefers-reduced-motion: reduce) {
  .res-go { transition: none; }
}
/* The quiet line under each list */
.list-foot {
  margin: 24px 0 0;
  padding: 0 10px;
  font-size: var(--t-caption);
  letter-spacing: var(--track-head);
  color: var(--ink-3);
}
.list-foot a {
  color: var(--ink-3);
  text-decoration: none;
  border-bottom: 1px solid var(--line-strong);
  transition: color 140ms ease, border-color 140ms ease;
}
.list-foot a:hover {
  color: var(--ink-2);
  border-bottom-color: var(--ink-2);
}

/* Parent-org line — beneath the directory, the quiet colophon. One size under
   the rows (--t-small) and all one light weight (300), link included. */
.foot-bottom {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-top: clamp(32px, 5vh, 48px);
  padding-bottom: clamp(16px, 3vh, 24px);
}
/* The credit column — the ALL-unlit weather mark sits above the byline (the
   hardware powered down, quiet at the foot of the page). */
.foot-credit {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 14px;
}
/* Footer status indicator — a live link to /status. The weather mark powers down
   to the OFF tone with only the SUN lit an operational green (no cloud); three
   caps lines stack beside it. (William, 2026-08-03) */
.foot-status {
  --status-ok: #22c55e;   /* operational / online green — a status semantic, NOT a brand chrome colour */
  display: inline-flex;
  align-items: center;   /* glyph centred against the three-line stack */
  gap: 14px;
  text-decoration: none;
}
.foot-status-mark {
  display: block;
  width: var(--wx-glyph);
  height: var(--wx-glyph);
  flex: 0 0 auto;
}
/* The mark's segments are lit by the shared light layer via data-wx-light="operational"
   on the .foot-status link (full sun green, no cloud) — see the unified light layer. */
.foot-status-lines { display: flex; flex-direction: column; gap: 3px; }
.foot-status-label,
.foot-status-verify,
.foot-status-domain {
  font-family: "Fira Code", ui-monospace, monospace;
  font-size: var(--t-eyebrow);
  letter-spacing: var(--track-caps);
  text-transform: uppercase;   /* stays capitalised through the app-wide lowercase pass */
  line-height: 1.3;
}
.foot-status-label  { font-weight: 500; color: var(--status-ok); }   /* green: operational */
.foot-status-verify { font-weight: 400; color: var(--ink-3); }       /* dim: the caution */
.foot-status-domain { font-weight: 500; color: var(--ink); }         /* white: the verified domain */
.site-footer-baseline {
  margin: 0;
  text-align: left;
  font-size: var(--t-caption);
  font-weight: 400;
  letter-spacing: var(--track-head);
  text-transform: lowercase;
  color: var(--ink-3);
}
/* Footer controls — the language switch (moved down from the bar centre, which
   now holds the live clock) sits beside the light / dark toggle at the foot of
   the directory. */
.foot-actions {
  display: flex;
  align-items: center;
  gap: clamp(10px, 2vw, 18px);
  flex: 0 0 auto;
}
/* In the footer the codes run in flow, not absolutely centred on a bar. */
.lang-switch--foot {
  position: static;
  transform: none;
  transition: none;
  gap: 4px;
}
.lang-switch--foot .lang-opt { min-width: auto; padding: 4px 4px; }
/* Light / dark switch — the eclipse toggle (see the track rules below).
   State + persistence live in surfaces-nav.js so the choice holds across
   every surface; the button is just the click target around the track. */
.theme-toggle {
  appearance: none;
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  cursor: pointer;
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: var(--hit);
  min-height: var(--hit);
}
/* The eclipse toggle — a
   night-sky pill; a sun disc rests left in light mode; toggling dark, the
   disc slides right and turns white while a second sky-toned disc glides
   in to bite it into a crescent moon. Both discs are the track's pseudo
   elements; the whole night sky is one overflow-hidden pill. Sky from OUR
   black scale (#0A0910 == dark --g-elev, fixed in both modes) inside a
   darker-grey stroke; sun #FFA41B, moon #FFF.
   Geometry: 48x26 pill, 18px disc, 3px inset, travel 22, bite offset 8. */
.theme-toggle-track {
  position: relative;
  display: block;
  width: 48px;
  height: 26px;
  border-radius: 100px;
  background-color: #0A0910;
  border: 1px solid #222222;
  overflow: hidden;
}
.theme-toggle-track::before,
.theme-toggle-track::after {
  content: "";
  display: block;
  position: absolute;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  top: 3px;
  left: 3px;
  transition: 0.5s ease;
}
/* The sun disc — and, slid right and whitened, the moon. */
.theme-toggle-track::before { background-color: #FFA41B; }
/* The bite — a sky-toned disc parked one disc-width off stage left. */
.theme-toggle-track::after {
  background-color: #0A0910;
  left: -15px;
  transform: scale(0.00001);
}
/* DARK mode is the resting state here: moon right, crescent bitten.
   travel = 46 − 2·3 − 18 = 22 (padding box 46); bite trails the moon by
   8px, so its glide = park gap (18) + travel (22) − 8 = 32. */
html:not(.is-light) .theme-toggle-track::before {
  background-color: #FFF;
  transform: translateX(22px);
}
html:not(.is-light) .theme-toggle-track::after {
  transform: translateX(32px) scale(1);
}
.theme-toggle:focus-visible { outline: none; }
.theme-toggle:focus-visible .theme-toggle-track {
  outline: 2px solid var(--ink);
  outline-offset: 3px;
}
/* The org link inherits the colophon's size, weight and ink — the whole line
   reads as one flat tone; only hover lifts the link. */
.site-footer-baseline .foot-link {
  font-size: inherit;
  font-weight: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  color: inherit;
}
.site-footer-baseline .foot-link:hover,
.site-footer-baseline .foot-link:focus-visible { color: var(--ink); }

/* Keyboard focus is always visible. Component rules may remove their local
   outline for pointer styling; this final rule restores a shared focus ring. */
html body :is(a, button, summary, input, select, textarea):focus-visible {
  outline: 2px solid var(--ink) !important;
  outline-offset: 3px !important;
}
html body summary {
  min-height: var(--hit);
  box-sizing: border-box;
}

/* --- Design-system pager (bottom Prev/Next) ------------------ */
.ds-pager {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  margin-top: clamp(48px, 9vw, 88px);
  padding-top: 24px;
  border-top: 1px solid var(--line);
}
.ds-pager-cell {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 14px 18px;
  border: 1px solid var(--line);
  border-radius: var(--r);
  min-width: 0;
  max-width: 48%;
  transition: border-color 160ms ease, background 160ms ease;
}
.ds-pager-cell.is-empty {
  border: 0;
  padding: 0;
  pointer-events: none;
}
.ds-pager-prev { align-items: flex-start; text-align: left; }
.ds-pager-next { align-items: flex-end; text-align: right; }
.ds-pager-cell:hover {
  border-color: var(--line-strong);
  background: rgba(241, 241, 241, 0.02);
}
.ds-pager-dir {
  font-size: var(--t-caption);
  letter-spacing: var(--track-eyebrow);
  text-transform: lowercase;
  color: var(--ink-3);
}
.ds-pager-name {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: var(--t-body);
  font-weight: 500;
  color: var(--ink-2);
  transition: color 160ms ease;
}
.ds-pager-cell:hover .ds-pager-name { color: var(--ink); }
.ds-pager-arrow {
  color: var(--accent);
  font-size: var(--t-label);
  line-height: 1;
}

/* --- Design-system bands (the page architecture) ---------------
   Geist-style build: every section is a full-bleed band that owns its
   ground; bands alternate the two grounds (--g, --g-elev) so the build
   is visible — the elevated ground sits between the darkest ground and
   the OFF tone (#121017), so unlit segments stay legible on both.
   Each band holds one h2, one short line of body, then the plate.
   Bands touch; space lives inside the band on the 8px grid. */
.band { width: 100%; background: var(--g); }
.band--elev { background: var(--g-elev); }
.band-head {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  text-align: center;
}
.band-head h2 {
  margin: 0;
  font-size: var(--t-head);
  font-weight: 500;
  letter-spacing: var(--track-head);
  text-transform: lowercase;
  color: var(--ink);
}
.band-head .band-lede {
  margin: 0;
  font-size: var(--t-body);
  line-height: 1.7;
  color: var(--ink-2);
  max-width: 52ch;
}

/* ---- Bottom tab bar (app chrome) -------------------------------
   A fixed five-slot service bar that coexists with the top-left menu.
   Injected by RSSurfaces.mountTabBar() on every surface but the agent.
   --bar-height is the visual bar; the iOS safe-area is added below it. */
:root {
  --bar-tabs-h: 68px;
  --bar-height: var(--bar-tabs-h);
}
.tabbar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  /* Clamped and centred to the SAME frame as the top menu bar: the surface
     stops at the frame (page black beyond it) and the five buttons line up
     with the top bar's lockup instead of spreading edge-to-edge on desktop.
     Sub-frame viewports keep the full width, so mobile is unchanged. The top
     hairline (below) is the one line that ignores the clamp — as up top. */
  max-width: var(--frame);
  margin: 0 auto;
  z-index: 60;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  background: var(--bar-surface);
  padding-bottom: env(safe-area-inset-bottom, 0px);
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  box-sizing: border-box;
  /* Hidden by default, slid fully below the fold; revealed only once the
     footer's colophon row is passed (body.tabbar-shown, toggled by an
     IntersectionObserver in surfaces-nav.js — no scroll listeners). The
     reveal animates TRANSFORM only, so it is pure compositor work: no
     layout, no paint, buttery at any scroll speed.
     The bar's own hairline (::after) rides along, already contained by the
     backdrop-filter. */
  transform: translateY(calc(100% + 2px));
  transition: transform 0.34s cubic-bezier(0.22, 1, 0.36, 1);
  will-change: transform;
}
body.tabbar-shown .tabbar { transform: none; }
@media (prefers-reduced-motion: reduce) {
  .tabbar { transition: none; }
}
/* Five service tabs — the primary row of the bottom menu bar. */
.tabbar-tabs {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  align-items: stretch;
  min-height: var(--bar-tabs-h);
  flex: 0 0 auto;
}
/* Full-window top hairline — runs edge-to-edge like the top bar's bottom
   hairline, while the bar surface stays clamped to the frame. Fixed, so it
   sits out of the grid flow (never becomes a sixth cell). */
.tabbar::after {
  content: "";
  position: fixed;
  left: 0;
  right: 0;
  bottom: calc(var(--bar-height) + env(safe-area-inset-bottom, 0px));
  height: 1px;
  background: var(--line);
  pointer-events: none;
  z-index: 60;
}
.tab {
  appearance: none;
  -webkit-appearance: none;
  background: none;
  border: 0;
  margin: 0;
  padding: 9px 4px 10px;
  min-height: var(--bar-tabs-h);
  display: flex;
  flex-direction: column; /* MOBILE: glyph only, centred */
  align-items: center;
  justify-content: center;
  gap: 4px;
  cursor: pointer;
  color: var(--ink);
}
/* State is carried by COLOUR, not opacity: the selected
   surface is lit — its mark in the weather colour, its label in full ink. Every
   other tab sits OFF: mark AND label share the design-system dormant tone (a
   step brighter than pure --unlit so both stay readable at tab size — pure
   --unlit vanished into the ground). Icon and text carry ONE tone so, on the
   wider screen where the label shows, the text matches the mark. */
.tab .tab-label { transition: color 140ms ease; }
.tab .tab-glyph path { transition: fill 140ms ease; }
/* Dim unselected tabs — is-preview (MORE hover mirror) keeps native fills. */
.tab:not(.is-on):not(.is-preview) .tab-glyph path { fill: color-mix(in srgb, var(--unlit) 86%, var(--ink)); }
.tab:not(.is-on) .tab-label { color: color-mix(in srgb, var(--unlit) 86%, var(--ink)); }
.tab:hover:not(.is-on):not(.is-preview) .tab-glyph path { fill: color-mix(in srgb, var(--unlit) 60%, var(--ink)); }
.tab:hover:not(.is-on) .tab-label { color: color-mix(in srgb, var(--unlit) 60%, var(--ink)); }
.tab.is-on .tab-label { color: var(--ink); } /* the one selected surface */
/* MORE is-on / preview: native product segment colours (never force white).
   Open alone on a core page lights the monochrome all-segments plate. */
.tab--more.is-on .tab-label { color: var(--ink); }
.tab--more[aria-expanded="true"]:not(.is-on):not(.is-preview) .tab-glyph path { fill: var(--lit); }
.tab--more[aria-expanded="true"]:not(.is-on):not(.is-preview) .tab-label { color: var(--ink); }
.tab-glyph {
  width: 44px; /* MOBILE: big glyph (icon-only bar) */
  height: 44px;
  display: block;
  flex: 0 0 auto;
}
.tab-glyph--more {
  width: 46px;
  height: 46px;
}
.tab-label {
  display: none; /* MOBILE: hidden — the bar is glyph-only */
  font-size: var(--t-caption);
  letter-spacing: var(--track-title);
  text-transform: lowercase;
  font-weight: 500;
  line-height: 1;
}
/* DESKTOP: the menu-bar lockup — glyph with the name to its right. */
@media (min-width: 680px) {
  .tab {
    flex-direction: row;
    gap: 10px;
    padding: 9px 6px;
  }
  .tab-glyph { width: 40px; height: 40px; }
  .tab-glyph--more { width: 42px; height: 42px; }
  .tab-label { display: inline-block; }
}

/* MORE sheet — solid product panel above the tab bar (top-menu law).
   Opaque ground, no blur, explicit close. Page behind is covered, not
   frosted. Close via ×, the MORE tab, or Escape. */
.tabgrid {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: calc(var(--bar-height) + env(safe-area-inset-bottom, 0px));
  z-index: 62;
  overflow-y: auto;
  overscroll-behavior: contain;
  /* Solid ground — same takeover as the top mode-menu, no glass/blur */
  background: var(--g);
  border: 0;
  border-bottom: 1px solid var(--line);
  max-width: var(--frame);
  margin: 0 auto;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
}
.tabgrid[hidden] { display: none !important; }
.tabgrid-head {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 14px 16px 8px;
  min-height: 56px;
  box-sizing: border-box;
}
.tabgrid-head-label {
  font-size: var(--t-caption);
  font-weight: 500;
  letter-spacing: var(--track-eyebrow);
  text-transform: lowercase;
  color: var(--ink-3);
}
.tabgrid-close {
  appearance: none;
  -webkit-appearance: none;
  background: transparent;
  border: 0;
  color: var(--ink-3);
  width: 44px;
  height: 44px;
  min-width: 44px;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--r);
  cursor: pointer;
  font-size: var(--t-title);
  font-weight: 300;
  line-height: 1;
  padding: 0;
  margin: 0 0 0 auto;
}
.tabgrid-close:hover,
.tabgrid-close:focus-visible {
  color: var(--ink);
  background: transparent;
  outline: none;
}
.tabgrid-close:active { color: var(--ink-2); }
.tabgrid-inner {
  display: flex;
  flex-direction: column;
  gap: 0;
  flex: 1 1 auto;
  max-width: none;
  margin: 0;
  padding: 4px 0 16px;
}
/* Rows — same rest/hover law as the top mode-menu: dark monotone until
   hover or focus. is-on does not stay lit; colour is a hover state. */
.tabgrid-cell {
  appearance: none;
  -webkit-appearance: none;
  background: none;
  border: 0;
  cursor: pointer;
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 18px;
  width: 100%;
  padding: 14px 16px;
  min-height: 72px;
  border-radius: 0;
  color: var(--ink-3); /* recede on the ink ladder,
    never element opacity — light mode's vanta ink stays full black */
  text-align: left;
  box-sizing: border-box;
  transition: background 140ms ease, color 140ms ease;
}
.tabgrid-cell.is-on {
  /* Active row rests at the same darkness as every other row */
  color: var(--ink-3);
  background: transparent;
}
.tabgrid-cell:hover,
.tabgrid-cell:focus-visible {
  background: color-mix(in srgb, var(--ink) 6%, transparent); /* ink wash — visible on BOTH grounds */
  color: var(--ink);
  outline: none;
}
/* Glyph left — larger than the top menu’s 44px rows; monochrome at rest */
.tabgrid-glyph {
  width: 56px;
  height: 56px;
  flex: 0 0 56px;
  opacity: 0.25; /* row recede is colour-only */
  display: block;
  transition: opacity 160ms ease;
}
.tabgrid-cell:hover .tabgrid-glyph,
.tabgrid-cell:focus-visible .tabgrid-glyph {
  opacity: 1;
}
/* ONE material at rest: every segment of every glyph takes the same ink */
.tabgrid-cell:not(:hover):not(:focus-visible) .tabgrid-glyph path {
  fill: var(--ink);
}
/* densest mark (all-segments / archive) sits a notch darker at rest */
.tabgrid-cell svg[data-wx="all-segments"].tabgrid-glyph {
  opacity: 0.14;
}
.tabgrid-cell:hover svg[data-wx="all-segments"].tabgrid-glyph,
.tabgrid-cell:focus-visible svg[data-wx="all-segments"].tabgrid-glyph {
  opacity: 1;
}
/* Title — top-menu m-label scale, a notch larger for the list */
.tabgrid-label {
  flex: 1 1 auto;
  min-width: 0;
  font-size: var(--t-label); /* 15px — bigger than lede grid labels */
  font-weight: 500;
  letter-spacing: var(--track-label);
  text-transform: lowercase;
  line-height: 1.2;
  max-width: none;
}
.tabgrid-label .m-when {
  display: block;
  margin-top: 6px;
  font-size: var(--t-small);
  font-weight: 400;
  letter-spacing: var(--track-micro);
  text-transform: none;
  color: var(--ink-3);
}

/* ---- Section strip ---------------------------------------------
   Per-page section tabs, revealed below the menu bar once you scroll past the
   hero (screen two). Click a tab to snap to that section. Built from
   [data-section] elements by RSSurfaces.mountSectionNav(). Same ink ramp as the
   tab bar — only the current section is lit; the rest dim to level 3. */
.section-strip {
  position: fixed;
  top: 64px;
  left: 0;
  right: 0;
  /* Clamp to the page frame like the menu bar (.top), so the strip shares the
     content column instead of the full viewport — its tabs land on the same
     left rail as the glyph and the section headline, not a wider 1120 column. */
  max-width: var(--frame);
  margin: 0 auto;
  z-index: 25;
  background: var(--bar-surface);
  border-bottom: 1px solid var(--line);
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  transform: translateY(-101%);
  opacity: 0;
  pointer-events: none;
  transition: transform 260ms cubic-bezier(0.4, 0, 0.2, 1), opacity 200ms ease;
}
.section-strip.is-shown {
  transform: translateY(0);
  opacity: 1;
  pointer-events: auto;
}
/* A short black-to-transparent fade below the strip, so content passing under
   the sub-menu dissolves into it rather than hard-cutting. */
.section-strip::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 100%;
  height: 133px;
  /* Solid black holds across the top before the fade starts, so the strip's
     lower edge reads properly black rather than fading from the first pixel. */
  background: linear-gradient(to bottom, #000 0%, #000 34%, transparent 100%);
  pointer-events: none;
}
.section-strip-inner {
  display: flex;
  align-items: stretch;
  gap: 2px;
  height: 46px;
  /* Fill the frame; the left inset is the rail MINUS the first tab's own 14px
     padding, so the tab's CONTENT (its symbol) lands on the 22px rail while each
     tab keeps symmetric padding — the label stays centred over its underline
     rather than jammed to one side. */
  max-width: none;
  margin: 0;
  padding: 0 var(--rail) 0 calc(var(--rail) - 14px);
  overflow-x: auto;
  scrollbar-width: none;
}
.section-strip-inner::-webkit-scrollbar { display: none; }
.section-tab {
  display: inline-flex;
  align-items: center;
  padding: 0 14px; /* symmetric — centres the word over the underline */
  white-space: nowrap;
  font-size: var(--t-eyebrow);
  letter-spacing: var(--track-caps);
  text-transform: lowercase; /* the tabs read as lowercase file paths (/services) */
  font-weight: 500;
  color: var(--ink-3); /* ink ramp — dimmed until selected, ON the ladder */
  text-decoration: none;
  border-bottom: 2px solid transparent;
  transition: color 140ms ease, border-color 140ms ease;
}
.section-tab:hover { color: var(--ink-2); }
.section-tab.is-on {
  color: var(--ink);
  border-bottom-color: var(--ink);
}
/* Marked sections clear BOTH the menu bar and the strip when snapped to.
   Scoped to .has-section-strip (set by mountSectionNav) so it overrides any
   per-page scroll-margin on those sections. */
.has-section-strip [data-section] { scroll-margin-top: 118px; }

/* ==== Content sections — the shared page rhythm ============================
   Every page (except the agent) is built from these: a stack of full-bleed
   sections, each framed on one comfortable screen with ample air from BOTH
   bars. Mobile-first. Order: eyebrow · title · lead · body, then media/lists. */
:root {
  --section-max: 720px;
  --section-pad-x: clamp(20px, 6vw, 40px);
  --section-gap: clamp(18px, 3.4vw, 30px);
}
.page-section {
  box-sizing: border-box;
  width: 100%;
  min-height: min(88svh, 760px);
  padding: clamp(64px, 12vh, 132px) var(--section-pad-x);
  display: flex;
  flex-direction: column;
  justify-content: center;
  scroll-margin-top: 118px;
}
.page-section + .page-section { border-top: 1px solid var(--line); }
.section-inner {
  width: 100%;
  max-width: var(--section-max);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: var(--section-gap);
}
.section-head { display: flex; flex-direction: column; gap: 12px; }
.section-eyebrow {
  margin: 0;
  font-size: var(--t-eyebrow);
  letter-spacing: var(--track-wide);
  text-transform: lowercase;
  color: var(--ink-3);
}
.section-title {
  /* Poster line — sized UP the ramp with a token (never raw px); house
     weight 500, natural casing. This IS the design-system heading. */
  margin: 0;
  font-size: var(--t-poster);
  font-weight: 500;
  letter-spacing: var(--track-tighter);
  line-height: 1.05;
  text-transform: none;
  color: var(--ink);
}
.section-lead {
  margin: 0;
  font-size: var(--t-body);
  line-height: 1.7;
  color: var(--ink-2);
}
.section-body {
  margin: 0;
  font-size: var(--t-body);
  line-height: 1.7;
  color: var(--ink-2);
}
/* ═══ Body-copy law — the app-wide reading column ══════════════════════════════
   Every run of SECTION body copy draws from ONE rule: left-aligned to the --read-w
   mobile-viewport measure, so long-form copy reads as the same narrow ragged-right
   column on every screen. Covers the plug-and-play paragraph (.prose), the section
   lede/body, the rs-duo copy column, and the page-level location / block ledes.
   Card copy, hero statements and labels are deliberately NOT here. (2026-07-31) */
.prose,
.section-lead,
.section-body,
.rs-duo-copy p,
.loc-body,
.block-lede,
.team-intro,
.home-lede,
.home-body,
.ds-lede,
.gloss-lede,
.kb-lede,
.kb-group-body,
.faq-lede,
.bp-lede,
.arch-intro p,
.studio-poster-caption p {
  max-width: var(--read-w, 430px);
  text-align: left;
}
/* Placeholder — a section with nothing in it yet. Honest stub so the app's
   architecture is complete before the content is written. */
.page-section--placeholder .section-title { color: var(--ink-3); }
.section-stub {
  margin: 0;
  font-size: var(--t-caption);
  letter-spacing: var(--track-eyebrow);
  text-transform: lowercase;
  color: var(--ink-3);
  border: 1px dashed var(--line-strong);
  border-radius: var(--r);
  padding: clamp(28px, 8vw, 56px) 20px;
  text-align: center;
  width: 100%;
}

/* ==== Card content used by plain cards ===================================
   These slots sit inside .seat-holder-card--plain. Rail geometry belongs only
   to .card-rail. */
.shc-media {
  aspect-ratio: 4 / 3;
  width: 100%;
  border-radius: var(--r);
  background: var(--g-elev);
  border: 1px solid var(--line);
  overflow: hidden;
  display: grid;
  place-items: center;
  font-size: var(--t-eyebrow);
  letter-spacing: var(--track-eyebrow);
  text-transform: lowercase;
  color: var(--ink-3);
}
.shc-media img { width: 100%; height: 100%; object-fit: cover; display: block; }
.shc-eyebrow {
  margin: 0;
  font-size: var(--t-eyebrow);
  letter-spacing: var(--track-wide);
  text-transform: lowercase;
  color: var(--ink-3);
}
.shc-title {
  /* Card title = --t-title token, house weight 500, natural casing. */
  margin: 0;
  font-size: var(--t-title);
  font-weight: 500;
  letter-spacing: var(--track-tight);
  line-height: 1.25;
  color: var(--ink);
}
.shc-copy {
  margin: 0;
  font-size: var(--t-body);
  line-height: 1.7;
  color: var(--ink-2);
}
/* The sense tag under an activity title — the day's sense as core info. */
.shc-sense {
  margin: 0;
  font-size: var(--t-eyebrow);
  letter-spacing: var(--track-wide);
  text-transform: lowercase;
  color: var(--ink-3);
}
.shc-includes {
  margin: 4px 0 0;
  padding: 14px 0 0;
  border-top: 1px solid var(--line);
}
.shc-includes-label {
  margin: 0 0 8px;
  font-size: var(--t-eyebrow);
  letter-spacing: var(--track-eyebrow);
  text-transform: lowercase;
  color: var(--ink-3);
}
.shc-includes ul {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.shc-includes li {
  font-size: var(--t-caption);
  letter-spacing: var(--track-micro);
  color: var(--ink-3);
  line-height: 1.4;
}
/* Body-scale includes — the same labelled detail list, sized to sit with .block-lede
   body copy instead of card metadata (host bios, section detail lists). Opt in with
   .shc-includes--body; tokens only, so it tracks the shared type + spacing scale. */
.shc-includes--body { padding-top: var(--space-lg); }
.shc-includes--body .shc-includes-label {
  font-size: var(--t-caption);
  letter-spacing: var(--track-wide);
  margin-bottom: var(--space-md);
  color: var(--ink-3);
}
.shc-includes--body ul { gap: var(--space-md); }
.shc-includes--body li {
  font-size: var(--t-body);
  line-height: 1.6;
  color: var(--ink-2);
}
/* Progress gauge: one sun rides below every rail that overflows. mountCardRails
   inlines the shared weather master here and lights its eight rays clockwise as
   you move through the cards — the last card leaves a fully radiant sun. */
.card-rail__dots {
  display: flex; align-items: center; justify-content: flex-start;
  width: min(100%, var(--card-w)); margin: 12px 0 0;
}
.card-rail__dots[hidden] { display: none; }
/* Crown variant: the sun rides ABOVE the section's /path-title (mountCardRails
   mounts it just before .rs-split-title), so the head's own 24px flex gap spaces
   it from the title. Zero the trailing margin the below-rail sun carries. (William, 2026-08-03) */
.card-rail__dots--top { margin: 0; }
.card-rail__sun { width: var(--hit); height: var(--hit); display: block; }
/* Show only the sun — the rest of the master's scene stays out of the gauge. */
.card-rail__sun .wx:not(.wx-sun):not(.wx-sun-lower) { display: none; }
/* Monochrome: only the RAYS are the gauge. The disc (top + bottom) stays a dim
   static body; the rays fill to full ink as you advance through the cards. */
.card-rail__sun .wx-sun-face { fill: var(--line); }
.card-rail__sun .wx-sun-ray {
  fill: var(--line);
  transition: fill var(--dur-base) var(--ease);
}
.card-rail__sun .wx-sun-ray.is-lit { fill: var(--ink-2); }
@media (prefers-reduced-motion: reduce) {
  .card-rail__sun .wx-sun-ray { transition: none; }
}

/* ==== The reveal — scroll law (mountReveal, surfaces-nav.js) ==============
   ONE settle-in for every content section and the hero: it fades + rises
   once as it enters view, then rests. mountReveal (IntersectionObserver)
   adds .is-in on entry, then unobserves — the move never re-plays. The
   transition rides the family clock (--dur-slow / --ease). The hidden
   initial state hangs off html.rs-anim, which the engine adds ONLY when it
   will actually observe — so no-JS, no-IO and reduced-motion pages rest
   fully visible, nothing ever missing. */
.rs-reveal {
  transition: opacity var(--dur-slow) var(--ease), transform var(--dur-slow) var(--ease);
}
html.rs-anim .rs-reveal {
  opacity: 0;
  transform: translateY(var(--reveal-rise));
}
/* Revealed — scoped under html.rs-anim too, so it out-specifies the hidden rule
   above (else the section would add .is-in but stay at opacity:0). */
html.rs-anim .rs-reveal.is-in { opacity: 1; transform: none; }
/* Fade-only — no rise — where a transform would break position:sticky or
   re-composite a live WebGL canvas (the hero mark, sense particles, globe). */
html.rs-anim .rs-reveal--fade { transform: none; }
@media (prefers-reduced-motion: reduce) {
  html.rs-anim .rs-reveal { opacity: 1; transform: none; }
  .rs-reveal { transition: none; }
}

/* The bar hides until the bottom, so the hero owns the FULL first screen
   (no bottom reservation) — the reserved space lives at the very end of the
   scroll, where the bar reveals over it, so content is never covered. */
.has-tabbar .surface { padding-bottom: calc(var(--bar-height) + env(safe-area-inset-bottom, 0px)); }
.has-tabbar .ds-layout { padding-bottom: calc(var(--bar-height) + env(safe-area-inset-bottom, 0px)); }
.has-tabbar .page-hero {
  min-height: calc(100vh - 64px);
  min-height: calc(100svh - 64px);
}
/* Agent (full-height .app grid, no window scroll): shrink the app by the bar
   so its composer sits directly above it, never behind it. */
.has-tabbar .app {
  height: calc(100% - var(--bar-height) - env(safe-area-inset-bottom, 0px));
}

/* ====================================================================
   Seat-card — the person / seat flip variant. Lifted from dao.html
   to single-source it (used on /network and /studio /founding-team). The front
   sets a content-led height; the back overlays as the skill.md. The flip handler
   lives in nav.js; the shared .card-rail owns layout on every page.
   ==================================================================== */
/* The front sets the height and the back overlays it. */
.seat-card { margin: 0; min-height: var(--card-min-h); perspective: 1400px; }
.seat-card-inner { position: relative; width: 100%; transform-style: preserve-3d; transition: transform .55s cubic-bezier(.22,1,.36,1); }
.seat-card.is-flipped .seat-card-inner { transform: rotateY(180deg); }
.seat-face { backface-visibility: hidden; -webkit-backface-visibility: hidden; display: flex; flex-direction: column; overflow: hidden; border: 1px solid var(--line); background: transparent; }
/* The front stays in normal flow and sets the flip card's height. The back uses
   that same height, so longer front content can never clip its action. */
.seat-front { position: relative; min-height: var(--card-min-h); }
.seat-back { position: absolute; inset: 0; transform: rotateY(180deg); padding: 18px; }
.seat-photo-wrap { position: relative; flex: none; }
.seat-photo { width: 100%; height: auto; aspect-ratio: var(--card-photo); object-fit: cover; display: block; }   /* height:auto lets aspect-ratio win over the img's height="" attribute (else the plate renders at the file's natural height) */
.seat-photo--empty { aspect-ratio: var(--card-photo); background: transparent; position: relative; }
/* The badge law: the PFP straddles the photo/body break — geometry from the
   shared --pfp-* tokens (shell.css .shc-badge) so persona + venture cards share
   ONE badge (overhang 52 of a 104 badge, 4px ring, 62px glyph). */
.seat-badge { position: absolute; left: 16px; bottom: calc(-1 * var(--pfp-overhang)); width: var(--pfp-badge); height: var(--pfp-badge); border-radius: 50%; background: var(--g); border: var(--pfp-ring) solid var(--g); display: inline-flex; align-items: center; justify-content: center; overflow: hidden; }
.seat-badge img { width: var(--pfp-glyph); height: var(--pfp-glyph); }
.seat-badge--photo img { width: 100%; height: 100%; object-fit: cover; }
/* Body clears the badge's lower half: the overhang + a space step of air. */
.seat-front-body { display: flex; flex-direction: column; flex: 1 0 auto; padding: calc(var(--pfp-overhang) + var(--space-md)) 18px 18px; text-align: left; }
.seat-grid { display: grid; grid-template-columns: 1fr auto; grid-template-rows: auto auto 1fr; gap: 14px 16px; flex: 1 0 auto; }
.seat-grid--open { grid-template-rows: auto 1fr auto; flex: 1 0 auto; }
/* Open seat holds the same height as the filled cards (rail is grid-auto-rows:1fr)
   even with less content: the front fills the stretched figure, the middle row
   flexes, and ENQUIRE pins full-width to the bottom. */
.seat-card--open { display: flex; flex-direction: column; }
.seat-card--open .seat-front { flex: 1 0 auto; }
.seat-card--open .seat-front-body { flex: 1 0 auto; }
.seat-cell--name { grid-column: 1; grid-row: 1; }
.seat-cell--seat { grid-column: 2; grid-row: 1; justify-self: end; text-align: right; }
/* Four elements only: name and seat share the header row, the ownership record
   sits below, and SKILL.MD stays full-width at the bottom. */
.seat-cell--own { grid-column: 1 / -1; grid-row: 2; }
.seat-cell--flip { grid-column: 1 / -1; grid-row: 3; justify-self: stretch; align-self: end; }
.seat-cell--flip .seat-flip { width: 100%; justify-content: center; }
.seat-grid--open .seat-cell--own { grid-column: 1 / -1; grid-row: 2; align-self: start; }
.seat-grid--open .seat-cell--flip { grid-column: 1 / -1; grid-row: 3; justify-self: stretch; align-self: end; margin-top: 4px; }
.seat-grid--open .seat-cell--flip .seat-flip { width: 100%; justify-content: center; }
.seat-lbl { display: block; font-size: var(--t-eyebrow); font-weight: 500; letter-spacing: var(--track-wide); text-transform: lowercase; color: var(--ink-3); margin-bottom: 4px; }
/* The persona title reads as THE unified card title (.h-card, shell.css): the
   file-path/terminal face — mono, lowercase, the leading slash reads white. So
   a persona card and a venture card wear the same title (William, 2026-07-30). */
.seat-name { margin: 0; font-family: var(--font-code, "Fira Code", ui-monospace, monospace); font-size: var(--t-card); font-weight: 500; letter-spacing: 0; text-transform: lowercase; line-height: var(--lh-title); color: var(--ink); }
.seat-seat { font-family: var(--font-code, "Fira Code", ui-monospace, monospace); font-size: var(--t-label); letter-spacing: var(--track-micro); font-variant-numeric: tabular-nums; color: var(--ink); }
/* Seat register: the live digit stays lit while the leading zeros and the /333
   total sit unlit, so the eye lands on the seat number. Open seat reads all-unlit. */
.seat-seat .c-dim { color: color-mix(in srgb, var(--ink) 24%, transparent); }
.seat-contract { font-family: "Fira Code", ui-monospace, monospace; font-size: var(--t-small); color: var(--ink-3); display: inline-flex; align-items: center; gap: 6px; text-decoration: none; transition: color .14s ease; }
.seat-contract:hover { color: var(--ink); }
.seat-eth { width: 9px; height: 13px; fill: currentColor; flex: none; }
.seat-open-copy { margin: 0; font-size: var(--t-small); color: var(--ink-3); line-height: 1.5; }
.seat-flip { flex: 0 0 auto; min-height: var(--hit); display: inline-flex; align-items: center; gap: 8px; font-family: "Fira Code", ui-monospace, monospace; font-size: var(--t-small); font-weight: 500; letter-spacing: var(--track-head); color: var(--ink-3); background: none; border: 1px solid var(--line); padding: 8px 13px; cursor: pointer; text-decoration: none; transition: color .14s ease, border-color .14s ease; }
.seat-flip:hover { color: var(--ink); border-color: var(--ink-3); }
.seat-flip-ico { color: var(--ink-3); transition: color .14s ease; }
.seat-flip:hover .seat-flip-ico { color: var(--spark); }
.seat-back-scroll { flex: 1; overflow-y: auto; font-family: "Fira Code", ui-monospace, monospace; font-size: var(--t-small); line-height: 1.65; color: var(--ink-2); white-space: pre-wrap; }
.seat-back-foot { display: flex; gap: 10px; margin-top: 12px; flex: none; }
.seat-copy { min-height: var(--hit); font-family: "Fira Code", ui-monospace, monospace; font-size: var(--t-small); font-weight: 500; color: var(--ink-2); background: var(--g); border: 1px solid var(--line); padding: 8px 13px; cursor: pointer; display: inline-flex; align-items: center; gap: 8px; transition: color .14s ease, border-color .14s ease; }
.seat-copy:hover { border-color: var(--ink-3); color: var(--ink); }
.seat-copy.is-copied { border-color: var(--spark); color: var(--ink); }
/* Rail CTA — one button below a card rail (e.g. /studio services + seats).
   Card-width so it lines up with the rail's first card. */
.rail-cta { margin: clamp(20px, 4vh, 40px) 0 0; width: var(--rail-basis); max-width: var(--card-w); }
.rail-cta .btn { width: 100%; justify-content: center; }

/* ── Content PFP — the full weather master as a card badge (all-segments-colour):
   every segment dormant in ONE OFF tone (the ALL OFF face is uniform — no
   always-lit base segment), each card lighting only its own segments via
   data-wx-light, at full colour. The brand-LCD "light the subset" system,
   single-sourced (home offers + studio services + clients). In a scrolling
   rail the light follows focus — the carousel gate at the end of this block
   holds every off-focus card at the uniform ALL OFF face. ── */
/* The card badge's OFF base + every per-segment FILL (cloud · stars · bolt ·
   rain · lrain · conc · star1-5) now come from the shared unified light layer
   below — .shc-badge carries data-wx-light, so the SAME context-agnostic rules
   light it. Only the badge's own `sun` exception stays prefixed here. */
/* Just the sun (upper + lower rays): the badge exception — hide EVERY segment
   (transparent, NOT display:none, so geometry is unchanged) so no dormant cloud
   crops the disc, then relight the two sun pieces. .shc-badge lifts it to
   (0,4,2), so it cleanly beats the shared layer's (0,3,2) sun→cloud rule. */
.shc-badge[data-wx-light="sun"] svg[data-wx="all-segments-colour"] .wx path { fill: transparent; }
.shc-badge[data-wx-light="sun"] svg[data-wx="all-segments-colour"] .wx-sun path,
.shc-badge[data-wx-light="sun"] svg[data-wx="all-segments-colour"] .wx-sun-lower path { fill: var(--sun); }

/* ── Unified weather-glyph light layer (William, 2026-08-03) ───────────────
   ONE master (all-segments-colour, inlined anywhere) + a data-wx-light key lights
   its segments; every other segment rests at the OFF tone. Context-agnostic — the
   SAME rules serve #topChrome (the top-left LCD), .shc-badge (card badges), the
   footer status, or any host that carries a light key. Opt-in via [data-wx-light]
   so a keyless master (e.g. the collectibles hero) keeps its full colour.

   Specificity: base = (0,3,2); every fill rule below = (0,3,2) and sits AFTER the
   base, so it wins the tie (the same base↔fill pattern the chrome + badges have
   always relied on). Higher-specificity context rules still override — the badge
   `sun` transparent-trick (.shc-badge… = 0,4,2) and the retreat hibiscus stars
   (body[data-surface="retreat"] #topChrome… = 1,4,2). MOTION stays chrome-local
   (see #topChrome programmes above): "light a segment" is the shared contract;
   the LCD weather motion is a chrome behaviour, and badges + footer stay still. */
[data-wx-light] svg[data-wx="all-segments-colour"] .wx { animation: none; }
[data-wx-light] svg[data-wx="all-segments-colour"] .wx path { fill: var(--wx-off); transition: fill 200ms ease; }

/* hard rain (wx-rain1-4) — brand/rain/all + the badge's rain key → accent */
:is([data-wx-light="brand"], [data-wx-light="rain"], [data-wx-light="all"]) svg[data-wx="all-segments-colour"] :is(.wx-rain1, .wx-rain2, .wx-rain3, .wx-rain4) path { fill: var(--accent); }
/* triathlon reads the three disciplines across the hard-rain streaks */
[data-wx-light="triathlon"] svg[data-wx="all-segments-colour"] .wx-rain1 path { fill: var(--lit); }
[data-wx-light="triathlon"] svg[data-wx="all-segments-colour"] :is(.wx-rain2, .wx-rain4) path { fill: var(--accent); }
[data-wx-light="triathlon"] svg[data-wx="all-segments-colour"] .wx-rain3 path { fill: var(--spark); }
/* drizzle (wx-lrain1-3) — chrome drizzle/coffee/all → storm; the badge's own lrain key → lit */
:is([data-wx-light="drizzle"], [data-wx-light="coffee"], [data-wx-light="all"]) svg[data-wx="all-segments-colour"] :is(.wx-lrain1, .wx-lrain2, .wx-lrain3) path { fill: var(--storm); }
[data-wx-light="lrain"] svg[data-wx="all-segments-colour"] :is(.wx-lrain1, .wx-lrain2, .wx-lrain3) path { fill: var(--lit); }
/* thunder (wx-bolts) — brand/storm/bolt/all + the badge bolt key → rain */
:is([data-wx-light="brand"], [data-wx-light="storm"], [data-wx-light="bolt"], [data-wx-light="all"]) svg[data-wx="all-segments-colour"] .wx-bolts path { fill: var(--rain); }
/* cloud — brand holds it steady (full cloud+thunder+hard-rain master), scent + the
   sunny/stormy keys join, plus the badge cloud key → lit */
:is([data-wx-light="brand"], [data-wx-light="storm"], [data-wx-light="coffee"], [data-wx-light="sun"], [data-wx-light="cloud"], [data-wx-light="scent"], [data-wx-light="all"]) svg[data-wx="all-segments-colour"] .wx-cloud path { fill: var(--lit); }
/* the sun disc — Greater Records + the sun/all keys → the SUN colour */
:is([data-wx-light="greater"], [data-wx-light="sun"], [data-wx-light="all"]) svg[data-wx="all-segments-colour"] .wx-sun path { fill: var(--sun); }
/* the six-star constellation — chrome scent/all + the badge stars key (identical
   colours): sun · cloud · hard rain · rain · snow · thunder */
:is([data-wx-light="scent"], [data-wx-light="all"], [data-wx-light="stars"]) svg[data-wx="all-segments-colour"] .wx-star1 path { fill: var(--sun); }
:is([data-wx-light="scent"], [data-wx-light="all"], [data-wx-light="stars"]) svg[data-wx="all-segments-colour"] .wx-star2 path { fill: var(--lit); }
:is([data-wx-light="scent"], [data-wx-light="all"], [data-wx-light="stars"]) svg[data-wx="all-segments-colour"] .wx-star3 path { fill: var(--accent); }
:is([data-wx-light="scent"], [data-wx-light="all"], [data-wx-light="stars"]) svg[data-wx="all-segments-colour"] .wx-star4 path { fill: var(--storm); }
:is([data-wx-light="scent"], [data-wx-light="all"], [data-wx-light="stars"]) svg[data-wx="all-segments-colour"] .wx-star5 path { fill: var(--gold); }
:is([data-wx-light="scent"], [data-wx-light="all"], [data-wx-light="stars"]) svg[data-wx="all-segments-colour"] .wx-star6 path { fill: var(--spark); }
/* concentration (badge) — all six stars one accent */
[data-wx-light="conc"] svg[data-wx="all-segments-colour"] :is(.wx-star1, .wx-star2, .wx-star3, .wx-star4, .wx-star5, .wx-star6) path { fill: var(--accent); }
/* individual stars — finer per-card badge segments (studio services) */
[data-wx-light="star1"] svg[data-wx="all-segments-colour"] .wx-star1 path { fill: var(--sun); }
[data-wx-light="star2"] svg[data-wx="all-segments-colour"] .wx-star2 path { fill: var(--accent); }
[data-wx-light="star3"] svg[data-wx="all-segments-colour"] .wx-star3 path { fill: var(--gold); }
[data-wx-light="star4"] svg[data-wx="all-segments-colour"] .wx-star4 path { fill: var(--storm); }
[data-wx-light="star5"] svg[data-wx="all-segments-colour"] .wx-star5 path { fill: var(--spark); }
/* operational (footer status) — the FULL sun green, cloud struck */
[data-wx-light="operational"] svg[data-wx="all-segments-colour"] :is(.wx-sun, .wx-sun-lower) path { fill: #22c55e; }  /* operational / online green — a status semantic, not a brand chrome colour */
[data-wx-light="operational"] svg[data-wx="all-segments-colour"] .wx-cloud { display: none; }  /* sunny = all good; no cloud over the full sun */

/* The stroke follows the light: a card's lit ring (--card-lit, the focus/hover
   border) IS the colour of the segments its badge lights, mapped from
   data-wx-light — one map, mirroring the fill rules above. */
.seat-holder-card:has([data-wx-light="cloud"]),
.seat-holder-card:has([data-wx-light="lrain"]) { --card-lit: var(--lit); }
.seat-holder-card:has([data-wx-light="bolt"]) { --card-lit: var(--rain); }
.seat-holder-card:has([data-wx-light="rain"]),
.seat-holder-card:has([data-wx-light="conc"]),
.seat-holder-card:has([data-wx-light="star2"]) { --card-lit: var(--accent); }
.seat-holder-card:has([data-wx-light="sun"]),
.seat-holder-card:has([data-wx-light="star1"]) { --card-lit: var(--sun); }
.seat-holder-card:has([data-wx-light="star3"]) { --card-lit: var(--gold); }
.seat-holder-card:has([data-wx-light="star4"]) { --card-lit: var(--storm); }
.seat-holder-card:has([data-wx-light="star5"]) { --card-lit: var(--spark); }
/* A multi-colour light strokes in ALL its colours: the six-star constellation
   card runs the same six star colours as a gradient ring while lit (border-image
   — the card is square, so no radius clash). Applies on rail focus, and on
   hover/keyboard focus anywhere a rail is not scrolling. */
.card-rail.is-overflowing > .seat-holder-card.is-focus:has([data-wx-light="stars"]),
:not(.card-rail.is-overflowing) > .seat-holder-card:has([data-wx-light="stars"]):is(:hover, :focus-within) {
  border-image: linear-gradient(135deg, var(--sun), var(--lit), var(--accent), var(--storm), var(--gold), var(--spark)) 1;
}
/* The carousel gate: while a rail scrolls (.is-overflowing), only the in-focus
   card may light its segments — every other card holds the uniform ALL OFF
   face. Outranks every [data-wx-light] rule above (including the sun card's
   transparent override), so a deselected card can never show a lit or hidden
   segment. */
.card-rail.is-overflowing > :not(.is-focus) .shc-badge svg[data-wx="all-segments-colour"] .wx path {
  fill: var(--wx-off);
}
