/* Brume site: "The Cabin Window" (cabin.css v153, 2026-09-13).
   CONTRAST PROOF: tools/contrast.py in the site repo. RUN IT BEFORE ANY COLOUR OR SURFACE CHANGE IN
   THIS FILE, and it must exit 0. The proof rests on four constants declared here:
     LAMP_MAX   .lampglow / .wl-lamp   opacity .16   the lamp wash never brightens the wall past 16%
     FIRE_MAX   .fireglow / .wl-fire   opacity .13   the fire wash never brightens the wall past 13%
     FLASH_MAX  .flash    / .wl-flash  opacity .17   the lightning wash is capped, behind every pane
     PANE       --pane  rgba(7,9,12,.82)             every text block on the wall carries this scrim
   NO BODY TEXT EVER SITS ON A LIVE CANVAS: every text block sits on the interior wall behind PANE
   (the footer included). Raise the canvas exposure inside assets/cabin.js, NEVER these caps, and
   mirror any colour or surface-alpha change into tools/contrast.py so it exits 0 again.

   FIX 2 (2026-09-13, phone controls). Below 900 px the control row under the window (.wctl,
   holding Wipe the glass, Listen, Volume and the honest note) is display:none and the phone
   bottom reserve --apb shrinks to the sill plus the nightstand plus the wordmark row, so the
   glass pane takes the space back. Listen moves to a fixed bottom-left chip (.listen) mirroring
   the Pause motion chip, and the honest note (.listen-note) flashes on a PANE scrim above it.
   Both are display:none at >= 900 px: the desktop layout is untouched and there is never a
   second visible Listen. New contrast pairs for both are in tools/contrast.py.

   FIX 3 (2026-09-13, phone layout). Below 900 px the window is NOT sticky: it scrolls away
   with the hero (cabin.js pauses the canvases while it is off screen), the sill row leaves the
   window and becomes the slim sticky .band under the fixed nav, the nightstand gets an inline
   twin (.nsin) in the falling-asleep chapter, --apb drops to the sill plus a little wall, and
   the hero pane keeps a chip's worth of bottom padding so its last line clears the two fixed
   chips. Anchor offsets ([id]{scroll-margin-top}) are nav + band. Desktop is unchanged: the
   window is sticky only inside the >= 900 px block, where .band and .nsin are display:none.

   MOOD ROW (2026-09-13, v153). The "What it looks like at 2 a.m." chapter now opens with the
   two owner marketing squares, above the eight screenshots; the .mood / .moodpair rules sit
   just above the shots block. That is the only change, so index.html moved to ?v=153 for the
   STYLESHEET only (cabin.js stays ?v=152, the fonts and GSAP stay ?v=150), and the sub-pages
   link this file and assets/cabin-pages.css at the same ?v=153.

   Fraunces is self-hosted: the production CSP is default-src 'self' with no font-src, so the faces
   are files on this origin and the Google Fonts CDN is never linked. The descriptors below are
   Google's own css2 output for the latin subset, verbatim apart from the URL (assets/vendor/
   VENDOR.txt records the sources and sha256 of both files). */
@font-face{font-family:"Fraunces";font-style:normal;font-weight:300 500;font-display:swap;src:url(fonts/fraunces-roman.woff2?v=150) format("woff2")}
@font-face{font-family:"Fraunces";font-style:italic;font-weight:300;font-display:swap;src:url(fonts/fraunces-italic.woff2?v=150) format("woff2")}

*{box-sizing:border-box;margin:0;padding:0}
:root{
  --night:#07090c;
  --wall:#0b0d10;
  --ink:#f1ece5;
  --text:#ced5dc;
  --dim:#9aa3ad;
  --faint:#7d8894;
  --line:rgba(190,205,225,.12);
  --line2:rgba(190,205,225,.26);
  --pane:rgba(7,9,12,.82);
  --pro:#a8965e;
  --free:#7cc99a;
  --lamp:#8fa7bd;
  --lampw:143,167,189;
  --lampA:1;
  --fireA:0;
  --flashA:0;
  --nightA:0;
  --serif:"Fraunces",Georgia,"Times New Roman",serif;
  --sans:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
  --pad:clamp(1.15rem,4.4vw,3rem);
  --ease:cubic-bezier(.2,.75,.25,1);
  --navh:58px;   /* fix 3: the nav's own height (.6rem padding + a 38px brand row + 1px) */
  --bandh:46px;  /* fix 3: the sticky sill band under it, phones only */
  --apx:6%;
  --apt:74px;
  --apb:46px; /* fix 3: sill + a little wall; the wordmark row is the sticky band now */
  --winh:55svh;
}
html[data-lamp="ember"]  {--lamp:#d8946e;--lampw:216,148,110}
html[data-lamp="pine"]   {--lamp:#79b088;--lampw:121,176,136}
html[data-lamp="fathom"] {--lamp:#7fa6d4;--lampw:127,166,212}
html[data-lamp="heather"]{--lamp:#c49ab4;--lampw:196,154,180}
html[data-lamp="plum"]   {--lamp:#ad93d9;--lampw:173,147,217}

html{background:var(--wall);color:var(--text);color-scheme:dark;overflow-x:clip;-webkit-text-size-adjust:100%;scroll-behavior:smooth}
html.rm{scroll-behavior:auto}
body{font-family:var(--sans);font-size:16px;line-height:1.62;background:var(--wall);color:var(--text);overflow-x:clip;min-height:100svh;-webkit-font-smoothing:antialiased}
img,svg,canvas,video{display:block;max-width:100%}
a{color:inherit;text-decoration:none}
b,strong{font-weight:600}
::selection{background:var(--lamp);color:#07090c}
[id]{scroll-margin-top:calc(var(--navh) + var(--bandh) + 14px)}

/* Focus: a browser without :focus-visible support drops the ":focus:not(:focus-visible)" rule
   whole (invalid selector) and therefore keeps the plain ring on every focus; a modern browser
   drops the plain ring for pointer focus and paints the two-tone ring instead. (REVIEW-B item 4.) */
:focus{outline:2px solid var(--ink);outline-offset:2px}
:focus:not(:focus-visible){outline:none}
:focus-visible{outline:2px solid transparent;outline-offset:2px;box-shadow:0 0 0 3px rgba(5,7,11,.94),0 0 0 5px var(--ink),0 0 0 8px rgba(5,7,11,.94);border-radius:8px}

.skip{position:absolute;left:-999px;top:.55rem;z-index:70;background:var(--ink);color:#07090c;padding:.75em 1.15em;border-radius:10px;font-weight:600;min-height:44px;display:inline-flex;align-items:center;box-shadow:0 0 0 3px rgba(5,7,11,.94)}
.skip:focus{left:.6rem}

/* fixed interior: the wall the whole page sits inside */
.bg{position:fixed;inset:0;z-index:0;pointer-events:none;overflow:hidden}
.wall{position:absolute;inset:0;background:
  radial-gradient(120% 70% at 50% 120%, rgba(24,20,17,.55), transparent 62%),
  linear-gradient(180deg,#06070a 0%, var(--wall) 38%, #0c0e11 78%, #07080b 100%)}
.wall::after{content:"";position:absolute;inset:0;opacity:.5;
  background:repeating-linear-gradient(96deg, rgba(255,255,255,.012) 0 2px, rgba(0,0,0,0) 2px 9px)}
.lampglow{position:absolute;inset:0;opacity:calc(.16*var(--lampA))}
.lampglow i{position:absolute;inset:0;display:block;background:
  radial-gradient(46% 42% at 14% 78%, rgba(var(--lampw),1), transparent 70%),
  radial-gradient(80% 60% at 30% 96%, rgba(var(--lampw),.5), transparent 72%);
  animation:breathe 9s ease-in-out infinite;transition:background .9s ease}
.fireglow{position:absolute;inset:0;opacity:calc(.13*var(--fireA))}
.fireglow i{position:absolute;inset:0;display:block;background:radial-gradient(52% 46% at 8% 104%, #d0763c, transparent 72%);animation:flick 4.7s ease-in-out infinite}
.flash{position:absolute;inset:0;background:radial-gradient(70% 55% at 30% 26%, #cfe0f4, rgba(150,180,215,.35) 55%, transparent 78%);opacity:calc(.17*var(--flashA))}
.night{position:absolute;inset:0;background:#04050a;opacity:calc(.88*var(--nightA))}
.vig{position:absolute;inset:0;background:radial-gradient(130% 90% at 50% 42%, transparent 42%, rgba(3,4,7,.62) 100%)}
@keyframes breathe{0%,100%{opacity:.88}50%{opacity:1}}
@keyframes flick{0%,100%{opacity:.72}18%{opacity:1}34%{opacity:.66}52%{opacity:.95}70%{opacity:.74}86%{opacity:1}}
html.paused .lampglow i,html.paused .fireglow i,html.rm .lampglow i,html.rm .fireglow i{animation:none}

.prog{position:fixed;top:0;left:0;height:2px;width:100%;background:var(--lamp);transform:scaleX(0);transform-origin:0 50%;z-index:45;pointer-events:none;transition:background .9s ease}

/* nav */
.nav{position:fixed;top:0;left:0;right:0;z-index:40;padding:.6rem var(--pad);background:rgba(7,9,12,.86);border-bottom:1px solid rgba(190,205,225,.07);-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px)}
.navin{display:flex;align-items:center;justify-content:space-between;gap:1rem;max-width:1420px;margin:0 auto}
.brand{display:inline-flex;align-items:center;gap:.55rem;min-height:38px;font-size:.76rem;letter-spacing:.3em;font-weight:600;color:var(--ink)}
.brand .logo-b{width:26px;height:26px;flex:none}
.navlinks{display:flex;gap:.1rem}
.navlinks a{font-size:.7rem;letter-spacing:.2em;text-transform:uppercase;color:var(--dim);padding:.65em .7em;min-height:26px;border-radius:999px;transition:color .3s}
.navlinks a:hover{color:var(--ink)}
@media (max-width:430px){
  .nav{padding-left:.7rem;padding-right:.7rem}
  .brand{font-size:.62rem;letter-spacing:.18em;gap:.35rem}
  .brand .logo-b{width:22px;height:22px}
  .navlinks a{font-size:.6rem;letter-spacing:.08em;padding:.65em .38em}
}
/* Get Merch joined the nav 2026-10-09 (five links). Measured: with the wordmark the row overflows up to ~540px
   and links broke inside, so up to 600px the wordmark beside the logo is visually hidden (the brand link keeps
   its name) and the links use the compact size above; below 360px tighter tracking and no gap. A nav link never
   breaks inside itself. */
.navlinks a{white-space:nowrap}
@media (max-width:600px){.brand > span{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0}.navlinks a{font-size:.6rem;letter-spacing:.08em;padding:.65em .38em}}
@media (max-width:359px){.navlinks{gap:0}.navlinks a{letter-spacing:.03em;padding:.65em .26em}}

/* motion chip */
.motion{position:fixed;right:12px;bottom:12px;z-index:42;font:inherit;font-size:.68rem;letter-spacing:.14em;text-transform:uppercase;color:var(--dim);background:rgba(7,9,12,.88);border:1px solid rgba(190,205,225,.5);border-radius:999px;padding:.7em 1.05em;min-height:36px;cursor:pointer;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px)}
.motion::before{content:"";display:inline-block;width:8px;height:8px;border-radius:50%;background:var(--dim);margin-right:.55em;vertical-align:1px}
.motion[aria-pressed="true"]{color:var(--ink);border-color:var(--ink)}
.motion[aria-pressed="true"]::before{background:var(--ink)}
html.rm .motion{display:none}
/* Fix 2: the phone Listen chip. It reuses .motion (same scrim, same border, same 36 px target,
   same aria-pressed styling) and only swaps sides. Hardware volume is the volume control here,
   so there is no slider. .listen-note is the honest note: permanent aria-describedby text that is
   shown for 5 s on each activation (cabin.js flashNote) on the same PANE scrim every other
   text block on the wall carries. Both go at >= 900 px, where .wctl comes back.
   NOTE the class is .listen, NOT .snd: .snd is already the seven sound lists. */
.listen{left:12px;right:auto}
.listen-note{position:fixed;left:12px;bottom:56px;z-index:42;max-width:min(24rem,calc(100% - 24px));font-size:.66rem;line-height:1.45;color:var(--dim);background:var(--pane);border:1px solid var(--line);border-radius:10px;padding:.5em .7em;pointer-events:none;opacity:0;transform:translateY(4px);transition:opacity .45s ease,transform .45s var(--ease)}
.listen-note.on{opacity:1;transform:none}
html.rm .listen,html.rm .listen-note,html:not(.js) .listen,html:not(.js) .listen-note{display:none}

/* stage: the window column and the copy column */
main{position:relative;z-index:1}
.stage{position:relative;display:block}
.win{position:relative;height:var(--winh);z-index:3;overflow:hidden}
html.enh .win{background:var(--wall);box-shadow:0 20px 42px -6px rgba(3,4,7,.92)}
.winwall{display:none;position:absolute;inset:0;z-index:0;background:
  radial-gradient(120% 80% at 50% 118%, rgba(24,20,17,.55), transparent 62%),
  linear-gradient(180deg,#06070a 0%, var(--wall) 40%, #0c0e11 86%, #0a0c0f 100%)}
html.enh .winwall{display:block}
.winwall i{position:absolute;inset:0;display:block}
.wl-lamp{opacity:calc(.16*var(--lampA));background:radial-gradient(56% 46% at 12% 98%, rgba(var(--lampw),1), transparent 72%);transition:background .9s ease}
.wl-fire{opacity:calc(.13*var(--fireA));background:radial-gradient(58% 48% at 6% 110%, #d0763c, transparent 74%)}
.wl-flash{opacity:calc(.17*var(--flashA));background:radial-gradient(76% 60% at 40% 22%, #cfe0f4, rgba(150,180,215,.35) 55%, transparent 80%)}
.wl-night{opacity:calc(.88*var(--nightA));background:#04050a}
/* Fix 3: the window is sticky ONLY at >= 900 px (see the media query). On a phone it scrolls
   away with the hero and .band below takes over as the sticky furniture. */
html.rm .win{height:62svh;position:relative}
.winin{position:absolute;inset:0}

.sash{position:absolute;left:calc(var(--apx) - 15px);right:calc(var(--apx) - 15px);top:calc(var(--apt) - 15px);bottom:calc(var(--apb) - 15px);border-radius:7px;
  background:linear-gradient(150deg,#2b2721 0%, #1b1915 42%, #24211b 68%, #15130f 100%);
  box-shadow:0 2px 0 rgba(255,236,210,.09) inset, 0 -2px 0 rgba(0,0,0,.5) inset, 0 26px 60px -22px rgba(0,0,0,.8)}
.sash::after{content:"";position:absolute;inset:0;border-radius:7px;background:linear-gradient(140deg,rgba(var(--lampw),.2),transparent 52%);transition:background .9s ease}
.ap{position:absolute;left:var(--apx);right:var(--apx);top:var(--apt);bottom:var(--apb);border-radius:3px;overflow:hidden;
  background:linear-gradient(180deg,#101b27 0%,#16232f 46%,#0d151d 62%,#080c12 100%);
  box-shadow:0 0 26px 6px rgba(0,0,0,.55) inset}
.ap canvas{position:absolute;inset:0;width:100%;height:100%}
#out{z-index:1;pointer-events:none}
#fog{z-index:2;touch-action:auto;-webkit-tap-highlight-color:transparent;-webkit-user-select:none;user-select:none;cursor:crosshair}
html.rm #out,html.rm #fog,html.rm #room{display:none}
.mull{position:absolute;z-index:3;pointer-events:none;background:linear-gradient(180deg,#2a2620,#171512)}
.mull-v{left:calc(50% - 5px);top:0;bottom:0;width:10px;background:linear-gradient(90deg,#332e26,#191612 55%,#0f0d0b);box-shadow:0 0 16px 3px rgba(0,0,0,.5)}
.mull-h{left:0;right:0;top:41%;height:10px;box-shadow:0 0 16px 3px rgba(0,0,0,.5)}
.glare{position:absolute;inset:0;z-index:4;pointer-events:none;background:
  linear-gradient(118deg, rgba(214,230,246,.055) 0%, transparent 26%, transparent 74%, rgba(214,230,246,.03) 100%),
  radial-gradient(64% 42% at 12% 96%, rgba(var(--lampw),.16), transparent 72%);transition:background .9s ease}
.ap::after{content:"";position:absolute;inset:0;z-index:5;pointer-events:none;box-shadow:0 0 40px 14px rgba(0,0,0,.5) inset}

.sill{position:absolute;z-index:4;left:calc(var(--apx) - 20px);right:calc(var(--apx) - 20px);bottom:calc(var(--apb) - 24px);height:24px;border-radius:3px 3px 5px 5px;
  background:linear-gradient(180deg,#3a342b 0 3px,#241f19 3px,#181511 70%,#100e0c 100%);
  box-shadow:0 20px 44px -18px rgba(0,0,0,.85)}
.sill::before{content:"";position:absolute;left:0;right:0;top:0;height:3px;background:linear-gradient(90deg,rgba(var(--lampw),.55),rgba(255,240,220,.16) 60%,transparent);transition:background .9s ease}
#room{position:absolute;inset:0;z-index:6;pointer-events:none}

.winfoot{position:absolute;z-index:7;left:calc(var(--apx) - 12px);right:calc(var(--apx) - 12px);bottom:10px;display:flex;flex-direction:column;gap:.5rem}
.sillrow{display:none;align-items:center;justify-content:space-between;gap:.8rem;pointer-events:none}
.sillmark{display:inline-flex;align-items:center;gap:.5rem;font-family:var(--serif);font-weight:300;font-size:clamp(1.15rem,4.6vw,1.7rem);letter-spacing:.34em;color:var(--ink);opacity:1;transition:opacity .7s ease;background:var(--pane);border-radius:14px;padding:.3em .5em .3em .55em;box-shadow:0 0 34px 18px var(--pane)}
.sillmark .logo-b{width:22px;height:22px;flex:none}
.sillcap{font-size:.6rem;letter-spacing:.34em;text-transform:uppercase;color:var(--lamp);opacity:0;transition:opacity .5s ease,color .9s ease;text-align:right;white-space:nowrap;background:var(--pane);border-radius:999px;padding:.5em .85em;box-shadow:0 0 26px 12px var(--pane)}

/* Fix 3 (phones, < 900 px): the sticky sill band. The window is no longer pinned, so this row
   of furniture moves OUT of it and becomes a real element in the flow (a direct child of
   .stage, immediately after .win). It starts life as the bottom edge of the window, rides up
   with it, and then sticks under the fixed nav at top:var(--navh) until .stage ends at the
   footer. --navh is the nav's own height, so the band tucks a fraction of a pixel under it and
   no page content can ever show between the two. display:none at >= 900 px, where .sillrow
   inside the sticky window does this job and the stage grid must keep exactly two items.
   Surface: the PANE scrim plus the nav's own blur, so copy passing underneath reads as a wash
   and never as text, and a hairline at the chip border alpha, because the band is the one
   surface on this page that has to read as an edge over moving content. The caption is the
   same cross-fading chapter marker as .sillcap and carries var(--lamp), so a lampshade tap
   still retints the band. Wordmark, caption and hairline are all in tools/contrast.py, in all
   six lamp themes, on the worst-case wall AND against the pure-white glass bound. */
.band{position:sticky;top:var(--navh);z-index:6;height:var(--bandh);display:flex;align-items:center;justify-content:space-between;gap:.8rem;overflow:hidden;padding:0 var(--pad);background:var(--pane);border-bottom:1px solid rgba(190,205,225,.5);-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px)}
.bandmark{display:inline-flex;align-items:center;gap:.45rem;font-family:var(--serif);font-weight:300;font-size:1rem;line-height:1;letter-spacing:.3em;color:var(--ink);white-space:nowrap}
.bandmark .logo-b{width:18px;height:18px;flex:none}
.bandcap{font-size:.6rem;letter-spacing:.34em;text-transform:uppercase;color:var(--lamp);opacity:0;transition:opacity .5s ease,color .9s ease;text-align:right;white-space:nowrap}
html.rm .band,html:not(.js) .band{position:static}
/* after .band, so this wins over its padding shorthand: the band keeps the same side gutter
   as the nav above it at every phone width. */
@media (max-width:430px){.band{padding-left:.7rem;padding-right:.7rem}}

.nstand{position:absolute;z-index:5;right:calc(var(--apx) + 6px);bottom:calc(var(--apb) - 6px);width:58px;height:110px;border-radius:11px;padding:5px;
  background:linear-gradient(160deg,#22262b,#0d0f12);border:1px solid rgba(190,205,225,.16);
  box-shadow:0 12px 28px -10px rgba(0,0,0,.9);transform:rotate(-3deg) translateY(6px);opacity:0;transition:opacity .8s ease,transform .8s var(--ease)}
.nstand.on{opacity:1;transform:rotate(-3deg) translateY(0)}
.ns-screen{position:relative;width:100%;height:100%;border-radius:7px;background:#07080a;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:5px;overflow:hidden}
.ns-time{font-family:var(--serif);font-weight:300;font-size:19px;letter-spacing:.02em;color:var(--dim);line-height:1}
.ns-dots{display:flex;gap:4px}
.ns-dots i{width:4px;height:4px;border-radius:50%;background:var(--lamp);opacity:.55;animation:blip 2.6s ease-in-out infinite}
.ns-dots i:nth-child(2){animation-delay:.5s}
.ns-dots i:nth-child(3){animation-delay:1s}
@keyframes blip{0%,100%{opacity:.3}50%{opacity:.9}}
html.paused .ns-dots i,html.rm .ns-dots i{animation:none}
.ring{position:absolute;left:50%;top:50%;width:calc(100% - 14px);height:auto;transform:translate(-50%,-50%);opacity:.8}
.ring circle{fill:none;stroke-width:1.6}
.ring .rbg{stroke:rgba(190,205,225,.14)}
.ring .rfg{stroke:var(--lamp);stroke-linecap:round;transform:rotate(-90deg);transform-origin:50% 50%;transition:stroke-dashoffset .35s linear,stroke .9s ease}

/* Fix 3 (phones): the nightstand's inline twin. On a phone the window has scrolled away long
   before the falling-asleep chapter, so the clock face and the sleep-timer ring are repeated
   in the copy, where the closing moment actually lands. It reuses the nightstand's own
   .ns-screen / .ns-time / .ns-dots / .ring parts, so the lamp theme retints it too; it carries
   no copy, it is aria-hidden decoration, and it is display:none at >= 900 px, where the real
   nightstand is still on screen inside the sticky window. One setInterval in cabin.js drives
   both clock faces and one line in setTargets drives both rings. */
.nsin{display:flex;justify-content:center;margin-top:1.8rem}
.nsin-ph{width:104px;height:196px;border-radius:18px;padding:8px;background:linear-gradient(160deg,#22262b,#0d0f12);border:1px solid rgba(190,205,225,.16);box-shadow:0 14px 32px -12px rgba(0,0,0,.9)}
.nsin .ns-screen{border-radius:12px;gap:8px}
.nsin .ns-time{font-size:28px}
.nsin .ns-dots{gap:5px}
.nsin .ns-dots i{width:5px;height:5px}
.nsin .ring{width:calc(100% - 24px)}

.wctl{display:none;flex-wrap:wrap;align-items:center;gap:.45rem}
.chip{font:inherit;font-size:.68rem;letter-spacing:.13em;text-transform:uppercase;color:var(--ink);background:rgba(7,9,12,.86);border:1px solid rgba(190,205,225,.5);border-radius:999px;padding:.62em 1em;min-height:36px;cursor:pointer;transition:border-color .3s,color .3s}
.chip:hover{border-color:var(--ink)}
.chip[aria-pressed="true"]{border-color:var(--lamp);color:var(--lamp)}
.vol{display:inline-flex;align-items:center;gap:.45rem;color:var(--dim);background:rgba(7,9,12,.86);border:1px solid rgba(190,205,225,.24);border-radius:999px;padding:.3em .8em;min-height:36px}
.vol input{width:74px;height:24px;accent-color:var(--lamp);background:transparent}
.vol .volic{width:16px;height:16px;flex:none}
.vol[hidden]{display:none}
.vhide{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}
.aunote{flex:1 1 100%;font-size:.66rem;line-height:1.45;color:var(--faint);background:rgba(7,9,12,.8);border-radius:8px;padding:.25em .5em;max-width:44ch}
html.rm .wctl,html.rm .nstand,html.rm .sillcap{display:none}
html:not(.js) .wctl{display:none}
html.rm .win{background:transparent;box-shadow:none}
html.rm .winwall{display:none}

/* copy column */
.copy{position:relative;z-index:2;padding:0 var(--pad)}
.grp{display:block}
.ch{position:relative;min-height:76svh;padding:9svh 0 7svh;display:flex;align-items:center}
.chin{position:relative;width:100%;max-width:36rem}
.ch-hero{min-height:82svh;padding-top:6svh}
/* Fix 3: the two fixed chips own the bottom 48 px of a phone screen (12px up, a 36px target),
   so the hero pane keeps 64 px of padding under its last line and nothing it says can end up
   behind them. Put back to the desktop value in the >= 900 px block, where there are no fixed
   chips and the pane is untouched. */
.ch-hero .pane{padding-bottom:calc(1.5rem + 64px)}
.ch-sleep{min-height:170svh;align-items:flex-start;padding-top:12svh}
.pane{position:relative;background:var(--pane);border-radius:24px;padding:1.35rem .9rem 1.5rem;margin:0 -.55rem;box-shadow:0 0 80px 46px var(--pane)}

.kick{font-size:.66rem;letter-spacing:.34em;text-transform:uppercase;color:var(--lamp);display:flex;gap:.8rem;align-items:center;transition:color .9s ease}
.kick::before{content:"";width:2rem;height:1px;background:currentColor;opacity:.65;flex:none}
h1{font-family:var(--serif);font-weight:300;font-size:clamp(2.1rem,7.4vw,3.6rem);line-height:1.08;letter-spacing:-.015em;color:var(--ink);margin:1rem 0 1.1rem;text-wrap:balance}
h2{font-family:var(--serif);font-weight:300;font-size:clamp(1.75rem,5.8vw,2.85rem);line-height:1.1;letter-spacing:-.012em;color:var(--ink);margin:.85rem 0 .9rem;text-wrap:balance}
.sub{color:var(--dim);max-width:46ch;font-size:clamp(1rem,3.2vw,1.12rem);line-height:1.62}
.lines .ln{display:block}
.wm{font-family:var(--serif);font-weight:300;font-size:clamp(2.4rem,11vw,4.2rem);letter-spacing:.2em;color:var(--ink);line-height:1}

.pillars{list-style:none;display:flex;flex-wrap:wrap;gap:.5rem;margin:1.6rem 0 0}
.pillars li{font-size:.72rem;letter-spacing:.12em;text-transform:uppercase;color:var(--dim);border:1px solid var(--line);background:rgba(10,13,17,.7);border-radius:999px;padding:.5em 1em}
.store-row{display:flex;align-items:center;flex-wrap:wrap;gap:1rem;margin-top:1.6rem}
.play-badge{height:74px;width:auto;margin:2px 0}
.playlink{display:inline-block;border-radius:12px;transition:transform .4s var(--ease)}
.app-store-badge{display:block;height:50px;width:auto;margin:4px 0}
@media (hover:hover) and (pointer:fine){.playlink:hover{transform:translateY(-3px)}}
.scrollhint{margin-top:2rem;font-size:.6rem;letter-spacing:.34em;text-transform:uppercase;color:var(--faint)}
.scrollhint i{display:block;width:1px;height:34px;margin-top:.6rem;background:linear-gradient(var(--faint),transparent);transform-origin:top;animation:bob 3s ease-in-out infinite}
@keyframes bob{0%,100%{transform:scaleY(.45)}50%{transform:scaleY(1)}}
html.paused .scrollhint i,html.rm .scrollhint i{animation:none}

/* shelves: a field guide */
.shelf{margin-top:1.4rem;border-top:1px solid var(--line);padding-top:1.1rem}
.shelf h3{display:flex;align-items:center;gap:.6rem;font-family:var(--sans);font-size:.72rem;font-weight:600;letter-spacing:.26em;color:var(--lamp);margin-bottom:.9rem;transition:color .9s ease}
.shelf h3 svg{width:19px;height:19px;flex:none;opacity:.95}
.snd{list-style:none;display:grid;grid-template-columns:repeat(auto-fill,minmax(190px,1fr));gap:0 1.6rem}
.snd li{display:flex;align-items:baseline;font-size:.97rem;color:var(--text);padding:.38em 0;line-height:1.4}
.snd li::after{content:"";order:2;flex:1 1 auto;min-width:1.2rem;border-bottom:1px dotted rgba(190,205,225,.22);margin:0 .5rem .3em}
.free{order:3;flex:none;font-size:.58rem;letter-spacing:.12em;color:var(--free);border:1px solid rgba(124,201,154,.42);border-radius:4px;padding:.15em .45em}

/* features */
.feats{list-style:none;margin-top:1.6rem;display:grid;gap:.7rem}
.feat{position:relative;background:linear-gradient(165deg,rgba(17,20,26,.9),rgba(8,10,14,.9));border:1px solid var(--line);border-radius:18px;padding:1.15rem 1.25rem}
.feat h3{font-family:var(--serif);font-weight:400;font-size:1.2rem;color:var(--ink);margin-bottom:.3rem;letter-spacing:-.005em}
.feat p{font-size:.92rem;color:var(--dim);line-height:1.58}
.feat .n{position:absolute;top:1.15rem;right:1.25rem;font-family:var(--serif);font-size:.9rem;color:var(--faint)}

/* pro */
.pro-card{margin-top:1.5rem;background:linear-gradient(165deg,rgba(21,26,34,.95),rgba(9,11,15,.95));border:1px solid rgba(168,150,94,.34);border-radius:24px;padding:1.7rem 1.5rem;box-shadow:0 32px 70px -40px rgba(0,0,0,.9)}
.price{font-family:var(--serif);font-weight:300;font-size:2.1rem;color:var(--ink);line-height:1.1}
.price small{display:block;font-family:var(--sans);font-size:.76rem;letter-spacing:.08em;color:var(--pro);margin-top:.3rem}
.pro-card ul{list-style:none;margin:1.1rem 0 .6rem}
.pro-card li{position:relative;padding:.35em 0 .35em 1.5rem;color:var(--text);font-size:.97rem}
.pro-card li::before{content:"";position:absolute;left:.15rem;top:.95em;width:.45rem;height:.45rem;border-left:1.5px solid var(--pro);border-bottom:1.5px solid var(--pro);transform:rotate(-45deg)}
.fine{color:var(--faint);font-size:.8rem;line-height:1.55;margin-top:.7rem}
.btn{display:inline-flex;align-items:center;justify-content:center;min-height:48px;padding:.8em 1.6em;border-radius:999px;font-weight:600;font-size:.95rem;color:#07090c;background:var(--ink);border:1px solid transparent;margin-top:.9rem;transition:transform .4s var(--ease),background .3s}
@media (hover:hover) and (pointer:fine){.btn:hover{transform:translateY(-2px);background:#fff}}

.lamps{border:0;margin-top:1.5rem;padding:1.1rem 1.15rem;background:rgba(9,11,15,.8);border:1px solid var(--line);border-radius:18px}
.lamps legend{font-size:.64rem;letter-spacing:.3em;text-transform:uppercase;color:var(--faint);padding:0 .4em;margin-left:-.4em}
.swatches{display:flex;flex-wrap:wrap;gap:.5rem;margin-top:.7rem}
.sw{position:relative;display:inline-flex;align-items:center;gap:.5rem;cursor:pointer;border:1px solid var(--line2);border-radius:999px;padding:.5em .95em .5em .6em;min-height:40px;font-size:.8rem;color:var(--dim);transition:border-color .3s,color .3s}
.sw input{position:absolute;opacity:0;width:1px;height:1px;margin:0}
.sw .dot{width:18px;height:18px;border-radius:50%;flex:none;box-shadow:0 0 12px 2px currentColor}
.sw:hover{color:var(--text);border-color:var(--line2)}
.sw:has(input:checked){border-color:currentColor;color:var(--ink);background:rgba(255,255,255,.04)}
.sw:focus-within{outline:2px solid transparent;box-shadow:0 0 0 3px rgba(5,7,11,.94),0 0 0 5px var(--ink),0 0 0 8px rgba(5,7,11,.94)}
.sw.is-on{border-color:currentColor;color:var(--ink);background:rgba(255,255,255,.04)}
.sw-classic{--c:#8fa7bd}.sw-ember{--c:#d8946e}.sw-pine{--c:#79b088}.sw-fathom{--c:#7fa6d4}.sw-heather{--c:#c49ab4}.sw-plum{--c:#ad93d9}
.sw .dot{background:var(--c);color:var(--c)}

/* mood row: the two owner marketing squares at the head of the 2 a.m. chapter, above the eight
   screenshots. ONE figure with ONE caption, because one honest line covers both pictures: the
   rooms and the person in them are illustrations and the phone screens are the real app. Static
   by construction: the desktop parallax selects '#shots .shots figure' and this figure is not
   inside .shots, so nothing here moves. Same 14 px frame, hairline and shadow as the
   screenshots, so it reads as part of the chapter. One column on a phone, where the pane is
   narrow and the art has to stay legible; two up from 900 px. */
.mood{margin-top:1.5rem}
.moodpair{display:grid;grid-template-columns:1fr;gap:.85rem}
.mood img{width:100%;height:auto;border-radius:14px;border:1px solid rgba(190,205,225,.18);box-shadow:0 18px 36px -16px rgba(0,0,0,.9)}
.mood figcaption{margin-top:.7rem;font-size:.76rem;line-height:1.4;color:var(--dim)}

/* shots */
.shots{list-style:none;margin-top:1.6rem;display:flex;gap:.85rem;overflow-x:auto;scroll-snap-type:x mandatory;padding:0 .9rem 1rem;margin-left:-.9rem;margin-right:-.9rem;-webkit-overflow-scrolling:touch;scrollbar-width:thin}
.shots figure{flex:0 0 auto;width:min(52vw,190px);scroll-snap-align:center;min-width:0}
.shots img{width:100%;height:auto;border-radius:14px;border:1px solid rgba(190,205,225,.18);box-shadow:0 18px 36px -16px rgba(0,0,0,.9)}
.shots figcaption{margin-top:.55rem;font-size:.76rem;line-height:1.4;color:var(--dim)}
.striphint{font-size:.62rem;letter-spacing:.28em;text-transform:uppercase;color:var(--faint);margin-top:.4rem}

/* video */
.promo{margin-top:1.5rem}
/* The real player from the live page: preload="none", so only the poster frame is fetched. It sits
   in a clipping frame, exactly as the concept's poster did, so the desktop scale settle (cabin.js,
   GSAP, >= 900 px) can never spill past the rounded corners or scale the border with it. */
.promo .frame{position:relative;display:block;border-radius:18px;overflow:hidden;border:1px solid var(--line);background:#05070a;box-shadow:0 26px 56px -28px rgba(0,0,0,.9)}
.promo video{width:100%;height:auto;aspect-ratio:16/9;background:#05070a}
.note{color:var(--faint);font-size:.82rem;line-height:1.55;margin-top:.8rem;display:flex;gap:.9rem;flex-wrap:wrap;align-items:baseline}
.note a{color:var(--lamp);transition:color .9s ease;display:inline-flex;align-items:center;min-height:24px}

/* privacy + download */
.privacy{margin-top:.4rem;background:rgba(11,14,19,.86);border:1px solid var(--line);border-radius:20px;padding:1.4rem 1.35rem}
.privacy strong{color:var(--lamp);transition:color .9s ease}
.privacy p{font-size:.97rem;color:var(--text)}
.privacy .fine{margin-top:.7rem}
.privacy .fine a{color:var(--lamp);transition:color .9s ease}
.dl{margin-top:1.4rem;background:rgba(11,14,19,.86);border:1px solid var(--line);border-radius:20px;padding:1.5rem 1.35rem}
.dl .note{color:var(--dim);display:block}

/* footer */
.foot{position:relative;z-index:2;background:var(--pane);box-shadow:0 0 46px 26px var(--pane);border-top:1px solid var(--line);margin-top:6svh;padding:2rem var(--pad) 7rem;display:flex;flex-wrap:wrap;gap:1rem 1.6rem;justify-content:space-between;color:var(--faint);font-size:.8rem;max-width:1420px;margin-left:auto;margin-right:auto}
.foot a{color:var(--dim);margin-right:1rem}
.foot div:first-child a{display:inline-flex;align-items:center;min-height:24px}
.foot a:hover{color:var(--ink)}
.foot div:last-child a{margin-right:0}
.legal-note{flex:1 1 100%;color:var(--faint);font-size:.74rem;line-height:1.5}

/* reveals: hidden state only when JS is present and motion is allowed */
html.js.enh:not(.failsafe) .rv{opacity:0;transform:translateY(22px);transition:opacity .9s ease,transform .9s var(--ease);transition-delay:var(--d,0s)}
html.js.enh:not(.failsafe) .rv.on{opacity:1;transform:none}
html.js.enh:not(.failsafe) .lines .ln{opacity:0;transform:translateY(.5em);transition:opacity .95s ease,transform 1.05s var(--ease);transition-delay:calc(var(--i,0)*.14s + .1s)}
html.js.enh:not(.failsafe) .lines.on .ln{opacity:1;transform:none}
.d1{--d:.09s}.d2{--d:.18s}.d3{--d:.27s}.d4{--d:.36s}.d5{--d:.45s}

/* desktop: window on the left, copy on the right */
@media (min-width:900px){
  :root{--apx:11%;--apt:13%;--apb:26%;--winh:100svh;--navh:58px}
  .stage{display:grid;grid-template-columns:minmax(0,55fr) minmax(0,45fr);align-items:start;max-width:1680px;margin:0 auto}
  html.enh .win{position:sticky;align-self:start;top:0}
  .copy{padding:0 clamp(1.6rem,3.4vw,3.4rem) 0 clamp(1.2rem,2.4vw,2.4rem)}
  .ch{min-height:100svh;padding:12svh 0}
  .ch-hero{min-height:100svh;padding-top:14svh}
  .ch-hero .pane{padding-bottom:1.6rem}
  .ch-sleep{min-height:200svh;padding-top:16svh}
  .chin{max-width:33rem}
  .sash{left:calc(var(--apx) - 17px);right:calc(var(--apx) - 17px);top:calc(var(--apt) - 17px);bottom:calc(var(--apb) - 17px)}
  .sill{left:calc(var(--apx) - 38px);right:calc(var(--apx) - 38px);bottom:calc(var(--apb) - 30px);height:28px}
  .winfoot{left:calc(var(--apx) - 34px);right:calc(var(--apx) - 34px);bottom:5svh;gap:1.3rem}
  html.enh .win{background:transparent;box-shadow:none}
  html.enh .winwall{display:none}
  .vol input{width:92px}
  .sillcap{font-size:.66rem}
  .nstand{width:78px;height:148px;right:calc(var(--apx) + 16px);bottom:calc(var(--apb) - 8px)}
  .ns-time{font-size:25px}
  .wctl{display:flex;gap:.55rem}
  .sillrow{display:flex}
  .band,.nsin{display:none}
  .listen,.listen-note{display:none}
  .aunote{font-size:.7rem}
  [id]{scroll-margin-top:calc(var(--navh) + 14px)}
  .pane{padding:1.5rem 1.25rem 1.6rem;margin:0 -1.25rem;border-radius:26px}
  .mull-h{top:38%}
  .moodpair{grid-template-columns:repeat(2,1fr);gap:1.1rem}
  .shots{display:grid;grid-template-columns:repeat(2,1fr);gap:1.4rem 1.1rem;overflow:visible;scroll-snap-type:none;margin-left:0;margin-right:0;padding-left:0;padding-right:0}
  .shots figure{width:auto}
  .shots{padding:0;margin-left:0;margin-right:0}
  .striphint{display:none}
  .snd{grid-template-columns:repeat(auto-fill,minmax(min(100%,200px),1fr))}
  .feats{gap:.8rem}
  .foot{padding-bottom:3rem}
}
@media (min-width:1280px){
  :root{--apx:13%;--apt:14%}
  .chin{max-width:35rem}
}

/* reduced motion: nothing pinned, nothing scrubbed, nothing hidden, canvases off */
html.rm .stage{display:block;max-width:52rem;margin:0 auto}
html.rm .copy{padding:0 var(--pad)}
html.rm .ch,html.rm .ch-hero,html.rm .ch-sleep{min-height:0;padding:5svh 0;display:block}
html.rm .win{max-width:52rem;margin:0 auto;height:56svh}
html.rm .ap{background:linear-gradient(180deg,#0f1a26 0%,#1a2a3a 40%,#101a24 58%,#070b10 100%)}
html.rm .ap::before{content:"";position:absolute;left:66%;top:16%;width:54px;height:54px;border-radius:50%;background:radial-gradient(circle,#e8edf2 38%,rgba(232,237,242,.16) 52%,transparent 72%)}
html.rm .chin{max-width:none}
html.rm .pane,html.rm .foot{box-shadow:none}
html.rm .prog{display:none}
html.rm [id]{scroll-margin-top:1rem}
html.failsafe .rv,html.failsafe .lines .ln{opacity:1;transform:none}

/* CARDS CHAPTER (site refresh wave 2026-09-27, cabin.css ?v=156). "12 . At a glance": the three
   owner-approved store story cards (shots/cards/*.webp at 540w and 1080w, made by the fleet site
   kit from the 2026-09-21 Play frames), hung as a quiet gallery between the falling-asleep chapter
   and #download. Each card already carries its headline and sub in the picture, so the page never
   repeats them as text (the alt carries them) and ONE honest caption sits under the set. Every
   class here starts with "cards" and collides with nothing, and nothing here is ever display:none
   (the checks.py hidden-class guard).
   PHONE (< 900 px): an ordinary chapter pane. The cards are a horizontal scroll-snap strip,
   min(80vw,340px) each, with the next one peeking in; the strip is a focusable, labelled region
   (tabindex 0, role region, aria-label) so a keyboard can scroll it, and the global focus ring
   draws on it. The three cards rise in turn when the strip is revealed, under the usual .rv
   guards: hidden only under html.js.enh without failsafe, so reduced motion and no-JS see them.
   DESKTOP (>= 900 px): the copy column holds 560 px, too narrow for three cards of 320 px, so this
   ONE chapter becomes a full-bleed band across the stage: its box is widened by percentage
   arithmetic on the copy column (the calc note below), and its pane drops its corners and side
   margin, so the scrim runs edge to edge with the usual feathered shadow above and below. That
   scrim is the denser GALLERY surface, rgba(7,9,12,.96) for the pane and its feather alike, so the
   window's glass, sill caption and chips never ghost through the band as it crosses them.
   The band has to paint OVER the sticky window (.win, z-index 3), so at >= 900 px .copy stops
   being a stacking context (z-index:auto) and this chapter alone takes z-index 4; every other
   chapter still sets no z-index and still paints under the window, exactly as before. Because
   the band crosses the aperture, its text is proven in tools/contrast.py against the pure-white
   glass bound as well as the worst-case wall. From 1200 px the heading sits left of the cards
   like a gallery label, so the whole set fits one 1440x900 screen. The lamp-tinted rail above
   the cards is the sill highlight again: decoration, no information. */
.cards-head{max-width:36rem}
.cards-strip{display:flex;gap:.9rem;margin:1.4rem -.9rem 0;padding:.35rem .9rem 1rem;overflow-x:auto;scroll-snap-type:x mandatory;scroll-padding-inline:.9rem;overscroll-behavior-x:contain;-webkit-overflow-scrolling:touch;scrollbar-width:thin}
.cards-fig{flex:0 0 auto;width:min(80vw,340px);scroll-snap-align:start}
.cards-fig img{width:100%;height:auto;border-radius:14px;border:1px solid rgba(190,205,225,.18);box-shadow:0 18px 36px -16px rgba(0,0,0,.9)}
.cards-cap{margin-top:.3rem;font-size:.76rem;line-height:1.4;color:var(--dim)}
html.js.enh:not(.failsafe) .cards-strip.rv .cards-fig{opacity:0;transform:translateY(26px);transition:opacity .9s ease,transform 1s var(--ease);transition-delay:var(--d,0s)}
html.js.enh:not(.failsafe) .cards-strip.rv .cards-fig:nth-child(2){transition-delay:calc(var(--d,0s) + .14s)}
html.js.enh:not(.failsafe) .cards-strip.rv .cards-fig:nth-child(3){transition-delay:calc(var(--d,0s) + .28s)}
html.js.enh:not(.failsafe) .cards-strip.rv.on .cards-fig{opacity:1;transform:none}
@media (min-width:900px){
  .copy{z-index:auto}
  /* The stage grid is minmax(0,55fr) minmax(0,45fr) with no gap, so the copy column is exactly .45
     of the stage width S, and a percentage on this box is of the copy column's CONTENT width
     C = .45 S - PL - PR, PL and PR being the column's own left and right padding (repeated verbatim
     from the .copy rule in the block above). So S = (C + PL + PR) / .45 = 222.22222% + 2.22222 (PL + PR),
     and the stage's left edge lies .55 S + PL to the left of where this box would start. */
  .cards{z-index:4;min-height:0;padding:12svh 0;width:calc(222.22222% + 2.22222*(clamp(1.2rem,2.4vw,2.4rem) + clamp(1.6rem,3.4vw,3.4rem)));margin-left:calc(-122.22222% - 1.22222*(clamp(1.2rem,2.4vw,2.4rem) + clamp(1.6rem,3.4vw,3.4rem)) - clamp(1.2rem,2.4vw,2.4rem))}
  .cards-in{max-width:none;margin:0;border-radius:0;background:rgba(7,9,12,.96);box-shadow:0 0 80px 46px rgba(7,9,12,.96);padding:3rem max(var(--pad),calc((100% - 86rem)/2));display:grid;grid-template-columns:minmax(0,1fr);gap:1.6rem 2.5rem;align-items:center}
  .cards-wall::before{content:"";display:block;height:1px;margin-bottom:1.4rem;background:linear-gradient(90deg,rgba(var(--lampw),.55),rgba(255,240,220,.16) 60%,transparent);transition:background .9s ease}
  .cards-strip{display:grid;grid-template-columns:repeat(3,minmax(0,340px));gap:1.5rem;margin:0;padding:0;overflow:visible;scroll-snap-type:none}
  .cards-fig{width:auto}
  .cards-cap{margin-top:1.1rem}
  /* reduced motion: the stage is one centred 52rem column with even gutters, so the band is simply
     as wide as the viewport, centred on that column (html and body clip any scrollbar overhang). */
  html.rm .cards{width:100vw;margin-left:calc(50% - 50vw)}
}
@media (min-width:1200px){
  .cards-in{grid-template-columns:minmax(0,16rem) minmax(0,1fr)}
}
/* AXE FIXES (site refresh 2026-09-27, WCAG 1.4.1 link-in-text-block): the two links that sit INSIDE a run of
   text were told apart by colour alone (1.68:1 and 1.41:1 against the words around them). Each is now
   underlined in its own colour, so the underline carries the text pair's own contrast (tools/contrast.py).
   The footer's link row and every standalone link keep their look. */
.privacy .fine a,.foot div:nth-child(2) a{text-decoration:underline;text-decoration-thickness:1px;text-underline-offset:.2em}

/* PHONE BADGE ROW (owner, 2026-09-27, cabin.css ?v=157). Below 900 px the window scene comes first, so the hero
   store row sat under the first viewport (top 902 of 740 at 360x740, 1008 of 915 at 412x915). This compact strip
   is the first thing in <main> on phones: Play + App Store, the same links as the two rows below, at 64 / 43 px
   (60 / 40 under 375 px) so the visible black plates match (Play x 168/250) and both sit on one line from 320 px
   up. It carries its own PANE scrim, has no reveal (visible at load), and tucks its padding under the fixed nav
   (padding-top = --navh + 4 px). margin-bottom:-48px lets the window's top WALL slide under it (z-index 4 over
   .win's 3): the strip ends 11 px above the sash, never over the glass or any canvas, and the whole aperture is
   still on screen (geom.py INVARIANT 12). The in-pane hero row was kept at first; since ?v=158 it is hidden on phones
   (PHONE ROW FIX 2 below). The strip carries no text; its only non-text pair is the focus ring on PANE
   (tools/contrast.py). Hidden at >= 900 px, where the desktop hero row is in the first viewport. */
.getrow{position:relative;z-index:4;background:var(--pane);border-bottom:1px solid var(--line);padding:calc(var(--navh) + 4px) .7rem 6px;margin-bottom:-48px}
.getrow-in{display:flex;justify-content:center;align-items:center;flex-wrap:wrap;gap:.25rem 1rem}
.getrow .play-badge{height:64px}
.getrow .app-store-badge{height:43px}
@media (max-width:374px){.getrow .play-badge{height:60px}.getrow .app-store-badge{height:40px}}
@media (min-width:900px){.getrow{display:none}}

/* PHONE ROW FIX 2 (owner, 2026-09-27, cabin.css ?v=158). One flick below the strip above, a phone met the
   in-pane hero row as well: the same two badges again, too wide for the pane (Play 191 + gap 16 + App Store
   150 = 357 px in a 249-346 px pane), so they wrapped into a LEFT-aligned stack whose Play plate sat 12 px
   right of the Apple plate (the Play PNG carries a transparent border, 41/250 of its height). Below 900 px
   that row is now hidden, so a phone sees exactly two store rows: the strip at the top and #download at the
   bottom. The markup is unchanged (it is still the desktop hero row, data-store="hero"); >= 900 px shows it
   exactly as before. Both phone rows now centre OPTICALLY: the Play link's negative inline margins eat the
   PNG's transparent sides (10.5 px at 64 px, 9.8 px at 60 px), so the 1rem flex gap IS the visible gap and
   the two outer margins are equal. #download takes the strip's sizes (64/43, 60/40 under 375 px), centred,
   and borrows 1rem of the .dl card's side padding (its 1.6rem top margin goes too, so the card's padding frames
   the badges evenly): one line from 360 px up, stacked and centred at 320 px.
   At >= 900 px the #download row is as before (74/50, left, 1rem gap). geom.py INVARIANT 13 proves the
   widths; the hidden rule is a NAMED selector in checks.py's hidden-class guard. */
.ch-hero [data-store="hero"]{display:none}
.getrow .playlink,.dl .playlink{margin-left:-10.5px;margin-right:-10.5px}
.dl .store-row{justify-content:center;gap:.25rem 1rem;margin:0 -1rem}
.dl .play-badge{height:64px}
.dl .app-store-badge{height:43px}
@media (max-width:374px){.getrow .playlink,.dl .playlink{margin-left:-9.8px;margin-right:-9.8px}.dl .play-badge{height:60px}.dl .app-store-badge{height:40px}}
@media (min-width:900px){.ch-hero [data-store="hero"]{display:flex}.dl .store-row{justify-content:normal;gap:1rem;margin:1.6rem 0 0}.dl .playlink{margin-left:0;margin-right:0}.dl .play-badge{height:74px}.dl .app-store-badge{height:50px}}
