/* ─────────────────────────────────────────────────────────────────────────────
   theme.css — THE shared design-token layer (systemic-check #21, v351).
   One source for the palette, font stacks, and the radius/space/shadow scales.
   Linked by every app page BEFORE its own <style>, so page-local rules still win.

   Structure (why the light palette appears twice):
     :root                       → LIGHT defaults + the theme-agnostic scales
     @media (prefers-color-scheme: dark) :root
                                 → DARK for ATTRIBUTE-LESS pages (the public share
                                   landing follows the OS; it has no toggle)
     html[data-theme="light"] / html[data-theme="dark"]
                                 → the app pages: the in-app toggle sets the
                                   attribute pre-paint and must beat the OS media.
   Values are the room-ui canon (docs/style-guide.html). Page-specific tokens
   (admin vendor hues, builder --red/--yellow, seen --gold, layout dims) stay in
   their own page. The md-viewers deliberately keep their own reading palette
   (a dedicated persona viewer is planned) — they do not link this file yet.
   ───────────────────────────────────────────────────────────────────────────── */

:root{
  /* font stacks — Latin primaries + the CJK cascade; the final generic MUST stay
     sans-serif (Android resolves a trailing `serif` to Song-style Noto Serif CJK) */
  /* v680 — OWNER CALL: the app is SANS, everywhere. The serif face and the reading-font
     toggle that chose it are both retired. The variable name stays so the ~67 call sites
     that ask for a display face keep their meaning (a title is still a title) — it simply
     resolves to the sans stack now, which makes this one line the whole change, and the
     one line to edit if a display face ever comes back. Only room2's design pages, which
     define their own vars, still set Source Serif. */
  /* v1073 (owner): ONE Chinese face on every phone — the self-hosted Noto Sans SC (lib/fonts/noto-sc.css)
     comes BEFORE the system names, so iOS (PingFang) and Android (Noto/Droid) no longer differ */
  --serif:'Inter','Noto Sans SC','PingFang SC','Microsoft YaHei','Noto Sans CJK SC',system-ui,sans-serif;
  --sans:'Inter','Noto Sans SC','PingFang SC','Microsoft YaHei','Noto Sans CJK SC',system-ui,sans-serif;
  --mono:'JetBrains Mono',ui-monospace,'Noto Sans SC','PingFang SC','Microsoft YaHei',monospace;

  /* scales (theme-agnostic; ADDITIVE — components adopt as they're touched, #22+) */
  --radius-s:8px; --radius-m:10px; --radius-l:12px; --radius-xl:16px; --radius-pill:999px;
  --space-1:4px; --space-2:8px; --space-3:12px; --space-4:16px; --space-5:24px;
  /* avatar scale (#27) — xs inline/byline · s row/card · m writer/browser · l profile head · xl Me hero */
  --av-xs:24px; --av-s:30px; --av-m:40px; --av-l:64px; --av-xl:84px;
  --shadow-1:0 3px 12px rgba(0,0,0,.12);
  --shadow-2:0 10px 30px rgba(0,0,0,.16);
  --shadow-3:0 24px 60px rgba(0,0,0,.22);

  /* LIGHT (default — attribute-less pages start here) */
  --bg:#F0EEE6; --surface:#F7F5EE; --surface-2:#E9E6DC; --ink:#20201D; --muted:#6E6A60; --line:#D9D5C8;
  --coral:#CC785C; --coral-soft:#F2DFD6; --slate:#5F7186; --slate-soft:#DDE3EA;
  --toast-bg:#5C574E; --toast-fg:#F3F1EA; --toast-cta:#EDB49E; --toast-muted:#C9C4B8; --toast-bd:rgba(0,0,0,.16); --toast-sh:0 4px 16px rgba(0,0,0,.12);   /* the softened toast pill — warm taupe, was a near-black inverted-ink slab */
  /* ⚠ v898 (owner:「the star looks awfully dark in light mode」) — THE ★ IS --gold AT ALPHA IN
     LIGHT AND --gold WHOLE IN DARK. It is not a new hue: the palette already had this answer in
     the roll card's intrinsic highlight（「house --gold at alpha: honey in light, amber glow in
     dark」）, and v798 spent a whole step deleting a SECOND yellow from the Studio, so adding one
     back for a badge would undo that.
     ⚠ …but the roll card mixes a BACKGROUND, and this is a filled glyph, so the alpha cannot be
     shared. Toward transparent means toward the ground: in light that is cream and --gold's
     #A9802F（3.31 against the row — text-level contrast on a 15px mark, which is exactly what
     read as「dark」）lands on a honey ~2.01. In dark the ground is #1C1B18, so the same mix walks
     the star toward BLACK — 8.28 down to 3.98, muddy. Dark keeps the token whole; it was never
     the half that was wrong.
     One hue, two strengths, and the strength is stated per theme rather than inferred. */
  --green:#5E8B6E; --green-soft:#DBE7DC; --gold:#A9802F; --gold-soft:#ECE2C9;
  --star:color-mix(in srgb, var(--gold) 62%, transparent);
  --teal:#4C8AAB; --teal-soft:#DCEAF0; --lilac:#8579A8; --lilac-soft:#E6E1EE; --plum:#8C6E96; --plum-soft:#E9DEEC; --rose:#C0708A; --rose-soft:#F2DEE5; --red:#D8453B; --drawer:#EFEADF;
  /* v651 · the tool palette (docs/tool-palette.html): clay 计时器 · aqua 记事 · blue 密存.
     ⚠ NOT repointed teal/lilac — those are spent elsewhere (admin vendor hues, builder chips,
     the artifact kinds), so a token named teal holding a blue would be a lie in four files. */
  --clay:#A06159; --clay-soft:#FCE3DF; --aqua:#359A96; --aqua-soft:#D2EEEC; --blue:#308CA5; --blue-soft:#D5ECF5;
  --seat-0:#CC785C; --seat-0-soft:#F2DFD6; --seat-1:#4C8AAB; --seat-1-soft:#DCEAF0; --seat-2:#5E8B6E; --seat-2-soft:#DBE7DC; --seat-3:#9E6593; --seat-3-soft:#EFDCEC; --seat-4:#A9802F; --seat-4-soft:#ECE2C9; --seat-5:#5F7186; --seat-5-soft:#DDE3EA; --seat-6:#7E78B5; --seat-6-soft:#E3E0F0; --seat-7:#C0708A; --seat-7-soft:#F2DEE5;
  --egg:#8A867B; --egg-soft:#E7E4DB; --egg-glow:rgba(138,134,123,.18);
  --sk:#E3E0D4; --sk-shine:rgba(255,255,255,.6);
  /* ── THE COST SERIES (2026-08-21) — the usage vocabulary's eight group colours ──
     ⚠ ONE DEFINITION, EVERY SURFACE. The admin dashboard and the chat's own Cost panel
     colour the SAME groups (speech · fp · tool · game · persona · room · app), so a person
     who learns one screen reads the other on sight; two copies would drift the day either
     is retouched. The ORDER is the colourblind-safety mechanism, not decoration — these
     eight cleared the lightness band, chroma floor, adjacent-pair CVD separation and the
     normal-vision floor against BOTH app surfaces (docs/usage-dashboard.html). Re-order or
     re-step only with the validator, never by eye. `--s-pre` is the era before the call
     ledger: a near-surface grey, so「no detail to give」reads as absence, not as a group. */
  --s1:#2a78d6; --s2:#eb6834; --s3:#1baf7a; --s4:#eda100;
  --s5:#e87ba4; --s6:#008300; --s7:#4a3aa7; --s8:#e34948; --s-pre:#C9C3B4;
}

/* DARK for attribute-less pages (the share landing follows the OS) */
@media (prefers-color-scheme: dark){
  :root{
    --bg:#1C1B18; --surface:#26241F; --surface-2:#211F1B; --ink:#ECE9E0; --muted:#9A9488; --line:#3A372F;
    --coral:#E0917A; --coral-soft:#382A24; --slate:#92A6BC; --slate-soft:#232C34;
  --toast-bg:#C6C2B6; --toast-fg:#2B2925; --toast-cta:#9A4E32; --toast-muted:#5F5B52; --toast-bd:rgba(0,0,0,.10); --toast-sh:0 6px 18px rgba(0,0,0,.38);   /* the softened toast pill — quiet bone, was a near-white inverted-ink slab */
    --green:#8FB59A; --green-soft:#222F27; --gold:#D6AE63; --gold-soft:#322B1C;
    --star:var(--gold);
    --teal:#82A8C2; --teal-soft:#1E2A31; --lilac:#BBB2D0; --lilac-soft:#272431; --plum:#B49EC0; --plum-soft:#2B2430; --rose:#CE93A4; --rose-soft:#33242A; --red:#E8635A; --drawer:#211F1A;
  --clay:#CF978F; --clay-soft:#372422; --aqua:#78C2BE; --aqua-soft:#142D2C; --blue:#78B8CD; --blue-soft:#162C33;
    --seat-0:#E0917A; --seat-0-soft:#382A24; --seat-1:#82A8C2; --seat-1-soft:#1E2A31; --seat-2:#8FB59A; --seat-2-soft:#222F27; --seat-3:#C495BC; --seat-3-soft:#2C2430; --seat-4:#D6AE63; --seat-4-soft:#322B1C; --seat-5:#92A6BC; --seat-5-soft:#232C34; --seat-6:#B0AAD6; --seat-6-soft:#232232; --seat-7:#CE93A4; --seat-7-soft:#33242A;
    --egg:#928D81; --egg-soft:#2C2A25; --egg-glow:rgba(146,141,129,.16);
    --sk:#322F29; --sk-shine:rgba(255,255,255,.07);
    --s1:#3987e5; --s2:#d95926; --s3:#199e70; --s4:#c98500;
    --s5:#d55181; --s6:#008300; --s7:#9085e9; --s8:#e66767; --s-pre:#4A463C;
  }
}

/* the app toggle — the data-theme attribute is set pre-paint and beats the OS */
html[data-theme="light"]{
  --bg:#F0EEE6; --surface:#F7F5EE; --surface-2:#E9E6DC; --ink:#20201D; --muted:#6E6A60; --line:#D9D5C8;
  --coral:#CC785C; --coral-soft:#F2DFD6; --slate:#5F7186; --slate-soft:#DDE3EA;
  --toast-bg:#5C574E; --toast-fg:#F3F1EA; --toast-cta:#EDB49E; --toast-muted:#C9C4B8; --toast-bd:rgba(0,0,0,.16); --toast-sh:0 4px 16px rgba(0,0,0,.12);   /* the softened toast pill — warm taupe, was a near-black inverted-ink slab */
  --green:#5E8B6E; --green-soft:#DBE7DC; --gold:#A9802F; --gold-soft:#ECE2C9;
  --star:color-mix(in srgb, var(--gold) 62%, transparent);
  --teal:#4C8AAB; --teal-soft:#DCEAF0; --lilac:#8579A8; --lilac-soft:#E6E1EE; --plum:#8C6E96; --plum-soft:#E9DEEC; --rose:#C0708A; --rose-soft:#F2DEE5; --red:#D8453B; --drawer:#EFEADF;
  /* v651 · the tool palette (docs/tool-palette.html): clay 计时器 · aqua 记事 · blue 密存.
     ⚠ NOT repointed teal/lilac — those are spent elsewhere (admin vendor hues, builder chips,
     the artifact kinds), so a token named teal holding a blue would be a lie in four files. */
  --clay:#A06159; --clay-soft:#FCE3DF; --aqua:#359A96; --aqua-soft:#D2EEEC; --blue:#308CA5; --blue-soft:#D5ECF5;
  --seat-0:#CC785C; --seat-0-soft:#F2DFD6; --seat-1:#4C8AAB; --seat-1-soft:#DCEAF0; --seat-2:#5E8B6E; --seat-2-soft:#DBE7DC; --seat-3:#9E6593; --seat-3-soft:#EFDCEC; --seat-4:#A9802F; --seat-4-soft:#ECE2C9; --seat-5:#5F7186; --seat-5-soft:#DDE3EA; --seat-6:#7E78B5; --seat-6-soft:#E3E0F0; --seat-7:#C0708A; --seat-7-soft:#F2DEE5;
  --egg:#8A867B; --egg-soft:#E7E4DB; --egg-glow:rgba(138,134,123,.18);
  --sk:#E3E0D4; --sk-shine:rgba(255,255,255,.6);
  --s1:#2a78d6; --s2:#eb6834; --s3:#1baf7a; --s4:#eda100;
  --s5:#e87ba4; --s6:#008300; --s7:#4a3aa7; --s8:#e34948; --s-pre:#C9C3B4;
}
html[data-theme="dark"]{
  --bg:#1C1B18; --surface:#26241F; --surface-2:#211F1B; --ink:#ECE9E0; --muted:#9A9488; --line:#3A372F;
  --coral:#E0917A; --coral-soft:#382A24; --slate:#92A6BC; --slate-soft:#232C34;
  --toast-bg:#C6C2B6; --toast-fg:#2B2925; --toast-cta:#9A4E32; --toast-muted:#5F5B52; --toast-bd:rgba(0,0,0,.10); --toast-sh:0 6px 18px rgba(0,0,0,.38);   /* the softened toast pill — quiet bone, was a near-white inverted-ink slab */
  --green:#8FB59A; --green-soft:#222F27; --gold:#D6AE63; --gold-soft:#322B1C;
  --star:var(--gold);
  --teal:#82A8C2; --teal-soft:#1E2A31; --lilac:#BBB2D0; --lilac-soft:#272431; --plum:#B49EC0; --plum-soft:#2B2430; --rose:#CE93A4; --rose-soft:#33242A; --red:#E8635A; --drawer:#211F1A;
  --clay:#CF978F; --clay-soft:#372422; --aqua:#78C2BE; --aqua-soft:#142D2C; --blue:#78B8CD; --blue-soft:#162C33;
  --seat-0:#E0917A; --seat-0-soft:#382A24; --seat-1:#82A8C2; --seat-1-soft:#1E2A31; --seat-2:#8FB59A; --seat-2-soft:#222F27; --seat-3:#C495BC; --seat-3-soft:#2C2430; --seat-4:#D6AE63; --seat-4-soft:#322B1C; --seat-5:#92A6BC; --seat-5-soft:#232C34; --seat-6:#B0AAD6; --seat-6-soft:#232232; --seat-7:#CE93A4; --seat-7-soft:#33242A;
  --egg:#928D81; --egg-soft:#2C2A25; --egg-glow:rgba(146,141,129,.16);
  --sk:#322F29; --sk-shine:rgba(255,255,255,.07);
  --s1:#3987e5; --s2:#d95926; --s3:#199e70; --s4:#c98500;
  --s5:#d55181; --s6:#008300; --s7:#9085e9; --s8:#e66767; --s-pre:#4A463C;
}

/* ── #22 · the ONE close/back treatment for dialogs, sheets, drawers (v352) ────
   .dialog-x        absolute top-right (positioned parent) — 40×40 hit box, 20px ×
   .dialog-x.flow   flex-bar placement (profile/Me/library headers, zoom bars) — 34×34
   .dialog-back     the ‹ back chevron, same box as .flow
   The image-lightbox keeps its own circular dark × (.lb-x) by design. */
.dialog-x{position:absolute; top:12px; right:12px; z-index:2; width:40px; height:40px;
  display:flex; align-items:center; justify-content:center; appearance:none; border:0;
  background:transparent; color:var(--muted); font-size:20px; line-height:1; padding:0; cursor:pointer}
.dialog-x:hover{color:var(--coral)}
.dialog-x.flow{position:static; width:34px; height:34px; flex:0 0 auto}
.dialog-back{width:34px; height:34px; display:flex; align-items:center; justify-content:center;
  appearance:none; border:0; background:transparent; color:var(--muted); line-height:0; padding:0;
  cursor:pointer; flex:0 0 auto}
.dialog-back:hover{color:var(--coral)}

/* ── #22b · the DISMISSAL GRAMMAR (gap 10, v516) — where the glyph sits, by surface type.
   Native muscle memory differentiates: a PAGE goes back top-left; a TASK DIALOG cancels
   top-left and COMMITS top-right; only OVERLAYS close top-right; SHEETS swipe down.
   THE CONTRACT: the DEFAULT above is unchanged — x stays top-right — so any surface that
   does not opt in cannot shift. A surface opts in by adding .task (or .hide-narrow) to its
   own .dialog-x. Every rule here is PHONE-ONLY: in wide mode x top-right is fully correct
   (that is where a window closes) for every type, so desktop is byte-identical to v515. */
@media(max-width:760px){
  .dialog-x.task{left:12px; right:auto}          /* task dialog — dismissal is a form of going back */
  .dialog-x.flow.task{order:-1}                  /*   …the flow variant sits in a flex bar: first child */
  .hide-narrow{display:none !important}          /* a PAGE that also carried an x — the ‹ is the only door */
}
/* ⚠ PAIRING RULE: if the header also carries a TITLE, .task/.corner MUST be paired with .hbar
   below. Absolute glyphs over a flowing title is exactly the v516 bug — the title has no width
   to shrink into and the glyph lands on top of it. Bare .task is only safe on a titleless head. */
/* the ‹ in a block .modal-head, where .dialog-back (a flow element) can't reach. A surface that
   reads as a PAGE on the phone but must stay a desktop window shows BOTH, one per breakpoint:
   the x carries .hide-narrow, this carries the mirror. Same close fn behind each. */
.dialog-back.corner{position:absolute; top:12px; left:12px; z-index:2; width:40px; height:40px}
@media(min-width:761px){ .dialog-back.corner{display:none} }   /* wide keeps its x top-right */
/* the restored COMMIT SLOT — a task dialog's real verb, top-right in the header, in the slot
   the x vacates. Text button, not a pill: it lives in chrome, not in the content. Geometry
   MIRRORS .dialog-x exactly (absolute by default for the block .modal-head; .flow for the
   flex bars), so the two are interchangeable corners rather than two different systems. */
.dialog-commit{position:static; appearance:none; border:0; cursor:pointer; flex:0 0 auto;
  background:var(--coral); color:#fff; border-radius:var(--radius-m, 8px); padding:7px 13px;
  font-family:var(--sans); font-size:13.5px; font-weight:600; line-height:1.25;
  -webkit-tap-highlight-color:transparent; white-space:nowrap}
.dialog-commit:disabled{background:var(--line); color:var(--muted); cursor:default}
.dialog-commit:not(:disabled):active{filter:brightness(.93)}
.dialog-commit:not(:disabled):hover{filter:brightness(1.04)}
@media(min-width:761px){ .dialog-commit{display:none} }   /* wide keeps its footer buttons — unchanged */

/* ── the HEADER BAR (v517) · ✕ — title — CTA on ONE row ────────────────────────────────────
   v516 shipped the ✕ and the CTA as ABSOLUTE corners over a still-flowing title, which on a
   real phone overlapped it, sat off its baseline, and left the title no width to shrink into
   (owner-caught on device — the emulator never showed it because the mock had always drawn a
   flex BAR, and the code hadn't built one).
   The fix needs no markup surgery on any surface: flex `order` composes the bar out of the
   head's EXISTING children. The glyphs go static and take the ends, the title flexes and
   truncates between them, and everything else — kicker, sub, search, sort — wraps below.
   Opt in by putting .hbar on the head; phone-only, like the rest of #22b. */
@media(max-width:760px){
  .modal-head.hbar{display:flex; flex-wrap:wrap; align-items:center; column-gap:10px}
  .modal-head.hbar>*{flex:0 0 100%; order:4}                    /* default: its own row, under the bar */
  .modal-head.hbar>.modal-kicker{order:0}                       /* the kicker rides ABOVE the bar */
  .modal-head.hbar>.dialog-x.task,
  .modal-head.hbar>.dialog-back.corner{position:static; order:1; flex:0 0 auto;
    top:auto; left:auto; right:auto; width:32px; height:32px; margin-left:-7px}
  .modal-head.hbar>.modal-title{order:2; flex:1 1 0; min-width:0; margin:0; font-size:17.5px;
    white-space:nowrap; overflow:hidden; text-overflow:ellipsis}   /* the ONLY thing allowed to shrink */
  .modal-head.hbar>.dialog-commit{order:3; flex:0 0 auto}
  .modal-head.hbar>.modal-sub{margin-top:6px}
  /* a flex bar that already exists in markup (the library overlay) only needs its title to yield */
  .lib-htop>.lib-title{min-width:0; white-space:nowrap; overflow:hidden; text-overflow:ellipsis}
}
/* the footer verbs a header commit replaces — .hide-narrow above does the hiding (it is
   !important because these footers carry display:flex/inline-flex of their own). */

/* ── #23 · the ONE search-box chrome (v353) — built by PersonaSearch.attachChrome:
   a clear-× at the input's right edge + the "Try searching" focus panel. Shared by
   room-ui's three pickers, the console's Personas view, and the Studio browser. */
.search-box{position:relative; display:block}
.search-box>input{padding-right:34px}
.search-x{position:absolute; top:50%; right:6px; transform:translateY(-50%); appearance:none; border:0; background:transparent;
  color:var(--muted); font-size:18px; line-height:1; cursor:pointer; padding:4px 7px; display:none; z-index:2}
.search-x:hover{color:var(--coral)}
.search-x.on{display:block}
.ps-suggest{margin-top:13px}
/* ⚠ v894 (owner) — THREE ROWS, AND THE DEVICE'S WIDTH DECIDES HOW MANY TERMS THAT IS. The panel
   wrapped as far as it liked, so on a narrow phone the same term list became six or seven rows and
   pushed the results it is meant to introduce off the screen. The cap is expressed in the panel's
   OWN units — the row box plus one row gap, three times, less the gap that follows the last row —
   so tuning the type or the gap moves the ceiling with it and no number here goes stale.
   `overflow:hidden` and not a scroller: a fourth row you can reach by scrolling INSIDE a panel that
   is itself above a scrolling list is two scrollers in one gesture. What does not fit is not offered. */
/* ⚠ --ps-row is the term's own LINE BOX (measured 16.5 at the 13.5px type), not a round number:
   set too generous, the cap falls between rows and a 10px sliver of the fourth peeks over the
   edge — which reads as a clipping fault rather than a boundary. Rounded UP to 17 so three rows
   always clear, and the fourth starts below the ceiling and is never seen at all. */
.ps-terms{--ps-row:17px; --ps-rowgap:10px;
  display:flex; flex-wrap:wrap; gap:var(--ps-rowgap) 18px;
  max-height:calc(var(--ps-row) * 3 + var(--ps-rowgap) * 2); overflow:hidden}
.ps-term{-webkit-tap-highlight-color:transparent; cursor:pointer; appearance:none; border:0; background:transparent; padding:0;
  font-family:var(--sans); font-size:13.5px; color:var(--ink); display:inline-flex; align-items:center; gap:6px; white-space:nowrap; transition:color .12s}
.ps-term:hover{color:var(--coral)}
.ps-term svg{opacity:.5; flex:0 0 auto}

/* ── #32 · THE ONE SCROLLBAR (v525) ───────────────────────────────────────────────────────
   A thin floating pill in the soft --line colour (→ --muted on hover), a transparent track
   that blends into the surface, and no arrow buttons. Theme-driven, so it follows light/dark
   for free. This lived only in room-ui, which left every OTHER page — most visibly the Studio,
   a separate document inside an iframe — with the platform's default chunky grey bar sitting
   against our chrome (owner-caught on desktop). Moving it to the shared layer is the whole fix:
   every page that links this file now matches, embedded frames included. Pages keep only their
   own EXCEPTIONS (room-ui still hides the bar on its in-message scrollers on narrow). */
*{ scrollbar-width:thin; scrollbar-color:var(--line) transparent; }            /* Firefox */
::-webkit-scrollbar{ width:10px; height:10px; }
::-webkit-scrollbar-track{ background:transparent; }
::-webkit-scrollbar-thumb{ background:var(--line); border:2px solid transparent;
  background-clip:padding-box; border-radius:8px; }
::-webkit-scrollbar-thumb:hover{ background:var(--muted); }
::-webkit-scrollbar-button{ display:none; width:0; height:0; }                 /* drop the up/down arrows */
::-webkit-scrollbar-corner{ background:transparent; }

/* ── #31 · NO OS TEXT-SELECTION ON TOUCH (v522) ───────────────────────────────────────────
   An accidental selection on a phone summons chrome the page cannot touch: Android Chrome
   raises a bottom bar (Translate · Search · Copy), iOS pops its callout. Neither can be
   styled, moved or dismissed by us — the ONLY lever the web has is not letting a selection
   start in the first place (the finding behind docs/text-selection.html: native selection UI
   is unsuppressable, so we gave it up and built our own).
   room-ui had already done this for its chat scroller; the Studio never did, so a tap on a
   persona card raised Chrome's bar (owner-caught on Android). Making it the APP-WIDE default
   is the fix — every page that links this file inherits it, including the embedded builder.
   THREE deliberate choices:
   • COARSE POINTERS ONLY — a mouse keeps native selection everywhere. Desktop is untouched.
   • NOT !important — every existing opt-in still wins on ordinary specificity (a code box's
     user-select:all, the text sheet's own rules). Nothing already working gets overridden.
   • Fields and READING surfaces opt back in. Text you are meant to copy must stay copyable;
     .sel-text is the hook for anything else that needs it. On touch, partial-text selection
     in the room lives in the double-tap text sheet, which owns its own selection UI. */
@media (pointer:coarse){
  body{-webkit-user-select:none; user-select:none; -webkit-touch-callout:none}
  input, textarea, [contenteditable], [contenteditable] *, .sel-text, .sel-text *{
    -webkit-user-select:text; user-select:text; -webkit-touch-callout:default}
}

/* ── #30 · the ONE toast (v354; softened v418) — a warm taupe pill (was a near-black/near-white
   inverted-ink slab: --ink/--bg was too high-contrast against the cream page); bottom-centre;
   room + console share it. Colours ride the --toast-* tokens so both themes soften in lockstep;
   the CTA counter-inverts via --toast-cta. The .lite variant (notice toasts) still overrides to a
   surface pill in room-ui. */
.toast{position:fixed; left:50%; bottom:30px; transform:translateX(-50%) translateY(8px); z-index:300;
  background:var(--toast-bg); color:var(--toast-fg); border:0.5px solid var(--toast-bd); font-family:var(--sans); font-size:13px; padding:10px 16px;
  border-radius:22px; box-shadow:var(--toast-sh); opacity:0; pointer-events:none;
  transition:opacity .2s, transform .2s; max-width:80vw; text-align:center}
.toast.show{opacity:1; transform:translateX(-50%) translateY(0)}

/* ── #28 · the pill family standard (v356) — bordered transparent pill, coral when .on;
   radius = --radius-pill, padding 4px 12px, 12px text. The named chips (.chip/.pb-pill/
   .sd-chip/.wc-chip/.fb-tag) follow this spec in their own files; .lib-mine (the
   owner-filter pill on the Studio browser) lives here. Uppercase micro-label tracking
   is two steps: .06em dense · .12em kicker. Builder status chips are a deliberate variant. */
.lib-mine{margin-top:9px; font-family:var(--sans); font-size:12px; font-weight:600; -webkit-tap-highlight-color:transparent;
  border:1px solid var(--line); background:transparent; color:var(--muted); border-radius:var(--radius-pill); padding:4px 12px; cursor:pointer}
.lib-mine.on{border-color:var(--coral); color:var(--coral); background:var(--coral-soft)}

/* ── #29 · the ONE popover engine (v357) — kebab/row/card menus share this base; each page
   keeps only its z-index (the ladders differ) and its variants (.hz, .danger, .sep, hovers).
   The composer's bottom-anchored model-menu and the md-viewers stay variants by design. */
.popmenu, .rowmenu, .cardmenu{position:fixed; min-width:150px; max-width:240px;
  background:var(--surface); border:1px solid var(--line); border-radius:var(--radius-l);
  box-shadow:var(--shadow-2); padding:5px; display:none; font-family:var(--sans)}
.popmenu.open, .rowmenu.open, .cardmenu.open{display:block}
.popmenu button, .rowmenu button, .cardmenu button{display:flex; align-items:center; gap:9px; width:100%;
  text-align:left; appearance:none; border:0; background:transparent; color:var(--ink); cursor:pointer;
  font-size:13.5px; padding:8px 11px; border-radius:var(--radius-s); font-family:inherit}

/* ── #25 · the ONE dialog scaffold (v358) — the scrim + the 580 card, shared by the
   main dialogs (.modal), the persona profile (.vp), and the Me sheet (.mep); the
   survey/confirm minis share the card VISUAL (own width/padding). Pages keep only
   deltas: z-ladder (80 · vp 84 · mep 68 · pb 62), heights, the sheet's --bg fill.
   Full-screen-on-narrow + hero-collapse stay with their owners in room-ui. */
.modal-scrim, .vp, .mep{position:fixed; inset:0; background:rgba(0,0,0,.40); opacity:0; visibility:hidden;
  transition:opacity .18s; display:grid; place-items:center; padding:20px}
.modal-scrim.open, .vp.open, .mep.open{opacity:1; visibility:visible}
.modal, .vp-card, .mep-card, .survey-card, .vc-card{background:var(--surface); border:1px solid var(--line);
  border-radius:var(--radius-xl); box-shadow:var(--shadow-3); transform:translateY(10px) scale(.99); transition:transform .18s}
.modal, .vp-card, .mep-card{width:580px; max-width:100%; display:flex; flex-direction:column}
.modal-scrim.open .modal, .vp.open .vp-card, .mep.open .mep-card,
.modal-scrim.open .survey-card, .modal-scrim.open .vc-card{transform:none}

/* ── #26 · the TWO button languages (v359) — decided: PILL = floating CTA · RECT = in-dialog action.
   The rect pair lives here once (.btn-primary/.btn-ghost; the console's .adm-btn IS the ghost and
   its .primary modifier IS the primary). The coral CTA base is shared by the named pills below —
   each site keeps only its size/context. Variants by design: .vc-cancel (ghost pill), .vc-danger
   (red confirm), round icon-buttons (share's composer .go), the .new-nudge floating pill. */
.btn-ghost, .adm-btn{appearance:none; border:1px solid var(--line); background:transparent; color:var(--muted);
  border-radius:var(--radius-m); padding:9px 16px; cursor:pointer; font-family:var(--sans); font-size:13.5px}
.btn-ghost:hover, .adm-btn:hover{border-color:var(--coral); color:var(--coral)}
.btn-ghost:disabled, .adm-btn:disabled{opacity:.45; cursor:not-allowed}
.btn-primary, .adm-btn.primary{appearance:none; border:1px solid var(--coral); background:var(--coral); color:#fff;
  border-radius:var(--radius-m); padding:9px 18px; cursor:pointer; font-family:var(--sans); font-size:13.5px; font-weight:600}
.btn-primary:disabled{opacity:.45; cursor:not-allowed}
.cta-pill, .vp-chat, .d-chat, .dv-notif, .sel-done, .vcp-post, .sg-btn, .sd-go, .vc-go{appearance:none; cursor:pointer;
  border:1px solid var(--coral); background:var(--coral); color:#fff; border-radius:var(--radius-pill);
  font-family:var(--sans); font-weight:600; -webkit-tap-highlight-color:transparent}
