/*
 * Phone page — the look.
 *
 * The shipped site palette and type ramp, with cream keys and a teal accent.
 * `--me` belongs to the guest: the ring around their name, not a second theme.
 *
 * Two structural rules that are load-bearing rather than stylistic, and that
 * cost real rounds elsewhere:
 *
 *   1. `touch-action: none` is declared on `html, body`, not only on controls.
 *      The property does not inherit, but the browser intersects the declared
 *      values up the ancestor chain when it decides what a gesture may do — so
 *      the ancestor declaration is the one that actually prevents
 *      pull-to-refresh and double-tap-zoom from swallowing a gameplay gesture.
 *      A control-only declaration looks equivalent and is not.
 *
 *   2. Every scrollable region re-enables exactly the axis it needs
 *      (`touch-action: pan-y`). Scrolling is opt-in here, not opt-out, and the
 *      only regions that get it are the ones that can legitimately overflow:
 *      the card strip, the lobby, and a sheet.
 *
 * Safe-area insets are spent deliberately at the bottom (the home indicator
 * sits exactly where a thumb rests) and at the top (the notch). They only
 * report real numbers because of `viewport-fit=cover` in the HTML.
 */

/* The design system — tokens and the closed ui-* component set — lives in its
   own short file so page code can read it whole. An @import must precede every
   other rule, which is why it is here and not beside the frame rules below. */
@import url("phone-system.css?v=frame1");

:root {
  /* homie.rocks identity, 2026-09-05. Seat colour is a ring, house teal is the accent. */
  --night: #020307;
  --cream: #f4efe6;
  --ink: #16140f;
  --lamp: #3ee0c8;
  --lamp-rgb: 62, 224, 200;
  --live: #ff6b6b;
  --success: #6fcf5f;
  --warning: #ffc857;
  --glass-rgb: 6, 10, 17;
  --glass-a: .52;
  --glass-blur: 28px;
  --glass-bright: .88;
  --line: rgba(214, 198, 166, .13);
  --lip: rgba(255, 249, 236, .13);
  --cut: 3px 18px 3px 18px;
  --card-shadow: inset 0 1px 0 var(--lip);
  --card: rgb(var(--glass-rgb) / .92);
  --font: "Bricolage Grotesque", ui-rounded, "SF Pro Display", system-ui, sans-serif;
  --mono: ui-monospace, "SF Mono", "IBM Plex Mono", Menlo, monospace;
  --cry: clamp(2.55rem, 11vw, 6.4rem);
  --h2: clamp(1.55rem, 3.2vw, 2.3rem);
  --h3: clamp(1.15rem, 2vw, 1.35rem);
  --dek: clamp(1.05rem, 2.6vw, 1.35rem);
  --kicker: .72rem;
  --body: 1rem;
  --fine: .875rem;
  --bg: var(--night);
  --bg-2: rgb(var(--glass-rgb));
  --bg-3: color-mix(in srgb, var(--cream) 6%, var(--night));
  --ink-2: color-mix(in srgb, var(--cream) 78%, transparent);
  --ink-3: color-mix(in srgb, var(--cream) 62%, transparent);
  --line-2: color-mix(in srgb, var(--cream) 24%, transparent);
  --me: var(--lamp);
  --go: var(--lamp);
  --stop: var(--live);
  --motion: 180ms ease-out;
  /* ------------------------------------------------------------------
     THE PLATE'S OWN COOL LIGHT, as an rgb triplet, and this is the FLOOR.

     `phone-plate-light.js` samples the photograph the phone is hanging and
     writes this property inline on `documentElement` — which is the element
     `:root` matches, so its value wins and REMOVING it restores exactly this
     line. What stands here is therefore the answer for a plate that could not
     be read (a cross-origin poster taints the canvas) and for a phone with no
     picture at all: the house teal, a colour somebody wrote down on purpose.

     Read `html[data-plate-sky]` to tell `read` from `none` from `unread` —
     "we could not measure it" and "we measured it and it was fine" must never
     render as the same colour, and here they render as the same colour on
     purpose, so the difference is published as data instead.
     ------------------------------------------------------------------ */
  --plate-sky-rgb: 59, 232, 199;
  --radius: var(--cut);
  --tap: 64px;               /* smallest thing a thumb is ever asked to hit */

  /* ==================================================================
     THE PHONE'S TYPE SCALE — pinned, because two rounds of it were not
     ==================================================================

     ART_DIRECTION's Type table is the television's: a 137 px cry, a 43 px dek,
     33 px keys, all reasoned from *"ten feet is the unit"*. The phone's unit is
     **arm's length**, roughly 14 inches, so none of those numbers transfer and
     for three rounds nothing stood in their place. What that cost, measured:

       · `span.me__house` shipped at 28 px and `.offer__title` at 26, so the
         largest type on the arrival frame was the name of the house and the
         second largest was what the room was actually offering. Two rules
         written a round apart, neither re-measured, both individually defensible.
       · the room's own question shipped at 22/650 pure white — close enough to
         the 26 px subject that the round-2 judge read the frame as *"four
         content systems"* and could not say which proposition was the real one.

     So this is the scale, and the rule that goes with it is the one
     ART_DIRECTION already states for the other surface: **exactly one thing on
     a frame is set at `--ph-cry`, and it is the subject of the picture.**
     Everything else is furniture for reaching it. A new role does not get a new
     number; it gets the nearest one here, or this table gets an argued row.

       --ph-cry        the subject. One per screen, never two
       --ph-qualifier  what the subject is OF — the house's name. Under the cry
       --ph-dek        the box's own voice, and any proposal it makes
       --ph-key        a word on the PRIMARY thing you press
       --ph-key-2      a word on a secondary key — an opener, a reply
       --ph-fine       a supporting fact in prose
       --ph-kicker     the band of facts. Small on purpose: uppercase and
                       letterspaced reads a full step larger than it measures

     Nothing a guest must read is under `--ph-kicker`, and nothing is set at
     `--ph-kicker` that they must read to use the room. */
  --ph-cry: var(--h2);
  --ph-qualifier: var(--h2);
  --ph-dek: var(--dek);
  --ph-key: var(--dek);
  --ph-key-2: var(--body);
  --ph-fine: var(--fine);
  --ph-kicker: var(--kicker);
  --safe-t: env(safe-area-inset-top, 0px);
  --safe-b: env(safe-area-inset-bottom, 0px);
  --safe-l: env(safe-area-inset-left, 0px);
  --safe-r: env(safe-area-inset-right, 0px);
  color-scheme: dark;
}

* { box-sizing: border-box; }

/* `hidden` has to actually hide, and on this page it did not.

   The `[hidden] { display: none }` everyone relies on lives in the *user agent*
   stylesheet, and any author rule beats the whole UA sheet regardless of
   specificity. So `.link { display: flex }` quietly made `ui.link.hidden = true`
   a no-op, and every phone in the room showed "Reconnecting…" for the entire
   evening while perfectly connected. A permanent, false, alarming banner is
   worse than no banner. `!important` is right here and nowhere else: an element
   asked to be hidden is not a layout question. */
[hidden] { display: none !important; }

html, body {
  margin: 0;
  height: 100%;
  background: var(--bg);
  color: var(--cream);
  font: 400 var(--body)/1.45 var(--font);
  overflow: hidden;
  overscroll-behavior: none;
  touch-action: none;                    /* see note 1 above */
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
  -webkit-tap-highlight-color: transparent;
  -webkit-text-size-adjust: 100%;
}

/* The accent wash. A pseudo-element with opacity rather than color-mix(), so it
   renders identically on an older guest phone. Seat colour, not a word: the
   phone belongs to a person before anything is written on it. */
body::before {
  content: "";
  position: fixed;
  inset: -20% -40% auto -40%;
  height: 78%;
  background:
    radial-gradient(46% 52% at 50% 28%, var(--me), transparent 72%),
    radial-gradient(80% 50% at 50% 100%, color-mix(in srgb, var(--night) 90%, transparent), transparent 70%);
  opacity: 0.22;
  pointer-events: none;
  z-index: 0;
  transition: opacity 400ms ease;
}

/* While a surface this box serves holds the glass, even the wash is the AI's
   to spend. A parent glow leaking around a transparent iframe is chrome. */
body[data-bleed="1"]::before { opacity: 0; }

/* Rest glass is still. The seat-colour wash was a teal blob dying into a
   black slab — 86% of the frame under lum 20 plus a chevron and a composer,
   which is a remote-sheet launcher. If the room has a picture that picture
   is the phone; if it does not, nothing of ours paints. */
body[data-screen="lobby"]::before { opacity: 0; }

button, input, textarea { font: inherit; color: inherit; }
button { -webkit-appearance: none; appearance: none; background: none; border: 0; cursor: pointer; }
button:disabled { cursor: default; }

/* Touch is the common path, never the only path. A Bluetooth remote, switch
   control, keyboard, or browser accessibility cursor must leave a visible
   position on every real control—not just on the three text fields that carry
   their own focus material. `:focus-visible` keeps the ring out of ordinary
   taps while preserving it for non-pointer navigation. */
:where(button, input, textarea, select, [role="button"], [role="slider"], [tabindex]):focus-visible {
  outline: 3px solid var(--go);
  outline: 3px solid color-mix(in srgb, var(--go) 76%, white 18%);
  outline-offset: 3px;
}
@supports not selector(:focus-visible) {
  :where(button, input, textarea, select, [role="button"], [role="slider"], [tabindex]):focus {
    outline: 3px solid var(--go);
    outline-offset: 3px;
  }
}

/* One preference, one promise. The page had six local reduced-motion rules and
   still let any newly added transition or pulse slip through. None of the
   phone's state machines depends on transition/animation events, so reducing
   every authored motion to one imperceptible frame is both safe and complete. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    transition-delay: 0ms !important;
  }
}

/* ========================================================================== */
/* Shell                                                                      */
/* ========================================================================== */

/* Flex column rather than `grid-template-rows: auto auto minmax(0,1fr)`.
   The grid version pinned the 1fr to the *third* child, and two of this shell's
   children come and go — the reconnect bar and the notice. With both hidden the
   stage was third and filled; with the reconnect bar visible the notice became
   third and grew to fill the screen while the stage fell into an implicit auto
   row. That was latent rather than harmless: the reconnect bar had never once
   appeared on this build (see phone-net.js STATUS_TICK_MS), so the broken case
   was simply unreachable, and the change that makes the bar work is the change
   that would have made this visible in front of guests. Flex has no positional
   dependency at all. */
#app {
  position: relative;
  z-index: 1;
  height: var(--vvh, 100dvh);
  display: flex;
  flex-direction: column;
  padding: var(--safe-t) var(--safe-r) 0 var(--safe-l);
}

/* FULL-BLEED. Every CSS pixel of the glass belongs to the surface the box
   serves: no header, no bar, no notice, no safe-area padding. The surface page
   spends the safe area itself — the injected HEAD in `phone-surface.ts` already
   does — so padding here would be the second inset nobody asked for.

   This is the switch the whole round is about. `.top` used to be `min-height:
   54px` above `#app`'s `padding-top`, which on a notched iPhone is roughly
   100–113 pt of an ~844 pt screen: about an eighth of the AI's private surface,
   permanently, so that a guest could be told something they already knew. */
body[data-bleed="1"] #app { padding: 0; }
body[data-bleed="1"] .link,
body[data-bleed="1"] .notice { display: none !important; }
/*
 * Safety net, not a second parent. `hostNow` / `hostWayIn` / `hostDeck` move
 * these nodes into the curtain when the stage is no longer ours. If a future
 * screen-change forgets one, a title or a row of keys sitting in `.stage`
 * would be chrome over the iframe — the photograph this switch exists to
 * make impossible. The curtain is a sibling of `.stage`, not a child, so a
 * summoned shell still draws identity, transport and the way back.
 */
body[data-bleed="1"] .stage .now,
body[data-bleed="1"] .stage .wayin,
body[data-bleed="1"] .stage .deck,
/* `hostSeat` moves the identity chip off the lobby the moment the screen stops
   being ours, exactly as the three above are moved. It is on this list for the
   same reason they are: if a future screen-change forgets one of them, a pill
   with a guest's name in it would be OUR pixel sitting on the AI's private
   full-bleed surface, which is the one thing this switch exists to make
   impossible. */
body[data-bleed="1"] .stage .chip,
body[data-bleed="1"] .stage .lobby { display: none !important; }
/*
 * WHILE BLEED=1 THE PARENT PAINTS NO PIXEL. The work ring, the
 * handle and the shadow host are the indigo spinner the critic
 * read on phone-show-seat. Progress lives in the summon. The
 * fallback handle over a surface that never armed is not this:
 * that host carries data-fallback=1 and stays a way back.
 */
/* Hide the handle only inside a private surface. On the lobby
   plate it is the way into the shell. */
body[data-bleed="1"] .shell-anchor-host:not([data-fallback="1"]) {
  display: none !important;
}
body[data-screen="lobby"]:not([data-frame="tabs"]) .shell-anchor-host {
  display: block !important;
  pointer-events: auto;
}

/* Fallback for browsers without dvh: 100% of a fixed-height body. */
@supports not (height: 100dvh) { #app { height: 100%; } }

/* The permanent header is GONE, and this is the note that replaces 130 lines
   of stylesheet.

   `.top`, `.top__title`, `.top__right` and `.iconbtn--remote` styled a bar that
   sat above whatever the AI had put on the glass, for ever, on the one surface
   whose entire claim is that it is private and full-bleed. Its own comment
   justified it as "so a guest always knows they are still in the room" — a
   thing said continuously, to people who already know it, at a cost paid every
   second by the thing that makes this box special.

   Identity, the remote and liveness did not disappear with it: identity and
   transport are behind the summon (`.shell` below), and liveness is the anchor,
   which is only on screen when it has something to say.

   `.chip` and `.iconbtn` survive because the panel uses them. */

/* The one that dismisses a panel. Same 44 pt as the rest, with a bigger glyph
   because it is the one people aim at from across a thumb. Back and the
   backdrop both do the same thing; see `openSheet`. */
.iconbtn--close { font-size: var(--h3); }

/* THE STATUS DOT IS GONE, AND THESE FIVE RULES WENT WITH IT — 2026-08-13.

   `.status` was the green/amber/orange dot beside the old resident header. The
   header was evicted (see the note above); the dot went with it and **nothing
   in `packages/phone/` has applied the class since**. The rules survived, and a
   stylesheet that still styles a dead class is not inert — it is evidence. Two
   harnesses were still reading the element it implies, both silently:

     · `tools/join-a-guest.mjs` asserted `.status` carried `is-live` and
       therefore reported "the control socket is not live — the phone hung up on
       a working daemon" on EVERY run, about a live socket. Repointed.
     · `tools/stuck-hunt.mjs` (`recovers-unattended`) waited for
       `.status.className.includes("is-live")` before it would call the phone
       recovered — a condition that could never be true, so the check could only
       ever time out. Repointed: it now reads the anchor's `data-state` and the
       `.link` bar, asserts the socket went DOWN before asserting it came back,
       and gained a fault of its own (`reconnect-never-returns`) where it had
       none. The same pass found four more rules of the same family in that file
       and one much worse thing in a neighbouring check — five hostile-name
       payloads that had never reached the input, so a security check had been
       green without once running its attack.

   Both readers are repaired. This block stays as the record of what a dead
   class cost: two harnesses reporting confidently about an element that had not
   existed for weeks, in opposite directions, neither of them noticed.

   The three states those rules drew are not lost, and their distinction — which
   the deleted comment was right to protect — is drawn by the anchor now:
   `paintAnchor()` (`phone-shell.js:518`) maps `retrying`/`stalled` to
   `data-state="down"`, and `.link` below is the second, louder tier. `unsure`
   keeps its own appearance there for the reason the old comment gave: "we could
   not measure it" and "we measured it and it was fine" must never render as the
   same colour.

   `@keyframes blink` went too: the two `.status` rules above were its only
   users, and a keyframe kept "in case" is the next dead rule somebody reasons
   from. `.link__spin` has its own `spin`, and the anchor's own animations live
   in `ANCHOR_CSS` inside its shadow root. */

/* The reconnect line. Deliberately thin and late — a blip must not paint
   anything at all, or the controller looks unreliable when it is merely a LAN. */
.link {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0 14px 6px;
  padding: 9px 14px;
  border-radius: 12px;
  background: color-mix(in srgb, var(--warning) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--warning) 24%, transparent);
  font-size: var(--fine);
  color: var(--warning);
}
.link.is-stalled { background: color-mix(in srgb, var(--live) 12%, transparent); border-color: color-mix(in srgb, var(--live) 26%, transparent); color: var(--cream); }
.link__spin {
  width: 12px; height: 12px; flex: none;
  border-radius: 50%;
  border: 2px solid currentColor;
  border-top-color: transparent;
  animation: spin 900ms linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* What is switched off tonight, said above whatever the guest is doing.

   Deliberately NOT a wall and deliberately NOT dismissable. It is the only
   thing standing between a guest and a capability that fails silently in the
   browser — tilt, mic and camera return nothing at all on plain http, with no
   error anywhere — so it stays on screen for as long as it is true. It is
   styled quieter than `.link` because `.link` is transient and urgent, and
   this is permanent and merely honest; the same colour at the same weight for
   the whole evening reads as an error the guest has to fix. */
.notice {
  margin: 0 14px 6px;
  padding: 9px 14px;
  border-radius: 12px;
  background: color-mix(in srgb, var(--warning) 8%, transparent);
  border: 1px solid color-mix(in srgb, var(--warning) 18%, transparent);
  font-size: var(--fine);
  line-height: 1.35;
  color: var(--cream);
  display: flex;
  flex-direction: column;
  gap: 5px;
}
.notice__line { margin: 0; }

/* `min-width: 0` IS THE PLATFORM'S ONLY DEFENCE AGAINST A PACKAGE'S OWN WIDTH.

   `.stage` is a flex ROW, so every column inside it — the lobby, the play
   screen — is a flex item, and a flex item's default `min-width: auto` floors
   it at the *min-content width of everything inside it*. Inside the play
   screen, "everything inside it" includes a card a package wrote, which this
   box does not author, review or bound. So a package with one wide row sets
   the width of the column that the platform's own chrome sits in, and the
   chrome — the game's name, the way into the shell, and the way out — is
   stretched off the right-hand edge of the glass with it.

   MEASURED on this daemon, Traitor at Dinner on the television, one phone:
   at 360, 344, 320, 300 and 280 pt of viewport, `.play` was 372 pt wide every
   time — its own min-content, not the phone's — and `Leave this` ended at
   x 358. `body { overflow-x: hidden }` means `document.scrollWidth` stays
   equal to `innerWidth`, so the part hanging over the bezel cannot be scrolled
   to: at 320 pt, 38 pt of the only visible way out of a game does not exist.
   The walkthrough measured the same shape one card wider — a generated game
   whose card floored `.play` at 435 — which puts the exit off EVERY phone
   narrower than 421: iPhone SE, 15, 16 and 16 Pro. Only the Pro Max was safe.

   So the column is sized by the glass and never by its contents. The package's
   own card is clipped at exactly the same edge it is clipped at today — that
   part does not change and is the package's bug to fix — but the platform is
   no longer the thing that gets pushed over the side by it. */
.stage { position: relative; flex: 1; min-width: 0; min-height: 0; display: flex; }

/* In the lobby the notice sits under everything instead of over it.
   Nothing is hidden and nothing needs a tap — it is the same text, in the same
   place in the reading order a guest would look for it, rather than the first
   thing they read. On the play screen it stays at the top, because that is
   where it is *actionable*: a tilt or mic control is on the screen, drawn
   switched off, and the banner is the sentence that explains the card they are
   looking at. Guest critic, round 1: "two developer warning banners are the
   first thing every guest reads." */
body[data-screen='lobby'] .notice {
  order: 3;
  margin: 2px 14px calc(10px + var(--safe-b));
  background: none;
  border: 1px solid var(--line);
  color: var(--ink-3);
}

/* ========================================================================== */
/* Lobby                                                                      */
/* ========================================================================== */

/* The lobby is the Now tab's root; its rules are in the NOW block below. */

/* `.me` — the identity card that used to open the lobby — is gone from the
   arrival screen entirely. A guest configures nothing, so there is nothing to
   lay out here; the name field and the swatches live in the shell's identity
   panel and are styled with it. `.me__name` and `.me__eyebrow` are kept below
   because the panel still uses them. */

/* The join state, drawn.

   This used to hold two English strings ("getting you in…" / "you are in") and
   they are now one mark: a hollow ring while the handshake is in flight, filled
   with the guest's own colour the instant `welcome` lands. Filling IS the
   announcement — it is instant, it reads at arm's length across a loud room,
   and it is the same signal in every language the sentence was not.

   The pulse is deliberately slow. A fast one reads as an error. */
.me__eyebrow {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 14px;
  font-size: var(--kicker);
  font-weight: 620;
  letter-spacing: 0.14em;
  color: var(--ink-3);
}
.me__eyebrow::before {
  content: "";
  width: 12px; height: 12px;
  border-radius: 50%;
  border: 2px solid var(--line-2);
  background: transparent;
  transition: background 180ms ease, border-color 180ms ease;
}
.me__eyebrow[data-state="joining"]::before { animation: joinpulse 1.6s ease-in-out infinite; }
.me__eyebrow[data-state="joined"]::before {
  background: var(--me, var(--go));
  border-color: var(--me, var(--go));
  animation: none;
}
@keyframes joinpulse {
  0%, 100% { opacity: 0.35; }
  50% { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .me__eyebrow[data-state="joining"]::before { animation: none; opacity: 0.55; }
}

.roster { display: flex; flex-direction: column; gap: 10px; }
/* `.roster__head` — the count over the pills — is gone from the markup; see
   `paintRoster`. `.roster__empty` never existed in this file's markup either;
   both rules went with the number. The list itself lives in the identity panel
   now, so it inherits the panel's own spacing. */
.roster__list { display: flex; flex-wrap: wrap; gap: 8px; }
/* Same glass as the chip it is drawn beside, because it is the same fact about a
   different person — and a panel in which "me" is made of one material and
   "everybody else" is made of another is drawing a distinction the room does not
   have. It was `var(--bg-2)` with a 1 px border, which is the flat pill the
   lobby chip's own block already calls the "website on a screen" tell. */
.pill {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 40px;
  padding: 8px 14px 8px 10px;
  border-radius: 999px;
  border: 0;
  background: linear-gradient(180deg, color-mix(in srgb, var(--bg-2) 60%, transparent), color-mix(in srgb, var(--bg-2) 72%, transparent));
  box-shadow:
    inset 0 1px 0 color-mix(in srgb, var(--cream) 12%, transparent),
    inset 0 0 0 1px color-mix(in srgb, var(--cream) 14%, transparent),
    0 4px 12px color-mix(in srgb, var(--night) 32%, transparent);
  font-size: var(--fine);
  max-width: 100%;
}
/* The dot is a bead, the same three-layer object as a swatch, so a person in the
   list and a colour on the sheet are visibly the same kind of thing. */
.pill__dot {
  width: 10px; height: 10px; border-radius: 50%; flex: none;
  background-image: radial-gradient(120% 120% at 32% 26%,
    color-mix(in srgb, var(--cream) 45%, transparent) 0%, color-mix(in srgb, var(--cream) 8%, transparent) 40%, color-mix(in srgb, var(--night) 26%, transparent) 100%);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--night) 35%, transparent);
}
.pill__name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pill.is-nearby, .pill.is-gone { opacity: 0.45; }

/* The lobby is one field now, so there is nothing left here to lay out in two
   columns on a short screen. `.seat`, `.create` and their whole media query went
   with the regions they styled — see `renderLobby` for what left and why.

   `.seat__private*` and `.seat__glass` went with them: they styled title/lines/
   options cards that this page stopped drawing when a seat's content became a
   free HTML surface. */

/* Seat ordinal — the same node as the chip's name, in the identity panel behind
   the summon.

   It is the guest's identity when they have no name (THE_SHELL §6: colour plus
   seat ordinal, both machine facts) and it is what the television renders for
   them. It reads because of where it stands: inside the chip, beside the colour
   dot that is the other half of the same identity, above the field that changes
   it. On the arrival screen, beside a headcount and a roster count, it was one
   of three bare digits on one screen — and it was drawn twice.

   `.seat__num` carries only what the chip does not: the guest's own colour, so
   a nameless person's ordinal is the same colour as their tile on the glass. */
.seat__num { color: var(--me, var(--cream)); }
.seat__num:empty { display: none; }

/* Fault-only / legacy class names kept so the two named fault branches paint:
   `dead-lobby`'s waiting paragraph and `two-ways-to-say-something`'s second
   field. Neither is on any path a guest can reach. */
.waiting__head { margin: 0; color: var(--cream); font-size: var(--dek); font-weight: 600; }
.waiting__sub { margin: 0; color: var(--ink-3); font-size: var(--fine); line-height: 1.45; }
.create { display: flex; align-items: flex-end; gap: 10px; }
.create__brief { flex: 1; min-height: 82px; padding: 12px 13px; border: 1px solid var(--line-2); border-radius: 12px; background: var(--bg); color: var(--cream); }
.create__brief:placeholder-shown { border-style: dashed; }
.create__go { min-height: 48px; min-width: 48px; border-radius: 12px; background: var(--me, var(--go)); color: var(--bg-2); }

/* Your own tile in the list, restated as a ring on the glass now that `.pill` has
   no border to colour. The seat colour is the whole mechanism by which a guest
   finds themselves — here and on the television — so it stays the loudest edge
   on this one pill and nowhere else in the list. Declared twice: the plain
   version is what an engine without `color-mix` (pre-Safari 16.4) gets, and a
   dropped declaration here would lose the one pill that answers "which am I". */
.pill.is-me {
  box-shadow:
    inset 0 1px 0 color-mix(in srgb, var(--cream) 16%, transparent),
    inset 0 0 0 1.25px color-mix(in srgb, var(--lamp) 55%, transparent),
    0 4px 14px color-mix(in srgb, var(--night) 34%, transparent);
  box-shadow:
    inset 0 1px 0 color-mix(in srgb, var(--cream) 16%, transparent),
    inset 0 0 0 1.25px color-mix(in srgb, var(--me, var(--go)) 65%, transparent),
    0 4px 14px color-mix(in srgb, var(--night) 34%, transparent);
}

/* ========================================================================== */
/* Agent-written surface (iframe of padN.<domain>)                            */
/* ========================================================================== */

/*
 * Full-bleed, and now that means what it says: no parent chrome above it, no
 * padding around it, both orientations, every pixel. The page inside handles
 * its own safe area (the injected HEAD does it), so the frame's only job is to
 * fill the glass and get out of the way.
 */
.surface-wrap {
  flex: 1;
  min-height: 0;
  min-width: 0;
  display: flex;
  position: relative;
  /* Edge to edge under the top bar; the surface page itself spends safe-area. */
  margin: 0;
}
.surface-frame {
  flex: 1;
  width: 100%;
  height: 100%;
  min-height: 0;
  border: 0;
  background: var(--bg);
  /* Allow the embedded page to take pointer events; do not let the parent
     steal horizontal pans meant for a landscape controller. */
  touch-action: manipulation;
}

/* When a surface is up the iframe IS the product. The stage takes the whole
   page, and a trouble that arrives becomes a mark on the anchor rather than a
   bar over somebody else's composition — see `paintNotice`. */
body[data-screen='surface'] .stage {
  flex: 1;
  min-height: 0;
}
body[data-bleed="1"] .stage {
  margin: 0;
  position: absolute;
  inset: 0;
}
body[data-bleed="1"] .surface-wrap {
  margin: 0;
  position: absolute;
  inset: 0;
}
body[data-bleed="1"] .surface-frame {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}
/* After keep-it the photograph changes inside the iframe
   (pad-bridge swaps the plate). A parent filter is the same
   dinner, graded — that is not a new still. */

/* ========================================================================== */
/* Play                                                                       */
/* ========================================================================== */

/* `min-width: 0` on all three, and on `.surface` below: see the note on
   `.stage`. `.play` is the flex item that must not inherit the package's
   min-content width; `.pad-bar` is the row that must not stretch past the
   glass even if something one day does; and both are cheap. */
.play {
  flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; position: relative;
}
/* AND THE SAME CORNER ON A MODEL-WRITTEN PAGE, which is the same defect on the
   flagship screen. `.surface-exit` sits at `right: max(14px, safe-r + 14px)` —
   inside the mark's 56 pt — and the mark is NOT suppressed there when it has
   news: `phone-shell.js` suppresses only the ordinary `handle` state on a
   full-bleed surface, precisely so that `work`, `note` and `failed` stay
   visible. So on the AI's own page, in exactly the states the AI is doing
   something, the puck lands on the only way out of it.

   UNPRESSED, and said plainly: this round could not reach a live model-written
   surface on this box — its agents answered nothing — so this is the rule
   applied to a second element by reading, not a reading taken off that screen.
   The play header above is the measured one. */
body:has(.shell-anchor-host[data-mark="on"]) .surface-head {
  /* 62 pt of corner, 14 of which the row's own right edge already keeps. */
  padding-right: 48px;
}
.play__activity,
.play__share,
.play__exit {
  flex: none;
  min-height: 44px;
  padding: 0 15px 0 12px;
  border-radius: 999px;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  color: var(--ink-2);
  background: color-mix(in srgb, var(--cream) 8%, transparent);
  box-shadow: inset 0 0 0 1px var(--line);
  font-size: var(--ph-fine);
  font-weight: 650;
}
.play__activity {
  padding: 0 13px;
  color: var(--lamp);
  background: rgba(var(--lamp-rgb), .1);
  box-shadow: inset 0 0 0 1px rgba(var(--lamp-rgb), .22);
}
.play__activity .mk { width: 17px; height: 17px; }
.play__activity:active { transform: scale(.96); background: rgba(var(--lamp-rgb), .17); }
/* Share this, beside the room key: the same pill, the room's own colour, so
   the three keys on a controller's head read as one row of ways out. */
.play__share { padding: 0 13px; }
.play__share .mk { width: 17px; height: 17px; }
.play__share:active { transform: scale(.96); }
.play__exit .mk { width: 18px; height: 18px; }
.play__exit:active { transform: scale(.96); background: color-mix(in srgb, var(--cream) 14%, transparent); }
.play__exit:disabled { opacity: .45; }
.surface-exit {
  flex: none;
  color: var(--cream);
  background: color-mix(in srgb, var(--bg-2) 78%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--cream) 18%, transparent), 0 8px 24px color-mix(in srgb, var(--night) 35%, transparent);
  -webkit-backdrop-filter: blur(14px) saturate(125%);
  backdrop-filter: blur(14px) saturate(125%);
}
/* While the shell's sheet is up it IS the top of the phone, and the page's
   floating keys have no business over it: the frames of 2026-09-03 showed
   "Share" hanging over the sheet's own nav a finger above "Library". */
body[data-shell="open"] .surface-keys, body[data-shell="open"] .surface-exit { visibility: hidden; }
/* …and the band itself steps out of the way. It sits at z-index 8 so a pull
   lands on it instead of the game's iframe; the sheet is z-index 6, so with
   the band still taking events the sheet's own close key was unreachable —
   the walk found the close tap landing on `.surface-head`. */
body[data-shell="open"] .surface-head { pointer-events: none; }
/* Homie and Share, one cluster at the top-left of a full-bleed page; Leave
   this keeps the top-right on its own (`.surface-exit`), because the way out
   is the one key that must never move. */
.surface-head {
  position: absolute;
  z-index: 8;
  top: max(10px, calc(var(--safe-t) + 8px));
  left: max(14px, calc(var(--safe-l) + 14px));
  right: max(14px, calc(var(--safe-r) + 14px));
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  /* THE ROW IS THE SUMMON BAND. At home the sheet is pulled down from the
     top 44 pt of the phone (`SUMMON.BAND_PT`); over a game's full-bleed page
     those events went to the page's iframe between the keys and the pull
     could never start — which is why a game needed a Homie key and home did
     not — two summons that read as two different swipe menus (2026-09-04,
     launch plan item 19). The row now owns its 54 pt from 10 pt down, so the
     same pull works over a game, and the keys still press. */
  pointer-events: auto;
}
.surface-keys {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}
/* An invitation to add a name outranks Share on the narrowest phones: with
   "Add your name" on the left there is no room for a third key at 375 pt. */
@media (max-width: 389px) {
  .surface-keys:has([data-play-activity="name"]) .surface-share { display: none; }
}
/* Three keys do not fit a 375 pt phone with their words on: MEASURED
   2026-09-03 on the walkthrough's late joiner, "Share" ran under "Leave this".
   Share keeps its mark and loses its word below 390 pt; Leave this keeps its
   word everywhere, because the way out is the one key that must be read. */
@media (max-width: 389px) {
  .surface-keys .surface-share span { display: none; }
  .surface-keys .surface-share { padding-left: 12px; padding-right: 12px; }
}
.surface-share {
  color: var(--cream);
  background: color-mix(in srgb, var(--bg-2) 78%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--cream) 18%, transparent), 0 8px 24px color-mix(in srgb, var(--night) 35%, transparent);
  -webkit-backdrop-filter: blur(14px) saturate(125%);
  backdrop-filter: blur(14px) saturate(125%);
}
.surface-activity {
  color: var(--lamp);
  background: color-mix(in srgb, var(--bg-2) 78%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--lamp) 24%, transparent), 0 8px 24px color-mix(in srgb, var(--night) 35%, transparent);
  -webkit-backdrop-filter: blur(14px) saturate(125%);
  backdrop-filter: blur(14px) saturate(125%);
}
.surface {
  flex: 1;
  /* The package's own region. `min-width: 0` here is what stops a wide card
     travelling back up the column and taking the platform's chrome with it —
     see the note on `.stage`. The card still draws at whatever width it asked
     for and is still clipped at the bezel; what changes is that the game's
     name and the way out are measured against the phone instead. */
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 0 14px calc(14px + var(--safe-b));
}

.strip {
  display: flex;
  flex-direction: column;
  gap: 10px;
  max-height: 62%;
  overflow-y: auto;
  touch-action: pan-y;
  -webkit-overflow-scrolling: touch;
  flex: none;
}

.field { flex: 1; min-height: 0; display: flex; flex-direction: column; gap: 10px; }
.field__row { flex: 1; min-height: 0; display: flex; gap: 10px; }
.zone { flex: 1; min-width: 0; display: flex; flex-direction: column; justify-content: flex-end; gap: 10px; }
.zone--right { flex-direction: row; flex-wrap: wrap-reverse; align-content: flex-end; justify-content: flex-end; align-items: flex-end; }
.zone--full { flex: 1; display: flex; }
.zone.is-empty { flex: 0.45; }
.field__row.has-direction-buttons .zone--left { flex: 1.15; }
.field__row.has-direction-buttons .zone--right {
  flex: .85;
  flex-direction: column-reverse;
  flex-wrap: nowrap;
  align-content: flex-end;
  align-items: flex-end;
  justify-content: flex-start;
}
.zone--left.has-direction-buttons {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 82px));
  grid-template-rows: repeat(2, 82px);
  align-content: end;
  justify-content: start;
  gap: 10px;
}
.zone--left.has-direction-buttons .ctl--button {
  width: 82px; height: 82px;
  color: var(--cream);
  background: linear-gradient(
    180deg,
    color-mix(in srgb, var(--me) 20%, var(--bg-3)),
    color-mix(in srgb, var(--me) 8%, var(--bg-2))
  );
  border-color: color-mix(in srgb, var(--me) 34%, transparent);
}
.zone--left.has-direction-buttons .ctl--button .button__face { font-size: var(--h3); }
.zone--left.has-direction-buttons .ctl--button[data-direction="left"] { grid-column: 1; grid-row: 2; }
.zone--left.has-direction-buttons .ctl--button[data-direction="right"] { grid-column: 2; grid-row: 2; }
.zone--left.has-direction-buttons .ctl--button[data-direction="down"] {
  grid-column: 1 / -1; grid-row: 1; justify-self: center;
}
.band { flex: none; display: flex; flex-direction: column; gap: 10px; }

.ctl { position: relative; }
.ctl__label {
  position: absolute;
  top: 8px; left: 12px;
  font-size: var(--kicker);
  font-weight: 620;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-3);
  pointer-events: none;
}

/* -- stick ---------------------------------------------------------------- */

.ctl--stick {
  flex: 1;
  min-height: 210px;
  border-radius: var(--radius);
  background: linear-gradient(180deg, color-mix(in srgb, var(--cream) 3.5%, transparent), color-mix(in srgb, var(--cream) 1.5%, transparent));
  border: 1px solid var(--line);
  overflow: hidden;
}
.stick__base {
  position: absolute;
  width: 148px; height: 148px;
  border-radius: 50%;
  background: radial-gradient(circle at 50% 40%, color-mix(in srgb, var(--cream) 7%, transparent), color-mix(in srgb, var(--cream) 2%, transparent) 70%);
  border: 1px solid var(--line-2);
  display: grid;
  place-items: center;
  transition: left 180ms cubic-bezier(.2,.9,.25,1), top 180ms cubic-bezier(.2,.9,.25,1);
}
.ctl--stick.is-active .stick__base { transition: none; }
.stick__knob {
  width: 74px; height: 74px;
  border-radius: 50%;
  background: var(--me);
  box-shadow: 0 6px 18px color-mix(in srgb, var(--night) 45%, transparent), inset 0 -3px 8px color-mix(in srgb, var(--night) 22%, transparent);
  transition: transform 150ms cubic-bezier(.2,.9,.25,1);
}
.ctl--stick.is-active .stick__knob { transition: none; }

@media (max-height: 640px) {
  .ctl--stick { min-height: 168px; }
  .stick__base { width: 124px; height: 124px; }
  .stick__knob { width: 64px; height: 64px; }
}

/* -- axis ----------------------------------------------------------------- */

.ctl--axis {
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--cream) 3%, transparent);
  border: 1px solid var(--line);
  padding: 22px 18px 14px;
}
.ctl--axis.is-horizontal { height: 96px; }
.ctl--axis.is-vertical { flex: 1; min-height: 200px; width: 96px; align-self: flex-end; }
.axis__track { position: relative; width: 100%; height: 100%; }
.ctl--axis.is-horizontal .axis__track { height: 44px; }
.axis__tick {
  position: absolute;
  background: var(--line-2);
}
.ctl--axis.is-horizontal .axis__tick { left: 50%; top: 4px; bottom: 4px; width: 2px; }
.ctl--axis.is-vertical .axis__tick { top: 50%; left: 4px; right: 4px; height: 2px; }
.axis__thumb {
  position: absolute;
  width: 56px; height: 56px;
  margin: -28px 0 0 -28px;
  border-radius: 50%;
  background: var(--me);
  box-shadow: 0 5px 14px color-mix(in srgb, var(--night) 40%, transparent);
  transition: left 140ms cubic-bezier(.2,.9,.25,1), top 140ms cubic-bezier(.2,.9,.25,1);
}
.ctl--axis.is-active .axis__thumb { transition: none; }

/* -- button --------------------------------------------------------------- */

.ctl--button {
  width: 96px; height: 96px;
  min-width: var(--tap); min-height: var(--tap);
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: linear-gradient(180deg, color-mix(in srgb, var(--cream) 10%, transparent), color-mix(in srgb, var(--cream) 3%, transparent));
  border: 1px solid var(--line-2);
  box-shadow: 0 6px 16px color-mix(in srgb, var(--night) 35%, transparent);
  transition: transform 90ms ease, filter 90ms ease;
}
.ctl--button .button__face {
  font-size: var(--fine); font-weight: 620; letter-spacing: 0.01em;
  text-align: center; padding: 0 8px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%;
}
.ctl--button[data-tone="go"] { background: linear-gradient(180deg, var(--go), var(--success)); border-color: transparent; color: var(--bg-2); }
.ctl--button[data-tone="stop"] { background: linear-gradient(180deg, var(--stop), var(--live)); border-color: transparent; color: var(--bg-2); }
.ctl--button[data-tone="special"] { background: linear-gradient(180deg, var(--me), color-mix(in srgb, var(--night) 25%, transparent)); border-color: transparent; }
.ctl--button.is-down { transform: scale(0.93); filter: brightness(1.25); }

@media (max-height: 640px) { .ctl--button { width: 82px; height: 82px; } }

/* -- dpad ----------------------------------------------------------------- */

.ctl--dpad {
  flex: 1;
  min-height: 210px;
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--cream) 3%, transparent);
  border: 1px solid var(--line);
  display: grid;
  place-items: center;
}
.dpad__pad { position: relative; width: 180px; height: 180px; }
.dpad__arm {
  position: absolute;
  background: color-mix(in srgb, var(--cream) 7%, transparent);
  border: 1px solid var(--line-2);
  border-radius: 12px;
  transition: background 80ms ease;
}
.dpad__arm.is-down { background: var(--me); border-color: transparent; }
.dpad__arm--up { left: 33.3%; right: 33.3%; top: 0; height: 36%; }
.dpad__arm--down { left: 33.3%; right: 33.3%; bottom: 0; height: 36%; }
.dpad__arm--left { top: 33.3%; bottom: 33.3%; left: 0; width: 36%; }
.dpad__arm--right { top: 33.3%; bottom: 33.3%; right: 0; width: 36%; }
.dpad__hub {
  position: absolute; inset: 33.3%;
  border-radius: 8px;
  background: color-mix(in srgb, var(--cream) 5%, transparent);
}

/* -- buzzer --------------------------------------------------------------- */

.ctl--buzzer {
  flex: 1;
  border-radius: 28px;
  display: grid;
  place-items: center;
  background: radial-gradient(circle at 50% 42%, var(--me), color-mix(in srgb, var(--night) 55%, transparent) 78%);
  border: 1px solid var(--line-2);
  overflow: hidden;
  transition: transform 90ms ease, filter 90ms ease;
}
.ctl--buzzer .buzzer__ring {
  position: absolute;
  width: min(66vw, 300px); aspect-ratio: 1;
  border-radius: 50%;
  border: 2px solid color-mix(in srgb, var(--cream) 28%, transparent);
}
.ctl--buzzer .buzzer__text {
  position: relative;
  font-size: clamp(30px, 12vw, 62px);
  font-weight: 760;
  letter-spacing: -0.02em;
  text-shadow: 0 4px 22px color-mix(in srgb, var(--night) 50%, transparent);
  text-align: center;
  padding: 0 16px;
}
.ctl--buzzer.is-down { transform: scale(0.975); filter: brightness(1.35); }

/* -- swipe ---------------------------------------------------------------- */

.ctl--swipe {
  flex: 1;
  border-radius: 28px;
  background: color-mix(in srgb, var(--cream) 3%, transparent);
  border: 1px solid var(--line);
  display: grid;
  place-items: center;
  overflow: hidden;
}
.swipe__arrows { position: absolute; inset: 0; }
.swipe__arrow { position: absolute; font-size: var(--h2); color: var(--ink-3); }
.swipe__arrow--up { top: 16px; left: 50%; transform: translateX(-50%); }
.swipe__arrow--down { bottom: 16px; left: 50%; transform: translateX(-50%); }
.swipe__arrow--left { left: 16px; top: 50%; transform: translateY(-50%); }
.swipe__arrow--right { right: 16px; top: 50%; transform: translateY(-50%); }
.swipe__trail {
  width: 78px; height: 78px;
  border-radius: 50%;
  background: var(--me);
  opacity: 0;
  transition: transform 140ms ease, opacity 140ms ease;
}
.ctl--swipe.is-active .swipe__trail { transition: none; }
.ctl--swipe.is-hit { animation: hit 260ms ease; }
.ctl--swipe.is-nope { animation: nope 260ms ease; }
@keyframes hit { 50% { background: color-mix(in srgb, var(--cream) 10%, transparent); } }
@keyframes nope { 25% { transform: translateX(-6px); } 75% { transform: translateX(6px); } }

/* -- draw ----------------------------------------------------------------- */

.ctl--draw {
  flex: 1;
  border-radius: 28px;
  background: color-mix(in srgb, var(--cream) 3%, transparent);
  border: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.draw__canvas { flex: 1; width: 100%; min-height: 0; display: block; }
.draw__bar { display: flex; gap: 10px; padding: 10px; border-top: 1px solid var(--line); }
.draw__bar .btn { flex: 1; }
.ctl--draw.is-hit { animation: hit 260ms ease; }

/* ========================================================================== */
/* Cards — text, choice, tilt, and anything switched off                      */
/* ========================================================================== */

.card {
  border-radius: var(--radius);
  background: var(--bg-2);
  border: 1px solid var(--line);
  padding: 14px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  flex: none;
}
.card__head { display: flex; align-items: center; gap: 8px; }
.card__title { font-size: var(--fine); font-weight: 620; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.card__tag { font-size: var(--kicker); color: var(--ink-3); font-variant-numeric: tabular-nums; }
.card__why { margin: 0; font-size: var(--fine); line-height: 1.5; color: var(--ink-2); }
.card__row { display: flex; gap: 10px; justify-content: flex-end; }
.card__dot { width: 8px; height: 8px; border-radius: 50%; background: var(--ink-3); flex: none; }

/* A disabled control looks disabled. It is never a live-looking control that
   returns nothing — that is precisely the silent failure the contract's
   `needsSecureContext` note exists to prevent. */
.card--off { background: repeating-linear-gradient(135deg, color-mix(in srgb, var(--cream) 2.6%, transparent) 0 10px, transparent 10px 20px), var(--bg-2); }
.card--off .card__title { color: var(--ink-2); }
/* "off" was a word in the tag slot. It is a struck circle now — what a dead
   indicator looks like on every appliance in this house. The card being struck
   through is the visibility requirement: a dead capability must never fail
   quietly, and a struck card is louder than a sentence, not quieter. */
.card--off .card__tag:empty::before { content: "⊘"; }
.card--off .card__tag {
  color: var(--live);
  background: color-mix(in srgb, var(--live) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--live) 28%, transparent);
  border-radius: 999px;
  padding: 2px 9px;
  font-weight: 620;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.field__input {
  width: 100%;
  background: var(--bg);
  border: 1px solid var(--line-2);
  border-radius: 14px;
  padding: 14px;
  font-size: var(--dek);                        /* < 16px makes iOS zoom the page on focus */
  min-height: var(--tap);
  touch-action: manipulation;
  -webkit-user-select: text;
  user-select: text;
  outline: none;
  resize: none;
}
.field__input:focus { border-color: var(--me); }

.btn {
  min-height: 48px;
  padding: 0 20px;
  border-radius: 14px;
  background: var(--bg-3);
  border: 1px solid var(--line-2);
  font-size: var(--fine);
  font-weight: 600;
}
.btn:active { filter: brightness(1.3); }
.btn:disabled { opacity: 0.4; }
.btn--go { background: var(--me); border-color: transparent; color: var(--bg-2); }

.card.is-sent { border-color: var(--go); }

.choice__list { display: flex; flex-direction: column; gap: 8px; }
.choice__opt {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: var(--tap);
  padding: 10px 14px;
  border-radius: 14px;
  background: var(--bg);
  border: 1px solid var(--line-2);
  text-align: left;
}
.choice__opt:active { filter: brightness(1.3); }
.choice__opt.is-picked { background: var(--me); border-color: transparent; color: var(--bg-2); }
.choice__key {
  width: 26px; height: 26px; flex: none;
  border-radius: 8px;
  display: grid; place-items: center;
  background: color-mix(in srgb, var(--cream) 8%, transparent);
  font-size: var(--fine); font-weight: 700;
}
.choice__opt.is-picked .choice__key { background: color-mix(in srgb, var(--night) 18%, transparent); }
.choice__text { font-size: var(--body); line-height: 1.3; }

/* ========================================================================== */
/* Big cards, sheets, hints                                                   */
/* ========================================================================== */

.bigcard {
  margin: auto;
  padding: 28px 24px calc(28px + var(--safe-b));
  max-width: 30em;
  text-align: center;
  display: flex;
  flex-direction: column;
  gap: 14px;
  align-items: center;
}
/* A CARD ON A REMOTE FRIEND'S PHONE HAS THE LIVING ROOM MOVING BEHIND IT.

   Their page is the television's own picture with the phone drawn over the top,
   so there is nothing opaque under these words — measured on a friend benched
   from Block Drop, where "It seats one player, and somebody is playing" ran
   straight through the falling board. Dark enough to read white text against a
   bright game, translucent enough that the picture they came for is still
   there. `data-away` is set by `renderBench` off the same reading that chooses
   the card's last sentence; a phone in the house has a background of its own
   and gets neither. */
.bigcard[data-away] {
  margin: auto 10px;
  padding: 24px 20px calc(24px + var(--safe-b));
  border-radius: 22px;
  background: color-mix(in srgb, var(--bg-2) 82%, transparent);
  box-shadow: 0 18px 60px color-mix(in srgb, var(--night) 50%, transparent);
}
.bigcard h2 { margin: 0; font-size: var(--h2); font-weight: 640; letter-spacing: -0.02em; }
.bigcard p { margin: 0; color: var(--ink-2); font-size: var(--body); line-height: 1.5; }
/* An empty state paragraph still holds its line so the card does not jump when
   the model's words arrive on the next turn. */
.bigcard__state:empty { min-height: 4px; }

/* The two situations the headings used to name, drawn instead.

   A phone that never got in: a dashed ring — a door that has not opened.
   A phone that lost the room: a broken ring — a link that has parted.

   They were genuinely different states and the pictures keep them different,
   which is all "Not in yet" versus "Not connected" was ever doing. */
.bigcard__mark {
  width: 54px; height: 54px;
  display: grid;
  place-items: center;
}
.bigcard__mark::before {
  content: "";
  width: 42px; height: 42px;
  border-radius: 50%;
  border: 3px solid color-mix(in srgb, var(--cream) 55%, transparent);
  border-right-color: transparent;
  border-top-color: transparent;
}
.bigcard__mark[data-state="not-let-in"]::before {
  border: 3px dashed color-mix(in srgb, var(--cream) 55%, transparent);
}
.bigcard[data-state] .btn { font-size: var(--h3); line-height: 1; min-width: 56px; }

.rotate {
  position: absolute;
  left: 50%; bottom: calc(10px + var(--safe-b));
  transform: translateX(-50%);
  padding: 8px 14px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--night) 55%, transparent);
  border: 1px solid var(--line);
  font-size: var(--fine);
  color: var(--ink-2);
  pointer-events: none;
  backdrop-filter: blur(8px);
}

.scrim { position: fixed; inset: 0; background: color-mix(in srgb, var(--night) 55%, transparent); z-index: 5; }

/* `.sheet` is the curtain now, and it comes from the TOP.
   The class name is kept deliberately: it is what four live instruments read to
   answer "is a panel open", and renaming it would have turned all four green on
   nothing in the same edit. Everything it declares is in `.shell` below — this
   rule only carries what a panel is made of. */
.sheet__body { display: flex; flex-direction: column; gap: 16px; }

/* ========================================================================== */
/* The curtain — the app's tabs (Play, Homie, Room, the remote)               */
/* ========================================================================== */

/*
 * WHAT THIS IS NOW. The curtain used to be a floating rounded sheet dropped
 * from the top — 94dvh tall, a 1 px border, a grab bar, a Homie wordmark, a
 * close X, a boxed five-key row and a "Back to Homie · <tab>" line — and the
 * whole app lived inside it (critique-r0 B.1, B.2: four navigation systems and
 * a 47 pt dead band of page under the sheet). It is the app's root now:
 * full-bleed, the base surface, a large title at the top of its scroller and
 * the tab bar (phone-system.css .ui-tabbar) over its bottom.
 *
 * It still OPENS and CLOSES, because Now is the page under it — the lobby or
 * the controller — and lowering the curtain is how Now is shown. `.sheet` stays
 * in its class list because four instruments read it as "a panel is open".
 *
 * `--pull` (0..1) is the summon gesture's progress over a game. It fades the
 * app in rather than sliding a sheet down: a full-bleed screen dragged down
 * from the top edge would uncover a strip of controller under a thumb.
 */
.shell {
  position: fixed;
  inset: 0;
  z-index: 6;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  padding: 0;
  background: var(--bg-base);
  color: var(--label-1);
  opacity: var(--pull, 1);
  transition: opacity var(--dur-fade) linear;
}
.shell__scrim { display: none; }
/*
 * The one scrolling region. Its bottom padding clears the dock (tab bar,
 * mini-player) plus 24 pt so a last row is never under the bar, and it pads
 * inline rather than cropping, so a card's corners are never cut (D.10).
 */
.shell__body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  overscroll-behavior: contain;
  touch-action: pan-y;
  padding: 0 var(--page-margin) calc(var(--dock-h) + var(--sp-6));
  display: flex;
  flex-direction: column;
  gap: var(--sp-5);
}
/* The large title spans the column; its own padding is the page margin. */
.shell__body > .ui-lt { margin: 0 calc(-1 * var(--page-margin)); }
.shell__body > [hidden] { display: none !important; }
.shell__back-to-game { flex: none; }
/* Over a package's phone surface the tabletop's own dock (96 px) is under us. */
body[data-screen="surface"] .shell__body { padding-bottom: calc(var(--dock-h) + 96px); }

/*
 * PLAY is the Guide, embedded. The curtain body is its scroller like every other
 * tab's — so the large title collapses on Play too — and the Guide is a
 * full-bleed column inside it that pads itself by the page margin, so a shelf
 * can scroll to the glass edge without its first tile leaving the margin.
 */
.shell__play { flex: none; min-width: 0; margin: 0 calc(-1 * var(--page-margin)); }
.shell .phone-guide[data-embedded="true"] {
  position: static;
  z-index: auto;
  overflow: visible;
  padding: 0;
  background: transparent;
}
.phone-guide[data-embedded="true"] .phone-guide-head { display: none; }
/* The television's absence note (`ui.guideScreenNote`) is an always-present
   8 pt box; empty, it cost the header a 20 pt step (its padding plus a gap). */
.phone-guide > .guide__screen-note:empty { display: none; }
.phone-guide > .guide__screen-note:not(:empty) { margin: 0 var(--page-margin); }
/* One header rhythm (critique-r1 B6): the large title, 8 pt, then the switcher. */
.shell__body[data-view="guide"] > .shell__play { margin-top: calc(var(--sp-2) - var(--sp-5)); }

/*
 * A THREAD IS A PUSH. With a thread open on Homie the tab's large title steps
 * aside and the AI hub's toolbar is the navigation bar ("‹ Homie", the thread's
 * title, one "+" icon key). Specificity is the frame's here on purpose: the
 * hub's older rules draw this toolbar as two bordered 44 pt squares.
 */
.shell__body[data-view="activity"][data-pushed="thread"] > .ui-lt,
.shell__body[data-view="activity"][data-pushed="thread"] > .ui-lt-bar { display: none; }
.shell__body[data-view="activity"][data-pushed="thread"] { padding-top: var(--safe-t); }
/* The hub runs edge to edge (homie lane), so the bar pads itself. A thread's bar
   is [chevron | title | nothing]: two 44 pt columns either side keep the title
   optically centred and give it 262 pt at 390, where the "‹ Homie" label and a
   "+" key left it 185 (critique-r1 B4). The parent's name stays the chevron's
   accessible label and is not drawn, as iOS does when a title needs the room. */
.aihub__toolbar.ui-navbar { display: grid; grid-template-columns: var(--hit) minmax(0, 1fr) var(--hit); gap: var(--sp-2); min-height: var(--hit); margin: 0; padding: 0 var(--sp-3); }
.aihub__toolbar.ui-navbar .ui-navbar__back {
  flex: none; width: var(--hit); height: var(--hit); min-height: var(--hit); padding: 0;
  border: 0; border-radius: var(--r-full); background: transparent; color: var(--accent);
  font: 400 var(--fs-body)/var(--lh-body) var(--font-text); letter-spacing: -0.4px;
}
.aihub__toolbar.ui-navbar .ui-navbar__back::before { content: none; }
.aihub__toolbar.ui-navbar .ui-navbar__back .ui-ic { flex: none; width: 28px; height: 28px; }
.aihub__toolbar.ui-navbar .ui-navbar__back-label { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
.aihub__toolbar.ui-navbar .ui-navbar__title { max-width: none; text-align: center; font: 600 var(--fs-body)/var(--lh-body) var(--font-text); letter-spacing: -0.4px; }
.aihub__toolbar.ui-navbar { position: relative; z-index: 2; transition: box-shadow var(--dur-fade) linear; }
.aihub__toolbar.ui-navbar[data-edge="on"] { box-shadow: 0 0.5px 0 var(--separator); }

/*
 * THE RELOAD FLOOR. A sibling of the scroller, drawn only on Homie when the
 * link is down or a turn is unknown or failed — so it can never overlap the
 * content it sits under. It sits above the dock.
 */
.shell__ways::before {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 100%;
  height: var(--sp-6);
  pointer-events: none;
  background: linear-gradient(180deg, transparent, var(--bg-base));
}
.shell__ways {
  position: relative;
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  z-index: 3;
  padding: var(--sp-2) var(--page-margin) calc(var(--sp-2) + var(--dock-h));
  background: var(--bg-base);
}
.shell__ways[hidden] { display: none; }
.shell__ways > .btn {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  min-height: var(--hit);
  width: auto;
  margin: 0;
  padding: 0 var(--sp-4);
  border: 0;
  border-radius: var(--r-full);
  background: var(--bg-elev2);
  color: var(--label-1);
}
.shell__ways > .btn .mk { width: 18px; height: 18px; }
.shell__way-word { font: 600 var(--fs-subhead)/var(--lh-subhead) var(--font-text); white-space: nowrap; }

/*
 * THE ANCHOR IS OFF THE GLASS WHERE THE TAB BAR IS UP. On a tab the Homie tab
 * carries its news (a badge); the corner mark stays the summon over a game or a
 * package's surface, where there is no tab bar.
 */
body[data-frame="tabs"] .shell-anchor-host { display: none !important; }


.shell__sec { display: flex; flex-direction: column; gap: 12px; }
.shell__id { display: flex; align-items: center; gap: 12px; }
/* A LABELLED CONTROL, WHICH IS WHAT TURNS A VALUE BACK INTO A FIELD.

   The identity panel was three unlabelled objects stacked: a 26 pt word (the
   name input, carrying the daemon's minted `Guest`), sixteen circles, and a row
   of pills. MEASURED with the curtain open, 2026-08-18: the word `Guest`
   rendered three times in one frame and nothing on the screen said which of
   them a person could change. The material repair to `.me__name` was necessary
   and was not sufficient — a recess with a word in it is a field, but it is
   still not *your name* until something says so.

   `.shell__field` is a `<label>` for the name (so the caption is a tap target
   for the control under it) and a plain box for the two that have no single
   focusable child. `gap: 8px` rather than the section's 12, because a caption
   and its control are one object and the section's rhythm is between objects. */
.shell__field { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
/* Small, spaced, and deliberately NOT the ink the value is drawn in. A caption
   at the same weight as its field is a second heading; at 12/650 with a stop of
   letterspacing it is a label, which is a thing an eye skips until it needs it.
   `--ink-3` is the panel's own quiet ink and the same colour the eyebrow used
   here before it moved to the glass. */
.shell__label {
  font-size: var(--kicker);
  font-weight: 650;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-3);
}
/* AND THE ROSTER'S CAPTION WITHDRAWS WHILE THERE IS NO ROSTER.

   `paintRoster` draws nothing at all until `welcome` has landed, deliberately —
   its own comment: *"a list of names on a phone that has not been let in is the
   same lie as 'you are in', just quieter"*. So for the first second of a join,
   the second a guest is most anxious about whether anything is happening, this
   would otherwise be a heading over a gap.

   `:has()` is Safari 15.4 / Chrome 105 and this is the mild direction to be
   wrong in where it is missing: the caption stands over an empty space for one
   second rather than a name appearing under no heading. */
.shell__field--roster:has(> .roster:empty) { display: none; }
/* WHO YOU ARE, AS ONE OBJECT.

   The colour dot and the name — or the seat ordinal when there is no name — are
   the whole of the identity this box hands out (THE_SHELL §6: colour plus seat
   ordinal, both machine facts, no word). They were two elements a thumb's width
   apart, both drawing the ordinal, and neither had a rule in this stylesheet at
   all: `.chip` and `.chip__dot` were referenced by the panel and styled nowhere,
   so a guest's own colour did not render. One node, one rule, and it is drawn
   the same way a person in the roster below is drawn — because it is the same
   fact about a different person. */
.chip {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  min-height: 44px;                       /* the platform minimum, and it is a floor */
  padding: 8px 16px 8px 12px;
  border-radius: 999px;
  background: var(--bg-2);
  border: 1px solid var(--line);
  color: var(--cream);
  font-size: var(--dek);
  font-weight: 600;
  max-width: 100%;
}
.chip__dot {
  width: 12px; height: 12px;
  border-radius: 50%;
  flex: none;
  background: var(--me, var(--ink-3));
}
.chip__name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* A CHEVRON, BECAUSE NOTHING ELSE ON THIS OBJECT SAYS IT OPENS SOMETHING.

   MEASURED before this: a dark glass pill with a coloured dot and a coloured
   label and a 0.96 `:active` scale — and a press-state is invisible until you
   have already pressed. The chip is the guest's own mark on the arrival screen
   AND the way to the panel where they change it, and the second of those two
   jobs had no pixel at all. Every operating system a guest has ever held draws
   "there is more behind this" as a chevron, it is geometry rather than language,
   and it therefore costs this surface none of its wordlessness.

   Drawn from borders on a pseudo-element rather than a glyph: no font
   dependency, no node (so THE_SHELL §16's inventory is unchanged), no text (so
   `copy-sweep-probe` has nothing to read), and it inherits `currentColor` so it
   is the guest's own colour exactly as the label beside it is. */
.chip::after {
  content: "";
  flex: none;
  width: 7px; height: 7px;
  margin-left: 2px;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  border-radius: 1px;
  opacity: 0.5;
  /* Points DOWN, because the curtain it opens comes down from the top. The
     gesture and the mark agree, which is the same self-teaching the grab bar
     relies on. */
  transform: translateY(-2px) rotate(45deg);
  transition: transform 160ms cubic-bezier(.2,.9,.25,1), opacity 160ms ease;
}
/* Empty only before `welcome`, when there is neither a name nor a seat yet. The
   dot alone is the honest picture of that: somebody is arriving, and the box has
   not said who yet. */
.chip__name:empty { display: none; }
.shell__id { flex-wrap: wrap; }
/* THE EYEBROW IS THE CHIP'S DOT DRAWN A SECOND TIME, AND ONLY ONE OF THEM MAY
   ANSWER.

   MEASURED with the curtain open, 390x844: `.me__eyebrow` was a 16x16 coloured
   disc at 18,148 with no text beside it, a thumb's width left of a chip whose
   own 12 pt dot carries the same colour from the same painter. Two marks for one
   fact — *"am I in the room yet"* — is the `colourOf` / `certHealth` shape
   CLAUDE.md records twice, and here it was also simply an orphan dot a guest has
   to be told the meaning of.

   The NODE stays and `paintEyebrow` keeps writing its `data-state`: `stuck-hunt`
   waits on `.me__eyebrow[data-state="joined"]` to decide whether a phone that
   touched nothing became somebody, and deleting the node would turn that check
   green on an absent element. Only the pixels go. The signal itself is not lost
   — it moves onto the chip's dot below, which is where a guest is already
   looking because it is the thing with their name on it. */
.shell__id .me__eyebrow { display: none; }
/* AND INSIDE THE PANEL THE CHIP STOPS SAYING THE NAME, BECAUSE THE FIELD SAYS IT.

   MEASURED with the curtain open before this round: the word the daemon hands
   out (`Guest`) rendered THREE times in one frame — on this chip, as the value
   of the field 12 pt below it, and on this guest's own pill in the roster. Three
   nodes answering *who am I*, none of them authoritative, which is the exact
   objection the round-2 critic raised and the same shape as `colourOf` and
   `certHealth`.

   On the LOBBY the chip's label is the whole point and is untouched — it is the
   only evidence a guest's phone reached anything. In the panel the field
   directly beneath it is the authority, because it is the one a guest can
   change, so the chip keeps the two jobs the field cannot do: it carries the
   colour bead, and its chevron opens the sixteen. One control, one job, one
   answer per fact.

   `.chip__name` is HIDDEN, not removed: `phone-shell-probe` reads its
   `textContent` to prove a nameless guest is still somebody, `stuck-hunt` waits
   on the node existing, and both read text rather than geometry — deleting it
   would turn those green on nothing. The `.seat__num` class on the same node is
   why the rule names both: it is one element wearing two names. */
.shell__id .chip .chip__name { display: none; }
/* AND WHAT IS LEFT SAYS WHAT IT DOES. With the face hidden the chip was a bead,
   a chevron and nothing else — the unlabelled pill the round-3 critic reported
   as MISSING. `.chip__act` is empty on the lobby (where the face IS the label)
   and carries one word here, at the same size and weight as the face it stands
   in for, so the object does not change shape between the two states. */
.chip__act { display: none; }
.shell__id .chip .chip__act {
  display: block;
  font-size: var(--fine);
  font-weight: 640;
  letter-spacing: -0.005em;
  white-space: nowrap;
}

/* A notification's words, waiting until they were summoned. Never a card over
   the AI's page: the anchor marked it, and this is where the words were all
   along. A note with no words still draws — something arrived IS the news. */
.shell__notes { display: flex; flex-direction: column; gap: 8px; }
.shell__note {
  margin: 0;
  padding: 10px 12px;
  border-radius: 12px;
  background: var(--bg);
  border: 1px solid var(--line);
  font-size: var(--fine);
  line-height: 1.4;
  color: var(--ink-2);
  overflow-wrap: anywhere;
}
.shell__note:empty { min-height: 12px; }
.shell__note:empty::before { content: "\2298"; opacity: 0.7; }
.shell__notes:empty { display: none; }

/* ========================================================================== */
/* The mark set                                                               */
/* ========================================================================== */

/*
 * One geometric alphabet, drawn by us, inheriting `currentColor` — which is the
 * whole reason it is not emoji. See phone-marks.js for why that is a
 * measurement rather than a preference.
 *
 * Two optical sizes, and both are floors rather than guesses: 28 pt in a hand
 * inside a 48 pt target (44 is the enforced minimum that `stuck-hunt` fails
 * under, and 48 gives the transport row room to be a row rather than a
 * scatter). The ten-foot size is the television's and is not set here.
 */
.mk { width: 28px; height: 28px; display: block; flex: none; }
.btn .mk, .create__go .mk { width: 24px; height: 24px; }
.iconbtn .mk { width: 22px; height: 22px; }
.swipe__arrow .mk { width: 22px; height: 22px; }
.remote__handoff-mark .mk { width: 20px; height: 20px; }

/* ========================================================================== */
/* Transport — the six verbs, and the way back                               */
/* ========================================================================== */

/* The deck: what is on, the six verbs, and the way back, as one object.

   It lives inside the curtain, and only there. It used to have a second parent
   — the lobby — and the screen that produced reduced the phone to a remote.
   See `hostDeck`. */
/* Laid out in THE REMOTE block below (r2 Room + Now lane). */

/* ==========================================================================
   NOW — the television in your hand (Now lane, phone redesign 2026-09-14,
   critique-r0 C.5)
   ==========================================================================

   This block replaced about eighteen hundred lines: the lobby as a fixed
   photograph with furniture pinned over it (`.lobby__strip`, the uppercase
   "You're in", the people pills, the Play · Make · Music · Video cards, the
   Guide key, the composer's own band) and every `--say-h` / `--strip-h`
   offset that kept those layers from landing on each other. The page is a
   flowed column now, so nothing can land on anything.

   Tokens only (phone-system.css): no size, radius or hue that is not one. */

/* The Now root scrolls under the tab bar like every tab root. */
.lobby {
  position: relative;
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  touch-action: pan-y;
  -webkit-overflow-scrolling: touch;
  /* The same ground as every other tab, so the stage's fade has one colour to
     land on. */
  background: var(--bg-base);
  padding: 0 0 calc(var(--safe-b) + var(--sp-6));
  display: flex;
  flex-direction: column;
  gap: var(--sp-5);
}
body[data-frame="tabs"][data-screen="lobby"] .lobby { padding-bottom: calc(var(--dock-h) + var(--sp-6)); }
/* The stage is the first thing on the page wherever its node was appended. */
.lobby > .now { order: -1; }

/* The corner: the avatar top-trailing and the house's name top-leading, both
   over the picture and both scrolling away with it. The title "Now" stays for
   assistive tech; the television is the visible headline. */
.lobby > .ui-lt {
  position: absolute; top: 0; right: 0; z-index: 3;
  min-height: 0; padding: calc(var(--safe-t) + var(--sp-2)) max(var(--page-margin), var(--safe-r)) 0 0;
}
.lobby > .ui-lt .ui-lt__title { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
.lobby > .ui-lt-bar { display: none; }
.now-house {
  position: absolute; top: calc(var(--safe-t) + var(--sp-2)); left: max(var(--page-margin), var(--safe-l)); z-index: 3;
  max-width: calc(100% - 2 * var(--page-margin) - var(--sp-14));
  min-height: var(--hit); margin: 0;
  display: flex; align-items: center;
  font: 600 var(--fs-subhead)/var(--lh-subhead) var(--font-text); letter-spacing: -0.2px;
  color: var(--label-1);
  text-shadow: 0 1px 8px rgba(11, 13, 16, 0.6);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.now-house:empty { display: none; }

/* -- the region, wherever it stands ---------------------------------------- */
.now { display: flex; flex-direction: column; gap: var(--sp-1); min-width: 0; }
.now[data-holder="nothing"] { display: none; }
.now__art { display: none; }
.now__title {
  margin: 0; min-width: 0;
  font: 600 var(--fs-body)/var(--lh-body) var(--font-text); letter-spacing: -0.4px;
  color: var(--label-1);
  overflow-wrap: anywhere;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
}
.now__by {
  margin: 0; min-width: 0;
  font: 400 var(--fs-footnote)/var(--lh-footnote) var(--font-text);
  color: var(--label-2);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.now__idle { display: block; font: 400 var(--fs-subhead)/var(--lh-subhead) var(--font-text); color: var(--label-2); }
.now__note { margin: 0; font: 400 var(--fs-footnote)/var(--lh-footnote) var(--font-text); color: var(--label-2); }

/* -- the stage (the lobby) ---------------------------------------------------
   Full bleed, 45 % of the glass when there is a picture (C.5), fading into the
   page; the name of the thing over its foot. With no picture there is nothing
   to spend that height on, so it is only as tall as its words: a blank
   gradient card is the defect this replaced. */
/* The page fills the glass and the stage takes what the deck does not, so the
   title and the key sit low, in thumb reach (C.5's bottom 55 %), whatever the
   picture's height. */
.lobby > .now { display: flex; gap: var(--sp-5); flex: 1 0 auto; }
.lobby > .now > .now__stage { flex: 1 0 auto; }
.lobby > .now[data-holder="nothing"] { display: flex; }
.now__stage {
  position: relative;
  display: flex; flex-direction: column; justify-content: flex-end;
  min-height: calc(var(--safe-t) + 176px);
  padding: calc(var(--safe-t) + var(--sp-14) + var(--sp-2)) var(--page-margin) 0;
  isolation: isolate;
}
.now__stage[data-art="yes"] { min-height: max(45vh, calc(var(--safe-t) + 320px)); }
.now__art-slot { position: absolute; inset: 0; z-index: -1; overflow: hidden; background: var(--bg-base); }
.now__art-slot:empty { display: none; }
.lobby .now__art { display: block; width: 100%; height: 100%; object-fit: cover; object-position: center; }
.now__art-slot::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(11, 13, 16, 0.55) 0%, rgba(11, 13, 16, 0) 26%, rgba(11, 13, 16, 0) 42%, rgba(11, 13, 16, 0.72) 76%, var(--bg-base) 100%);
}
.now__words { display: flex; flex-direction: column; align-items: flex-start; gap: var(--sp-1); min-width: 0; }
.now__kicker {
  display: inline-flex; align-items: center; gap: var(--sp-2); margin: 0;
  font: 600 var(--fs-footnote)/var(--lh-footnote) var(--font-text);
  color: var(--label-2);
}
.now__live-dot { width: 8px; height: 8px; border-radius: var(--r-full); background: var(--live-dot); }
.now__stage .now__title,
.now__stage .offer__title {
  font: 700 var(--fs-title1)/var(--lh-title1) var(--font-display); letter-spacing: -0.3px;
  color: var(--label-1);
  -webkit-line-clamp: 3;
  text-shadow: 0 1px 12px rgba(11, 13, 16, 0.45);
}
.now__stage .now__title--ask { font: 650 var(--fs-title2)/var(--lh-title2) var(--font-display); letter-spacing: -0.2px; -webkit-line-clamp: 4; }
.now__stage .now__by,
.now__stage .offer__line {
  font: 400 var(--fs-subhead)/var(--lh-subhead) var(--font-text); letter-spacing: -0.2px;
  color: var(--label-2);
  white-space: normal;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
}

/* The deck: in thumb reach, the one thing to press, then the ways on. */
.now__deck { display: flex; flex-direction: column; gap: var(--sp-3); padding: 0 var(--page-margin); }
.now__deck .offer { display: flex; flex-direction: column; gap: var(--sp-2); }
.now__deck .ui-btn .mk { width: 20px; height: 20px; flex: none; }
.lobby > .now > .ui-group { margin: 0 var(--page-margin); }
.lobby > .now .ui-row__tile .homie-brand-mark,
.lobby > .now .ui-row__tile svg { width: 18px; height: 18px; }
.now__replies { display: flex; flex-direction: column; gap: var(--sp-3); }
.now__askcard { display: flex; flex-direction: column; gap: var(--sp-2); }
.now__askcard[data-hero="no"] { padding: var(--sp-4); border-radius: var(--r-md); background: var(--bg-elev1); }
.now__askwords { margin: 0 0 var(--sp-2); font: 600 var(--fs-body)/var(--lh-body) var(--font-text); color: var(--label-1); }


/* -- behind the curtain: the Remote view's card above the keys -------------- */
.shell__now:empty { display: none; }
.shell__now .now {
  display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: center;
  column-gap: 0; row-gap: 2px;
  padding: var(--sp-3); border-radius: var(--r-md); background: var(--bg-elev1);
}
.shell__now .now__art {
  display: block; grid-row: 1 / 4; margin-right: var(--sp-3);
  width: 56px; height: 56px; border-radius: var(--r-sm);
  object-fit: cover; object-position: center; pointer-events: none;
}
.shell__now .now__title, .shell__now .now__by, .shell__now .now__state, .shell__now .now__where { grid-column: 2; }
.shell__now .now > .now__title:first-child { grid-column: 1 / -1; }
/* r2 Room + Now lane: the title centres on the thumbnail when it is alone, and
   the state line ("3 playing · 1 seat free") sits under it in label-2. */
.shell__now .now__title { align-self: end; }
.shell__now .now:not(:has(.now__by, .now__state, .now__where)) .now__title { align-self: center; grid-row: 1 / 4; }
.shell__now .now__state {
  font: 400 var(--fs-footnote)/var(--lh-footnote) var(--font-text); color: var(--label-2);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Nothing on: one quiet line, not a card holding a sentence (critique-r1 B12). */
.shell__now .now[data-holder="nothing"] { display: block; padding: 0 var(--sp-1); background: transparent; }
.shell__now .now[data-holder="nothing"] .now__idle { font: 400 var(--fs-subhead)/var(--lh-subhead) var(--font-text); color: var(--label-2); }
/* The mini-player's worded action (Join) is a compact capsule. */
.ui-mini__action.ui-mini__action--text {
  width: auto; height: 36px; min-width: 0; padding: 0 var(--sp-4); margin-right: var(--sp-1);
  border-radius: var(--r-full); background: var(--fill-primary); color: var(--on-primary);
  font: 600 var(--fs-subhead)/1 var(--font-text); position: relative;
}
.ui-mini__action.ui-mini__action--text::after { content: ""; position: absolute; inset: -4px 0; }
.now[data-holder="nothing"][data-offer="yes"] { display: flex; }
.shell__now .offer { grid-column: 1 / -1; width: 100%; }
.shell__now .offer__title { font: 600 var(--fs-title3)/var(--lh-title3) var(--font-display); letter-spacing: -0.1px; }
.shell__now .offer__play { margin-top: var(--sp-3); }

/* -- the offer's own parts, in either parent ---------------------------------- */
.offer { display: flex; flex-direction: column; align-items: stretch; gap: var(--sp-1); min-width: 0; }
.offer__eyebrow {
  display: block; max-width: 100%; margin: 0 0 var(--sp-1);
  font: 600 var(--fs-footnote)/var(--lh-footnote) var(--font-text);
  color: var(--label-2);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.offer__live { display: inline-flex; align-items: baseline; gap: 0.42em; }
.offer__pip {
  align-self: center; width: 8px; height: 8px; flex: 0 0 auto;
  border-radius: var(--r-full); background: var(--live-dot);
  animation: offer-live-beat 2.4s ease-in-out infinite;
}
@keyframes offer-live-beat {
  0%, 62%, 100% { opacity: 1; }
  32%           { opacity: 0.34; }
}
@media (prefers-reduced-motion: reduce) { .offer__pip { animation: none; } }
.offer__dot { margin: 0 0.1em 0 0.34em; }
.offer__title { color: var(--label-1); display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.offer__line { color: var(--label-2); max-width: 100%; }
.offer__refused { margin: var(--sp-1) 0 0; font: 600 var(--fs-footnote)/var(--lh-footnote) var(--font-text); color: var(--label-1); }
.offer__refused-do { margin: 0; font: 400 var(--fs-footnote)/var(--lh-footnote) var(--font-text); color: var(--label-2); }

/* -- the play position, in either parent --------------------------------------- */
.now__bar { position: relative; flex: 1 1 auto; min-width: 0; height: 4px; border-radius: var(--r-full); background: var(--bg-elev3); pointer-events: none; }
.now__bar-run { height: 100%; border-radius: var(--r-full); background: var(--label-1); transition: width 900ms linear; }
.now__grip { display: none; }
.now__where {
  display: flex; align-items: center; gap: var(--sp-3); margin-top: var(--sp-1);
  font: 400 var(--fs-caption)/var(--lh-caption) var(--font-text);
  font-variant-numeric: tabular-nums;
  color: var(--label-2);
}
.now__elapsed, .now__left { flex: 0 0 auto; min-width: 34px; }
.now__left { text-align: right; }
.now__where[data-live="yes"] .now__left { min-width: 0; padding: 0 var(--sp-2); border-radius: var(--r-xs); font-weight: 600; color: var(--label-2); background: var(--bg-elev2); }
.now__where[data-live="yes"][data-edge="yes"] .now__left { color: #fff; background: var(--live-dot); }
.now__where[data-seekable="yes"] { touch-action: none; -webkit-user-select: none; user-select: none; min-height: var(--hit); cursor: pointer; }
.now__where[data-seekable="yes"] .now__bar { height: 6px; }
.now__where[data-seekable="yes"] .now__grip {
  display: block; position: absolute; top: 50%; left: 0;
  width: 20px; height: 20px; margin: -10px 0 0 -10px; border-radius: var(--r-full);
  background: var(--label-1);
  transition: left 900ms linear, transform var(--dur-press-out) var(--ease-nav);
  pointer-events: none;
}
.now__where[data-held="yes"] .now__bar-run, .now__where[data-held="yes"] .now__grip { transition: none; }
.now__where[data-held="yes"] .now__grip { transform: scale(1.4); }
.now__where[data-held="yes"] { color: var(--label-1); }
@media (prefers-reduced-motion: reduce) {
  .now__where .now__bar-run, .now__where .now__grip { transition: none; }
}

/* -- the join chip, in the Profile's identity panel ------------------------------
   Kept from the block this replaced: the chip's arriving / arrived signal. */
.chip[data-state="joining"] .chip__dot {
  box-shadow: inset 0 0 0 2px rgba(11, 13, 16, 0.55);
  animation: joinpulse 1.6s ease-in-out infinite;
}
.chip[data-state="joined"] .chip__dot { opacity: 1; animation: none; }
@media (prefers-reduced-motion: reduce) {
  .chip[data-state="joining"] .chip__dot { animation: none; opacity: 0.55; }
}
.chip[data-named="0"] .chip__name { color: var(--go); font-weight: 650; }
.chip[data-named="0"]::after {
  color: var(--go);
  width: 15px; height: 15px; margin-left: 4px;
  border: 0; border-radius: 0; opacity: 0.9; transform: none;
  background:
    linear-gradient(currentColor, currentColor) center / 13px 2.5px no-repeat,
    linear-gradient(currentColor, currentColor) center / 2.5px 13px no-repeat;
}

/* ========================================================================== */
/* Guide — a catalogue designed for a thumb                                  */
/* ========================================================================== */

.phone-guide-launch {
  position: fixed;
  top: calc(var(--safe-t) + 50px);
  right: max(16px, calc(var(--safe-r) + 16px));
  z-index: 7;
  min-height: 44px;
  padding: 0 15px 0 12px;
  border-radius: 999px;
  display: flex;
  align-items: center;
  gap: 9px;
  color: var(--cream);
  background: color-mix(in srgb, var(--bg-2) 66%, transparent);
  box-shadow: inset 0 1px 0 color-mix(in srgb, var(--cream) 16%, transparent), 0 12px 30px color-mix(in srgb, var(--night) 34%, transparent);
  -webkit-backdrop-filter: blur(18px) saturate(130%);
  backdrop-filter: blur(18px) saturate(130%);
  font-size: var(--ph-key-2);
  font-weight: 680;
  transition: transform 120ms ease, background 180ms ease;
}
.phone-guide-launch__mark {
  width: 19px;
  height: 17px;
  display: block;
  border-radius: 4px;
  background:
    linear-gradient(90deg, var(--me) 0 3px, transparent 3px 100%),
    repeating-linear-gradient(180deg, color-mix(in srgb, var(--cream) 90%, transparent) 0 2px, transparent 2px 6px);
}
.phone-guide-launch:active { transform: scale(.94); background: color-mix(in srgb, var(--bg-2) 90%, transparent); }

/* Home has three stable zones: Room at the top, Now in the picture, and Do at
   the bottom. On a normal phone Guide joins Ask Homie in that bottom action
   band instead of hovering through the room roster. The composer keeps its
   full-width questions and answers; only its resting input row reserves the
   guide key. Narrow phones use the compact key below so the Homie opener
   remains clear in the header even on short screens. */

/*
 * PLAY — the Guide as a shelf (critique-r0 C.5), built from the system's tokens
 * and components only (phone-system.css). What it replaced, for the record: a
 * one-card deck browsed with Next, a players <select>, masked chip bars with a
 * `More N` key, cover monograms cut off at the top, 4:5 tiles cropping 16:9 art
 * and a Next · Share · Play row at 1 : 1 : 2.
 *
 * Geometry. Everything is full-bleed and pads itself by the page margin, so a
 * horizontal shelf scrolls to the glass edge but its first tile starts where the
 * page does and nothing is ever cropped at rest (D.10). Art is 16:9 — the
 * covers, posters and stills this box has all measured 1.78 — and square for a
 * song; the made cover takes the same shape, so a shelf keeps one rhythm.
 */
.phone-guide {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: var(--safe-t) 0 calc(max(var(--dock-h), env(safe-area-inset-bottom, 0px)) + var(--sp-6));
  background: var(--bg-base);
  color: var(--label-1);
  font: 400 var(--fs-body)/var(--lh-body) var(--font-text);
}
.phone-guide[hidden], .phone-guide [hidden] { display: none !important; }
.phone-guide-head {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  min-height: var(--hit);
  padding: var(--sp-2) var(--page-margin) 0;
}
.phone-guide-title {
  flex: 1 1 auto;
  min-width: 0;
  font: 700 var(--fs-title1)/var(--lh-title1) var(--font-display);
  letter-spacing: -0.3px;
}
.phone-guide-browse { display: flex; flex-direction: column; gap: var(--sp-3); min-width: 0; }
.phone-guide-notice,
.phone-guide-searchrow,
.phone-guide-kinds,
.phone-guide-live-rooms { margin: 0 var(--page-margin); }
/* Search open: the field and a plain Cancel on one 44 pt row (embedded only). */
.phone-guide-searchrow { display: flex; align-items: center; gap: var(--sp-2); min-width: 0; }
.phone-guide-searchrow > .phone-guide-searchbox { flex: 1 1 auto; min-width: 0; }
.phone-guide-search-cancel { flex: none; padding-inline: var(--sp-1); }
/* The magnifier in the large title, quiet beside the avatar. */
.phone-guide-search-key { width: var(--hit); height: var(--hit); margin-right: calc(-1 * var(--sp-1)); background: transparent; color: var(--accent); }
.phone-guide-search-key .ui-ic { width: 24px; height: 24px; }
.phone-guide-search-key[hidden] { display: none; }

/* The four shelves as one segmented control: 44 pt so every segment is a full
   tap target, with the selected thumb inset 2 pt by a ring of the track. */
.phone-guide-kinds {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  height: var(--hit);
  border-radius: var(--r-full);
  background: var(--bg-elev1);
}
.phone-guide-kind {
  min-width: 0;
  margin: 0;
  padding: 0 var(--sp-2);
  border: 0;
  border-radius: var(--r-full);
  background: transparent;
  color: var(--label-2);
  font: 500 var(--fs-subhead)/var(--lh-subhead) var(--font-text);
  letter-spacing: -0.2px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  -webkit-tap-highlight-color: transparent;
  transition: background-color var(--dur-press-out) var(--ease-nav), color var(--dur-press-out);
}
.phone-guide-kind.is-on {
  color: var(--label-1);
  font-weight: 600;
  background: var(--bg-elev3);
  box-shadow: inset 0 0 0 2px var(--bg-elev1);
}

.phone-guide-searchbox { position: relative; display: block; }
.phone-guide-searchbox__glyph {
  position: absolute;
  top: 50%;
  left: var(--sp-3);
  display: grid;
  color: var(--label-2);
  transform: translateY(-50%);
  pointer-events: none;
}
.phone-guide-searchbox__glyph .ui-ic { width: 20px; height: 20px; }
.phone-guide-search {
  width: 100%;
  height: var(--hit);
  margin: 0;
  padding: 0 var(--sp-4) 0 var(--sp-11);
  border: 0;
  border-radius: var(--r-full);
  outline: none;
  background: var(--bg-elev1);
  color: var(--label-1);
  font: 400 var(--fs-body)/var(--lh-body) var(--font-text);
  letter-spacing: -0.4px;
  -webkit-appearance: none;
  appearance: none;
  -webkit-user-select: text;
  user-select: text;
  touch-action: manipulation;
}
.phone-guide-search::placeholder { color: var(--label-3); }
/* A focused search field is shown by its caret and its Cancel, as on iOS; a
   teal ring around a field that is already the only thing on the row doubled
   the page's accent for nothing. */
.phone-guide-search:focus, .phone-guide-search:focus-visible { outline: none; box-shadow: none; background: var(--bg-elev2); }

/* The chip row is the system's `.ui-chips`, full-bleed and padded by the margin. */
.phone-guide-groups { margin: 0; scroll-padding-inline: var(--page-margin); overscroll-behavior-inline: contain; }
.phone-guide-live-rooms { border-radius: var(--r-md); background: var(--bg-elev1); text-decoration: none; }

.phone-guide-notice {
  padding: var(--sp-3) var(--sp-4);
  border-radius: var(--r-md);
  background: var(--bg-elev2);
  font: 400 var(--fs-subhead)/var(--lh-subhead) var(--font-text);
}

.phone-guide-results { position: relative; display: flex; flex-direction: column; min-width: 0; }
.phone-guide-count {
  margin: 0 var(--page-margin) var(--sp-3);
  color: var(--label-2);
  font: 400 var(--fs-footnote)/var(--lh-footnote) var(--font-text);
  font-variant-numeric: tabular-nums;
}
.phone-guide-shelves { display: flex; flex-direction: column; gap: var(--section-gap); }

/* ---- a tile: art, a name, one line -------------------------------------- */
.phone-guide-tile {
  position: relative;
  display: flex;
  flex-direction: column;
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--label-1);
  text-align: left;
  -webkit-tap-highlight-color: transparent;
  transition: transform var(--dur-press-out) var(--ease-nav);
}
.phone-guide-tile:active { transform: scale(0.97); transition-duration: var(--dur-press-in); }
.phone-guide-tile__art,
.phone-guide-card__art,
.phone-guide-hero__art {
  position: relative;
  display: block;
  overflow: hidden;
  background: var(--bg-elev1);
}
.phone-guide-tile__art { width: 100%; aspect-ratio: 16 / 9; border-radius: var(--r-sm); }
.phone-guide-tile__art[data-shape="square"] { aspect-ratio: 1; }
:is(.phone-guide-tile__art, .phone-guide-card__art, .phone-guide-hero__art) img {
  position: absolute;
  inset: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* A broadcaster's mark on a quiet plate, never stretched to the edge. */
:is(.phone-guide-tile__art, .phone-guide-card__art).is-logo { background: var(--bg-elev2); }
:is(.phone-guide-tile__art, .phone-guide-card__art).is-logo img { inset: 18% 22%; width: 56%; height: 64%; object-fit: contain; }
:is(.phone-guide-tile__art, .phone-guide-card__art) > i {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  color: var(--label-2);
  font: 700 var(--fs-title2)/1 var(--font-display);
  font-style: normal;
}
.phone-guide-tile__title {
  margin-top: var(--sp-2);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font: 600 var(--fs-subhead)/var(--lh-subhead) var(--font-text);
  letter-spacing: -0.2px;
}
.phone-guide-tile__meta {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--label-2);
  font: 400 var(--fs-footnote)/var(--lh-footnote) var(--font-text);
}
/* A get that failed is news: two lines, primary label, never red (it is a
   friend's box switched off, not something this guest did). */
.phone-guide-tile__meta[data-failed] {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  white-space: normal;
  color: var(--label-1);
}
.phone-guide-tile__favourite {
  position: absolute;
  top: var(--sp-2);
  right: var(--sp-2);
  z-index: 2;
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  border-radius: var(--r-full);
  background: var(--bar-fill);
  color: var(--accent);
  font: 400 var(--fs-footnote)/1 var(--font-text);
  font-style: normal;
}

/* ---- a shelf -------------------------------------------------------------- */
.phone-guide-shelf { display: flex; flex-direction: column; gap: var(--sp-2); min-width: 0; }
.phone-guide-shelf__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  min-height: var(--hit);
  padding: 0 var(--sp-3) 0 var(--page-margin);
}
.phone-guide-shelf__title {
  min-width: 0;
  margin: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font: 650 var(--fs-title2)/var(--lh-title2) var(--font-display);
  letter-spacing: -0.2px;
}
.phone-guide-shelf__all { flex: none; }
.phone-guide-rail {
  display: flex;
  gap: var(--sp-3);
  overflow-x: auto;
  overscroll-behavior-inline: contain;
  scroll-snap-type: x proximity;
  scroll-padding-inline: var(--page-margin);
  padding: 0 var(--page-margin);
  scrollbar-width: none;
}
.phone-guide-rail::-webkit-scrollbar { display: none; }
/* 240 pt at 16:9 leaves a third of the next tile on a 390 pt phone and a
   quarter on a 375: the edge of the next one is what says the shelf moves. */
.phone-guide-rail > .phone-guide-tile { flex: 0 0 240px; scroll-snap-align: start; }
.phone-guide-shelf[data-shape="square"] .phone-guide-rail > .phone-guide-tile { flex-basis: 150px; }

/* ---- the featured pager ---------------------------------------------------- */
.phone-guide-hero { display: flex; flex-direction: column; gap: var(--sp-3); }
.phone-guide-hero__pager {
  display: flex;
  gap: var(--sp-3);
  overflow-x: auto;
  overscroll-behavior-inline: contain;
  scroll-snap-type: x mandatory;
  scroll-padding-inline: var(--page-margin);
  padding: 0 var(--page-margin);
  scrollbar-width: none;
}
.phone-guide-hero__pager::-webkit-scrollbar { display: none; }
.phone-guide-hero__page { flex: 0 0 100%; min-width: 0; display: flex; flex-direction: column; gap: var(--sp-3); scroll-snap-align: start; }
.phone-guide-hero__open {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  margin: 0;
  padding: 0;
  overflow: hidden;
  border: 0;
  border-radius: var(--r-lg);
  background: var(--bg-elev1);
  color: var(--label-1);
  text-align: left;
  -webkit-tap-highlight-color: transparent;
  transition: transform var(--dur-press-out) var(--ease-nav);
}
.phone-guide-hero__open:active { transform: scale(0.98); transition-duration: var(--dur-press-in); }
.phone-guide-hero__art { position: absolute; inset: 0; }
.phone-guide-hero__bar { display: flex; align-items: center; gap: var(--sp-3); min-width: 0; }
.phone-guide-hero__words { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
.phone-guide-hero__kicker,
.phone-guide-hero__title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.phone-guide-hero__kicker { color: var(--label-2); font: 400 var(--fs-footnote)/var(--lh-footnote) var(--font-text); }
.phone-guide-hero__kicker:empty { display: none; }
.phone-guide-hero__title { font: 600 var(--fs-title3)/var(--lh-title3) var(--font-display); letter-spacing: -0.1px; }
.phone-guide-hero__go { flex: none; }
.phone-guide-hero__dots { display: flex; justify-content: center; gap: var(--sp-2); }
.phone-guide-hero__dots i { width: 6px; height: 6px; border-radius: var(--r-full); background: var(--label-4); transition: background-color var(--dur-fade); }
.phone-guide-hero__dots i[data-on="yes"] { background: var(--label-1); }

/* ---- the grid (anything narrowed) ---------------------------------------- */
.phone-guide-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  align-items: start;
  gap: var(--sp-5) var(--sp-3);
  padding: 0 var(--page-margin);
}
@media (min-width: 700px) {
  .phone-guide-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

.phone-guide-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-8) var(--page-margin);
  color: var(--label-2);
  text-align: center;
  font: 400 var(--fs-body)/var(--lh-body) var(--font-text);
}
.phone-guide-empty > span { max-width: 32ch; }
.phone-guide[data-loading='yes'] .phone-guide-results::after {
  content: "";
  align-self: center;
  width: 28px;
  height: 28px;
  margin: var(--sp-8) 0;
  border: 3px solid var(--bg-elev3);
  border-top-color: var(--accent);
  border-radius: var(--r-full);
  animation: spin 800ms linear infinite;
}

/* ---- a title's page (the push) ------------------------------------------- */
/* Fixed to the glass: embedded it covers the tab's large title and stops under
   the tab bar, which stays up on a push (C.1). */
.phone-guide-page {
  position: fixed;
  inset: 0;
  z-index: 10;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding-bottom: calc(max(var(--dock-h), env(safe-area-inset-bottom, 0px)) + var(--sp-6));
  background: var(--bg-base);
  color: var(--label-1);
}
.phone-guide-page > .ui-navbar {
  padding-top: var(--safe-t);
  transition: background-color var(--dur-fade), box-shadow var(--dur-fade);
}
.phone-guide-page[data-scrolled="yes"] > .ui-navbar {
  background: var(--bar-fill);
  -webkit-backdrop-filter: var(--bar-blur);
  backdrop-filter: var(--bar-blur);
  box-shadow: inset 0 -0.5px 0 var(--separator);
}
.phone-guide-page .ui-navbar__title { transition: opacity var(--dur-fade); }
.phone-guide-page:not([data-scrolled="yes"]) .ui-navbar__title { opacity: 0; }
/*
 * The back key is a round chevron on a blurred plate (the Apple TV app's title
 * page): it floats over the art at rest, and stays the same key once the bar
 * fills in on scroll, so it never changes shape under a thumb.
 */
.phone-guide-page .ui-navbar { padding-inline: var(--sp-3); }
.phone-guide-page .ui-navbar__back {
  justify-content: center;
  width: var(--hit);
  padding: 0;
  color: var(--label-1);
}
.phone-guide-page .ui-navbar__back::before {
  content: "";
  position: absolute;
  width: 36px;
  height: 36px;
  border-radius: var(--r-full);
  background: var(--bar-fill);
  -webkit-backdrop-filter: var(--bar-blur);
  backdrop-filter: var(--bar-blur);
}
.phone-guide-page .ui-navbar__back { position: relative; }
.phone-guide-page .ui-navbar__back .ui-ic { position: relative; width: 24px; height: 24px; margin: 0 2px 0 0; }
.phone-guide-page .ui-navbar__back-label { display: none; }
.phone-guide-card { display: flex; flex-direction: column; gap: var(--sp-5); padding-top: var(--sp-2); }
.phone-guide-card__art { margin: 0 var(--page-margin); aspect-ratio: 16 / 9; border-radius: var(--r-md); }
/* A wide title's art runs to the glass edges and under the bar, the way a
   film's page opens on its still; the words start at the page margin below. */
.phone-guide-card[data-shape="wide"] { padding-top: 0; }
.phone-guide-card[data-shape="wide"] .phone-guide-card__art {
  margin: calc(-1 * (var(--safe-t) + var(--hit))) 0 0;
  border-radius: 0;
}
.phone-guide-card[data-shape="square"] .phone-guide-card__art {
  width: min(260px, calc(100% - 2 * var(--page-margin)));
  aspect-ratio: 1;
  margin: 0 auto;
  box-shadow: 0 16px 40px color-mix(in srgb, var(--bg-base) 70%, transparent);
}
.phone-guide-card__art > i { font-size: var(--fs-large-title); }
/* A live preview plays over the art; a made film on this phone plays in the
   copy, above the keys, with the browser's own controls. */
.phone-guide-card__art > .phone-guide-card__video {
  position: absolute;
  inset: 0;
  z-index: 2;
  width: 100%;
  height: 100%;
  object-fit: cover;
  background: var(--bg-base);
}
.phone-guide-card__audio { display: block; width: 100%; height: var(--hit); border-radius: var(--r-sm); }
.phone-guide-card__audio.phone-guide-card__video { height: auto; max-height: 42svh; object-fit: contain; background: var(--bg-base); }
.phone-guide-card__copy { display: flex; flex-direction: column; gap: var(--sp-2); padding: 0 var(--page-margin); min-width: 0; }
.phone-guide-card__title {
  margin: 0;
  overflow-wrap: anywhere;
  text-wrap: balance;
  font: 700 var(--fs-title1)/var(--lh-title1) var(--font-display);
  letter-spacing: -0.3px;
}
.phone-guide-card__channel { color: var(--label-2); font: 600 var(--fs-subhead)/var(--lh-subhead) var(--font-text); }
.phone-guide-card__facts {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0 6px;
  color: var(--label-2);
  font: 400 var(--fs-footnote)/var(--lh-footnote) var(--font-text);
  font-variant-numeric: tabular-nums;
}
.phone-guide-card__facts:empty { display: none; }
.phone-guide-dot { color: var(--label-3); }
.phone-guide-card__about { position: relative; margin-top: var(--sp-1); }
/* Information: the frame's grouped list, a section below the keys. */
.phone-guide-card__info { margin-top: var(--section-gap); }
.phone-guide-card__desc {
  margin: 0;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  overflow: hidden;
  color: var(--label-2);
  font: 400 var(--fs-body)/var(--lh-body) var(--font-text);
  letter-spacing: -0.4px;
}
.phone-guide-card__desc[data-expanded="yes"] { display: block; -webkit-line-clamp: unset; overflow: visible; }
/* "More" over the end of the clamped last line, on a fade of the page colour. */
.phone-guide-card__more {
  position: absolute;
  right: 0;
  bottom: 0;
  min-height: var(--lh-body);
  height: var(--lh-body);
  padding: 0 0 0 var(--sp-11);
  background: linear-gradient(90deg, transparent, var(--bg-base) var(--sp-8));
  font: 600 var(--fs-body)/var(--lh-body) var(--font-text);
}
.phone-guide-card__more::after { content: ""; position: absolute; inset: -11px 0; }
.phone-guide-card__next { display: flex; flex-direction: column; gap: 2px; color: var(--label-2); font: 400 var(--fs-footnote)/var(--lh-footnote) var(--font-text); }
.phone-guide-card__next i { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-style: normal; }
.phone-guide-card__progress { display: block; height: 4px; margin-top: var(--sp-2); overflow: hidden; border-radius: var(--r-full); background: var(--bg-elev3); }
.phone-guide-card__progress i { display: block; height: 100%; border-radius: inherit; background: var(--accent); }
.phone-guide-card__actions { display: flex; flex-direction: column; gap: var(--sp-3); margin-top: var(--sp-3); }
.phone-guide-card__press { margin: 0; color: var(--label-1); font: 400 var(--fs-subhead)/var(--lh-subhead) var(--font-text); }
.phone-guide-card__press:is([data-state="guide-getting-from"], [data-state="guide-get-here-from"], [data-state="guide-get-failed-before"]) {
  color: var(--label-2);
  font: 400 var(--fs-footnote)/var(--lh-footnote) var(--font-text);
}
.phone-guide-preview[data-state="loading"] span::after { content: "…"; }
:is(.phone-guide-preview[data-state="playing"], .phone-guide-preview[data-state="sounding"], .phone-guide-onphone[data-state="on"], .phone-guide-remind.is-on) {
  background: var(--accent-fill);
  color: var(--accent);
}
:is(.phone-guide-preview, .phone-guide-remind):is([data-state="unavailable"], [data-state="refused"]) { opacity: 0.5; }
.phone-guide-card__manage { display: flex; align-items: center; gap: var(--sp-2); padding-right: var(--sp-2); }
.phone-guide-favourite { font: 400 var(--fs-body)/1 var(--font-text); }
.phone-guide-favourite.is-on { color: var(--accent); }
.phone-guide-manage .mk { width: 20px; height: 20px; }
.phone-guide-series { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-3); margin-top: var(--sp-2); }
.phone-guide-series__field { display: flex; flex-direction: column; gap: var(--sp-1); min-width: 0; color: var(--label-2); font: 400 var(--fs-footnote)/var(--lh-footnote) var(--font-text); }
.phone-guide-series__select {
  width: 100%;
  height: var(--hit);
  padding: 0 var(--sp-3);
  border: 0;
  border-radius: var(--r-sm);
  background: var(--bg-elev1);
  color: var(--label-1);
  font: 400 var(--fs-body)/var(--lh-body) var(--font-text);
}
.phone-guide-series__state { grid-column: 1 / -1; display: flex; align-items: center; min-height: var(--hit); color: var(--label-2); font: 400 var(--fs-subhead)/var(--lh-subhead) var(--font-text); }
.phone-guide-series__retry {
  min-height: var(--hit);
  padding: 0 var(--sp-4);
  border: 0;
  border-radius: var(--r-full);
  background: var(--bg-elev2);
  color: var(--label-1);
  font: 600 var(--fs-subhead)/var(--lh-subhead) var(--font-text);
}

body[data-phone-guide='on'] .shell-anchor-host,
body[data-phone-guide='on'] .lobby > .now,
body[data-phone-guide='on'] .phone-guide-launch { visibility: hidden; }
body[data-shell='open'] .phone-guide-launch { visibility: hidden; }
body[data-shell='open'] .play__exit,
body[data-shell='open'] .play__activity { visibility: hidden; }

@media (prefers-reduced-motion: reduce) {
  .phone-guide-launch { transition: none; }
  .phone-guide[data-loading='yes'] .phone-guide-results::after { animation: none; }
}

/* WHY A KEY IS NOT THERE. Draws nothing at all when nothing is missing — the
   ordinary state — for the same reason `.remote__feedback` gave up its
   `min-height`: a container drawn whether or not it has anything in it is the
   empty box that was photographed. */
.remote__absence {
  font-size: var(--fine);
  line-height: 1.35;
  color: var(--ink-3);
}
.remote__absence:empty { display: none; }

/* ==========================================================================
   THE REMOTE — one touch surface and the round keys under it
   (r2 Room + Now lane, 2026-09-14; replaces the stacked plates of round 1)
   ==========================================================================

   The round-1 remote was three rows of tiles that ended at 345 pt and left
   53 % of the glass empty (critique-r1 B12). The Apple TV Remote app is one
   large surface you do not aim at, with round keys under it; this is that.

   `.deck` is a three-column grid and the sections inside it are
   `display: contents`, so the surface, the way back and the plates are laid out
   as one composition without moving a node out of the section that builds it
   (`sectionTransport`, `sectionWayBack`, the cast opener):

     what is on                              (full width)
     [ touch surface ]                       (full width)
     (Back) (Home) ( ⏮  ⏯  ⏭ )               row of round keys + playback plate
     (  −    |    mute    |    +  )          volume plate, full width
   and with nothing playing the volume plate takes the playback plate's place
   beside Back and Home. Every plate is still one family (the worth-holding
   probe's law), now drawn as a capsule. */
.deck {
  display: grid; grid-template-columns: auto auto minmax(0, 1fr);
  column-gap: var(--sp-3); row-gap: var(--sp-4); align-items: center;
}
.deck > .shell__sec, .deck .remote__deck, .deck .remote__nav { display: contents; }
.deck .shell__now, .deck .remote__absence, .deck .remote__feedback, .deck .remote__pad,
.deck .remote__handoff, .deck > .cast, .deck > :not(.shell__sec) { grid-column: 1 / -1; }
.deck .shell__now { order: 1; }
.deck .remote__absence { order: 2; }
.deck .remote__feedback { order: 3; }
.deck .remote__pad { order: 4; }
.deck .remote__key--nav { order: 5; grid-column: auto; }
.deck .remote__bank[data-bank="playback"] { order: 6; grid-column: 3; }
.deck .remote__bank[data-bank="volume"] { order: 7; }
.deck:not(:has(.remote__bank[data-bank="playback"])) .remote__bank[data-bank="volume"] { order: 6; grid-column: 3; }
.deck .remote__bank { order: 8; grid-column: 1 / -1; }
.deck .remote__handoff { order: 9; }
.deck > :not(.shell__sec) { order: 10; }

/* -- the surface ----------------------------------------------------------- */
.remote__pad {
  position: relative; overflow: hidden;
  height: clamp(200px, calc(100svh - 420px), 420px);
  border-radius: var(--r-lg);
  background: radial-gradient(120% 90% at 50% 0%, var(--bg-elev2), var(--bg-elev1) 70%);
  box-shadow: inset 0 0 0 0.5px var(--separator);
  touch-action: none; user-select: none; -webkit-user-select: none;
  -webkit-tap-highlight-color: transparent; outline: none;
}
.shell__body:has(.shell__back-to-game:not([hidden])) .remote__pad { height: clamp(200px, calc(100svh - 500px), 360px); }
.remote__pad:focus-visible { box-shadow: inset 0 0 0 2px var(--accent); }
/* A soft light under the finger, the way the app shows where you are touching. */
.remote__pad-glow {
  position: absolute; left: var(--pad-x, 50%); top: var(--pad-y, 50%);
  width: 120px; height: 120px; margin: -60px 0 0 -60px; border-radius: var(--r-full);
  background: radial-gradient(closest-side, rgba(255, 255, 255, 0.14), transparent);
  opacity: 0; transition: opacity var(--dur-fade); pointer-events: none;
}
.remote__pad[data-touch="yes"] .remote__pad-glow { opacity: 1; transition-duration: var(--dur-press-in); }
.remote__pad-edge, .remote__pad-centre {
  position: absolute; display: grid; place-items: center;
  width: var(--hit); height: var(--hit); color: var(--label-3);
  transition: color var(--dur-press-out), transform var(--dur-press-out);
  pointer-events: none;
}
.remote__pad-edge .mk { width: 22px; height: 22px; }
.remote__pad-centre .mk { width: 30px; height: 30px; }
.remote__pad-edge[data-edge="up"] { top: var(--sp-3); left: calc(50% - 22px); }
.remote__pad-edge[data-edge="down"] { bottom: var(--sp-8); left: calc(50% - 22px); }
.remote__pad-edge[data-edge="left"] { left: var(--sp-3); top: calc(50% - 22px); }
.remote__pad-edge[data-edge="right"] { right: var(--sp-3); top: calc(50% - 22px); }
.remote__pad-centre { left: calc(50% - 22px); top: calc(50% - 22px); color: var(--label-2); }
.remote__pad[data-mode="navigate"] .remote__pad-centre::before {
  content: ""; width: 10px; height: 10px; border-radius: var(--r-full); background: var(--label-3);
}
.remote__pad-hint {
  position: absolute; left: var(--sp-4); right: var(--sp-4); bottom: var(--sp-3);
  text-align: center; font: 400 var(--fs-caption)/var(--lh-caption) var(--font-text); color: var(--label-3);
  pointer-events: none;
}
/* The flash: the edge a gesture went to lights, or the whole surface nudges
   when that gesture had nothing to press. */
.remote__pad[data-flash="up"] [data-edge="up"], .remote__pad[data-flash="down"] [data-edge="down"],
.remote__pad[data-flash="left"] [data-edge="left"], .remote__pad[data-flash="right"] [data-edge="right"],
.remote__pad[data-flash="select"] .remote__pad-centre { color: var(--label-1); transform: scale(1.18); transition-duration: var(--dur-press-in); }
.remote__pad[data-flash="none"] { animation: remote-pad-bump 220ms var(--ease-nav); }
@keyframes remote-pad-bump { 30% { transform: translateX(-4px); } 70% { transform: translateX(3px); } }
@media (prefers-reduced-motion: reduce) {
  .remote__pad[data-flash="none"] { animation: none; }
  .remote__pad-edge, .remote__pad-centre { transition: none; transform: none !important; }
}

/* -- the round keys and the capsule plates --------------------------------- */
.remote__bank {
  display: flex; align-items: stretch; min-height: var(--sp-14);
  border-radius: var(--r-full); overflow: hidden;
  background: var(--bg-elev1);
}
.remote__bank:empty { display: none; }
.remote__bank[data-bank="tracks"] { flex-wrap: wrap; border-radius: var(--r-md); }
.remote__key {
  flex: 1 1 0; min-width: 0; min-height: var(--sp-14);
  display: grid; place-items: center; padding: 0 var(--sp-2);
  border: 0; border-radius: 0; background: transparent;
  box-shadow: inset -0.5px 0 0 var(--separator);
  color: var(--label-1);
  transition: background-color var(--dur-press-out);
}
.remote__key:last-child { box-shadow: none; }
.remote__key--nav {
  flex: none; width: var(--sp-14); height: var(--sp-14); padding: 0;
  border-radius: var(--r-full); background: var(--bg-elev1); box-shadow: none;
}
/* The playback key is the same material as its neighbours: the surface's
   centre is the big play/pause, and a cream zone inside a dark capsule read as
   a second primary button. */
.remote__key--primary { background: transparent; color: var(--label-1); }
.remote__key .mk { width: 24px; height: 24px; }
.key__face { display: grid; place-items: center; }
/* Round keys and the two rockers are glyph-only, as on every remote (critique-r1
   B5 counted "icon over a grey label" as its own button style); the word stays
   in the document as the key's name. Channel, track and unknown plates keep
   their words, because those glyphs are not universal. */
.key__word {
  display: block; max-width: 100%; margin-top: var(--sp-1);
  font: 500 var(--fs-caption)/var(--lh-caption) var(--font-text); color: var(--label-2);
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
  overflow: hidden; overflow-wrap: anywhere;
}
.key__word:empty { display: none; }
.remote__key--nav .key__word,
.remote__bank[data-bank="volume"] .key__word,
.remote__bank[data-bank="playback"] .key__word {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.remote__key:active:not(:disabled) { background-color: var(--bg-elev3); transition-duration: var(--dur-press-in); }
.remote__key--nav:active:not(:disabled) { transform: scale(0.94); }
@media (prefers-reduced-motion: reduce) {
  .remote__key--nav:active:not(:disabled) { transform: none; }
}
.remote__key:disabled,
.remote__key[data-state="dark"] { opacity: 1; color: var(--label-4); }

/* The feedback line: a sentence somebody ELSE wrote — a `spoken` line from
   `/watch/*` or `/listen/*` — or nothing at all.

   `min-height` is gone with it. An empty bordered box 42 pt tall was the *[
   empty box ]* row in that photograph: a container drawn whether or not it
   had anything in it, which is the box asking to be explained. It costs no
   height now until something has been said. */
.remote__feedback {
  display: flex;
  align-items: center;
  padding: var(--sp-3) var(--sp-4);
  border-radius: var(--r-md);
  background: var(--bg-elev1);
  font: 400 var(--fs-footnote)/var(--lh-footnote) var(--font-text);
  color: var(--label-2);
  overflow-wrap: anywhere;
}
.remote__feedback:empty { display: none; }
.remote__section-head {
  margin-top: 4px;
  font-size: var(--kicker);
  font-weight: 680;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.remote__handoff {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 10px;
  border: 1px solid color-mix(in srgb, var(--me) 45%, var(--line));
  border-radius: 14px;
  background: color-mix(in srgb, var(--me) 7%, var(--bg));
}
.remote__handoff[hidden] { display: none; }
.remote__handoff-button {
  min-height: var(--tap);
  padding: 10px 13px;
  border-radius: 12px;
  background: var(--me);
  color: var(--bg-2);
  font-weight: 700;
  text-align: left;
}
.remote__handoff-button:active:not(:disabled) { transform: scale(0.985); filter: brightness(1.1); }
.remote__handoff-button:disabled { opacity: 0.45; }

/* The shelf, the search field, the queue door and the mode tabs are gone from
   this stylesheet with the panes they painted. That block was a second launcher
   living inside the shell — the "bunch of square buttons" verdict in literal
   form — and browsing is the AI's, on the AI's own full-bleed surface, where it
   can be composed for the room rather than listed for it. */

/* An empty shelf draws a dash rather than a sentence about being empty, and an
   unreachable one draws a struck circle. "Nothing here" and "we could not look"
   must never render as the same thing — that is the rule in CLAUDE.md, and the
   marks are what carry it now. */
.remote__empty { margin: 8px 0; color: var(--ink-3); font-size: var(--fine); }
.remote__empty:empty::before { content: "—"; opacity: 0.55; }
.remote__empty--error { color: var(--cream); }
.remote__empty--error:empty::before { content: "⊘"; opacity: 0.8; }

/* `.remote__state` is gone, and with it the green dot, the `◑ 50` and the `⋯`
   count. It rendered as `●  … ◑ 50` on the phone: a dot whose colour
   meant "the daemon can steer Chrome", a volume level nobody asked for beside
   the two keys that change it, and a count of a queue this surface does not
   show. Three things that had to be explained, on a screen that may not need
   explaining. The readings behind them still light and darken the keys. */
.remote__handoff-mark { margin-right: 8px; opacity: 0.7; }
.remote__feedback[data-state="remote-busy"]::before { content: "⋯"; opacity: 0.7; }

/* `.facts` — `link / rtt-ms / input-channel / secure-context / screen-lock /
   box` — is gone. It was engineering telemetry on the surface a guest holds,
   and `secure-context: true` is the clearest example this project has produced
   of something that passes law 1 (nobody wrote it) and fails the only test that
   matters in a living room (nobody can read it). See `sectionLiveness`. */

/* ========================================================================== */
/* Landscape                                                                  */
/* ========================================================================== */

@media (orientation: landscape) and (max-height: 500px) {
  .shell__body { gap: 12px; }
  .surface { padding: 0 10px calc(10px + var(--safe-b)); }
  .strip { max-height: 78%; }
  .ctl--stick, .ctl--dpad { min-height: 150px; }
  .stick__base { width: 128px; height: 128px; }
  .stick__knob { width: 62px; height: 62px; }
  .ctl--button { width: 84px; height: 84px; }
  /* Landscape: the surface gives the height back to the keys. */
  .remote__pad { height: 160px; }
}

@media (prefers-reduced-motion: reduce) {
  * { animation-duration: 1ms !important; transition-duration: 1ms !important; }
}

/* ========================================================================== */
/* Self-test output                                                           */
/* ========================================================================== */

.selftest {
  height: 100%;
  overflow-y: auto;
  touch-action: pan-y;
  padding: calc(18px + var(--safe-t)) 18px calc(24px + var(--safe-b));
  font: 13px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;
}
.selftest h1 { font-size: var(--dek); margin: 0 0 6px; font-family: inherit; }
.selftest .note { color: var(--ink-2); margin: 0 0 16px; font-size: var(--kicker); line-height: 1.6; }
.selftest .row { display: flex; gap: 10px; padding: 7px 0; border-bottom: 1px solid var(--line); }
.selftest .row b { flex: none; width: 62px; }
.selftest .pass b { color: var(--go); }
.selftest .fail b { color: var(--stop); }
.selftest .verdict { margin: 16px 0 0; padding: 14px; border-radius: 14px; font-size: var(--fine); line-height: 1.5; }
.selftest .verdict.ok { background: color-mix(in srgb, var(--success) 12%, transparent); border: 1px solid color-mix(in srgb, var(--success) 30%, transparent); }
.selftest .verdict.bad { background: color-mix(in srgb, var(--live) 12%, transparent); border: 1px solid color-mix(in srgb, var(--live) 30%, transparent); }
.selftest-stage { position: fixed; left: -10000px; top: 0; width: 390px; height: 700px; }

/* ========================================================================== */
/* States that used to be sentences                                           */
/* ========================================================================== */

/*
 * Everything below exists because a string was deleted from the JS above it.
 * The rule the whole change follows: a state a person needs is DRAWN, and words
 * — when there are any — come from the model at runtime and are laid over the
 * drawing. Nothing here invents content; `content:` here is only ever a mark.
 */

/* The connection bar. The spinner was always the message; the three sentences
   beside it were core narrating it. `stalled` slows and reddens, `unsure` is
   the state that used to render as a green dot and total silence and keeps its
   own distinct look so that fix is not quietly lost again. */
.link__text:empty { display: none; }
.link[data-state="link-unsure"] .link__spin { animation-duration: 2.4s; opacity: 0.7; }

/* A WARNING WITH NO WORDS IS WORSE THAN NO WARNING, AND IT WAS THE FIRST THING
   ON EVERY SCREEN.

   What this replaced: `.notice__line:empty { min-height: 10px }` and a
   `::before` painting "⊘". Its comment — *a trouble with no words yet is still
   a trouble; the bar must not go invisible for want of a sentence* — is
   describing a real failure and it is still guarded against below. What it did
   not anticipate is that the wordless case is not rare and not transient: it is
   the state of every phone that came in through the raw-IP plain-http door,
   which CLAUDE.md calls a permanent part of the design, for the whole evening.
   `hub.ts` sends `{ t: 'trouble', code: 'rejected', message: '' }` for an
   insecure context on purpose, so that the words come from the model rather
   than from core — and until the model has ever spoken about that state, there
   are none.

   MEASURED on this daemon, one phone at 390x844, in a game: `.notice`
   379x38 at x 14 y 0 — display flex, opacity 1, visibility visible,
   `innerText` "" — sitting ABOVE the game's own name, containing one glyph.
   On the lobby too. On both phones. A guest cannot act on "⊘": it does not say
   mic, or camera, or tilt, or http, and it is the same shape every night, so
   the one bar that will later carry real news is furniture before it ever
   carries any.

   So: a line with no words draws no line, and a bar with no lines draws no bar.
   The node itself is untouched — `<p class="notice__line" data-state="…">` is
   still in the document, so the state is still readable by an instrument and
   still there for the model to write into, and the *instant* any words arrive
   (from the box, or from `wordsFor` remembering what the model last said about
   this code) the bar comes back in full. That is the difference from the
   failure the old comment feared: going invisible for want of a sentence is
   only quiet if the sentence never comes. Nothing here suppresses a trouble
   that has one. */
.notice__line:empty { display: none; }
.notice:not(:has(.notice__line:not(:empty))) { display: none; }

/* A choice card waiting on its options. It used to say so; it now shows three
   dim placeholder rows, which is a card that is clearly not finished rather
   than a row of blank buttons that reads as a controller that has failed. */
.card__why[data-state="choice-awaiting-options"] {
  display: flex;
  gap: 6px;
  margin: 6px 0 0;
}
.card__why[data-state="choice-awaiting-options"]::before {
  content: "";
  flex: 1;
  height: 34px;
  border-radius: 10px;
  background: repeating-linear-gradient(135deg, color-mix(in srgb, var(--cream) 5%, transparent) 0 8px, transparent 8px 16px);
}

/* Tilt's readout. `ready` / `live` / `not allowed` were three words; they are a
   hollow dot, a lit dot and a struck dot. The numeric readout replaces them
   entirely the moment real values start arriving. */
.card__tag[data-tilt]:empty::before { content: "○"; }
.card__tag[data-tilt="live"]:empty::before { content: "●"; color: var(--go); }
.card__tag[data-tilt="refused"]:empty::before { content: "⊘"; color: var(--cream); }

/* Every submit on this phone is the same mark, so a thumb learns it once. */
.btn[data-state$="-send"], .create__go { font-size: var(--h3); line-height: 1; }

/* Sheet heads no longer carry a title. The mark that opened the sheet is still
   on screen behind it, so the guest already knows which panel this is. */
.sheet__title:empty { display: none; }

/* ========================================================================== */
/* The way in — the one field on the phone                                    */
/* ========================================================================== */

/* One node, two possible parents: the lobby when nothing holds this seat, and
   the top of the curtain from everywhere else. See `hostWayIn` — a person
   inside a game must still be able to reach the house, and a second field would
   be a second sentence in flight with two places for one reply to land.

   `.lobby__say` — the lobby's wrapper — is gone: the field is appended to the
   lobby itself now, so there is no always-present, usually-empty node of ours
   in the tree and nothing for THE_SHELL §16 to have to admit. See
   `renderLobby`. */
.shell__say:empty { display: none; }
.shell__say .wayin { padding: 0; }

/* ==========================================================================
   FRIENDS RULES THE HOUSE PAGES STILL WEAR
   ==========================================================================
   The Room tab itself is styled in the ROOM block further down (Room lane,
   2026-09-14). What stays here is shared: the House switches and chips reuse
   `.friends__toggle` and `.friends__scope`, and `.friends__quiet` /
   `.friends__refused` are the quiet line and the refusal line on many pages. */
.friends__toggle { display: flex; align-items: center; gap: 10px; width: 100%; padding: 10px 12px; border: 1px solid var(--line); border-radius: 14px; background: none; color: var(--cream); font: inherit; font-size: var(--fine); text-align: left; }
.friends__toggle-knob { flex: none; width: 34px; height: 20px; border-radius: 999px; background: color-mix(in srgb, var(--cream) 25%, transparent); position: relative; transition: background .15s; }
.friends__toggle-knob::after { content: ""; position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; border-radius: 50%; background: var(--cream); transition: left .15s; }
.friends__toggle.is-on .friends__toggle-knob { background: var(--me); }
.friends__toggle.is-on .friends__toggle-knob::after { left: 16px; background: var(--bg); }
.stage-band { display: grid; gap: 10px; margin: 12px 0 4px; }
.stage-places { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 8px; }
.stage-place {
  aspect-ratio: 16 / 10; border: 1px solid var(--line); border-radius: 10px; background:
    linear-gradient(160deg, color-mix(in srgb, var(--cream) 12%, transparent), transparent 55%);
  position: relative;
}
.stage-place::after {
  content: ""; position: absolute; width: 34%; height: 38%; border-radius: 3px;
  background: var(--cream); opacity: .55;
}
.stage-place[data-place="tl"]::after { top: 10%; left: 10%; }
.stage-place[data-place="tr"]::after { top: 10%; right: 10%; }
.stage-place[data-place="bl"]::after { bottom: 10%; left: 10%; }
.stage-place[data-place="br"]::after { bottom: 10%; right: 10%; }
.stage-place[data-place="bottom"]::after { left: 10%; right: 10%; width: auto; height: 28%; bottom: 10%; }
.stage-place[data-place="blend"]::after { left: 28%; right: 28%; width: auto; height: 70%; bottom: 0; border-radius: 8px 8px 0 0; opacity: .8; }
.stage-place.is-on { border-color: var(--me); box-shadow: 0 0 0 1px var(--me); }
.stage-place.is-on::after { background: var(--me); opacity: 1; }
.stage-record { display: flex; align-items: center; justify-content: center; gap: 8px; width: 100%; min-height: 44px; }
.stage-record.is-on { color: #ff5a5a; }
.stage-last { font-size: var(--fine); font-weight: 640; color: var(--me); }
.friends__words-key { align-self: start; margin-left: calc(-1 * var(--sp-2)); }
.friends__scope {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: color-mix(in srgb, var(--bg-2) 48%, transparent);
  color: var(--ink-2);
  font: inherit;
  text-align: left;
}
.friends__scope.is-on { color: var(--cream); border-color: var(--me); box-shadow: 0 0 0 1px var(--me); }
.friends__scope:active { transform: scale(.97); }
.friends__scope-name { font-size: var(--fine); font-weight: 640; line-height: 1.2; }
.friends__scope-hint { font-size: var(--kicker); line-height: 1.3; color: var(--ink-3); }
/* The refusal is not red. A box that cannot open a shared room yet has not
   failed at anything a guest did, and an alarm colour on an ordinary,
   explainable state is how a room learns to distrust the whole surface. */
.friends__refused { margin: 0; font-size: var(--fine); font-weight: 620; line-height: 1.35; color: var(--cream); }
.friends__quiet { margin: 0; font-size: var(--fine); line-height: 1.4; color: var(--ink-3); }

/* ==========================================================================
   HOMIE — "a conversation with a workshop" (critique-r0 C.5, homie lane)
   ==========================================================================
   The Homie tab is three regions stacked in one column: the navigation bar a
   thread wears (hidden on the list, where the tab's large title says Homie),
   ONE scroller, and the composer. Only the middle scrolls; the composer docks
   8 pt above the tab bar (D.10) with a 24 pt fade over the list's last line.

   The hub's own buttons are written `.aihub.aihub .x`: the shared floor
   `.shell button:not([class*="ui-"])` gives every curtain button 44 pt of
   min-height at (0,2,1), and a 36 pt key inside a 52 pt capsule has to beat it
   without `!important`. The 44 pt hit area is each key's ::after instead.

   Everything in the scroller is the design system's: sections headed in
   title2, inset grouped lists of 64 pt rows, a two-up gallery for what Homie
   made. No borders on cards, no uppercase, no hue that is not a token. The
   rules this replaced were four generations of overrides stacked on one
   another (bordered cards with a coloured inner edge, a swipe-width Stop, a
   three-row composer); they are deleted rather than overridden. */

.shell__body[data-view="activity"] {
  overflow: hidden;
  gap: 0;
  padding-bottom: calc(var(--dock-h) + var(--sp-2));
}
.shell__ai { width: 100%; min-width: 0; max-width: 100%; min-height: 0; }
/* The hub runs edge to edge so its scroller pads inline itself: a scroller that
   clips at the page margin crops the corners of what it holds (D.10). */
.shell__body[data-view="activity"] .shell__ai {
  display: flex;
  flex: 1 1 0;
  overflow: hidden;
  width: auto;
  max-width: none;
  margin: 0 calc(-1 * var(--page-margin));
}
.aihub {
  position: relative;
  display: flex;
  flex: 1 1 0;
  flex-direction: column;
  width: 100%;
  min-width: 0;
  min-height: 0;
  color: var(--label-1);
  font: 400 var(--fs-body)/var(--lh-body) var(--font-text);
  letter-spacing: -0.4px;
}
.aihub__toolbar { flex: none; padding: 0 var(--page-margin); }
.aihub__toolbar[hidden] { display: none; }

/* Homie · This project — the household's second list, only in development. */
.aihub__sources {
  flex: none;
  display: grid;
  grid-template-columns: 1fr 1fr;
  margin: 0 var(--page-margin) var(--sp-3);
  padding: 2px;
  border-radius: var(--r-full);
  background: var(--bg-elev1);
}
.aihub__sources[hidden] { display: none; }
.aihub.aihub .aihub__sources button {
  min-height: 32px;
  border: 0;
  border-radius: var(--r-full);
  color: var(--label-2);
  background: transparent;
  font: 500 var(--fs-subhead)/var(--lh-subhead) var(--font-text);
}
.aihub.aihub .aihub__sources button[aria-pressed="true"] { color: var(--label-1); background: var(--bg-elev3); }

.aihub__main {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  gap: var(--section-gap);
  min-width: 0;
  min-height: 0;
  padding: var(--sp-2) var(--page-margin) var(--sp-6);
  overflow-x: hidden;
  overflow-y: auto;
  overscroll-behavior: contain;
  touch-action: pan-y;
  -webkit-overflow-scrolling: touch;
}
.aihub__main > * { flex: none; }

/* -- the list ------------------------------------------------------------- */
.aihub__section { gap: var(--sp-3); }
.aihub__sessions { width: 100%; }
.aihub__sessions .ui-row { min-height: 64px; }
/* Up to two lines: a conversation's title is still the sentence somebody typed. */
.aihub__row-title { font-weight: 600; white-space: normal; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow-wrap: anywhere; }
.aihub__subtitle { display: flex; min-width: 0; font-variant-numeric: tabular-nums; }
.aihub__mode { flex: none; }
/* A loop's word carries its pass and countdown, so it is the half that ellipses. */
.aihub__mode[data-loop] { flex: 0 1 auto; max-width: 72%; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.aihub__mode[data-loop] + .aihub__session-meta { flex: 1 1 0; }
.aihub__mode + .aihub__session-meta:not(:empty)::before { content: " · "; white-space: pre; color: var(--label-2); }
.aihub__session-meta { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.aihub__session-meta[data-tone="bad"] { color: var(--destructive); }
.aihub__when { font: 400 var(--fs-subhead)/var(--lh-subhead) var(--font-text); font-variant-numeric: tabular-nums; color: var(--label-3); }
.aihub__sessions > .ui-row:has(> .ui-row__tile) + *::before { left: 57px; }

/* The mascot in a 29 pt tile, white on the mode's tint. */
.aihub__tile .homie-brand-mark,
.aihub__tile .homie-brand-mark svg { display: block; width: 22px; height: 22px; }
.aihub__tile[data-live="yes"] { background: var(--accent-fill); color: var(--accent); }
.aihub__tile[data-live="yes"] .homie-brand-mark { animation: aihub-bob 2s ease-in-out infinite; }
@keyframes aihub-bob { 50% { transform: translateY(-2px); } }
/* 1.6 s shimmer across a thin track under the words (D.9 "working state"): the thread's live card only. */
.aihub__progress {
  display: block;
  height: 2px;
  margin-top: 6px;
  border-radius: var(--r-full);
  background:
    linear-gradient(90deg, transparent, var(--accent) 50%, transparent) 0 0 / 40% 100% no-repeat,
    var(--accent-fill);
  animation: aihub-shimmer 1.6s linear infinite;
}
@keyframes aihub-shimmer { from { background-position: -40% 0, 0 0; } to { background-position: 140% 0, 0 0; } }

/* Working: ONE sideways strip of 52 pt capsules, bleeding to the glass so it
   reads as a shelf that scrolls, with the page margin as its inline padding. */
.aihub__section--working { margin: 0 calc(-1 * var(--page-margin)); }
.aihub__strip {
  display: flex;
  gap: var(--sp-2);
  padding: 0 var(--page-margin);
  overflow-x: auto;
  overflow-y: hidden;
  overscroll-behavior-x: contain;
  scroll-padding-inline: var(--page-margin);
  scroll-snap-type: x proximity;
  scrollbar-width: none;
}
.aihub__strip::-webkit-scrollbar { display: none; }
.aihub.aihub .aihub__capsule {
  flex: none;
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  min-width: 176px;
  max-width: 272px;
  height: 52px;
  min-height: 52px;
  padding: 0 var(--sp-4) 0 var(--sp-2);
  border: 0;
  border-radius: var(--r-full);
  color: var(--label-1);
  background: var(--bg-elev1);
  text-align: left;
  scroll-snap-align: start;
  /* A held finger is our menu, not the system's "Copy · Look Up". */
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
  transition: transform var(--dur-press-out) var(--ease-nav), background-color var(--dur-press-out) linear;
}
.aihub.aihub .aihub__capsule:active { transform: scale(0.97); background: var(--bg-elev2); transition-duration: var(--dur-press-in); }
.aihub__capsule-tile { flex: none; display: grid; place-items: center; width: 36px; height: 36px; border-radius: var(--r-full); color: #fff; background: #1f9e82; }
.aihub__capsule-tile[data-tint="orange"] { background: #d9730d; }
.aihub__capsule-tile[data-tint="purple"] { background: #7a52d6; }
.aihub__capsule-tile .homie-brand-mark { animation: aihub-bob 2s ease-in-out infinite; animation-delay: inherit; }
.aihub__capsule-tile .homie-brand-mark,
.aihub__capsule-tile svg { display: block; width: 24px; height: 24px; }
.aihub__capsule-words { display: flex; min-width: 0; flex-direction: column; }
.aihub__capsule-title { overflow: hidden; font: 600 var(--fs-subhead)/var(--lh-subhead) var(--font-text); letter-spacing: -0.2px; text-overflow: ellipsis; white-space: nowrap; }
.aihub__capsule-line { overflow: hidden; color: var(--label-2); font: 400 var(--fs-footnote)/var(--lh-footnote) var(--font-text); font-variant-numeric: tabular-nums; text-overflow: ellipsis; white-space: nowrap; }

/* Nothing to read yet: the mascot, a question, and this house's starter asks as chips. */
.aihub__welcome { display: flex; flex-direction: column; align-items: center; gap: var(--sp-2); padding-top: var(--sp-5); text-align: center; }
.aihub__welcome-mark { color: var(--accent); }
.aihub__welcome-mark .homie-brand-mark,
.aihub__welcome-mark svg { display: block; width: 56px; height: 56px; }
.aihub__welcome-title { margin: var(--sp-2) 0 0; color: var(--label-1); font: 700 var(--fs-title3)/var(--lh-title3) var(--font-display); letter-spacing: -0.3px; }
.aihub__welcome-line { max-width: 300px; margin: 0; color: var(--label-2); font: 400 var(--fs-subhead)/var(--lh-subhead) var(--font-text); }
.aihub__suggestions { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--sp-2); margin-top: var(--sp-4); }
.aihub.aihub .aihub__suggestion {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  max-width: 100%;
  min-height: var(--hit);
  padding: 0 var(--sp-4) 0 6px;
  border: 0;
  border-radius: var(--r-full);
  color: var(--label-1);
  background: var(--bg-elev1);
  font: 500 var(--fs-subhead)/var(--lh-subhead) var(--font-text);
  text-align: left;
  transition: transform var(--dur-press-out) var(--ease-nav), background-color var(--dur-press-out) linear;
}
.aihub.aihub .aihub__suggestion:active { transform: scale(0.97); background: var(--bg-elev2); transition-duration: var(--dur-press-in); }
.aihub__suggestion-tile { flex: none; display: grid; place-items: center; width: 32px; height: 32px; border-radius: var(--r-full); color: #fff; background: #1f9e82; }
.aihub__suggestion-tile[data-tint="orange"] { background: #d9730d; }
.aihub__suggestion-tile .homie-brand-mark,
.aihub__suggestion-tile svg { display: block; width: 20px; height: 20px; }
.aihub__suggestion-words { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Made by Homie: two across, a 16:9 still, the name and one word, then Try (and Keep). */
.aihub__gallery { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-3); }
.aihub__studio { display: flex; min-width: 0; flex-direction: column; gap: var(--sp-2); }
.aihub__still {
  position: relative;
  display: grid;
  place-items: center;
  aspect-ratio: 16 / 9;
  border-radius: var(--r-sm);
  color: var(--label-1);
  background: var(--bg-elev2);
  overflow: hidden;
}
.aihub__still::before { content: ""; position: absolute; inset: 0; opacity: .38; background: radial-gradient(120% 90% at 20% 0%, var(--still-tint, var(--accent)), transparent 70%); }
.aihub__still[data-tint="teal"] { --still-tint: #1f9e82; }
.aihub__still[data-tint="blue"] { --still-tint: #2f6fe0; }
.aihub__still[data-tint="orange"] { --still-tint: #d9730d; }
.aihub__still[data-tint="purple"] { --still-tint: #7a52d6; }
.aihub__still[data-tint="green"] { --still-tint: #2e9e52; }
.aihub__still-mark { position: relative; opacity: .9; }
.aihub__still-mark .homie-brand-mark,
.aihub__still-mark svg { display: block; width: 44px; height: 44px; }
/* The creation's own picture from the art door covers the stand-in once it has loaded. */
.aihub__still-art { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.aihub__still[data-art="yes"]::before,
.aihub__still[data-art="yes"] .aihub__still-mark { display: none; }
.aihub__still-more { position: absolute; z-index: 1; top: var(--sp-1); right: var(--sp-1); background: rgba(11, 13, 16, 0.55); }
.aihub__studio-copy { display: flex; min-width: 0; flex-direction: column; }
.aihub__studio-copy strong { overflow: hidden; font: 600 var(--fs-subhead)/var(--lh-subhead) var(--font-text); letter-spacing: -0.2px; text-overflow: ellipsis; white-space: nowrap; }
.aihub__studio-meta { color: var(--label-2); font: 400 var(--fs-footnote)/var(--lh-footnote) var(--font-text); }
.aihub__studio-actions { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: var(--sp-2); }
.aihub__studio-actions .ui-btn { min-width: 0; padding: 0 var(--sp-2); }

/* Loading, could not load, a project with nothing in it: the mascot, one headline, one line. */
.aihub__empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-8) var(--sp-4);
  color: var(--label-2);
  text-align: center;
  font: 400 var(--fs-subhead)/var(--lh-subhead) var(--font-text);
}
.aihub__empty strong { color: var(--label-1); font: 600 var(--fs-body)/var(--lh-body) var(--font-text); }
.aihub__empty p { margin: 0; }
.aihub__empty-mark { color: var(--accent); }
.aihub__empty-mark .homie-brand-mark,
.aihub__empty-mark svg { display: block; width: 56px; height: 56px; }
.aihub__empty--loading .aihub__empty-mark { animation: aihub-bob 2s ease-in-out infinite; }
.aihub__empty .aihub__retry { margin-top: var(--sp-2); }
.aihub__why { margin: 0; color: var(--label-2); font: 400 var(--fs-footnote)/var(--lh-footnote) var(--font-text); overflow-wrap: anywhere; }

/* -- the thread ----------------------------------------------------------- */
.aihub__thread { display: flex; flex-direction: column; gap: var(--sp-5); min-width: 0; }
.aihub__thread-when {
  display: flex;
  justify-content: center;
  margin: 0;
  color: var(--label-2);
  font: 400 var(--fs-footnote)/var(--lh-footnote) var(--font-text);
  font-variant-numeric: tabular-nums;
}
.aihub__exchange { display: flex; flex-direction: column; gap: var(--sp-4); min-width: 0; }
.aihub__message { display: flex; min-width: 0; flex-direction: column; gap: var(--sp-1); }
/* The person: a right-aligned bubble, elevated, no tail, no border; a name over it only when it is somebody else's. */
.aihub__message--user { align-self: flex-end; align-items: flex-end; max-width: 85%; }
.aihub__message-meta {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 0 var(--sp-3);
  color: var(--label-2);
  font: 400 var(--fs-footnote)/var(--lh-footnote) var(--font-text);
}
.aihub__by { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; }
.aihub__bubble {
  min-width: 0;
  padding: 10px var(--sp-4);
  border-radius: var(--r-md);
  background: var(--bg-elev2);
}
.aihub__request { margin: 0; white-space: pre-wrap; overflow-wrap: anywhere; }
/* Homie: full-width prose, Body style, no bubble; Copy under it on the leading edge. */
.aihub__message--ai { align-self: stretch; }
.aihub__answer { color: var(--label-1); font: 400 var(--fs-body)/var(--lh-body) var(--font-text); letter-spacing: -0.4px; }
.aihub__message-foot { display: flex; align-items: center; margin: 0 0 calc(-1 * var(--sp-2)); }
.aihub.aihub .aihub__copy.ui-btn--plain { min-height: var(--hit); margin-left: calc(-1 * var(--sp-3)); padding: 0 var(--sp-3); font-size: var(--fs-subhead); }
.aihub__copy[data-state="copied"] { color: var(--label-2); }

/* What a finished turn made: the still, its name and state, and the key that puts it on. */
.aihub__output { display: flex; min-width: 0; flex-direction: column; margin-top: var(--sp-3); overflow: hidden; border-radius: var(--r-md); background: var(--bg-elev1); }
.aihub__output-still { border-radius: 0; }
.aihub__output-body { display: flex; align-items: center; gap: var(--sp-3); padding: var(--sp-3) var(--sp-3) var(--sp-3) var(--sp-4); }
.aihub__output-words { flex: 1 1 auto; display: flex; min-width: 0; flex-direction: column; }
.aihub__output-words strong { overflow: hidden; font: 600 var(--fs-body)/var(--lh-body) var(--font-text); letter-spacing: -0.4px; text-overflow: ellipsis; white-space: nowrap; }
.aihub__output-words span { color: var(--label-2); font: 400 var(--fs-footnote)/var(--lh-footnote) var(--font-text); }
.aihub__output-try { flex: none; }

/* A turn that fell over: a card with a way forward, never a red paragraph. */
.aihub__failure { display: flex; min-width: 0; flex-direction: column; gap: var(--sp-3); padding: var(--sp-4); border-radius: var(--r-md); background: var(--bg-elev1); }
.aihub__failure-head { display: flex; align-items: flex-start; gap: var(--sp-3); min-width: 0; }
.aihub__failure-words { display: flex; min-width: 0; flex-direction: column; gap: 2px; }
.aihub__failure-words strong { font: 600 var(--fs-body)/var(--lh-body) var(--font-text); letter-spacing: -0.4px; }
.aihub__failure-keys { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); padding-left: 41px; }

/* "Worked for 27 min · 6 steps ›" — the process, as one row between the question and the answer. */
.aihub__worklog { min-width: 0; border-radius: var(--r-md); background: var(--bg-elev1); }
.aihub__worklog > summary {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: var(--hit);
  padding: 0 var(--sp-4);
  color: var(--label-2);
  font: 400 var(--fs-subhead)/var(--lh-subhead) var(--font-text);
  font-variant-numeric: tabular-nums;
  list-style: none;
  cursor: pointer;
}
.aihub__worklog > summary::-webkit-details-marker { display: none; }
.aihub__worklog-words { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.aihub__worklog-chevron { display: grid; color: var(--label-3); transition: transform var(--dur-fade) var(--ease-nav); }
.aihub__worklog-chevron .ui-ic { width: 16px; height: 16px; }
.aihub__worklog[open] > summary .aihub__worklog-chevron,
.aihub__tech[open] > summary .aihub__worklog-chevron { transform: rotate(90deg); }
/* The steps: a dot on a hairline rail, what happened, how many times, and a quiet trailing clock. */
.aihub__timeline { margin: 0; padding: 0 var(--sp-4) var(--sp-2); list-style: none; }
.aihub__step { position: relative; min-width: 0; }
.aihub__step-line { display: flex; align-items: center; gap: var(--sp-3); min-height: 36px; list-style: none; }
.aihub__step-details > summary { cursor: pointer; }
.aihub__step-details > summary::-webkit-details-marker { display: none; }
.aihub__step-rail { position: relative; flex: none; align-self: stretch; display: grid; place-items: center; width: 8px; }
.aihub__step-rail::before { content: ""; position: absolute; top: 0; bottom: 0; left: 3.5px; width: 1px; background: var(--separator); }
.aihub__step:first-child .aihub__step-rail::before { top: 18px; }
.aihub__step:last-child .aihub__step-rail::before { bottom: calc(100% - 18px); }
.aihub__step-dot { position: relative; width: 8px; height: 8px; border-radius: var(--r-full); background: var(--accent); box-shadow: 0 0 0 3px var(--bg-elev1); }
.aihub__step[data-state="failed"] .aihub__step-dot { background: var(--destructive); }
.aihub__step-title { flex: 0 1 auto; min-width: 0; overflow: hidden; color: var(--label-1); font: 400 var(--fs-subhead)/var(--lh-subhead) var(--font-text); text-overflow: ellipsis; white-space: nowrap; }
.aihub__step[data-state="failed"] .aihub__step-title { color: var(--destructive); }
.aihub__step-times { flex: none; color: var(--label-2); font: 400 var(--fs-footnote)/var(--lh-footnote) var(--font-text); font-variant-numeric: tabular-nums; }
.aihub__step-time { flex: none; margin-left: auto; color: var(--label-3); font: 400 var(--fs-caption)/var(--lh-caption) var(--font-text); font-variant-numeric: tabular-nums; }
.aihub__step[data-kind="say"] { display: flex; gap: var(--sp-3); }
.aihub__step-say { min-width: 0; margin: 0; padding: 2px 0 var(--sp-2); color: var(--label-2); font: 400 var(--fs-footnote)/var(--lh-footnote) var(--font-text); white-space: pre-wrap; overflow-wrap: anywhere; }
.aihub__step-payload {
  max-height: 220px;
  margin: 0 0 var(--sp-2) 20px;
  padding: var(--sp-2) var(--sp-3);
  overflow: auto;
  border-radius: var(--r-sm);
  color: var(--label-1);
  background: var(--bg-base);
  font: 400 var(--fs-caption)/var(--lh-caption) ui-monospace, SFMono-Regular, Menlo, monospace;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* Stills captured while testing: a paged row of 16:9. */
.aihub__checkpoints { display: flex; min-width: 0; flex-direction: column; gap: var(--sp-2); }
.aihub__section-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-3); color: var(--label-2); font: 400 var(--fs-footnote)/var(--lh-footnote) var(--font-text); }
.aihub__section-head strong { color: var(--label-1); font-weight: 600; }
.aihub__artifacts { display: grid; gap: var(--sp-2); min-width: 0; }
.aihub__checkpoints .aihub__artifacts {
  grid-auto-columns: 82%;
  grid-auto-flow: column;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
}
.aihub__artifacts img {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border-radius: var(--r-sm);
  background: var(--bg-elev2);
  scroll-snap-align: start;
}
.aihub__artifacts img[aria-busy="true"] {
  background: linear-gradient(110deg, transparent 30%, rgba(245, 242, 236, 0.06) 45%, transparent 60%) 0 0 / 240% 100%, var(--bg-elev2);
  animation: aihub-artifact-loading 1.5s linear infinite;
}
.aihub__artifacts img.is-failed { display: none; }
@keyframes aihub-artifact-loading { to { background-position: -240% 0, 0 0; } }

/* The live end: what it is doing, how long, and — where this phone may — its Stop. */
.aihub__working {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  min-width: 0;
  padding: var(--sp-3) var(--sp-3) var(--sp-3) var(--sp-4);
  border-radius: var(--r-md);
  background: var(--bg-elev1);
}
.aihub__working .ui-row__tile { align-self: flex-start; }
.aihub__working-words { flex: 1 1 auto; display: flex; min-width: 0; flex-direction: column; }
.aihub__working-words strong { overflow: hidden; font: 600 var(--fs-subhead)/var(--lh-subhead) var(--font-text); text-overflow: ellipsis; white-space: nowrap; }
.aihub__working-line { color: var(--label-2); font: 400 var(--fs-footnote)/var(--lh-footnote) var(--font-text); font-variant-numeric: tabular-nums; }
.aihub__stop.ui-btn--destructive { flex: none; }

.aihub__note { margin: 0; color: var(--label-2); font: 400 var(--fs-footnote)/var(--lh-footnote) var(--font-text); text-align: center; }
.aihub__note[data-tone="bad"] { color: var(--destructive); }

/* Owner only: which model, the mode, the tools, the session — a grouped row that opens in place. */
.aihub__tech { min-width: 0; overflow: hidden; border-radius: var(--r-md); background: var(--bg-elev1); }
.aihub__tech > summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  min-height: var(--hit);
  padding: 0 var(--sp-4);
  color: var(--label-1);
  font: 400 var(--fs-body)/var(--lh-body) var(--font-text);
  letter-spacing: -0.4px;
  list-style: none;
  cursor: pointer;
}
.aihub__tech > summary::-webkit-details-marker { display: none; }
.aihub__tech dl {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--sp-1) var(--sp-4);
  margin: 0;
  padding: var(--sp-3) var(--sp-4);
  box-shadow: inset 0 0.5px 0 var(--separator);
  font: 400 var(--fs-footnote)/var(--lh-footnote) var(--font-text);
}
.aihub__tech dt { color: var(--label-2); }
.aihub__tech dd { margin: 0; color: var(--label-1); overflow-wrap: anywhere; text-align: right; }

/* -- the composer --------------------------------------------------------- */
.aihub__composer {
  position: relative;
  flex: none;
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  padding: var(--sp-2) var(--page-margin) 0;
  background: var(--bg-base);
}
/* The 24 pt scroll-edge fade over the list's last line (D.10). */
.aihub__composer::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 100%;
  height: var(--sp-6);
  pointer-events: none;
  background: linear-gradient(180deg, transparent, var(--bg-base));
}
.aihub__composer[hidden] { display: none; }
.aihub__composer-row { display: flex; align-items: flex-end; gap: var(--sp-2); min-width: 0; }
.aihub__composer-row > .attach__key { margin-bottom: var(--sp-2); }
.aihub__field {
  flex: 1 1 auto;
  display: flex;
  align-items: flex-end;
  min-width: 0;
  min-height: 52px;
  padding: 4px;
  border-radius: var(--r-lg);
  background: var(--bg-elev1);
  box-shadow: inset 0 0 0 1px var(--separator);
  transition: background-color var(--dur-fade) linear;
}
/* Focus is a fill AND a ring, so the change reads without the ring (critique-r1 B9). */
.aihub__field:focus-within { background: var(--bg-elev2); box-shadow: inset 0 0 0 1px rgba(79, 209, 176, 0.5); }
/* Past one line: the text on top at full width, the chip and the trailing key on a row under it. */
.aihub__field[data-lines="many"] {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  grid-template-areas: "input input input" "chip . key";
  align-items: center;
}
.aihub__field[data-lines="many"] .aihub__input { grid-area: input; padding-left: var(--sp-3); }
.aihub__field[data-lines="many"] .aihub__mode-chip { grid-area: chip; }
.aihub__field[data-lines="many"] .aihub__mic,
.aihub__field[data-lines="many"] .aihub__send { grid-area: key; }
/* A 44 pt key, a 32 pt pill drawn inside it. */
.aihub.aihub .aihub__mode-chip {
  flex: none;
  display: grid;
  place-items: center;
  height: 44px;
  min-height: 44px;
  padding: 0 2px 0 2px;
  border: 0;
  border-radius: var(--r-full);
  color: var(--accent);
  background: transparent;
}
.aihub.aihub .aihub__mode-chip[hidden] { display: none; }
.aihub__mode-chip-pill {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  height: 32px;
  padding: 0 var(--sp-2) 0 var(--sp-3);
  border-radius: var(--r-full);
  background: var(--accent-fill);
  font: 600 var(--fs-subhead)/var(--lh-subhead) var(--font-text);
  white-space: nowrap;
}
.aihub__mode-chip-pill .ui-ic { width: 16px; height: 16px; }
.aihub__mode-chip:active .aihub__mode-chip-pill { background: rgba(79, 209, 176, 0.24); }
.aihub__input {
  flex: 1 1 auto;
  box-sizing: border-box;
  min-width: 0;
  height: 44px;
  min-height: 44px;
  max-height: 132px;
  margin: 0;
  padding: 11px var(--sp-1) 11px var(--sp-2);
  border: 0;
  border-radius: 0;
  resize: none;
  outline: none;
  color: var(--label-1);
  background: transparent;
  font: 400 var(--fs-body)/var(--lh-body) var(--font-text);
  letter-spacing: -0.4px;
}
.aihub__input::placeholder { color: var(--label-3); opacity: 1; }
/* The capsule's own ring (:focus-within) is the focus indicator; a second one inside it boxed the text. */
.aihub__input:focus-visible { outline: none; }
.aihub__input[data-drop="over"] { background: var(--accent-fill); border-radius: var(--r-sm); }
/* The trailing key: a 44 pt target, a 36 pt circle drawn by ::before. */
.aihub.aihub .aihub__mic,
.aihub.aihub .aihub__send {
  position: relative;
  flex: none;
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  min-height: 44px;
  padding: 0;
  border: 0;
  border-radius: var(--r-full);
  color: var(--label-1);
  background: transparent;
}
.aihub__mic::before,
.aihub__send::before { content: ""; position: absolute; inset: 4px; border-radius: inherit; }
.aihub__mic .ui-ic, .aihub__mic .mk, .aihub__send .ui-ic { position: relative; }
.aihub.aihub .aihub__mic[hidden],
.aihub.aihub .aihub__send[hidden] { display: none; }
.aihub.aihub .aihub__send { color: var(--on-primary); }
.aihub__send::before { background: var(--accent); }
.aihub.aihub .aihub__send:disabled { color: var(--label-3); }
.aihub__send:disabled::before { background: var(--bg-elev3); }
.aihub__send .ui-ic, .aihub__mic .ui-ic { width: 20px; height: 20px; }
.aihub__send .ui-ic path { stroke-width: 2.4; }
.aihub__mic .mk { width: 22px; height: 22px; }
.aihub.aihub .aihub__mic[data-state="recording"] { color: #fff; }
.aihub__mic[data-state="recording"]::before { background: var(--destructive); }
.aihub__mic[data-state="transcribing"] { animation: aihub-breathe 1.2s ease-in-out infinite alternate; }
@keyframes aihub-breathe { to { opacity: .45; } }
.aihub__left { color: var(--label-2); font: 400 var(--fs-footnote)/var(--lh-footnote) var(--font-text); text-align: right; }
.aihub__left[hidden] { display: none; }

/* A problem with a send, a stop or a load: one line over the composer, never a red slab. */
.aihub__problem {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  margin: 0 var(--page-margin) var(--sp-2);
  padding: var(--sp-2) var(--sp-2) var(--sp-2) var(--sp-4);
  border-radius: var(--r-md);
  color: var(--label-1);
  background: var(--bg-elev2);
  font: 400 var(--fs-subhead)/var(--lh-subhead) var(--font-text);
}
.aihub__problem[hidden] { display: none; }
.aihub__problem > span { min-width: 0; }
.aihub__problem .aihub__why { display: block; }
.aihub.aihub .aihub__problem button {
  flex: none;
  min-height: 36px;
  padding: 0 var(--sp-4);
  border: 0;
  border-radius: var(--r-full);
  color: var(--label-1);
  background: var(--bg-elev3);
  font: 600 var(--fs-subhead)/var(--lh-subhead) var(--font-text);
}

/* Back to the live end, IN the composer's column while you read history: it
   takes a row above the field and the scroller gives up that height, so the
   pill can never sit on a log row (critique-r1 B10, thread-03). */
.aihub.aihub .aihub__latest {
  position: relative;
  align-self: center;
  min-height: 36px;
  max-width: 100%;
  padding: 0 var(--sp-4);
  border: 0;
  border-radius: var(--r-full);
  color: var(--label-1);
  background: var(--bg-elev2);
  font: 600 var(--fs-subhead)/var(--lh-subhead) var(--font-text);
  white-space: nowrap;
}
.aihub__latest::after { content: ""; position: absolute; inset: -4px; border-radius: inherit; }
.aihub__latest[hidden] { display: none; }

/* The attach menu and tray (phone-attach.js), in the composer's material. */
.attach__sheet {
  position: absolute;
  left: var(--page-margin);
  right: var(--page-margin);
  bottom: calc(100% + var(--sp-1));
  z-index: 6;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border-radius: var(--r-md);
  background: var(--bg-elev2);
  box-shadow: 0 16px 40px rgba(0, 0, 0, 0.5);
}
.attach__sheet[hidden] { display: none; }
.aihub.aihub .attach__choice {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  min-height: 50px;
  padding: 0 var(--sp-4);
  border: 0;
  border-radius: 0;
  color: var(--label-1);
  background: transparent;
  font: 400 var(--fs-body)/var(--lh-body) var(--font-text);
  text-align: left;
}
.attach__choice + .attach__choice { box-shadow: inset 0 0.5px 0 var(--separator); }
.attach__choice:active { background: var(--bg-elev3); }
.attach__choice-mark { display: grid; place-items: center; width: 29px; height: 29px; flex: none; border-radius: var(--r-xs); color: #fff; background: #1f9e82; }
.attach__choice-mark .mk { width: 18px; height: 18px; }
.attach__input { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
/* Sideways, never a second row: a composer that grows with each photograph moves the field under a thumb mid-sentence. */
.attach { display: flex; gap: var(--sp-2); min-width: 0; padding-bottom: 2px; overflow-x: auto; overflow-y: hidden; overscroll-behavior-x: contain; scrollbar-width: none; }
.attach[hidden] { display: none; }
.attach::-webkit-scrollbar { display: none; }
.attach__item {
  position: relative;
  flex: none;
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  max-width: 210px;
  min-height: 52px;
  padding: var(--sp-1) 32px var(--sp-1) var(--sp-1);
  border-radius: var(--r-sm);
  background: var(--bg-elev2);
  overflow: hidden;
}
.attach__item[data-state="sending"] { opacity: .82; }
.attach__item[data-state="failed"] { box-shadow: inset 0 0 0 1px var(--destructive); }
.attach__item[data-kind="image"] { max-width: none; padding: 0; background: none; }
.attach__item[data-kind="image"] .attach__meta { display: none; }
.attach__thumb { flex: none; width: 52px; height: 52px; border-radius: var(--r-sm); object-fit: cover; background: var(--bg-elev2); }
.attach__glyph,
.aihub__file-glyph { display: grid; place-items: center; flex: none; width: 40px; height: 40px; border-radius: var(--r-xs); color: var(--label-2); background: var(--bg-elev3); }
.attach__glyph .mk,
.aihub__file-glyph .mk { width: 22px; height: 22px; }
.attach__meta { display: flex; min-width: 0; flex-direction: column; }
.attach__name { min-width: 0; max-width: 128px; overflow: hidden; font: 600 var(--fs-footnote)/var(--lh-footnote) var(--font-text); text-overflow: ellipsis; white-space: nowrap; }
.attach__size { color: var(--label-2); font: 400 var(--fs-caption)/var(--lh-caption) var(--font-text); }
/* The bar is the bottom edge of the chip itself. */
.attach__bar { position: absolute; left: 0; bottom: 0; width: 100%; height: 3px; background: var(--accent); transform: scaleX(var(--attach-progress, .04)); transform-origin: left center; transition: transform .18s ease-out; }
.aihub.aihub .attach__remove {
  position: absolute;
  top: var(--sp-1);
  right: var(--sp-1);
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  min-height: 24px;
  padding: 0;
  border: 0;
  border-radius: var(--r-full);
  color: var(--label-1);
  background: rgba(11, 13, 16, 0.7);
}
.attach__remove .mk { width: 14px; height: 14px; }
/* 24 pt to see, 48 pt to hit: a thumb that misses lands on nothing, not on "delete this photograph". */
.attach__remove::after { content: ""; position: absolute; inset: -12px; }
.aihub.aihub .attach__retry { min-height: 32px; padding: 0 var(--sp-3); border: 0; border-radius: var(--r-full); color: var(--on-primary); background: var(--accent); font: 600 var(--fs-footnote)/var(--lh-footnote) var(--font-text); }
.attach__problem { color: var(--destructive); font: 400 var(--fs-footnote)/var(--lh-footnote) var(--font-text); }
.attach__problem[hidden] { display: none; }

/* What was attached, under the message it was attached to. */
.aihub__files { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: var(--sp-2); min-width: 0; margin-top: var(--sp-2); }
.aihub__file { display: flex; align-items: center; gap: var(--sp-2); min-width: 0; max-width: 100%; min-height: var(--hit); padding: var(--sp-1); border: 0; border-radius: var(--r-sm); color: inherit; background: var(--bg-elev3); font: inherit; text-align: left; }
.aihub__file--visual { padding: 0; background: none; }
.aihub__file--visual img { width: 104px; height: 104px; border-radius: var(--r-sm); object-fit: cover; background: var(--bg-elev2); }
.aihub__file--visual img[aria-busy="true"] { animation: aihub-breathe 1.2s ease-in-out infinite alternate; }
.aihub__file--visual img.is-failed { display: none; }
.aihub__file[data-kind="video"] { flex-direction: column; align-items: flex-start; }
.aihub__file-name,
.aihub__file-copy strong { min-width: 0; max-width: 190px; overflow: hidden; font: 600 var(--fs-footnote)/var(--lh-footnote) var(--font-text); text-overflow: ellipsis; white-space: nowrap; }
.aihub__file-copy { display: flex; min-width: 0; flex-direction: column; }
.aihub__file-copy span { color: var(--label-2); font: 400 var(--fs-caption)/var(--lh-caption) var(--font-text); }

/* One picture, the size of the phone. */
.aihub__viewer { position: absolute; inset: 0; z-index: 20; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--sp-3); padding: var(--sp-5); background: var(--bg-base); }
.aihub__viewer[hidden] { display: none; }
.aihub__viewer-body { display: flex; align-items: center; justify-content: center; min-height: 0; max-width: 100%; }
.aihub__viewer-body img,
.aihub__viewer-video { max-width: 100%; max-height: 70vh; border-radius: var(--r-sm); object-fit: contain; }
.aihub__viewer-name { max-width: 100%; color: var(--label-2); font: 400 var(--fs-footnote)/var(--lh-footnote) var(--font-text); overflow-wrap: anywhere; }
.aihub.aihub .aihub__viewer-close { position: absolute; top: var(--sp-3); right: var(--sp-3); display: grid; place-items: center; width: 44px; height: 44px; padding: 0; border: 0; border-radius: var(--r-full); color: var(--label-1); background: var(--bg-elev2); }
/* Off-screen, never `display: none` — a clipboard fallback needs a node the browser will let it select. */
.aihub__clip { position: absolute; left: -9999px; top: 0; width: 1px; height: 1px; opacity: 0; }

/* -- the action sheet (Stop, the mode menu, a creation's details) --------- */
.aihub__sheet-layer { position: fixed; inset: 0; z-index: 40; display: flex; flex-direction: column; justify-content: flex-end; }
.aihub__sheet-scrim { position: absolute; inset: 0; background: var(--scrim); opacity: 0; transition: opacity var(--dur-fade) linear; }
.aihub__sheet {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  max-height: 88vh;
  padding: 0 var(--sp-2) calc(var(--sp-2) + env(safe-area-inset-bottom, 0px));
  transform: translateY(100%);
  transition: transform var(--dur-sheet) var(--ease-nav);
  color: var(--label-1);
  font: 400 var(--fs-body)/var(--lh-body) var(--font-text);
}
.aihub__sheet-layer[data-open="true"] .aihub__sheet-scrim { opacity: 1; }
.aihub__sheet-layer[data-open="true"] .aihub__sheet { transform: translateY(0); }
.aihub__sheet-group { display: flex; flex-direction: column; min-height: 0; overflow-y: auto; border-radius: var(--r-md); background: var(--bg-elev2); }
.aihub__sheet-head { display: flex; flex-direction: column; gap: 2px; padding: var(--sp-4) var(--sp-4) var(--sp-3); text-align: center; }
.aihub__sheet-head strong { font: 600 var(--fs-footnote)/var(--lh-footnote) var(--font-text); color: var(--label-2); }
.aihub__sheet-head p { margin: 0; color: var(--label-2); font: 400 var(--fs-footnote)/var(--lh-footnote) var(--font-text); overflow-wrap: anywhere; }
.aihub__sheet-action,
.aihub__sheet-cancel {
  min-height: 56px;
  margin: 0;
  padding: 0 var(--sp-4);
  border: 0;
  color: var(--accent);
  background: transparent;
  font: 400 var(--fs-body)/var(--lh-body) var(--font-text);
  box-shadow: inset 0 0.5px 0 var(--separator);
}
.aihub__sheet-action[data-tone="destructive"] { color: var(--destructive); }
.aihub__sheet-action:active,
.aihub__sheet-cancel:active { background: var(--bg-elev3); }
.aihub__sheet-cancel { border-radius: var(--r-md); background: var(--bg-elev2); box-shadow: none; font-weight: 600; }
/* Inside the mode sheet: three rows, each its word and its own line; then who answers. */
.aihub__session-options { display: flex; flex-direction: column; box-shadow: inset 0 0.5px 0 var(--separator); }
.aihub__modes { display: flex; flex-direction: column; }
.aihub__modes button {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  min-height: 64px;
  padding: 10px 48px 10px var(--sp-4);
  border: 0;
  color: var(--label-1);
  background: transparent;
  text-align: left;
}
.aihub__modes button + button { box-shadow: inset 0 0.5px 0 var(--separator); }
.aihub__modes button:active { background: var(--bg-elev3); }
.aihub__modes button strong { font: 600 var(--fs-body)/var(--lh-body) var(--font-text); }
.aihub__modes button span { color: var(--label-2); font: 400 var(--fs-subhead)/var(--lh-subhead) var(--font-text); }
.aihub__modes button[aria-pressed="true"]::after {
  content: "";
  position: absolute;
  top: 50%;
  right: var(--sp-4);
  width: 7px;
  height: 13px;
  margin-top: -9px;
  border: solid var(--accent);
  border-width: 0 2.5px 2.5px 0;
  transform: rotate(45deg);
}
.aihub__mode-hint { margin: 0; padding: 0 var(--sp-4) var(--sp-2); color: var(--label-2); font: 400 var(--fs-footnote)/var(--lh-footnote) var(--font-text); }
.aihub__mode-hint[hidden] { display: none; }
.aihub__provider-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  min-height: 52px;
  padding: 0 var(--sp-4);
  box-shadow: inset 0 0.5px 0 var(--separator);
}
.aihub__provider-row[hidden] { display: none; }
.aihub__provider {
  min-width: 0;
  max-width: 60%;
  min-height: var(--hit);
  border: 0;
  color: var(--label-2);
  background: transparent;
  font: 400 var(--fs-body)/var(--lh-body) var(--font-text);
  text-align: right;
}
.aihub__budget { padding: 0 var(--sp-4) var(--sp-3); color: var(--label-2); font: 400 var(--fs-footnote)/var(--lh-footnote) var(--font-text); overflow-wrap: anywhere; }
.aihub__budget[hidden] { display: none; }

/* The Play-with-an-AI desk at the top of the list, in the list's material. */
.aihub__main > .players--desk { margin: 0; border: 0; border-radius: var(--r-md); background: var(--bg-elev1); }

/*
 * LANDSCAPE, OVER A GAME: 390 pt of height with a large title, a mini-player
 * and a tab bar left about 110 pt for content (critique-r1-flows #10). The
 * Homie tab gives back what it owns: a 44 pt capsule, a tighter rhythm, no
 * welcome mascot, four stills across, and a thread held to a readable measure
 * rather than an 800 pt line.
 */
@media (orientation: landscape) and (max-height: 500px) {
  .aihub__main { gap: var(--sp-5); padding-top: 0; padding-bottom: var(--sp-3); }
  .aihub__composer { padding-top: var(--sp-1); }
  .aihub__composer::before { height: var(--sp-3); }
  .aihub__field { min-height: 44px; padding: 0 0 0 2px; }
  .aihub__composer-row > .attach__key { margin-bottom: 4px; }
  .aihub__welcome { padding-top: 0; }
  .aihub__welcome-mark { display: none; }
  .aihub__gallery { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .aihub__thread { gap: var(--sp-4); width: 100%; max-width: 640px; margin: 0 auto; }
  /* A 640 pt 16:9 still is taller than the glass: the output card lies down, still beside its words. */
  .aihub__output { flex-direction: row; align-items: stretch; }
  .aihub__output-still { flex: none; width: 224px; }
}
@media (max-height: 650px) {
  .shell[data-view="activity"] .shell__ways { padding-top: 6px; padding-bottom: calc(6px + var(--safe-b)); }
}
@media (prefers-reduced-motion: reduce) {
  .aihub__progress { animation: none; background: var(--accent-fill); }
  .aihub__tile[data-live="yes"] .homie-brand-mark,
  .aihub__empty--loading .aihub__empty-mark,
  .aihub__mic[data-state="transcribing"] { animation: none; }
  .aihub__sheet { transition: opacity 150ms linear; transform: none; opacity: 0; }
  .aihub__sheet-layer[data-open="true"] .aihub__sheet { opacity: 1; }
}

/* ========================================================================== */
/* What an agent wrote                                                        */
/* ========================================================================== */

/*
 * A reply is a DOCUMENT, and this is the whole difference between reading one
 * and giving up on it. Nothing here is decoration: every rule below exists so
 * that a list looks like a list and a command looks like a command at arm's
 * length on a phone. See `phone-markdown.js` for why none of it is markup.
 */
.md { display: flex; flex-direction: column; gap: 9px; min-width: 0; max-width: 100%; }
.md__p {
  min-width: 0;
  max-width: 100%;
  margin: 0;
  line-height: 1.46;
  overflow-wrap: anywhere;
  white-space: pre-wrap;
}
.md__h { margin: 4px 0 0; color: var(--cream); font-weight: 800; letter-spacing: -.015em; overflow-wrap: anywhere; }
.md__h--1 { font-size: var(--dek); }
.md__h--2 { font-size: var(--body); }
.md__h--3,
.md__h--4 { font-size: var(--fine); color: var(--ink-2); }
.md__list { margin: 0; padding-left: 20px; display: flex; flex-direction: column; gap: 5px; }
.md__item { min-width: 0; line-height: 1.42; overflow-wrap: anywhere; }
.md__item::marker { color: var(--go); }
.md__strong { color: var(--cream); font-weight: 780; }
.md__em { font-style: italic; }
.md__strike { color: var(--ink-3); }
.md__link { color: var(--go); text-underline-offset: 3px; overflow-wrap: anywhere; }
.md__quote {
  margin: 0;
  padding: 2px 0 2px 12px;
  border-left: 2px solid color-mix(in srgb, var(--lamp) 40%, transparent);
  color: var(--ink-2);
}
.md__hr { width: 100%; margin: 2px 0; border: 0; border-top: 1px solid color-mix(in srgb, var(--cream) 16%, transparent); }
.md__code {
  /* A long command wraps, and both halves must still look like one chip. Without
     `clone` the second line inherits no left padding and no rounded corner, so
     `homie install-preview` breaking across two lines renders as a chip and a
     fragment — which reads as a rendering bug rather than as a wrapped word. */
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
  padding: 1px 5px;
  border-radius: 6px;
  background: color-mix(in srgb, var(--bg-2) 80%, transparent);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .88em;
  overflow-wrap: anywhere;
}
.md__pre {
  min-width: 0;
  max-width: 100%;
  border: 1px solid color-mix(in srgb, var(--cream) 14%, transparent);
  border-radius: 13px;
  background: color-mix(in srgb, var(--bg-2) 92%, transparent);
  overflow: hidden;
}
.md__pre-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  min-height: 34px;
  padding: 0 6px 0 12px;
  border-bottom: 1px solid color-mix(in srgb, var(--cream) 10%, transparent);
}
.md__lang { color: var(--ink-3); font-size: var(--kicker); font-weight: 700; letter-spacing: .04em; text-transform: uppercase; }
.md__copy {
  min-height: 30px;
  border: 1px solid color-mix(in srgb, var(--cream) 18%, transparent);
  border-radius: 9px;
  padding: 0 10px;
  color: var(--ink-2);
  background: color-mix(in srgb, var(--bg-2) 72%, transparent);
  font: inherit;
  font-size: var(--kicker);
  font-weight: 720;
}
.md__copy[data-state="copied"] {
  color: var(--bg-2);
  background: var(--go);
  border-color: transparent;
}
/* A code block scrolls inside itself. A transcript that grows a horizontal
   scrollbar because one reply contained a long command is the same defect
   `long-tool-output-cannot-widen-the-phone` already watches for. */
.md__pre pre {
  margin: 0;
  padding: 10px 12px;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: var(--kicker);
  line-height: 1.5;
}
.md__table-wrap { min-width: 0; max-width: 100%; overflow-x: auto; overscroll-behavior-x: contain; }
.md__table { border-collapse: collapse; font-size: var(--kicker); }
.md__table th,
.md__table td {
  padding: 6px 10px;
  border: 1px solid color-mix(in srgb, var(--cream) 14%, transparent);
  text-align: left;
  white-space: nowrap;
}
.md__table th { color: var(--cream); font-weight: 760; background: color-mix(in srgb, var(--bg-2) 50%, transparent); }

/* The remote needs the same short-phone honesty as Activity because its real
   controls use the height. Profile deliberately remains a compact curtain;
   stretching two settings to the full phone left a large, inert void between
   the people list and Close. Keep the shared tabs compact in both views. */
@media (max-height: 650px) {
  
  
  .shell:not([data-view="activity"]) .shell__body {
    gap: 10px;
    padding-bottom: 8px;
  }
  
  
  
  .shell[data-view="remote"] .shell__ways {
    padding-top: 6px;
    padding-bottom: calc(6px + var(--safe-b));
  }
  .shell[data-view="remote"] .shell__ways > .btn { min-height: 44px; }
}

/*
 * Moved here from `phone-say.js`, which injected it as a `<style>` node.
 * `style.textContent = …` is a rendered position and `copy-sweep-probe` reads
 * the tree for exactly those; it went red on the CSS itself. Nothing in this
 * block is content: sizes, a mark that pulses while the box works, and three
 * resolved colours that are the same three the shell anchor uses, so two
 * indicators in one hand cannot tell a room different stories.
 */


.wayin { display: grid; grid-template-columns: 1fr auto; gap: 10px; align-items: end;
  padding: 14px 0 4px; }
/* ==========================================================================
   WHAT THE ROOM WAS ASKED — the box's own voice, and NOT a second headline
   ==========================================================================

   MEASURED, `.director/eye/pair-phone-r2-before.png`: the lobby carried the
   offer's subject at 31 px and this at 22/650 in pure `--ink`, 200 points
   below it, and the two were about the same baseball game — the guide row
   "Toronto at Tampa" and the box's *"Jays at Tampa at 3:40. Scherzer. I'll put
   it on then."* The round-2 judge could not tell which proposition was real:

     *"The frame still stacks three separate baseball propositions under one
     left margin ... the reference frame has one cry and two keys, this has four
     content systems and eight pressables in 844 pt. Give the frame one
     subject."*

   Neither of them may be deleted. The offer is a standing fact about the
   television and the press that starts it; the ask is the box speaking to the
   room and waiting for an answer, and suppressing either would be this surface
   lying about the house again — the round-2 defect this whole region was built
   against.

   What CAN be fixed is that they were drawn as the same KIND of thing. So:
   ART_DIRECTION's rule for the other surface, applied here — exactly one thing
   on a frame is the cry, and it is the subject of the photograph. This is the
   dek: the box's voice, one step down (`--ph-dek`), set against the cream rule
   `.now__idle` already uses for the same purpose one region up, so it reads as
   a thing being SAID into the room rather than as a second title of it. Nothing
   moves, nothing goes; the hierarchy stops being flat.

   The rule is cream and not the accent, deliberately. The accent is the
   plate's, it is spent on the live band, and a second saturated hue on this
   frame is the exact defect the kicker was just repaired for. */
.wayin__ask { grid-column: 1 / -1; display: flex; flex-direction: column; gap: 12px; padding-bottom: 8px; }
.wayin__ask:empty { display: none; }
.wayin__ask-words {
  padding-left: 13px;
  border-left: 3px solid color-mix(in srgb, var(--cream) 92%, transparent);
  font-size: var(--ph-dek);
  font-weight: 600;
  letter-spacing: -.012em;
  line-height: 1.32;
  color: var(--cream);
  text-shadow: 0 1px 12px color-mix(in srgb, var(--night) 55%, transparent);
}
/* An ask with no words is not a quotation, and a bare rule floating over the
   composer would be furniture. The plates below it can be present without it —
   the region's own `:empty` rule covers the two-of-nothing case. */
.wayin__ask-words:empty { display: none; }
.wayin__plates { display: flex; flex-wrap: wrap; gap: 10px; }
.wayin__plate {
  all: unset; box-sizing: border-box; display: inline-flex; align-items: center;
  min-height: var(--tap); padding: 12px 18px; border-radius: 999px; cursor: pointer;
  font: 650 var(--ph-key-2)/1 inherit; color: var(--bg-2);
  background: linear-gradient(157deg, color-mix(in srgb, var(--bg-2) 66%, transparent), color-mix(in srgb, var(--bg-2) 72%, transparent));
  color: var(--cream);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--cream) 16%, transparent);
}
.wayin__plate:active, .wayin__plate[data-focused] {
  color: var(--bg-2);
  background: linear-gradient(157deg, var(--lamp), var(--lamp) 52%, var(--lamp));
}
/* THE FIELD IS A HELD OBJECT, NOT A FORM CONTROL.

   What it was: a 1 px hairline round a flat 72 %-opaque slab. Over a photograph
   that reads as a browser text input pasted onto a still — the single loudest
   "this is a web page" tell on the surface, on the one element a guest is
   supposed to reach for first.

   What it is: a piece of dark glass with a lit top edge and a dark bottom one,
   so it has a direction of light and therefore a thickness. The hairline is an
   inset ring rather than a border, so the corner radius stays clean at 2x and
   the ring can strengthen on focus without the box changing size and reflowing
   the row.

   `backdrop-filter` is the material and not a garnish: it is what makes the
   photograph *behind* the field visibly the same photograph, softened —
   which is what separates glass from paint. Where it is unsupported the
   background colour underneath is already opaque enough to read on, so the
   fallback is a slightly flatter version of the same object rather than an
   unreadable one. */
.wayin__field {
  grid-column: 1;
  min-height: 64px;
  resize: none;
  width: 100%;
  border-radius: 22px;
  border: 0;
  background: linear-gradient(180deg, color-mix(in srgb, var(--bg-2) 62%, transparent), color-mix(in srgb, var(--bg-2) 72%, transparent));
  -webkit-backdrop-filter: blur(22px) saturate(150%);
  backdrop-filter: blur(22px) saturate(150%);
  box-shadow:
    inset 0 1px 0 color-mix(in srgb, var(--cream) 14%, transparent),      /* the lit top edge */
    inset 0 0 0 1px color-mix(in srgb, var(--cream) 16%, transparent),    /* the hairline, as a ring */
    0 10px 28px color-mix(in srgb, var(--night) 34%, transparent);              /* it sits ON the picture */
  color: inherit;
  font: inherit;
  padding: 18px 18px 16px;
  transition: box-shadow 200ms cubic-bezier(.2,.9,.25,1), background 200ms ease;
}
/* THE VERB, AND THE RULE UNDER IT.

   This block used to open: *"the answer to 'what is this box for', in no
   language"*, and argue that a ruled line inside an empty box needs no
   translation. The line is true. The conclusion drawn from it was not, and both
   round-1 critics landed on the same sentence: **the front door has no verb.**
   A ruled line says *this takes writing*. It cannot say what happens when you
   write, who reads it, or that there is anything on the other end — and a
   stranger who has just scanned a code in somebody else's living room needs all
   three before they will type into a dark box in front of a room full of people.

   So the box now says what it is for, out loud, in Homie's own words
   (`phone-words.js` — shell language, not this household's content), and the
   rule stays underneath it doing the job it was always good at: marking the
   line the writing goes on.

   THE CARET STROKE IS GONE. It was a 2 pt upright in the guest's own colour at
   the start of the rule, there to say WHERE a hand begins — a real gap on a
   268 pt line with nothing else on it. The placeholder now stands in exactly
   that position and says it in a way that also says what to write, so the
   stroke had become a second mark for one fact, sitting between the words and
   the rule. `caret-color` still resolves to the seat colour, so the first
   thing a thumb summons is still the colour the television is drawing them in.

   A background-image rather than a pseudo-element because this is a
   `<textarea>`, and `::before` does not render on a replaced form control. */
.wayin__field:placeholder-shown {
  background-image:
    linear-gradient(180deg, color-mix(in srgb, var(--bg-2) 62%, transparent), color-mix(in srgb, var(--bg-2) 72%, transparent)),
    /* THE RULE IS GRADED, AND IT IS BRIGHTER THAN IT WAS.
       It shipped flat at 22 % across the whole width, and the round-1 critic
       measured the consequence: over a busy photograph the entire composer was
       dark-on-dark and *"the arrival frame has zero pixels that invite a
       press"*. Raised to 38 % at the left — where a hand starts writing and
       where a caret will appear — falling to 12 % at the right, because a line
       of even weight is a divider and a line that fades is somewhere to write.
       The fix asked for by the critic was to light the composer's EDGE or its
       RULE rather than re-brighten the disabled send disc, which would put back
       the bright-button-that-looks-broken this material replaced. */
    linear-gradient(90deg,
      color-mix(in srgb, var(--cream) 38%, transparent) 0%,
      color-mix(in srgb, var(--cream) 28%, transparent) 45%,
      color-mix(in srgb, var(--cream) 12%, transparent) 100%);
  background-repeat: no-repeat, no-repeat;
  background-size: 100% 100%, calc(100% - 36px) 1.5px;
  /* THE RULE MOVED UP UNDER THE WORDS.

     It was pinned 21 pt off the bottom of an 80 pt box while the box was empty
     of everything — correct when there was nothing above it, and 20 pt of dead
     glass once the placeholder occupies the first line. Anchored to the TOP now,
     just under that line's descenders, so the two are one lockup: a verb with a
     writing rule under it, which is what a paper form looks like.

     It is only ever drawn while `:placeholder-shown` is true, so it cannot
     collide with a second line of somebody's typing — the moment there is text
     the whole layer withdraws and the text is the thing to see. */
  background-position: 0 0, 18px 46px;
  /* One more stop of light on the top edge while the box is empty, so the field
     has a visible lip against the picture before anybody has touched it. It
     drops back to the resting 14 % the moment there is text, because then the
     text is the thing to see. */
  box-shadow:
    inset 0 1px 0 color-mix(in srgb, var(--cream) 22%, transparent),
    inset 0 0 0 1px color-mix(in srgb, var(--cream) 22%, transparent),
    0 10px 28px color-mix(in srgb, var(--night) 34%, transparent);
}
/* THE INVITATION IS NOT TYPED TEXT, AND MUST NOT LOOK LIKE IT.

   A placeholder at the same weight and ink as a value is a field that already
   has something in it, which is the `.me__name` defect in the other direction:
   there, a value read as a heading; here, a prompt would read as a draft
   somebody left. Lighter weight, and enough ink to survive a bright photograph
   behind the glass — 0.62 measured against a stadium floodlight in
   `.director/eye/r2-before.png`, which is the brightest ground this field is
   ever going to stand on in this house.

   Both spellings: `::placeholder` is the standard and `::-webkit-input-
   placeholder` is what older iOS Safari answers to, and a selector list
   containing one unknown pseudo-element is dropped WHOLE by every engine — so
   they are two rules, never a comma. */
.wayin__field::placeholder {
  color: color-mix(in srgb, var(--cream) 62%, transparent);
  font-weight: 500;
  opacity: 1;                      /* Firefox defaults this to 0.54 */
}
.wayin__field::-webkit-input-placeholder {
  color: color-mix(in srgb, var(--cream) 62%, transparent);
  font-weight: 500;
  opacity: 1;
}
/* FOCUS IN THE GUEST'S OWN COLOUR, NOT THE HOUSE'S.

   It is the same hue the television is drawing this person in, so the first
   time the field lights up under their thumb it has already said which of the
   marks on the far wall is theirs — a thing no sentence on this screen is
   allowed to say. `var(--me, var(--go))` is the fallback pattern this stylesheet
   already uses (`.now__mark`, `.me__eyebrow`): before `welcome` lands there is
   no seat colour and the house teal stands in.

   Each `box-shadow` is declared twice: a plain-colour version first, then the
   `color-mix` one. `color-mix` is Safari 16.4; on anything older the WHOLE
   declaration is dropped as invalid, and a focus state that silently loses its
   ring is a guest who cannot tell whether the keyboard is theirs. The first
   declaration is what they get instead. */
.wayin__field:focus {
  outline: none;
  background: linear-gradient(180deg, color-mix(in srgb, var(--bg-2) 70%, transparent), color-mix(in srgb, var(--bg-2) 80%, transparent));
  /* One and a quarter points of ring and a wide soft bloom, not two points of
     saturated line. Measured by eye at 2x: a 1.5 pt ring at 62 % reads as a
     gold BORDER — the browser focus outline this whole block exists to get away
     from. The seat colour has to say "this is yours and it is live" without
     becoming the loudest edge in the frame. */
  box-shadow:
    inset 0 1px 0 color-mix(in srgb, var(--cream) 18%, transparent),
    inset 0 0 0 1.25px color-mix(in srgb, var(--lamp) 42%, transparent),
    0 0 22px color-mix(in srgb, var(--lamp) 18%, transparent),
    0 12px 32px color-mix(in srgb, var(--night) 40%, transparent);
  box-shadow:
    inset 0 1px 0 color-mix(in srgb, var(--cream) 18%, transparent),
    inset 0 0 0 1.25px color-mix(in srgb, var(--me, var(--go)) 42%, transparent),
    0 0 22px color-mix(in srgb, var(--me, var(--go)) 18%, transparent),
    0 12px 32px color-mix(in srgb, var(--night) 40%, transparent);
}

/* THE SEND MARK: TWO MATERIALS, NEVER ONE MATERIAL AT TWO OPACITIES.

   It was `opacity: 0.35` on a full teal disc, which over a photograph reads as
   a bright button that is broken rather than as a control waiting for input —
   the second-most-broken-looking thing in the measured frame after the sliced
   title. Empty field and ready-to-send are now two different objects: glass
   with a dim glyph, and a lit disc with a shadow under it. The change between
   them is the reward for typing, and it is the only motion on this screen a
   guest causes.

   It stays the HOUSE colour and deliberately not `--me`. Send is the same act
   from every hand, and a seat colour would make it red for one guest out of
   sixteen — a red send button is a destructive one in every product a person
   has ever used, which is a thing this screen would then have to explain. */
.wayin__go {
  grid-column: 2;
  width: var(--tap);
  height: var(--tap);
  border-radius: 50%;
  border: 0;
  background: linear-gradient(157deg, var(--lamp), var(--lamp) 52%, var(--lamp));
  color: var(--bg-2);
  display: grid;
  place-items: center;
  box-shadow:
    inset 0 1px 0 color-mix(in srgb, var(--cream) 34%, transparent),
    0 8px 22px color-mix(in srgb, var(--lamp) 30%, transparent),
    0 6px 18px color-mix(in srgb, var(--night) 34%, transparent);
  transition: background 200ms ease, box-shadow 200ms ease, transform 140ms cubic-bezier(.2,.9,.25,1);
}
.wayin__go:active { transform: scale(0.94); }
.wayin__go[disabled] {
  /* Not dimmed — a different material. Same glass as the field beside it, so
     the pair reads as one object with one live end. */
  opacity: 1;
  background: linear-gradient(180deg, color-mix(in srgb, var(--bg-2) 62%, transparent), color-mix(in srgb, var(--bg-2) 72%, transparent));
  -webkit-backdrop-filter: blur(22px) saturate(150%);
  backdrop-filter: blur(22px) saturate(150%);
  /* The GLYPH, not the disc. 42 % put the arrow below the point where it reads
     as an arrow over a bright photograph — measured against
     `.director/eye/r2-before.png`, where the send mark is a ghost. Lifting the
     mark says "this is a control that is not armed yet"; lifting the disc would
     say "this control is broken", which is the trade this whole block exists to
     avoid. The disc's material is unchanged. */
  color: color-mix(in srgb, var(--cream) 66%, transparent);
  /* AND THE DISC HAS TO BE AN OBJECT WHILE IT WAITS.

     MEASURED by the round-2 critic over the same 64x64 box: the baseline's
     filled teal disc averaged (22,66,60) and this glass one averaged (14,18,25)
     — the arrow was the only bright thing in it, so the one control that commits
     what a guest typed had become the dimmest mark in the lower third, on top of
     a photograph that can be bright behind it. Raising the glyph again would not
     fix that; the object was missing, not the mark.

     A rim rather than a fill: a ring of the house colour just inside the
     hairline, light caught in the glass behind it, and a little escaping. That
     gives the disc an edge a person can find against a stadium floodlight while
     keeping the two-material rule this block exists for — waiting still reads as
     glass, armed still reads as a lit disc, and the change between them is still
     the reward for typing.

     MEASURED over the same 64x64 box, same instrument, same route: (15,18,26)
     before this rule, (17,32,36) after. It is deliberately NOT back at the
     baseline's (23,67,61) — that number is a filled teal disc on a control that
     cannot yet do anything, which is the "bright button that is broken" reading
     this whole block was written to remove. The claim here is narrower and is
     the one that was actually failing: the object now HAS an edge. Judge it on
     .director/eye/r3-e.png, not on the mean. */
  box-shadow:
    inset 0 1px 0 color-mix(in srgb, var(--cream) 24%, transparent),
    inset 0 0 0 1.5px color-mix(in srgb, var(--lamp) 34%, transparent),   /* the rim: the object's edge */
    inset 0 0 20px color-mix(in srgb, var(--lamp) 20%, transparent),      /* light caught inside the glass */
    0 0 18px color-mix(in srgb, var(--lamp) 16%, transparent),            /* and a little of it escaping */
    0 10px 28px color-mix(in srgb, var(--night) 40%, transparent);
}
.wayin__echo { grid-column: 1 / -1; display: flex; gap: 10px; align-items: baseline;
  flex-wrap: wrap; padding-top: 4px; }
.wayin__said { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; opacity: 0.9; }
.wayin__stage { flex: 1 1 100%; font-size: var(--fine); color: var(--ink-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wayin__words {
  color: color-mix(in srgb, var(--cream) 62%, transparent);
  font-size: var(--kicker);
  font-weight: 760;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.wayin__age { font-variant-numeric: tabular-nums; opacity: 0.55; }
.wayin__age::after { content: "s"; }
.wayin__retry {
  min-height: 34px;
  padding: 0 13px;
  border-radius: 999px;
  color: var(--bg-2);
  background: var(--warning);
  box-shadow: inset 0 1px 0 color-mix(in srgb, var(--cream) 45%, transparent), 0 5px 14px color-mix(in srgb, var(--night) 25%, transparent);
  font-size: var(--kicker);
  font-weight: 800;
}
.wayin__retry:active { transform: scale(.95); }
/* The stop key. Present for exactly as long as the turn is in flight, and
   drawn as a key rather than a link because it is the one act a person can
   take during a working turn and it has to be findable under a thumb. */
.wayin__stop {
  min-height: 34px;
  padding: 0 13px;
  border-radius: 999px;
  color: color-mix(in srgb, var(--cream) 92%, transparent);
  background: color-mix(in srgb, var(--cream) 10%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--cream) 28%, transparent);
  font-size: var(--kicker);
  font-weight: 800;
  letter-spacing: 0.04em;
}
.wayin__stop:active { transform: scale(.95); }
.wayin__stop[disabled] { opacity: .5; }
/* The box's own words. Full width, on their own, given the room a sentence
   needs to be read — the person waited minutes for these. No card, no bubble,
   no avatar: the only thing distinguishing them from the echo above is that
   they are the ones nothing on this phone wrote. */
.wayin__heard { grid-column: 1 / -1; flex: 1 1 100%; display: flex; flex-direction: column;
  gap: 6px; padding-top: 6px; }
.wayin__heard-line { margin: 0; font-size: var(--dek); line-height: 1.35; overflow-wrap: anywhere; }
/* Working is motion; the three ended states are three different colours,
   because "it failed", "we do not know" and "it worked" must never render the
   same. Reduced motion keeps the colour and drops the animation. */
.wayin[data-state="taken"] .wayin__age,
.wayin[data-state="working"] .wayin__age { color: var(--lamp); opacity: 1; animation: wayin-pulse 2.6s ease-in-out infinite; }
.wayin[data-state="answered"] .wayin__age { color: var(--success); opacity: 1; }
/* The room did the thing and a clock ended the turn before it could say so.
   Green, because it is done — the words beside the number say the rest. */
.wayin[data-state="delivered"] .wayin__age { color: var(--success); opacity: 1; }
.wayin[data-state="unanswered"] .wayin__age { color: var(--live); opacity: 1; }
.wayin[data-state="unknown"] .wayin__age { color: var(--warning); opacity: 1; }
/* Stopped is the person's act, not a failure: neither red nor green. */
.wayin[data-state="stopped"] .wayin__age { color: color-mix(in srgb, var(--cream) 72%, transparent); opacity: 1; }
@keyframes wayin-pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.35; } }
@media (prefers-reduced-motion: reduce) { .wayin__age { animation: none !important; } }

/* The phone console material. Layout anchors above remain the authority. */
:root {
  --brand-mark: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2032%2032%22%20width%3D%2232%22%20height%3D%2232%22%20role%3D%22img%22%20aria-label%3D%22Homie%22%3E%0A%20%20%0A%20%20%3Crect%20x%3D%224%22%20y%3D%224%22%20width%3D%2224%22%20height%3D%2224%22%20rx%3D%227%22%20fill%3D%22none%22%20stroke%3D%22%23f4efe6%22%20stroke-width%3D%224%22%2F%3E%0A%20%20%3Crect%20x%3D%2211.5%22%20y%3D%2211.5%22%20width%3D%229%22%20height%3D%229%22%20rx%3D%222.2%22%20fill%3D%22%23f4efe6%22%2F%3E%0A%3C%2Fsvg%3E%0A");
  --brand-lockup: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20470%20128%22%20width%3D%22470%22%20height%3D%22128%22%20role%3D%22img%22%20aria-label%3D%22Homie%22%3E%3Cdefs%3E%3Cmask%20id%3D%22homie-lockup%22%20maskUnits%3D%22userSpaceOnUse%22%20x%3D%220%22%20y%3D%220%22%20width%3D%22128%22%20height%3D%22128%22%3E%3Crect%20width%3D%22128%22%20height%3D%22128%22%20fill%3D%22black%22%2F%3E%3Cg%20stroke%3D%22white%22%20stroke-width%3D%225%22%20stroke-linejoin%3D%22round%22%20stroke-linecap%3D%22round%22%3E%0A%20%20%3Cpath%20d%3D%22M30%2062C17%2068%207%2073%207%2079C7%2086%2022%2087%2036%2084L37%2077%22%20fill%3D%22white%22%2F%3E%0A%20%20%3Cpath%20d%3D%22M29%2064C17%2024%2047%207%2072%2010C94%2011%20104%2026%20106%2043L98%2046L35%2070Z%22%20fill%3D%22white%22%20stroke%3D%22none%22%2F%3E%0A%20%20%3Cpath%20d%3D%22M35%2064L96%2044L120%2070Q125%2077%20117%2079H113V102Q113%20109%20106%20109H39Q32%20109%2032%20102V79H28Q22%2077%2026%2071Z%22%20fill%3D%22black%22%2F%3E%0A%20%20%3Cpath%20d%3D%22M77%2038C74%2024%2090%2020%2097%2033L98%2037Z%22%20fill%3D%22black%22%20stroke%3D%22none%22%2F%3E%0A%20%20%3Cpath%20d%3D%22M77%2037L98%2032Q103%2031%20104%2036L104%2039Q104%2042%20100%2043L79%2048Z%22%20fill%3D%22white%22%20stroke-width%3D%221.5%22%2F%3E%0A%20%20%3Cg%20fill%3D%22black%22%20stroke%3D%22none%22%3E%3Ccircle%20cx%3D%2285%22%20cy%3D%2242%22%20r%3D%221.2%22%2F%3E%3Ccircle%20cx%3D%2291%22%20cy%3D%2240.5%22%20r%3D%221.2%22%2F%3E%3Ccircle%20cx%3D%2297%22%20cy%3D%2239%22%20r%3D%221.2%22%2F%3E%3C%2Fg%3E%0A%20%20%3Cg%20fill%3D%22none%22%20stroke-width%3D%224%22%3E%3Cpath%20d%3D%22M52%2078C52%2070%2062%2070%2062%2078M86%2078C86%2070%2096%2070%2096%2078M67%2087Q74%2094%2081%2087%22%2F%3E%3C%2Fg%3E%0A%20%20%3Cpath%20d%3D%22M67%20108V97H81V108%22%20fill%3D%22white%22%20stroke-width%3D%222%22%2F%3E%0A%3C%2Fg%3E%3C%2Fmask%3E%3C%2Fdefs%3E%3Crect%20width%3D%22128%22%20height%3D%22128%22%20fill%3D%22%23faf6ee%22%20mask%3D%22url(%23homie-lockup)%22%2F%3E%3Cg%20transform%3D%22translate(146%208)%20scale(1.02)%22%3E%3C!--%20Outlined%20from%20the%20bundled%20Bricolage%20Grotesque%20ExtraBold.%20SIL%20Open%20Font%20License.%20--%3E%3Cg%20fill%3D%22%23faf6ee%22%3E%3Cpath%20d%3D%22M6.2%2080V46.4V8H22.3V24.5Q22.3%2026.5%2022.15%2028.65Q22%2030.8%2021.65%2033.05Q21.3%2035.3%2020.95%2037.6Q20.6%2039.9%2020.1%2042.1H22.5Q23.9%2037.2%2026%2033.55Q28.1%2029.9%2031.5%2027.85Q34.9%2025.8%2040.2%2025.8Q49.6%2025.8%2054.25%2032.35Q58.9%2038.9%2058.9%2052.5V80H42.7V55.2Q42.7%2047.1%2040.35%2043.05Q38%2039%2033.2%2039Q29.5%2039%2027.1%2041.3Q24.7%2043.6%2023.5%2047.45Q22.3%2051.3%2022.3%2056V80Z%22%2F%3E%3Cpath%20d%3D%22M93.4%2081.4Q85.4%2081.4%2079.25%2078.3Q73.1%2075.2%2069.6%2069Q66.1%2062.8%2066.1%2053.5Q66.1%2044.2%2069.6%2038.05Q73.1%2031.9%2079.3%2028.85Q85.5%2025.8%2093.5%2025.8Q101.5%2025.8%20107.7%2028.9Q113.9%2032%20117.4%2038.15Q120.9%2044.3%20120.9%2053.6Q120.9%2063.1%20117.3%2069.3Q113.7%2075.5%20107.45%2078.45Q101.2%2081.4%2093.4%2081.4ZM93.9%2069.7Q97.5%2069.7%2099.9%2068.05Q102.3%2066.4%20103.5%2063Q104.7%2059.6%20104.7%2054.6Q104.7%2049.3%20103.4%2045.65Q102.1%2042%2099.55%2040.05Q97%2038.1%2093%2038.1Q89.6%2038.1%2087.15%2039.75Q84.7%2041.4%2083.5%2044.8Q82.3%2048.2%2082.3%2053.3Q82.3%2061.5%2085.35%2065.6Q88.4%2069.7%2093.9%2069.7Z%22%2F%3E%3Cpath%20d%3D%22M128.9%2080V45.9V27.2H141.6L141.8%2042.7H143.8Q145.2%2037%20147.5%2033.25Q149.8%2029.5%20153.1%2027.65Q156.4%2025.8%20160.8%2025.8Q165.4%2025.8%20168.45%2027.75Q171.5%2029.7%20173.3%2033.45Q175.1%2037.2%20175.7%2042.7H177.4Q179%2036.8%20181.6%2033.05Q184.2%2029.3%20187.8%2027.55Q191.4%2025.8%20195.6%2025.8Q200.1%2025.8%20203.45%2027.55Q206.8%2029.3%20209.05%2032.75Q211.3%2036.2%20212.4%2041.35Q213.5%2046.5%20213.5%2053.3V80H197.3V55.3Q197.3%2050%20196.45%2046.5Q195.6%2043%20193.8%2041.25Q192%2039.5%20189.2%2039.5Q186%2039.5%20183.75%2041.6Q181.5%2043.7%20180.3%2047.45Q179.1%2051.2%20179.1%2055.9V80H163.3V55.6Q163.3%2050.1%20162.35%2046.6Q161.4%2043.1%20159.55%2041.3Q157.7%2039.5%20154.9%2039.5Q151.7%2039.5%20149.5%2041.6Q147.3%2043.7%20146.15%2047.45Q145%2051.2%20145%2055.9V80Z%22%2F%3E%3Cpath%20d%3D%22M223.8%2080V27.2H239.9V80ZM231.9%2019.8Q227.3%2019.8%20224.85%2017.85Q222.4%2015.9%20222.4%2012.3Q222.4%208.5%20224.85%206.55Q227.3%204.6%20231.9%204.6Q236.6%204.6%20239.05%206.55Q241.5%208.5%20241.5%2012.2Q241.5%2015.9%20239.05%2017.85Q236.6%2019.8%20231.9%2019.8Z%22%2F%3E%3Cpath%20d%3D%22M276.1%2081.4Q269.5%2081.4%20264.3%2079.65Q259.1%2077.9%20255.45%2074.5Q251.8%2071.1%20249.9%2066.1Q248%2061.1%20248%2054.6Q248%2048.3%20249.85%2043Q251.7%2037.7%20255.15%2033.85Q258.6%2030%20263.65%2027.9Q268.7%2025.8%20275%2025.8Q281.4%2025.8%20286.25%2027.85Q291.1%2029.9%20294.25%2033.9Q297.4%2037.9%20298.9%2043.65Q300.4%2049.4%20299.9%2056.8L258.6%2057.1V48.7L290.7%2048.4L285%2052.3Q285.7%2047.3%20284.4%2044.15Q283.1%2041%20280.6%2039.55Q278.1%2038.1%20275.2%2038.1Q271.7%2038.1%20269.05%2040Q266.4%2041.9%20264.9%2045.5Q263.4%2049.1%20263.4%2054.3Q263.4%2062.5%20266.95%2066.2Q270.5%2069.9%20276%2069.9Q278.7%2069.9%20280.5%2069.15Q282.3%2068.4%20283.45%2067.25Q284.6%2066.1%20285.25%2064.7Q285.9%2063.3%20286.3%2062L300.3%2065Q299.5%2068.7%20297.75%2071.7Q296%2074.7%20293.05%2076.9Q290.1%2079.1%20285.95%2080.25Q281.8%2081.4%20276.1%2081.4Z%22%2F%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E");
}
.friends__door,.friends__scope,.friends__share,.shell__note {
  border: 1px solid var(--line);
  border-radius: var(--cut);
  background: var(--card);
  box-shadow: var(--card-shadow);
}



.shell__body, .sheet__body, .shell__sec { min-width: 0; }
.shell__body { overflow-x: hidden; }





.shell__label { font-size: var(--kicker); color: var(--ink-2); }
/* A person's colour travels with the chip, never with the card or primary key. */
.pill, .chip { border: 1px solid var(--person, var(--me)); background: var(--bg-2); color: var(--cream); box-shadow: none; }
.chip__dot, .pill__dot { box-shadow: none; background-image: none; }
.friends__go {
  background: var(--cream); color: var(--ink); border: 1px solid transparent;
  border-radius: var(--cut); box-shadow: var(--card-shadow); min-height: 52px;
}
.shell button:not([class*="ui-"]), .shell a, .shell input:not(.ui-switch), .shell select, .phone-guide button:not([class*="ui-"]) {
  min-height: 44px;
}
.shell .iconbtn { min-width: 44px; }
.wayin__field, .wayin__field:placeholder-shown, .wayin__field:focus {
  border: 1px solid var(--line-2); border-radius: var(--cut); box-shadow: var(--card-shadow);
  background: var(--brand-mark) 12px 22px / 18px 18px no-repeat, var(--bg-2);
  padding-left: 38px; font-family: var(--font); font-size: var(--body); color: var(--cream);
}
.wayin__field::placeholder, .wayin__field::-webkit-input-placeholder { color: var(--ink-2); opacity: 1; }
.wayin__go, .wayin__go[disabled] { background: var(--lamp); color: var(--ink); border: 1px solid var(--line-2); box-shadow: var(--card-shadow); }
.wayin__go[disabled] { background: var(--bg-2); color: var(--ink-3); }
.wayin__stop, .wayin__retry { min-height: 44px; }
button { transition: transform var(--motion), background var(--motion), border-color var(--motion); }
button:active:not(:disabled), .wayin__go:active:not(:disabled) { transform: translateY(2px) scale(.98); }
:where(button, input, textarea, select, a, [tabindex]):focus-visible {
  outline: 3px solid var(--lamp); outline-offset: 2px;
}
/* No arrival flourish; only intentional state changes move. */
body::before, .offer, .chip, .pill { animation: none; }
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
}

@font-face {
  font-family: "Bricolage Grotesque";
  src: url("./bricolage-grotesque.woff2") format("woff2");
  font-weight: 200 800; font-style: normal; font-display: swap;
}
/* Cast's independent stylesheet predates the site's dark-ink token. */
.cast { --ink: var(--cream); }
.friends__toggle {
  border-radius: var(--cut); border: 1px solid var(--line);
  background: var(--card); box-shadow: var(--card-shadow);
}
.friends__scope-hint { font-size: var(--fine); line-height: 1.35; }
/* The shadow anchor inherits the same palette and timing through its host. */
.btn--go, .create__go {
  background: var(--cream); color: var(--ink); border-color: transparent;
  border-radius: var(--cut); box-shadow: var(--card-shadow);
}
.pill.is-me { background: var(--bg-2); box-shadow: none; }

/* The feedback draft stays inside the existing scrollable Homie pane. */
.feedback { padding: 8px 0 24px; }
.feedback[hidden] { display: none; }
.feedback h2 { font-size: 22px; margin: 0 0 12px; }
.feedback p { line-height: 1.5; }
.feedback__form { display: grid; gap: 10px; margin-top: 20px; }
.feedback__form label { font-size: 14px; font-weight: 650; }
.feedback__form input, .feedback__form select, .feedback__form textarea {
  box-sizing: border-box; width: 100%; min-width: 0; min-height: 46px;
  padding: 12px; border: 1px solid #ffffff40; border-radius: 12px;
  background: var(--bg-2); color: var(--cream); font: inherit; font-size: 16px;
}
.feedback__form textarea { resize: vertical; }
.feedback__privacy { font-size: 13px; color: var(--ink-3); }
.feedback__status { overflow-wrap: anywhere; }

.feedback__form[hidden] { display: none; }
.feedback__privacy a { color: inherit; text-underline-offset: 3px; }

/* Shared game controls: the default is a finished controller even when a
   newly created game supplies nothing beyond its input scheme. */
.surface .zone--right { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); grid-auto-rows: minmax(54px, 1fr); gap: 12px; align-items: stretch; align-content: stretch; }
.surface .zone--right > .ctl--stick, .surface .zone--right > .ctl--axis { grid-column: 1 / -1; }
.surface .zone--right > .ctl--button { width: 100%; height: 100%; min-height: 54px; border-radius: 24px; }
.surface[data-actions="1"] .zone--right, .surface[data-actions="2"] .zone--right, .surface[data-actions="3"] .zone--right { grid-template-columns: 1fr; }
.surface .zone.is-empty { display: none; }
.surface .ctl--button { border-color: #42535e; background: linear-gradient(145deg, #ffffff14, transparent 65%), #14212c; box-shadow: 0 5px 0 #0008, 0 10px 18px #0005, inset 0 1px 0 #ffffff35; color: var(--cream); }
.surface .ctl--button .button__face { white-space: normal; overflow-wrap: anywhere; font-size: 16px; line-height: 1.25; font-weight: 750; }
.surface .ctl--button[data-tone="go"], .surface .zone--right > .ctl--button:first-of-type { border-color: #67e8cf80; background: linear-gradient(145deg, #ffffff12, transparent), #163a3d; }
.surface .ctl--button[data-tone="stop"] { border-color: #ff6b6b70; background: linear-gradient(145deg, #ffffff12, transparent), #3b242c; color: var(--cream); }
.surface .ctl--button.is-down { transform: translateY(4px); filter: brightness(1.3); box-shadow: 0 1px 0 #0008, inset 0 2px 6px #0004; }
.surface .ctl--stick, .surface .ctl--dpad { min-height: 120px; min-width: 0; border-radius: 30px; border-color: #344754; background: radial-gradient(circle at center, transparent 29%, #ffffff10 30%, transparent 31%, transparent 43%, #ffffff08 44%, transparent 45%), #111e29; box-shadow: inset 0 10px 24px #0005, 0 1px 0 #ffffff24; }
.surface .stick__base { width: min(130px, 85%); height: auto; aspect-ratio: 1; background: transparent; border-color: #ffffff24; }
.surface .stick__knob { background: radial-gradient(circle at 35% 25%, #ffffff60, transparent 65%), #67e8cf; border: 1px solid #ffffff70; box-shadow: 0 6px 0 #0008, 0 13px 22px #0007, inset 0 2px 0 #ffffff70; }
.surface .ctl__label { top: 14px; left: 14px; color: #d8e4e8; letter-spacing: .06em; }
.surface .dpad__pad { width: min(180px, 94%); height: auto; aspect-ratio: 1; }
.surface .dpad__arm { background: linear-gradient(145deg,#ffffff18,transparent), #243742; border-color: #48606d; box-shadow: 0 3px 0 #0006; }
.surface .dpad__arm.is-down { background: #67e8cf; }
.surface .dpad__hub { background: #243742; }
.surface .zone--left.has-direction-buttons { grid-template-columns: repeat(2,minmax(0,1fr)); grid-template-rows: repeat(3,minmax(54px,82px)); gap: 8px; align-content: end; }
.surface .zone--left.has-direction-buttons .ctl--button { width: 100%; height: 100%; border-radius: 20px; }
.surface .zone--left.has-direction-buttons .ctl--button[data-direction="up"] { grid-column: 1 / -1; grid-row: 1; width: 50%; justify-self: center; }
.surface .zone--left.has-direction-buttons .ctl--button[data-direction="down"] { grid-column: 1 / -1; grid-row: 3; width: 50%; justify-self: center; }
.surface .zone--right > .ctl--stick { flex: none; }
@media (orientation: landscape) and (max-height: 600px) {
  .play > .surface { gap: 8px; padding-bottom: max(10px, var(--safe-b)); }
  .surface .field__row, .surface .zone--right { gap: 10px; }
  .surface .zone--right { grid-template-columns: repeat(3,minmax(0,1fr)); }
  .surface .ctl--button .button__face { font-size: 14px; }
  .surface .zone--left.has-direction-buttons { grid-template-rows: repeat(3,minmax(44px,1fr)); }
  .surface .zone--right > .ctl--stick { grid-column: 1; grid-row: 1 / span 3; }
  .surface[data-sticks="2"] .zone--right { flex: 2; }
  .surface[data-sticks="2"] .zone--right > .ctl--button { grid-column: auto; }
}
@media (max-width: 360px) {
  .play > .surface { padding-inline: 10px; gap: 8px; }
  .surface .field__row, .surface .zone--right { gap: 8px; }
  .surface .ctl--button .button__face { font-size: 13px; padding: 6px; }
  .surface .stick__knob { width: 58px; height: 58px; }
}
@media (orientation: portrait) {
  .surface .field { max-height: 520px; margin-top: auto; }
  .surface .field__row.has-direction-buttons .zone--right { max-height: 250px; align-self: flex-end; }
  .surface[data-sticks="0"] .field:not(:has(.ctl--buzzer, .ctl--swipe, .ctl--draw, .ctl--dpad)) { max-height: 420px; }
}
.surface .zone--right { align-items: center; align-content: center; grid-auto-rows: minmax(54px,112px); }
.surface .zone--right > .ctl--button { width: min(100%,104px); height: 100%; max-height: 104px; justify-self: center; align-self: center; border-radius: 50%; border-width: 2px; }
.surface .zone--right > .ctl--button:not(:first-of-type) { max-width: 94px; max-height: 94px; }
.surface .ctl--stick { background: none; border: 0; box-shadow: none; overflow: visible; }
.surface .stick__base { background: repeating-radial-gradient(circle at center,#ffffff05 0 3px,transparent 4px 7px),#0b1720; border: 2px solid #344754; box-shadow: inset 0 7px 20px #0009,0 1px 0 #ffffff20; }
.surface .stick__knob { background: radial-gradient(circle at 40% 28%,#ffffff20,transparent 70%),#263c47; border: 2px solid #67e8cf; box-shadow: 0 7px 0 #02070b,0 13px 20px #000a,inset 0 2px 0 #ffffff30; }
.surface .ctl--stick > .ctl__label { top: calc(50% + 78px); left: 0; width: 100%; text-align: center; font-size: 10px; }
.surface .field__row.has-direction-buttons .zone--right > .ctl--button { width: 94px; height: 94px; }
@media (orientation: landscape) and (max-height:600px) {
  .surface .zone--right { grid-auto-rows: minmax(44px,1fr); }
  .surface .zone--right > .ctl--button { border-radius: 20px; max-height: 80px; }
  .surface .ctl--stick > .ctl__label { top: auto; bottom: 3px; }
  .surface .field__row.has-direction-buttons .zone--right > .ctl--button { width: min(100%,94px); height: 100%; }
}
@media (max-width:360px) {
  .surface .zone--right > .ctl--button { min-width: 44px; font-size: 13px; }
}
.surface .zone--right > .ctl--stick > .ctl__label { top: 4px; bottom: auto; }
.surface[data-actions="4"] .zone--right > .ctl--button,
.surface[data-actions="5"] .zone--right > .ctl--button,
.surface[data-actions="6"] .zone--right > .ctl--button,
.surface[data-actions="7"] .zone--right > .ctl--button { border-radius: 22px; }
/* Answer cards own the available space; do not draw an empty gamepad beneath
   a text/choice-only scheme or let a long card list push action keys away. */
.surface > .strip { flex: 1 1 auto; max-height: none; min-height: 0; }
.surface:has(> .strip) > .field { flex: 0 0 150px; margin-top: 0; }
.surface:has(> .strip) .zone--right { grid-template-columns: repeat(2,minmax(0,1fr)); grid-auto-rows: minmax(44px,1fr); }
.surface:has(> .strip) .zone--right > .ctl--button { border-radius: 20px; height: 100%; max-height: 90px; }
@media (orientation:landscape) and (max-height:600px) {
  .surface:has(> .strip) > .field { flex-basis: 86px; }
  .surface:has(> .strip) .field__row { padding: 8px; }
  .surface:has(> .strip) .zone--right > .ctl--button { min-height: 48px; }
}





/* ==========================================================================
   THE EPIC PASS, 2026-09-10 — the screens brought up to the game card's bar
   ==========================================================================
   Appended last on purpose: the late block above re-skins every card with
   `var(--card)` and a hairline at equal specificity, and these have to win.
 */


/* ==========================================================================
   ROOM (the old Friends) — who is here and what they can see
   ==========================================================================

   Room lane, 2026-09-14, against critique r0 C.5. Top to bottom: a presence
   row of avatars, the one segmented mode control with a footnote, the open
   card while the room is shared, one wide card with the art of what is shared
   (Change opens a sheet of posters), a switch row, the two-up invitations and
   the link they produce, the wifi address, the other ways as an inset list,
   Go live, the Homies in this house, and the Rooms that are live elsewhere.

   Surfaces separate by fill (elev1 cards, elev2 inside them), never outline;
   one selection idiom per control (segment thumb, teal ring on a poster, teal
   fill on a lit invitation); tokens only, so nothing here adds a size, radius
   or hue the system does not already have. The share-card region replaced a
   104 pt rail with no inline padding that clipped 18–27 boxes. */
.shell__sec--friends { gap: var(--sp-6); }
.shell__sec--share { gap: var(--sp-4); }
.shell__sec--friends .friends__quiet { margin: 0; font: 400 var(--fs-footnote)/var(--lh-footnote) var(--font-text); color: var(--label-2); }
.shell__sec--friends .friends__refused { margin: 0; font: 600 var(--fs-subhead)/var(--lh-subhead) var(--font-text); color: var(--label-1); }

/* -- presence: the one roster node, drawn as avatars -------------------------
   `display: contents` lifts the pills out of the field, the roster and its list
   so they are flex items of this scroller, and the "+" (`order: 1`) stays last
   however often `hostSeat` re-appends the roster. The scroller bleeds to the
   screen edge and pads back in, so the first and last avatars are never cut. */
.friends__presence {
  display: flex; gap: var(--sp-3);
  margin: calc(-1 * var(--sp-2)) calc(-1 * var(--page-margin));
  padding: var(--sp-2) var(--page-margin);
  overflow-x: auto; overscroll-behavior-x: contain; scrollbar-width: none;
}
.friends__presence::-webkit-scrollbar { display: none; }
/* r2 Room + Now lane: the row fades at the trailing edge, so a row longer than
   the glass reads as one that scrolls and the "+" at its end is discoverable
   (critique-r1 B6: Invite sat at x=400 pt with no peek). */
.friends__presence { -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 44px), transparent); mask-image: linear-gradient(to right, #000 calc(100% - 44px), transparent); }
.room-people { display: flex; flex-direction: column; gap: var(--sp-2); }
.room-people__line { margin: 0 var(--sp-4); font: 400 var(--fs-footnote)/var(--lh-footnote) var(--font-text); color: var(--label-2); }
.room-people__line:empty { display: none; }
.friends__presence > .shell__field--roster,
.friends__presence .roster,
.friends__presence .roster__list { display: contents; }
.friends__presence .shell__label {
  position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}
.shell__sec--friends .friends__presence .pill,
.friends__presence-add {
  flex: none; order: 0;
  display: flex; flex-direction: column; align-items: center; gap: var(--sp-1);
  width: 64px; min-height: 0; margin: 0; padding: 0;
  border: 0; border-radius: 0; background: none; box-shadow: none;
  color: var(--label-1); font: inherit;
  -webkit-tap-highlight-color: transparent;
}
.friends__presence-add { order: 1; cursor: pointer; }
.shell__sec--friends .friends__presence .pill__dot {
  position: relative; display: grid; place-items: center;
  width: 56px; height: 56px; border-radius: var(--r-full);
  box-shadow: none; background-image: none;
  font: 600 var(--fs-title3)/1 var(--font-text); color: var(--on-primary);
}
/* The initial is an attribute read as generated content: text, never markup,
   for a name that is untrusted guest input. */
.friends__presence .pill__dot::before { content: attr(data-initial); }
/* On a controller: a teal ring that breathes outside the avatar. */
.friends__presence .pill.is-playing .pill__dot::after {
  content: ""; position: absolute; inset: -4px; border-radius: inherit;
  box-shadow: 0 0 0 2px var(--accent);
  animation: room-ring 2s var(--ease-nav) infinite;
}
@keyframes room-ring { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }
.friends__presence .pill__name,
.friends__presence-name {
  max-width: 64px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font: 400 var(--fs-footnote)/var(--lh-footnote) var(--font-text); color: var(--label-2);
}
.friends__presence .pill.is-me .pill__name { color: var(--label-1); font-weight: 600; }
.friends__presence-ring {
  display: grid; place-items: center;
  width: 56px; height: 56px; box-sizing: border-box; border-radius: var(--r-full);
  border: 1.5px dashed var(--label-3); color: var(--accent);
  transition: transform var(--dur-press-out) var(--ease-nav);
}
.friends__presence-ring .ui-ic { width: 24px; height: 24px; }
.friends__presence-add:active .friends__presence-ring { transform: scale(0.94); transition-duration: var(--dur-press-in); }
.friends__presence-add:focus-visible { outline: none; }
.friends__presence-add:focus-visible .friends__presence-ring { outline: 2px solid var(--accent); outline-offset: 2px; }

/* -- the mode -------------------------------------------------------------- */
.room-band { display: flex; flex-direction: column; gap: var(--sp-2); }
.room-band .ui-seg__opt { display: inline-flex; align-items: center; justify-content: center; gap: 6px; }
.room-band .ui-seg__opt:disabled { color: var(--label-4); }
/* ON AIR, and only then (`paintRoomBand` removes the node otherwise). */
.room-band__dot { flex: none; width: 8px; height: 8px; border-radius: var(--r-full); background: var(--live-dot); animation: room-ring 2s var(--ease-nav) infinite; }
.room-band__line { margin: 0 var(--sp-4); font: 400 var(--fs-footnote)/var(--lh-footnote) var(--font-text); color: var(--label-2); }
.room-band__connect { display: flex; flex-direction: column; align-items: flex-start; gap: 0; margin: 0 var(--sp-4); }
.room-band__connect[hidden] { display: none; }
.room-band__connect-key { margin-left: calc(-1 * var(--sp-2)); }

/* -- the room is open ------------------------------------------------------- */
.friends__open { display: flex; flex-direction: column; gap: var(--sp-1); padding: var(--sp-4); border-radius: var(--r-md); background: var(--bg-elev1); }
.friends__open[hidden] { display: none; }
.friends__open-dot { flex: none; width: 8px; height: 8px; border-radius: var(--r-full); background: var(--accent); animation: room-ring 2s var(--ease-nav) infinite; }
.friends__open-name { margin: var(--sp-1) 0 0; font: 600 var(--fs-title3)/var(--lh-title3) var(--font-display); letter-spacing: -0.1px; color: var(--label-1); }
.friends__open-listed { margin: 0; font: 400 var(--fs-footnote)/var(--lh-footnote) var(--font-text); color: var(--label-2); }
/* r2: one status row — live dot, "Sharing Kart Royale" at 17 pt, Stop trailing — and one footnote. */
.friends__open { padding: var(--sp-3) var(--sp-2) var(--sp-3) var(--sp-4); }
.room-open__head { display: flex; align-items: center; gap: var(--sp-2); min-height: var(--hit); }
.room-open__title { flex: 1 1 auto; min-width: 0; margin: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font: 600 var(--fs-body)/var(--lh-body) var(--font-text); letter-spacing: -0.4px; color: var(--label-1); }
.room-open__head > .friends__stop { flex: none; }
.room-open__facts { margin: 0 var(--sp-2) 0 calc(8px + var(--sp-2)); font: 400 var(--fs-footnote)/var(--lh-footnote) var(--font-text); color: var(--label-2); }
.friends__open .friends__open-listed, .friends__open .friends__refused, .friends__open .friends__quiet { margin-left: calc(8px + var(--sp-2)); }

/* -- what is shared: one wide card with its art ------------------------------ */
/* r2: no question over the card ("What are you sharing?" — critique r0 and r1);
   the label stays for assistive tech, and Change is a capsule on the art. */
.friends__field--games { position: relative; display: flex; flex-direction: column; }
.friends__what { position: absolute; z-index: 2; top: var(--sp-2); right: var(--sp-2); }
.friends__what .ui-group__header { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; margin: 0; }
.friends__change.ui-btn--plain {
  min-height: 32px; padding: 0 var(--sp-3); position: relative;
  border-radius: var(--r-full); background: rgba(11, 13, 16, 0.6); color: var(--label-1);
  -webkit-backdrop-filter: var(--bar-blur); backdrop-filter: var(--bar-blur);
  font: 600 var(--fs-subhead)/1 var(--font-text);
}
.friends__change.ui-btn--plain::after { content: ""; position: absolute; inset: -6px -4px; }
.friends__change[hidden] { display: none; }
.friends__games:empty { aspect-ratio: 16 / 9; border-radius: var(--r-md); background: var(--bg-elev1); }
.friends__art {
  display: block; position: relative; overflow: hidden;
  background: linear-gradient(150deg, var(--accent-fill), var(--bg-elev2));
  background-size: cover; background-position: center;
}
.friends__art-img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.friends__card { position: relative; display: block; aspect-ratio: 16 / 9; overflow: hidden; border-radius: var(--r-md); background: var(--bg-elev1); }
.friends__card > .friends__art { position: absolute; inset: 0; }
.friends__card::after { content: ""; position: absolute; inset: 40% 0 0; background: linear-gradient(to bottom, rgba(11, 13, 16, 0), rgba(11, 13, 16, 0.86)); pointer-events: none; }
.friends__game-name { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; }
.friends__card > .friends__game-name {
  position: absolute; z-index: 1; left: var(--sp-4); right: var(--sp-4); bottom: var(--sp-3);
  font: 600 var(--fs-title3)/var(--lh-title3) var(--font-display); letter-spacing: -0.1px; color: var(--label-1);
}
/* The posters in the Change sheet: 2:3, two columns, the selected one ringed
   in teal 3 pt outside the art with a check badge (D.6 choice tiles). The inner
   ring is the sheet's own fill so the gap reads as air. */
.friends__posters { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-5) var(--sp-3); padding: var(--sp-2) 0 var(--sp-6); }
.friends__poster { display: flex; flex-direction: column; gap: var(--sp-2); min-width: 0; margin: 0; padding: 0; border: 0; background: none; color: var(--label-1); text-align: left; font: inherit; cursor: pointer; -webkit-tap-highlight-color: transparent; }
.friends__poster-art { position: relative; display: block; aspect-ratio: 2 / 3; border-radius: var(--r-sm); transition: transform var(--dur-press-out) var(--ease-nav); }
.friends__poster-art > .friends__art { position: absolute; inset: 0; border-radius: var(--r-sm); }
.friends__poster.is-on .friends__poster-art { box-shadow: 0 0 0 3px var(--bg-elev1), 0 0 0 6px var(--accent); }
.friends__poster-check { position: absolute; top: var(--sp-2); right: var(--sp-2); display: none; place-items: center; width: 24px; height: 24px; border-radius: var(--r-full); background: var(--accent); color: var(--on-primary); }
.friends__poster-check .ui-ic { width: 16px; height: 16px; }
.friends__poster.is-on .friends__poster-check { display: grid; }
.friends__poster:active .friends__poster-art { transform: scale(0.97); transition-duration: var(--dur-press-in); }
.friends__poster > .friends__game-name { font: 600 var(--fs-subhead)/var(--lh-subhead) var(--font-text); letter-spacing: -0.2px; }
.friends__scopes[hidden] { display: none; }
.friends__keep-row { cursor: pointer; }

/* -- the invitations -------------------------------------------------------- */
.friends__acts { display: flex; flex-direction: column; gap: var(--sp-3); }
.friends__acts:empty { display: none; }
/* r2: equal halves, Invite to play filled (the tab's primary action). Words
   only — with a glyph each, "Invite to watch" did not fit half of 375 pt. */
.friends__invite { padding: 0 var(--sp-3); }
.friends__invite-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.friends__seats { margin: calc(-1 * var(--sp-2)) var(--sp-4) 0; }
.room-options-row[hidden], .room-options[hidden] { display: none; }
/* -- Sharing options, pushed inside Room (r2) -------------------------------
   The tab's large title steps aside for the page's nav bar, and the root's
   sections are hidden, not removed, so every painter keeps its node. */
.shell__body[data-view="friends"][data-room-pushed="options"] > .ui-lt,
.shell__body[data-view="friends"][data-room-pushed="options"] > .ui-lt-bar { display: none; }
.shell__body[data-view="friends"][data-room-pushed="options"] { padding-top: var(--safe-t); }
.shell__body[data-room-pushed="options"] .shell__sec--friends > :not(.room-options) { display: none; }
.room-options { display: flex; flex-direction: column; gap: var(--sp-6); }
.room-options > .ui-navbar { margin: 0 calc(-1 * var(--page-margin)); background: var(--bg-base); }
.friends__seats:empty { display: none; }

/* -- the link in hand -------------------------------------------------------
   Selectable on purpose: on the plain-HTTP door there is no Clipboard API, so
   selecting it is the only copy a guest who came in that way has. */
.friends__out { display: flex; flex-direction: column; gap: var(--sp-2); padding: var(--sp-4); border-radius: var(--r-md); background: var(--bg-elev1); }
.friends__out[data-state="idle"], .friends__out:empty { display: none; }
.friends__out .ui-group__header { margin: 0; }
.friends__link {
  padding: var(--sp-3); border-radius: var(--r-sm); background: var(--bg-elev2); color: var(--label-1);
  font: 400 var(--fs-subhead)/var(--lh-subhead) ui-monospace, SFMono-Regular, Menlo, monospace;
  word-break: break-all; user-select: all; -webkit-user-select: all;
}
.friends__hands { margin-top: var(--sp-2); }
/* A room's other links as their numbers, wrapping so every chip stays on the
   glass. 44 pt each (D.11.5): eight wrap to two rows, which beats a target a
   thumb lands one number over. */
.friends__linkpick { display: flex; flex-direction: column; gap: var(--sp-2); padding-top: var(--sp-2); }
.friends__chips { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.friends__chip {
  display: grid; place-items: center; width: var(--hit); height: var(--hit); margin: 0; padding: 0;
  border: 0; border-radius: var(--r-full); background: var(--bg-elev2); color: var(--label-1);
  font: 600 var(--fs-subhead)/1 var(--font-text); font-variant-numeric: tabular-nums;
  transition: transform var(--dur-press-out) var(--ease-nav);
}
.friends__chip:active { transform: scale(0.94); transition-duration: var(--dur-press-in); }
/* Sent from THIS phone: a hint, still pressable — the friend may never have opened it. */
.friends__chip[data-link="sent"] { background: transparent; box-shadow: inset 0 0 0 1.5px var(--bg-elev3); color: var(--label-2); }
/* Used: the room's own reading. Struck, not hidden — how many have gone is the count. */
.friends__chip[data-link="used"] { background: transparent; color: var(--label-3); text-decoration: line-through; }
/* Somebody is on this one right now. */
.friends__chip[data-link="live"] { box-shadow: inset 0 0 0 2px var(--accent); color: var(--accent); text-decoration: none; }
.friends__chip.is-on { background: var(--accent); box-shadow: none; color: var(--on-primary); text-decoration: none; }

/* -- the address, the other ways, the page ---------------------------------- */
.room-band__address[hidden] { display: none; }
.room-band__addr-row { padding-right: var(--sp-2); }
.room-band__addr-row > .ui-btn, .room-band__addr-row > .ui-iconbtn { flex: none; }
.friends__more { display: flex; flex-direction: column; gap: var(--sp-3); }
.friends__more:empty { display: none; }
.friends__more .ui-row__subtitle { white-space: normal; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; }
.friends__more .ui-row.is-on { background: var(--accent-fill); }
.friends__more .ui-row:disabled { opacity: 0.4; }
.friends__page-after { display: flex; flex-direction: column; align-items: stretch; gap: var(--sp-3); }
.friends__page { display: flex; flex-direction: column; gap: var(--sp-2); padding: var(--sp-4); border-radius: var(--r-md); background: var(--bg-elev1); }
.friends__page-said { margin: 0; font: 600 var(--fs-body)/var(--lh-body) var(--font-text); color: var(--label-1); }
.friends__page-url { font: 400 var(--fs-subhead)/var(--lh-subhead) var(--font-text); color: var(--accent); overflow-wrap: anywhere; }
.friends__words { display: grid; gap: var(--sp-3); padding: var(--sp-4); border-radius: var(--r-md); background: var(--bg-elev1); }
.friends__words .shell__label { font: 600 var(--fs-body)/var(--lh-body) var(--font-text); text-transform: none; letter-spacing: -0.4px; color: var(--label-1); }
.friends__word { display: grid; gap: var(--sp-1); }
.friends__word-name { font: 400 var(--fs-footnote)/var(--lh-footnote) var(--font-text); color: var(--label-2); }
.friends__word-field {
  box-sizing: border-box; width: 100%; min-height: var(--hit); padding: 11px var(--sp-3);
  border: 0; border-radius: var(--r-sm); background: var(--bg-elev2); color: var(--label-1);
  font: 400 var(--fs-body)/var(--lh-body) var(--font-text); resize: vertical;
}
.friends__word-field:focus { outline: 2px solid var(--accent); outline-offset: 0; }

/* -- Go live -------------------------------------------------------------- */
.share-broadcast { display: flex; flex-direction: column; align-items: stretch; gap: var(--sp-1); padding: var(--sp-4); border-radius: var(--r-md); background: var(--bg-elev1); }
.share-broadcast[hidden] { display: none; }
.share-broadcast h3 { margin: 0; font: 600 var(--fs-body)/var(--lh-body) var(--font-text); letter-spacing: -0.4px; color: var(--label-1); }
.share-broadcast p { margin: 0; font: 400 var(--fs-footnote)/var(--lh-footnote) var(--font-text); color: var(--label-2); }
.share-broadcast p[role="status"] { font: 400 var(--fs-subhead)/var(--lh-subhead) var(--font-text); color: var(--label-1); }
.share-broadcast > .ui-btn { margin-top: var(--sp-3); }
.share-broadcast > .ui-btn--plain, .share-broadcast > .ui-btn--destructive { align-self: flex-start; margin: var(--sp-1) 0 calc(-1 * var(--sp-2)) calc(-1 * var(--sp-2)); }

/* -- the Homies in this house, and the Rooms live elsewhere ----------------- */
.shell__sec--friends .homies { gap: 0; }
.homies__rows > .friends__quiet { padding: var(--sp-3) var(--sp-4); }
.homies__row.is-on { background: var(--accent-fill); }
.homies__acts { display: flex; flex-direction: column; gap: var(--sp-2); margin-top: var(--sp-3); }
.homies__acts:empty { display: none; }
.homies__acts > .friends__quiet { margin: 0 var(--sp-4); }
.homies__act { border-radius: var(--r-md); background: var(--bg-elev1); }
.homies__act:disabled { opacity: 0.4; }
.homies__field {
  box-sizing: border-box; width: 100%; min-height: var(--hit); padding: 0 var(--sp-4);
  border: 0; border-radius: var(--r-sm); background: var(--bg-elev1); color: var(--label-1);
  font: 400 var(--fs-body)/var(--lh-body) var(--font-text);
}
.homies__field::placeholder { color: var(--label-3); }
.homies__field:focus { outline: 2px solid var(--accent); outline-offset: 0; }
.shell__sec--friends .homies__out { margin-top: var(--sp-3); }
.friends__rooms { display: flex; flex-direction: column; }
.friends__rooms[hidden] { display: none; }
.friends__rooms > .friends__quiet { margin: 0 var(--sp-4); }
.friends__room-watch { flex: none; }

@media (prefers-reduced-motion: reduce) {
  .friends__presence .pill.is-playing .pill__dot::after, .room-band__dot, .friends__open-dot { animation: none; }
}

/* the television's join-code keys: no band at all while the television offers none
   (the keys are hidden, the status line is empty) — an empty bordered pill was
   drawn between Console sounds and Family mode, MEASURED after-01/settings-1 */


/* ── PLAY WITH AN AI ────────────────────────────────────────────────────────
   An AI can be seated as a player in any game from the phone (2026-09-12).
   Before this there was no way to add one from the phone app at all.

   Two surfaces: `.players` is a row of the controller's "…" menu (drawn with
   the list rows, in the controller + door block at the end of this file), and
   `.players--desk` is the panel on the AI screen. They
   share a key shape so that a person who presses one recognises the other.

   The row takes 44 pt of the controller's flex space and no more. `.surface`
   is `flex: 1`, so it gives the height up and re-lays out — the same trade
   `.play__seat` above it already makes, and the reason both rows are drawn
   only while they have something to say. */

/* The panel on the AI screen.

   It rests as ONE 44 pt key and is the first thing inside the list's own
   scroller, not a sibling above it. MEASURED at 375x812 with four housemates
   (tools/phone-ai-desk-fold-probe.mjs): drawn open above the list it took
   172-324 pt and left the history 18 pt, zero sessions readable. As a sibling
   even one line is a fixed slice of a list that only has ~228 pt at that size;
   inside the scroller it is the top of "now" and scrolls away into "history". */
.players--desk {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 0;
  width: 100%;
  min-width: 0;
  margin: 0 0 10px;
  padding: 0;
  border: 1px solid color-mix(in srgb, var(--cream) 14%, transparent);
  border-radius: 14px;
  background: color-mix(in srgb, var(--bg-2) 72%, transparent);
}
.players__summary {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  min-width: 0;
  min-height: 44px;
  padding: 0 12px;
  border: 0;
  border-radius: 14px;
  color: var(--ink-2);
  background: transparent;
  font: inherit;
  text-align: left;
}
.players__summary:active { background: color-mix(in srgb, var(--cream) 6%, transparent); }
.players__heads {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: 5px 0;
}
.players__title { font-size: var(--ph-fine); font-weight: 700; color: var(--cream); white-space: nowrap; line-height: 1.2; }
/* The half that can be long ellipses; the heading over it never does. */
.players__glance {
  min-width: 0;
  line-height: 1.25;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--ph-fine);
  color: var(--ink-3);
}
.players__chevron { flex: none; display: inline-flex; color: var(--ink-3); transition: transform .18s ease; }
.players__chevron .mk { width: 18px; height: 18px; }
.players--desk.is-open .players__chevron { transform: rotate(180deg); }
.players__body {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 2px 12px 12px;
}
.players__line { font-size: var(--ph-fine); color: var(--ink-3); line-height: 1.3; }
/* Wraps rather than scrolls. Four housemates at 375 pt is two rows, and a
   horizontal scroller hides the fourth one behind a gesture nobody performs —
   the shape that made the door's QR the element most likely to be lost. */
.players__keys { display: flex; flex-wrap: wrap; gap: 8px; }
.players__key {
  flex: none;
  min-height: 44px;
  padding: 0 14px;
  border-radius: 999px;
  display: inline-flex;
  align-items: center;
  color: var(--ink-2);
  background: color-mix(in srgb, var(--cream) 8%, transparent);
  box-shadow: inset 0 0 0 1px var(--line);
  font-size: var(--ph-fine);
  font-weight: 650;
}
/* The house's own pick, marked rather than merely placed first: four equal
   keys is four keys where the free local one is not obviously the free one. */
.players__key.is-default {
  color: var(--cream);
  background: color-mix(in srgb, var(--cream) 16%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--cream) 30%, transparent);
}
.players__key:active { transform: scale(.96); }
.players__key:disabled { opacity: .45; }
.players__key.is-busy { opacity: .7; }
.players__table { display: flex; flex-direction: column; gap: 6px; }
.players__seated {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  font-size: var(--ph-fine);
  color: var(--ink-2);
}
.players__seated strong { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The seat ordinal, which is the one fact that tells two AIs of the same
   housemate apart on the television. */
.players__seat {
  flex: none;
  min-width: 22px;
  padding: 1px 6px;
  border-radius: 999px;
  text-align: center;
  color: var(--ink-3);
  background: color-mix(in srgb, var(--cream) 10%, transparent);
}
.players__stop {
  flex: none;
  margin-left: auto;
  min-height: 36px;
  padding: 0 12px;
  border-radius: 999px;
  color: var(--ink-2);
  background: color-mix(in srgb, var(--cream) 8%, transparent);
  box-shadow: inset 0 0 0 1px var(--line);
  font-size: var(--ph-fine);
  font-weight: 650;
}
.players__stop:active { transform: scale(.96); }
.players__stop:disabled { opacity: .45; }

/* ========================================================================== */
/*  Profile lane (2026-09-14): the Profile sheet and every House page in it    */
/* ========================================================================== */
/*
 * iOS Settings geometry (critique-r0 C.4/C.5, D.4–D.7), built from the
 * frame's closed set: `.ui-group`/`.ui-row`/`.ui-switch`/`.ui-btn` do the
 * rows, switches and buttons, and this block only lays out what those do not
 * have — the avatar header, the colour grid, form cards, the routines
 * timeline, the confirmation sheet and the feedback form. Tokens only. It
 * replaced the old House room block (`.house__*`, ~190 rules of hairline rows,
 * uppercase tags, bordered cards and blue switches) and the identity panel's
 * `.shell__sec--me` / `.me__name` / `.swatch` material, all deleted.
 */

/* A `hidden` group or row inside the sheet is gone; the components' own
   `display: flex` would otherwise outrank the attribute. */
.ui-sheet :is(.ui-group, .ui-row, .ui-btn)[hidden] { display: none; }

/* The sheet's sections stack on the section gap, and an empty slot spends none of it. */
.shell__sec--house, .house__status, .house__hub, .house__page, .house__keys {
  display: flex; flex-direction: column; gap: var(--section-gap); min-width: 0;
}
:is(.house__status, .house__hub, .house__page, .house__keys):empty,
:is(.house__status, .house__hub, .house__page, .house__keys)[hidden] { display: none; }
.house__tv { display: contents; }

/* ---- Who you are ---------------------------------------------------------- */
.shell__sec--me { display: flex; flex-direction: column; gap: var(--sp-6); min-width: 0; }
/* The chip and the roster park here between their other homes (`hostSeat`);
   the avatar is who you are on this sheet, and Room owns who else is here. */
.shell__sec--me > .shell__id, .shell__sec--me > .shell__rosterslot { display: none; }
.ui-avatar-btn.profile__avatar { width: var(--sp-14); height: var(--sp-14); margin: 0; }
.profile__avatar .ui-avatar {
  width: 100%; height: 100%;
  font: 700 var(--fs-title2)/1 var(--font-display); letter-spacing: -0.2px;
}
.profile__avatar .ui-avatar[data-empty="true"]::before { width: var(--sp-6); height: var(--sp-6); }
/* Who you are, one card (r2, critique-r1 B7): the avatar and the name on one
   row, a pencil trailing; the colours under a separator. */
.profile__card { overflow: hidden; }
.profile__name-row { display: flex; flex-direction: row; align-items: center; gap: var(--sp-3); padding: var(--sp-3) var(--sp-4); }
.profile__name { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: row; align-items: center; gap: var(--sp-2); min-height: var(--hit); cursor: text; }
.shell__sec--me .me__name {
  flex: 1 1 auto; width: auto; min-width: 0; min-height: var(--hit); box-sizing: border-box; margin: 0;
  padding: var(--sp-2) 0;
  border: 0; border-radius: 0; background: transparent; box-shadow: none;
  color: var(--label-1); caret-color: var(--accent); text-align: left;
  font: 600 var(--fs-title3)/var(--lh-title3) var(--font-display); letter-spacing: -0.1px;
  -webkit-user-select: text; user-select: text; outline: none; touch-action: manipulation;
}
.shell__sec--me .me__name::placeholder { color: var(--label-3); font-weight: 500; }
.profile__pencil { flex: none; display: grid; place-items: center; color: var(--label-3); }
.profile__pencil .ui-ic { width: 20px; height: 20px; }
.profile__name:focus-within .profile__pencil { color: var(--accent); }

/*
 * The colour grid: sixteen colours as two even rows of eight (r2; six a row
 * left an orphan row of four). Each cell is a whole 44 pt target; the colour
 * is a 32 pt disc painted inside it (`background-clip: content-box`, since the
 * painter owns `background-color`), which leaves room for the ring.
 */
.profile__swatches { position: relative; padding: var(--sp-2) 0; }
.profile__swatches::before { content: ""; position: absolute; top: 0; left: var(--sp-4); right: 0; height: 0.5px; background: var(--separator); }
.profile__swatches .swatches {
  display: grid; grid-template-columns: repeat(8, minmax(0, 1fr));
  gap: var(--sp-1) 0; justify-items: center;
}
.profile__swatches .swatch {
  width: 100%; max-width: var(--hit); aspect-ratio: 1; margin: 0; padding: 6px;
  border: 0; border-radius: var(--r-full); background-clip: content-box;
  cursor: pointer; -webkit-tap-highlight-color: transparent; touch-action: manipulation;
  transition: transform var(--dur-press-out) var(--ease-nav), box-shadow var(--dur-press-out);
}
/* Selected: the app's one selection ring, teal, standing 4 pt off the disc so
   it reads around a teal colour too. */
.profile__swatches .swatch.is-mine { box-shadow: inset 0 0 0 2px var(--accent); }
.profile__swatches .swatch:active { transform: scale(0.92); transition-duration: var(--dur-press-in); }
.profile__swatches .swatch:focus-visible { outline: 2px solid var(--accent); outline-offset: 6px; }

/* ---- Rows the frame's row does not draw ------------------------------------- */
/* Settings, r2: an explanation is the subtitle of the switch it explains, whole. */
.ui-row.house__row--wraps { align-items: center; padding-block: var(--sp-3); }
.ui-row.house__row--wraps .ui-row__subtitle { white-space: normal; overflow: visible; overflow-wrap: anywhere; }
/* "Finish setting up": iOS's red count before the chevron. */
.house__badge {
  display: inline-grid; place-items: center; min-width: var(--sp-6); height: var(--sp-6); padding: 0 var(--sp-2);
  border-radius: var(--r-full); background: var(--destructive); color: var(--label-1);
  font: 600 var(--fs-subhead)/1 var(--font-text); font-variant-numeric: tabular-nums;
}
.ui-row__value:has(> .house__badge) { max-width: none; overflow: visible; }
/* Skills: a shared skill's purpose may take two lines, like the house's own. */
.ui-row__subtitle.house__skill-line { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; white-space: normal; overflow-wrap: anywhere; }
a.ui-row { text-decoration: none; }
.house__dot { flex: none; width: var(--sp-2); height: var(--sp-2); margin: 0 var(--sp-1); border-radius: var(--r-full); background: var(--label-3); }
.house__dot[data-on="yes"] { background: var(--success-dot); }
.ui-row__tile.house__avatar { border-radius: var(--r-full); color: var(--on-primary); font: 600 var(--fs-subhead)/1 var(--font-text); }
.ui-row__tile.house__step-tile { font: 600 var(--fs-subhead)/1 var(--font-text); font-variant-numeric: tabular-nums; }
.ui-row.house__step.is-done .ui-row__title { color: var(--label-2); }
/* "Set up AI later" belongs to the step above it, so it starts where that step's title does. */
.ui-row.house__step-aside { padding-left: calc(var(--sp-4) + 29px + var(--sp-3)); }
.house__check { flex: none; display: grid; place-items: center; width: var(--sp-6); color: var(--accent); }
.house__check .ui-ic { width: 20px; height: 20px; }
.house__check .ui-ic path { stroke-width: 2.5; }
.house__technical .ui-row__subtitle { white-space: normal; overflow-wrap: anywhere; -webkit-user-select: text; user-select: text; }

/* ---- Cards: a thing waiting on a person, or a form ------------------------ */
.house__card {
  display: flex; flex-direction: column; gap: var(--sp-3); min-width: 0;
  padding: var(--sp-4); border-radius: var(--r-md); background: var(--bg-elev2);
}
.house__card-title { margin: 0; font: 600 var(--fs-body)/var(--lh-body) var(--font-text); letter-spacing: -0.4px; color: var(--label-1); overflow-wrap: anywhere; }
.house__card-name { margin: 0; font: 650 var(--fs-title3)/var(--lh-title3) var(--font-display); letter-spacing: -0.1px; color: var(--label-1); overflow-wrap: anywhere; }
.house__card-note { margin: 0; font: 400 var(--fs-footnote)/var(--lh-footnote) var(--font-text); color: var(--label-2); overflow-wrap: anywhere; }
:is(.house__card, .house__conn-form) > :is(.ui-btn--plain, .ui-btn--destructive) { align-self: flex-start; margin-left: calc(-1 * var(--sp-2)); }
.house__mono { font-family: ui-monospace, "SF Mono", Menlo, monospace; word-break: break-all; }
.house__recovery { display: block; padding: var(--sp-3) var(--sp-4); border-radius: var(--r-sm); background: var(--bg-elev3); color: var(--label-1); font-size: var(--fs-body); line-height: var(--lh-body); }
.house__pair-error { margin: 0; font: 400 var(--fs-footnote)/var(--lh-footnote) var(--font-text); color: var(--destructive); overflow-wrap: anywhere; }
.house__keys > .house__pair-error, .house__hub > .house__pair-error, .house__page > .house__pair-error { padding: 0 var(--sp-4); }
.house__field {
  width: 100%; box-sizing: border-box; min-height: var(--hit); margin: 0;
  padding: var(--sp-3) var(--sp-4);
  border: 0; border-radius: var(--r-sm); background: var(--bg-elev3); color: var(--label-1);
  font: 400 var(--fs-body)/var(--lh-body) var(--font-text); letter-spacing: -0.4px;
  -webkit-user-select: text; user-select: text; outline: none;
}
.house__field::placeholder { color: var(--label-3); }
.house__field:focus { box-shadow: inset 0 0 0 2px var(--accent); }
.house__field:disabled { opacity: 0.4; }
.house__field-label { margin: 0 0 calc(-1 * var(--sp-1)); font: 400 var(--fs-footnote)/var(--lh-footnote) var(--font-text); color: var(--label-2); }
.house__field.house__pair-code { text-align: center; font: 600 var(--fs-title1)/var(--lh-title1) var(--font-display); letter-spacing: 0.3em; font-variant-numeric: tabular-nums; }

/* ---- Connect: a row that discloses its form inside the same list ---------- */
.house__conn .ui-row__chevron { transition: transform var(--dur-press-out) var(--ease-nav); }
.house__conn[aria-expanded="true"] .ui-row__chevron { transform: rotate(90deg); }
.house__conn[data-conn-state="connected"] .ui-row__subtitle::before {
  content: ""; display: inline-block; width: var(--sp-2); height: var(--sp-2); margin-right: var(--sp-1);
  border-radius: var(--r-full); background: var(--success-dot); vertical-align: 1px;
}
.house__conn[data-conn-state="broken"] .ui-row__subtitle { color: var(--destructive); }
.house__conn-form, .house__conn-extra { display: flex; flex-direction: column; gap: var(--sp-3); padding: var(--sp-1) var(--sp-4) var(--sp-4); }
.ui-list > :is(.house__conn-form, .house__conn-extra)::before { display: none; }

/* ---- Routines: a timeline, not cards (C.5) -------------------------------- */
.ui-row.house__routine { align-items: stretch; gap: var(--sp-3); padding: 0 var(--sp-4) 0 0; }
.ui-row.house__routine > .ui-switch { align-self: center; }
.house__routine-main {
  flex: 1 1 auto; min-width: 0;
  display: flex; align-items: center; gap: var(--sp-3);
  margin: 0; padding: var(--sp-3) 0 var(--sp-3) var(--sp-4);
  border: 0; border-radius: 0; background: transparent; color: inherit; text-align: left; font: inherit;
  cursor: pointer; -webkit-tap-highlight-color: transparent;
}
.ui-row.house__routine:has(> .house__routine-main:active) { background: var(--bg-elev3); }
.house__routine-main:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.house__when { flex: none; width: calc(var(--sp-14) + var(--sp-2)); display: flex; flex-direction: column; align-items: flex-start; gap: 2px; }
.house__when-top { font: 600 var(--fs-body)/var(--lh-body) var(--font-text); letter-spacing: -0.4px; font-variant-numeric: tabular-nums; color: var(--label-1); white-space: nowrap; }
.house__when-top .ui-ic { width: 22px; height: 22px; }
.house__when-sub { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font: 400 var(--fs-footnote)/var(--lh-footnote) var(--font-text); color: var(--label-2); }
.house__routine-title, .house__routine-line { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; overflow-wrap: anywhere; }
.house__routine-title { font: 600 var(--fs-body)/var(--lh-body) var(--font-text); letter-spacing: -0.4px; color: var(--label-1); }
.house__routine-line { font: 400 var(--fs-subhead)/var(--lh-subhead) var(--font-text); letter-spacing: -0.2px; color: var(--label-2); }
.house__routine-line[data-state="failed"] { color: var(--destructive); }
.house__routine[data-enabled="no"] :is(.house__when-top, .house__routine-title) { color: var(--label-2); }
.ui-row.house__prose {
  display: block; margin: 0; padding: var(--sp-3) var(--sp-4);
  font: 400 var(--fs-body)/var(--lh-body) var(--font-text); letter-spacing: -0.4px; color: var(--label-1);
  white-space: pre-wrap; overflow-wrap: anywhere; -webkit-user-select: text; user-select: text;
}

/* ---- The confirmation sheet: as tall as what it says ---------------------- */
.house-confirm .ui-sheet { top: auto; }
.house-confirm .ui-sheet > .ui-navbar { display: none; }
.house-confirm .ui-sheet__body { flex: none; gap: var(--sp-6); padding-top: var(--sp-5); }
.house-confirm__words { display: flex; flex-direction: column; gap: var(--sp-2); padding: 0 var(--sp-2); text-align: center; }
.house-confirm__title { margin: 0; font: 600 var(--fs-body)/var(--lh-body) var(--font-text); letter-spacing: -0.4px; color: var(--label-1); }
.house-confirm__note { margin: 0; font: 400 var(--fs-footnote)/var(--lh-footnote) var(--font-text); color: var(--label-2); }
.house-confirm__keys { display: flex; flex-direction: column; gap: var(--sp-3); }

/* ---- Help & feedback, as a page of grouped fields -------------------------- */
.feedback.house__help { display: flex; flex-direction: column; gap: var(--sp-4); padding: 0; }
.house__help > h2 { display: none; }
.house__help > p:not(.feedback__privacy) { margin: 0; padding: 0 var(--sp-4); font: 400 var(--fs-subhead)/var(--lh-subhead) var(--font-text); color: var(--label-2); }
.house__help .feedback__form { display: flex; flex-direction: column; gap: var(--sp-2); margin: 0; }
.house__help .feedback__form > label { margin: var(--sp-3) var(--sp-4) 0; font: 400 var(--fs-footnote)/var(--lh-footnote) var(--font-text); color: var(--label-2); }
.house__help .feedback__form > label:first-child { margin-top: 0; }
.house__help .feedback__form :is(input, textarea, select) {
  width: 100%; box-sizing: border-box; min-height: var(--hit); margin: 0;
  padding: var(--sp-3) var(--sp-4);
  border: 0; border-radius: var(--r-md); background: var(--bg-elev2); box-shadow: none; color: var(--label-1);
  font: 400 var(--fs-body)/var(--lh-body) var(--font-text); letter-spacing: -0.4px; outline: none;
}
.house__help .feedback__form :is(input, textarea):focus { box-shadow: inset 0 0 0 2px var(--accent); }
.house__help .feedback__form textarea { min-height: calc(var(--sp-14) * 2 + var(--sp-8)); resize: none; }
.house__help .feedback__form > select[hidden] { display: none; }
.house__help .feedback__privacy { margin: 0; padding: 0 var(--sp-4); font: 400 var(--fs-footnote)/var(--lh-footnote) var(--font-text); color: var(--label-2); }
.house__help .feedback__form .btn--go {
  min-height: 50px; margin-top: var(--sp-3);
  border: 0; border-radius: var(--r-full); background: var(--fill-primary); color: var(--on-primary); box-shadow: none;
  font: 600 var(--fs-body)/var(--lh-body) var(--font-text); letter-spacing: -0.4px;
}
.house__help .feedback__status:empty { display: none; }
/* Terms · Privacy: two plain links with a 44 pt target each, not 16 pt underlines. */
.house__help .house__help-links { display: flex; align-items: center; gap: var(--sp-1); padding: 0 var(--sp-2); }
.house__help .feedback__privacy a { display: inline-flex; align-items: center; min-height: var(--hit); padding: 0 var(--sp-2); color: var(--accent); text-decoration: none; }
/* Inside the sheet the lists are elev2, so a control drawn ON a row steps up once more. */
.ui-sheet :is(.ui-list, .house__card) .ui-btn--secondary { background: var(--bg-elev3); }
.house__help .ui-seg { background: var(--bg-elev2); }
/* A step's "Done" is for assistive tech; the green check tile says it to the eye. */
.house__step .house__tag { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }


/* ==========================================================================
   THE CONTROLLER AND THE DOOR (controller + door lane, round 2, 2026-09-14)
   ==========================================================================
   critique-r1 B13 scored these 34 and 31: the old pill chrome over a bordered
   pad, a Start bar at the top where no thumb is, "MOVE" in tracked capitals,
   a red-rimmed B that read as destructive, and a first-run screen that was
   367 pt of black. Everything below is tokens and the app's own components;
   the rules it replaced were deleted, not overridden. */

/* -- the bar: game · who · "…" ------------------------------------------- */
.pad-bar {
  flex: none; position: relative; z-index: 2;
  display: flex; align-items: center; gap: var(--sp-2);
  min-width: 0; min-height: var(--hit); box-sizing: border-box;
  padding: 0 var(--sp-2) 0 var(--page-margin);
}
.pad-bar__words { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; justify-content: center; }
.pad-bar__title {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font: 600 var(--fs-body)/var(--lh-body) var(--font-text); letter-spacing: -0.4px; color: var(--label-1);
}
.pad-bar__title:empty { display: none; }
.pad-bar__who {
  display: flex; align-items: center; gap: 6px; min-width: 0;
  font: 400 var(--fs-caption)/var(--lh-caption) var(--font-text); color: var(--label-2);
}
.pad-bar__who-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pad-bar__dot { flex: none; width: 8px; height: 8px; border-radius: var(--r-full); background: var(--label-3); }
.pad-bar__spin {
  display: none; flex: none; width: 10px; height: 10px; box-sizing: border-box;
  border: 1.5px solid currentColor; border-top-color: transparent; border-radius: var(--r-full);
  animation: spin 900ms linear infinite;
}
/* Not live: the dot becomes the spinner and the name becomes the sentence, in
   the same place — one picture of "reconnecting", never over a key. */
.pad-bar__who:not([data-link="live"]) .pad-bar__dot { display: none; }
.pad-bar__who:not([data-link="live"]) .pad-bar__spin { display: block; }
.pad-bar__who[data-link="link-stalled"] { color: var(--destructive); }
.pad-bar__more { width: var(--hit); height: var(--hit); background: transparent; }
.pad-bar__more .ui-ic { width: 24px; height: 24px; }
.pad-bar__more::after { inset: 0; }
/* The corner mark's news, while the mark stands down for this bar. */
.pad-bar__badge {
  display: none; position: absolute; top: 9px; right: 8px;
  width: 8px; height: 8px; border-radius: var(--r-full); background: var(--accent);
  box-shadow: 0 0 0 2px var(--bg-base);
}
.pad-bar__more:is([data-badge="work"], [data-badge="note"], [data-badge="failed"], [data-badge="unknown"]) .pad-bar__badge { display: block; }
.pad-bar__more[data-badge="failed"] .pad-bar__badge { background: var(--destructive); }
.pad-bar__more[data-badge="unknown"] .pad-bar__badge { background: var(--label-3); }

/* -- the menu behind "…": sized to its rows, not to a detent ----------------- */
.pad-menu .ui-sheet[data-detent="medium"]:not([data-resizable]) {
  top: auto; max-height: calc(100% - env(safe-area-inset-top, 0px) - var(--sp-2));
}
.pad-menu .ui-sheet__body { flex: 0 1 auto; gap: var(--sp-5); }
.pad-menu .players { position: relative; }
.pad-menu .players[hidden] { display: none; }
.pad-menu .players__add:disabled { opacity: 1; }
.pad-menu .players__add:disabled .ui-row__title { color: var(--label-3); }
.pad-menu .players[data-state="adding"] .ui-row__title { color: var(--label-2); }
.players__note[hidden] { display: none; }

/* The invitation to add a name, while this seat has none: the app's compact
   Secondary capsule, on the page margin under the bar. */
.play__seat { flex: none; padding: 0 var(--page-margin) var(--sp-2); }
.play__seat .chip { min-height: var(--hit); padding: 0 var(--sp-4) 0 var(--sp-3); border: 0; background: var(--bg-elev2); font: 600 var(--fs-subhead)/var(--lh-subhead) var(--font-text); }
.play__seat .chip .chip__dot { width: 10px; height: 10px; }
.play__seat .chip[data-named="0"] .chip__name { color: var(--accent); }
.play__seat .chip[data-named="0"]::after { color: var(--accent); }

/* -- the frame around a package's controls ----------------------------------- */
.play > .surface { gap: var(--sp-3); padding: 0 var(--page-margin) calc(var(--sp-4) + var(--safe-b)); }
/* Start, Ready, Pause, Again: the one Primary capsule, at the bottom where the
   thumb is — the heaviest thing on the controller used to sit at the top. A
   second system key beside it is the Secondary capsule, as equal halves. */
.surface > .controller-system { order: 2; flex: none; display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: var(--sp-3); }
.surface .controller-system .ctl--button {
  width: auto; height: 50px; min-height: 50px; border: 0; border-radius: var(--r-full);
  background: var(--fill-primary); color: var(--on-primary); box-shadow: none;
}
.surface .controller-system .ctl--button + .ctl--button { background: var(--bg-elev2); color: var(--label-1); }
.surface .controller-system .button__face { padding: 0 var(--sp-4); font: 600 var(--fs-body)/var(--lh-body) var(--font-text); letter-spacing: -0.4px; white-space: nowrap; }
.surface .controller-system .ctl--button.is-down { transform: scale(0.97); filter: none; box-shadow: none; background: #dcd7cd; }
/* The pad is not a card: no outline, no fill, the controls on the page. */
.surface .field__row { padding: 0; border: 0; border-radius: 0; background: none; box-shadow: none; }

/* A package's labels, in sentence case at footnote size (D.1 bans capitals). */
.surface .ctl__label { text-transform: none; letter-spacing: -0.1px; font: 500 var(--fs-footnote)/var(--lh-footnote) var(--font-text); color: var(--label-2); }
.surface .ctl--stick > .ctl__label { font-size: var(--fs-footnote); }

/* Action buttons: one material. A neutral key is a lit disc; `go` (and the
   first key of a scheme that declares no tone) is the accent; `stop` is a
   warm tint, not red — a brake, not a delete; `special` is this player's
   colour. No rim, no ledge. */
.surface .ctl--button {
  border: 0; color: var(--label-1);
  background: rgba(255, 255, 255, 0.11);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.10), 0 6px 16px rgba(0, 0, 0, 0.28);
}
.surface .zone--right > .ctl--button { border-width: 0; }
.surface .ctl--button .button__face { font: 600 var(--fs-title3)/var(--lh-title3) var(--font-text); letter-spacing: -0.2px; }
.surface .zone > .ctl--button[data-tone="go"],
.surface .zone--right > .ctl--button:first-of-type:not([data-tone]) { background: var(--accent); color: var(--on-primary); }
/* `stop` draws as the neutral key: on a pad it is a brake or a back, never a delete. */
.surface .zone > .ctl--button[data-tone="stop"] { background: rgba(255, 255, 255, 0.11); color: var(--label-1); }
.surface .zone > .ctl--button[data-tone="special"] { background: color-mix(in srgb, var(--me) 55%, transparent); color: var(--label-1); }
.surface .ctl--button.is-down { transform: scale(0.93); filter: brightness(1.3); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.10); }
.surface .zone--left.has-direction-buttons .ctl--button { background: rgba(255, 255, 255, 0.11); }

/* The stick: a quiet well and a knob in this player's colour — the same
   colour as the dot in the bar and the player on the television. */
.surface .stick__base {
  background: rgba(255, 255, 255, 0.05); border: 0;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.08), inset 0 8px 24px rgba(0, 0, 0, 0.35);
}
.surface .stick__knob {
  background: radial-gradient(circle at 40% 30%, rgba(255, 255, 255, 0.28), transparent 62%), var(--me, var(--accent));
  border: 0; box-shadow: 0 8px 20px rgba(0, 0, 0, 0.45);
}
.surface .ctl--dpad { border: 0; background: none; box-shadow: none; }
.surface .dpad__arm { background: rgba(255, 255, 255, 0.11); border: 0; box-shadow: none; }
.surface .dpad__arm.is-down { background: var(--me, var(--accent)); }
.surface .dpad__hub { background: rgba(255, 255, 255, 0.11); }

@media (orientation: landscape) and (max-height: 600px) {
  /* Sideways the capsule is a centred key under the pad, not a full-width bar
     that would take a seventh of a 390 pt glass. */
  .surface > .controller-system { justify-self: center; align-self: center; width: min(360px, 60%); }
  .surface .controller-system .ctl--button { height: var(--hit); min-height: var(--hit); }
  /* Discs sideways too: the older landscape rule squared them off. */
  .surface .zone--right > .ctl--button { border-radius: var(--r-full); width: auto; height: 100%; max-height: 96px; aspect-ratio: 1; justify-self: center; }
  .play > .surface { gap: var(--sp-2); padding-bottom: calc(var(--sp-2) + var(--safe-b)); }
}

/* -- trouble: a phone that cannot reach the house --------------------------- */
.bigcard.trouble { gap: var(--sp-3); width: 100%; box-sizing: border-box; padding: var(--sp-6) var(--page-margin) calc(var(--sp-6) + var(--safe-b)); }
.trouble__house { font: 700 var(--fs-title1)/var(--lh-title1) var(--font-display); letter-spacing: -0.3px; color: var(--label-1); overflow-wrap: anywhere; }
.bigcard.trouble .bigcard__state { font: 400 var(--fs-body)/var(--lh-body) var(--font-text); color: var(--label-2); }
.trouble__keys { display: flex; flex-direction: column; gap: var(--sp-3); width: 100%; max-width: 360px; margin-top: var(--sp-4); }

/* -- the door ------------------------------------------------------------------ */
.lobby:has(> .now[data-stage="name"]) { padding-bottom: 0; overflow: hidden; }
/* One question: no avatar in the corner of a person who has no name yet, and
   no second copy of the house's name once the first letter names them. */
.lobby:has(> .now[data-stage="name"]) > :is(.ui-lt, .now-house) { display: none; }
.lobby > .now[data-stage="name"] { flex: 1 1 auto; min-height: 0; gap: 0; padding: 0; }
.door { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; background: var(--bg-base); }
/* The picture and the words: this half gives up its height to the keyboard
   (`#app` is sized to visualViewport), so the field and Join never go under it. */
.door__top {
  position: relative; isolation: isolate; container-type: size;
  flex: 1 1 auto; min-height: 0; overflow: hidden;
  display: flex; flex-direction: column; justify-content: flex-end;
  padding: var(--sp-8) max(var(--page-margin), var(--safe-r)) var(--sp-5) max(var(--page-margin), var(--safe-l));
}
.door__art { position: absolute; inset: 0; z-index: -1; overflow: hidden; }
.door__img { display: block; width: 100%; height: 100%; object-fit: cover; object-position: center; }
.door__art::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(11, 13, 16, 0.35) 0%, rgba(11, 13, 16, 0) 30%, rgba(11, 13, 16, 0.55) 62%, var(--bg-base) 100%);
}
/* No picture: the house's mark in a low teal glow, not a black void. */
.door[data-art="no"] .door__art { background: radial-gradient(90% 60% at 50% 34%, rgba(79, 209, 176, 0.16), transparent 70%); }
.door__mark { position: absolute; left: 50%; top: 36%; width: 96px; height: 96px; transform: translate(-50%, -50%); color: var(--label-1); opacity: 0.92; }
.door__mark .homie-brand-mark, .door__mark svg { width: 100%; height: 100%; display: block; }
.door__words { display: flex; flex-direction: column; align-items: flex-start; gap: var(--sp-2); min-width: 0; }
.door__kicker { margin: 0; font: 600 var(--fs-subhead)/var(--lh-subhead) var(--font-text); color: var(--label-2); }
.door__house {
  margin: 0; max-width: 100%; overflow-wrap: anywhere;
  font: 700 var(--fs-large-title)/var(--lh-large-title) var(--font-display); letter-spacing: -0.4px; color: var(--label-1);
  text-shadow: 0 1px 12px rgba(11, 13, 16, 0.45);
}
.door__kicker + .door__house { margin-top: calc(-1 * var(--sp-2)); }
.door__here, .door__link {
  display: flex; align-items: center; gap: var(--sp-2); margin: 0; min-width: 0; max-width: 100%;
  font: 400 var(--fs-subhead)/var(--lh-subhead) var(--font-text); letter-spacing: -0.2px; color: var(--label-2);
}
.door__faces { flex: none; display: flex; padding-left: 6px; }
.door__face {
  width: 28px; height: 28px; margin-left: -6px; border-radius: var(--r-full);
  display: grid; place-items: center; box-shadow: 0 0 0 2px var(--bg-base);
  font: 600 var(--fs-footnote)/1 var(--font-text); color: #fff; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.35);
}
.door__face::before { content: attr(data-initial); }
.door__name + .door__name::before { content: ", "; }
.door__here-word::before { content: " "; }
.door__here-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.door__link[hidden] { display: none; }
.door__link[data-state="link-stalled"] { color: var(--destructive); }
.door__spin {
  flex: none; width: 12px; height: 12px; box-sizing: border-box;
  border: 2px solid currentColor; border-top-color: transparent; border-radius: var(--r-full);
  animation: spin 900ms linear infinite;
}
/* Squeezed by a keyboard: the lines that are nice to know go first, the
   house's name stays. */
@container (max-height: 200px) {
  .door__kicker, .door__here, .door__mark { display: none; }
  .door__house { font-size: var(--fs-title2); line-height: var(--lh-title2); }
}
.door__form {
  flex: none; display: flex; flex-direction: column; gap: var(--sp-3); width: auto;
  padding: var(--sp-2) max(var(--page-margin), var(--safe-r)) calc(var(--sp-4) + var(--safe-b)) max(var(--page-margin), var(--safe-l));
}
.door__q { font: 600 var(--fs-title3)/var(--lh-title3) var(--font-display); letter-spacing: -0.1px; color: var(--label-1); }
.door__field {
  box-sizing: border-box; width: 100%; min-width: 0; min-height: 50px; padding: 0 var(--sp-4);
  border: 0; border-radius: var(--r-sm); background: var(--bg-elev2); color: var(--label-1);
  font: 400 var(--fs-body)/var(--lh-body) var(--font-text);
}
.door__field::placeholder { color: var(--label-3); opacity: 1; }
.door__field:focus { outline: 2px solid var(--accent); outline-offset: 0; }
@media (orientation: landscape) and (max-height: 500px) {
  /* Sideways, the picture and the form sit side by side. */
  .door { flex-direction: row; }
  .door__top { flex: 1 1 55%; }
  .door__form { flex: 1 1 45%; justify-content: center; padding-top: var(--sp-4); }
}
@media (prefers-reduced-motion: reduce) {
  .pad-bar__spin, .door__spin { animation: none; border-top-color: currentColor; opacity: 0.6; }
}
