/* ============================================================
   RB9 — DARK SYSTEM page kit (system-pages/)
   Base: dark-system-v2.html (amber phosphor, legibility pass:
   readable 13-14px payload over tiny 9-10px caps-mono chrome).
   Extended with the components the inner pages need: backdrop,
   film head, scoreband, stat band, profile tabs, badge cards,
   forms, settings layout, floor grid, ticket stubs.
   Content pages use real portal structure and copy; posters are
   real TMDB art behind a neutral "system screen" treatment.
   ============================================================ */

/* Text on the accent ground is var(--bg), never a hardcoded black. Every
   one of these sat on var(--amber) with color:#000, which assumes amber
   is bright - true for the seven dark themes, false for receipt, where
   amber is a dark brown and black on it measures 3.29. var(--bg) is
   near-black on dark (visually identical to what shipped) and paper on
   light, so the pairing follows the theme instead of assuming it. Found
   when profile's tab row got an active state; it was already wrong on
   the nav, the settings rail, the booth tabs and ::selection. */

:root{
  --bg:#0e0f0d;
  --panel:#171814;
  --inset:#101110;
  --line:#3a3c33;
  --line-hi:#565948;
  --hi:#efecdc;
  --txt:#cdcaba;
  --dim:#8f8d7e;
  --amber:#e8a33d;
  --amber-dim:#a87f35;
  --green:#7fd460;
  --red:#e8604d;
  --px:"Dotemp",monospace;       /* D - display numbers, avatars */
  --t1:"Dotemp",monospace;       /* T1 - big movie titles */
  --dot:"Dotemp",monospace;      /* H - T2 titles */
  --sys:"IBM Plex Mono",Consolas,monospace; /* B - body copy */
  --mono:"IBM Plex Mono",Consolas,monospace; /* M - chrome + readouts */
  --fs-body:13px;
  --fs-title:14px;
  --fs-cap:11px;
  /* The mono chrome below --fs-cap, tokenised (spec 35 M4). These two
     tiers were 52 hard-coded 9px and 10px declarations - readouts,
     kickers, card feet, timestamps, rarity lines. On a desktop at
     arm's length that texture is the point; on a phone it is the
     dominant type on the page. Named so the touch scale can lift the
     whole tier in one place instead of chasing 52 selectors. */
  --fs-micro:9px;
  --fs-chrome:10px;
  --pad:14px;
  --row-pad:11px;
  --rack-gap:10px;
}
/* Any rule that sets `display` outranks the UA's [hidden] rule, so
   scripted hide/show silently stops hiding. styles.css has carried this
   guard since the redesign; the skin did not, and had been patching the
   symptom one selector at a time (.search-results, .watch-overlay, the
   unread pip). It was still missing for .poster-fallback - which every
   ported page uses - and for the home feed's tab filter. One rule. */
[hidden]{display:none !important}

body.compact{
  --fs-body:11px; --fs-title:11px; --fs-cap:10px;
  --pad:10px; --row-pad:7px; --rack-gap:8px;
}

*{box-sizing:border-box}
/* Reserve the scrollbar gutter on every page (2026-07-26): without this a
   page that fits the viewport is ~15px wider than one that scrolls, so the
   nav/header changed width page to page. Same fix the live portal uses. */
html{color-scheme:dark;scrollbar-gutter:stable}
body{
  margin:0;background:var(--bg);color:var(--txt);
  font-family:var(--sys);font-size:var(--fs-body);line-height:1.6;
  background-image:radial-gradient(rgba(255,255,255,.015) 1px, transparent 1px);
  background-size:3px 3px;
}
a{color:var(--amber);text-decoration:none}
a:hover{color:#fff;text-decoration:underline}
::selection{background:var(--amber);color:var(--bg)}
:focus-visible{outline:1px dashed var(--amber);outline-offset:2px}
img{max-width:100%}

.shell{max-width:960px;margin:0 auto;padding:14px 16px 40px}

/* ---------- panel ---------- */
.panel{border:1px solid var(--line);background:var(--panel);margin:0 0 12px}
.tbar{
  display:flex;justify-content:space-between;align-items:center;gap:10px;
  padding:6px 12px;border-bottom:1px solid var(--line);background:var(--inset);
}
.tbar .lbl{
  font-family:var(--mono);font-weight:600;font-size:12px;letter-spacing:1.5px;text-transform:uppercase;
  color:var(--hi);
}
.tbar .lbl::before{content:"▚ ";color:var(--amber)}
.tbar .ro{font-family:var(--mono);font-size:var(--fs-chrome);color:var(--dim)}
.tbar .ro a{color:var(--amber-dim)}
.tbar .ro .hot{color:var(--red)}
.pad{padding:var(--pad)}
/* a panel whose spacing is owned by a grid/flex parent rather than by itself */
.panel--flush{margin:0}

/* ---------- chips: sentence case, portal vocabulary ---------- */
.chip{
  display:inline-block;font-family:var(--mono);font-size:var(--fs-chrome);
  border:1px solid var(--line-hi);padding:0 7px;line-height:18px;color:var(--dim);
  white-space:nowrap;
}
a.chip:hover{color:var(--amber);border-color:var(--amber);text-decoration:none}
.chip--late{border-color:var(--red);color:var(--red)}
.chip--soon{border-color:var(--amber-dim);color:var(--amber)}
.chip--live{border-color:var(--green);color:var(--green)}
.chip--ghost{border-style:dashed;color:var(--dim)}
.blink{animation:bl 1.1s steps(1) infinite}
@keyframes bl{50%{opacity:.25}}
.chip-cluster{display:flex;gap:7px;flex-wrap:wrap;align-items:center}

.btn{
  display:inline-block;font-family:var(--mono);font-weight:600;font-size:12px;letter-spacing:1px;
  border:1px solid var(--line-hi);background:var(--inset);color:var(--txt);
  padding:6px 16px;cursor:pointer;text-transform:uppercase;
  /* Explicit, because <button> does not inherit it. Without this the UA
     gives buttons line-height:normal while anchors inherit body's 1.6, so
     an <a class="btn"> stood 4px taller than a <button class="btn"> beside
     it - every mixed row in the skin was subtly ragged, not just the film
     page's actions. Same class, same height, whatever the element. */
  line-height:1.6;
}
.btn:hover{border-color:var(--amber);color:var(--amber);text-decoration:none}
.btn--hot{border-color:var(--amber);color:var(--amber)}
.btn--hot:hover{background:var(--amber);color:var(--bg)}
.btn--sm{font-size:11px;padding:3px 10px}

/* ---------- header + nav ---------- */
.head{display:flex;align-items:baseline;gap:14px;flex-wrap:wrap;padding:6px 2px 12px}
.wordmark{
  font-family:var(--px);font-size:26px;line-height:1;color:var(--hi);
  text-transform:uppercase;letter-spacing:-1px;margin:0;
}
.wordmark a{color:var(--hi)} .wordmark a:hover{text-decoration:none}
.wordmark .n9{color:var(--amber)}
.head .ver{font-family:var(--mono);font-size:var(--fs-chrome);color:var(--dim)}
.head .right{margin-left:auto;display:flex;gap:14px;align-items:center}
.clockbox{font-family:var(--mono);font-size:var(--fs-chrome);color:var(--dim);text-align:right}
.clockbox b{color:var(--txt);font-weight:normal}
.density{
  font-family:var(--mono);font-size:var(--fs-chrome);color:var(--dim);
  border:1px solid var(--line);background:var(--inset);padding:3px 8px;cursor:pointer;
}
.density b{color:var(--amber);font-weight:normal}
.density:hover{border-color:var(--amber)}

/* Two columns, not one wrapping row. As a single flex-wrap row the links
   and the utility cluster shared one flow, so the moment the eight items
   stopped fitting - around 1150px, well before any breakpoint - the whole
   cluster (search, bell, account) broke onto a second line and landed at
   its LEFT edge, because the old .fill spacer had already eaten the slack
   on line one. It read as a broken layout because nothing was where it
   belonged.

   Now the links wrap inside their own column and the cluster holds the
   right-hand one at any width. Links stay visible rather than scrolling
   out of reach - they are the primary navigation, and a nav you have to
   scroll to discover is worse than a nav on two lines. */
nav.sysnav{
  display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:stretch;
  border:1px solid var(--line);background:var(--panel);margin:0 0 12px;
}
.sysnav-links{display:flex;flex-wrap:wrap;min-width:0}
/* .sysnav-links > a, not nav.sysnav a: the cluster holds links too (the
   bell, the account menu, the signed-out login) and they are not nav
   items. */
.sysnav-links > a{
  font-family:var(--mono);font-weight:600;font-size:12px;letter-spacing:1px;text-transform:uppercase;
  color:var(--dim);padding:7px 13px;border-right:1px solid var(--line);
}
.sysnav-links > a:hover{color:var(--amber);text-decoration:none;background:var(--inset)}
.sysnav-links > a.on{color:var(--bg);background:var(--amber)}
/* The cluster's own left rule, since the last link's right border only
   reaches the end of whatever line it landed on. */
nav.sysnav .stat{
  display:flex;align-items:center;gap:10px;padding:0 10px;
  border-left:1px solid var(--line);
  font-family:var(--mono);font-size:var(--fs-chrome);color:var(--dim);
}
nav.sysnav .stat a{color:var(--txt)}
nav.sysnav .stat a:hover{color:var(--amber);text-decoration:none}

/* an SVG now, not a text glyph - it needs to sit on the nav's baseline
   grid like an icon rather than flow like a character */
.bell{position:relative;color:var(--dim);display:inline-flex;align-items:center}
.bell svg{display:block}
.bell:hover{color:var(--amber)}
.bell .dot{position:absolute;top:-1px;right:-3px;width:6px;height:6px;border-radius:50%;background:var(--red)}

/* ---------- poster treatment ---------- */
.shot{position:relative;display:block;overflow:hidden;background:var(--inset)}
.shot img{
  display:block;width:100%;height:100%;object-fit:cover;
  filter:grayscale(.4) saturate(.75) contrast(1.02) brightness(.86);
  transition:filter .15s;
}
.shot::after{
  content:"";position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(165deg, rgba(232,163,61,.06), rgba(14,15,13,.3));
  transition:opacity .15s;
}
/* ---------- hover is a pointer state (spec 35 M6) ----------
   On touch there is no hover, but a tap leaves the LAST tapped element
   in its :hover state until you tap elsewhere. On this skin that is not
   a subtle tint: the poster treatments swap a heavy grayscale filter for
   `none`, so coming back from a film page left exactly one poster in the
   grid lit up like it was selected. State is information here (law 5),
   and that one was lying.

   Guarded rather than deleted - the pointer behaviour is unchanged, and
   these are the rules where a stuck state reads as a state rather than
   as a tint. */
@media (hover:hover){
  a:hover .shot img, .film:hover .shot img{filter:none}
  a:hover .shot::after, .film:hover .shot::after{opacity:0}
}

/* ---------- racks ---------- */
.rack{display:grid;grid-template-columns:repeat(5,1fr);gap:var(--rack-gap);padding:var(--pad)}
.rack--6{grid-template-columns:repeat(6,1fr)}
.film{display:block;position:relative}
.film .art{aspect-ratio:2/3;border:1px solid var(--line-hi)}
@media (hover:hover){.film:hover .art{border-color:var(--amber)}}
.film .cap{
  display:block;font-size:var(--fs-cap);color:var(--dim);margin-top:4px;line-height:1.4;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
.film .cap b{color:var(--txt);font-weight:normal}
.film .flag{position:absolute;top:5px;left:5px;z-index:2;background:var(--bg)}
.stars{color:var(--amber);font-size:11px;letter-spacing:1px}
.stars .off{color:var(--line-hi)}

/* ---------- hero / backdrop ---------- */
.backdrop{border:1px solid var(--line);border-bottom:0;height:190px;position:relative;overflow:hidden;background:var(--inset)}
.backdrop img{
  width:100%;height:100%;object-fit:cover;
  filter:grayscale(.35) saturate(.75) brightness(.62);
}
/* Fades to the PANEL, not to a hardcoded ink. Ending on ink left a hard
   seam wherever a backdrop met the panel below it - visible on film,
   films, roll and watch, and worst on profile, where the head straddles
   that edge and half of one text block sat on a different ground from
   the other half. It was also the one scrim that did not follow the
   theme (receipt needed its own override to stop being near-black on
   paper), which is what a token would have prevented. */
.backdrop::after{
  content:"";position:absolute;inset:0;
  background:linear-gradient(to bottom, rgba(0,0,0,.12), rgba(0,0,0,.34) 45%, var(--panel) 98%);
}
.hero-panel{position:relative}
.hero-stage{position:relative}
.hero-panel .backdrop{height:280px}
.hero-panel .backdrop::after{
  background:linear-gradient(to bottom, rgba(0,0,0,.18), rgba(0,0,0,.30) 45%, var(--panel) 97%);
}
.hero-panel .hero-body{
  position:absolute;left:0;right:0;bottom:0;padding:var(--pad);z-index:2;
}
.hero-panel h1{
  font-family:var(--dot);font-size:30px;line-height:1.1;margin:6px 0 0;color:var(--hi);
  letter-spacing:.5px;
}
.hero-panel h1 a{color:var(--hi)}
.hero-panel .yr{color:var(--dim);font-size:18px}
.hero-kicker{font-family:var(--mono);font-size:var(--fs-chrome);color:var(--amber);letter-spacing:1px;text-transform:uppercase}
.overview{max-width:60ch;color:var(--txt);font-size:var(--fs-body);margin:8px 0 0}

/* ---------- film head ---------- */
.film-head{display:grid;grid-template-columns:150px 1fr;gap:16px;padding:var(--pad);align-items:stretch}
.film-head-body{min-width:0}
/* poster: top pins into the backdrop overlap, bottom pins to the
   content column's bottom (whatever it ends up being - director
   line, factlines, actions, status pills) so it never floats short
   or overshoots. object-fit:cover crops cleanly at any height. */
/* the poster stretches to the row's height - which the content column sets -
   so its bottom always lands flush with whatever ends the head (factlines,
   actions, status pills), while margin-top lifts its top into the backdrop.
   The img must be out of flow, or its intrinsic 2:3 height would drive the
   row instead and the poster would float short. */
.film-head .poster{
  align-self:stretch;margin-top:-96px;position:relative;overflow:hidden;
  border:1px solid var(--line-hi);
}
.film-head .poster img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
.film-head h1{font-family:var(--dot);font-size:28px;line-height:1.1;margin:0;color:var(--hi);letter-spacing:.5px}
.film-head .yr{color:var(--dim);font-size:17px}
.byline{margin:7px 0 0;font-size:var(--fs-body)}
.byline b{color:var(--hi)}
.factline{margin:5px 0 0;font-family:var(--mono);font-size:10.5px;color:var(--dim)}
.cta{display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-top:14px}
.synopsis{max-width:66ch;font-size:var(--fs-body);color:var(--txt);margin:0}

/* ---------- scoreband / stat band ---------- */
.scoreband{display:grid;grid-template-columns:repeat(2,1fr)}
.scoreband > div{padding:var(--pad);border-right:1px solid var(--line)}
.scoreband > div:last-child{border-right:0}
.stat-band{display:grid;grid-template-columns:repeat(4,1fr)}
.stat-band > a, .stat-band > div{padding:var(--pad);border-right:1px solid var(--line);color:var(--txt);display:block}
.stat-band > a:hover{background:var(--inset);text-decoration:none}
.stat-band > :last-child{border-right:0}
.score-kicker{font-family:var(--mono);font-size:var(--fs-micro);letter-spacing:1px;text-transform:uppercase;color:var(--dim);display:block}
.score-num{font-family:var(--px);font-size:26px;color:var(--hi);margin-top:6px;line-height:1}
.score-num.green{color:var(--green)} .score-num.amber{color:var(--amber)}
.score-num.red{color:var(--red)} .score-num.dim{color:var(--dim)}
.score-den{font-size:13px;color:var(--dim)}
.score-note{font-size:var(--fs-cap);color:var(--dim);margin-top:5px}

/* ---------- tabs strip (profile sections) ---------- */
.tabs{display:flex;border-bottom:1px solid var(--line);flex-wrap:wrap}
.tabs a, .tabs button{
  font-family:var(--mono);font-weight:600;font-size:11.5px;letter-spacing:1px;text-transform:uppercase;
  background:none;border:0;border-right:1px solid var(--line);color:var(--dim);
  padding:6px 14px;cursor:pointer;
}
.tabs a:hover, .tabs button:hover{color:var(--amber);text-decoration:none}
/* Both markers, at the component level. The styleguide has claimed since
   it was written that the skin styles .on AND aria-current; it did not -
   aria-current was patched once, for .admin-page only, so profile's tab
   row shipped with nothing marked current at all. A component-level rule
   is what the styleguide was describing. */
.tabs a.on, .tabs button.on,
.tabs a[aria-current="page"], .tabs button[aria-current="page"]{background:var(--amber);color:var(--bg)}

/* ---------- feed ---------- */
ul.feed{list-style:none;margin:0;padding:0}
ul.feed li{
  display:grid;grid-template-columns:30px 1fr auto;gap:11px;align-items:start;
  padding:var(--row-pad) 12px;border-bottom:1px solid var(--line);
}
ul.feed li:nth-child(even){background:rgba(255,255,255,.025)}
ul.feed li:last-child{border-bottom:0}
.fthumb{width:30px;height:43px;border:1px solid var(--line-hi)}
.fava{
  width:30px;height:30px;border:1px solid var(--line-hi);background:var(--inset);
  display:grid;place-items:center;font-family:var(--px);font-size:11px;color:var(--amber);
}
.fwho{font-size:var(--fs-cap);color:var(--dim);display:flex;gap:7px;flex-wrap:wrap;align-items:center}
.fwho b{color:var(--txt)}
.fverb{color:var(--amber-dim)}
.ftitle{color:var(--hi);font-weight:normal;font-size:var(--fs-title)}
.fquote{margin:4px 0 0;font-size:var(--fs-cap);color:var(--dim);font-style:italic}
/* The member's verdict, not the house's. Chips are boxed because the house
   is asserting a status; a recommendation belongs with the stars and the
   quote, so it takes the mono chrome voice and no box. Boxed and bright
   green, it was the loudest thing in a who-line already carrying an
   avatar, a name, a verb and a rating - and it said what the rating said. */
.verdict{
  font-family:var(--mono);font-size:var(--fs-chrome);letter-spacing:.5px;color:var(--dim);
  white-space:nowrap;
}
.verdict--yes{color:var(--green)}
body.airy .verdict{font-size:11px}

/* ---------- guest book ----------
   Every row below is built by _guestbook_script.html at runtime, so these
   class names are a contract the sheet honours rather than markup Jinja
   could restructure (same rule settings established). The skin replaces
   styles.css, so none of this arrived for free - without it the guest
   book renders as a stack of undecorated divs. */
.gb-form{display:flex;gap:8px;margin-top:2px;max-width:520px}
.gb-form .input{flex:1;min-width:0}
.gb-notes{margin-top:16px;display:flex;flex-direction:column;gap:14px;max-width:720px}
.gb-note{display:flex;gap:11px;align-items:flex-start}
.gb-body{min-width:0;flex:1}
.gb-head{display:flex;gap:10px;align-items:baseline;flex-wrap:wrap}
.gb-who{font-family:var(--dot);font-size:13px;color:var(--hi);letter-spacing:.3px}
.gb-head .meta{font-family:var(--mono);font-size:var(--fs-micro);text-transform:uppercase;letter-spacing:1px}
.gb-head .gb-del{font-family:var(--mono);font-size:var(--fs-micro);text-transform:uppercase;letter-spacing:1px;color:var(--dim)}
.gb-head .gb-del:hover{color:var(--red)}
.gb-text{margin:4px 0 0;font-size:var(--fs-body);color:var(--txt);line-height:1.55;overflow-wrap:break-word}
body.airy .gb-who{font-size:14px}
body.airy .gb-notes{gap:18px}

/* The script builds these too (guest book rows), and the sheet had no
   avatar primitive at all - home's feed uses .fava. Square, like every
   other frame in this skin; roundness is the old sheet's idea. */
.avatar{
  width:34px;height:34px;border:1px solid var(--line-hi);background:var(--inset);
  overflow:hidden;flex:none;display:block;
}
.avatar img{width:100%;height:100%;object-fit:cover;display:block}
.avatar--sm{width:24px;height:24px}

/* A row of filters that are links, not form controls - the shelf picker.
   The current one fills, same marker the tabs use. */
.filter-row{display:flex;gap:8px;flex-wrap:wrap;margin-top:var(--gap-stack)}
.btn[aria-current="page"]{background:var(--amber);border-color:var(--amber);color:var(--bg)}
.btn[aria-current="page"]:hover{background:var(--amber);color:var(--bg)}

/* A summary paragraph that leads a page: body ink, one step up in size. */
.lede{font-size:14px;line-height:1.6;color:var(--txt);max-width:64ch;margin:0}
body.airy .lede{font-size:15px}

/* A sub-heading inside a panel body, for the rare body that has two parts
   the titlebar cannot name (the collection editor's poster/films columns).
   Base rule: this existed only as .settings-nav, .field and .admin-page
   copies, so a fourth user got nothing. The scoped ones are more specific
   and still win where they differ. */
.kicker{
  display:block;font-family:var(--mono);font-weight:600;font-size:var(--fs-chrome);
  letter-spacing:1.5px;text-transform:uppercase;color:var(--dim);
}
body.airy .kicker{font-size:11px}

/* ---------- collection editor ----------
   The skin already knew this page's runtime-built rows (.mc-result,
   .mc-film-row, .mc-thumb and friends, learned when settings was ported)
   but not the three that carry its GEOMETRY. Without them the poster
   column and the form stack instead of sitting side by side, and the
   poster box has no aspect ratio so the column jumps as art loads. This
   is the audit that matters: not "does the class have a rule" but "did
   the layout come over". */
.mc-editor{
  display:grid;grid-template-columns:320px minmax(0,1fr);gap:12px;
  align-items:start;margin:0 0 12px;
}
@media (max-width:900px){.mc-editor{grid-template-columns:1fr}}
.mc-editor .field{max-width:none;margin-top:0}
.mc-editor .field + .field{margin-top:16px}
.mc-poster{
  width:100%;aspect-ratio:2/3;overflow:hidden;position:relative;
  border:1px solid var(--line-hi);background:var(--inset);
}
.mc-poster img{width:100%;height:100%;object-fit:cover;display:block}
.mc-poster .empty{
  position:absolute;inset:0;display:flex;flex-direction:column;gap:8px;
  align-items:center;justify-content:center;text-align:center;padding:20px;
  color:var(--dim);font-size:var(--fs-cap);line-height:1.5;
}
.mc-poster-actions{display:flex;gap:10px;align-items:center;margin-top:10px;flex-wrap:wrap}
/* A <label> wrapping a hidden file input: it is a button, so it needs the
   pointer a button has. */
.mc-file-btn{cursor:pointer}
.mc-list-head{margin-top:28px}

/* A one-line form: a field and its button on the same row. */
.row-form{display:flex;gap:8px;max-width:440px}
.row-form .input{flex:1;min-width:0}

/* A tile whose poster is a link but whose card is not (it carries a
   second control). The anchor has to be a block or the poster's
   aspect-ratio box sits on a text baseline and leaves a gap under it. */
.film > a{display:block}
.film .cap form{display:block}
.fwhen{font-family:var(--mono);font-size:var(--fs-micro);color:var(--dim);white-space:nowrap;padding-top:3px}
.older{display:block;text-align:center;padding:9px;border-top:1px solid var(--line);
  font-family:var(--mono);font-weight:600;font-size:11.5px;text-transform:uppercase;letter-spacing:1px}
.pagerline{display:flex;justify-content:space-between;align-items:center;padding:8px 12px;border-top:1px solid var(--line);
  font-family:var(--mono);font-size:var(--fs-chrome);color:var(--dim)}

/* ---------- ledger ---------- */
/* Built for a label and a NUMBER. The rap sheet puts a film title and a
   phrase in the value, which found three things at once: no gap, so the
   longest row's label and value touched; a 460px measure the long values
   nearly filled; and row padding tuned for single glyphs. The gap is the
   real fix - space-between with no gap only looks safe while the values
   stay short. */
.ledger{font-family:var(--mono);font-size:11px;padding:var(--pad)}
.ledger .r{
  display:flex;justify-content:space-between;align-items:baseline;gap:28px;
  padding:7px 0;border-bottom:1px dotted var(--line);
}
.ledger .r:first-child{padding-top:0}
.ledger .r:last-child{border-bottom:0;padding-bottom:0}
.ledger .r span{color:var(--dim);font-size:var(--fs-chrome);flex:none}
.ledger .r b{color:var(--txt);font-weight:normal;text-align:right;min-width:0}
.ledger .r .neg{color:var(--red)} .ledger .r .pos{color:var(--green)}

/* ---------- badge cards ---------- */
.badge-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;padding:var(--pad)}
.badge-card{border:1px solid var(--line-hi);background:var(--inset);padding:12px}
.badge-card .emoji{font-size:22px;display:block}
/* overflow-wrap, because badge names are display titles and some of them
   are one long made-up word - "Technomancer" measures 134px in a 107px
   column and spilled its card the moment the grid went two-up (spec 35
   M3.1). `anywhere` only breaks a word that cannot fit on its own line,
   so every name that already fitted is untouched, at every width. */
.badge-card .name{font-family:var(--dot);font-size:14px;color:var(--hi);display:block;margin-top:8px;letter-spacing:.5px;overflow-wrap:anywhere}
/* same reason as .name above: badge copy names actors ("...with Arnold
   Schwarzenegger"), and a 14-letter surname does not fit a 72px column */
.badge-card .desc{font-size:var(--fs-cap);color:var(--dim);margin:6px 0 0;overflow-wrap:anywhere}
.badge-card .card-foot{
  font-family:var(--mono);font-size:var(--fs-micro);letter-spacing:1px;text-transform:uppercase;
  color:var(--amber-dim);margin-top:10px;
  /* the divider look that used to arrive by .foot collision, now deliberate */
  display:flex;justify-content:space-between;gap:8px;flex-wrap:wrap;
  /* The owner slot holds a real Discord tag, and members pick things like
     "4_pandas_in_a_trashcan" - 174px of unbroken token in a 107px card
     once the grid went two-up (spec 35 M3.1). It wrapped to its own line
     and then spilled sideways over the neighbouring card, because a flex
     item's min-width:auto will not let it shrink below its content. */
  overflow-wrap:anywhere;
  border-top:1px solid var(--line);padding-top:8px;
}
/* and the slots themselves must be allowed to shrink */
.badge-card .card-foot > *{min-width:0}
.badge-card .name.yours{color:var(--amber)}
.badge-card.locked{opacity:.55;border-style:dashed}
/* A set card is the same card as a whole link (spec 35 M3.2). <a> is
   inline, so it needs the block .badge-card assumes; and the card must
   take the hover, not the text inside it. */
.set-card{display:block;color:inherit}
@media (hover:hover){
  .set-card:hover{border-color:var(--amber);text-decoration:none}
  .set-card:hover .name{color:var(--amber)}
}
/* Set cards carry an emoji, a name and one line of foot - short enough to
   pair up where the item grid's 1-column drop is right for a longer card. */
@media (max-width:560px){
  /* minmax(0,1fr), not 1fr: a long set name sets the track's min-content
     width and the two columns come out uneven (measured 162 / 146). */
  .badge-grid.badge-grid--sets{grid-template-columns:repeat(2,minmax(0,1fr))}
}

/* ---------- a panel whose titlebar is its disclosure ----------
   The loose macguffins belong to no set, so unlike the sets they have no
   detail page to move to - and there are more of them than of everything
   else on the page. Folding them is the only way the index states its own
   size. The <summary> takes .tbar, so the fold adds no chrome: the bar
   that was already there becomes the control.

   list-style AND ::-webkit-details-marker: .tbar's display:flex already
   suppresses the marker in Chrome and Firefox, but not in Safari. */
.panel--fold > summary{cursor:pointer;list-style:none}
.panel--fold > summary::-webkit-details-marker{display:none}
.panel--fold > summary::marker{content:""}
.panel--fold > summary:hover .lbl{color:var(--amber)}
.panel--fold > summary .ro::after{content:" ▸";color:var(--amber)}
.panel--fold[open] > summary .ro::after{content:" ▾"}
/* closed, the titlebar's own bottom rule would double the panel's */
.panel--fold:not([open]) > summary{border-bottom:0}

/* ---------- ticket stubs ---------- */
.stub-strip{display:flex;gap:10px;flex-wrap:wrap;padding:var(--pad)}
.stub{
  border:1px dashed var(--line-hi);background:var(--inset);padding:10px 14px;min-width:150px;
  position:relative;
}
.stub::before{content:"";position:absolute;left:-1px;top:50%;width:8px;height:8px;margin-top:-4px;
  border-radius:50%;background:var(--bg);border-right:1px dashed var(--line-hi)}
.stub-kind{font-family:var(--mono);font-size:var(--fs-micro);letter-spacing:1px;text-transform:uppercase;color:var(--amber);display:block}
.stub-title{font-family:var(--dot);font-size:13px;color:var(--hi);display:block;margin-top:4px}
.stub-date{font-family:var(--mono);font-size:var(--fs-micro);color:var(--dim);display:block;margin-top:4px}

/* ---------- floor grid (films page cards) ---------- */
.floor-grid{display:grid;grid-template-columns:repeat(6,1fr);gap:12px}
.fc{border:1px solid var(--line);background:var(--panel)}
.fc-tall{grid-column:span 3}
.fc-half{grid-column:span 3}
.fc-third{grid-column:span 2}
.fc-tile{grid-column:span 2;display:flex;flex-direction:column;align-items:flex-start;justify-content:center;padding:var(--pad)}
.fc-big{font-family:var(--px);font-size:44px;color:var(--red);line-height:1}
.floor-list{list-style:none;margin:0;padding:4px 0}
.floor-list li{display:flex;gap:10px;align-items:center;padding:7px 12px;border-bottom:1px solid var(--line)}
.floor-list li:last-child{border-bottom:0}
.floor-list .mini{width:26px;height:37px;border:1px solid var(--line-hi);flex:none}
.floor-line{min-width:0;flex:1;font-size:var(--fs-cap);color:var(--dim)}
.floor-line b{color:var(--txt);font-size:var(--fs-body)}
.floor-rank{font-family:var(--px);font-size:14px;color:var(--dim);width:20px;flex:none;text-align:center}
.floor-rank--top{color:var(--amber)}
.rec-meter{display:block;height:4px;background:var(--inset);border:1px solid var(--line);margin-top:5px}
.rec-meter i{display:block;height:100%;background:var(--green)}
.fc-door{display:flex;flex-direction:column;padding:var(--pad)}
.fc-door .btns{margin-top:auto;display:flex;gap:8px;flex-wrap:wrap;padding-top:12px}

/* ---------- forms ---------- */
.field{margin-top:18px;max-width:440px}
.field .flabel{
  font-family:var(--mono);font-size:var(--fs-micro);letter-spacing:1px;text-transform:uppercase;
  color:var(--dim);display:block;margin-bottom:6px;
}
/* A standalone field, outside a .field wrapper - the guest book's sign
   box, the new-collection name, both search inputs. The sheet styled
   `.field input` and nothing else, so every one of these was rendering as
   a browser-default control: grey box, 2px inset border, 21px tall. It is
   the same control, so it gets the same rule rather than a second look. */
.input,
.field input[type="text"], .field select, .field textarea{
  /* background-COLOR, not the shorthand: the shorthand resets
     background-image, and selects draw their arrow with one */
  width:100%;background-color:var(--inset);border:1px solid var(--line-hi);
  padding:8px 10px;color:var(--hi);font-size:var(--fs-body);font-family:var(--sys);
  outline:none;
}
.input:focus,
.field input:focus, .field select:focus, .field textarea:focus{border-color:var(--amber)}
.input::placeholder,
.field input::placeholder{color:var(--dim)}
.fnote{font-size:var(--fs-cap);color:var(--dim);margin:6px 0 0}
.check{display:flex;gap:10px;align-items:flex-start;margin-top:18px;cursor:pointer;max-width:56ch}
.check input{margin-top:3px;accent-color:var(--amber)}
.check .sub{display:block;font-size:var(--fs-cap);color:var(--dim);margin-top:3px}
.chippick{display:flex;gap:7px;flex-wrap:wrap;margin-top:8px}
.chippick label{cursor:pointer}
.chippick input{position:absolute;opacity:0;pointer-events:none}
.chippick input:checked + .chip{border-color:var(--amber);color:var(--amber);background:rgba(232,163,61,.08)}
.chippick .chip:hover{border-color:var(--amber-dim)}

/* ---------- settings layout ---------- */
.settings-layout{display:grid;grid-template-columns:190px 1fr;gap:12px;align-items:start}
.settings-nav{border:1px solid var(--line);background:var(--panel);padding:10px 0}
.settings-nav .grp{
  font-family:var(--mono);font-size:var(--fs-micro);letter-spacing:1px;text-transform:uppercase;
  color:var(--dim);display:block;padding:6px 14px 4px;
}
.settings-nav a{
  display:block;font-family:var(--mono);font-weight:600;font-size:12px;letter-spacing:1px;text-transform:uppercase;
  color:var(--dim);padding:5px 14px;
}
.settings-nav a:hover{color:var(--amber);text-decoration:none;background:var(--inset)}
.settings-nav a.on{color:var(--bg);background:var(--amber)}
.settings-title{font-family:var(--dot);font-size:20px;color:var(--hi);margin:0;letter-spacing:.5px}
.settings-sub{font-size:var(--fs-cap);color:var(--dim);margin:6px 0 0}

/* ---------- profile head ---------- */
.profile-head{display:flex;gap:14px;align-items:center;padding:var(--pad);position:relative;z-index:2}
.profile-head .pava{
  width:64px;height:64px;border:1px solid var(--line-hi);background:var(--inset);
  display:grid;place-items:center;font-family:var(--px);font-size:24px;color:var(--amber);flex:none;
}
.profile-head h1{font-family:var(--px);font-size:22px;color:var(--hi);margin:0;line-height:1.1}
.earned-title{font-family:var(--dot);font-size:13px;color:var(--amber);display:block;margin-top:4px;letter-spacing:.5px}
.profile-head .chips{margin-top:9px}
.profile-head .pp-head-chips{margin-top:9px}
.bd-pull{margin-top:-64px}
/* The mock's avatar is a letter tile; the real one is a Discord image. */
.profile-head .pava{overflow:hidden}
.profile-head .pava img{width:100%;height:100%;object-fit:cover;display:block}

/* An earned personalization, so it keeps moving - but in this skin's own
   colours. Live's version slides a six-hue rainbow, which would import
   more colour into the head than the whole rest of the page has. Amber
   through highlight and back reads as the same shimmer without breaking
   law 3. Exempt from the motion budget the way the LED is: it is one
   member's name on one profile, not a page-wide alert. */
.sparkle{
  background:linear-gradient(100deg,
    var(--amber-dim), var(--hi), var(--amber), var(--hi), var(--amber-dim));
  background-size:300% 100%;
  -webkit-background-clip:text;background-clip:text;color:transparent;
  animation:sys-sparkle 3.2s linear infinite;
}
@keyframes sys-sparkle{from{background-position:0% 50%}to{background-position:300% 50%}}

/* ---------- misc ---------- */
.flash{
  border:1px solid var(--green);background:rgba(127,212,96,.06);color:var(--green);
  font-size:var(--fs-cap);padding:9px 12px;margin:12px 0 0;
}
.flash--error{border-color:var(--red);background:rgba(232,96,77,.06);color:var(--red)}
.meta{font-size:var(--fs-cap);color:var(--dim)}
.foot{
  border-top:1px solid var(--line);margin-top:8px;padding:10px 2px;
  display:flex;justify-content:space-between;gap:10px;flex-wrap:wrap;
  font-family:var(--mono);font-size:var(--fs-micro);color:var(--dim);
}
.foot a{color:var(--dim)} .foot a:hover{color:var(--amber)}

/* ---------- responsive ---------- */
@media (max-width:900px){
  .rack, .rack--6{grid-template-columns:repeat(4,1fr)}
  .floor-grid{grid-template-columns:1fr 1fr}
  .fc-tall,.fc-half,.fc-third,.fc-tile{grid-column:span 2}
  .settings-layout{grid-template-columns:1fr}
  .film-head{grid-template-columns:110px 1fr}
  .film-head .poster{align-self:start;aspect-ratio:2/3;margin-top:-56px}
  .stat-band{grid-template-columns:repeat(2,1fr)}
  .stat-band > :nth-child(2){border-right:0}
  .badge-grid{grid-template-columns:repeat(2,1fr)}
}
@media (max-width:560px){
  .rack, .rack--6{grid-template-columns:repeat(3,1fr)}
  /* Two, not one (spec 35 M3.1). A badge card is an emoji, a title, one
     line of copy and a stat - it does not need 305px, and giving it that
     made /achievements 156 cards tall: 33,889px, forty phone screens, for
     the page that is one of only two the club shows signed-out visitors.
     minmax(0,1fr) because a long badge name would otherwise set the
     track's min-content width and skew the pair. */
  .badge-grid{grid-template-columns:repeat(2,minmax(0,1fr))}
}
@media (prefers-reduced-motion:reduce){*,*::before,*::after{animation:none!important;transition:none!important}}

/* ============================================================
   home page components (ported from dark-system-v2.html so the
   whole site shares one sheet)
   ============================================================ */
.cols{display:grid;grid-template-columns:1fr 272px;gap:12px;align-items:start}
table.rent{width:100%;border-collapse:collapse;font-size:var(--fs-body)}
table.rent td{padding:var(--row-pad) 12px;border-bottom:1px solid var(--line);vertical-align:middle}
table.rent tr:last-child td{border-bottom:0}
/* 600, not bold: only 400 and 600 are loaded, so 700 gets synthesised */
table.rent .ttl{color:var(--hi);font-weight:600;font-size:var(--fs-title)}
table.rent .ttl a{color:var(--hi)}
table.rent .ttl a:hover{color:var(--amber)}
table.rent .yr{color:var(--dim);font-weight:normal;font-size:var(--fs-cap)}
table.rent .due{font-family:var(--mono);font-size:var(--fs-chrome);color:var(--dim);text-align:right;white-space:nowrap}
table.rent tr.hot td{background:rgba(232,96,77,.09)}
.rentmini{width:30px;height:43px;border:1px solid var(--line-hi)}
.balance{
  display:flex;justify-content:space-between;align-items:center;gap:10px;flex-wrap:wrap;
  padding:10px 12px;border-top:1px solid var(--line);background:var(--inset);
  font-family:var(--mono);font-size:var(--fs-chrome);color:var(--dim);
}
.balance b{color:var(--red);font-size:13px}
.panel--live{background:rgba(127,212,96,.05)}
.live{display:flex;align-items:center;gap:12px;padding:var(--pad);flex-wrap:wrap}
.led{width:8px;height:8px;background:var(--green);flex:none;box-shadow:0 0 7px var(--green)}
.live .t{color:var(--hi);font-weight:600;font-size:var(--fs-title)}
.live .m{color:var(--dim);font-size:var(--fs-cap)}
.live .btn{margin-left:auto}
.idcard .row{display:flex;gap:11px;align-items:center;padding:var(--pad) var(--pad) 8px}
.idcard .av{
  width:38px;height:38px;border:1px solid var(--line-hi);background:var(--inset);
  display:grid;place-items:center;font-family:var(--px);font-size:15px;color:var(--amber);
}
.idcard .nm{font-family:var(--px);font-size:15px;color:var(--hi);line-height:1}
.idcard .sc{font-family:var(--mono);font-size:var(--fs-micro);color:var(--dim);margin-top:4px}
.punch{display:flex;gap:5px;align-items:center;padding:0 var(--pad) var(--pad)}
.punch i{width:10px;height:10px;border:1px solid var(--line-hi);display:block}
.punch i.p{background:var(--amber);border-color:var(--amber)}
.punch small{margin-left:auto;font-family:var(--mono);font-size:var(--fs-micro);color:var(--dim)}
.case{display:grid;grid-template-columns:repeat(4,1fr);gap:7px;padding:var(--pad)}
.case span{
  aspect-ratio:1;border:1px solid var(--line-hi);background:var(--inset);
  display:grid;place-items:center;font-size:15px;
}
.case span.iconic{border-color:var(--amber-dim)}
.case span.empty{border-style:dashed;border-color:var(--line);color:var(--line);font-family:var(--px);font-size:var(--fs-chrome)}
.staffpick .art{height:120px;border-bottom:1px solid var(--line)}
.staffpick .art img{object-position:center 22%}
.staffpick p{margin:0;padding:10px 12px;font-size:var(--fs-cap);color:var(--txt);font-style:italic}
.staffpick .pad{padding-top:0}
/* notifications */
.notif-list{list-style:none;margin:0;padding:0}
.notif-list li{
  display:grid;grid-template-columns:28px 1fr auto;gap:10px;align-items:center;
  padding:9px 12px;border-bottom:1px solid var(--line);
}
.notif-list li:last-child{border-bottom:0}
.notif-list li.unread{background:rgba(232,163,61,.05)}
.notif-list .nicon{font-size:14px}
.notif-list .nwhen{font-family:var(--mono);font-size:var(--fs-micro);color:var(--dim);white-space:nowrap}
@media (max-width:900px){.cols{grid-template-columns:1fr}}

/* ============================================================
   mobile fixes (2026-07-25 full-site audit): the rent table's
   date column never hid on small screens (that rule lived in
   dark-system-v4's inline css, not here), and pages that set
   grid-template-columns inline (curation 4-up, admin 6-up
   stat band, admin 2-up cols) beat the plain media queries -
   !important inside the breakpoints wins those back.
   ============================================================ */
.rack > *, .film{min-width:0}
.ledger .r{flex-wrap:wrap}
@media (max-width:700px){
  table.rent .due{display:none}
  table.rent .yr{display:none}
}
@media (max-width:900px){
  .cols{grid-template-columns:1fr !important}
}
@media (max-width:560px){
  .rack, .rack--6{grid-template-columns:repeat(3,1fr) !important}
  .stat-band{grid-template-columns:repeat(2,1fr) !important}
  .stat-band > *{border-right:0}
  .scoreband{grid-template-columns:1fr 1fr}
}

/* ============================================================
   AIRY mode (2026-07-25): the maintainer's ask - the dark
   redesign's visual language unchanged, at the live portal's
   spacing and scale. Same panels, titlebars, readouts, chips,
   ledgers; bigger canvas, bigger type, real air. Default mode;
   the header toggle cycles AIRY -> COMFORT -> COMPACT.
   ============================================================ */
body.airy{
  --fs-body:14px;
  --fs-title:17.5px;
  --fs-cap:12.5px;
  --pad:24px;
  --row-pad:16px;
  --rack-gap:22px;
}
body.airy .shell{max-width:1280px;padding:22px 40px 70px}
body.airy .panel{margin:0 0 30px}
body.airy .shell > :nth-last-child(2){margin-bottom:30px}
body.airy .tbar{padding:11px 18px}
body.airy .tbar .lbl{font-size:13.5px}
body.airy .tbar .ro{font-size:11px}
body.airy .chip{font-size:11.5px;line-height:22px;padding:0 10px}
body.airy .btn{font-size:13px;padding:10px 24px}
body.airy .btn--sm{font-size:11px;padding:5px 14px}
body.airy .wordmark{font-size:34px}
body.airy .head{padding:10px 2px 20px}
body.airy .sysnav-links > a{font-size:13px;padding:11px 19px}
body.airy nav.sysnav .stat{font-size:11px;padding:0 14px}
body.airy .cols{grid-template-columns:1fr 330px;gap:30px}
body.airy .rack{grid-template-columns:repeat(4,1fr)}
body.airy .rack--6{grid-template-columns:repeat(5,1fr)}
body.airy .film .cap{margin-top:7px}
body.airy .rentmini{width:40px;height:58px}
body.airy table.rent td{padding:var(--row-pad) 18px}
body.airy .balance{padding:14px 18px;font-size:11px}
body.airy .balance b{font-size:15px}
body.airy .fthumb{width:44px;height:64px}
body.airy .fava{width:40px;height:40px;font-size:14px}
body.airy .ftok{width:40px;height:40px;font-size:19px}
body.airy ul.feed li{grid-template-columns:44px 1fr auto;gap:16px;padding:var(--row-pad) 18px}
/* The timestamp column costs the copy 79px it cannot spare on a phone.
   Harmless in the home feed, where a row is one short line - but the film
   page runs the same component for the diary, and a member's quote was
   rendering at 146px of a 337px row. Found by the state sweep after the
   offer-card report; same shape, smaller. The stamp moves under the copy
   and right-aligns, so the row keeps its reading order. */
@media (max-width:700px){
  body.airy ul.feed li, ul.feed li{
    grid-template-columns:44px minmax(0,1fr);
    column-gap:12px;row-gap:2px;
  }
  ul.feed li > .fwhen{grid-column:2;justify-self:end}
}
body.airy .fwhen{font-size:11px}
body.airy .fquote{margin-top:6px}
body.airy .older{padding:13px;font-size:14px}
body.airy .tabs a, body.airy .tabs button{font-size:12.5px;padding:10px 20px}
body.airy .ledger{font-size:13px;padding:var(--pad)}
body.airy .ledger .r{padding:11px 0;gap:36px}
body.airy .ledger .r:first-child{padding-top:0}
body.airy .ledger .r:last-child{padding-bottom:0}
body.airy .ledger .r span{font-size:11.5px}
body.airy .idcard .av{width:48px;height:48px;font-size:18px}
body.airy .idcard .nm{font-size:18px}
body.airy .idcard .sc{font-size:10.5px}
body.airy .punch i{width:13px;height:13px}
body.airy .case{gap:10px}
body.airy .case span{font-size:19px}
body.airy .staffpick .art{height:170px}
body.airy .backdrop{height:280px}
body.airy .hero-panel .backdrop{height:360px}
body.airy .hero-panel h1{font-size:42px}
body.airy .hero-kicker{font-size:11px}
body.airy .overview{max-width:64ch}
body.airy .film-head{grid-template-columns:200px 1fr;gap:26px}
body.airy .film-head h1{font-size:36px}
body.airy .film-head .yr{font-size:22px}
body.airy .factline{font-size:12px;margin-top:7px}
body.airy .score-num{font-size:34px}
body.airy .score-kicker{font-size:10.5px}
body.airy .score-note{margin-top:7px}
body.airy .badge-grid{gap:16px}
body.airy .badge-card{padding:18px}
body.airy .badge-card .emoji{font-size:28px}
body.airy .badge-card .name{font-size:17px}
body.airy .badge-card .card-foot{font-size:var(--fs-chrome);margin-top:14px}
body.airy .stub{padding:14px 20px;min-width:180px}
body.airy .stub-title{font-size:16px}
body.airy .stub-kind, body.airy .stub-date{font-size:var(--fs-chrome)}
body.airy .floor-grid{gap:22px}
body.airy .floor-list li{padding:11px 18px}
body.airy .floor-list .mini{width:34px;height:49px}
body.airy .floor-rank{font-size:17px;width:26px}
body.airy .fc-big{font-size:56px}
body.airy .settings-layout{grid-template-columns:230px 1fr;gap:26px}
body.airy .settings-nav a{font-size:13px;padding:8px 18px}
body.airy .settings-nav .grp{font-size:var(--fs-chrome);padding:8px 18px 5px}
body.airy .settings-title{font-size:24px}
body.airy .field{max-width:480px}
body.airy .input,
body.airy .field input[type="text"], body.airy .field select, body.airy .field textarea{padding:11px 14px}
body.airy .profile-head{gap:20px}
body.airy .profile-head .pava{width:84px;height:84px;font-size:32px}
body.airy .profile-head h1{font-size:28px}
body.airy .earned-title{font-size:16px;margin-top:6px}
body.airy .bd-pull{margin-top:-84px}
body.airy .notif-list li{padding:13px 18px;grid-template-columns:34px 1fr auto}
body.airy .notif-list .nicon{font-size:18px}
body.airy .foot{font-size:var(--fs-chrome);padding:14px 2px}
@media (max-width:900px){
  body.airy .shell{padding:16px 18px 50px}
  body.airy{--pad:16px;--row-pad:12px;--rack-gap:12px}
  body.airy .cols{gap:14px}
  body.airy .panel{margin:0 0 16px}
  body.airy .rack{grid-template-columns:repeat(3,1fr)!important}
  body.airy .rack--6{grid-template-columns:repeat(3,1fr)!important}
  body.airy .settings-layout{grid-template-columns:1fr}
  body.airy .film-head{grid-template-columns:120px 1fr;gap:14px}
  body.airy .film-head .poster{align-self:start;aspect-ratio:2/3;margin-top:-56px}
  body.airy .film-head h1{font-size:26px}
  body.airy .hero-panel h1{font-size:28px}
  body.airy .backdrop{height:190px}
  .hero-panel .backdrop{height:170px}
  body.airy .hero-panel .backdrop{height:170px}
  .hero-panel .backdrop::after{background:linear-gradient(to bottom, rgba(0,0,0,.12), var(--panel) 97%)}
  .hero-panel .hero-body{position:static;padding-top:10px}
}

/* airy mobile follow-ups (375px audit): nowrap chips were setting the
   min-content width of film-head / profile-head grids wider than the
   viewport, and the airy rent-table padding tipped home 3px over. */
.profile-head > div{min-width:0}
@media (max-width:700px){
  .chip{white-space:normal;line-height:1.5;padding-top:2px;padding-bottom:2px}
  body.airy table.rent td{padding:10px}
  body.airy .profile-head .pava{width:64px;height:64px;font-size:24px}
  body.airy .bd-pull{margin-top:-64px}
}

/* ============================================================
   THE TOUCH SCALE (spec 35, M4 - 2026-07-30)

   A third density step, in the same shape as the two that already
   exist. body.compact and body.airy each re-declare the same five
   or six tokens and let the components follow; this does the same
   for a finger. Written as one block on purpose - the alternative
   is what the file was drifting toward, a new @media per component
   at whatever width that component happened to break.

   Two rules, and only two:

   1. NOTHING BELOW 11px. The chrome tiers are tokens now, so the
      whole tier lifts here rather than in 52 selectors. Six airy
      rules hard-code their own 10/10.5px and are restated below;
      everything else already sits at 11px or more.

   2. 44px ON STANDALONE CONTROLS. Deliberately NOT on every anchor.
      A film tile's caption measures 18px, but the tap target is the
      whole 180px tile that contains it, and a member's name inside
      a feed sentence is prose - padding either to 44px would wreck
      the thing it was meant to help. The controls that DO get it
      are the ones that read as controls: buttons, tabs, link-chips,
      selects, inputs, the settings rail, the theme switch.
   ============================================================ */
@media (max-width:700px){
  :root{--fs-micro:11px;--fs-chrome:11.5px}

  /* the six that hard-code past the token */
  body.airy .badge-card .card-foot,
  body.airy .stub-kind, body.airy .stub-date,
  body.airy .settings-nav .grp,
  body.airy .foot,
  body.airy .idcard .sc,
  body.airy .score-kicker{font-size:var(--fs-chrome)}

  /* --- standalone controls: 44px --- */
  body.airy .btn,
  body.airy .btn--sm,
  body.airy .tabs a, body.airy .tabs button,
  body.airy .settings-nav a,
  .link-btn, .older, .density, .sysmenu-list a{
    min-height:44px;
    display:inline-flex;align-items:center;justify-content:center;
  }
  /* .older is a full-width "load more" strip, not an inline control */
  .older{display:flex;width:100%}
  body.airy .btn, body.airy .btn--sm{padding-top:0;padding-bottom:0}
  .density{padding:0 12px}

  /* A chip that is a LINK is a control; a chip that is a status
     (Overdue, Due fri, Live now) is a label and must not grow -
     inflating those would put 44px boxes through every feed row. */
  a.chip{min-height:44px;display:inline-flex;align-items:center}

  /* 16px is where iOS Safari stops zooming the viewport on focus,
     and it never zooms back. Every field, not just search's (M2). */
  input, select, textarea,
  body.airy .input,
  body.airy .field input[type="text"],
  body.airy .field select,
  body.airy .field textarea{font-size:16px;min-height:44px}
  /* the visually-hidden inputs behind custom toggles are not fields */
  input[type="checkbox"], input[type="radio"]{min-height:0}

  /* A settings toggle is a <label class="check"> wrapping the input and
     its copy, so the LABEL is the hit area - and it was 24px of text
     height, twenty-six times over. Padding, not min-height alone:
     align-items stays flex-start because a .sub line makes some of
     these two-line rows. */
  .check{min-height:44px;padding:10px 0;margin-top:8px}
}

/* ---------- the settings rail (spec 35, M5) ----------
   At <=900px settings-layout already collapses to one column, which put
   the rail ON TOP: 477px of nav, ten links, and the first field at
   y=863. Every visit to settings began by scrolling past a screen of
   navigation. It becomes the same horizontal strip the profile tabs
   already use. The group headings go - with ten self-describing labels
   in a row, "Profile" and "Account" are the only things that would need
   a second line to keep. */
@media (max-width:900px){
  body.airy .settings-nav{
    display:flex;padding:0;overflow-x:auto;scrollbar-width:none;
    border-left:0;border-right:0;
  }
  body.airy .settings-nav::-webkit-scrollbar{display:none}
  body.airy .settings-nav .kicker,
  body.airy .settings-nav .grp{display:none}
  body.airy .settings-nav a{
    flex:none;white-space:nowrap;
    min-height:44px;display:inline-flex;align-items:center;
    padding:0 16px;border-right:1px solid var(--line);
  }
  body.airy .settings-nav a:last-child{border-right:0}
  body.airy .settings-nav a + .grp{margin-top:0}
}

/* ============================================================
   THEMES (2026-07-25): the palette audition's eight schemes as
   live user-cyclable themes. The token names stay semantic slots
   (--amber is "the workhorse accent" whatever its hue) and a
   data-theme attribute re-maps them. Default (no attribute) =
   Chinatown, the amber phosphor.

   The attribute lives on <html>, not <body> (2026-07-26): theme.js
   runs in <head> before first paint, where document.body does not
   exist yet. Putting it on the root element is what lets a stored
   theme apply with no flash of the default palette.
   ============================================================ */
:root[data-theme="ice"]{
  --bg:#0d0e10;--panel:#141619;--inset:#0f1114;--line:#31353c;--line-hi:#4c525b;
  --hi:#e7eaee;--txt:#c2c7ce;--dim:#7e8590;
  --amber:#a5c8e8;--amber-dim:#6b87a8;--green:#9ad8ba;--red:#e39cb4;
}
:root[data-theme="salmon"]{
  --bg:#121213;--panel:#19191b;--inset:#131314;--line:#333336;--line-hi:#4e4e53;
  --hi:#eceae6;--txt:#c6c4bf;--dim:#8a8880;
  --amber:#a5c8e8;--amber-dim:#6b87a8;--green:#9ad8ba;--red:#e8a291;
}
:root[data-theme="green"]{
  --bg:#0b0e0b;--panel:#111712;--inset:#0c110d;--line:#2c3a2e;--line-hi:#48584a;
  --hi:#e2eedd;--txt:#bdd0b8;--dim:#7c8f78;
  --amber:#8fdd75;--amber-dim:#5f9c4e;--green:#8fdd75;--red:#e8a33d;
}
:root[data-theme="blue"]{
  --bg:#0b0e1a;--panel:#111527;--inset:#0c101e;--line:#2b3352;--line-hi:#465179;
  --hi:#e6eaf6;--txt:#c0c8e0;--dim:#7a84a8;
  --amber:#7fd0e8;--amber-dim:#5490a8;--green:#9ad8ba;--red:#e88fa0;
}
:root[data-theme="carpet"]{
  --bg:#120e16;--panel:#1a1420;--inset:#140f19;--line:#3c3346;--line-hi:#5c4f6a;
  --hi:#f0e9f4;--txt:#d0c4d8;--dim:#907e9c;
  --amber:#e08fd0;--amber-dim:#a86898;--green:#7fd4c0;--red:#e8695f;
}
:root[data-theme="noir"]{
  --bg:#0e0e0e;--panel:#161616;--inset:#111111;--line:#363636;--line-hi:#525252;
  --hi:#f0f0ee;--txt:#c8c8c4;--dim:#8a8a86;
  --amber:#d8d8d2;--amber-dim:#9a9a94;--green:#b8b8b0;--red:#e8443c;
}
:root[data-theme="receipt"]{
  --bg:#e8e3d5;--panel:#f2eee2;--inset:#ded8c6;--line:#b8b09c;--line-hi:#948c76;
  /* --dim and --green are darker than the audition sheet's values
     (2026-07-26). Measured against real content in the browser, this is
     the only theme where secondary ink failed WCAG AA: --dim landed at
     3.52 and the announcement banner at 3.83 on paper, where every dark
     theme sits between 5.2 and 10.9. A light palette needs more ink than
     a dark one to say the same thing quietly. Hues are unchanged - these
     are the same warm gray and the same green, just carrying enough
     weight to read. Now 4.58 and 5.01.

     2026-07-27: --dim went a step darker again (#6b6455 -> #655e50). The
     first pass measured it against --panel only, but plenty of secondary
     ink sits on --inset, which is darker: titlebar right slots, badge
     card descriptions, stub dates, ledger labels. On that ground the old
     value was 4.12. A token has to clear on every ground the sheet
     actually puts it on, not just the one it was auditioned against. */
  --hi:#211d15;--txt:#3c372c;--dim:#655e50;
  /* The accent pair is the second half of the light-theme contrast fix
     (2026-07-26). --amber was 3.22 on paper, below AA, which put earned
     badge names and every accented link under the floor.

     The two tokens also had to stop meaning what they mean on dark. There,
     the quiet form is simply darker - closer to the ground. On paper,
     darker is LOUDER, so quiet cannot be a lightness move without failing
     contrast all over again. --amber-dim is quieter here by being
     desaturated instead: same warmth, no shout. Both clear AA (4.99 and
     5.40), and the saturated one still reads as the accent because it is
     the only saturated thing on the page. */
  --amber:#8a5209;--amber-dim:#6b563a;--green:#336b26;--red:#c03325;
  color-scheme:light;
}
/* The two pastels (2026-07-31): daylight palettes like receipt, so they
   inherit its hard-won rules rather than relearning them. Every ink token
   below clears WCAG AA on --inset, the darkest ground the sheet puts ink
   on, not just the --panel it is easiest to audition against. And the
   accent obeys receipt's discovery that on light ground "quiet" must be a
   saturation move, not a lightness one - darker is LOUDER on paper. */
:root[data-theme="blossom"]{
  --bg:#f2e4e4;--panel:#faf0ef;--inset:#e9d6d6;--line:#c9a8a8;--line-hi:#a37f7f;
  --hi:#2b1d1d;--txt:#463232;--dim:#6d5252;
  --amber:#a03448;--amber-dim:#7d5560;--green:#33682e;--red:#b03030;
  color-scheme:light;
}
:root[data-theme="seafoam"]{
  --bg:#e0ece5;--panel:#eef5f0;--inset:#d2e1d8;--line:#a4bcaf;--line-hi:#7f9a8c;
  --hi:#1d2b24;--txt:#32463c;--dim:#526458;
  --amber:#6d4a96;--amber-dim:#5f5773;--green:#2e6b3e;--red:#ac3a30;
  color-scheme:light;
}

/* the paper grain replaces body's dark dot pattern - it has to target body,
   since the attribute now sits on the root element */
:root[data-theme="receipt"] body,
:root[data-theme="blossom"] body,
:root[data-theme="seafoam"] body{
  background-image:radial-gradient(rgba(0,0,0,.035) 1px, transparent 1px);
}
:root[data-theme="receipt"] .shot img,
:root[data-theme="blossom"] .shot img,
:root[data-theme="seafoam"] .shot img{filter:grayscale(.25) saturate(.85) brightness(1)}
:root[data-theme="receipt"] .backdrop img,
:root[data-theme="blossom"] .backdrop img,
:root[data-theme="seafoam"] .backdrop img{filter:grayscale(.25) saturate(.8) brightness(.95)}
/* Paper still needs its own scrim - on light, the veil that keeps art
   readable is white, not black - but it ends on the panel token like the
   dark themes now do, so the seam closes here too. */
:root[data-theme="receipt"] .backdrop::after{
  background:linear-gradient(to bottom, rgba(232,227,213,.12), rgba(232,227,213,.4) 45%, var(--panel) 98%);
}
:root[data-theme="blossom"] .backdrop::after{
  background:linear-gradient(to bottom, rgba(242,228,228,.12), rgba(242,228,228,.4) 45%, var(--panel) 98%);
}
:root[data-theme="seafoam"] .backdrop::after{
  background:linear-gradient(to bottom, rgba(224,236,229,.12), rgba(224,236,229,.4) 45%, var(--panel) 98%);
}

/* law 6: watched films recede via treatment, never opacity (design-system
   violations ledger, 2026-07-25). Used on curation/collection grids. */
.film--watched .art img{filter:grayscale(.8) saturate(.45) brightness(.62)}
@media (hover:hover){.film--watched:hover .art img{filter:none}}
.film--watched .cap{color:var(--dim)}

/* ============================================================
   grounded footer (2026-07-25): on short pages the footer used
   to hug the last panel and the site floated in air. The shell
   is now a full-viewport flex column and the footer is the
   floor - margin-top:auto pins it to the bottom, ground fills
   the gap. Tall pages are unchanged.
   ============================================================ */
.shell{display:flex;flex-direction:column;min-height:100dvh}
.shell > *{width:100%}
.foot{margin-top:auto}
/* The footer's breathing room cannot come from the footer: margin-top:auto
   is the floor-pin and collapses to 0 the moment the page is tall enough to
   scroll. Panels carry their own bottom margin, but layout wrappers (main,
   floor-grid, room, settings-layout, cols) do not - so guarantee the gap on
   whatever element sits directly above the footer, whatever type it is. */
.shell > :nth-last-child(2){margin-bottom:12px}

/* ============================================================
   cycling hero titles (2026-07-26): the live home hero's title
   switcher in system dress - the row of dash-marked links under
   the featured rental. Ported from home.html's real behavior.
   ============================================================ */
/* No backdrop to run under the head. new_on_shelf carries poster art but
   no backdrops, so a member with nothing rented featured a new title over
   360px of empty inset. The head keeps its type scale and drops the
   image well entirely rather than reserving space for nothing. */
.hero-panel--flat .backdrop{display:none}
.hero-panel--flat .hero-body{position:static;padding-top:var(--pad)}

.hero-titles{display:flex;gap:20px;margin-top:14px;flex-wrap:wrap}
.hero-titles a{
  font-family:var(--mono);font-size:var(--fs-chrome);letter-spacing:1px;text-transform:uppercase;
  color:var(--dim);position:relative;padding-top:8px;
}
.hero-titles a::before{
  content:"";position:absolute;top:0;left:0;width:22px;height:2px;
  background:var(--line-hi);
}
.hero-titles a:hover{color:var(--txt);text-decoration:none}
.hero-titles a.on{color:var(--hi)}
.hero-titles a.on::before{background:var(--amber)}
.hero-note{font-family:var(--mono);font-size:var(--fs-chrome);color:var(--dim);letter-spacing:.5px;text-transform:uppercase}
.hero-panel .hero-meta{display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-top:10px}
.hero-panel .backdrop img{transition:opacity .3s ease}
.hero-panel .backdrop img.swapping{opacity:0}
body.airy .hero-titles a{font-size:11px}

/* ============================================================
   Dotemp (self-hosted) is the primary face - the DOT BUREAU package:
   the 8bit cut renders the logo, the regular cut owns the entire display
   tier (T1 movie titles, T2 titles, display numbers, avatars), and IBM
   Plex Mono is the terminal underneath it (400 body + readouts, 600 caps
   controls). Two families, five roles.
   8bit = the logo only. Regular = the whole display tier.
   ============================================================ */
@font-face{
  font-family:"Dotemp 8bit";
  src:url("fonts/dotemp-8bit-webfont.woff2") format("woff2"),
      url("fonts/dotemp-8bit-webfont.woff") format("woff");
  font-weight:normal;font-style:normal;font-display:swap;
}
@font-face{
  font-family:"Dotemp";
  src:url("fonts/dotemp-regular-webfont.woff2") format("woff2"),
      url("fonts/dotemp-regular-webfont.woff") format("woff");
  font-weight:normal;font-style:normal;font-display:swap;
}
.wordmark{font-family:"Dotemp 8bit",var(--px);letter-spacing:0}
.hero-panel h1{font-family:var(--t1);font-weight:normal;letter-spacing:0}
.film-head h1{font-family:var(--t1);font-weight:normal;letter-spacing:0}

/* T2 conformance (caught during the Dotemp integration): the doc says
   entry titles are DotGothic16 at --fs-title, but .ftitle never got the
   assignment and rendered as bold Plex. Now matching the spec. */
.ftitle{font-family:var(--dot);letter-spacing:.3px}


/* ============================================================
   film head rework (2026-07-26 maintainer feedback): the
   director line sat on the backdrop's bottom margin - the bleed
   law says text over an image is all-in or all-out. Film page
   goes all-OUT, classic movie-page anatomy: only the poster
   overlaps the backdrop; every line of text starts below the
   image. The CTA stack turns vertical to fill the space beside
   the tall poster instead of running wide.
   ============================================================ */
/* actions stay one row; status pills take the row beneath them
   (maintainer clarification 2026-07-26 - the buttons were never meant
   to stack, only the chips were meant to break to their own line) */
.film-head .cta{flex-wrap:wrap;gap:10px;margin-top:20px}
.film-head .cta-status{
  display:flex;flex-wrap:wrap;gap:10px;align-items:center;margin-top:12px;
}
@media (max-width:900px){
}


/* ============================================================
   DOT BUREAU metric compensation (2026-07-26): the 8bit logo is
   blockier than Audiowide was, so the wordmark comes back up;
   Plex Mono is wider than Inter so body returns to 14px; Plex
   600 restores real weight to the controls, so the extra
   letter-tracking Share Tech Mono needed comes back off.
   ============================================================ */
.wordmark{font-size:22px}
body.airy .wordmark{font-size:28px}
body.airy{--fs-body:14px}


/* ============================================================
   PRIMITIVES (2026-07-26) - closing the inline-style violation.

   Law 7 says compose tokens, never inline styles, but the pages
   carried ~150 hand-set margins, widths and colours because the
   vocabulary to say those things did not exist yet. This block is
   that vocabulary. It is deliberately small and deliberately
   NAMED - .gap-section, not .mt-18 - so the port can retune the
   scale in one place instead of grepping for pixel values.

   Three kinds of thing live here:
     1. missing component bases the inline styles were standing in
        for (.ftok, .art, .media, .page-title)
     2. component variants for the choices a component genuinely
        offers (.rack--4, .stat-band--6, .field--short)
     3. a short rhythm/ink/row scale for one-off composition

   Variants that have to beat a body.airy rule are written with a
   doubled class (.rack.rack--4) to match its specificity, and this
   block sits late in the file so ties resolve here.
   ============================================================ */

/* ---------- how these win ----------
   The components in this file reach (0,2,2) - `nav.sysnav .stat a`,
   `body.airy .panel`. A utility class is (0,1,0) and loses to all of
   them, silently: the class lands in the markup, the value never
   applies, and the page looks unchanged while the source lies.

   So the layer splits by kind:
     - SINGLE-PURPOSE utilities (one declaration whose entire job is to
       override a component default) carry !important. This is the one
       use of !important the system sanctions, and it is what makes a
       utility a utility rather than a suggestion.
     - COMPONENT VARIANTS carry real declarations, so they are written
       at the specificity of the rule they modify (body-prefixed to
       match body.airy) and win on order, never on force.
   ---------------------------------------------------------------- */

/* ---------- rhythm: named steps, never raw pixels ---------- */
:root{
  --gap-tight:5px;
  --gap-text:10px;
  --gap-stack:12px;
  --gap-section:16px;
  --gap-section-lg:22px;
}
/* .meta never had a margin rule, so every use was inheriting the browser's
   1em block margins and every page was cancelling them by hand. It owns
   zero now; spacing comes from the gap classes below, deliberately. */
.meta{margin:0}
.flush{margin:0 !important}
.gap-tight{margin-top:var(--gap-tight) !important}
.gap-text{margin-top:var(--gap-text) !important}
.gap-stack{margin-top:var(--gap-stack) !important}
.gap-section{margin-top:var(--gap-section) !important}
.gap-section-lg{margin-top:var(--gap-section-lg) !important}

/* .subsection: a kicker-headed block in a content column. The legacy
   sheet has had these since the 2026-07-25 rhythm pass and the templates
   are written against them, but they were never ported here - so under
   this skin every .subsection was an unstyled class contributing nothing,
   and Settings > Linked logins ran its four blocks together with no gaps
   at all. Same names, this skin's tighter scale. */
.subsection{margin-top:var(--gap-section)}
.subsection--danger{margin-top:var(--gap-section-lg)}
.subsection>.kicker{display:block}
.subsection>.kicker+*{margin-top:var(--gap-text)}

/* Same omission, same templates: an input and its button sharing one
   line. Without this the delete-confirm field went full width and dropped
   its button underneath. */
.input-row{display:flex;gap:10px;max-width:460px;flex-wrap:wrap}
/* basis, not flex:1 - with min-width:0 alone the input shrinks to a
   50px stub on a phone instead of wrapping under its button. */
.input-row>.input{flex:1 1 220px;min-width:0}

/* ---------- measure ---------- */
.measure{max-width:60ch !important}
.measure--wide{max-width:64ch !important}
.measure--full{max-width:100% !important}

/* ---------- the landing ----------
   The one page a stranger sees, and the only place this skin goes
   full-bleed: no shell, no border, the street window before you walk into
   the store. base.html drops the shell when hide_header is set, which is
   set for exactly this response.

   Two stacked layers so a backdrop can cross-fade into the next while the
   active one slowly zooms - ported from live, which has the behaviour the
   mock's single static image did not. */
.landing{position:fixed;inset:0;overflow:hidden;background:var(--inset)}
.landing-bg{position:absolute;inset:0}
.landing-bg img{
  position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  opacity:0;transition:opacity 1.8s ease;will-change:opacity,transform;
  /* Worst case is a white frame arriving under centred text. brightness
     .42 plus the .35 centre stop below composites to ~74/255 there, which
     keeps --txt at 5.3:1. At .5 and .25 it was 3.6 and the headline would
     have been unreadable on any bright backdrop. */
  filter:grayscale(.45) saturate(.7) brightness(.42);
}
.landing-bg img.on{opacity:1}
.landing-bg img.zooming{animation:landing-zoom 10s ease-out forwards}
@keyframes landing-zoom{from{transform:scale(1)}to{transform:scale(1.09)}}
.landing-bg::after{
  content:"";position:absolute;inset:0;
  background:radial-gradient(ellipse at center, rgba(14,15,13,.42), rgba(14,15,13,.92) 78%);
}
.landing-center{
  position:relative;z-index:2;height:100%;
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  text-align:center;padding:40px 20px 72px;
}
/* The logo ships its own cream fill, so it is an <img> rather than an
   inlined, currentColor-driven mark. That fill reads on the landing in
   every theme because the landing's scrim is dark in all of them. */
.landing-logo{width:min(320px,68vw);height:auto;display:block}
.landing-kicker{
  font-family:var(--mono);font-size:var(--fs-chrome);color:var(--amber);letter-spacing:2px;
  text-transform:uppercase;display:block;margin-top:18px;
}
.landing-title{
  font-family:var(--dot);font-weight:normal;font-size:34px;line-height:1.2;
  color:var(--hi);margin:14px 0 0;letter-spacing:.5px;
}
.landing-sub{color:var(--txt);font-size:14px;line-height:1.6;margin:16px auto 0;max-width:54ch}
.landing-ctas{display:flex;gap:10px;justify-content:center;flex-wrap:wrap;margin-top:28px}
/* --txt, not --dim. On a panel the quiet ink step is a hierarchy move; on
   a photograph it is a legibility failure - over the worst-case frame
   --dim measures 2.6. These notes carry the invite rule and the only
   links out, so they get body ink and stay quiet by SIZE instead. */
.landing-note{font-family:var(--mono);font-size:var(--fs-chrome);color:var(--txt);margin-top:18px;max-width:52ch}
/* Full accent, same reason: --amber-dim is 2.7 over the worst frame. */
.landing-note a{color:var(--amber);text-decoration:underline;text-underline-offset:2px}
.landing-note a:hover{color:var(--hi)}
/* The landing carries its own footer rule: the shell's is not rendered. */
.landing-foot{
  position:absolute;left:0;right:0;bottom:0;z-index:2;
  display:flex;justify-content:space-between;gap:10px;flex-wrap:wrap;
  padding:12px 24px;font-family:var(--mono);font-size:var(--fs-micro);
  letter-spacing:1px;text-transform:uppercase;color:var(--dim);
  border-top:1px solid rgba(255,255,255,.07);
  background:linear-gradient(to top, rgba(0,0,0,.35), transparent);
}
.landing-foot a{color:var(--dim)}
.landing-foot a:hover{color:var(--amber)}
@media (max-width:700px){
  .landing-title{font-size:24px}
  .landing-sub{font-size:13px}
}
@media (prefers-reduced-motion:reduce){
  .landing-bg img{transition:none}
  .landing-bg img.zooming{animation:none}
}

/* The two signed-in-but-not-a-member states. Same full-bleed frame as the
   landing, minus the backdrop - a held card, not a shop window. */
.splash{
  position:fixed;inset:0;display:flex;flex-direction:column;
  align-items:center;justify-content:center;text-align:center;padding:40px 20px;
}
.splash img{width:96px;margin:0 auto 22px;image-rendering:pixelated}
.splash h1{font-family:var(--px);font-weight:normal;font-size:28px;color:var(--hi);margin:0}
.splash .meta{margin-top:12px;max-width:52ch}
.splash-ctas{display:flex;gap:10px;justify-content:center;flex-wrap:wrap;margin-top:26px}

/* ---------- prose ----------
   The styleguide has listed "a .pad of prose" as one of the panel body
   patterns since it was written, but nothing here made prose look like
   prose: the sheet sets no rhythm on p and every list in it is
   list-style:none, because until now every list was a component. A
   document page (privacy, terms, welcome) needs the real thing.

   Markers are drawn, not native. A browser bullet is a filled circle in
   a sheet that has no filled circles - the chrome is squares, rules and
   the ▚ glyph - and its indent is the UA's to decide. */
.prose{max-width:64ch;color:var(--txt);font-size:var(--fs-body);line-height:1.65}
.prose > :first-child{margin-top:0}
.prose > :last-child{margin-bottom:0}
.prose p{margin:0 0 1em}
.prose ul{list-style:none;margin:0 0 1em;padding:0}
.prose li{position:relative;padding-left:18px;margin-bottom:.5em}
.prose li:last-child{margin-bottom:0}
/* Accent, not accent-dim: amber-dim on the carpet panel measures 4.38,
   and a bullet is small enough that the quiet step buys nothing. */
.prose li::before{
  content:"▪";position:absolute;left:2px;top:0;color:var(--amber);
  font-family:var(--mono);
}
.prose a{text-decoration:underline;text-underline-offset:2px}
body.airy .prose{font-size:14px;line-height:1.7}

/* ---------- ink: the ladder, as classes ---------- */
.ink-hi{color:var(--hi) !important}
.ink{color:var(--txt) !important}
.ink-dim{color:var(--dim) !important}
.ink-accent{color:var(--amber) !important}
.ink-alert{color:var(--red) !important}

/* ---------- rows ---------- */
.row{display:flex;gap:var(--gap-stack);align-items:center;flex-wrap:wrap}
.row--fields{gap:14px;align-items:flex-start}
/* margin shorthand, not margin-top: these land on <p> as often as <div>,
   and a stray UA bottom margin would push the panel floor out */
.row-actions{display:flex;gap:8px;flex-wrap:wrap;margin:var(--gap-section) 0 0}
.self-center{align-self:center !important}
.grow{flex:1;min-width:0}

/* ---------- media: a poster beside a block of text ----------
   The art column is a token so the three sizes in use are three
   variants rather than three hand-set grid templates. */
.media{
  display:grid;grid-template-columns:var(--media-art,110px) 1fr;
  gap:16px;align-items:start;
}
.media--center{align-items:center;gap:14px}
.media--md{--media-art:120px}
.media--lg{--media-art:150px}

/* ---------- titles ----------
   T1 for a page that has no backdrop to hang a title on, T2 for an
   entry inside a panel. Both were being spelled out inline. */
.page-title{
  font-family:var(--dot);font-size:26px;color:var(--hi);
  margin:0;letter-spacing:.5px;font-weight:normal;
}
.entry-title{
  font-family:var(--dot);font-size:22px;color:var(--hi);
  letter-spacing:.5px;font-weight:normal;
}
/* the year after a title: same line, quieter, never bold */
.yr{color:var(--dim);font-weight:normal}
.entry-title .yr{font-size:16px}
body.airy .page-title{font-size:30px}
body.airy .entry-title{font-size:25px}

/* ---------- poster art outside a .film ----------
   .film .art already existed; posters that are not links needed the
   same frame, so the frame becomes a class in its own right. */
.art{aspect-ratio:2/3;border:1px solid var(--line-hi)}

/* ---------- feed token (the base was missing entirely) ----------
   body.airy .ftok has always existed, but the class it scaled had no
   base rule - every use was carrying the whole look inline. */
.ftok{
  width:30px;height:30px;border:1px solid var(--line-hi);
  background:var(--inset);display:grid;place-items:center;font-size:14px;
}
.ftok--iconic{border-color:var(--amber-dim)}

/* An avatar inside the who-line rather than in the row's first column.
   Home's feed spends column one on the poster (or the token), so identity
   has to ride alongside the name. .fwho is a centred flex row, so this
   only has to state its size - no vertical-align, which a flex item
   ignores anyway. */
.fava--inline{width:16px;height:16px;border-radius:50%;flex:0 0 auto}
body.airy .fava--inline{width:19px;height:19px}

/* ---------- law 6 treatments: state is a treatment, not a colour ----------
   A tape that is out reads heavier than one on the shelf. */
.film--out .shot img{filter:grayscale(.85) saturate(.4) brightness(.6)}
@media (hover:hover){.film--out:hover .shot img{filter:none}}

/* ---------- feed line without the quote voice ----------
   .fquote is a member's own words and is italic for it; a status line
   ("Last returned Wanda, 5 hours ago") is the system talking. */
.fline{margin:4px 0 0;font-size:var(--fs-cap);color:var(--dim)}

/* ---------- rarity above common, on a card foot ---------- */
.rarity{color:var(--amber)}

/* ---------- a rule above a band that follows another band ---------- */
.rule-top{border-top:1px solid var(--line)}

/* ---------- a line of guidance sitting between panels ----------
   Not a panel, but it has to keep the panel's rhythm and edge. */
.standalone-note{margin:0 0 12px;padding:0 2px;max-width:60ch}
body.airy .standalone-note{margin-bottom:30px}

/* ---------- component variants ----------
   body-prefixed so they sit at the same specificity as the body.airy
   rules they have to beat, and win by coming later. */
body .rack.rack--3{grid-template-columns:repeat(3,1fr);max-width:560px}
body .rack.rack--4{grid-template-columns:repeat(4,1fr)}
body .badge-grid.badge-grid--4{grid-template-columns:repeat(4,1fr)}
/* The doubled-class variant outranks the narrow-screen .badge-grid rules,
   so a 4-up grid stayed 4-up on a phone and pushed 13px past the viewport.
   Same trap the shelves hit: a modifier that wins everywhere has to
   restate what it wants at every width. */
@media (max-width:700px){body .badge-grid.badge-grid--4{grid-template-columns:repeat(2,minmax(0,1fr))}}
/* The 480px single-column override that used to sit here is gone (spec
   35 M3.1), and with it the file's last stray breakpoint - the ladder is
   now 560 / 700 / 900 plus the measured nav steps at 1090/1240/1300.
   Worth recording that the audit blamed the wrong rule: --4 is used on
   exactly one grid, the styleguide's specimen, so this override never
   touched /achievements at all. The page that was forty screens tall
   runs on the plain .badge-grid, and its fix is at the 560 rule above. */
body .stat-band.stat-band--6{grid-template-columns:repeat(6,1fr)}
body .cols.cols--even{grid-template-columns:1fr 1fr}
body .panel.panel--flush{margin:0}
/* a stat band cell whose value is a word, not a number */
body .score-num--word{font-size:18px}
body.airy .score-num--word{font-size:22px}
/* ledgers that sit inside a text column rather than filling a panel */
body .ledger--narrow{max-width:460px}
/* The label-column form. The default ledger right-aligns its values, which
   is the receipt idiom and only reads when every value is short: give one
   row a film title and the next a bare number and the number ends up 470px
   from its own label with nothing connecting them. Here the label column is
   fixed and values start together on the left, so the eye reads down the
   values instead of across a gulf. Use this whenever the values are text. */
body .ledger--record{max-width:640px}
body .ledger--record .r{display:grid;grid-template-columns:148px 1fr;gap:20px}
body .ledger--record .r b{text-align:left}
body.airy .ledger--record .r{grid-template-columns:170px 1fr}
@media (max-width:560px){
  body .ledger--record .r,
  body.airy .ledger--record .r{grid-template-columns:1fr;gap:3px}
}
/* Sits inside a block that already pads, so it carries none of its own.
   Body-prefixed AND airy-matched: at (0,1,1) this lost to
   `body.airy .ledger` at (0,2,1) and the variant has never actually
   dropped its padding. */
body .ledger--inline,
body.airy .ledger--inline{padding:14px 0 0;max-width:300px}
/* form fields narrower than the default measure */
/* width, not just max-width: these sit in flex rows (.row--fields), where
   a flex item with only a max-width shrinks to fit its content instead.
   That rendered the Rating select at 76px next to Recommend at 145px on
   the return form - two controls of the same rank, different sizes.
   max-width:100% keeps them from overflowing a narrow column. */
body .field.field--short{width:180px;max-width:100%}
body .field.field--half{width:280px;max-width:100%}
body .field.field--full{max-width:100%}
/* the first field in a panel body should not push off the padding */
body .field.field--lead{margin-top:0}
/* the notifications bell, when you are already on that page - has to
   outrank nav.sysnav .stat a, which is (0,2,2) */
nav.sysnav .stat a.bell.on{color:var(--amber)}

/* ---------- the selected-film row on the watch page ---------- */
.pick{
  display:flex;gap:12px;align-items:center;margin-top:var(--gap-stack);
  border:1px solid var(--line);background:var(--inset);
  padding:10px 12px;max-width:440px;
}

/* ---------- structural fixes that remove the need for a class ---------- */
/* a title inside a card caption or floor row is not a link colour */
.cap a, .floor-line a, .chip a{color:inherit}
/* the film head sits over the backdrop it follows */
.film-head{position:relative;z-index:2}
/* the door card owns its own padding rather than each page restating it */
.fc-door > .meta{padding:10px 12px 0;margin:0}
.fc-door .btns{padding:0 12px 12px}
/* a note directly under its label hugs it */
.flabel + .fnote{margin-top:0}
/* a second group heading in the settings rail needs air above it */
.settings-nav a + .grp{margin-top:14px}

/* ---------- measured values are DATA, not style ----------
   The one sanctioned use of a style attribute: a custom property
   carrying a number the page measured. .rec-meter fills to whatever
   percentage of the club said rent it, and no class can spell an
   arbitrary percentage. The port sets --fill from the real figure.
   Everything else that is style belongs in this file. */
.rec-meter i{width:var(--fill,0%)}


/* ============================================================
   LIGHTS DOWN (2026-07-26): the second sanctioned full-bleed.
   The design system has always named two moments where the page
   is allowed to leave the shell - the landing's street window and
   the watch room's theater mode. This is the second one, finally
   built rather than described.

   Everything that is store furniture goes away (header, nav,
   footer, chat rail, room titlebar, seat row) and the picture
   takes the viewport. What survives is what you need in the dark:
   the picture, and the transport that gets you out of it. The
   film is letterboxed, not cropped - a theater shows you the
   whole frame.

   Exits: the Lights up button, or Escape.
   ============================================================ */
/* The class goes on <html>, not <body>, for one concrete reason: the
   scrollbar gutter this stylesheet reserves globally is a root property, and
   a fixed layer inset to 0 stops 15px short of the edge while it is held.
   Theater mode is the one state allowed to release it. */
.lights-down{overflow:hidden;scrollbar-gutter:auto}
.lights-down body{overflow:hidden}
.lights-down .head,
.lights-down nav.sysnav,
.lights-down .foot,
.lights-down .room > aside,
.lights-down .panel--screen > .tbar,
.lights-down .seatrow{display:none}
.lights-down .shell{max-width:none;padding:0}
.lights-down .room{
  position:fixed;top:0;left:0;z-index:60;
  /* explicit size rather than inset:0 - the airy floor-gap rule targets the
     second-to-last shell child, which is this element, and on an inset-sized
     fixed box that stray 30px margin comes straight off the height. Stating
     the size outright makes the layer immune to whatever margin it inherits. */
  width:100vw;height:100dvh;margin:0;
  grid-template-columns:1fr;gap:0;background:#000;
}
.lights-down .room > main{height:100dvh;display:flex;flex-direction:column;min-width:0}
/* .room in the selector is load-bearing: body.airy .panel carries the 30px
   stack margin at (0,2,1) and would otherwise outrank a plain .lights-down
   .panel--screen, leaving a black band under the transport. */
.lights-down .room .panel--screen{
  flex:1;display:flex;flex-direction:column;min-height:0;
  margin:0;border:0;background:#000;
}
.lights-down .screen{
  flex:1;min-height:0;aspect-ratio:auto;border-bottom:0;background:#000;
}
.lights-down .screen img{object-fit:contain;filter:none}
/* the scanline veil is store decoration; in the dark it is just dirt on the lens */
.lights-down .screen::after{display:none}
.lights-down .transport{
  flex:none;background:rgba(0,0,0,.9);border-top:1px solid var(--line);
  padding:12px 20px;
}
/* the way out is the one thing that must never be hard to find */
.lights-down #lights{border-color:var(--amber);color:var(--amber)}


/* ============================================================
   PORTED CHROME (2026-07-26) - the parts of the live nav the
   static mock never had.

   The mock's nav carried a bell and a name, which meant Sign Out
   existed nowhere in twenty pages and Settings was reachable only
   from body copy. Both gaps are closed here rather than back in
   the mock, which is now frozen reference: from this commit
   forward THIS FILE is the source of truth for the skin.

   Both components deliberately reuse the live markup contract -
   <details>/<summary> for the menu, and the
   .search-box / #portal-search-input / #portal-search-results
   trio that search.js already binds to - so the skin is a
   stylesheet swap and no JavaScript changes hands.
   ============================================================ */

/* ---------- nav: film search ---------- */
.sysnav .search-box{position:relative;display:flex;align-items:center}
/* Fixed width, and deliberately so. An earlier version grew the input on
   focus, which pushed the nav's total width past the shell and wrapped
   the whole right-hand cluster onto a second row the moment you clicked
   it - the nav is flex-wrap, and .fill had already eaten the slack.
   A search box that relayouts the header when you use it is worse than
   a slightly narrow one. */
.sysnav .search-box input{
  font-family:var(--mono);font-size:11px;color:var(--hi);
  background:var(--inset);border:1px solid var(--line);
  padding:5px 9px;width:150px;outline:none;
  transition:border-color .12s;
}
.sysnav .search-box input::placeholder{
  color:var(--dim);text-transform:uppercase;letter-spacing:1px;
}
.sysnav .search-box input:focus{border-color:var(--amber)}

.search-results{
  position:absolute;top:calc(100% + 6px);right:0;z-index:80;
  width:300px;max-height:64svh;overflow-y:auto;
  border:1px solid var(--line-hi);background:var(--panel);
}
.search-section h5{
  margin:0;padding:6px 12px;
  font-family:var(--mono);font-weight:600;font-size:var(--fs-chrome);
  letter-spacing:1.5px;text-transform:uppercase;color:var(--dim);
  background:var(--inset);border-bottom:1px solid var(--line);
}
.search-result{
  display:flex;gap:10px;align-items:center;width:100%;
  padding:8px 12px;border:0;border-bottom:1px solid var(--line);
  background:none;text-align:left;cursor:pointer;
  font-family:var(--sys);font-size:var(--fs-cap);color:var(--txt);
}
.search-result:hover{background:var(--inset);color:var(--amber);text-decoration:none}
.search-result-thumb{
  width:26px;height:37px;flex:none;object-fit:cover;
  border:1px solid var(--line-hi);background:var(--inset);
}
.search-result-thumb-fallback{
  display:grid;place-items:center;
  font-family:var(--px);font-size:11px;color:var(--dim);
}
.search-result-avatar{height:26px;border-radius:0}
.search-empty{
  margin:0;padding:12px;font-size:var(--fs-cap);color:var(--dim);
}

/* ---------- nav: account menu ----------
   A <details> menu, so it opens with no JavaScript. The summary
   marker is removed rather than restyled: the avatar square IS
   the affordance, and a disclosure triangle beside it reads as a
   second control. */
.sysmenu{position:relative}
.sysmenu > summary{
  display:flex;align-items:center;gap:8px;cursor:pointer;
  padding:3px 4px;list-style:none;
}
.sysmenu > summary::-webkit-details-marker{display:none}
.sysmenu > summary::marker{content:""}
.sysmenu > summary:hover .sysname{color:var(--amber)}
.sysmenu > summary:hover .sysava{border-color:var(--amber)}
.sysmenu[open] > summary .sysava{border-color:var(--amber)}
.sysava{
  width:24px;height:24px;flex:none;overflow:hidden;
  border:1px solid var(--line-hi);background:var(--inset);
  display:grid;place-items:center;
  font-family:var(--px);font-size:11px;color:var(--amber);
}
.sysava img{width:100%;height:100%;object-fit:cover;display:block}
.sysname{
  font-family:var(--mono);font-size:11px;color:var(--txt);
  max-width:14ch;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
.sysmenu-list{
  position:absolute;top:calc(100% + 6px);right:0;z-index:80;
  min-width:170px;border:1px solid var(--line-hi);background:var(--panel);
}
.sysmenu-list a{
  display:block;padding:8px 14px;border-bottom:1px solid var(--line);
  font-family:var(--mono);font-weight:600;font-size:11px;
  letter-spacing:1px;text-transform:uppercase;color:var(--dim);
}
.sysmenu-list a:last-child{border-bottom:0}
.sysmenu-list a:hover{background:var(--inset);color:var(--amber);text-decoration:none}

body.airy .sysava{width:28px;height:28px;font-size:13px}
body.airy .sysname{font-size:12px}
body.airy .sysnav .search-box input{font-size:12px;padding:6px 11px;width:170px}
/* the nav is one row and stays one row: nothing in the right-hand cluster
   may shrink, and the cluster as a whole never wraps away from the links */
.sysnav .stat{flex:none}
.sysnav .search-box, .sysnav .search-box input, .sysmenu, .bell{flex:none}

/* Measured, not guessed. The eight links come to 884px and the cluster to
   308px, so one row needs 1192px - and the widest the nav ever gets inside
   a 1280px shell is 1185. It missed by seven pixels, which is why a
   1280px laptop wrapped. Two steps buy back more than that:

   1300  trim the link padding (64px back) so 1280 sits on one row
   1240  drop the search box (180px back) - with it shown, one row only
         holds to 1223, so a wider breakpoint leaves a band that wraps
         while still showing the box it could not afford
   1090  drop the name beside the avatar, holding one row to about 1005

   Below that it wraps, which is fine now that wrapping happens inside the
   links column and the cluster keeps the right edge. Search is the thing
   that goes because it is the only item with a duplicate route - every
   page has a Films link - while the bell and the account menu do not. */
@media (max-width:1300px){
  body.airy .sysnav-links > a{padding:11px 15px}
}
/* ---------- search below 1240px: a sheet, not a deletion (spec 35 M2) ----

   The rule here used to be `display:none` and nothing else, with the
   reasoning that search is the only nav item with a duplicate route -
   every page has a Films link. True on a laptop, where the alternative
   is one click away. On a phone it meant the only cross-cutting way to
   find a film or a member did not exist, and the alternative was 4.7
   screens of poster grid.

   So the box is not deleted, it is re-hung: a button in the cluster
   opens the same three elements as a sheet under the bar. search.js is
   unchanged in what it binds to - the sheet IS .search-box, restyled.
   Nothing above 1240px moves. */
nav.sysnav{position:relative}   /* the sheet anchors here, not to .stat */
.sysfind-open{
  display:none;align-items:center;justify-content:center;
  padding:0;border:0;background:none;color:var(--dim);cursor:pointer;
}
.sysfind-open:hover{color:var(--amber)}
nav.sysnav.searching .sysfind-open{color:var(--amber)}

@media (max-width:1240px){
  .sysfind-open{display:flex;min-height:44px;min-width:44px}
  /* (0,2,1) and (0,3,1) - both clear the old (0,2,0) `.sysnav .search-box`
     this block used to be, so closed stays closed and open wins. */
  nav.sysnav .search-box{
    display:none;
    position:absolute;top:100%;right:0;left:auto;z-index:79;
    width:min(420px,100%);padding:9px;
    background:var(--panel);border:1px solid var(--line-hi);border-top:0;
  }
  /* align-items:stretch, explicitly: the inline box sets align-items:center
     to sit the field on the nav's baseline, and in a COLUMN flex that
     centres children on the cross axis and shrinks them to content - the
     results panel came out 175px wide, floating in the middle of a 337px
     sheet. */
  nav.sysnav.searching .search-box{
    display:flex;flex-direction:column;align-items:stretch;gap:9px;
  }
  /* 16px is the floor at which iOS Safari stops zooming the viewport on
     focus - and never zooming back. This is the field members will use
     most, so it takes the floor now rather than waiting for M5.

     Doubled selector, measured not guessed: the airy scale sets this at
     `body.airy .sysnav .search-box input`, which is (0,3,2). A plain
     `nav.sysnav .search-box input` is (0,2,2) and lost - the sheet
     opened with a 12px, 29px-tall field. body.airy + nav.sysnav is
     (0,3,3) and wins; the bare rule stays for the dormant compact
     preset, where no .airy class exists. */
  nav.sysnav .search-box input,
  body.airy nav.sysnav .search-box input{
    width:100%;font-size:16px;padding:11px 12px;min-height:44px;
  }
  /* results sit under the input inside the sheet rather than hanging off
     it as a 300px dropdown */
  nav.sysnav .search-results{
    position:static;width:auto;max-height:min(56dvh,420px);
  }
}

@media (max-width:700px){
  /* full width on a phone: 420px is most of the bar anyway, and a sheet
     that stops short of the left edge reads as a misaligned dropdown */
  nav.sysnav .search-box{left:0;right:0;width:auto}
}

@media (max-width:1090px){
  /* the search box already went at 1240 */
  .sysname{display:none}
}

/* ---------- the phone's own chrome (spec 35, M1 - 2026-07-30) ----------

   What this replaces: a 560px rule that stacked the cluster over the
   links so the links got full width, taking the nav from five wrapped
   rows down to three. It was the right fix for the wrong problem. Three
   rows of bordered boxes is still a table of contents, and measured on
   production at 390px the masthead and nav together came to 269px - 32%
   of the viewport, on every page, before a single word of content. Then
   it scrolled away and nothing in this file was position:sticky, so on
   /macguffins (59 screens at the time) the only way back to navigation
   was 59 screens of scrolling.

   So below 700px the links go behind .sysdrawer and the bar sticks. The
   bar is deliberately four things and one line: home, search's slot, the
   bell, the menu. The masthead above it keeps only the theme control -
   the clock and the build id are ambient facts that belong in the
   footer's register, not above the fold on a phone.

   700 rather than 560: it is where the links stop fitting on two rows,
   and it is already a breakpoint in this file. Between 700 and 1090 the
   desktop nav wraps to two rows exactly as it does today - unchanged. */

/* Both are desktop-invisible by default and turned on inside the query,
   so nothing above 700px moves. */
.sysdrawer{display:none;position:relative}
.sysdrawer > summary{
  display:flex;align-items:center;justify-content:center;
  cursor:pointer;list-style:none;color:var(--dim);
}
.sysdrawer > summary::-webkit-details-marker{display:none}
.sysdrawer > summary::marker{content:""}
.sysdrawer[open] > summary{color:var(--amber)}
.sysdrawer-list{
  position:absolute;top:calc(100% + 1px);right:0;z-index:80;
  min-width:232px;border:1px solid var(--line-hi);background:var(--panel);
  /* An admin gets twelve rows at ~46px. On a short phone (an SE is 667)
     that puts Sign Out below the fold of a panel the page cannot scroll,
     because the bar it hangs from is sticky. Scroll the panel itself. */
  max-height:calc(100dvh - 60px);overflow-y:auto;
}
/* nav.sysnav-prefixed on purpose. `nav.sysnav .stat a` is (0,2,2) and sets
   a colour; a bare `.sysdrawer-list a` is (0,1,1) and loses to it, which
   is the same trap already documented for .bell.on above. (0,3,1) beats
   (0,2,2) on class count, so the active and hover states actually land. */
nav.sysnav .sysdrawer-list a{
  display:block;padding:13px 16px;border-bottom:1px solid var(--line);
  font-family:var(--mono);font-weight:600;font-size:12px;letter-spacing:1px;
  text-transform:uppercase;color:var(--dim);
}
nav.sysnav .sysdrawer-list a:last-child{border-bottom:0}
nav.sysnav .sysdrawer-list a:hover{background:var(--inset);color:var(--amber);text-decoration:none}
nav.sysnav .sysdrawer-list a.on{color:var(--bg);background:var(--amber)}
/* the account section's header - a label, not a link */
.sysdrawer-list .grp{
  display:block;padding:10px 16px 7px;border-bottom:1px solid var(--line);
  border-top:1px solid var(--line-hi);background:var(--inset);
  font-family:var(--mono);font-size:var(--fs-chrome);letter-spacing:1.5px;
  text-transform:uppercase;color:var(--dim);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}

@media (max-width:700px){
  /* The masthead is the identity row: wordmark hard left, theme control
     hard right, on the page's own margin rather than inside a bordered
     cell. Revised 2026-07-30 - the wordmark used to sit in the nav bar
     as its first grid cell, which boxed it and gave the bar four things
     to hold. Out here it lines up with the theme chip and the bar is
     just controls. */
  .head{padding:2px 2px 8px;align-items:center;gap:10px}
  .head .ver,.head .clockbox{display:none}
  /* clamp, not another breakpoint: "RETURN BY 9" at a flat 19px is 163px
     wide, which plus the theme chip and the gap does not fit a 320px
     phone - the masthead wrapped into two rows and the two stopped being
     aligned, which is the whole point of putting them on this line.
     Scaling it keeps them on one row all the way down without adding a
     fifth step to the 560/700/900 ladder. */
  body.airy .head .wordmark,
  .head .wordmark{display:block;font-size:clamp(15px,4.9vw,19px);letter-spacing:-.5px}
  .head .right{margin-left:auto}

  nav.sysnav{
    position:sticky;top:0;z-index:50;
    /* one column now that the wordmark has gone up to the masthead */
    grid-template-columns:minmax(0,1fr);
    background:var(--panel);
  }
  .sysnav-links{display:none}
  .sysmenu{display:none}
  .sysdrawer{display:block}

  nav.sysnav .stat{justify-content:flex-end;gap:0;padding:0;border-left:0}

  /* 44px on every control in the bar. It is the only navigation on the
     page now, so nothing in it may be a 16px glyph. */
  .sysdrawer > summary,
  nav.sysnav .stat > .bell,
  nav.sysnav .stat > .syslogin{
    min-height:44px;min-width:44px;
    display:flex;align-items:center;justify-content:center;
  }
  nav.sysnav .stat > .syslogin{padding:0 14px;font-size:11px}
  /* the dot rode the 16px glyph's corner; on a 44px hit area it needs to
     ride the glyph, not the box */
  .bell .dot{top:12px;right:11px}
}


/* ---------- port support (batch 2) ----------
   Live templates carry real member avatars and a poster fallback tile;
   the mock had neither (letter avatars, CSS-fill posters). These give
   the skin's frames somewhere to put the real thing. */
.fava{overflow:hidden}
.fava img{width:100%;height:100%;object-fit:cover;display:block}
/* the poster proxy 404s for films with no cached art - degrade to a
   letter tile inside the same frame, never a broken image */
.shot .poster-fallback{
  position:absolute;inset:0;display:grid;place-items:center;
  font-family:var(--px);font-size:22px;color:var(--dim);background:var(--inset);
}
.film .cap .meta{display:block}
/* collection cards are divs, not links (nested-anchor rule), so the
   hover reward has to be wired to the card rather than the anchor */
@media (hover:hover){.film:hover .cap b{color:var(--hi)}}


/* ============================================================
   LIVE-CLASS COMPATIBILITY (2026-07-26, delete at the flip)

   The film page carries machinery that is skin-neutral: a rent
   modal, watchlist and collection forms, request banners, two
   polling scripts. Duplicating all of it into a system branch
   would double the surface on the portal's most interactive page
   for no visual gain, so it is SHARED between the skins and this
   block teaches the skin the handful of live class names it uses.

   Strictly aliases and small components - no layout. If this
   block starts growing, that is the signal a page is being
   shimmed rather than ported, which is not the deal.
   ============================================================ */
.btn--primary{border-color:var(--amber);color:var(--amber)}
.btn--primary:hover{background:var(--amber);color:var(--bg)}
.chip--in{border-color:var(--green);color:var(--green)}
.chip--due{border-color:var(--amber-dim);color:var(--amber)}
/* a submit button that has to read as a link inside a row of text */
.link-btn{
  background:none;border:0;padding:0;cursor:pointer;
  color:var(--amber-dim);font-size:inherit;font-family:inherit;
}
.link-btn:hover{color:var(--amber)}

dialog.portal-modal{
  border:1px solid var(--line-hi);background:var(--panel);color:var(--txt);
  padding:var(--pad);max-width:440px;
}
dialog.portal-modal::backdrop{background:rgba(0,0,0,.72)}
.modal-title{
  font-family:var(--dot);font-size:20px;color:var(--hi);
  margin:0;letter-spacing:.5px;font-weight:normal;
}
.modal-body{color:var(--txt);font-size:var(--fs-body);margin:12px 0 0}
.modal-body b{color:var(--hi);font-weight:normal}
dialog.portal-modal .btn{margin-top:20px}

/* the add-to-collection menu carries its own inline styling for the live
   skin (a rounded card); square it off and give it the skin's surfaces */
.mc-picker > .stack{
  border-radius:0 !important;border-color:var(--line-hi) !important;
  background:var(--panel) !important;
}
.mc-picker > .stack .meta{margin:0}


/* ---------- the shelf: one row, scrolled sideways ----------
   Caught in the browser (2026-07-26): the live page has two different
   poster containers and the port collapsed them into one. .poster-grid
   WRAPS (curation, collections - correct as a rack), but .shelf-row is a
   single row you scroll sideways, and rendering "New on the shelf" as a
   wrapping rack turned ten films into three screens of grid that buried
   the rest of the page. Same for Similar films.
   Body-prefixed to clear body.airy .rack's column template. */
body .rack.rack--shelf{
  grid-template-columns:none;
  grid-auto-flow:column;grid-auto-columns:132px;
  overflow-x:auto;scrollbar-width:none;
  /* proximity, never mandatory (spec 35 M6): the shelf hides its
     scrollbar and on touch it hides its arrows too, so a swipe is the
     only way through it - and mandatory snapping on a row whose tiles
     are wider than a thumb's flick can trap you between two tiles.
     Proximity lands the row on a poster when you let go near one and
     otherwise stays out of the way. */
  scroll-snap-type:x proximity;
  scroll-padding-left:var(--pad);
}
body .rack.rack--shelf > *{scroll-snap-align:start}
body .rack.rack--shelf::-webkit-scrollbar{display:none}
body.airy .rack.rack--shelf{grid-auto-columns:168px}
/* The narrow-screen rack rules above are !important - they have to clear
   the doubled-class .rack--3/.rack--4 templates - and that also clobbers
   the shelf's "no explicit tracks". Three 1fr tracks inside a column-flow
   grid that overflows resolve to 0px, so the first three tiles of every
   shelf collapsed to nothing at phone width. Restated here, louder. */
@media (max-width:700px){
  body .rack.rack--shelf,
  body.airy .rack.rack--shelf{
    grid-template-columns:none !important;
    grid-auto-columns:120px;
  }
}

/* ---------- secondary nav does not stack (2026-07-30) ----------
   .tabs is flex-wrap:wrap, which on a phone turned every secondary nav
   into a block of ragged rows: the profile's seven sections came out
   three rows deep with a dead cell after MacGuffins and another after
   Guest Book, and the home feed's four filters broke across two. A wrapped
   row of boxes reads as a grid of buttons, not as one nav.

   One row that scrolls instead - the same move the settings rail makes,
   so the portal has one answer for "too many tabs" rather than two. The
   scrollbar is hidden because the partial tab at the edge is the
   affordance, exactly as it is on the shelves. */
@media (max-width:700px){
  .tabs{flex-wrap:nowrap;overflow-x:auto;scrollbar-width:none}
  .tabs::-webkit-scrollbar{display:none}
  .tabs a, .tabs button,
  body.airy .tabs a, body.airy .tabs button{
    flex:none;white-space:nowrap;
  }
}

/* ---------- a rack identified by name, not by art (2026-07-30) ----------
   Found by the state sweep after the offer-card report. Three-up at 390px
   gives a tile 94px, which is fine for a film - you know a poster on
   sight - and useless for a collection, whose tile is a four-poster
   mosaic and whose NAME is the only thing that says which one it is.
   .cap ellipsizes by design (correct: a wrapping caption throws a rack of
   posters out of alignment), so every card on /curation read "BORN TO
   DIE…", "Chris Coded…", "Entry-level…", and the counts under them were
   cut at "38 films · …" too.

   Two-up, and these captions are allowed to wrap - the same exception
   .cap--status already has. !important because body.airy .rack carries
   its own !important three-up rule at <=900. */
@media (max-width:700px){
  body .rack.rack--named,
  body.airy .rack.rack--named{
    grid-template-columns:repeat(2,minmax(0,1fr)) !important;
  }
  .rack--named .film .cap{
    white-space:normal;overflow:visible;text-overflow:clip;
    /* members name collections things like "Movie/Actor/Movie/Actor/etc."
       and a slash is not a break opportunity - 210px of it in a 146px
       tile. Same hazard as the badge titles and the Discord tags. */
    overflow-wrap:anywhere;
  }
}

/* ---------- and the arrows that scroll it ----------
   Caught in the browser (2026-07-27): the shelf hides its scrollbar
   because on live the injected arrows are the affordance, but shelf.js
   only knew live's .shelf-row, so in this skin every shelf was silently
   scrollable - no bar, no arrows, shift+wheel or nothing. The script now
   matches both markups; these are the arrows in this skin's hand.

   They mount INSIDE the panel's .tbar rather than floating above the row
   (live's placement, which here would land on the titlebar). Square, not
   round - nothing in this kit has a radius - and they take the amber
   ground on hover like every other control. */
.tbar--shelf .lbl{margin-right:auto}
.tbar .shelf-nav{display:flex;gap:3px}
.tbar .shelf-arrow{
  width:18px;height:18px;padding:0;
  border:1px solid var(--line-hi);background:transparent;color:var(--dim);
  font-family:var(--mono);font-size:13px;line-height:1;cursor:pointer;
  display:flex;align-items:center;justify-content:center;
}
.tbar .shelf-arrow:hover{background:var(--amber);border-color:var(--amber);color:var(--bg)}
.tbar .shelf-arrow[disabled]{opacity:.3;pointer-events:none}
/* Same reasoning as live: where swiping is the gesture, the arrows are
   chrome nobody presses. The row keeps its own overflow either way. */
@media (hover:none),(max-width:700px){
  .tbar .shelf-nav{display:none}
}


/* ---------- status captions must not clip ----------
   Caught in the browser (2026-07-26). .cap ellipsizes by design - it holds
   film titles, which should truncate rather than wrap a rack out of
   alignment. But the notifications rentals put a CHIP in a caption, and
   live's chip text is "Overdue · was due jul 18", not the mock's short
   "Overdue". At 188px in a 156px card it was cut off mid-word, with no
   ellipsis, because a bordered inline-block does not ellipsize.

   Two changes, because either alone leaves a hole: the rack gets enough
   width for the common case to sit on one line, and a caption carrying
   status is allowed to wrap so a longer date can never clip - the failure
   mode becomes two lines, which is fine, instead of a severed word. */
body .rack.rack--3{max-width:700px}
.film .cap--status{white-space:normal;overflow:visible;text-overflow:clip}
.film .cap--status .chip{white-space:normal}


/* ============================================================
   SETTINGS (2026-07-26)

   Ported almost entirely here rather than in the template, because
   roughly half of that page's DOM does not exist until the scripts
   run: .mc-result, .mc-film-row, .pp-pin-slot and the rest are
   built by JS, so their class names are a contract the template
   cannot restructure. Rewriting the markup around them would mean
   touching 400 lines of form fields - which is where field names
   and CSRF wiring get broken - for no gain the stylesheet cannot
   deliver. So the skin learns live's class names for this page.

   Same reasoning for two things the template does own: field labels
   are written as .kicker, and the active rail item is marked with
   aria-current rather than a class.
   ============================================================ */

/* ---------- the rail ---------- */
.settings-nav .kicker{
  display:block;font-family:var(--mono);font-weight:600;font-size:var(--fs-chrome);
  letter-spacing:1.5px;text-transform:uppercase;color:var(--dim);
  padding:8px 18px 5px;
}
.settings-nav a[aria-current="page"]{color:var(--bg);background:var(--amber)}

/* ---------- the content column is the panel ----------
   Each tab opens with its own <h2 class="settings-title">, so that
   heading IS the panel titlebar - bled to the panel edges with a
   negative margin rather than duplicated by a second title above it. */
.settings-content{
  border:1px solid var(--line);background:var(--panel);
  padding:var(--pad);min-width:0;
}
.settings-content > .settings-title{
  margin:calc(var(--pad) * -1) calc(var(--pad) * -1) var(--pad);
  padding:11px 18px;background:var(--inset);border-bottom:1px solid var(--line);
  font-family:var(--mono);font-weight:600;font-size:13.5px;letter-spacing:1.5px;
  text-transform:uppercase;color:var(--hi);
}
/* the literal glyph, matching .tbar .lbl. Two bugs in the escaped form:
   \25A8 is the wrong character, and a CSS hex escape swallows the space
   that terminates it - so it also jammed against the title. */
.settings-content > .settings-title::before{content:"▚ ";color:var(--amber)}
.settings-content > .settings-sub{margin:0 0 4px}

/* ---------- field labels written as .kicker ----------
   On the COMPONENT, not on a page. This was written twice, once scoped to
   .settings-content and once to .admin-page, with identical declarations -
   so the third form to use a .field (welcome) would have had unlabelled
   fields. Same mistake as aria-current: a page-scoped patch standing in
   for a component rule.

   DIRECT children only. As a descendant selector this also caught the
   pronoun picker's labels, which live inside a .field, and uppercased
   them - "he/him" rendered "HE/HIM". A field's label is its own child;
   a label deeper down belongs to something else. */
.field > label,
.field > .kicker,
.field > label .kicker{
  display:block;font-family:var(--mono);font-weight:600;font-size:11px;
  letter-spacing:1px;text-transform:uppercase;color:var(--dim);
  margin-bottom:6px;
}
.field > label .kicker{margin-bottom:0}
.field .meta{font-size:var(--fs-cap);color:var(--dim)}

/* The button row under a form. Live has carried this since the redesign;
   this sheet never learned it, so settings' form actions have been
   shipping with no gap and no space above them. */
.form-actions{display:flex;gap:12px;align-items:center;margin-top:18px;flex-wrap:wrap}
body.airy .form-actions{margin-top:22px}

/* ---------- chip pickers (pronouns, rating style, mood) ----------
   Structurally identical to .chippick, which the skin already has. */
.pp-mood-picker{display:flex;gap:7px;flex-wrap:wrap;margin-top:8px}
.pp-mood-chip{cursor:pointer;position:relative}
.pp-mood-chip input{position:absolute;opacity:0;width:1px;height:1px;pointer-events:none}
.pp-mood-chip:hover .chip{border-color:var(--amber-dim)}
.pp-mood-chip input:checked + .chip{
  border-color:var(--amber);color:var(--amber);background:rgba(232,163,61,.08);
}
.pp-mood-chip input:focus-visible + .chip{outline:1px dashed var(--amber);outline-offset:2px}

/* ---------- services picker: a chip you can take back off ----------
   Scoped, so no other chip in the skin grows a remove button. */
.svc-picked{margin-top:8px}
/* overflow:visible so the tap target below is not clipped */
.svc-picked .chip{display:inline-flex;align-items:center;gap:5px;padding-right:3px;overflow:visible}
.svc-picked .x{
  background:none;border:0;cursor:pointer;color:inherit;font-family:var(--mono);
  font-size:12px;line-height:1;padding:1px 4px;opacity:.6;position:relative;
}
/* The glyph stays small; the thing you can hit does not. The skin's chips
   are 20px tall by design, so the target is grown outward instead. */
.svc-picked .x::after{content:"";position:absolute;inset:-6px -5px}
.svc-picked .x:hover{opacity:1;color:var(--amber)}
.svc-picked .x:focus-visible{opacity:1;outline:1px dashed var(--amber);outline-offset:1px}
.pp-stack{max-width:720px}
.pp-divided{border-top:1px solid var(--line);padding-top:14px;margin-top:18px}

/* ---------- banner picker ---------- */
.pp-banner-grid{
  display:grid;grid-template-columns:repeat(auto-fill,minmax(160px,1fr));
  gap:10px;margin-top:14px;
}
.pp-banner-choice{position:relative;cursor:pointer;display:block}
.pp-banner-choice input{position:absolute;opacity:0;width:1px;height:1px;pointer-events:none}
.pp-banner-thumb{
  display:block;position:relative;aspect-ratio:16/9;overflow:hidden;
  background:var(--inset);border:1px solid var(--line-hi);
}
.pp-banner-thumb img{
  width:100%;height:100%;object-fit:cover;display:block;
  filter:grayscale(.4) saturate(.75) brightness(.86);transition:filter .15s;
}
.pp-banner-choice:hover .pp-banner-thumb img{filter:none}
.pp-banner-choice input:checked + .pp-banner-thumb{border-color:var(--amber)}
.pp-banner-choice input:checked + .pp-banner-thumb img{filter:none}
.pp-banner-thumb--auto{display:grid;place-items:center;text-align:center;padding:10px}
.pp-banner-thumb--auto small{color:var(--dim);font-family:var(--mono);font-size:var(--fs-chrome)}
/* An overlay, because the markup gives no choice: this label is a child of
   .pp-banner-thumb, not a sibling, so it lives INSIDE the picture. The
   skin's instinct is captions below art (.film .cap everywhere else), and
   the first attempt did that by resetting position:static - which put the
   label inside a fixed-aspect, overflow-hidden box under an image already
   filling it, so the titles vanished. When dressing a live class, read the
   DOM before deciding where the thing can go. */
.pp-banner-name{
  position:absolute;left:0;right:0;bottom:0;margin:0;
  padding:16px 8px 6px;font-size:var(--fs-cap);color:var(--hi);
  background:linear-gradient(to top, rgba(0,0,0,.88), transparent);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}

/* ---------- showcase badge slots ---------- */
.pp-pin-slots{display:flex;gap:10px;margin-top:14px;flex-wrap:wrap}
.pp-pin-slot{
  flex:1;min-width:180px;min-height:58px;display:flex;align-items:center;gap:10px;
  padding:12px 14px;border:1px dashed var(--line);color:var(--dim);
  font-size:var(--fs-cap);justify-content:center;background:var(--inset);
}
.pp-pin-slot.filled{border-style:solid;border-color:var(--line-hi);color:var(--txt);justify-content:flex-start}
.pp-pin-slot .emoji{font-size:20px;line-height:1}
.pp-pin-slot .name{font-family:var(--dot);font-size:14px;color:var(--hi);min-width:0}
.pp-pin-slot .link-btn{margin-left:auto}
.pp-crown{font-size:13px}
.pp-badge-picker{margin-top:20px}
.pp-badge-picker > summary{
  cursor:pointer;font-family:var(--mono);font-weight:600;font-size:11px;
  letter-spacing:1px;text-transform:uppercase;color:var(--dim);
}
.pp-badge-picker > summary:hover{color:var(--amber)}

/* ---------- the film picker (top 8, collections) ---------- */
.mc-films{margin-top:12px;display:flex;flex-direction:column}
/* The controls that open under a member's row in the Members tab. Without
   a containing treatment they float between two rows and read as
   belonging to the one BELOW - which, for a box labelled Delete, is the
   worst ambiguity on the page. The inset and the left rule tie the panel
   to the row above it. */
.admin-member-panel{
  background:var(--inset);
  border-left:2px solid var(--amber);
  padding:var(--pad);
  /* Flush to the row it belongs to, clear of the next one. The asymmetry
     is the point: equal gaps would leave the panel floating between two
     members. */
  margin:0 0 var(--gap-section);
}
.admin-member-panel > :first-child{margin-top:0}

.mc-film-row,.mc-result{
  display:flex;align-items:center;gap:12px;padding:7px 9px;
  border-bottom:1px solid var(--line);
}
.mc-film-row:last-child,.mc-result:last-child{border-bottom:0}
.mc-film-row:hover,.mc-result:hover{background:var(--inset)}
.mc-film-row .ord{
  color:var(--dim);font-family:var(--px);font-size:13px;min-width:20px;text-align:right;
}
.mc-thumb{
  width:34px;height:49px;flex:none;overflow:hidden;background:var(--inset);
  border:1px solid var(--line-hi);display:grid;place-items:center;
  font-family:var(--px);font-size:14px;color:var(--dim);
}
.mc-thumb img{width:100%;height:100%;object-fit:cover;display:block}
.mc-film-row .who,.mc-result .who{min-width:0}
.mc-film-row .who .t,.mc-result .who .t{color:var(--hi);font-family:var(--dot);font-size:var(--fs-title)}
.mc-film-row .who .t:hover{color:var(--amber)}
.mc-film-row .who .meta,.mc-result .who .meta{margin-top:2px;font-size:var(--fs-cap)}
.mc-row-actions{margin-left:auto;display:flex;gap:2px;align-items:center;opacity:.4;transition:opacity .15s}
.mc-film-row:hover .mc-row-actions,.mc-film-row:focus-within .mc-row-actions{opacity:1}
.mc-icon-btn{
  background:none;border:0;cursor:pointer;color:var(--dim);
  font-size:14px;padding:5px 8px;line-height:1;
}
.mc-icon-btn:hover{background:var(--inset);color:var(--hi)}
.mc-icon-btn.danger:hover{color:var(--red)}
.mc-icon-btn:disabled{opacity:.25;cursor:default;background:none}
.mc-search-panel{background:var(--inset);border:1px solid var(--line);padding:12px;margin-top:12px}
.mc-search{display:flex;gap:8px}
.mc-search .input,.mc-search input[type="text"]{
  flex:1;background:var(--bg);border:1px solid var(--line-hi);color:var(--hi);
  padding:7px 9px;font-family:var(--sys);font-size:var(--fs-cap);outline:none;
}
.mc-search .input:focus,.mc-search input[type="text"]:focus{border-color:var(--amber)}
.mc-results{margin-top:4px;display:flex;flex-direction:column}
.mc-results-head{display:flex;align-items:center;justify-content:space-between;margin-top:12px}
.mc-result .added{color:var(--green);font-size:var(--fs-cap);margin-left:auto}
.mc-result .btn{margin-left:auto}

/* ---------- the save toast ---------- */
.mc-toast{
  position:fixed;bottom:24px;left:50%;transform:translateX(-50%);
  background:var(--panel);border:1px solid var(--amber);color:var(--hi);
  padding:9px 18px;font-family:var(--mono);font-size:11px;letter-spacing:1px;
  text-transform:uppercase;opacity:0;transition:opacity .2s;
  pointer-events:none;z-index:90;
}
.mc-toast.show{opacity:1}

/* ---------- a file input has to look like it belongs ---------- */
.settings-content input[type="file"]{
  font-family:var(--mono);font-size:var(--fs-cap);color:var(--dim);
}
@media (max-width:900px){
  .settings-content > .settings-title{font-size:12px}
}


/* ============================================================
   WATCH TOGETHER (2026-07-26)

   Ported the way settings was, and for a stronger reason: the
   player, the sync loop, the chat socket and the fullscreen
   handling all read and write these exact elements. So this block
   re-dresses SURFACES ONLY - colour, border, radius, type. It
   does not touch the grid, the flex directions, or any absolute
   offset inside the stage. A geometry change here is a desync or
   a dead fullscreen button, not a cosmetic bug.
   ============================================================ */

/* the stage sits flush inside its panel */
.watch-layout{margin-top:0}
.watch-stage{background:#000;border:1px solid var(--line);border-left:0;border-right:0}
.watch-stage video{display:block;width:100%}

.watch-overlay{background:rgba(0,0,0,.82);color:var(--txt)}
.watch-overlay .headline{
  font-family:var(--dot);font-size:20px;color:var(--hi);letter-spacing:.5px;
}

.watch-chat{
  background:var(--panel);border:1px solid var(--line);border-radius:0;
}
.watch-chat-log{padding:10px}
.watch-chat-msg{font-size:var(--fs-cap);line-height:1.5}
.watch-chat-msg .chat-name{color:var(--amber);margin-right:6px;font-family:var(--mono)}
.watch-chat-msg .chat-text{color:var(--txt)}
.watch-chat-empty{color:var(--dim);font-size:var(--fs-cap)}
.watch-chat-form{border-top:1px solid var(--line);padding:8px}
.watch-chat-form input{
  background:var(--inset);border:1px solid var(--line-hi);border-radius:0;
  color:var(--hi);font-family:var(--sys);font-size:var(--fs-cap);padding:7px 9px;
}
.watch-chat-form input:focus{border-color:var(--amber);background:var(--inset)}
.chat-brent-avatar{border-radius:0;border:1px solid var(--line-hi)}

/* stage furniture: square, mono, and quiet until pointed at */
.stage-btn,.watch-sound{
  background:rgba(0,0,0,.7);color:var(--txt);border:1px solid var(--line-hi);
  border-radius:0;font-family:var(--mono);font-weight:600;font-size:11px;
  letter-spacing:1px;text-transform:uppercase;padding:6px 11px;
}
.stage-btn:hover,.watch-sound:hover{background:#000;color:var(--amber);border-color:var(--amber)}
.watch-fs-status{
  background:rgba(0,0,0,.7);border-radius:0;border:1px solid var(--line);
  font-family:var(--mono);font-size:var(--fs-chrome);color:var(--dim);padding:5px 9px;
}

.watch-statusline{
  font-family:var(--mono);font-size:var(--fs-chrome);letter-spacing:.5px;
  text-transform:uppercase;color:var(--dim);
}
.watch-people{display:flex;gap:6px;flex-wrap:wrap;margin-top:10px}
.watch-people > *{
  border:1px solid var(--line-hi);background:var(--inset);border-radius:0;
  font-family:var(--mono);font-size:var(--fs-chrome);color:var(--dim);padding:3px 8px;
}
.watch-actions{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-top:14px}
.watch-actions select{
  background-color:var(--inset);border:1px solid var(--line-hi);border-radius:0;
  color:var(--hi);font-family:var(--sys);font-size:var(--fs-cap);padding:6px 9px;
}
.resume-banner{
  display:flex;gap:12px;align-items:center;flex-wrap:wrap;
  border:1px solid var(--amber);background:rgba(232,163,61,.06);
  border-radius:0;padding:10px 14px;margin-top:12px;
  font-size:var(--fs-cap);color:var(--txt);
}

/* the screening room's intermission card */
.fr-intermission{background:rgba(0,0,0,.88);color:var(--txt)}
.fr-intermission .headline{font-family:var(--dot);font-size:20px;color:var(--hi)}
.fr-intermission .count{font-family:var(--px);font-size:30px;color:var(--amber)}

/* host dashboard */
.queue-panel{border:1px solid var(--line);background:var(--panel);padding:var(--pad)}
.booth-tabs{border-bottom:1px solid var(--line);margin-top:10px}
.booth-tabs button{
  background:none;border:0;cursor:pointer;
  font-family:var(--mono);font-weight:600;font-size:12px;letter-spacing:1px;
  text-transform:uppercase;color:var(--dim);padding:9px 16px;
}
.booth-tabs button:hover{color:var(--amber)}
.booth-tabs button.on{background:var(--amber);color:var(--bg)}
.queue-list{margin-top:12px;display:flex;flex-direction:column}
.queue-add{margin-top:12px;position:relative}
.queue-add input{
  width:100%;background:var(--inset);border:1px solid var(--line-hi);border-radius:0;
  color:var(--hi);font-family:var(--sys);font-size:var(--fs-cap);padding:8px 10px;outline:none;
}
.queue-add input:focus{border-color:var(--amber)}
.played-list{margin-top:14px}
.played-list > summary{
  cursor:pointer;font-family:var(--mono);font-weight:600;font-size:11px;
  letter-spacing:1px;text-transform:uppercase;color:var(--dim);
}
.played-list > summary:hover{color:var(--amber)}
.played-list p{font-size:var(--fs-cap);color:var(--dim);margin:8px 0 0}

/* ============================================================
   LIGHTS DOWN, on the real room.

   The mock's version targeted a mock DOM (.room, .panel--screen,
   .transport). This is the same idea aimed at the player that
   actually exists: put the store furniture away, give the stage
   the viewport, keep the room. It is layout, but ONLY while the
   class is on, and it changes nothing the sync loop reads - the
   video element keeps its size from its own container either way.

   Distinct from the Fullscreen button beside it: that hands the
   screen to the browser and takes the chat with it. This stays in
   the page and keeps the chat rail.
   ============================================================ */
.lights-down .watch-layout{
  position:fixed;top:0;left:0;width:100vw;height:100dvh;z-index:60;
  margin:0;gap:0;background:#000;
  grid-template-columns:minmax(0,1fr) 300px;
}
.lights-down .watch-stage{
  border:0;display:flex;flex-direction:column;justify-content:center;min-height:0;
}
.lights-down .watch-stage video{max-height:100dvh;object-fit:contain}
.lights-down .watch-chat{border:0;border-left:1px solid var(--line);max-height:100dvh}
@media (max-width:900px){
  .lights-down .watch-layout{grid-template-columns:1fr;grid-template-rows:1fr auto}
  .lights-down .watch-chat{border-left:0;border-top:1px solid var(--line);max-height:38dvh}
}


/* ============================================================
   ADMIN (2026-07-27)

   The last page, and the only one with no scripts at all - so it
   could have been restructured. It mostly is not, because every
   one of its fifteen sections already has the shape a panel
   wants: a <section> wrapping a .kicker label and one body
   pattern. The skin turns that shape into a panel, the same move
   that made each settings tab's own h2 into its titlebar.

   The payoff is not just the churn avoided. A new admin section
   written in the house pattern gets the panel for free, without
   anyone remembering to add a class.
   ============================================================ */

.admin-page{min-width:0}
.admin-page > section{
  border:1px solid var(--line);background:var(--panel);
  padding:var(--pad);margin:0 0 12px;
}
body.airy .admin-page > section{margin:0 0 30px}
/* the section's own label, bled to the panel edges as its titlebar */
.admin-page > section > .kicker:first-child{
  display:block;
  margin:calc(var(--pad) * -1) calc(var(--pad) * -1) var(--pad);
  padding:11px 18px;background:var(--inset);border-bottom:1px solid var(--line);
  font-family:var(--mono);font-weight:600;font-size:13.5px;letter-spacing:1.5px;
  text-transform:uppercase;color:var(--hi);
}
.admin-page > section > .kicker:first-child::before{content:"▚ ";color:var(--amber)}
/* the page-level panel and the status band are already panels; do not
   double-frame them */
.admin-page > section.panel{padding:0}
.admin-page > section.panel > .tbar{margin:0}
.admin-page > .stat-band{
  border:1px solid var(--line);background:var(--panel);margin-top:0 !important;
  /* The base .stat-band is 4-up, which is right for the profile's four
     scores. Admin's strip carries SIX - bot, relay, plex, film cache,
     letterboxd, outbox - so it wrapped to a second row with two dead
     cells and a ragged rule down the middle. auto-fit rather than a hard
     repeat(6): it collapses the empty tracks, so the strip is six across
     when there is room and folds honestly when there is not. */
  grid-template-columns:repeat(auto-fit,minmax(150px,1fr));
}
/* with wrapping possible, the rule has to come off the last cell of each
   row, not just the last cell overall */
.admin-page > .stat-band > div{border-right:1px solid var(--line)}
.admin-page > .stat-band > div:last-child{border-right:0}
.admin-page > .stat-band .kicker{
  display:block;font-family:var(--mono);font-weight:600;font-size:var(--fs-micro);
  letter-spacing:1px;text-transform:uppercase;color:var(--dim);
}

/* tab strip: live marks the active item with aria-current, not a class */
.admin-page > nav.tabs{
  border:1px solid var(--line);background:var(--panel);
  border-bottom-width:1px;margin:0 0 12px !important;
}
body.airy .admin-page > nav.tabs{margin:0 0 30px !important}
/* (the aria-current active state is now on .tabs itself) */

/* (field labels are a component rule now - see .field label above) */
/* a .kicker that is NOT the section title and NOT a field label is a
   sub-heading inside a body - keep it quiet and inline */
.admin-page .kicker{
  font-family:var(--mono);font-weight:600;font-size:var(--fs-chrome);letter-spacing:1px;
  text-transform:uppercase;color:var(--dim);
}

/* the live .stack list, used for job and room rows */
.admin-page .stack{
  list-style:none;margin:12px 0 0;padding:0;display:flex;flex-direction:column;
}
.admin-page .stack li{
  display:flex;gap:12px;align-items:center;
  padding:8px 0;border-bottom:1px solid var(--line);
}
.admin-page .stack li:last-child{border-bottom:0}
.admin-page .stack .thumb{
  width:34px;flex:none;aspect-ratio:2/3;overflow:hidden;
  background:var(--inset);border:1px solid var(--line-hi);border-radius:0;
}
.admin-page .stack .thumb img{width:100%;height:100%;object-fit:cover;display:block}

/* .no-rule is a live spacing hook with no meaning in the skin */
.admin-page .no-rule{border:0}
.admin-page section > .meta:first-of-type{max-width:76ch}
.admin-page textarea{min-height:70px}


/* ============================================================
   MAINTAINER FEEDBACK (2026-07-27)
   ============================================================ */

/* The site banner is its own element, not a form flash. It sits between
   the nav and the page, so it needs air on BOTH sides - .flash carries
   margin-top only, which left it welded to the panel underneath it. */
/* A notice from the house is not a confirmation, and green said it was -
   it read as "your thing worked" every time, in the OK slot that flashes
   own. Law 3 lists four semantics and a notice is none of them, so it
   takes no colour at all: neutral ink on the inset step, told apart from
   a flash by SHAPE - a full-width band with an accent edge - rather than
   by a fifth hue. */
.site-banner{
  border:1px solid var(--line);border-left:3px solid var(--amber);
  background:var(--inset);color:var(--txt);
  font-size:var(--fs-cap);padding:10px 14px;margin:0 0 12px;
}
.site-banner a{color:var(--amber)}
/* the nav contributes 12px above at every scale, so the banner adds the
   difference itself rather than sitting closer to the nav than to the
   page - even air on both sides is what makes it read as its own thing */
body.airy .site-banner{margin:10px 0 22px;padding:12px 16px}

/* Nothing in a status cell may escape it. The timestamps are relative
   labels now, but a long build string or an unbroken id would do the same
   thing, and a readout that overflows its own box reads as breakage. */
/* break-word, NOT anywhere. `anywhere` also shrinks the intrinsic size,
   so the browser breaks eagerly and "RENTALS" became "RENT / ALS" in a
   narrow cell. break-word only breaks a word that genuinely cannot fit -
   which is all the original ISO-timestamp overflow ever needed. */
.stat-band > a, .stat-band > div{min-width:0;overflow-wrap:break-word}
.score-note{overflow-wrap:break-word}

/* ---------- watch: the film picker's results ----------
   The bug: no room could be created, because the results panel never
   appeared under the search box. .search-results is absolutely
   positioned, and in the nav it anchors to .sysnav .search-box. On the
   watch page it is a SIBLING of the search box inside .watch-picker,
   which the skin never made a containing block - so it anchored to the
   viewport instead and hung off somewhere nobody would look.

   Live solves this with .watch-picker{position:relative} and full-width
   results; the skin now does the same. The lesson generalises: an
   absolutely positioned component carries a requirement about its
   ancestor, and that requirement travels with it to every new context. */
.watch-picker{position:relative;max-width:440px}
.watch-picker .search-results{
  left:0;right:auto;top:calc(100% + 6px);width:100%;
}
/* the picker's own results rows are buttons, not links */
.watch-picker .search-result{width:100%;text-align:left;cursor:pointer}


/* ============================================================
   WATCH ROOM LAYOUT (2026-07-27) - the correction

   The first pass at this page re-dressed surfaces and deliberately
   left geometry alone, on the reasoning that the player's layout is
   load-bearing and must not be disturbed. That reasoning was wrong
   in one decisive way: the skin REPLACES styles.css, it does not
   layer on top of it. There was no geometry to preserve, because
   none of it was loading. The grid, the chat's scroll containment,
   the overlay and stage-bar positioning, and the whole is-fs
   fullscreen mode all lived in a stylesheet the skin swaps out.

   The visible result was the chat stacked under the video at full
   width, growing the page forever instead of scrolling in its own
   column - and fullscreen quietly broken too.

   So the geometry is ported here, faithfully, with only the
   surfaces changed. Anything below that looks like a plain copy of
   the live rule IS one, on purpose.
   ============================================================ */

/* ---------- the two-column room ---------- */
.watch-layout{
  display:grid;grid-template-columns:minmax(0,1fr) 300px;gap:12px;
  align-items:stretch;margin-top:0;
}
.watch-layout .watch-stage{margin-top:0;min-width:0}

/* ---------- the stage ---------- */
.watch-stage{
  position:relative;background:#000;overflow:hidden;
  border:1px solid var(--line);border-left:0;border-right:0;
}
/* aspect-ratio keeps the stage a player-shaped box before a source is
   attached; without it the video falls back to 300x150 and the lobby
   state renders as a squished strip (live's note, still true here) */
.watch-stage video{display:block;width:100%;aspect-ratio:16/9;max-height:72svh;background:#000}

.watch-overlay{
  position:absolute;inset:0;z-index:3;
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:14px;text-align:center;padding:28px;
  background:rgba(0,0,0,.82);color:var(--txt);
}
.watch-overlay p{color:var(--dim);max-width:44ch;font-size:var(--fs-body)}
.watch-overlay .headline{
  font-family:var(--dot);font-size:20px;color:var(--hi);letter-spacing:.5px;
}

.watch-sound{position:absolute;left:12px;bottom:12px;z-index:4}
.watch-stage-bar{position:absolute;top:10px;right:10px;z-index:4;display:flex;gap:8px}
.watch-stage:hover .watch-stage-bar,.watch-stage-bar:focus-within{opacity:1}
.watch-stage-bar{opacity:.6;transition:opacity .2s}
.watch-fs-status{position:absolute;left:14px;bottom:14px;z-index:4;display:none}
.watch-fs-status:empty{display:none !important}

/* ---------- the chat rail: its own scroll, never the page's ---------- */
.watch-chat{
  display:flex;flex-direction:column;min-height:0;max-height:72svh;overflow:hidden;
  background:var(--panel);border:1px solid var(--line);
}
.watch-chat-log{
  flex:1;min-height:200px;overflow-y:auto;
  display:flex;flex-direction:column;gap:8px;padding:10px;
}
.watch-chat-empty{max-width:26ch}
.watch-chat-form{display:flex;gap:8px;padding:8px;border-top:1px solid var(--line)}
.watch-chat-form input{flex:1;min-width:0}

.watch-statusline{margin-top:12px;min-height:22px}
.watch-people{display:flex;align-items:center;gap:6px;flex-wrap:wrap;margin-top:12px}
.watch-viewer{
  display:inline-flex;align-items:center;gap:7px;
  border:1px solid var(--line-hi);background:var(--inset);
  padding:3px 9px;font-family:var(--mono);font-size:var(--fs-chrome);color:var(--txt);
}
.watch-viewer::before{
  content:"";width:6px;height:6px;border-radius:50%;background:var(--green);flex:none;
}
.watch-viewer.buffering::before{background:var(--amber);animation:watch-pulse 1s ease-in-out infinite}
.watch-viewer .role{color:var(--dim)}
@keyframes watch-pulse{50%{opacity:.3}}

/* ---------- fullscreen (is-fs), driven by the player's own JS ---------- */
.watch-layout.is-fs{display:block;position:relative;margin:0;height:100%;background:#000}
.watch-layout.is-fs .watch-stage{border:0;height:100%}
.watch-layout.is-fs .watch-stage video{
  width:100%;height:100%;max-height:none;aspect-ratio:auto;object-fit:contain;
}
.watch-layout.is-fs .watch-fs-status{display:block}
.watch-layout.is-fs .watch-chat{
  position:absolute;top:0;right:0;bottom:0;z-index:5;
  width:min(340px,85vw);max-height:none;border:0;border-left:1px solid var(--line);
  background:rgba(23,24,20,.96);
  transform:translateX(100%);transition:transform .25s ease;
}
.watch-layout.is-fs.chat-open .watch-chat{transform:translateX(0)}

/* ---------- the screening room's intermission card ---------- */
.fr-intermission{
  position:absolute;inset:0;z-index:3;
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;
  background:rgba(0,0,0,.9);text-align:center;padding:24px;
}

/* ---------- host dashboard bodies ---------- */
.booth-pane{margin-top:18px}
.queue-panel .queue-list{display:flex;flex-direction:column;margin-top:12px}
.queue-item{display:flex;align-items:center;gap:12px;padding:8px 0;border-bottom:1px solid var(--line)}
.queue-item:last-child{border-bottom:0}
.queue-item .q-index{width:18px;flex:none;font-family:var(--px);color:var(--dim);text-align:right}
.queue-item .q-title{min-width:0;color:var(--hi)}
.queue-item .q-cost{flex:none;font-family:var(--mono);font-size:var(--fs-chrome);color:var(--dim)}
.queue-item .q-cost-remux{color:var(--green)}
.queue-item .q-cost-transcode{color:var(--amber)}
.queue-item .q-actions{flex:none;display:flex;gap:4px;margin-left:auto}
.queue-add{max-width:420px}
.stream-info p{max-width:70ch;font-size:var(--fs-cap);color:var(--dim);margin-top:6px}
.stream-info b{color:var(--hi);font-weight:normal}
.stream-info .stream-err{color:var(--red)}
.brent-line-row{
  display:flex;align-items:baseline;gap:10px;padding:6px 0;
  font-size:var(--fs-cap);color:var(--dim);max-width:60ch;
}
.brent-line-row button{background:none;border:0;color:var(--dim);cursor:pointer}
.brent-line-row button:hover{color:var(--amber)}

@media (max-width:900px){
  .watch-layout{grid-template-columns:1fr}
  .watch-chat{max-height:40svh}
}


/* ============================================================
   WATCH ROOM POLISH (2026-07-27, maintainer feedback)
   ============================================================ */

/* Collapse the chat column and give the width back to the picture.
   Only the grid changes - the chat keeps its DOM and its socket, so
   messages still arrive and the unread count still counts. */
.watch-layout.chat-hidden{grid-template-columns:minmax(0,1fr)}
.watch-layout.chat-hidden .watch-chat{display:none}
/* fullscreen owns its own drawer; the column toggle must not fight it */
.watch-layout.is-fs.chat-hidden .watch-chat{display:flex}

/* Everything under the stage lives in the panel's padding. It was running
   flush to the panel's left edge, which read as fallen-off rather than
   deliberate. */
.watch-below{padding-top:var(--pad)}
.watch-below > :first-child{margin-top:0}

/* the stage bar now carries three controls, so give it room to breathe
   and keep it legible against a bright frame */
.watch-stage-bar{gap:6px}


/* ============================================================
   WATCH ROOM: the room bar (2026-07-27, maintainer feedback)
   ============================================================ */

/* Icon buttons on the stage. Square, same footprint each, so three of
   them read as one instrument cluster rather than three words of
   different lengths sitting on the picture. */
.stage-btn--icon{
  position:relative;display:grid;place-items:center;
  width:30px;height:30px;padding:0;
}
.stage-btn--icon svg{display:block}
/* the unread pip, same language as the nav bell's */
.stage-btn--icon .dot{
  position:absolute;top:-3px;right:-3px;width:7px;height:7px;
  border-radius:50%;background:var(--red);
  border:1px solid #000;
}

/* One band under the stage: state on the left, controls on the right.
   Wraps to two rows before it ever squeezes, because a cramped control
   row is worse than a taller one. */
.room-bar{
  display:flex;align-items:flex-start;justify-content:space-between;
  gap:16px;flex-wrap:wrap;
}
.room-bar-state{display:flex;align-items:center;gap:10px;flex-wrap:wrap;min-width:0}
.room-bar-controls{
  display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-left:auto;
}
/* the two .watch-actions groups inside the bar stop being stacked blocks */
.room-bar .watch-actions{margin:0;padding:0;border:0;gap:8px}
.room-bar .watch-statusline{margin:0;min-height:0}
.room-bar .watch-people{margin:0}
.room-bar .flabel{
  font-family:var(--mono);font-weight:600;font-size:var(--fs-chrome);letter-spacing:1px;
  text-transform:uppercase;color:var(--dim);margin:0;
}
.room-bar select{
  background-color:var(--inset);border:1px solid var(--line-hi);color:var(--hi);
  font-family:var(--sys);font-size:var(--fs-cap);padding:5px 8px;max-width:200px;
}
.room-bar form{display:inline}

/* the explanation is a footnote, not a paragraph competing with the bar */
.room-note{
  margin:12px 0 0;padding-top:12px;border-top:1px solid var(--line);
  max-width:70ch;color:var(--dim);
}

@media (max-width:700px){
  .room-bar-controls{margin-left:0}
}


/* ============================================================
   ACTION TIERS (2026-07-27)

   The film page put five different kinds of thing in one flex
   row as flat siblings - a primary action, secondary actions,
   two tertiary ones, status chips and explanatory prose - with
   no rule saying which was which. The visible symptom was mixed
   button sizes; the actual fault was that tier was being
   expressed by SCALE. A small bordered button beside a large
   bordered button reads as a lesser version of the same thing,
   not as a different kind of thing.

   So: tier is a TREATMENT, size is a CONTEXT.

     primary    amber border and ink, fills on hover. Exactly one
                per view (law 4).
     secondary  bordered, ink text. Consequential verbs -
                Watch on Plex, Return, Rent Film.
     tertiary   borderless, dim, hover fills. Additive and
                reversible - + Watchlist, + Collection. Reads as
                a link with a hitbox, which is what it is.

   --sm is a DENSITY modifier for places where everything is
   small (card footers, feed rows, table rows). It must never
   appear beside a full-size button in the same row; that is
   the bug this block exists to stop.
   ============================================================ */

/* transparent border, not no border: the box keeps the exact metrics of
   its bordered peers, so a quiet button sits on the same baseline and the
   row does not jitter when one changes tier */
.btn--quiet{
  border:1px solid transparent;background:none;color:var(--dim);
}
.btn--quiet:hover{
  background:var(--inset);border-color:var(--line);color:var(--amber);
}
/* the on-state of a toggle - it is still an action, just already done */
.btn--quiet.is-on{color:var(--amber)}
.btn--quiet.is-on:hover{border-color:var(--amber)}

/* ---------- actions and status never share a row ----------
   The chips and the explanatory prose are pushed to a second line by a
   zero-height full-width flex item, so the split needs no wrapper and no
   duplicated forms - the watchlist and collection controls stay exactly
   where the shared markup put them. */
.film-head .cta{display:flex;flex-wrap:wrap;gap:10px;align-items:center}
.film-head .cta::after{content:"";flex-basis:100%;order:1;height:0}
.film-head .cta > .chip,
.film-head .cta > .meta{order:2}
/* the status line reads as a caption to the actions above it */
.film-head .cta > .meta{max-width:60ch;color:var(--dim)}


/* ============================================================
   STYLE GUIDE (2026-07-27)

   Scaffolding for /styleguide only - the specimens on that page
   are real components and must NOT be styled here. If a rule
   below starts describing how a button or a chip looks, it has
   escaped its job.
   ============================================================ */

.sg-laws{display:block}
.sg-law{
  display:grid;grid-template-columns:38px 1fr;gap:12px;align-items:start;
  padding:var(--row-pad) var(--pad);border-bottom:1px solid var(--line);
}
.sg-law:last-child{border-bottom:0}
.sg-law .n{font-family:var(--px);font-size:20px;color:var(--amber);line-height:1}
.sg-law b{color:var(--hi);font-weight:normal;font-family:var(--dot);letter-spacing:.3px}
.sg-law p{margin:4px 0 0;font-size:var(--fs-cap);color:var(--dim)}

.sg-spec{
  display:grid;grid-template-columns:170px 1fr;gap:14px;align-items:baseline;
  padding:10px var(--pad);border-bottom:1px solid var(--line);
}
.sg-spec:last-child{border-bottom:0}
.sg-spec .k{
  font-family:var(--mono);font-weight:600;font-size:var(--fs-chrome);letter-spacing:1px;
  text-transform:uppercase;color:var(--dim);
}
.sg-spec .note{font-size:var(--fs-cap);color:var(--dim);margin-top:6px;max-width:72ch}
/* specimens sit in a row so several fit on one line without inline styles */
.sg-spec > div > .btn{margin-right:6px}

.sg-dd{display:grid;grid-template-columns:1fr 1fr;gap:10px;padding:var(--pad)}
.sg-do,.sg-dont{border:1px solid var(--line);padding:10px 12px;font-size:var(--fs-cap);color:var(--dim)}
.sg-do{border-left:3px solid var(--green)}
.sg-dont{border-left:3px solid var(--red)}
.sg-do .tag,.sg-dont .tag{
  font-family:var(--mono);font-weight:600;font-size:var(--fs-micro);letter-spacing:1.5px;
  display:block;margin-bottom:5px;
}
.sg-do .tag{color:var(--green)}
.sg-dont .tag{color:var(--red)}

.sg-ok{color:var(--green)}
.sg-swatch{
  display:inline-block;width:34px;height:20px;border:1px solid var(--line-hi);
  vertical-align:middle;margin-right:5px;
}
.sg-swatch--bg{background:var(--bg)}
.sg-swatch--panel{background:var(--panel)}
.sg-swatch--inset{background:var(--inset)}
.sg-code{
  font-family:var(--mono);font-size:var(--fs-chrome);color:var(--txt);
  background:var(--inset);border:1px solid var(--line);padding:3px 7px;
}
.sg-t5{
  font-family:var(--mono);font-weight:600;font-size:12px;letter-spacing:1px;
  text-transform:uppercase;color:var(--txt);
}
.sg-t6{
  font-family:var(--mono);font-size:var(--fs-chrome);letter-spacing:1px;
  text-transform:uppercase;color:var(--dim);
}
/* The stat band is 4-up by design. A two-cell specimen therefore filled
   two of four tracks and each label got 37px of content width, which
   shredded "Rentals" into two lines - a specimen bug that read as a system
   bug. The specimen states its own column count. */
.sg-band{max-width:340px;border:1px solid var(--line);grid-template-columns:repeat(2,1fr)}

@media (max-width:700px){
  .sg-spec{grid-template-columns:1fr;gap:4px}
  .sg-dd{grid-template-columns:1fr}
}
/* a specimen that is itself a panel variant needs a frame to sit in, or it
   reads as part of the styleguide panel around it */
.sg-frame{border:1px solid var(--line);background:var(--panel)}

/* ---------- state survives a missing poster ----------
   The --out and --watched treatments filter the <img>, so a film whose art
   404s degraded to a letter tile with NO state on it: a rented tape and an
   available one looked identical the moment the poster proxy failed. State
   is information (law 5) and must not depend on art loading. Caught by the
   styleguide's own specimen, which uses fallback tiles by design. */
.film--out .shot .poster-fallback,
.film--watched .art .poster-fallback{color:var(--line-hi);background:var(--bg)}
@media (hover:hover){
  .film--out:hover .shot .poster-fallback,
  .film--watched:hover .art .poster-fallback{color:var(--dim);background:var(--inset)}
}

/* ---------- the select arrow ----------
   Native appearance draws the arrow at the very edge of the control, hard
   against the border, and its position is the UA's to decide. So the skin
   draws its own: two gradient triangles, which means it takes CSS
   variables and follows the theme - a background-image SVG could not. */
select{
  appearance:none;-webkit-appearance:none;
  background-image:
    linear-gradient(45deg, transparent 50%, var(--dim) 50%),
    linear-gradient(135deg, var(--dim) 50%, transparent 50%);
  background-position:calc(100% - 17px) calc(50% + 1px), calc(100% - 12px) calc(50% + 1px);
  background-size:5px 5px;
  background-repeat:no-repeat;
  padding-right:34px;
}
select:hover, select:focus{
  background-image:
    linear-gradient(45deg, transparent 50%, var(--amber) 50%),
    linear-gradient(135deg, var(--amber) 50%, transparent 50%);
}
/* the arrow needs room; anything that set a tight right padding gives it back */
.field select, .watch-actions select, .room-bar select, .admin-page select{padding-right:34px}


/* ---------- the select arrow needs its room back ----------
   body.airy .field select sets `padding:11px 14px` at (0,2,2), which beats
   the plain `.field select` rule that reserved space for the drawn arrow -
   so the arrow sat on top of the text and against the border anyway.
   Matched specificity, placed later. */
body.airy .field select,
body.airy .watch-actions select,
body.airy .room-bar select,
body.airy .admin-page select{padding-right:34px}

/* ---------- admin poster picker (2026-07-28) ----------
   In the film page's admin-only dialog. Named admin-poster-* on purpose:
   .poster-grid already means the poster-figure layout in this sheet, and
   reusing it would inherit a grid built for something else. */
/* `dialog.` prefix, not a bare class: the base rule above is
   `dialog.portal-modal{max-width:440px}`, and element+class (0,1,1) beats a
   plain class (0,1,0) regardless of source order. Written as
   `.portal-modal--wide` in 2026-07-28 it never applied once - measured live
   2026-07-30, the picker was 440px wide, not 760. */
dialog.portal-modal--wide { max-width: 760px; width: calc(100vw - 48px); }
.admin-poster-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(92px, 1fr)); gap: 10px; margin: 14px 0; max-height: 52svh; overflow-y: auto; }
.admin-poster-option button { display: block; width: 100%; padding: 0; border: 1px solid transparent; background: none; cursor: pointer; border-radius: 4px; }
/* --amber, not --accent: this sheet has no --accent, so the rule copied
   from styles.css in 2026-07-28 highlighted nothing here for two days. */
.admin-poster-option button:hover { border-color: var(--amber); }
.admin-poster-option img { width: 100%; display: block; border-radius: 3px; }
.admin-poster-option .meta { display: block; text-align: center; font-size: 11px; }

/* ---------- the admin film editor (2026-07-30) ----------
   One dialog with tabs down the left, following Plex's edit modal. It
   replaced three dialogs and two buttons in the film head.

   The twin of the block in app/static/styles.css, with this sheet's own
   variables. Keeping both is the cost of two skins; a rule in only one of
   them silently does nothing on the other, which is how the first cut of
   this shipped completely unstyled on the skin the maintainer uses. */
/* Wider than portal-modal--wide's 760px (maintainer, 2026-07-30). Also
   `dialog.` prefixed - see the note above. Two rounds of raising this number
   changed nothing because dialog.portal-modal's 440px was winning the whole
   time; measured live, the field area was 228px and the six grid columns
   were 27px each, which is why every attempt still read as squished. */
dialog.admin-edit-modal { max-width: 1080px; }
.admin-edit { display: flex; gap: 22px; align-items: flex-start; margin: 16px 0 0; }
.admin-edit-tabs { display: flex; flex-direction: column; gap: 2px; flex: none; width: 140px; }
.admin-edit-tab {
  text-align: left; padding: 7px 10px; border: none; border-left: 2px solid transparent;
  background: none; color: var(--dim); font-family: var(--mono);
  font-size: var(--fs-body); cursor: pointer;
}
.admin-edit-tab:hover { color: var(--hi); background: var(--inset); }
.admin-edit-tab.is-on { color: var(--amber); background: var(--inset); border-left-color: var(--amber); }
.admin-edit-body { flex: 1 1 auto; min-width: 0; max-height: 58svh; overflow-y: auto; padding-right: 4px; }
/* `hidden` is only a default; state it, so a later display rule on a pane
   can't leave an inactive tab's contents on screen. */
.admin-edit-pane[hidden] { display: none; }
.admin-edit-note { color: var(--dim); font-size: var(--fs-cap); margin: 0 0 12px; line-height: 1.5; }
.admin-edit-note b { color: var(--hi); font-weight: normal; }
.admin-edit-foot { display: flex; justify-content: flex-end; margin-top: 14px; }
/* dialog.portal-modal .btn carries margin-top:20px in this sheet; the footer
   supplies the gap, so cancel it inside the editor. */
.admin-edit-modal .btn { margin-top: 0; }

/* Fields are label-above-input. Stated on the span because a bare `label`
   here is inline, which put every caption beside its box.

   align-items:end bottom-aligns a row's inputs, so a caption that wraps to
   two lines can never shove its own box below its neighbour's again. */
.admin-edit-form {
  display: grid; grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: 14px 12px; align-items: end;
}
.admin-edit-field { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.admin-edit-field > span {
  color: var(--dim); font-size: var(--fs-cap); font-family: var(--mono);
  letter-spacing: .06em; text-transform: uppercase;
}
.admin-edit-field input, .admin-edit-field textarea {
  width: 100%; box-sizing: border-box; background: var(--inset); color: var(--hi);
  border: 1px solid var(--line); border-radius: 2px; padding: 6px 8px;
  font-family: var(--mono); font-size: var(--fs-body);
}
.admin-edit-field input:focus, .admin-edit-field textarea:focus {
  outline: none; border-color: var(--line-hi);
}
.admin-edit-field textarea { resize: vertical; min-height: 150px; line-height: 1.5; }
.admin-edit-field input[type="file"] { padding: 5px; color: var(--txt); }
/* Row 1: title + year + runtime. Row 2: director + cast. Then full width. */
.admin-edit-field--main { grid-column: span 4; }
.admin-edit-field--half { grid-column: span 3; }
.admin-edit-field--wide { grid-column: 1 / -1; }
.admin-edit-actions { display: flex; gap: 8px; align-items: center; margin-top: 4px; }
.admin-edit-row { display: flex; gap: 8px; align-items: flex-end; margin: 0 0 12px; }
.admin-edit-row input[type="search"], .admin-edit-row input[type="number"] {
  flex: 1 1 auto; min-width: 0; background: var(--inset); color: var(--hi);
  border: 1px solid var(--line); border-radius: 2px; padding: 6px 8px;
  font-family: var(--mono); font-size: var(--fs-body);
}
.admin-edit-row input:focus { outline: none; border-color: var(--line-hi); }
.admin-edit-row .admin-edit-field { flex: 1 1 auto; }

/* Candidates are rows, not a poster grid: choosing a match means reading
   runtime and director, not looking at pictures. */
.admin-match-list { display: flex; flex-direction: column; gap: 8px; margin: 0 0 14px; }
.admin-match-option {
  display: flex; gap: 12px; align-items: flex-start; padding: 10px;
  border: 1px solid var(--line); border-radius: 2px; background: var(--inset);
}
.admin-match-option:hover { border-color: var(--amber); }
.admin-match-option img, .admin-match-noart {
  width: 46px; height: 69px; flex: none; display: block; border-radius: 2px; object-fit: cover;
}
.admin-match-noart { background: var(--panel); border: 1px solid var(--line); }
.admin-match-body { display: flex; flex-direction: column; gap: 3px; min-width: 0; flex: 1 1 auto; }
.admin-match-name { color: var(--hi); font-size: var(--fs-body); }
.admin-match-overview {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; opacity: .8;
}
/* The runtime gap is the signal, so it gets the only colour in the row. */
.match-near { color: var(--green); }
.match-far { color: var(--red); }

.admin-artwork-slot { display: flex; gap: 10px; align-items: flex-end; flex-wrap: wrap; margin: 0 0 12px; }

/* Narrow enough that six columns would squeeze the year box: keep the two
   short fields paired, let everything else run full width. */
@media (max-width:700px) {
  .admin-edit-field--main, .admin-edit-field--half { grid-column: 1 / -1; }
  .admin-edit-field { grid-column: span 3; }
}
@media (max-width:560px) {
  .admin-edit { flex-direction: column; gap: 10px; }
  .admin-edit-tabs { flex-direction: row; flex-wrap: wrap; width: auto; }
  .admin-edit-tab { border-left: none; border-bottom: 2px solid transparent; }
  .admin-edit-tab.is-on { border-bottom-color: var(--amber); }
  .admin-edit-form { grid-template-columns: minmax(0, 1fr); }
  .admin-edit-field, .admin-edit-field--main, .admin-edit-field--half { grid-column: 1 / -1; }
}

/* The admin editor's handle on the poster. Subtle by default and solid on hover,
   rather than hover-only: a hover-only control is invisible on a phone,
   where it would simply not exist. */
.poster-edit {
  position: absolute; right: 8px; bottom: 8px; z-index: 2;
  width: 28px; height: 28px; padding: 0; border: none; border-radius: 50%;
  background: rgba(0, 0, 0, .62); color: #fff; font-size: 13px; line-height: 28px;
  text-align: center; cursor: pointer; opacity: .5; transition: opacity .15s ease;
}
.poster:hover .poster-edit, .poster-edit:hover, .poster-edit:focus-visible { opacity: 1; }


/* C10: the founding-member mark - a gold ring on the profile picture. The
   avatar already carries a 1px --line-hi border here, so this replaces it
   rather than stacking, and the size is unchanged. */
.profile-head .pava.is-founding,
.profile-head .avatar.is-founding {
  border: 2px solid var(--amber);
}


/* ── the counter (spec 34 M-CLERK-f) ───────────────────────────────────────────
   Brent's tile: fixed warm paper, chosen not eyeballed - his silhouette
   is #070808 and only the silhouette has to fight the ground (cream,
   yellow and blue are interior, bounded by that outline). Paper puts
   the black at 13.5:1 where the dark themes had it at 1.04:1. Fixed
   across themes; the border is the part that follows the theme. */
.brent { flex: none; background: #d9d4c2; border: 1px solid var(--line-hi);
         padding: 6px; line-height: 0;
         animation: brent-idle 5.5s ease-in-out infinite; }
.brent-svg { display: block; width: 100%; height: auto; }
.brent-lids rect { fill: #f4f1c2; opacity: 0;
                   animation: brent-blink 6.5s steps(1) infinite; }
@keyframes brent-blink { 0%,96% { opacity: 0 } 97%,99% { opacity: 1 } 100% { opacity: 0 } }
@keyframes brent-idle  { 0%,100% { transform: translateY(0) } 50% { transform: translateY(-2px) } }
@media (prefers-reduced-motion: reduce) {
  .brent, .brent-lids rect { animation: none }
}
.brent--sm { width: 56px } .brent--md { width: 76px } .brent--lg { width: 100px }

/* The dialogue window: Brent speaks in the same inset strip under the
   titlebar in every state, ruled off from the body; he never appears
   below the rule. Facts about the session live in the chrome bars. */
.counter-say { display: flex; gap: 16px; align-items: flex-start;
               padding: var(--pad); background: var(--inset);
               border-bottom: 1px solid var(--line); }
.counter-say .said { min-width: 0; padding-top: 2px; }
.counter-say .who { font-family: var(--mono); font-size: 10px; letter-spacing: 1px;
                    color: var(--amber); text-transform: uppercase;
                    display: block; margin-bottom: 6px; }
.counter-say .line { color: var(--hi); font-size: 15px; line-height: 1.5; margin: 0; }
.counter-say .aside { color: var(--dim); font-size: 13px; margin: 8px 0 0; }

/* Your lines: an RPG response list. You answer Brent in dialogue;
   buttons exist only on objects (a film row you can take), never as
   answers to him - that grammar is load-bearing. The cursor glyph is
   dim on every line so touch users see a selectable list, amber and
   indented on the line under the pointer or keyboard focus. */
.dlg { margin: 0; padding: 0; list-style: none; max-width: 46ch; }
.dlg button, .dlg a { display: block; width: 100%; text-align: left;
      background: none; border: 1px solid transparent; cursor: pointer;
      font-family: var(--mono); font-size: 13px; letter-spacing: .4px;
      color: var(--txt); padding: 9px 12px 9px 30px; position: relative;
      text-decoration: none; transition: padding-left .1s ease; }
.dlg button::before, .dlg a::before { content: "▸"; position: absolute;
      left: 12px; color: var(--dim); }
/* The hover here MOVES the row (padding-left 30 -> 36). On touch the last
   thing tapped keeps its hover state, so answering the clerk left one
   option permanently indented and lit, reading as a selection that had
   not been made. focus-visible is not guarded - a keyboard user wants it. */
@media (hover:hover){
  .dlg button:hover, .dlg a:hover {
        color: var(--amber); background: var(--inset);
        border-color: var(--line); padding-left: 36px; }
  .dlg button:hover::before, .dlg a:hover::before { color: var(--amber); }
}
.dlg button:focus-visible, .dlg a:focus-visible {
      color: var(--amber); background: var(--inset);
      border-color: var(--line); padding-left: 36px; }
.dlg button:focus-visible::before, .dlg a:focus-visible::before {
      color: var(--amber); }

/* the clerk's answers are the primary control of this whole flow */
@media (max-width:700px){
  .dlg{max-width:none}
  .dlg button, .dlg a{min-height:44px;padding:12px 12px 12px 30px}
  /* Scoped to the shortlist row, not .btn at large: "Take it" was
     breaking across three lines in a 70px box. There is room for it on
     one - the row is 337px and the thumb takes 34 - but a blanket nowrap
     would push some longer label off the side of some other page. */
  .counter-pick .btn{white-space:nowrap}
}

/* footer bar: .tbar with its rule on the other edge */
.tbar--foot { border-bottom: 0; border-top: 1px solid var(--line); }

/* The offer: film-page treatment - backdrop underlay, poster pulled up
   into it, title in the display face. Borderless; the panel owns the
   edges. */
.offer-drop { border: 0; }
.offer-head { display: grid; grid-template-columns: auto 1fr; gap: 16px;
              padding: var(--pad); align-items: start; }
/* The poster spans the whole offer: top pulled -84px into the backdrop,
   bottom flush with the last dialogue line. Height is the row's plus the
   pull-up; width follows from the 2/3 ratio (capped so a wordy overview
   cannot balloon it - object-fit crops once the cap bites). */
.offer-head .poster { margin-top: -84px; position: relative; z-index: 2;
                      border: 1px solid var(--line-hi); line-height: 0;
                      height: calc(100% + 84px); aspect-ratio: 2/3;
                      min-width: 110px; max-width: 200px;
                      background: var(--inset); }
.offer-head .poster img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* the letter tile when the poster art is missing or 404s - .poster-fallback
   is only styled inside .shot cards, so it needs its own rules here */
.offer-head .poster .poster-fallback { display: flex; width: 100%; height: 100%;
                 align-items: center; justify-content: center;
                 font-family: var(--dot); font-size: 34px; color: var(--line-hi);
                 line-height: 1; }
.offer-head h2 { font-family: var(--dot); font-size: 26px; line-height: 1.1;
                 margin: 0; color: var(--hi); letter-spacing: .5px; }
.offer-head .yr { color: var(--dim); font-size: 16px; }
/* no backdrop on file -> no hero: the poster row, not a second design.
   No pull-up here, and no stretch either - the hero's calc(100% + 84px)
   against this grid row is self-referential without the hero to absorb
   it and balloons past the panel (the Bee Movie bug). Plain fixed
   poster instead. */
.offer-head--flat .poster { margin-top: 0; height: auto; width: 130px; }

/* The offer on a phone (2026-07-30, reported from a real device). The
   auto/1fr split held at 390px, so the poster took its 200px cap and left
   the copy about 145px: the title wrapped onto four lines, the credits
   broke mid-name, the overview became a ribbon and the three answers
   stacked into a column narrower than their own labels.

   One column, and the poster stops being the row's full height. That
   height:calc(100% + 84px) is only meaningful while the poster sits
   BESIDE the copy - once it is its own grid row the calc is
   self-referential and balloons, which is the same trap .offer-head--flat
   above already documents (the Bee Movie bug). Fixed width, ratio does
   the rest, and the pull-up matches the film head's at this width. */
@media (max-width:700px){
  .offer-head{grid-template-columns:minmax(0,1fr);gap:12px}
  .offer-head .poster{
    height:auto;width:120px;min-width:0;max-width:120px;
    margin-top:-56px;justify-self:start;
  }
  .offer-head--flat .poster{margin-top:0}
  .offer-head h2{font-size:22px}
}

/* 1b's chips are inputs: same chip, selectable. */
.chip-pick { cursor: pointer; position: relative; }
.chip-pick input { position: absolute; opacity: 0; pointer-events: none; }
.chip-pick:has(input:checked) { border-color: var(--amber); color: var(--amber);
                                background: rgba(232,163,61,.08); }

/* state 5's shortlist thumbs */
.counter-pick .mc-thumb { width: 34px; height: 50px; overflow: hidden; }
.counter-pick .mc-thumb img { width: 100%; height: 100%; object-fit: cover; }

/* ---------- welcome: Brent's hello (spec 36 M-NM-h) ---------- */
.welcome-intro{display:flex;gap:20px;align-items:flex-start;flex-wrap:wrap}
.welcome-hello{flex:1 1 320px;min-width:0;color:var(--txt)}
.welcome-hello p{margin:0;font-size:var(--fs-body);line-height:1.6}
.welcome-hello p + p{margin-top:var(--gap-text)}

/* ---------- landing: the two CTA columns ---------- */
.landing-cols{
  display:flex;gap:56px;justify-content:center;margin-top:32px;
  flex-wrap:wrap;
}
/* Centred, like everything else on this page. Left-aligned columns read
   as a stray block against a hero that is centred top to bottom. */
.landing-col{
  display:flex;flex-direction:column;align-items:center;gap:10px;min-width:170px;
}
.landing-col-head{
  font-family:var(--mono);font-size:var(--fs-chrome);color:var(--amber);
  letter-spacing:2px;text-transform:uppercase;margin-bottom:2px;
}
.landing-link{
  display:flex;align-items:center;justify-content:center;gap:10px;
  color:var(--txt);font-size:14px;line-height:1.4;text-decoration:none;
  padding:2px 0;
}
.landing-link:hover{color:var(--hi)}
.landing-link .brandmark{width:16px;height:16px;flex:none}
@media (max-width:560px){
  .landing-cols{gap:28px;flex-direction:column;align-items:center}
  .landing-col{min-width:0}
}
