/* hub-boot: the boot-sequence hero. One rAF clock in hub-boot.js drives every
   beat; this sheet holds layout and rest states.
   Tokens copied from site.css :root (colors, spacing, radii, durations, eases). */

/* STAGE-ONLY SHEET. It loads beside site.css and hero.css on / (the promoted
   hero, 2026-09-06), so it must not carry page-level rules: the mock page's
   own frame (its :root tokens, resets, .hero/.hero-copy/.sub/.sb-btn/.foot)
   lives inline in site/home-hub-boot.html. The eleven tokens below are the
   only ones the stage needs that site.css and hero.css do not already define;
   the
   rest (--bg, --fg, --muted, --line, --accent, --accent-ink, --danger, --warn,
   --stage, --window, --font-mono, --sp-*, --r-*, --dur-*, --ease-*) come from
   the host page. (A leaked `.hero h1 { max-width: 18ch }` from here is what
   capped the lander's headline to three lines.) */
:root { --win: #0d0d0d; --side: #0d0d0d; --surface: #181818; --ink: #000000; --ui: var(--font-ui); --line-strong: #5c5c5c; --sb-icon: url(/site/assets/brand/superbot-app-icon.png); } /* the miniature's surfaces sit on the site ladder (site.css: canvas #000000, card #0d0d0d, raised #181818); --ink is the DARK ink the vendor avatar chips and the panel chips print with; --line-strong is Mobile's lineStrong (the dashed new-chat outline); --sb-icon is the superbot tile's app icon, the one url the HUD mark's .ground layer and the <img class="tile"> both load */
/* vendor brand pairs, category coding only (vendor identity, the one place a
   hex off the site ladder is allowed): --brand-<vendor> is the tile ground,
   --brand-<vendor>-ink the mark on it. Dark-UI variants as the app shows them
   (cursor prints the mark WHITE on black; openai and gemini are the inverse,
   black on white, per superbot-desktop brand.tokens.json "openai"). */
:root {
  --ok: #34d399; /* status green: the online dot (the amber/red pair's third member) */
  /* the app's one filled action ramp, used by the header's Ask Superbot (the
     sidebar's New chat wore a fill until superbot-desktop fc132325b; it is a
     dashed list row now). The referent's own two stops; the only hex off the
     site ladder this sheet adds, on the owner's instruction (2026-09-12). */
  --go: linear-gradient(135deg, #3b5bff 0%, #7c3aed 100%);
  --brand-openai: #ffffff;    --brand-openai-ink: #000000;
  --brand-claude: #d97757;    --brand-claude-ink: #ffffff;
  --brand-gemini: #ffffff;    --brand-gemini-ink: #1f1f1f;
  --brand-cursor: #000000;    --brand-cursor-ink: #ffffff;
  --brand-vscode: #0078d4;    --brand-vscode-ink: #ffffff;
  --brand-zed: #084ccf;       --brand-zed-ink: #ffffff;
  --brand-windsurf: #0b100f;  --brand-windsurf-ink: #58e5bb;
  --brand-copilot: #24292f;   --brand-copilot-ink: #ffffff;
}

/* ---------- the terminal stage ---------- */
.stage { position: relative; aspect-ratio: 16 / 10; background: var(--stage); border: 1px solid var(--line); border-radius: var(--r-xl); overflow: hidden; display: grid; grid-template-rows: 28px minmax(0, 1fr); box-shadow: 0 14px 28px rgba(0,0,0,.24), 0 0 8px rgba(0,0,0,.2); max-width: none; container: stage / inline-size; } /* max-width: none — hero.css caps .stage at --content (720px), which would squeeze the promoted stage on lander-agent; container lets the narrow-stage rules below key off the STAGE width, not the viewport */
.stage-bar { position: relative; display: flex; align-items: center; gap: 6px; padding: 0 var(--sp-3); border-bottom: 1px solid var(--line); font-size: 12px; color: var(--muted); } /* position: relative — hero.css's .stage-bar is position:absolute (its cards float over the stage); absolute here would pull the bar out of the stage grid and drop .body into the 28px row */
.stage-bar i { width: 10px; height: 10px; border-radius: 50%; background: var(--line); }
.stage-bar span { margin-left: var(--sp-2); }
.stage-bar .led { margin-left: auto; width: 8px; height: 8px; border-radius: 50%; background: var(--line); }
.stage-bar .led.on { background: var(--accent); box-shadow: 0 0 8px var(--accent); }
.body { position: relative; display: grid; grid-template-columns: 21% minmax(0, 1fr); min-height: 0; grid-row: 2; } /* grid-row: 2 pins the body to the stage's second row even if the bar ever leaves the flow */

/* margin log: the terminal's own transcript; the mascot parks here at the end */
.margin { position: relative; border-right: 1px solid var(--line); padding: var(--sp-3); font-size: 12px; line-height: 18px; color: var(--muted); overflow: hidden; white-space: pre-wrap; word-break: break-word; }
.margin .ln { display: block; white-space: pre; overflow: hidden; text-overflow: ellipsis; }
.margin .ln.cmd { color: var(--fg); }
.margin .ln.kv { padding-left: 1.2em; }
.margin .ln.kv b { color: var(--fg); font-weight: 650; }
.margin .ln.done::before { content: "✓ "; color: var(--accent); }
.margin .ln.gap { height: 9px; }
.margin .caret { display: inline-block; width: 7px; height: 14px; vertical-align: -2px; background: var(--accent); animation: caret 1s steps(2, jump-none) infinite; /* deliberate */ }
@keyframes caret { to { opacity: 0; } }

/* arena: everything animated lives here, positioned from its center */
.arena { position: relative; overflow: hidden; }
.arena > * { position: absolute; }

.arena .boot-mascot { left: 50%; top: 50%; width: clamp(96px, 15vw, 210px); aspect-ratio: 1; transform: translate(-50%, -50%); z-index: 3; } /* the mascot is the vector mark (markSvg() + mascot-mark.css), not the retired ASCII cat; its own class, so hero.css's legacy .stage .mascot rules (bottom-anchored glyph typography) never reach it. The box IS the mark: layout() centres the bubble above it and the keys under it from its rect */
/* the rig is the one element hub-boot.js's pose() transforms (rise, plump,
   squash, glance, typing dip); it scales from his feet (y 86 of the 100-unit
   viewBox), so he rises up out of the flat line drawn there */
.boot-mascot .mark-rig { position: absolute; inset: 0; display: block; transform-origin: 50% 86%; opacity: 0; }
.boot-mascot .boot-mark { width: 100%; height: 100%; }
/* the flat line he boots out of (the old glyph row), with the light that runs it */
.boot-mascot .mark-flat { position: absolute; left: 50%; top: 86%; width: 150%; height: 2px; margin-top: -1px; border-radius: 1px; background: rgb(255 255 255 / .55); transform: translateX(-50%); transform-origin: 50% 50%; }
.boot-mascot .mark-flat b { position: absolute; left: 0; top: -1px; width: 16.667%; height: 4px; border-radius: 2px; background: var(--accent); box-shadow: 0 0 8px var(--accent); opacity: 0; }
/* expressions on the story clock (pose() sets data-expr): the hello wink and the
   ^_^ of the gulp landing (the same arcs mascot-mark.css shows on a click) */
.boot-mascot .mark-eye { transform-box: fill-box; transform-origin: 50% 50%; transition: transform 90ms ease-out; }
.boot-mascot[data-expr="wink"] .mark-eye-r { transform: scaleY(.1); }
.boot-mascot[data-expr="happy"] .mark-eye { opacity: 0; }
.boot-mascot[data-expr="happy"] .mark-happy { opacity: 1; }
/* parked: after the story he sits in the terminal margin, beside the hub, still
   idling (mascot-mark.css's bob/breath/ears, the blink and the pupils). Parking
   moves him OUT of .arena, so the size is restated here */
.boot-mascot.parked { width: clamp(56px, 8vw, 116px); aspect-ratio: 1; }
.margin .boot-mascot.parked { position: absolute; left: 50%; top: auto; bottom: 46px; transform: translateX(-50%); }
.margin .sb-keys.parked { top: auto; bottom: 22px; }

/* ---------- the `perch` cut (ship, perch, bloom, tour, full) ----------
   At the end the terminal column collapses to nothing and the arena takes the
   whole stage, while the mascot is lent OUT of the stage to the anchor the host
   page puts beside its headline. The width change is a layout animation on
   purpose: the collapse IS the beat, and 700ms reads as a settle rather than a
   snap (design-motion's 300ms cap covers incidental motion, not the one gesture
   a variant exists for). These two selectors are the only rules in this
   stage-only sheet that reach outside .stage; they style the mascot the stage
   lends, never anything the host page owns. */
.body { transition: grid-template-columns 700ms var(--ease-out); } /* deliberate */
.stage.perched .body { grid-template-columns: 0% minmax(0, 1fr); }
.stage.perched .margin { opacity: 0; padding: 0; border-right: 0; transition: opacity var(--dur-4) var(--ease-std); }
/* (the old perch anchor beside the headline is gone: the brand tile sits
   there now and the mascot fades with the terminal, owner 2026-09-08) */
/* play again (2026-09-26, the user: "somewhere less invasive"): a bare muted
   glyph at the right end of the window's title bar, after the led, for every
   arm. It used to be a 44px accent disc centred on the stage's foot, with the
   hub lifted 48px off the foot to make it a strip; now the hub keeps its inset
   and nothing sits over the app. The button is the bar's full 28px height and
   --target-sm wide (28px on a mouse); under a coarse pointer --target-sm is
   44px and ::after carries the hit area 16px down into the stage, since the
   stage clips anything above the bar. The bar stacks over .body so that strip
   reaches the finger. [hidden] until the story reaches its hold. */
.stage-bar { z-index: 10; }
.replay-park { position: relative; flex: none; width: 28px; height: 28px; margin-right: calc(-1 * var(--sp-1)); padding: 0; border: 0; border-radius: var(--r-control); background: none; color: var(--muted); font: 13px/28px var(--font-mono); text-align: center; cursor: pointer; transition: color var(--dur-2) var(--ease-std); }
.replay-park::after { content: ""; position: absolute; top: 0; left: calc((100% - var(--target-sm)) / 2); width: var(--target-sm); height: var(--target-sm); }
.replay-park:focus-visible { color: var(--accent); outline-offset: -2px; }
/* the hover tint sits inside (hover:hover): the button is [hidden] until the
   story reaches its hold, and an unqualified :hover rule made the mobile matrix
   read the hidden button as a hover-only reveal (measured 2026-09-17). A finger
   never hovers; the button simply appears when it is armed. */
@media (hover: hover) { .replay-park:hover { color: var(--accent); } }
.replay-park[hidden] { display: none; }

/* speech bubble: centred above the face (layout() sets left/top), wraps, and
   never wider than the arena, so it cannot run off the edge */
.bubble { left: 50%; top: 20%; transform: translate(-50%, -100%); max-width: calc(100% - 32px); width: max-content; text-align: center; font-size: 13px; line-height: 18px; padding: 6px 10px; background: var(--window); border: 1px solid var(--line); border-radius: var(--r-lg); border-bottom-left-radius: 2px; color: var(--fg); white-space: normal; overflow-wrap: anywhere; opacity: 0; z-index: 4; }
.bubble .caret { display: inline-block; width: 6px; height: 12px; vertical-align: -1px; background: var(--accent); margin-left: 2px; }

.scan { left: 0; right: 0; top: 0; height: 16px; font: 12px/16px var(--font-mono); color: var(--accent); white-space: nowrap; overflow: hidden; opacity: 0; letter-spacing: 0; text-shadow: 0 0 10px var(--accent); mask-image: linear-gradient(90deg, transparent, #000 15%, #000 85%, transparent); z-index: 2; }

/* inventory: app tiles and chips, moved only by transform */
#items { inset: 0; } /* the layer must span the arena or the items' 50% anchors resolve against a zero box */
.item { position: absolute; left: 50%; top: 50%; opacity: 0; will-change: transform; z-index: 3; }
.item.app { width: 64px; height: 64px; margin: -32px 0 0 -32px; border-radius: 16px; background: var(--window); border: 1px solid var(--line); display: grid; place-items: center; color: var(--fg); box-shadow: 0 8px 16px rgba(0,0,0,.28); }
.item.app svg { width: 34px; height: 34px; }
.item.chip { height: 30px; margin: -15px 0 0 -70px; padding: 0 12px; border-radius: var(--r-pill); background: var(--window); border: 1px solid var(--line); color: var(--fg); font-size: 13px; line-height: 28px; white-space: nowrap; }
.item.chip b { color: var(--accent); font-weight: 650; }

/* the miniature's 11px base is the desktop scene's own scale; on a touch
   surface at tablet width (iPad Mini 744, iPad Pro 11 834) the same labels
   are body copy a reader is meant to read, and the mobile matrix measures
   them against the 12px floor (10.92px search/ask, 11px blank-tx, measured
   2026-09-17). Above 1100 the scene is unchanged — it is a picture there. */
@media (pointer: coarse) and (max-width: 1400px) {
  #hub .chat-head .search { font-size: 12px; }
  #hub #feed .blank-tx { font-size: 14px; line-height: 20px; }
  /* the chat-list column reads at 11px / 10.01px / 9.02px off the miniature's
     11px base; on a coarse pointer those are the reader's text, not a scaled
     picture, and the matrix flagged 29-32 nodes under 12px on the landscape
     tablets (1080-1376px wide, 2026-09-17). The base font-size stays 11px so
     the mock's geometry does not move; only these runs take the floor. */
  #hub .chats .row, #hub .chats .row .t, #hub .chats .row em, #hub .chats .row .pv,
  #hub .chats .lane-tab, #hub .chats .today { font-size: 12px; line-height: 16px; }
  /* the same floor for the user row (11.0044px at iPad Mini landscape,
     2026-09-17) */
  #arena .chats .acct b, #arena .chats .acct .acct-plan, #arena .chats .acct .upg { font-size: 12px; line-height: 15px; }
  /* and the Servers menu's rows, which read at the same 11px / 9px ladder */
  #hub .hud-menu-tx b, #hub .hud-menu-tx small { font-size: 12px; line-height: 16px; }
  /* the open thread (#feed) rides the same 11px base: the bubble, the reply
     and its list read at 11px, the fold/worked meta at .91em (10.01px) and
     the date rule at .82em (9.02px) on every touch device in the matrix
     (2026-09-23). The shrink's source is the em ladder, not a transform (the
     hub rests at scale 1), so the thread's runs take the 12px floor here; the
     muted colour keeps the meta below the reply. */
  #hub #feed .bub, #hub #feed .m-text, #hub #feed .m-list,
  #hub #feed .worked, #hub #feed .date-div { font-size: var(--fs-xs); line-height: var(--lh-xs); }
}

/* the badge: logo.svg, popped out of the crushed mascot */
.badge { left: 50%; top: 50%; width: 88px; height: 88px; margin: -44px 0 0 -44px; opacity: 0; z-index: 5; filter: drop-shadow(0 8px 16px rgba(0,0,0,.4)); }
.badge img { display: block; width: 100%; height: 100%; }

/* ---------- the hub: the real app's layout ---------- */
/* the hub is ONE miniature of the real app, so its type ladder rides em off
   this 11px base rather than a scatter of px values: rescale the scene from
   this line and every label follows. The 12px design floor is a rule for
   real UI; the sizes declared below are the miniature's, relative to this
   base (.82em reads 9px, .91em reads 10px, 1em reads 11px). */
.hub { inset: 8px; right: 8px; background: var(--win); border: 1px solid var(--line); border-radius: var(--r-lg); overflow: hidden; display: grid; grid-template-columns: 264px minmax(0, 1fr); font: 11px/15px var(--ui); opacity: 0; transform-origin: 0 0; z-index: 6; } /* two tracks: the sidebar, then the main lane. The sidebar track is the old rail and sidebar tracks together (56 + 208), which were taken as RATIOS off the owner's capture of the GUI (2026-09-12: rail 84 of 1472 = 5.7%, sidebar 330 = 22.4%) and rendered at the miniature's scale; since 2026-09-26 the app has no rail column and its sidebar opens with the HUD, so the one track keeps the same share of the window. The health panel exists in the app but opens collapsed, so the hub draws none and the second track is the whole main lane. */
.hub > * { min-width: 0; min-height: 0; display: flex; flex-direction: column; }
.hub .inner { opacity: 0; }
.hub .cap { font-size: .82em; line-height: 12px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); }
.hub .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--accent); }
.hub .dot.warn { background: var(--warn); }
.hub .dot.bad { background: var(--danger); }
.hub .dot.off { background: var(--line); }
.hub .act { display: inline-block; padding: 0 7px; border-radius: var(--r-control); background: var(--raised); border: 1px solid var(--line); color: var(--fg); font-size: .91em; line-height: 16px; }

/* ---------- the sidebar and its HUD ----------
   The app has no rail column since 2026-09-26: the sidebar is the first track
   and opens with the HUD (superbot-desktop hub/SidebarHud.tsx,
   sidebar-hud.css). .side holds the HUD and then the chat list (.chats).
   Sizes are the app's tokens at the miniature's 11/14 scale, in em off the
   11px base: the --size-side-hud-row 56 row is the 44px mark, the
   --spacing-24 gap 1.7em, the --text-wordmark 22/28 line 1.57em/2em. The
   discs keep the app's proportions to the 44px mark (48 : 24): the
   --size-avatar-sm disc is 2em, each next disc steps back a third of it
   (--spacing-8, .67em) and the --size-ring cutout is .17em. The top band is
   the app's window clearance, scaled. Both .hud and .chats carry the sidebar's ground
   and its right hairline, so hub-handoff.js can hold the HUD's ground back
   the way it held the rail's. */
.side > .hud { flex: none; position: relative; display: flex; align-items: center; gap: 1.7em; padding: 2.2em .36em 2.2em 1.15em; background: var(--side); border: 0 solid var(--line); border-right-width: 1px; z-index: 2; }
.side > .chats { flex: 1; min-height: 0; display: flex; flex-direction: column; }
/* the Superbot mark wears the desktop app's DEFAULT skin, 'plain' = "No
   background" (superbot-desktop 72e599a44, 2026-09-25: storm-tile.tsx
   skin='plain' + storm-tile-plain.css; the owner: "the character animates but
   the background stays the same as inactive"). At rest the mark IS the app
   icon (brand/superbot-app-icon.png, the desktop's marks/assets icon at 512px,
   the size the sphere and realui heroes fly it at: a pure vertical grey
   gradient, rgb 48 at the top to 20 at the bottom, every column identical,
   under the white duotone face). Woken (.sel, the open route; .working, a
   live turn) the ONLY change is the face coming alive: no storm ring, no
   aura, no halo, no selected box. Three layers share one grid cell, bottom
   to top:
   - .tile: the app icon itself, on in EVERY state, so the ground under every
     layer is the idle pixels at every moment of the fade;
   - .ground: the SAME png stretched from its leftmost 1% column (left of the
     face's 7.2% edge), i.e. the ground with no face in it; it fades in on wake
     and covers the icon's static face, so the living face's ear swings, bob
     and eye moves never reveal a second face through its eye holes;
   - .face: the living mark (markSvg() markup inline in lander-agent.html,
     motion from mascot-mark.css .sb-mark, blink and pupils from
     mountMascotMark), teal and magenta ghosts kept because the idle icon's
     face wears them. It sits on the icon's own face seat (desktop styles.css
     `.storm-tile > svg`: translateY(-3.9%) scale(1.144) of the 0..100 mark
     box, within 0.2% of the icon's face on every edge), written here as the
     svg's box (114.4% square at -7.2%, -11.1%) because .sb-mark's bob owns
     the svg's transform.
   The 44/14 box is the old tile's, so the heroes that fly the tile onto the
   mark keep their landing size. Motion is unconditional (motion-always-on.mdc). */
.hud-mark { position: relative; flex: none; display: grid; width: 44px; height: 44px; border-radius: 14px; color: var(--accent-ink); opacity: 0; }
.hud-mark > .tile, .hud-mark > .ground, .hud-mark > .face { grid-area: 1 / 1; position: relative; width: 100%; height: 100%; border-radius: inherit; }
.hud-mark > .tile { z-index: 0; display: block; }
.hud-mark > .ground { z-index: 1; background: var(--sb-icon) left top / 10000% 100% no-repeat; } /* fixed: the icon's 1% source column, not a spacing value */
.hud-mark > .face { z-index: 2; display: block; overflow: hidden; }
.hud-mark > .face .sb-mark { position: absolute; left: -7.2%; top: -11.1%; width: 114.4%; height: 114.4%; max-width: none; cursor: default; } /* the icon's face seat, see above; max-width: none undoes site.css:179 */
.hud-mark > .ground, .hud-mark > .face { opacity: 0; transition: opacity var(--dur-4) var(--ease-std); }
.hud-mark.sel > .ground, .hud-mark.sel > .face,
.hud-mark.working > .ground, .hud-mark.working > .face { opacity: 1; }
/* the text column: the title line over the disc stack, --spacing-4 apart */
.hud-text { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: .29em; }
.hud-word { display: block; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font: 700 1.57em/2em var(--ui); letter-spacing: -.02em; color: var(--fg); opacity: 0; }
/* a vendor destination's label takes the line at the headline's weight
   (--text-headline 500): the 700 lockup is the 'superbot' name alone */
.hud-word.vendor { font-weight: 500; letter-spacing: -.018em; }
/* the destination discs (packages/ui avatar-stack.tsx): round, overlapping,
   each next one stepped back over the one before it and painted ABOVE it
   (tree order is paint order); every disc wears an outside ring of the
   sidebar ground, the cutout that punches it out of the one under it, and a
   hairline edge inside its circle. The SELECTED disc keeps its slot and
   gains a second ring in the fg ink outside the cutout */
.hud-discs { display: flex; align-items: center; min-width: 0; padding-left: .17em; isolation: isolate; }
.hud .hud-disc { position: relative; flex: none; display: grid; place-items: center; width: 2em; height: 2em; border-radius: var(--r-pill); background: var(--raised); color: var(--fg); box-shadow: 0 0 0 .17em var(--side); opacity: 0;
  transition: background-color var(--dur-4) var(--ease-std), color var(--dur-4) var(--ease-std), box-shadow var(--dur-4) var(--ease-std), translate var(--dur-2) var(--ease-std); }
.hud-disc::after { content: ""; position: absolute; inset: 0; border-radius: inherit; box-shadow: inset 0 0 0 1px var(--line); pointer-events: none; }
.hud-disc + .hud-disc { margin-left: -.67em; }
.hud .hud-disc.sel { box-shadow: 0 0 0 .17em var(--side), 0 0 0 .34em var(--fg); }
/* a hovered disc lifts --spacing-2 and rises over its neighbours (the app's
   hover); `translate`, not `transform`, so it never fights the reveal's own
   transform writes */
@media (hover: hover) { .hud-disc:hover { translate: 0 -.18em; z-index: 1; } }
.hud-disc svg, .hud-menu-art svg { width: 62%; height: 62%; }
.hud-disc > img, .hud-menu-art > img { display: block; width: 100%; height: 100%; border-radius: inherit; object-fit: cover; }
/* superbot's disc is the bare mono mark on the black tile ground */
.hud-disc.sb, .hud-menu-art.sb { background: var(--tile-bg); }
.hud-disc.sb > img, .hud-menu-art.sb > img { width: 64%; height: 64%; border-radius: 0; }
/* the trailing + closes the stack a little apart from it */
.hud .hud-disc.add { margin-left: .36em; background: var(--raised); color: var(--muted); font: 400 1.1em/1 var(--ui); }
/* vendor discs: the app's own icons, COLORED, each on its own ground (user,
   2026-09-08). Ground + ink per brand pair; the edge hairline above keeps
   the black ground (cursor) reading as a disc on the black. */
.hud-disc[data-app="openai"], .hud-menu-art[data-app="openai"] { background: var(--brand-openai); color: var(--brand-openai-ink); }
/* ChatGPT is its full-bleed app-icon art: the knot fills 75% of sb-ic-openai's
   viewBox, so the svg runs at the disc's width */
.hud-disc[data-app="openai"] svg, .hud-menu-art[data-app="openai"] svg { width: 96%; height: 96%; }
.hud-disc[data-app="claude"], .hud-menu-art[data-app="claude"] { background: var(--brand-claude); color: var(--brand-claude-ink); }
.hud-disc[data-app="gemini"], .hud-menu-art[data-app="gemini"] { background: var(--brand-gemini); color: var(--brand-gemini-ink); }
.hud-disc[data-app="cursor"], .hud-menu-art[data-app="cursor"] { background: var(--brand-cursor); color: var(--brand-cursor-ink); }
/* the Servers menu the mark opens (ServerSwitcherMenu.tsx): a raised card
   hung under the HUD row, one row per destination (its art, its name, one
   short status in words), a hairline, then + Add. Hidden until the HUD
   carries .menu-open; hub-boot.js opens it for one beat of the tour. */
.hud .hud-menu { position: absolute; left: .55em; top: calc(100% - 1.8em); z-index: 9; width: 22em; display: grid; gap: .1em; padding: .36em; border-radius: calc(var(--r-control) * 1.25); background: var(--surface); border: 1px solid var(--line); box-shadow: 0 1.27em 2.55em rgba(0,0,0,.4);
  opacity: 0; visibility: hidden; translate: 0 -.36em;
  transition: opacity var(--dur-3) var(--ease-std), translate var(--dur-3) var(--ease-out), visibility 0s linear var(--dur-3); }
.hud.menu-open .hud-menu { opacity: 1; visibility: visible; translate: 0 0; transition-delay: 0s; }
.hud-menu-row { display: flex; align-items: center; gap: .73em; min-width: 0; padding: .36em .55em; border-radius: var(--r-control); color: var(--fg); }
.hud-menu-row.on { background: color-mix(in srgb, var(--fg) 8%, transparent); }
.hud-menu-art { flex: none; display: grid; place-items: center; width: 2em; height: 2em; box-shadow: inset 0 0 0 1px var(--line); border-radius: var(--r-pill); background: var(--raised); color: var(--fg); font-style: normal; }
.hud-menu-art.add { background: transparent; box-shadow: none; color: var(--muted); font: 400 1.3em/1 var(--ui); }
.hud-menu-tx { min-width: 0; display: grid; }
.hud-menu-tx b { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 500; }
.hud-menu-tx small { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--muted); font-size: .82em; line-height: 1.3; }
.hud-menu-div { height: 1px; margin: .18em .55em; background: var(--line); }

/* ---------- client themes (the `tabs` cut's act two) ----------
   Selecting a client retints the HUB — never the stage frame, which is
   superbot's own window — to that vendor's palette, so the hero shows what it
   claims: the app wears each client's skin, and coming home to superbot reads
   as the neutral overview. The palettes are our own tokens picked to read as
   the vendor; nothing is lifted beyond the HUD's existing vendor marks.
   Only custom properties change here. A custom property is not itself
   animatable without @property, so the cross-fade is carried by the transition
   below on the background-color / color / border-color that consume them.
   300ms is design-motion's cap: this is incidental motion, not the one
   deliberate gesture the 700ms terminal collapse is. */
.hub[data-app="openai"] { --bg: #0c0d0d; --side: #131414; --surface: #1b1c1c; --raised: #212322; --window: #101111; --win: #0a0b0b; --line: #2a2c2b; --accent: #10a37f; --accent-ink: #03130e; }
.hub[data-app="claude"] { --bg: #191817; --side: #201f1d; --surface: #2a2825; --raised: #322f2b; --window: #1f1e1c; --win: #151413; --line: #3a3733; --muted: #a49c90; --accent: #d97757; --accent-ink: #1c0c05; }
/* every descendant, not a hand-kept list: the hub is ~150 static nodes and an
   enumerated list left the hub's own ground, the status dots, the meter fills,
   the HUD's cutout rings and the code tinting snapping at each switch while their
   neighbours faded. box-shadow is in the list for .row.on's inset accent bar.
   None of these four properties is one the script animates (it drives opacity
   and transform), so nothing here fights the clock. */
.hub, .hub * {
  transition: background-color var(--dur-4) var(--ease-std), border-color var(--dur-4) var(--ease-std),
              color var(--dur-4) var(--ease-std), box-shadow var(--dur-4) var(--ease-std);
}

.chats { background: var(--side); border-right: 1px solid var(--line); }
/* the tab row carries filter and search at its right end, as the app's
   sidebar does under the HUD (the old superbot.gg head row above the tabs is
   gone with the rail) */
.lane-tabs .side-ic { flex: none; align-self: center; display: grid; place-items: center; font-style: normal; font-weight: 400; color: var(--muted); }
.lane-tabs .side-ic:first-of-type { margin-left: auto; } /* the two buttons push right: filter, then search */
.lane-tabs .side-ic + .side-ic { margin-left: -.55em; } /* the row's 12px tab gap, closed to the app's 6px between the pair */
/* filter and search are the app's sm IconButton (packages/ui primitives/
   icon-button.tsx:49-50): a control-radius square, never a disc, on the card
   ground with the control hairline; 8 of 24 is 6 of this 18px box */
.lane-tabs .side-ic.ic-btn { width: 18px; height: 18px; border-radius: calc(var(--r-control) * 3 / 4); background: var(--win); border: 1px solid var(--line-strong); }
.lane-tabs .side-ic svg { width: 10px; height: 10px; }
/* Chat | Code: the app's lane switch as it ships since superbot-desktop
   fc132325b (packages/ui lane-view-switch.tsx, styles.css .sb-lane-tabs /
   .sb-lane-tab / .sb-lane-tabs-bar). Two text tabs, labels left-aligned so
   "Chat" lands on the same x as "Today" and the row text under it; a hairline
   runs under the strip and the open lane wears a 2px fg underline sitting ON
   that hairline (bottom: -1px), its word in fg at weight 500; the idle lane is
   muted. The app's --spacing-16 gap and --size-target height are scaled to the
   miniature's 11px base (14px in the app), the same ratio the rows use. The
   gradient pill it replaces (--super-grad thumb) was the pre-fc132325b switch. */
.lane-tabs { flex: none; display: flex; align-items: stretch; justify-content: flex-start; gap: 12px; margin: 0 10px 4px; border-bottom: 1px solid var(--line); }
.lane-tab { position: relative; display: inline-flex; align-items: center; gap: 0.4em; min-height: 22px; padding: 0 3px 0 0; color: var(--muted); font: 400 1em/15px var(--ui); white-space: nowrap; }
/* the leading lane icon (lucide MessageCircle / Code2, the app's 16px at a
   14px label, so 8/7 of the label here): outline in currentColor so it takes
   the open or idle ink, inside the tab so the underline runs under icon + word */
.lane-tab svg { flex: none; width: 1.15em; height: 1.15em; }
.lane-tab.on { color: var(--fg); font-weight: 500; }
.lane-tab.on::after { content: ""; position: absolute; left: 0; right: 0; bottom: -1px; height: 2px; background: var(--fg); }
/* New chat: the list's first row, not a filled button (superbot-desktop hub.css
   .hub-side-new-chat-item, fc132325b). It sits directly under the tab strip and
   above the first section header (Pinned/Today): tabs, then this row, then
   Today, with 6px above it and 2px below before Today's own top padding. One
   chat row tall (.chats .row.chat's
   27px floor and 1.09em title size), inset 6px as a --r-control box drawn as a
   1px DASHED --line-strong outline with no fill, muted ink, and a plus in the
   16px icon slot; 6 inset + 1 border + 7 padding puts the plus on the chat
   rows' 14px icon edge. The hub is an aria-hidden picture with no hover on any
   row, so the app's hover (solid border + row fill) is not drawn here. */
.chats .row.new { min-height: 27px; margin-top: 6px; margin-bottom: 2px; padding: 0 7px; font-size: 1.09em; border: 1px dashed var(--line-strong); background: transparent; color: var(--muted); }
.row.new .nc-mark { flex: none; display: grid; place-items: center; width: 16px; height: 16px; font-style: normal; }
.row.new .nc-mark svg { width: 12px; height: 12px; }
/* the list's section label ('Pinned', 'Recent'; a day caption only when the
   app's Group by day is on, which is off by default). The class keeps its old
   name so the font floor above still reaches it. */
.today { display: flex; align-items: center; padding: 5px 10px 2px; color: var(--muted); font-size: .82em; }
/* Recent carries the app's collapse (−) and new (+) buttons at its right edge */
.today .day-ic { font: 400 1.2em/1 var(--ui); font-style: normal; color: var(--muted); width: 12px; text-align: center; }
.today .day-ic:first-of-type { margin-left: auto; }
.today + .row { margin-top: 2px; }
.row.chat + .today { margin-top: 6px; }
.row { position: relative; display: flex; align-items: center; gap: 6px; min-height: 24px; margin: 0 6px; padding: 0 8px; border-radius: var(--r-control); color: var(--muted); white-space: nowrap; overflow: hidden; }
/* unread: the bar at the row's own left edge, so read and unread rows keep their
   icons on one line (the referent does exactly this) */
.row.chat.unread::before { content: ""; position: absolute; left: 2px; top: 50%; transform: translateY(-50%); width: 3px; height: 13px; border-radius: var(--r-pill); background: var(--fg); opacity: .85; }
.row.chat.unread .t { color: var(--fg); }
/* the day's rows open with the app's own tile: the superbot mark or the client's */
.row .sp { flex: none; width: 16px; height: 16px; border-radius: var(--r-control); background: var(--raised); display: grid; place-items: center; }
.row .sp img { width: 11px; height: 11px; }
.row .sp svg { width: 11px; height: 11px; }
.row svg { flex: none; width: 12px; height: 12px; stroke-width: 2; }
.row .sp { flex: none; } /* the chat-bubble slot at a chat row's left edge: title, bubble, timestamp = the three slots a row needs */
/* a chat imported from a client wears that client's own tile (the build draws
   the Claude burst on its brand ground for Claude Code sessions) */
.row .sp.app-claude { background: var(--brand-claude); color: var(--brand-claude-ink); }
/* the build's list rows read larger than the old miniature: a 14px title
   against a 12px base, so 1.09em here, with the time in the mono face */
.chats .row.chat { min-height: 27px; font-size: 1.09em; }
/* the app's chat row (packages/ui chat-row.tsx:329-350): no corner radius and
   no inset, the highlight runs edge to edge as a flat band, and the open row
   is the --overlay-row-active state layer (fg at 14%) over the list, never a
   raised fill. Padding carries the old margin so the icons do not move. */
.chats .row.chat { margin: 0; padding: 0 14px; border-radius: 0; }
.chats .row.chat.on { background: color-mix(in srgb, var(--fg) 14%, transparent); }
/* the provider mark (chat-row.tsx:376-390): a 16px tile at the app-icon
   corner (half --radius-8) with the list's hairline, the glyph at 11px */
.chats .row.chat .sp { width: 16px; height: 16px; background: transparent; }
.chats .row.chat .sp.app-claude { background: var(--brand-claude); border-radius: calc(var(--r-control) / 2); box-shadow: inset 0 0 0 1px var(--line); }
/* title over a one-line preview when the summary carries one
   (chat-row.tsx:416-429): the open row shows the reply's first line */
.row .tx { flex: 1; min-width: 0; display: grid; }
.row .pv { min-width: 0; overflow: hidden; text-overflow: ellipsis; color: var(--muted); font-size: .82em; line-height: 1.3; }
.chats .row.chat:has(.pv) { padding-top: 3px; padding-bottom: 3px; }
.row em { margin-left: auto; font-style: normal; font-size: .91em; }
.chats .row.chat em { font: .75em/1 var(--font-mono); padding-left: 6px; }
/* a pinned chat: the app's raised rounded card under 'Pinned', unlike the flat
   Recent rows. Tile, the title in fg over a muted one-line preview, and NO
   time. 6px inset + 8px padding keeps its tile on the rows' 14px icon edge;
   the ground is the theme's --raised so it follows each client's palette. */
.chats .row.pin { min-height: 34px; margin: 0 6px 3px; padding: 4px 8px; gap: 8px; background: var(--raised); font-size: 1.09em; }
.chats .row.pin .sp { width: 16px; height: 16px; background: transparent; }
.chats .row.pin .sp.app-claude { background: var(--brand-claude); border-radius: calc(var(--r-control) / 2); box-shadow: inset 0 0 0 1px var(--line); }
.chats .row.pin .t { color: var(--fg); }
.row.pin + .today { margin-top: 3px; }
.row .t { min-width: 0; flex: 0 1 auto; overflow: hidden; text-overflow: ellipsis; } /* the title truncates, the timestamp always shows */
.row .cnt { margin-left: auto; flex: none; min-width: 13px; padding: 0 3px; border-radius: var(--r-pill); background: var(--danger); color: #ffffff; font: 700 8px/13px var(--font-mono); text-align: center; }
.row.on { background: var(--raised); color: var(--fg); } /* the open chat is one raised row: the app draws no accent bar on the list */
/* the footer user bar floors the column in ONE row (superbot-desktop hub
   account-widgets, the owner's build of 2026-09-26): the avatar with its
   status dot, the address over the tier line ("Free"), then the Invite,
   Report a bug and Settings wells, the bare More, then UPGRADE. Invite and
   Report a bug moved here from the retired rail. */
.acct { margin: auto 6px 6px; padding: 7px 8px; display: flex; align-items: center; gap: .45em; min-width: 0; border-radius: var(--r-surface); background: var(--surface); border: 1px solid var(--line); }
.acct .avatar { position: relative; width: 26px; height: 26px; line-height: 26px; color: var(--fg); box-shadow: inset 0 0 0 1px var(--line-strong); margin-right: .18em; }
.acct-names { flex: 1; min-width: 0; display: grid; gap: 1px; }
.acct-names b { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; }
.acct .acct-plan { display: flex; align-items: center; gap: 4px; min-width: 0; font-weight: 400; color: var(--muted); font-size: .82em; white-space: nowrap; }
.acct .acct-plan img { flex: none; width: 1.2em; height: 1.2em; }
.acct .avatar .dot { position: absolute; right: -1px; bottom: -1px; width: 6px; height: 6px; background: var(--storm-2); box-shadow: 0 0 0 2px var(--surface); }
/* the tool wells: the app's sm IconButton at the footer's size, a
   control-radius square on the card ground with the control hairline; More
   is the bare glyph */
.acct-tool { flex: none; display: grid; place-items: center; width: 1.45em; height: 1.45em; border-radius: calc(var(--r-control) * 3 / 4); background: var(--win); border: 1px solid var(--line-strong); color: var(--muted); font-style: normal; }
.acct-tool svg { width: .82em; height: .82em; }
.acct-tool.more { border-color: transparent; background: transparent; }
/* UPGRADE: the pill drawn as a ring in the app's one ramp over the card
   ground, the word in fg */
.acct .upg { flex: none; padding: 0 .64em; border-radius: var(--r-pill); border: 1px solid transparent; background: linear-gradient(var(--surface), var(--surface)) padding-box, var(--super-grad) border-box; color: var(--fg); font: 700 .75em/1.45em var(--ui); letter-spacing: .08em; }
/* the app's one ramp (packages/ui styles.css:1258 --super-grad): the
   UPGRADE ring and the armed SUPER chip share it */
.hub { --super-grad: linear-gradient(135deg, #3fd6d0 0%, #4f7bff 48%, #c45cff 100%); }
.hub .dot.ok { background: var(--ok); }
/* the stepped-in list has no room for the timestamps, and a half-clipped "12m"
   reads as broken where a clipped title reads as truncated */
.hub.slim:not(.docked) .row em { display: none; } /* docked, the later .hub.docked grid restores the full sidebar, and the app never hides a row's stamp (packages/ui chat-row.tsx:7-9) */
/* and the stepped-in footer keeps the address readable: Invite and Report a
   bug give up their wells first */
.hub.slim:not(.docked) .acct-tool.invite, .hub.slim:not(.docked) .acct-tool.bug { display: none; }
/* the step-in itself, scoped to the widest stage tier: below 820px the chat
   list is already hidden and the narrower templates own the grid, so a slim
   override down there would only leave a dead column. */
/* one combined list: a second `.hub { transition: ... }` here would out-order
   the retint transition above (same specificity, later wins) and leave the
   hub's own --win ground snapping while every descendant faded */
.hub { transition: background-color var(--dur-4) var(--ease-std), border-color var(--dur-4) var(--ease-std), /* right left the list: a layout property, so the stage's horizontal step snaps; colours still fade */
       color var(--dur-4) var(--ease-std), box-shadow var(--dur-4) var(--ease-std),
       grid-template-columns var(--dur-4) var(--ease-out); }
@container stage (min-width: 821px) { .hub.slim { grid-template-columns: 206px minmax(0, 1fr); } } /* the perched step-in: the old 56px rail plus a sidebar at 75% of the app's (150), which still holds the whole HUD row; 112px was the "too small" the user rejected, the user row needs the room to read as the app's */

.main { background: var(--side); }
.chat-head { flex: none; display: flex; align-items: center; gap: 8px; padding: 9px 12px; border-bottom: 1px solid var(--line); font-weight: 700; font-size: 12px; }
.chat-head .topic { flex: 0 1 auto; min-width: 0; font-weight: 400; color: var(--muted); font-size: .83em; border-left: 1px solid var(--line); padding-left: 8px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* header chrome, exactly the real app's: channel name left, Search alone in
   the middle, the quiet toggle at the trailing edge. Ask Superbot left the
   header on 2026-09-22 for the command palette (hub/MainPane.tsx), so no filled
   action sits here any more. */
/* the build's header (2026-09-23 capture): the thread title left, a WIDE search
   field centred in the lane, the panel toggle at the trailing edge. No border
   under it: the toolbar row below carries the lane's one rule. */
.chat-head { border-bottom: 0; padding-bottom: 5px; }
.chat-head b { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chat-head .search { margin: 0 auto; display: flex; align-items: center; gap: 6px; flex: 0 1 60%; min-width: 0; padding: 0 8px; border-radius: var(--r-control); background: var(--raised); border: 1px solid var(--line); color: var(--muted); font-weight: 400; font-size: .91em; line-height: 20px; }
.chat-head .search svg { flex: none; width: 10px; height: 10px; }
.chat-head .search kbd { margin-left: auto; font: 9px/12px var(--font-mono); color: var(--muted); }
.chat-tools { flex: none; display: flex; justify-content: flex-end; gap: 12px; padding: 2px 14px 6px; border-bottom: 1px solid var(--line); color: var(--muted); }
.chat-tools i { display: grid; place-items: center; }
.chat-tools svg { width: 11px; height: 11px; }
/* the collapsed health-board's only remnant, exactly as in the app: one small
   panel-toggle icon at the far right edge of the header */
.chat-head .ptoggle { flex: none; display: grid; place-items: center; width: 20px; height: 20px; border-radius: var(--r-control); background: var(--raised); border: 1px solid var(--line); color: var(--muted); }
.chat-head .ptoggle svg { width: 11px; height: 11px; }
.feed { flex: 1; min-height: 0; padding: 10px 12px; display: grid; align-content: start; gap: 9px; overflow: hidden; }
/* the empty NEW CHAT, which is what the app shows when nothing is open: the
   duotone mark over one line, centred, and nothing else. The old welcome block
   and the date divider left with the transcript (2026-09-12). */
.feed.blank { align-content: center; justify-items: center; text-align: center; gap: 7px; }
.feed.blank .blank-mark { width: 88px; height: 88px; }
.feed.blank .blank-tx { margin: 0; color: var(--secondary); text-wrap: balance; }
.msg { display: flex; gap: 8px; opacity: 0; }
/* the open THREAD (the build's reading column): a centred date rule, the
   user's turn as a right-aligned bubble, then the reply under its folded step
   count, closed by the turn's outcome chip. No avatars, no names. */
.feed.thread { padding: 12px 8% 8px; gap: 12px; }
.date-div { display: flex; align-items: center; gap: 10px; color: var(--muted); font-size: .82em; }
.date-div::before, .date-div::after { content: ""; flex: 1; height: 1px; background: var(--line); }
/* the date sits in a raised pill between the hairlines (packages/ui
   date-divider.tsx:97-107: rounded-pill bg-raised px-8 py-4) */
.date-div span { padding: 2px 8px; border-radius: var(--r-pill); background: var(--raised); }
.msg.own { justify-content: flex-end; }
/* the user's bubble has no hairline (packages/ui message-row.tsx:470:
   rounded-16 bg-raised px-16 py-4) */
.msg .bub { margin: 0; max-width: 72%; padding: 5px 11px; border-radius: 14px; background: var(--raised); border: 0; color: var(--fg); line-height: 15px; }
.msg.reply .m-main { gap: 7px; }
.msg.reply p.m-text { margin: 0; color: var(--fg); }
.m-list { margin: 0; padding-left: 14px; display: grid; gap: 3px; line-height: 15px; color: var(--fg); }
.m-list b { font-weight: 650; }
.worked { display: inline-flex; align-items: center; gap: 5px; justify-self: start; color: var(--muted); font-size: .91em; }
.chev { font-style: normal; font-size: 1.2em; line-height: 1; }
.chev.dn { transform: rotate(90deg); }
.msg.cont .avatar { visibility: hidden; }
.avatar { flex: none; width: 24px; height: 24px; border-radius: 50%; background: var(--c, #facc15); color: var(--ink); font: 700 10px/24px var(--ui); text-align: center; }
/* superbot's avatar is the kit's bare mark on the black tile (a static surface,
   so no ring): the same glyph the HUD's disc, the header and the list carry */
.avatar.sb { background: var(--tile-bg, #000000); box-shadow: inset 0 0 0 1px var(--line); font-size: 0; overflow: hidden; display: grid; place-items: center; }
.avatar.sb img { width: 62%; height: 62%; display: block; }
.m-main { min-width: 0; flex: 1; display: grid; gap: 5px; }
.m-head { display: flex; gap: 6px; align-items: baseline; }
.m-name { font-weight: 700; }
.m-when { font-size: .82em; color: var(--muted); }
.app { padding: 0 4px; border-radius: 3px; background: var(--accent); color: var(--accent-ink); font: 700 8px/12px var(--ui); letter-spacing: .04em; }
.m-text { font-size: 1em; line-height: 15px; }
.ref { display: inline-block; padding: 0 4px; border-radius: 4px; background: var(--raised); color: var(--accent); font-weight: 650; }
.card { background: var(--surface); border: 1px solid var(--line); border-radius: 6px; padding: 6px 8px; display: grid; gap: 3px; }
.arena .card { position: static; width: auto; } /* hero.css's .card is absolutely positioned at min(280px,42%) for the incident cards; that must not leak into the hub's chat cards or the phone's */
.card.rule { border-left: 3px solid var(--accent); justify-items: start; max-width: 200px; }
.card.rule b { font-size: 1em; }
.card.rule small { color: var(--muted); font-size: .82em; }
.card.council { border-left: 3px solid var(--accent); font-size: .91em; }
.c-head { display: flex; gap: 6px; align-items: center; color: var(--muted); }
.c-head .cost { margin-left: auto; font: 10px var(--font-mono); }
.tags { display: flex; gap: 4px; flex-wrap: wrap; }
.tags span { padding: 0 5px; border-radius: 3px; background: color-mix(in srgb, var(--accent) 22%, var(--surface)); color: var(--accent); font-size: .9em; line-height: 14px; }
.tags span.dim { background: var(--raised); color: var(--muted); }
/* the composer, as the app draws it today (owner's capture, 2026-09-12): TWO
   lines in one raised bar. The plus and the placeholder on the first, then the
   mode switch, the Manage switch, the model picker and the send on the second (superseded below, 2026-09-23).
   The bar carries the storm ramp on its own border and a faint bloom under it,
   which is the one piece of brand chrome the app leaves on the input. */
.composer { flex: none; position: relative; margin: 6px 8% 8px; border-radius: var(--r-surface); }
/* the build's composer (2026-09-23 capture): one box on a blue hairline, the
   placeholder over a foot of +, the SUPER Half pill, then the model picker, the
   screen-share, the mic and the send at the right; the disclaimer under it */
.composer .pill { position: relative; display: grid; gap: 8px; padding: 8px 8px 6px 10px; border-radius: var(--r-surface); background: var(--surface); border: 1px solid color-mix(in srgb, var(--storm-2) 62%, var(--line)); color: var(--muted); font-size: 1em; box-shadow: 0 0 14px -6px rgb(59 91 255 / .45); }
.composer .line { display: flex; align-items: center; gap: 8px; min-width: 0; }
.composer .plus { flex: none; width: 16px; text-align: center; font: 400 14px/16px var(--ui); color: var(--muted); }
.composer .ph { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* the SUPER chip, the HALF PILL (superbot-desktop 9634ac086, packages/ui
   styles.css .bc-super / -cap / -spark / -tag / -chev), at the miniature's
   scale (the send round is 20px here, 28 in the app): one pill at the send's
   height split at a straight seam. LEFT a roughly square cap holding a
   four-point sparkle (lucide Sparkle, filled): fg at 8% with a muted sparkle
   while unarmed (OFF, an idle AUTO), the storm gradient with a white sparkle
   when [data-on] (ON, a lit AUTO). RIGHT a dark half on a hairline reading
   the mode in title case, then a small down chevron. No "SUPER" word, no
   switch. The page draws it OFF. */
.composer .super { flex: none; display: flex; align-items: stretch; height: 20px; border-radius: var(--r-pill); color: var(--fg); font: 500 .82em/1 var(--ui); white-space: nowrap; }
.composer .super-cap { flex: none; display: grid; place-items: center; width: 24px; border-radius: var(--r-pill) 0 0 var(--r-pill); background: color-mix(in srgb, var(--fg) 8%, transparent); }
.composer .super[data-on] .super-cap { background: var(--super-grad); }
.composer .super-spark { width: 10px; height: 10px; color: var(--muted); opacity: .7; }
.composer .super[data-on] .super-spark { color: #fff; opacity: 1; }
.composer .super-tag { display: flex; align-items: center; gap: 3px; padding: 0 6px 0 7px; border: 1px solid color-mix(in srgb, var(--line) 50%, var(--line-strong)); border-left: 0; border-radius: 0 var(--r-pill) var(--r-pill) 0; background: color-mix(in srgb, var(--fg) 3%, transparent); }
.composer .super-chev { width: 9px; height: 9px; color: var(--muted); }
/* the model pill (.bc-plat, styles.css:1706-1725): the bare superbot face,
   the lowercase word in the muted ink, and a stroke chevron */
.composer .model { margin-left: auto; flex: none; display: flex; align-items: center; gap: 4px; color: var(--muted); font-size: .91em; line-height: 15px; }
.composer .model .glitch { display: grid; }
.composer .model img { width: 10px; height: 10px; }
.composer .model .cv { width: 9px; height: 9px; }
.composer .monitor, .composer .mic, .composer .send { flex: none; display: grid; place-items: center; color: var(--muted); }
.composer .monitor svg { width: 12px; height: 12px; }
.composer .mic, .composer .send { width: 20px; height: 20px; border-radius: 50%; background: var(--raised); }
.composer .mic svg, .composer .send svg { width: 11px; height: 11px; }
/* the composer's footer line (hub/ContextStatusLine.tsx:8-12, Thread.tsx
   18252-18293): the thread's context reading in mono with its fill bar leads,
   the AI disclosure sits at the right end of the same row */
.composer .cfoot { display: flex; align-items: center; gap: 8px; margin: 4px 2px 0; min-width: 0; }
.composer .ctx { flex: none; display: inline-flex; align-items: center; gap: 6px; color: var(--muted); font: .82em/12px var(--font-mono); }
.composer .ctx .bar { width: 40px; height: 3px; border-radius: var(--r-pill); background: var(--line-strong); overflow: hidden; }
.composer .ctx .bar i { display: block; width: 9%; height: 100%; border-radius: inherit; background: var(--secondary); }
.composer .disclaim { margin: 0 0 0 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: right; color: var(--muted); font-size: .82em; line-height: 12px; }

/* the tour transcript: a SECOND feed stacked on the story's own, holding
   whichever client's exchange act two is showing. Two feeds rather than
   rewriting #feed's innerHTML, because the story's messages are captured as
   element refs at module load and rewriting the container would drop them
   (the same trap renderMargin() documents for the parked mascot). */
.main { position: relative; }
.feed.tour { position: absolute; inset: 62px 0 92px; opacity: 0; } /* header + toolbar above, composer + disclaimer below */
.card.code { background: var(--win); padding: 0; gap: 0; overflow: hidden; }
.card.code .c-bar { display: flex; align-items: center; gap: 6px; padding: 3px 8px; border-bottom: 1px solid var(--line); color: var(--muted); font-size: .82em; }
.card.code pre { margin: 0; padding: 6px 8px; font: 9px/14px var(--font-mono); color: var(--fg); white-space: pre; overflow: hidden; }
.card.code pre .k { color: var(--accent); }
.card.code pre .c { color: var(--muted); }
/* superbot's answer is the payoff of the tour, so it carries the one lit edge
   in the scene — the accent it is tinted with is ours again by then */
.card.code.better { border-color: color-mix(in srgb, var(--accent) 45%, var(--line)); box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 16%, transparent), 0 6px 22px color-mix(in srgb, var(--accent) 14%, transparent); }


/* ---------- the phone: CSS only, same hub compact ---------- */
.phone { right: 3%; bottom: 3%; width: 19.5%; aspect-ratio: 9 / 19; background: #000000; border: 2px solid var(--line); border-radius: var(--r-surface); padding: 6px; box-shadow: 0 14px 28px rgba(0,0,0,.4); opacity: 0; z-index: 8; transform: translateY(120%); } /* 19.5% of the wider arena = the same absolute size 22% was before the margin went 30%→21%; right/bottom/width left the transition list (layout properties, check-ui): the dock morph snaps, the transform entrance still animates */
/* docked (the end state, owner 2026-09-08: "the mobile could become like a
   column on the right that they can see to see that it's synced in real
   time"): the phone parks as a right column beside the hub, its bottom on the
   hub's bottom, so the two screens sit side by side with the same last line */
.hub.docked { grid-template-columns: 264px minmax(0, 1fr); right: calc(24% + 20px); }
.phone.docked { right: 8px; bottom: 8px; top: 8px; width: 24%; aspect-ratio: auto; } /* a touch wider than the floating phone: 19.5% read as narrow beside the hub (owner, 2026-09-08) */ /* docked, the phone is the hub's height: a right column, not a floating device */
/* the phone is superbot Mobile's chat screen (superbot-mobile
   src/features/thread/ThreadScreen.tsx) as the app draws it on 2026-09-24, in
   two states: the new chat (hero: the white cat face over the two-line serif
   greeting "Good afternoon." / "Where do we go?", nothing else) and, once #phone carries .thread (the beats add it as the first
   turn lands), the open thread (the hero faded out, the thread title in the
   header, the date divider, the user bubble and the plain reply). Sizes are
   the app's points at the miniature's ~0.55 scale (the 402pt canvas divides
   down to the ~220px screen): body 15pt -> 9px, caption 12pt -> 7px, title
   17pt -> 10px, header 52pt -> 30px, composer field 56pt -> 30px. Colours are
   the app's dark theme, which the site ladder already equals (themes.ts:338:
   canvas #000000 = --bg, elevated #181818 = --raised, line #262626, text
   #ececec / #cfcfcf / #9a9a9a); the one ramp is --go on the composer's dim
   ring. */
.phone .screen { position: absolute; inset: 6px; background: var(--bg); border-radius: var(--r-control); overflow: hidden; display: grid; grid-template-rows: auto auto minmax(0, 1fr) auto auto auto; grid-template-columns: minmax(0, 1fr); font: 9px/12px var(--ui); color: var(--fg); } /* the one column is minmax(0,1fr), not the implicit min-content track: the turn and the composer row were sizing to their text and running 80px past the screen (measured 2026-09-08) */
/* the iOS status band: the clock left; cellular bars, wifi and battery right (the captures' own band) */
.phone .ph-status { display: flex; justify-content: space-between; align-items: center; padding: 12px 14px 4px; font-size: 1em; font-weight: 600; color: var(--fg); }
.phone.docked .ph-sig { visibility: hidden; } /* the sync chip takes the status glyphs' slot once docked */
.phone .ph-sig { display: flex; align-items: center; gap: 3px; }
.phone .ph-bars { display: flex; align-items: flex-end; gap: 1px; height: 6px; }
.phone .ph-bars b { width: 2px; height: 100%; border-radius: 1px; background: var(--fg); }
.phone .ph-bars b:nth-child(1) { height: 40%; } .phone .ph-bars b:nth-child(2) { height: 60%; } .phone .ph-bars b:nth-child(3) { height: 80%; }
.phone .ph-wifi { width: 9px; height: 9px; }
.phone .ph-batt { position: relative; display: block; width: 13px; height: 6px; border: 1px solid var(--muted); border-radius: 2px; padding: 1px; }
.phone .ph-batt b { display: block; height: 100%; border-radius: 1px; background: var(--fg); }
/* ScreenHeader (ScreenHeader.tsx:216-260): sizes.header row, space[16] edges, the leading back circle, the title with its muted chevron.down (titleChevron="down", ThreadScreen.tsx:11292), then share and search, over the hairline */
.phone .ph-head { display: flex; align-items: center; gap: 6px; height: 30px; padding: 0 8px; border-bottom: 1px solid var(--line); color: var(--fg); }
.phone .ph-back { flex: none; display: grid; place-items: center; width: 11px; height: 11px; color: var(--fg); }
.phone .ph-back svg { width: 11px; height: 11px; }
.phone .ph-title { flex: 1; min-width: 0; display: flex; align-items: center; gap: 2px; }
.phone .ph-title b { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 500; font-size: 1.12em; line-height: 13px; } /* type.title: 17pt medium, at the miniature's 9px base (the screen's own font shorthand) */
.phone .ph-t-thread, .phone.thread .ph-t-new { display: none; }
.phone.thread .ph-t-thread { display: block; }
.phone .ph-tcv { flex: none; width: 8px; height: 8px; color: var(--muted); } /* sizes.icon.sm, text.muted */
.phone .ph-ic { flex: none; display: grid; place-items: center; width: 11px; height: 11px; color: var(--secondary); } /* the share glyph tints text.secondary (ThreadScreen.tsx:11405) */
.phone .ph-ic svg { width: 11px; height: 11px; }
.phone .ph-head .sync { position: absolute; right: 12px; top: 12px; padding: 0 3px; background: var(--bg); font: 8px/12px var(--font-mono); color: var(--accent); opacity: 0; } /* on the status band, in the status glyphs' slot: its plate is the canvas black, so as the beat fades the chip in it covers the glyphs instead of printing over them */
/* the transcript area: the hero and the feed share it, one at a time */
.phone .ph-canvas { position: relative; min-width: 0; min-height: 0; overflow: hidden; }
/* the new chat screen, the app's final design of 2026-09-25: the white cat
   face (no tile, no ring) centred in the area over a two-line greeting in the
   system serif, "Good afternoon." over "Where do we go?", large and regular
   weight. Nothing else sits in the area: no cards, no second greeting line.
   The hero is one layer and fades out as the first turn lands (.thread). The
   greeting's size follows the screen's width (the ph-screen container below):
   "Where do we go?" measures about 7.4 times its font size in the serif, so
   dividing the padded width by 8 keeps each line whole on the narrowest
   floating screen, capped at the app's scale on the wider docked one. */
.phone .ph-hero { position: absolute; inset: 0; display: flex; flex-direction: column; min-width: 0; transition: opacity var(--dur-3) var(--ease-std); }
.phone.thread .ph-hero { opacity: 0; }
.phone .ph-greet { flex: 1; min-height: 0; display: flex; flex-direction: column; justify-content: center; align-items: center; padding: 0 8px; text-align: center; }
.phone .ph-face { display: block; width: 40px; height: 40px; margin-bottom: 10px; }
.phone .ph-where { font: 400 min(1.9em, calc((100cqi - 16px) / 8))/1.12 var(--font-serif); letter-spacing: -.01em; color: var(--fg); white-space: nowrap; }
/* the footer line in the new chat state is the AI disclosure alone; the
   context bar joins it once a thread exists */
.phone:not(.thread) .ph-ctx { display: none; }
/* the thread (FlashList anchored at the bottom): the date divider (DateDivider.tsx: caption muted between two hairlines, space[12] above and below), then the turns in MessageRow's space[16] gutters */
.phone .ph-feed { position: absolute; inset: 0; padding: 6px 0; display: flex; flex-direction: column; justify-content: flex-end; gap: 2px; min-width: 0; opacity: 0; transition: opacity var(--dur-3) var(--ease-std); }
.phone.thread .ph-feed { opacity: 1; }
.phone .ph-date { display: flex; align-items: center; gap: 4px; padding: 6px 8px; font-size: .78em; line-height: 9px; color: var(--muted); }
.phone .ph-date::before, .phone .ph-date::after { content: ""; flex: 1; height: 1px; background: var(--line); }
.phone .msg { display: block; min-width: 0; padding: 2px 8px; }
/* the user turn: right-aligned surface.elevated bubble, r16 continuous, maxWidth 85%, space[12]/space[16] padding (MessageRow.tsx:163-176) */
.phone .ph-own { display: flex; justify-content: flex-end; }
.phone .ph-bub { max-width: 85%; margin: 0; padding: 6px 8px; border-radius: var(--r-control); background: var(--raised); color: var(--fg); overflow-wrap: anywhere; }
/* the assistant turn: full width, plain body text, no surface of its own (MessageRow.tsx:177-180) */
.phone .ph-reply { color: var(--fg); }
.phone:not(.replied) .ph-reply { display: none; } /* the reply row is not in the list until it lands, so the user turn sits on the bottom anchor alone */
.phone .ph-reply p { margin: 0; }
.phone .ph-reply ul { margin: 2px 0; padding-left: 11px; }
.phone .ph-reply li { margin: 1px 0; }
.phone .ph-reply b { font-weight: 600; }
/* the full composer, the app's final design: one raised r16 box on a hairline,
   the placeholder line "How can superbot help you today?" over the controls
   row (a round +, the "superbot" chip with the cat and a caret, then the mic
   and the white round send at the right). Every size is in em off the
   composer's own font size, which follows the screen's width (100cqi / 22,
   capped at the miniature's 9px base), so the whole row and the placeholder
   stay whole on the narrow floating screen as on the docked one. */
.phone .ph-composer { padding: 4px 7px 0; min-width: 0; font-size: min(9px, calc(100cqi / 22)); line-height: 1.3; }
.phone .ph-box { display: grid; gap: .7em; padding: .85em .7em .7em; border: 1px solid var(--line); border-radius: var(--r-surface); background: var(--raised); color: var(--muted); }
.phone .ph-place { padding-left: .3em; white-space: nowrap; overflow: hidden; }
.phone .ph-row { display: flex; align-items: center; gap: .45em; min-width: 0; }
.phone .ph-row i { flex: none; display: grid; place-items: center; width: 2.3em; height: 2.3em; border-radius: 50%; border: 1px solid var(--line); color: var(--fg); }
.phone .ph-row i svg { width: 1.15em; height: 1.15em; }
.phone .ph-row .ph-mic { margin-left: auto; }
.phone .ph-row .ph-send { border: 0; background: var(--fg); color: var(--bg); }
.phone .ph-chip { flex: none; display: inline-flex; align-items: center; gap: .4em; height: 2.3em; padding: 0 .75em 0 .6em; border: 1px solid var(--line); border-radius: var(--r-pill); color: var(--fg); font-weight: 400; white-space: nowrap; }
.phone .ph-chip img { width: 1.3em; height: 1.3em; }
.phone .ph-chip svg { width: .9em; height: .9em; color: var(--muted); }
/* the footer line under the composer (Composer.tsx:2497-2523, styles.footerSlot): centred, the AI disclosure alone on one line in the new chat; in a thread the context-window bar (ContextWindowBar: an 88pt well on the storm ramp + the estimated "~N%" caption) leads it, the pair wrapping rather than clipping on a narrow phone */
.phone .ph-foot { display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: 1px 6px; padding: 4px 7px 0; min-width: 0; font-size: .78em; line-height: 9px; color: var(--muted); white-space: nowrap; }
.phone .ph-ctx { flex: none; display: flex; align-items: center; gap: 4px; overflow: hidden; }
.phone .ph-ctx i { flex: none; display: block; width: 26px; height: 2px; border-radius: 999px; background: var(--line); overflow: hidden; }
.phone .ph-ctx b { display: block; width: 8%; height: 100%; background: var(--go); }
.phone .ph-ai { flex: none; }
/* the disclosure is one sentence on one line, never clipped or ellipsised. The
   phone is a scaled picture whose width follows the arena (19.5% floating, 24%
   docked) while its type is fixed px, so the footer's caption size (.78em, the
   app's 12pt caption at the miniature's scale; the site ladder has no step under
   --fs-xs 12px) is capped by the screen's own width: the 37-character sentence
   measures about 19.4 times its font size, so dividing the padded screen width
   by 20 keeps it on one line at every phone width the stage draws (about 5.9px
   on the 132px floating screen, which the stage then shows at 1.25x). */
.phone .screen { container: ph-screen / inline-size; }
.phone .ph-foot { font-size: min(.78em, calc((100cqi - 14px) / 20)); }
.phone .ph-home { justify-self: center; width: 34%; height: 3px; border-radius: 999px; background: var(--fg); margin: 6px 0 4px; } /* the iOS home indicator reads light on a dark app */
.phone-label { right: 3%; bottom: calc(3% - 22px); width: 19.5%; text-align: center; font: 11px/16px var(--font-mono); color: var(--accent); opacity: 0; z-index: 8; }

/* sync beam: one curve from the chat lane to the phone's shoulder, drawn twice.
   The rail dissolves at BOTH ends through its gradient rather than stopping at a
   hard terminus, so it reads as a connection instead of a line ruled from
   nowhere, and a single light runs it — replacing the dashed arc with an ascii
   glyph riding a textPath, which read as debris on a dotted line.
   The classes are .rail/.run on purpose: hero.css:183-188 styles `.beam .line`
   and `.beam .traffic` for the V0 hero and BOTH sheets load on the lander, so
   renaming retires that collision outright instead of overriding it (the old
   `.beam .line { opacity: 1 }` override went with it). hero.css's `.beam path`
   still sets fill/stroke, but a class selector outranks it. */
.beam { inset: 0; width: 100%; height: 100%; opacity: 0; z-index: 7; pointer-events: none; overflow: visible; }
.beam .rail, .beam .run { fill: none; stroke-linecap: round; }
.beam .rail { stroke: url(#beam-fade); stroke-width: 1.5; }
.beam .run { stroke: url(#beam-run); stroke-width: 2.5; }
.beam .g-off { stop-color: var(--accent); stop-opacity: 0; }
.beam .g-rail { stop-color: var(--accent); stop-opacity: .3; }
.beam .g-hot { stop-color: var(--accent); stop-opacity: 1; }

/* ---------- breakpoints ---------- */
/* narrow hubs: the sidebar's chat list leaves (as it did below 900px before),
   and the HUD row survives as a slim header across the top of the lane, the
   mark, the wordmark and the discs as the app draws them. The old rail column
   stayed here; a lone mark in a column would be the rail again, while the
   header is the HUD the app opens with. */
@media (max-width: 900px) {
  .hub, .hub.slim { grid-template-columns: minmax(0, 1fr); grid-template-rows: auto minmax(0, 1fr); }
  .side > .chats { display: none; }
  .side > .hud { padding: .55em 1.1em; border-right-width: 0; border-bottom-width: 1px; }
  .hud-text { flex-direction: row; align-items: center; gap: .64em; } /* the slim header: the wordmark and the discs share one line beside the mark */
  .hud .hud-menu { top: calc(100% + .27em); }
  .phone { width: 30%; }
  .phone-label { width: 30%; }
}
/* The old <=1000px tier (the panel's last two cards and the composer's model
   picker) is RETIRED, 2026-09-12: the health panel is collapsed at every width
   and the model picker is part of the app's composer, so hiding it on the
   lander's 960px stage was hiding the referent itself. */
/* tablet-width stages (641px viewports up to ~900px): the hub sheds its
   Chats column, then its panel, so the chat lane never drops under ~180px
   (at a 593px stage all four columns left it 23px). The phone cut below
   640px takes over from here with its own two-column hub. */
@container stage (max-width: 820px) {
  .hub, .hub.slim { grid-template-columns: minmax(0, 1fr); grid-template-rows: auto minmax(0, 1fr); }
  .side > .chats { display: none; }
  .side > .hud { padding: .55em 1.1em; border-right-width: 0; border-bottom-width: 1px; }
  .hud-text { flex-direction: row; align-items: center; gap: .64em; } /* the slim header: the wordmark and the discs share one line beside the mark */
  .hud .hud-menu { top: calc(100% + .27em); }
  /* 21% of a 720px stage is 151px: too narrow for "auth sessions   3", the
     transcript's widest line, so the column widens to 27% here */
  .body { grid-template-columns: 27% minmax(0, 1fr); }
}
@container stage (max-width: 660px) {
  /* the SUPER switch and the screen-share leave first: they are the bar's
     furniture, and the placeholder is the bar */
  .composer .super, .composer .monitor { display: none; }
  .feed.blank .blank-mark { width: 64px; height: 64px; }
  /* the app tiles shrink here, not in the phone media block below: hub-boot.js
     picks its tidy-grid pitch (52 vs 78) off this same stage width, so the
     drawn tile and the pitch switch together */
  .item.app { width: 44px; height: 44px; margin: -22px 0 0 -22px; border-radius: 12px; }
  .item.app svg { width: 24px; height: 24px; }
}

/* phones (<=640px): the compact cut of the story. A portrait 4/5 stage (the
   16/10 one left the hub 170px tall and the phone mock covering half of it),
   the terminal transcript as a 3-line strip under the arena that shows its
   LAST lines (a flex column pinned to the end; the old nth-last-child trick
   counted the parked mascot and keys and hid every line), the hub down to
   the HUD header over the chat lane at 12px, and no phone/beam (the viewer is
   already on one). */
@media (max-width: 640px) {
  .stage { aspect-ratio: 4 / 5; }
  .body { grid-template-columns: minmax(0, 1fr); grid-template-rows: minmax(0, 1fr) 54px; } /* 54 = 6px padding x2 + exactly three 14px lines, so no fourth line peeks half-clipped at the top */
  .margin { order: 2; border-right: 0; border-top: 1px solid var(--line); padding: 6px 10px; font-size: 12px; line-height: 14px; display: flex; flex-direction: column; justify-content: flex-end; white-space: nowrap; }
  .margin .ln { flex: none; }
  .margin .ln.gap { height: 4px; }
  .margin .boot-mascot.parked, .margin .sb-keys.parked { display: none; }
  .hub { inset: 6px; grid-template-columns: minmax(0, 1fr); grid-template-rows: auto minmax(0, 1fr); font-size: 12px; line-height: 16px; }
  /* the header gives the ~310px hub back to the lane: the mark steps down to
     36px (the old phone tile) */
  .side > .hud { gap: .8em; padding: .45em .8em; }
  .hud-mark { width: 36px; height: 36px; border-radius: 11px; }
  .side > .chats, .phone, .phone-label, .beam { display: none; }
  .composer .model, .composer .super, .composer .monitor, .composer .cfoot, .chat-tools { display: none; }
  .arena .boot-mascot { width: clamp(80px, 30vw, 132px); }
  .bubble { font-size: 12px; }
  .item.chip { height: 26px; margin: -13px 0 0 -60px; font-size: 12px; line-height: 24px; padding: 0 10px; }
  /* the 12px text floor (mobile-web-design.mdc) for everything the phone cut
     still shows in the chat lane; the desktop hub keeps its 9-11px captions
     because it is a scaled-down picture of the app, not phone body text */
  .hub .cap, .m-when, .m-text, .hub .act, .composer, .composer .send,
  .chat-head .topic, .chat-head .search { font-size: 12px; }
  .hub .cap, .m-text, .hub .act { line-height: 16px; }
  /* the two header chips measured 10.92px (a .91em of the 12px header), under
     the 12px floor; the empty-state line under them is the app's running copy,
     not chrome, so it takes 14px (mobile-web-design.mdc; measured 2026-09-17).
     Both sit here, in the last phone block, so they out-order the .chat-head
     and .feed.blank base rules declared earlier in this sheet. */
  .feed.blank .blank-tx { font-size: 14px; line-height: 20px; }
}

/* phone landscape sits ABOVE the 640px phone cut, so the floors above do not
   fire there and the hub's .91em header chips measured 10.92px again, with the
   empty line at the 11px base (mobile audit 2026-09-17; iPhone 15 landscape).
   Short landscape is still a phone: restate the two floors. */
@media (orientation: landscape) and (max-height: 500px) {
  .chat-head .search { font-size: 12px; }
  .feed.blank .blank-tx { font-size: 14px; line-height: 20px; }
}


/* ---------- the 2026-09-23 build redraw: narrow stages ----------
   The app lets the composer foot wrap; a still picture cannot, so on
   narrower stages the least load-bearing chrome leaves first: the SUPER
   switch and the screen-share, then the model picker and the search hint. */
/* The desktop stage (958px at a 1440 viewport, a 643px-tall hub) holds the
   whole composer foot, as the app does at its 1100px window floor; only below
   760px does the foot run out of width for SUPER and the screen-share. */
@container stage (max-width: 760px) { .composer .super, .composer .monitor { display: none; } }
@container stage (max-width: 900px) { .composer .model, .chat-head .search kbd { display: none; } .feed.thread { align-content: end; } .feed.thread .date-div { display: none; } }
@media (max-width: 640px) { .feed.thread { align-content: end; padding: 10px 12px 8px; } .composer { margin: 6px 10px 8px; } }
/* the HUD header takes a row off every narrow hub (the tablet stage, the
   phone cut, a phone on its side), so the thread's two meta lines (the folded
   step count and the worked-for chip) give it back and the user's bubble
   stays whole above the reply */
@container stage (max-width: 820px) { .feed.thread .worked { display: none; } }
/* the docked hub (phone beside it) restates its tracks at every width, so
   once the chat list hides below 900px the narrow stages keep the one-column
   grid (the HUD header over the lane) when docked too */
@media (max-width: 900px) { .hub.docked { grid-template-columns: minmax(0, 1fr); } }
@container stage (max-width: 820px) { .hub.docked { grid-template-columns: minmax(0, 1fr); } }
@media (max-width: 640px) { .hub.docked { grid-template-columns: minmax(0, 1fr); } .feed.thread .date-div { display: none; } }

/* phones (portrait, and short landscape, which the matrix also counts as a
   phone): the thread's sentences are running copy, so the bubble, the reply
   and its list take 14px; the fold/worked meta stays on the 12px floor set in
   the coarse block above, one step under the copy. Declared last so it
   out-orders that block at the same specificity. */
@media (pointer: coarse) and (max-width: 599px), (pointer: coarse) and (orientation: landscape) and (max-height: 500px) {
  #hub #feed .bub, #hub #feed .m-text, #hub #feed .m-list { font-size: var(--fs-sm); line-height: var(--lh-sm); }
}

/* a phone on its side (landscape pass, 2026-09-26). The landscape stage is a
   ~330x224-266px window (hero.css), and docked beside a 24% phone column the
   hub's lane was ~170px wide: the chat title cut to "Releas…", the search chip
   to "Searc", the context line to "su…", and the phone mock's copy ran one
   word a line at 2-9px. The reader is already holding a phone, so landscape
   takes the portrait phone cut's answer (the 640px block above): no phone, no
   beam, no label. hub-handoff.js reads the phone's display at prepStage
   (noPhone) and then never docks, so the hub keeps the whole arena. The HUD
   header steps its mark down so the shorter hub keeps its lane, and the lane
   sheds the search chip, the thread toolbar, the model picker and the
   composer's foot line, so the title and the placeholder read whole. Scoped
   to #stage: the /features content spliced into #more shares no class with
   the stage, and this keeps it that way. */
@media (orientation: landscape) and (max-height: 500px) {
  #stage .phone, #stage .phone-label, #stage .beam { display: none; }
  #stage .hub, #stage .hub.docked { grid-template-columns: minmax(0, 1fr); grid-template-rows: auto minmax(0, 1fr); }
  #stage .side > .chats { display: none; }
  #stage .side > .hud { gap: .8em; padding: .36em .8em; border-right-width: 0; border-bottom-width: 1px; }
  #stage .hud-mark { width: 36px; height: 36px; border-radius: 11px; }
  #stage .chat-head .search, #stage .chat-tools, #stage .composer .model, #stage .composer .cfoot { display: none; }
  /* the placeholder is the last line under the 12px floor once the phone
     mock is gone (it read 11px, the hub's base) */
  #stage .composer .ph { font-size: var(--fs-xs); }
}
