/* ═══════════════════════════════════════════════════════════════════════════════
   THE BEBERT SHELL — one warm room, and a sheet in it.
   ═══════════════════════════════════════════════════════════════════════════════

   THE SACRED RULE: the board is the whole screen; the talk is a SHEET over it.
   The model TALKS in the bottom sheet and SHOWS on the board behind it. Nothing
   ever navigates away from the conversation.

   ⚠ THIS FILE IS A SHARED COMPONENT LIBRARY, not just the chathome shell.
     `game.html` (world mode) and `line.html` link it too, and every module under
     js/spaces/*.js paints with its `.ch-*` vocabulary. 183 class contracts live
     here. Rename nothing without grepping the whole `public/` tree first.

   TOKENS: the room is `--board / --sheet / --surface / --ink / --muted / --line`,
   the mates are `--persona-{bebert,bebert,wendy,frank}`, and `--persona-current`
   (set per turn on :root by chathome.js applyTurn) is the ONE accent every
   accented thing reads. Dark is the base; light is a pure custom-property
   override — zero markup changes. The old persimmon `--ch-accent` is RETIRED.
   The `--ch-*` names survive as ALIASES of the room tokens (see §2) because the
   whole library speaks them.

   TOC
     §1  fonts
     §2  tokens — dark room (base) + --ch-* compat aliases
     §3  tokens — light room (override only)
     §4  tokens — the mates: --persona-current + --ink-on-accent
     §5  document + shell
     §6  THE BOARD — the full-bleed layer
     §7  THE SHEET — the talk, over the board
     §8  bubbles + the in-bubble mascot header
     §9  composer + voice notes
     §10 the menu sheet + THE FACES ROW
     §11 boards — the space-view vocabulary
     §12 ceremony cards, chips, tour
     §13 the daybook, in this room
     §14 boot, rotate, toast
     §15 foreign hosts of this library (world mode, /line)
   ═══════════════════════════════════════════════════════════════════════════════ */

* {
  box-sizing: border-box;
  -webkit-tap-highlight-color: transparent;
}

/* ── §1 fonts ─────────────────────────────────────────────────────────────────
   Self-hosted + swap: the app is usable on the system stack before they land.
   CSP is `default-src 'self'` — Google Fonts is BLOCKED, never link it. */
@font-face {
  font-family: "Baloo 2";
  src: url("/fonts/baloo2-latin-var.woff2") format("woff2");
  font-weight: 400 800; /* real fvar range (wght 400..800) */
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Nunito";
  src: url("/fonts/nunito-latin-var.woff2") format("woff2");
  font-weight: 200 1000; /* real fvar range (wght 200..1000) */
  font-style: normal;
  font-display: swap;
}

/* ── §2 tokens — DARK ROOM (the base) ─────────────────────────────────────── */
:root {
  --ch-sat: env(safe-area-inset-top, 0px); /* read by chathome.js measure() — the top-peek (Maps rule) must clear the notch */
  /* the room — FABLE dark (design handoff 2026-07-21): near-neutral black that
     catches spill from lit objects; dot grid only where nothing is lit. */
  --board: #0b0d08;
  --board-dot: rgba(240, 230, 207, 0.09);
  --sheet: #14180f;
  --surface: #171c11;
  --ink: #e9eee1;
  --muted: #8b9479;
  --line: rgba(233, 238, 225, 0.12);
  --sheet-shadow: 0 -14px 44px rgba(0, 0, 0, 0.5);
  --sheet-hair: rgba(233, 238, 225, 0.08);
  --card-shadow: 0 6px 18px -8px rgba(0, 0, 0, 0.5);
  --scrim: rgba(5, 7, 4, 0.6);
  /* the user's own bubble is NEUTRAL — the mates own all the color */
  --you-bg: #efe9db;
  --you-ink: #1a1a14;

  /* the mates */
  --persona-bebert: #a3e635;
  --persona-peachy: #ef9663;
  --persona-wendy: #1fc3ab;
  --persona-frank: #ff5a60;
  /* frank's bubble edge — FIRE ENGINE, at full strength (KP 2026-07-16). frank and
     bebert are neighbours on the wheel: as a 40% wash their borders sit ΔE 16 apart
     and the room tint only ΔE 2.8, so a glance couldn't tell whose turn it was. The
     edge is where identity actually lives, so frank's gets shouted instead of mixed:
     ΔE 74 from bebert's, and it reads as frank without needing the nameplate. */
  --frank-edge: #ff2a1f;
  /* whose turn it is. applyTurn re-points this on :root every time the face
     changes; §4 keeps it honest from the data-persona attribute alone. */
  --persona-current: var(--persona-bebert);
  --ink-on-accent: #1a2405;

  /* type */
  --font-display: "Baloo 2", ui-rounded, system-ui, -apple-system, sans-serif;
  --font-body: "Nunito", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
    sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

  /* geometry */
  --ch-board-top: calc(env(safe-area-inset-top) + 38px); /* under the board label */
  --ch-sheet-radius: 26px;

  /* ── --ch-* COMPAT ALIASES ────────────────────────────────────────────────
     Not a second palette — one source of truth, two vocabularies. Every
     js/spaces module + line.html + world mode paints with these names, and
     game.css's #gw-window-body re-declares them to re-skin the same components
     per mood (see §15). Aliasing keeps all of that working for free.
     `--ch-accent` is deliberately ABSENT: the persimmon retired, and every
     consumer in this file now reads --persona-current. */
  --ch-bg: var(--board);
  --ch-panel-bg: var(--board);
  --ch-chat-bg: var(--sheet);
  --ch-surface: var(--surface);
  --ch-ink: var(--ink);
  --ch-muted: var(--muted);
  --ch-line: var(--line);
  /* `--ch-divider-line` / `--ch-grip` / `--ch-grip-strong` are GONE: they were
     shell-only, this shell's grip is --persona-current, and nothing else in the
     tree ever read them (grep before resurrecting). game.css still declares them
     on #gw-window-body — harmless, and not ours to touch. */
}

/* ── §3 tokens — LIGHT ROOM (override only — same markup) ─────────────────── */
@media (prefers-color-scheme: light) {
  :root {
    /* FABLE light / paper (design handoff 2026-07-21): the board is the white
       chalkboard; the chat channel carries the mate tint — one tinted surface. */
    --board: #fffdf8;
    --board-dot: rgba(120, 90, 60, 0.16);
    --sheet: #e6e3cb;
    --surface: #ffffff;
    --ink: #332a1e;
    --muted: #8f8471;
    --line: rgba(51, 42, 30, 0.12);
    --sheet-shadow: 0 -14px 44px rgba(45, 34, 51, 0.18);
    --sheet-hair: rgba(51, 42, 30, 0.08);
    --card-shadow: 0 2px 8px -4px rgba(45, 34, 51, 0.12);
    --scrim: rgba(51, 42, 30, 0.34); /* warm brown ink, never the cool mauve */
    --you-bg: #372c22;
    --you-ink: #ffffff;
    /* darkened one step to hold contrast on cream */
    --persona-bebert: #59a616;
    --persona-peachy: #d97a4e;
    --persona-wendy: #0f8579;
    --persona-frank: #cc3038;
    --frank-edge: #ce2029; /* the canonical fire engine — holds 5.4:1 on the white bubble */
  }
}

/* ── §4 tokens — the mates: --persona-current + --ink-on-accent ────────────────
   applyTurn sets `data-persona` on :root and NOTHING ELSE — these rules are the
   single owner of both tokens, so a face can never be half-applied. Keep it that
   way: writing --persona-current inline (as setPersonaState once did) beats every
   rule here AND `:root.ch-biz` below, which strands the business override and
   desyncs the pair — the accent stuck on the inline face while --ink-on-accent,
   with no inline twin, flipped to wendy's. One owner = they move together.
   The var() indirection means the light-room block above re-resolves every one of
   these for free. */
/* brand-keep: "bebert" here is the MATE, not the product name. The eightmate
   build (/root/eightmate-app/pwa/brand.py) sweeps Bebert->Bebert at deploy time;
   unprotected, it rewrote this selector to data-persona="bebert" — a DUPLICATE
   that outranked bebert's own rule, so the wrong mate's hue shipped. */
/*brand-keep*/
:root[data-persona="bebert"] {
  --persona-current: var(--persona-bebert);
  --ink-on-accent: #1a2405;
}
:root[data-persona="peachy"] {
  --persona-current: var(--persona-peachy);
  --ink-on-accent: #2b1207;
}
:root[data-persona="wendy"] {
  --persona-current: var(--persona-wendy);
  --ink-on-accent: #062621;
}
:root[data-persona="frank"] {
  --persona-current: var(--persona-frank);
  --ink-on-accent: #2b0608;
}
/*brand-keep-end*/

/* ── §5 document + shell ──────────────────────────────────────────────────── */
html,
body {
  margin: 0;
  height: 100%;
  /* Lock the document so ONLY the shell (.ch-root) moves under the iOS keyboard
     — matches game.css. The shell follows the visual viewport via JS
     (js/lib/shell-viewport.js); the body must not also drift. */
  position: fixed;
  inset: 0;
  width: 100%;
  overflow: hidden;
  background: var(--board);
  color: var(--ink);
  font: 400 16px / 1.5 var(--font-body);
  -webkit-font-smoothing: antialiased;
  overscroll-behavior: none;
}

.ch-root {
  position: fixed;
  inset: 0;
  height: 100dvh;
  overflow: hidden;
}

/* ── §6 THE BOARD — the full-bleed layer ──────────────────────────────────────
   The room IS the screen. The sheet floats over it; the board never resizes, so
   there is no seam to butt anything against.
   ⚠ id, not class: `.ch-board` is TAKEN (the world-board content cards, §11) and
     `#ch-panel` is what chathome.js queries (`#ch-panel .ch-panel-inner`). Both
     names are load-bearing — don't "tidy" either. */
#ch-panel {
  position: absolute;
  inset: 0;
  overflow: hidden;
  background-color: var(--board);
  background-image: radial-gradient(var(--board-dot) 1px, transparent 1px);
  background-size: 22px 22px;
}

/* whose board it is — mascot head + the lowercase label (mascot place 2 of 4) */
.ch-board-label {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 7px;
  padding: calc(env(safe-area-inset-top) + 8px) 16px 8px;
  pointer-events: none;
}
.ch-board-face {
  width: 22px;
  height: 22px;
  flex: none;
  display: block;
}
.ch-board-name {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.06em;
  color: var(--muted);
  text-transform: lowercase;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* the mounted view. Pinned top/bottom so height:100% children (the daybook,
   .ch-space-view) resolve against a definite box.
   `bottom` is the SHEET's top edge, not the screen's: the board paints full-bleed
   (no seam), but its CONTENT must lay out in the strip the sheet isn't covering —
   the sheet is opaque, so anything under it is simply gone. applyH() publishes
   --ch-sheet-h on #ch-panel every frame; the fallback keeps the box sane pre-JS. */
.ch-panel-inner {
  position: absolute;
  left: 0;
  right: 0;
  top: var(--ch-board-top);
  bottom: var(--ch-sheet-h, 110px);
  overflow-y: auto;
  padding: 8px 14px 16px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  /* the query container for boards that must adapt to the strip they're given
     (see .ch-hello). Its height is already definite (top/bottom), so size
     containment costs nothing and the daybook's height:100% chain still resolves. */
  container-type: size;
  container-name: board;
}
/* a mounted view (e.g. the calendar) goes full-bleed — drop the card padding */
.ch-panel-inner.ch-space-mounted {
  padding: 0;
  gap: 0;
  overflow: hidden;
}

/* THE FELT-EMOTION TAKEOVER (KP 2026-07-31). A wheel painting covers the board for
   two seconds when the model feels something strong, then fades and hands it back.
   Deliberately a SIBLING of .ch-panel-inner, never a child: a space module re-mounts
   by replacing its own innerHTML on nearly every turn (Gotcha 29), and a child would
   be deleted mid-flash. Same two vars as the strip, so a drag during the flash moves
   it with the board. z-index 3 clears the board label (2) but stays under the sheet
   (6) — the chat is never occluded, whatever the AI is feeling. */
.ch-feel-flash {
  position: absolute;
  left: 0;
  right: 0;
  top: var(--ch-board-top);
  bottom: var(--ch-sheet-h, 110px);
  z-index: 3;
  overflow: hidden;
  pointer-events: none;
}
.ch-feel-flash svg {
  width: 100%;
  height: 100%;
  display: block;
}
@media (prefers-reduced-motion: reduce) {
  .ch-feel-flash {
    transition: none !important;
  }
}
/* the calendar mounts #daybook inside a bare wrapper div; that wrapper must carry a
   definite height so #daybook's height:100% resolves — otherwise .db-scroll (flex:1)
   and the absolute % day-grid collapse to 0 and the paper page renders blank. */
#ch-panel .ch-cal-mount {
  height: 100%;
}
#ch-panel #daybook {
  height: 100%;
  max-height: none;
  min-height: 0;
  border: none;
  border-radius: 0;
}

/* the hello board — the mate greets you at open, full character (mascot place 1 of 4) */
.ch-hello {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  text-align: center;
  gap: 2px;
  padding: 26px 32px 24px;
  animation: ch-pop 0.25s ease;
}
/* Sized for the mascot <img>, but ALSO for a bare emoji <div> — spaces/sports.js
   reuses this whole .ch-hello block with `<div class="ch-hello-face">🏅</div>`.
   width/height frame the img; font-size/line-height are inert on it and are what
   actually draw the glyph in the div. One rule, both shapes — don't split it. */
.ch-hello-face {
  width: 100px;
  height: 100px;
  flex: none;
  display: block;
  font-size: 62px;
  line-height: 100px;
  text-align: center;
  animation: ch-hello-bob 3.6s ease-in-out infinite;
}
.ch-hello-h {
  margin: 8px 0 0;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 22px;
  letter-spacing: -0.01em;
  color: var(--ink);
}
.ch-hello-sub {
  margin: 6px auto 0;
  max-width: 280px;
  color: var(--muted);
  font-size: 13.5px;
  line-height: 1.5;
}
@keyframes ch-hello-bob {
  0%,
  100% {
    transform: translateY(1px) rotate(-3deg);
  }
  50% {
    transform: translateY(-6px) rotate(3deg);
  }
}
@media (prefers-reduced-motion: reduce) {
  .ch-hello-face {
    animation: none;
  }
}
/* The hello must survive the board it actually gets. The app opens chat-forward
   (detent 1 = board ⅓ ≈ 205px of usable strip on a 390×844), and the full-dress
   hello — 100px mascot + display heading + two lines — needs ~222px. Unfixed, the
   greeting clipped mid-sentence on first open: the first thing a new user ever
   sees. Shrink the mate and tighten the rhythm when the strip is short; the full
   100px returns the moment the board is pulled up. */
@container board (max-height: 250px) {
  .ch-hello {
    padding: 12px 28px 10px;
  }
  .ch-hello-face {
    width: 72px;
    height: 72px;
    font-size: 46px;
    line-height: 72px;
  }
  .ch-hello-h {
    margin-top: 2px;
    font-size: 19px;
  }
  .ch-hello-sub {
    font-size: 12.5px;
  }
}
/* container queries are Chrome 105/Safari 16 — on older WebViews the block above
   drops and the first-run hello clips mid-sentence again (the exact scar this rule
   fixed). @supports-not + a short-screen media floor covers the phone case. */
@supports not (container-type: size) {
  @media (max-height: 700px) {
    .ch-hello {
      padding: 12px 28px 10px;
    }
    .ch-hello-face {
      width: 72px;
      height: 72px;
      font-size: 46px;
      line-height: 72px;
    }
    .ch-hello-h {
      margin-top: 2px;
      font-size: 19px;
    }
    .ch-hello-sub {
      font-size: 12.5px;
    }
  }
}

/* (`.ch-brain-tab`, the old design's teeny corner label, is GONE — .ch-board-label
   above replaces it, and nothing in the tree ever mounted it.) */

/* ── §7 THE SHEET — the talk, over the board ──────────────────────────────────
   Anchored to the bottom, rounded top, its own upward shadow, overflow hidden.
   chathome.js sets its HEIGHT (the detents live there); at the lowest detent it
   is grip + composer only, so you can always talk. */
#ch-sheet {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 6;
  height: 68dvh; /* pre-JS default ≈ detentIdx 1; JS takes over on boot */
  display: flex;
  flex-direction: column;
  /* THE ROOM WEARS THE MATE (KP 2026-07-16). The tint lives on the sheet, not the
     bubble: whoever is talking colours the whole room they're talking in, and the
     model's bubbles stay clean white so the text keeps maximum contrast. Swapped
     from the handoff, which tinted each bubble ~12% and left the sheet constant.
     Kept low (7%) — this is a large field behind body text, not an accent. */
  background: var(--sheet); /* fallback: color-mix is Chrome 111/Safari 16.2 — without this the SHEET went transparent on older WebViews */
  background: color-mix(in srgb, var(--persona-current) 7%, var(--sheet));
  transition: background 420ms ease; /* the room changes hands, it doesn't blink */
  border-radius: var(--ch-sheet-radius) var(--ch-sheet-radius) 0 0;
  border-top: 1px solid var(--sheet-hair);
  box-shadow: var(--sheet-shadow);
  overflow: hidden;
  contain: strict; /* keep the per-frame resize cheap */
  will-change: height;
}

/* the grip — the thing you grab CARRIES WHOSE TURN IT IS */
.ch-divider {
  position: relative; /* the hint floats; the pill stays DEAD centre */
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  /* KP dogfood 2026-07-26: "still a little too thin for me, and the bottom
     margin underneath the little green line" — bottom 4px → 14px so the pill
     sits in air instead of on the composer's forehead. Safe to change: the
     sheet floor is MEASURED (divider.offsetHeight + form.offsetHeight in
     measure()), never a constant, so the drag math follows automatically. */
  padding: 10px 0 14px;
  touch-action: none; /* we own the gesture */
  cursor: grab;
  -webkit-user-select: none;
  user-select: none;
}
.ch-grip {
  /* 6px → 9px, same pass: thin enough to miss with a thumb on a phone. */
  width: 56px;
  height: 9px;
  border-radius: 999px;
  background: var(--persona-current);
  transition: width 0.16s ease, opacity 0.16s ease;
}
.ch-divider.is-grabbed {
  cursor: grabbing;
}
.ch-divider.is-grabbed .ch-grip {
  width: 68px;
  animation: none;
}

/* first-run "drag me" nudge — bobs to invite a clueless user, gone after a couple
   drags. ABSOLUTE on purpose: as a flex sibling it shoved the pill off-centre
   (justify-content centred the PAIR), and the grip must read as dead-centre. */
.ch-hintlabel {
  display: none;
  position: absolute;
  left: calc(50% + 36px);
  top: 50%;
  transform: translateY(-50%);
  font-size: 11px;
  letter-spacing: 0.03em;
  color: var(--muted);
  user-select: none;
  white-space: nowrap;
  pointer-events: none;
}
.ch-divider.show-hint .ch-hintlabel {
  display: block;
}
.ch-divider.show-hint .ch-grip {
  animation: ch-bob 1.6s ease-in-out infinite;
}
@keyframes ch-bob {
  0%,
  100% {
    transform: translateY(2.5px);
  }
  50% {
    transform: translateY(-3px);
  }
}
@media (prefers-reduced-motion: reduce) {
  .ch-divider.show-hint .ch-grip {
    animation: none;
  }
}

/* `overflow: hidden` is what actually makes the chat GO AWAY at the bottom detent.
   .ch-chat correctly computes to 0 there — but a flex item can never be shorter than
   its own padding (border-box floors at padding+border), so .ch-log bottomed out at
   8+6 = 14px and OVERFLOWED its 0-height parent: a sliver of the last message hanging
   over the composer, "almost invisible but not actually invisible" (KP, 2026-07-16).
   min-height:0 lets the log TRY to shrink; only the clip finishes the job. Don't drop
   either — the log keeps its own overflow-y:auto for scrollback, so this clips nothing
   a user should see. */
.ch-chat {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
.ch-log {
  flex: 1;
  overflow-y: auto;
  padding: 8px 12px 6px;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
}
/* Newest-at-the-bottom, WITHOUT `justify-content: flex-end`.
   ⚠ DO NOT "restore" justify-content here (the frames show it; they never
     overflowed). On a real scroll container it collapses scrollHeight to
     clientHeight — content overflowing the START edge becomes unreachable and
     the user can NEVER scroll back through the conversation. Measured: the first
     bubble sat 238px above the top with scrollTop pinned at 0.
   `margin-top: auto` on the first child bottom-aligns a short log and resolves
   to 0 once there's no free space, so scrollback works. */
.ch-log > :first-child {
  margin-top: auto;
}

/* ── §8 bubbles + the in-bubble mascot header ─────────────────────────────────
   The mates own all the color; the user's bubble is neutral cream/ink. The
   mascot appears ONLY on the first bubble of a speaker run (mascot place 4 of 4)
   and it goes INSIDE the bubble as a header row — this replaced both the outside
   avatar and the old `.ch-persona` chip. */
.ch-bubble {
  max-width: 82%;
  padding: 9px 14px;
  border-radius: 18px;
  line-height: 1.4;
  font-size: 15.5px;
  animation: ch-pop 0.18s ease;
}
/* --bubble-hue = the face that spoke THIS bubble (data-persona is stamped
   per-bubble in applyTurn), so scrollback keeps EACH speaker's color instead of
   recoloring to whoever is talking now. One formula, four aliases. */
.ch-bubble.ai {
  --bubble-hue: var(--persona-bebert);
  align-self: flex-start;
  /* Clean surface — white in the light room. The mate's colour is carried by the
     ROOM (#ch-sheet) and by this 1px border; the bubble itself stays uncoloured so
     body text sits on maximum contrast. Matches frame 1G: #ffffff + a 40% persona
     border. (The handoff's ~12% wash moved to the sheet — KP 2026-07-16.) */
  background: var(--surface);
  border: 1px solid var(--line);
  border: 1px solid color-mix(in srgb, var(--bubble-hue) 40%, var(--line));
  border-bottom-left-radius: 6px;
}
/* brand-keep: the mate's name, not the product's — see the note at :root[data-persona]. */
/*brand-keep*/
.ch-bubble.ai[data-persona="peachy"] {
  --bubble-hue: var(--persona-peachy);
}
/*brand-keep-end*/
.ch-bubble.ai[data-persona="wendy"] {
  --bubble-hue: var(--persona-wendy);
}
/* frank's edge is NOT mixed down to 40% like the others — it's the full fire engine.
   He's the one mate a glance kept confusing with bebert; the shout is the fix. */
.ch-bubble.ai[data-persona="frank"] {
  --bubble-hue: var(--persona-frank);
  border-color: var(--frank-edge);
}
/* THINKING: nobody has spoken yet — the dealer hasn't dealt, so this bubble has no
   data-persona and would inherit the .ch-bubble.ai default (bebert's lime). A green
   "…" promises bebert and then bebert answers. Stay neutral until the face is known.
   The same element becomes the reply (chathome.js stamps data-persona on it), so the
   moment the dots are replaced this stops matching and the real hue lands. */
.ch-bubble.ai:has(.ch-typing),
.ch-bubble.ai.is-typing {
  /* class twin: :has() is Safari 15.4/Chrome 105 — old WebViews drop it and the thinking bubble wears the wrong mate's tint */
  background: var(--surface);
  border-color: var(--line);
}
.ch-bubble.you {
  align-self: flex-end;
  background: var(--you-bg);
  color: var(--you-ink);
  border-bottom-right-radius: 6px;
}
@keyframes ch-pop {
  from {
    opacity: 0;
    transform: translateY(4px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* the header row: ~20px head + "bebert's here", on its own line ABOVE the text */
/* THE ARRIVAL (KP ruling 2026-07-26). The thinking bubble deliberately wears the
   LAST speaker's face: at the moment those dots appear nobody knows who will
   answer, because the dealer picks server-side after reading the message. So a
   face change is always a visible correction — and the fix KP chose is not to
   predict it but to make it LAND WELL. The mascot steps in (a small overshoot,
   like someone arriving) and the name follows it. Without this the swap reads as
   a glitch: one head instantly replaced by another. */
@keyframes ch-arrive {
  from {
    opacity: 0;
    transform: translateX(-7px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
@keyframes ch-arrive-face {
  from {
    opacity: 0;
    transform: scale(0.6);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}
.ch-said {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 0 0 5px;
  animation: ch-arrive 0.24s ease-out both;
}
.ch-said-face {
  width: 20px;
  height: 20px;
  flex: none;
  display: block;
  animation: ch-arrive-face 0.36s cubic-bezier(0.34, 1.56, 0.64, 1) both;
}
@media (prefers-reduced-motion: reduce) {
  .ch-said,
  .ch-said-face {
    animation: none;
  }
}
.ch-said-name {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.06em;
  text-transform: lowercase;
  color: var(--bubble-hue);
}

/* AI reply markdown */
.ch-bubble.ai p {
  margin: 0;
}
.ch-bubble.ai p + p {
  margin-top: 7px;
}
.ch-bubble.ai ul,
.ch-bubble.ai ol {
  margin: 5px 0;
  padding-left: 18px;
}
.ch-bubble.ai li {
  margin: 2px 0;
}
.ch-bubble.ai code {
  background: rgba(127, 127, 127, 0.18);
  padding: 1px 5px;
  border-radius: 5px;
  font-size: 0.92em;
}
.ch-bubble.ai a {
  color: var(--bubble-hue);
}
/* the dealer's mode this reply — a quiet badge under the AI text (admin x-ray) */
.ch-mode {
  display: block;
  margin-top: 5px;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.05em;
  text-transform: lowercase;
  color: var(--muted);
  opacity: 0.65;
}

/* typing dots */
.ch-typing {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px 1px;
}
/* The face that's about to answer, beside the dots (KP 2026-07-26). Sized to
   the dots' optical weight — big enough to read as a mascot, small enough that
   the bubble stays a thinking bubble and not an avatar row. */
.ch-typing-face {
  width: 20px;
  height: 20px;
  margin-right: 3px;
  flex: none;
  border-radius: 50%;
  object-fit: contain;
}
/* The summon flair (2026-07-21): the tapped mate's face pops in ahead of the
   typing dots while their entrance composes — small, springy, gone the moment
   the real greeting replaces the bubble. */
.ch-summon-face {
  border-radius: 50%;
  margin-right: 4px;
  animation: ch-summon-pop 0.45s cubic-bezier(0.34, 1.56, 0.64, 1) both;
}
@keyframes ch-summon-pop {
  from {
    transform: scale(0.2) rotate(-12deg);
    opacity: 0;
  }
  to {
    transform: scale(1) rotate(0deg);
    opacity: 1;
  }
}
.ch-typing i {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--muted);
  animation: ch-blink 1.4s infinite both;
}
.ch-typing i:nth-child(2) {
  animation-delay: 0.2s;
}
.ch-typing i:nth-child(3) {
  animation-delay: 0.4s;
}
@keyframes ch-blink {
  0%,
  80%,
  100% {
    opacity: 0.25;
  }
  40% {
    opacity: 1;
  }
}
@media (prefers-reduced-motion: reduce) {
  .ch-bubble {
    animation: none;
  }
}

/* the sent voice-note placeholder (replaced by the transcript once it lands) */
.ch-voicenote {
  font-style: italic;
  opacity: 0.85;
}

/* spoken reply — voice bubble inside the ai reply (round play · track · countdown) */
.ch-voicemsg {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 8px;
  padding: 8px 10px;
  border-radius: 14px;
  background: var(--surface);
  background: color-mix(in srgb, var(--bubble-hue, var(--persona-current)) 10%, transparent);
  border: 1px solid var(--line);
  border: 1px solid color-mix(in srgb, var(--bubble-hue, var(--persona-current)) 25%, transparent);
}
.ch-vm-play {
  flex: none;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  border: none;
  background: var(--bubble-hue, var(--persona-current));
  color: var(--ink-on-accent);
  font-size: 14px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.ch-vm-play:active {
  transform: scale(0.93);
}
.ch-vm-track {
  flex: 1;
  height: 5px;
  border-radius: 999px;
  overflow: hidden;
  min-width: 60px;
  background: var(--line);
  background: color-mix(in srgb, var(--bubble-hue, var(--persona-current)) 25%, transparent);
}
.ch-vm-fill {
  display: block;
  height: 100%;
  width: 0%;
  background: var(--bubble-hue, var(--persona-current));
  border-radius: 999px;
  transition: width 0.2s linear;
}
.ch-vm-time {
  flex: none;
  font-family: var(--font-mono);
  font-size: 12.5px;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
}
.ch-vm-speed {
  flex: none;
  font: inherit;
  font-size: 11.5px;
  font-weight: 700;
  padding: 3px 8px;
  border-radius: 999px;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  border: 1px solid var(--line);
  border: 1px solid color-mix(in srgb, var(--bubble-hue, var(--persona-current)) 35%, var(--line));
}
/* the user's own clip lives inside their NEUTRAL bubble — invert against the cream */
.ch-voicemsg.mine {
  background: color-mix(in srgb, var(--you-ink) 10%, transparent);
  border-color: color-mix(in srgb, var(--you-ink) 22%, transparent);
}
.ch-voicemsg.mine .ch-vm-play {
  background: var(--you-ink);
  color: var(--you-bg);
}
.ch-voicemsg.mine .ch-vm-track {
  background: color-mix(in srgb, var(--you-ink) 22%, transparent);
}
.ch-voicemsg.mine .ch-vm-fill {
  background: var(--you-ink);
}
.ch-voicemsg.mine .ch-vm-time,
.ch-voicemsg.mine .ch-vm-speed {
  color: color-mix(in srgb, var(--you-ink) 70%, transparent);
  border-color: color-mix(in srgb, var(--you-ink) 30%, transparent);
}

/* ── §9 composer + voice notes ────────────────────────────────────────────────
   Mic when the box is empty, send arrow with text, check while recording. The
   morph mechanics are unchanged (KP-chosen tap-toggle) — only the tint moved to
   the current mate. */
.ch-composer {
  flex: none;
  display: flex;
  align-items: flex-end;
  gap: 8px;
  padding: 8px 10px calc(14px + env(safe-area-inset-bottom));
  background: var(--sheet);
}
.ch-input {
  flex: 1;
  min-width: 0;
  min-height: 44px;
  max-height: 140px;
  height: 44px;
  padding: 11px 16px;
  border-radius: 22px;
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.3;
  outline: none;
  resize: none;
  overflow-y: auto;
}
.ch-input::placeholder {
  color: var(--muted);
}
.ch-input:focus {
  border-color: var(--persona-current);
  border-color: color-mix(in srgb, var(--persona-current) 55%, var(--line));
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--persona-current) 15%, transparent);
}
/* same trap as .ch-rec below: `display: inline-flex` on the class beats the UA's
   [hidden] rule, so showReview()'s `sendBtn.hidden = true` was a silent no-op —
   the circular send button squatted next to the review bar's own "send" pill and
   ate the 44px its flex:1 layout wanted back. */
.ch-send[hidden] {
  display: none;
}
.ch-send {
  flex: none;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: none;
  background: var(--persona-current);
  color: var(--ink-on-accent);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* hold-to-record: the browser must not scroll/select/long-press-menu on it */
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}
.ch-send svg {
  width: 22px;
  height: 22px;
}
.ch-send:active {
  transform: scale(0.94);
}
.ch-send:disabled {
  opacity: 0.5;
}
/* recording: the check on a haloed circle, in the mate's hue */
.ch-send[data-mode="rec"] {
  animation: ch-rec-pulse 1.4s ease-in-out infinite;
}
@keyframes ch-rec-pulse {
  0%,
  100% {
    box-shadow: 0 0 0 0 color-mix(in srgb, var(--persona-current) 45%, transparent);
  }
  50% {
    box-shadow: 0 0 0 9px transparent;
  }
}
@media (prefers-reduced-motion: reduce) {
  .ch-send[data-mode="rec"] {
    animation: none;
    box-shadow: 0 0 0 6px color-mix(in srgb, var(--persona-current) 18%, transparent);
  }
}

/* `display: flex` on the class beats the UA's [hidden] rule — re-assert it,
   or the bar squats in the composer at boot (KP saw exactly this). */
.ch-rec[hidden] {
  display: none;
}
.ch-rec {
  flex: 1;
  min-width: 0;
  height: 44px;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 14px 0 4px;
  border-radius: 22px;
  border: 1px solid color-mix(in srgb, var(--persona-current) 50%, transparent);
  background: color-mix(in srgb, var(--persona-current) 8%, transparent);
}
.ch-rec-cancel {
  flex: none;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  border: 1.5px solid color-mix(in srgb, var(--persona-current) 55%, transparent);
  background: var(--surface);
  color: var(--ink);
  font-size: 14px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  position: relative; /* anchors the hit expander below */
}
/* The circle stays 36px — it's sized to sit inside the 44px bar with clearance —
   but the TAP is the 44px floor. This one scraps an in-progress voice note, so a
   near-miss costs the user their words: expand the target, not the paint. -4px on
   each side = 36+8 = 44, which stops exactly at the bar's edge and still leaves
   6px of the bar's 10px gap before the dot. */
.ch-rec-cancel::after {
  content: "";
  position: absolute;
  inset: -4px;
  border-radius: 50%;
}
.ch-rec-cancel:active {
  transform: scale(0.92);
  background: color-mix(in srgb, var(--persona-current) 12%, transparent);
}
.ch-rec-dot {
  flex: none;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--persona-current);
  animation: ch-rec-blink 1.1s ease-in-out infinite;
}
@keyframes ch-rec-blink {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.25;
  }
}
.ch-rec-time {
  flex: none;
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.ch-rec-hint {
  flex: 1;
  min-width: 0;
  font-size: 11px;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* slide-away-to-cancel armed: the whole bar goes cold */
.ch-composer.rec-cancel-armed .ch-rec {
  border-color: var(--line);
  background: transparent;
  opacity: 0.6;
}
/* welcome tour: composer locked (chips-only) so a stray text cannot bail out */
.ch-composer.ch-locked {
  opacity: 0.5;
}

/* the voice-note review bar (tap-mode stop): play back · trash · send */
.ch-review {
  flex: 1;
  min-width: 0;
  height: 44px;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 6px;
  border-radius: 22px;
  border: 1px solid var(--line);
  background: var(--surface);
}
.ch-rev-play,
.ch-rev-trash {
  flex: none;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  border: 1px solid var(--line);
  background: transparent;
  color: var(--ink);
  font-size: 15px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.ch-rev-dur {
  flex: 1;
  font-family: var(--font-mono);
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
}
.ch-rev-send {
  flex: none;
  font: inherit;
  font-size: 14.5px;
  font-weight: 700;
  padding: 9px 18px;
  border-radius: 999px;
  border: none;
  background: var(--persona-current);
  color: var(--ink-on-accent);
  cursor: pointer;
}
/* the mic-help diagnostic line — the browser's raw error name, tiny and gray */
.ch-mic-diag {
  font-size: 11px;
  color: var(--muted);
  opacity: 0.7;
  margin-top: 4px;
}

/* left composer button — the menu (all the pages + sign out) */
.ch-menu {
  flex: none;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--muted);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: color 0.8s ease;
}
.ch-menu svg {
  width: 20px;
  height: 20px;
  transition: filter 0.8s ease;
}
.ch-menu:active {
  transform: scale(0.94);
}
/* the AI's mood face: applyFeeling() sets the glyph color (a wheel-family hue) + data-feel;
   a strong feeling gets a soft same-color halo, an intense one a brighter one. */
.ch-menu[data-feel] svg {
  filter: drop-shadow(0 0 3px currentColor);
}
.ch-menu[data-feel="intense"] svg {
  filter: drop-shadow(0 0 6px currentColor);
}
@media (prefers-reduced-motion: reduce) {
  .ch-menu,
  .ch-menu svg {
    transition: none;
  }
}
.ch-menu.is-open {
  background: var(--ink);
  color: var(--board);
  border-color: transparent;
}
/* the welcome tour's menu beat: a soft pulse while bebert points at it */
.ch-menu-hl {
  box-shadow: 0 0 0 2px var(--persona-current), 0 0 0 6px
    color-mix(in srgb, var(--persona-current) 22%, transparent);
  animation: ch-menu-hl-ring 1.2s ease-in-out infinite;
}
@keyframes ch-menu-hl-ring {
  0%,
  100% {
    box-shadow: 0 0 0 2px var(--persona-current), 0 0 0 6px
      color-mix(in srgb, var(--persona-current) 22%, transparent);
  }
  50% {
    box-shadow: 0 0 0 2px var(--persona-current), 0 0 0 11px transparent;
  }
}

/* ── §10 the menu sheet + THE FACES ROW ───────────────────────────────────────
   The faces row is the one NEW component: the four mates as 56px tiles plus a
   fifth dashed "?" tile — a new mate is always hatching. Tap a face = hand the
   turn over. The quadrant items sit below it, unchanged. */
.ch-menu-backdrop {
  position: fixed;
  inset: 0;
  z-index: 60;
  background: var(--scrim);
}
.ch-menu-sheet {
  position: fixed;
  z-index: 61;
  left: 8px;
  right: 8px;
  bottom: calc(env(safe-area-inset-bottom) + 8px);
  max-width: 460px;
  margin: 0 auto;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 24px;
  padding: 14px 10px 10px;
  box-shadow: 0 -10px 40px rgba(0, 0, 0, 0.5);
  max-height: 72vh;
  overflow-y: auto;
  animation: ch-sheet-in 0.18s ease;
}
@keyframes ch-sheet-in {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

.ch-faces {
  display: flex;
  justify-content: space-around;
  padding: 0 6px 12px;
  border-bottom: 1px solid var(--sheet-hair);
}
.ch-face {
  text-align: center;
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  cursor: pointer;
}
.ch-face-tile {
  width: 56px;
  height: 56px;
  border-radius: 16px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1.5px solid transparent;
}
.ch-face-tile img {
  width: 46px;
  height: 46px;
  display: block;
}
.ch-face-cap {
  display: block;
  margin-top: 5px;
  font-size: 11px;
  color: var(--muted);
}
/* the current mate: an accent ring + a tinted bed */
.ch-face.is-on .ch-face-tile {
  background: color-mix(in srgb, var(--face-hue) 12%, transparent);
  border-color: color-mix(in srgb, var(--face-hue) 50%, transparent);
}
.ch-face.is-on .ch-face-cap {
  font-weight: 700;
  color: var(--ink);
}
/* brand-keep: the mate's name, not the product's — unprotected, the sweep turned
   this into a second data-face="bebert" rule and the menu showed bebert twice. */
/*brand-keep*/
.ch-face[data-face="bebert"] {
  --face-hue: var(--persona-bebert);
}
.ch-face[data-face="peachy"] {
  --face-hue: var(--persona-peachy);
}
.ch-face[data-face="wendy"] {
  --face-hue: var(--persona-wendy);
}
.ch-face[data-face="frank"] {
  --face-hue: var(--persona-frank);
}
/*brand-keep-end*/
.ch-face:active .ch-face-tile {
  transform: scale(0.95);
}
/* the fifth tile — a new mate is always hatching */
.ch-face-soon .ch-face-tile {
  border: 1.5px dashed color-mix(in srgb, var(--ink) 25%, transparent);
  font-family: var(--font-display);
  font-size: 22px;
  color: var(--muted);
}

.ch-menu-link {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  text-align: left;
  padding: 13px 12px;
  border: none;
  border-top: 1px solid var(--sheet-hair);
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-size: 15.5px;
  font-weight: 700;
  text-decoration: none;
  cursor: pointer;
}
.ch-faces + .ch-menu-link {
  border-top: none;
}
.ch-menu-link:active {
  background: var(--line);
}
/* walk-locked rows (first-run): greyed, inert — unlocks when the walk lands */
.ch-menu-link.is-locked {
  opacity: 0.4;
  pointer-events: none;
  cursor: default;
}
.ch-menu-link .ch-ml-icon {
  font-size: 20px;
  flex: none;
  line-height: 1;
}
.ch-menu-link .ch-ml-body {
  flex: 1;
  min-width: 0;
}
.ch-menu-link .ch-ml-sub {
  display: block;
  font-size: 12.5px;
  font-weight: 400;
  color: var(--muted);
  margin-top: 1px;
}
.ch-menu-sep {
  height: 1px;
  background: var(--line);
  margin: 6px 8px;
  border: none;
}
.ch-menu-head {
  padding: 10px 12px 4px;
  font-family: var(--font-mono);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: lowercase;
  color: var(--muted);
}
.ch-menu-head + .ch-menu-link {
  border-top: none;
}

/* ── §11 boards — the space-view vocabulary ───────────────────────────────────
   Every dash page mounts on the board under the slim header. Styled once here =
   every js/spaces/* view lifts together. Cards are surface-colored, radius 16,
   soft shadow; glance boards may carry a ±0.4–0.6° tilt (index cards on his
   board — he's a little clumsy). Every board must survive being ⅓ of the screen:
   lead with the useful strip, scroll the rest. */
.ch-space-view {
  height: 100%;
  overflow-y: auto;
  padding: 4px 18px 22px;
  animation: ch-space-in 0.22s ease;
}
@keyframes ch-space-in {
  from {
    opacity: 0;
    transform: translateY(5px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
@media (prefers-reduced-motion: reduce) {
  .ch-space-view {
    animation: none;
  }
}
/* the board heading + its 34×4 accent underline */
.ch-space-h {
  margin: 0 0 6px;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 22px;
  letter-spacing: -0.01em;
  color: var(--ink);
}
.ch-space-h::after {
  content: "";
  display: block;
  width: 34px;
  height: 4px;
  margin-top: 7px;
  border-radius: 999px;
  background: var(--persona-current);
}
.ch-space-sub {
  margin: 8px 0 14px;
  color: var(--muted);
  font-size: 13px;
  line-height: 1.4;
}
.ch-space-row {
  display: flex;
  align-items: flex-start;
  gap: 11px;
  padding: 12px 10px;
  margin: 0 -10px;
  border-radius: 12px;
  border-bottom: 1px solid var(--line);
  transition: background 130ms ease;
}
.ch-space-row:last-child {
  border-bottom: none;
}
.ch-space-row:hover {
  background: color-mix(in srgb, var(--persona-current) 6%, transparent);
  border-bottom-color: transparent;
}
.ch-space-row .dot {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  flex: none;
  margin-top: 2px;
  border: 2px solid var(--persona-current);
  background: transparent;
}
.ch-space-name {
  font-size: 15px;
  font-weight: 600;
}
.ch-space-meta {
  color: var(--muted);
  font-size: 13px;
  margin-top: 2px;
}
.ch-space-empty {
  margin: 10px 0;
  padding: 22px 16px;
  text-align: center;
  color: var(--muted);
  font-size: 14px;
  line-height: 1.5;
  border: 1.5px dashed var(--line);
  border-radius: 16px;
}
.ch-space-empty::before {
  content: "✨";
  display: block;
  font-size: 22px;
  margin-bottom: 8px;
  opacity: 0.85;
}
.ch-hint {
  margin: 0 2px 2px;
  color: var(--muted);
  font-size: 13.5px;
  line-height: 1.45;
}

.ch-card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 16px;
  padding: 12px 14px;
  box-shadow: var(--card-shadow);
}
.ch-card + .ch-card {
  margin-top: 10px;
}
.ch-card h4 {
  margin: 0 0 8px;
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--muted);
  font-weight: 400;
  letter-spacing: 0.06em;
  text-transform: lowercase;
}
/* Index cards on his board — he's a little clumsy. GLANCE boards only; never on a
   form, or a list you read down, or anything with a caret in it.
   ⚠ UNWIRED HOOK: the only things that emit .ch-card are the js/spaces/* modules,
     which weren't in this rebuild's scope. Whoever does the board pass adds the
     class there; the design vocabulary is parked here so it isn't re-derived. */
.ch-card.ch-tilt-l {
  transform: rotate(-0.5deg);
}
.ch-card.ch-tilt-r {
  transform: rotate(0.45deg);
}

/* quadrant landing-page link tiles — the whole card is the tap target. The emoji
   glyph STAYS (it's the product voice), now on a mate-tinted tile. */
.ch-link-card {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  margin: 0 0 9px;
  padding: 13px 14px;
  border-radius: 16px;
  text-align: left;
  border: 1px solid var(--line);
  background: var(--surface);
  box-shadow: var(--card-shadow);
  font: inherit;
  color: var(--ink);
  cursor: pointer;
  transition: transform 120ms ease, box-shadow 120ms ease, border-color 120ms ease;
}
.ch-link-card:active {
  transform: scale(0.98);
}
.ch-link-card:hover {
  border-color: color-mix(in srgb, var(--persona-current) 40%, var(--line));
}
.ch-link-card .ch-lc-icon {
  width: 38px;
  height: 38px;
  border-radius: 11px;
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 19px;
  line-height: 1;
  background: color-mix(in srgb, var(--persona-current) 14%, transparent);
}
.ch-link-card .ch-lc-body {
  flex: 1;
  min-width: 0;
}
.ch-link-card .ch-lc-title {
  font-size: 15px;
  font-weight: 700;
}
.ch-link-card .ch-lc-desc {
  font-size: 12.5px;
  color: var(--muted);
  margin-top: 2px;
  line-height: 1.4;
}
.ch-link-card .ch-lc-chev {
  flex: none;
  color: var(--muted);
  font-size: 15px;
  opacity: 0.6;
}
.ch-link-card.ch-lc-stub {
  opacity: 0.6;
  cursor: default;
}
.ch-link-card.ch-lc-stub:hover {
  border-color: var(--line);
}

/* ── §11.1 channels — "how we reach each other" (spaces/channels.js) ──────────
   The activation board. Read top-down it answers, in order: where are we at →
   who already reaches you → what's left to add. The HERO owns that first
   question AND the single next tap, so a reader never hunts for the action;
   everything below it is reference. `.ch-cn-*` is this board's private
   vocabulary — channels.js is the only emitter. (It replaced `.ch-card-warm`,
   which was a border tweak this page was the sole consumer of.) */

/* the hero — state glyph, headline, why-line, ONE button */
.ch-cn-hero {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 9px;
  margin: 2px 0 4px;
  padding: 20px 18px;
  background: var(--surface);
  border-radius: 18px;
  border: 1.5px solid color-mix(in srgb, var(--persona-current) 30%, var(--line));
  box-shadow: var(--card-shadow);
}
.ch-cn-ring {
  width: 62px;
  height: 62px;
  border-radius: 50%;
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 27px;
  background: radial-gradient(
    closest-side,
    color-mix(in srgb, var(--persona-current) 26%, transparent),
    transparent 74%
  );
  box-shadow: 0 0 0 1px var(--line);
  animation: ch-cn-breathe 7s ease-in-out infinite;
}
.ch-cn-hero-h {
  font-family: var(--font-display);
  font-size: 19px;
  font-weight: 700;
  color: var(--ink);
}
.ch-cn-hero-line {
  margin: 0;
  max-width: 34ch;
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--muted);
}
.ch-cn-hero .ch-line-save {
  margin-top: 5px;
}
/* settled — the board stops asking: the ring holds still, the frame firms up */
.ch-cn-hero.is-set {
  border-color: color-mix(in srgb, var(--persona-current) 48%, var(--line));
}
.ch-cn-hero.is-set .ch-cn-ring {
  animation: none;
}
@keyframes ch-cn-breathe {
  0%,
  100% {
    transform: scale(0.94);
  }
  50% {
    transform: scale(1.06);
  }
}
@media (prefers-reduced-motion: reduce) {
  .ch-cn-ring {
    animation: none;
  }
}

/* a quiet, low-stakes action (turn that off) — never competes with the hero's yes */
.ch-cn-quiet {
  font: inherit;
  font-size: 12.5px;
  padding: 4px 12px;
  border-radius: 999px;
  border: none;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 3px;
}
.ch-cn-quiet:disabled {
  opacity: 0.5;
}

/* section heads — "your lines" / "add another way" */
.ch-cn-head {
  margin: 22px 0 8px;
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--muted);
  font-weight: 400;
  letter-spacing: 0.06em;
  text-transform: lowercase;
}

/* a line that already reaches you */
.ch-cn-row {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 11px 2px;
  border-bottom: 1px solid var(--line);
}
.ch-cn-row:last-child {
  border-bottom: none;
}
.ch-cn-ico {
  flex: none;
  width: 30px;
  height: 30px;
  border-radius: 9px;
  font-size: 15px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: color-mix(in srgb, var(--persona-current) 12%, transparent);
}
.ch-cn-body {
  flex: 1 1 auto;
  min-width: 0;
}
.ch-cn-name {
  font-size: 14.5px;
  font-weight: 600;
  color: var(--ink);
}
/* the address line. `overflow-wrap:anywhere` is the BELT: channelAddr() must
   never hand this a raw address (push = a ~190-char endpoint URL, telegram = an
   opaque chat id — printing either is what this board used to do). If a new
   channel type ever slips one through, it wraps instead of blowing the row out. */
.ch-cn-addr {
  display: block;
  margin-top: 1px;
  font-size: 12.5px;
  color: var(--muted);
  overflow-wrap: anywhere;
}
/* a row's actions — "main"/"make main", plus "turn off" on the push line */
.ch-cn-rowacts {
  flex: none;
  display: flex;
  align-items: center;
  gap: 4px;
}
.ch-cn-rowacts:empty {
  display: none;
}

/* an option still to add */
.ch-cn-opt {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  margin: 0 0 9px;
  padding: 13px 14px;
  border-radius: 16px;
  text-align: left;
  border: 1px solid var(--line);
  background: var(--surface);
  box-shadow: var(--card-shadow);
}
.ch-cn-opt .ch-cn-ico {
  width: 38px;
  height: 38px;
  border-radius: 11px;
  font-size: 19px;
}
.ch-cn-why {
  display: block;
  margin-top: 2px;
  font-size: 12.5px;
  line-height: 1.4;
  color: var(--muted);
}

/* the telegram handshake — QR beside the buttons, stacking on a narrow board */
.ch-cn-hand {
  display: flex;
  gap: 14px;
  align-items: center;
  flex-wrap: wrap;
  margin: 10px 0 2px;
}
.ch-cn-acts {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.ch-cn-qr {
  flex: none;
  line-height: 0;
}

/* the signal link code */
.ch-cn-code {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 8px;
}
.ch-cn-code code {
  flex: 1 1 auto;
  min-width: 0;
  overflow-wrap: anywhere;
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--ink);
}

/* a rail that isn't live yet — ONE quiet line, never a full card a reader has to
   process and then discard. (SIGNAL_PRODUCT_NUMBER is unset on live halo-api, so
   this is Signal's real state today, indefinitely.) */
.ch-cn-soon {
  margin: 14px 2px 0;
  font-size: 12.5px;
  color: var(--muted);
  opacity: 0.8;
}

/* the chat shortcut — the board's last word, and it actually talks (ctx.say) */
.ch-cn-say {
  margin: 20px 0 4px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
}
.ch-cn-say p {
  margin: 0 0 9px;
  font-size: 12.5px;
  color: var(--muted);
}
.ch-cn-chip {
  font: inherit;
  font-size: 13.5px;
  padding: 8px 14px;
  margin: 0 6px 6px 0;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--ink);
  cursor: pointer;
  transition: transform 120ms ease, border-color 120ms ease;
}
.ch-cn-chip:active {
  transform: scale(0.95);
  border-color: var(--persona-current);
}

/* a failure the reader must actually notice. --persona-current, NOT a new red:
   this line used to read `var(--ch-accent)`, a variable §3 deliberately deleted,
   so it silently rendered as plain muted body text. */
.ch-cn-err {
  margin: 8px 0 0;
  font-size: 12.5px;
  color: var(--persona-current);
}
.ch-cn-err:empty {
  display: none;
}

/* ── §11.2 morning briefing — the daily paper (spaces/briefing.js) ────────────
   Read top-down it answers, in order: what did he write me today → is it
   running → change it. The EDITION leads (the useful strip on a ⅓-board
   open); the running-state strip and the settings form sit under it. Before
   this section the page was inline styles on a bare form — the invitation,
   the paper and the form each get a real shape now. `.ch-bf-*` is this
   board's private vocabulary — briefing.js is the only emitter. */

/* the invitation (no briefing yet) — the channels-hero language: one glyph,
   one line of why, then the form right there. Friction is bad. */
.ch-bf-hero {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 9px;
  margin: 2px 0 14px;
  padding: 20px 18px;
  background: var(--surface);
  border-radius: 18px;
  border: 1.5px solid color-mix(in srgb, var(--persona-current) 30%, var(--line));
  box-shadow: var(--card-shadow);
}
.ch-bf-sun {
  width: 62px;
  height: 62px;
  border-radius: 50%;
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 27px;
  background: radial-gradient(
    closest-side,
    color-mix(in srgb, var(--persona-current) 26%, transparent),
    transparent 74%
  );
  box-shadow: 0 0 0 1px var(--line);
}
.ch-bf-hero-h {
  font-family: var(--font-display);
  font-size: 19px;
  font-weight: 700;
  color: var(--ink);
}
.ch-bf-hero-line {
  margin: 0;
  max-width: 36ch;
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--muted);
}

/* the setup/edit form — one card, block labels, one strong yes */
.ch-bf-form {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 16px;
  padding: 16px 14px 14px;
  box-shadow: var(--card-shadow);
}
.ch-bf-form input[type="time"].ch-fi {
  width: auto;
  min-width: 112px;
}
.ch-bf-form .ch-line-toggle {
  padding-bottom: 14px;
}
.ch-bf-form .ch-line-toggle i {
  display: block;
  font-style: normal;
  font-weight: 400;
  color: var(--muted);
  font-size: 12px;
  margin-top: 2px;
}
.ch-bf-acts {
  display: flex;
  gap: 10px;
  align-items: center;
  margin-top: 2px;
}

/* today's edition — the paper itself. Prose sized for actual reading. */
.ch-bf-paper {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 16px;
  padding: 16px 16px 12px;
  box-shadow: var(--card-shadow);
  margin-bottom: 12px;
}
.ch-bf-date {
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.08em;
  text-transform: lowercase;
  color: var(--muted);
  margin-bottom: 10px;
}
.ch-bf-prose {
  font-size: 15px;
  line-height: 1.65;
  color: var(--ink);
}
.ch-bf-prose p {
  margin: 0 0 12px;
}
.ch-bf-listen {
  font: inherit;
  font-size: 14px;
  font-weight: 700;
  padding: 9px 18px;
  border-radius: 999px;
  border: none;
  cursor: pointer;
  background: var(--persona-current);
  color: var(--ink-on-accent);
  margin: 0 0 12px;
}
.ch-bf-listen:active {
  transform: scale(0.96);
}
.ch-bf-listen:disabled {
  opacity: 0.6;
}
.ch-bf-upsell {
  margin: 0 0 12px;
  font-size: 12.5px;
  color: var(--muted);
  line-height: 1.45;
}
.ch-bf-share {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  align-items: center;
  padding-top: 10px;
  border-top: 1px solid var(--line);
}

/* the running-state strip — glyph, plain words, the two quiet actions */
.ch-bf-status {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 11px 2px;
}
.ch-bf-status-ico {
  flex: none;
  width: 34px;
  height: 34px;
  border-radius: 10px;
  font-size: 17px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: color-mix(in srgb, var(--persona-current) 12%, transparent);
}
.ch-bf-status.is-paused .ch-bf-status-ico {
  background: color-mix(in srgb, var(--ink) 8%, transparent);
}
.ch-bf-status-body {
  flex: 1 1 auto;
  min-width: 0;
}
.ch-bf-status-line {
  font-size: 14.5px;
  font-weight: 600;
  color: var(--ink);
}
.ch-bf-status-sub {
  font-size: 12.5px;
  color: var(--muted);
  margin-top: 1px;
  overflow-wrap: anywhere;
}
.ch-bf-status-acts {
  flex: none;
  display: flex;
  gap: 4px;
}

/* an error the reader must notice (the ch-cn-err lesson: accent, never muted) */
.ch-bf-msg {
  margin: 10px 2px 0;
  font-size: 12.5px;
  color: var(--persona-current);
}
.ch-bf-msg:empty {
  display: none;
}

/* "or just say it in chat" — the board's last word, and it actually talks */
.ch-bf-say {
  margin: 18px 0 4px;
  padding-top: 12px;
  border-top: 1px solid var(--line);
}
.ch-bf-say p {
  margin: 0 0 9px;
  font-size: 12.5px;
  color: var(--muted);
}

/* meters */
.ch-bar {
  height: 8px;
  border-radius: 999px;
  background: var(--line);
  background: color-mix(in srgb, var(--ink) 10%, transparent);
  overflow: hidden;
}
.ch-bar i {
  display: block;
  height: 100%;
  background: var(--persona-current);
  border-radius: 999px;
}
.ch-todo {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 5px 0;
  font-size: 15px;
}
.ch-todo + .ch-todo {
  border-top: 1px solid var(--line);
}

/* the `ch-catchup-*` vocabulary — BORROWED, not a module. There is no catchup.js
   (nor tracking.js): the catch-up screen these were built for never shipped, and
   the ~20 selectors that only it emitted are gone. What's left is the subset other
   spaces reuse for the same shapes, and each is live — keep them named for their
   emitters, not for the screen that never was:
     ·-main / ·-dot-urgent  → escalations.js · leads.js (a row + its urgent dot)
     ·-transcript / ·-tline / ·-twho / ·-tempty → conversations.js (a call's lines)
     ·-btn / ·-contact      → home.js · about-me.js · leads.js · digest.js */
.ch-catchup-main {
  flex: 1 1 auto;
  min-width: 0;
}
.ch-catchup-dot-urgent {
  border-color: var(--persona-current);
  background: var(--persona-current);
}
.ch-catchup-transcript {
  display: flex;
  flex-direction: column;
  gap: 7px;
  max-height: 40vh;
  overflow-y: auto;
  margin-bottom: 11px;
}
.ch-catchup-tline {
  font-size: 13px;
  line-height: 1.4;
}
.ch-catchup-twho {
  display: inline-block;
  min-width: 44px;
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: 10px;
  text-transform: lowercase;
  letter-spacing: 0.04em;
  vertical-align: top;
}
.ch-catchup-tempty {
  color: var(--muted);
  font-size: 13px;
  margin: 2px 0 11px;
}
.ch-catchup-btn {
  font: inherit;
  font-size: 13px;
  padding: 6px 15px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--ink);
  cursor: pointer;
}
.ch-catchup-btn:active {
  transform: scale(0.96);
}
.ch-catchup-btn:disabled {
  opacity: 0.4;
}
.ch-catchup-btn[data-act="dismiss"] {
  color: var(--persona-current);
}
.ch-catchup-contact {
  background: var(--persona-current);
  color: var(--ink-on-accent);
  border-color: transparent;
  text-decoration: none;
}

/* the sandbox — the model's canvas (sandbox.js). The stage fills the WHOLE board,
   edge to edge, NO margin (KP standing rule: the whiteboard is the AI's full
   expressive surface — never reserve a gutter). */
.ch-sandbox {
  height: 100%;
  overflow: auto;
  padding: 0;
  color: var(--ink);
  display: flex;
  align-items: center;
  justify-content: center;
}
.ch-sandbox-stage {
  width: 100%;
  height: 100%;
  flex: none;
  position: relative;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 0;
  background: transparent;
}
.ch-sandbox-stage svg {
  width: 100%;
  height: 100%;
}
.ch-sandbox-stage pre {
  margin: 0;
  max-width: 100%;
  max-height: 100%;
  overflow: auto;
  text-align: left;
  white-space: pre;
  font-family: var(--font-mono);
  font-size: 12px;
  line-height: 1.15;
  letter-spacing: 0;
}
.ch-sandbox-empty {
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  text-align: center;
  color: var(--muted);
  padding: 24px;
}
.ch-sandbox-idle {
  font-size: 34px;
  opacity: 0.85;
  animation: ch-sandbox-breathe 3.4s ease-in-out infinite;
}
.ch-sandbox-empty p {
  margin: 0;
  font-size: 14px;
  max-width: 240px;
  line-height: 1.45;
}
@keyframes ch-sandbox-breathe {
  0%,
  100% {
    transform: scale(1);
    opacity: 0.7;
  }
  50% {
    transform: scale(1.12);
    opacity: 1;
  }
}
/* the model's tappable chips (<button data-say>) — "things you can say back" */
.ch-sandbox-stage button {
  appearance: none;
  font: inherit;
  font-size: 14px;
  line-height: 1.2;
  padding: 8px 14px;
  margin: 3px;
  border-radius: 999px;
  cursor: pointer;
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--ink);
  transition: transform 120ms ease;
}
.ch-sandbox-stage button:active {
  transform: scale(0.95);
  border-color: var(--persona-current);
}
.ch-sandbox-stage a {
  color: var(--persona-current);
  text-underline-offset: 2px;
}

/* CRISIS MODE's held surface (crisis.js — docs/PLAN-CRISIS-MODE.md §7).
 * Grey rock by design: no alarm colour, no urgency styling, no countdown — the
 * same calm board on the first hold and the fiftieth. Warm neutrals only. */
.ch-crisis {
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 16px;
  padding: 20px;
  overflow-y: auto;
  text-align: center;
}
.ch-crisis-gap {
  margin: 0;
  font-size: 13px;
  color: var(--muted);
}
.ch-crisis-card {
  width: min(340px, 100%);
  border: 1px solid var(--line);
  border-radius: 14px;
  padding: 14px 16px;
  background: color-mix(in srgb, var(--persona-peachy) 6%, transparent);
}
.ch-crisis-head {
  margin: 0 0 10px;
  font-size: 13px;
  color: var(--muted);
}
.ch-crisis-nums {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.ch-crisis-num {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: 10px;
  text-decoration: none;
  color: var(--ink);
  background: var(--bg, transparent);
}
.ch-crisis-num strong {
  font-size: 18px;
  letter-spacing: 0.02em;
}
.ch-crisis-num span {
  font-size: 12px;
  color: var(--muted);
  text-align: right;
}
.ch-crisis-num:active {
  border-color: var(--persona-current);
}
.ch-crisis-generic {
  margin: 0;
  font-size: 13px;
  color: var(--ink);
  line-height: 1.5;
}
.ch-crisis-scope {
  margin: 10px 0 0;
  font-size: 11.5px;
  color: var(--muted);
  line-height: 1.45;
}
.ch-crisis-wayout {
  margin: 0;
  max-width: 320px;
  font-size: 12.5px;
  color: var(--muted);
  line-height: 1.5;
}

/* the hurt home — the tender space the dealer raises on a hard read (hurt.js). */
.ch-hurt {
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 20px;
  text-align: center;
  padding: 24px;
}
.ch-hurt-ring {
  width: 150px;
  height: 150px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: radial-gradient(
    closest-side,
    color-mix(in srgb, var(--persona-peachy) 22%, transparent),
    transparent 72%
  );
  box-shadow: 0 0 0 1px var(--line);
  animation: ch-hurt-ring-breathe 8s ease-in-out infinite;
}
.ch-hurt-core {
  font-size: 56px;
  line-height: 1;
}
.ch-hurt-line {
  margin: 0;
  font-size: 15px;
  color: var(--ink);
}
.ch-hurt-breath {
  position: relative;
  margin: 0;
  height: 1.4em;
  width: 100%;
  font-size: 14px;
  color: var(--muted);
}
.ch-hurt-breath span {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  white-space: nowrap;
  opacity: 0;
}
.ch-hurt-in {
  animation: ch-hurt-cue-in 8s ease-in-out infinite;
}
.ch-hurt-out {
  animation: ch-hurt-cue-out 8s ease-in-out infinite;
}
@keyframes ch-hurt-ring-breathe {
  0%,
  100% {
    transform: scale(0.88);
  }
  45%,
  55% {
    transform: scale(1.14);
  }
}
@keyframes ch-hurt-cue-in {
  0% {
    opacity: 0;
  }
  10%,
  38% {
    opacity: 1;
  }
  50%,
  100% {
    opacity: 0;
  }
}
@keyframes ch-hurt-cue-out {
  0%,
  50% {
    opacity: 0;
  }
  60%,
  88% {
    opacity: 1;
  }
  100% {
    opacity: 0;
  }
}
@media (prefers-reduced-motion: reduce) {
  .ch-sandbox-idle {
    animation: none;
  }
  .ch-hurt-ring {
    animation: none;
  }
  .ch-hurt-breath {
    display: none;
  }
}

/* "your line" as a space view (spaces/line.js). .ch-fi = form input (the composer
   already owns .ch-input). */
.ch-fi {
  width: 100%;
  font: inherit;
  font-size: 16px;
  color: var(--ink); /* 16px floor: <16 makes iOS zoom-and-strand on focus */
  background: var(--board);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 8px 11px;
}
.ch-fi:focus {
  outline: none;
  border-color: color-mix(in srgb, var(--persona-current) 55%, var(--line));
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--persona-current) 15%, transparent);
}
textarea.ch-fi {
  resize: vertical;
  line-height: 1.45;
}
.ch-line-call {
  align-items: flex-start;
}
.ch-line-call.is-dismissed {
  opacity: 0.55;
}
.ch-line-icon {
  flex: none;
  font-size: 18px;
  margin-top: 1px;
}
.ch-line-summary {
  font-size: 13.5px;
  line-height: 1.45;
  margin-top: 2px;
}
.ch-line-badge {
  display: inline-block;
  padding: 0 8px;
  border-radius: 999px;
  font-size: 11px;
  background: color-mix(in srgb, var(--persona-current) 14%, transparent);
  color: var(--persona-current);
}
.ch-line-x {
  flex: none;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  border: none;
  background: transparent;
  color: var(--muted);
  font-size: 13px;
  cursor: pointer;
}
.ch-line-x:active {
  transform: scale(0.92);
  background: var(--line);
}
.ch-line-cleared summary {
  cursor: pointer;
  color: var(--muted);
  font-size: 13px;
  padding: 10px 0 2px;
  list-style: none;
}
.ch-line-cleared summary::-webkit-details-marker {
  display: none;
}
.ch-line-toggle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 2px 0 12px;
  font-size: 14.5px;
  font-weight: 600;
}
.ch-line-toggle input {
  width: 20px;
  height: 20px;
  accent-color: var(--persona-current);
}
.ch-line-field {
  display: block;
  margin: 0 0 14px;
}
.ch-line-label {
  display: block;
  font-size: 13.5px;
  font-weight: 700;
  margin-bottom: 6px;
}
.ch-line-label i {
  font-style: normal;
  font-weight: 400;
  color: var(--muted);
  font-size: 12px;
}
.ch-line-quiet {
  display: flex;
  align-items: center;
  gap: 8px;
}
.ch-line-quiet .ch-fi {
  width: auto;
  flex: 1;
}
.ch-line-quiet span {
  color: var(--muted);
  font-size: 13px;
}
.ch-line-wl {
  display: grid;
  grid-template-columns: 1fr 1.2fr 1fr auto;
  gap: 6px;
  margin-bottom: 6px;
  align-items: center;
}
@media (max-width: 480px) {
  .ch-line-wl {
    grid-template-columns: 1fr 1.4fr auto;
  }
  .ch-wl-note {
    display: none;
  }
}
.ch-line-add {
  font: inherit;
  font-size: 12.5px;
  padding: 5px 13px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: transparent;
  color: var(--muted);
  cursor: pointer;
}
.ch-line-save {
  font: inherit;
  font-size: 14px;
  font-weight: 700;
  padding: 9px 20px;
  border-radius: 999px;
  border: none;
  background: var(--persona-current);
  color: var(--ink-on-accent);
  cursor: pointer;
  margin-top: 4px;
}
.ch-line-save:active {
  transform: scale(0.96);
}
.ch-line-save:disabled {
  opacity: 0.6;
}
.ch-line-save.ch-line-save-alt {
  background: transparent;
  color: var(--muted);
  border: 1px solid var(--line);
}
.ch-line-status {
  margin-left: 10px;
  font-size: 13px;
  color: var(--muted);
}

/* the plate as a space view WITH actions (spaces/todos.js) */
.ch-todo-row {
  display: flex;
  align-items: flex-start;
  gap: 11px;
  padding: 11px 0;
  border-bottom: 1px solid var(--line);
}
/* The undo strip — what a removed row becomes for its 7 seconds. Muted, so it
   reads as a receipt rather than a live item, but the button is full strength:
   this is the only thing standing between a mis-tap and a permanent delete. */
.ch-todo-undo {
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}
.ch-todo-undo-text {
  color: var(--muted);
  font-size: 13px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ch-todo-row:last-child {
  border-bottom: none;
}
.ch-check {
  flex: none;
  width: 22px;
  height: 22px;
  margin-top: 1px;
  border-radius: 50%;
  border: 2px solid var(--muted);
  background: transparent;
  color: var(--ink-on-accent);
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.ch-check:active {
  transform: scale(0.92);
}
.ch-todo-row.is-done .ch-check {
  border-color: transparent;
  background: var(--persona-current);
}
.ch-todo-main {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 15px;
  line-height: 1.4;
}
.ch-todo-row.is-done .ch-todo-main {
  color: var(--muted);
  text-decoration: line-through;
}
.ch-check:disabled,
.ch-line-x:disabled {
  opacity: 0.4;
}
.ch-todo-main[data-act="toggle"] {
  cursor: pointer;
}
.ch-todo-row.is-open {
  border-bottom: none;
}
.ch-todo-detail {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 2px 0 12px 33px;
  border-bottom: 1px solid var(--line);
}
.ch-todo-detail .ch-fi {
  font-size: 14px;
}
.ch-todo-detail-row {
  display: flex;
  align-items: center;
  gap: 8px;
}
.ch-todo-row.is-done + .ch-todo-detail {
  opacity: 0.85;
}

/* the first-open world board (whiteboard-major boot).
   ⚠ `.ch-board` is CONTENT, not the board layer — see §6. */
.ch-board {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 4px 2px;
}
.ch-board-greet {
  font-family: var(--font-display);
  font-size: 26px;
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.15;
}
.ch-board-date {
  color: var(--muted);
  font-size: 14px;
  margin-top: -6px;
}
.ch-board-wx {
  display: flex;
  align-items: baseline;
  gap: 10px;
  flex-wrap: wrap;
}
.ch-board-temp {
  font-family: var(--font-display);
  font-size: 34px;
  font-weight: 600;
}
.ch-board-cond {
  font-size: 15px;
  color: var(--ink);
}
.ch-board-hilo,
.ch-board-sun {
  font-size: 13px;
  color: var(--muted);
}
.ch-board-waiting {
  margin-top: 14px;
}
.ch-board-nudge {
  margin-top: 6px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.ch-board-ask {
  font-size: 15.5px;
  line-height: 1.45;
  color: var(--ink);
}
/* the notification homecoming — warm, calm, no data */
.ch-welcome {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding: 8px 18px 20px;
  gap: 4px;
}
.ch-welcome-art {
  width: 100%;
  max-width: 320px;
  border-radius: 14px;
  display: block;
}
.ch-welcome-greet {
  margin-top: 16px;
  font-family: var(--font-display);
  font-size: 22px;
  font-weight: 600;
  color: var(--ink);
  letter-spacing: -0.01em;
}
.ch-welcome-sub {
  font-size: 15px;
  line-height: 1.5;
  color: var(--muted);
  max-width: 30ch;
}
.ch-mind-hl {
  font-family: var(--font-display);
  font-size: 17px;
  font-weight: 600;
  color: var(--ink);
  margin-top: 8px;
}

/* ── §12 ceremony cards, chips, tour ──────────────────────────────────────────
   KP-LOVED, standing direction: a mode's key moment gets a native card INLINE in
   the chat, never a paragraph of reply text. Centered = the cheap delimiter:
   bubbles hug the edges and TALK; anything centered is the SYSTEM offering
   buttons. */
.ch-walkchips {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  align-self: center;
  justify-content: center;
  padding: 2px 2px 6px;
  animation: ch-pop 0.18s ease;
}
.ch-walkchip {
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  padding: 8px 15px;
  border-radius: 999px;
  cursor: pointer;
  border: 1.5px solid color-mix(in srgb, var(--persona-current) 45%, transparent);
  background: var(--surface);
  color: var(--ink);
  transition: transform 120ms ease, background 130ms ease;
}
.ch-walkchip:active {
  transform: scale(0.95);
  background: color-mix(in srgb, var(--persona-current) 10%, transparent);
}
.ch-walkchip-primary {
  background: var(--persona-current);
  color: var(--ink-on-accent);
  border-color: var(--persona-current);
}
.ch-walkchip-primary:active {
  background: var(--persona-current);
}
/* The "never ask again" chip (two-chip rule, KP 2026-07-23). A real stop button,
   deliberately quieter than the enable chip beside it — present and easy to take,
   never competing for the tap. */
.ch-walkchip-quiet {
  font-weight: 500;
  color: var(--ink-muted, var(--ink));
  border-color: color-mix(in srgb, var(--ink) 18%, transparent);
  opacity: 0.75;
}
.ch-walkchip-quiet:active {
  background: color-mix(in srgb, var(--ink) 6%, transparent);
}

.ch-ceremony {
  /* full chat width minus the log's margins — a ceremony OWNS its moment */
  align-self: stretch;
  margin: 2px 0 6px;
  padding: 12px 16px 10px;
  border-radius: 18px;
  text-align: center;
  border: 1.5px solid color-mix(in srgb, var(--persona-current) 35%, transparent);
  background: var(--surface);
  box-shadow: var(--card-shadow);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  animation: ch-pop 0.18s ease;
  transition: opacity 0.35s ease, transform 0.35s ease;
}
.ch-ceremony.out {
  opacity: 0;
  transform: translateY(6px);
}
.ch-cer-dots {
  display: flex;
  gap: 6px;
  margin-bottom: 2px;
}
.ch-cer-dots i {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--persona-current) 25%, transparent);
}
.ch-cer-dots i.cur {
  background: var(--persona-current);
}
.ch-cer-dots i.on {
  background: color-mix(in srgb, var(--persona-current) 55%, transparent);
}
.ch-cer-emoji {
  font-size: 26px;
  line-height: 1;
}
.ch-cer-title {
  font-family: var(--font-display);
  font-size: 16.5px;
  font-weight: 600;
  color: var(--ink);
}
.ch-cer-line {
  font-size: 14px;
  line-height: 1.45;
  color: var(--muted);
}
.ch-cer-yes {
  font: inherit;
  font-size: 15px;
  font-weight: 700;
  margin-top: 4px;
  width: 100%;
  padding: 12px 16px;
  border-radius: 999px;
  border: none;
  cursor: pointer;
  background: var(--persona-current);
  color: var(--ink-on-accent);
}
.ch-cer-yes:active {
  transform: scale(0.97);
}
.ch-cer-yes:disabled {
  opacity: 0.6;
}
.ch-cer-yes.ch-cer-ok {
  background: var(--persona-current);
  color: var(--ink-on-accent);
}
.ch-cer-yes.ch-cer-no {
  opacity: 0.7;
}
.ch-cer-skip {
  font: inherit;
  font-size: 13px;
  padding: 6px 12px;
  border: none;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
}
.ch-cer-done {
  font-size: 14.5px;
  color: var(--ink);
  padding: 6px 2px;
}
/* the arrival card + the "you're in" confirmation pill */
.ch-ceremony.ch-arrival {
  border-color: color-mix(in srgb, var(--persona-current) 40%, transparent);
}
.ch-board.ch-arrival {
  margin: 10px auto;
  max-width: 360px;
  padding: 16px 18px;
  border-radius: 18px;
  border: 1.5px solid color-mix(in srgb, var(--persona-current) 35%, transparent);
  background: color-mix(in srgb, var(--persona-current) 6%, var(--surface));
}
.ch-welcome-art .ch-board.ch-arrival {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
}
.ch-cer-badge {
  align-self: center;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.03em;
  color: var(--persona-current);
  background: color-mix(in srgb, var(--persona-current) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--persona-current) 35%, transparent);
  border-radius: 999px;
  padding: 4px 12px;
}

/* ── the guided walk (walk-kit.js) — the ceremony card grown up ─────────────
   Two-lane journeys (Winston's two-view, KP 2026-08-01): the say-card rides
   .ch-ceremony's language + these additions (inputs, checklists, ghost
   buttons); the show lane draws .ch-mock screens on the board. First
   consumer: the Spotify setup journey (spaces/music.js). */
.ch-walk .ch-walk-body {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 8px;
  width: 100%;
}
.ch-walk-list {
  text-align: left;
  margin: 2px 0 0;
  padding-left: 22px;
  font-size: 14px;
  line-height: 1.55;
  color: var(--muted);
}
.ch-walk-list b {
  color: var(--ink);
}
.ch-walk-input {
  width: 100%;
  box-sizing: border-box;
  padding: 11px 12px;
  border-radius: 10px;
  border: 1px solid color-mix(in srgb, var(--persona-current) 30%, transparent);
  background: color-mix(in srgb, var(--persona-current) 4%, var(--surface));
  color: var(--ink);
  font: 500 14px/1.3 ui-monospace, SFMono-Regular, Menlo, monospace;
}
.ch-walk-row {
  display: flex;
  gap: 8px;
}
.ch-walk-row .ch-cer-yes {
  margin-top: 0;
  flex: 1;
  width: auto;
}
.ch-walk-ghost {
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  padding: 12px 14px;
  border-radius: 999px;
  border: 1.5px solid color-mix(in srgb, var(--persona-current) 35%, transparent);
  background: transparent;
  color: var(--ink);
  cursor: pointer;
  flex: 0 0 auto;
}
a.ch-cer-yes {
  display: block;
  text-decoration: none;
  text-align: center;
  box-sizing: border-box;
}
.ch-walk-note {
  font-size: 13px;
  line-height: 1.4;
  color: var(--muted);
  text-align: center;
  min-height: 1em;
}

/* the SHOW lane: a stylized mock of the Spotify screen they're on over there —
   a drawing carrying the real labels, deliberately not a screenshot. Forced
   dark regardless of theme: it reads as "their site", not ours. */
.ch-mock {
  max-width: 340px;
  margin: 8px auto 0;
}
.ch-mock-frame {
  border-radius: 14px;
  overflow: hidden;
  background: #16181c;
  color: #f3f5f7;
  border: 1px solid rgba(255, 255, 255, 0.08);
  box-shadow: var(--card-shadow);
  font-size: 13px;
}
.ch-mock-bar {
  display: flex;
  align-items: center;
  padding: 8px 12px;
  background: #0d0f12;
  color: #aab2bb;
  font-size: 12px;
}
.ch-mock-bar span {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #3a3f46;
  box-shadow: 14px 0 0 #3a3f46, 28px 0 0 #3a3f46;
  margin-right: 42px;
}
.ch-mock-page {
  padding: 14px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.ch-mock-h {
  font-weight: 700;
  font-size: 14px;
}
.ch-mock-btn {
  align-self: flex-start;
  padding: 8px 16px;
  border-radius: 999px;
  background: #1db954;
  color: #08240f;
  font-weight: 700;
  font-size: 13px;
}
.ch-mock-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.ch-mock-label {
  font-size: 11px;
  letter-spacing: 0.02em;
  color: #aab2bb;
}
.ch-mock-box {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 10px;
  border-radius: 8px;
  background: #0d0f12;
  border: 1px solid rgba(255, 255, 255, 0.12);
}
.ch-mock-box code {
  flex: 1;
  min-width: 0;
  font-size: 11px;
  overflow-wrap: anywhere;
  color: #e8ecef;
}
.ch-mock-copy {
  flex: 0 0 auto;
  font-size: 11px;
  font-weight: 600;
  color: #1db954;
  white-space: nowrap;
}
.ch-mock-check {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
}
.ch-mock-check i {
  width: 16px;
  height: 16px;
  border-radius: 4px;
  border: 1.5px solid #565d66;
  display: inline-block;
}
.ch-mock-check.on i {
  background: #1db954;
  border-color: #1db954;
}
.ch-mock-hot {
  outline: 2.5px solid var(--persona-current);
  outline-offset: 3px;
  animation: ch-walk-pulse 1.6s ease-in-out infinite;
}
@keyframes ch-walk-pulse {
  0%,
  100% {
    outline-offset: 3px;
  }
  50% {
    outline-offset: 6px;
  }
}
@media (prefers-reduced-motion: reduce) {
  .ch-mock-hot {
    animation: none;
  }
}
.ch-mock-cap {
  text-align: center;
  margin: 8px 0 0;
}
.ch-walk-map {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 13px;
}
.ch-walk-map span {
  font-size: 18px;
  flex: 0 0 auto;
}
.ch-walk-addr {
  display: block;
  width: 100%;
  box-sizing: border-box;
  text-align: center;
  padding: 10px;
  border-radius: 10px;
  border: 1.5px dashed color-mix(in srgb, var(--persona-current) 45%, transparent);
  background: color-mix(in srgb, var(--persona-current) 6%, var(--surface));
  color: var(--ink);
  font: 600 12px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace;
}

/* scripted welcome tour — the crew card as Bebert introduces each mate. */
.ch-mate-card {
  align-self: flex-start;
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 4px 0 2px;
  padding: 8px 16px 8px 10px;
  border-radius: 16px;
  border: 1px solid var(--line);
  background: var(--surface);
  box-shadow: var(--card-shadow);
  animation: ch-pop 0.18s ease;
}
.ch-mate-img {
  width: 56px;
  height: 56px;
  flex: none;
  object-fit: contain;
}
.ch-mate-meta {
  display: flex;
  flex-direction: column;
  gap: 2px;
  line-height: 1.2;
}
.ch-mate-name {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 16px;
  color: var(--ink);
}
.ch-mate-role {
  font-size: 12.5px;
  color: var(--muted);
}
/* the incoming-text beat: bebert's number texts you — an SMS landing in the chat */
.ch-tour-sms {
  background: color-mix(in srgb, var(--persona-current) 12%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--persona-current) 30%, var(--line));
}
.ch-tour-sms-h {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.03em;
  color: var(--persona-current);
  font-weight: 700;
  text-transform: lowercase;
  margin-bottom: 3px;
}
.ch-tour-sms-t {
  font-size: 15px;
  line-height: 1.4;
}

/* the front-desk pill — wendy's at the desk. Visibility is pure CSS off :root.ch-biz. */
.ch-desk-chip {
  position: absolute;
  top: 8px;
  right: 12px;
  z-index: 6;
  display: flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: lowercase;
  color: var(--persona-wendy);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 4px 11px;
  opacity: 0;
  transform: translateY(-4px);
  pointer-events: none;
  transition: opacity 0.45s ease, transform 0.45s ease, color 0.45s ease;
}
.ch-desk-chip .dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--persona-wendy);
}
:root.ch-biz .ch-desk-chip {
  opacity: 1;
  transform: none;
}
/* THE FRONT DESK LIGHT. The register no longer repaints the ROOM cool (the room is
   warm, always — the MATES own the color, and wendy IS the front desk), so the
   business state now reads as her teal accent + the pill.
   This beats §4's per-mate rules by source order at equal specificity (0,2,0) —
   which only works because nothing writes --persona-current inline. See §4. */
:root.ch-biz {
  --persona-current: var(--persona-wendy);
  --ink-on-accent: #062621;
}
/* the WORK LIGHT on the whiteboard: the model's canvas paints carry their own
   baked colors that no CSS var can reach. A hue-blend layer re-hues WHATEVER is
   painted (stale pre-flip paints included) into wendy's family while keeping the
   art's lightness. isolation keeps the blend inside the stage; pointer-events:none
   keeps tap-chips tappable. The opaque backdrop is LOAD-BEARING: over a transparent
   one the hue layer composites as near-SOLID teal (KP: "WAY too dark"). */
:root.ch-biz .ch-sandbox-stage {
  isolation: isolate;
  background: var(--board);
}
:root.ch-biz .ch-sandbox-stage::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background: var(--persona-wendy);
  mix-blend-mode: hue;
}
/* let the room-change be felt — everything below resolves from the vars above */
body,
#ch-panel,
#ch-sheet,
.ch-composer {
  transition: background-color 0.6s ease;
}
.ch-bubble.ai {
  transition: background-color 0.6s ease, border-color 0.6s ease;
}
.ch-bubble.you {
  transition: background-color 0.6s ease;
}
.ch-input,
.ch-send,
.ch-grip {
  transition: background-color 0.6s ease;
}

/* ── §13 the daybook, in this room ────────────────────────────────────────────
   The book stops carrying its own OS-keyed theme and reads the SHELL's room —
   one source of truth, so the two halves of the screen can never disagree again.
   Scoped `#ch-panel #daybook` (2 ids) so it outranks daybook.css's own `#daybook`
   (1 id) base tokens. It no longer has a `[data-theme="light"]` block to beat —
   that retired with the room switch, and chathome.js stopped asking for it — but
   the 2 ids are still what puts THIS room's tokens on top of the book's defaults.
   world mode mounts the book outside #ch-panel, so none of this reaches it.
   ⚠ These are token overrides only. The daybook's own geometry/markup is
     daybook.css's — not this file's to redesign. */
#ch-panel #daybook {
  --db-paper: #221b16; /* the board's material, one step deeper */
  --db-paper-sunk: #1b1511;
  --db-ink: var(--ink);
  --db-ink-soft: var(--muted);
  --db-chrome: var(--sheet);
  --db-rule: rgba(243, 233, 221, 0.22);
  /* --db-violet / --db-cal / --db-lilac are deliberately NOT re-declared here.
     --db-violet is gone (the hue retired; nothing reads it). --db-cal and
     --db-lilac already derive from daybook.css's own --db-accent, which reads the
     --persona-current we publish — so overriding them here resolved to the exact
     same hue while shadowing daybook's chain from 2 ids. One derivation, one
     place. Re-declare a --db-* here only to say something daybook.css can't. */
  --db-copper: #d97444; /* the now-line, and nothing else. Never a button. */
  --db-open: rgba(243, 233, 221, 0.035);
  --db-open-edge: color-mix(in srgb, var(--persona-current) 26%, transparent);
  --db-open-glow: color-mix(in srgb, var(--persona-current) 10%, transparent);
  --db-open-ink: var(--muted);
  --db-font: var(--font-body); /* the "Hanken Grotesk" ghost (never loaded) is gone */
  --db-mono: var(--font-mono);
}
@media (prefers-color-scheme: light) {
  #ch-panel #daybook {
    --db-paper: #fdf7ef;
    --db-paper-sunk: #f6efe4;
    --db-chrome: #ffffff;
    --db-rule: rgba(51, 38, 30, 0.25);
    --db-copper: #c2683b;
    --db-open: rgba(51, 38, 30, 0.04);
  }
}
#ch-panel #daybook .db-chrome,
#ch-panel #daybook .db-daydate {
  color: var(--db-ink);
}
#ch-panel #daybook .db-slimbtn {
  color: var(--db-ink-soft);
  border-color: var(--db-rule);
  background: transparent;
}

/* ── §14 boot, rotate, toast ──────────────────────────────────────────────── */
/* ⚠ THE BOOT SPLASH IS NOT STYLED HERE ANY MORE (KP 2026-07-20: "can we not
   separate the loading css from the rest of the site completely").
   Every #ch-boot / .ch-boot-* rule now lives INLINE in the <head> of
   chathome.html and nowhere else. Two reasons it is inline rather than a
   boot.css file: the splash must paint on the first byte with zero network
   round trips, and keeping it in one place means it can never drift from the
   copy the app ships. Do not re-add boot rules to this file — the whole point
   is that the loading screen and the app no longer depend on each other. */

/* the quick-confirm toast (floats over the board when the sheet is low) */
.ch-toast {
  position: fixed;
  left: 12px;
  right: 12px;
  top: calc(env(safe-area-inset-top) + 12px);
  z-index: 50;
  max-width: 460px;
  margin: 0 auto;
  text-align: left;
  background: var(--surface);
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: 16px;
  padding: 12px 14px;
  box-shadow: 0 14px 32px rgba(0, 0, 0, 0.34);
  display: flex;
  flex-direction: column;
  gap: 3px;
  font: inherit;
  cursor: pointer;
  animation: ch-toast-in 0.22s ease;
}
.ch-toast.out {
  animation: ch-toast-out 0.2s ease forwards;
}
.ch-toast-text {
  font-size: 15px;
  line-height: 1.35;
}
.ch-toast-more {
  font-size: 11.5px;
  color: var(--muted);
}
@keyframes ch-toast-in {
  from {
    opacity: 0;
    transform: translateY(-10px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
@keyframes ch-toast-out {
  to {
    opacity: 0;
    transform: translateY(-10px);
  }
}

/* landscape-PHONE nudge — HELP, not gate. Dismissible; only phones (short even
   sideways) ever see it; tablets and portrait never do. */
.ch-rotate {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 200;
  background: var(--board);
  color: var(--ink);
  align-items: center;
  justify-content: center;
  padding: 24px;
  text-align: center;
}
@media (orientation: landscape) and (max-height: 500px) {
  .ch-rotate {
    display: flex;
  }
}
body.rotate-ok .ch-rotate {
  display: none !important;
}
.ch-rotate-card {
  max-width: 320px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
}
.ch-rotate-phone {
  width: 42px;
  height: 74px;
  border: 3px solid var(--ink);
  border-radius: 9px;
  position: relative;
  animation: ch-rot 2.6s ease-in-out infinite;
}
.ch-rotate-phone::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: 5px;
  transform: translateX(-50%);
  width: 12px;
  height: 3px;
  border-radius: 2px;
  background: var(--muted);
}
@keyframes ch-rot {
  0%,
  35% {
    transform: rotate(-90deg);
  }
  55%,
  100% {
    transform: rotate(0);
  }
}
.ch-rotate-h {
  margin: 8px 0 0;
  font-family: var(--font-display);
  font-size: 18px;
  font-weight: 600;
}
.ch-rotate-sub {
  margin: 0;
  color: var(--muted);
  font-size: 14px;
  line-height: 1.45;
}
.ch-rotate-dismiss {
  margin-top: 10px;
  background: none;
  border: none;
  color: var(--persona-current);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
  text-decoration: underline;
}
@media (prefers-reduced-motion: reduce) {
  .ch-rotate-phone {
    animation: none;
    transform: rotate(0);
  }
}

/* ── §15 foreign hosts of this library ────────────────────────────────────────
   world mode's module windows (#gw-window-body, game.css) and the standalone
   /line page (body.pg + peachy.css) both still DECLARE `--ch-accent` to skin
   these components — world mode per game-skin mood, /line in bebert persimmon.
   The chathome shell never declares it, so :root falls back to the mate; where
   it IS declared, honor it, and those two surfaces stay exactly as they were.
   This is the ONLY reason --ch-accent is still named anywhere in this file.
   `--ink-on-accent` pins to white for them too — that was those surfaces' old
   accent-button ink, and no mate token can be right for an unknown mood hue.
   Delete these two selectors the day those pages stop linking this stylesheet. */
#gw-window-body,
body.pg {
  --persona-current: var(--ch-accent, var(--persona-bebert));
  --ink-on-accent: #ffffff;
}

/* ── welcome tour curtain: the first-mission input pulse (beat 5). Functional
   baseline — the design pass owns the final look. ── */
.ch-input-pulse {
  animation: ch-input-pulse 2.2s ease-in-out infinite;
}
@keyframes ch-input-pulse {
  0%,
  100% {
    box-shadow: 0 0 0 0 rgba(122, 186, 92, 0);
  }
  50% {
    box-shadow: 0 0 0 4px rgba(122, 186, 92, 0.35);
  }
}

/* ── §welcome-tour · FABLE language (design handoff 2026-07-21) ───────────────
   Semantic palette, one hue one job: present(green)=bebert alive · arriving
   (peach)=the world reaching in (call/SMS) · time(amber)=the thursday card ·
   your-words(warm card + green left edge)=the seed, held not owned. Tokens pair
   with the app's day/night flip (dark base, light override) like every surface. */
:root {
  --tour-present: #a3e635;
  --tour-arriving: #ef9668;
  --tour-time: #e8a838;
  --tour-card: #14180f;
  --tour-card-line: rgba(240, 230, 207, 0.18);
  --tour-card-ink: #e9eee1;
  --tour-card-muted: #8b9479;
  --tour-card-shadow: 0 14px 34px rgba(0, 0, 0, 0.5);
  --tour-dash: rgba(240, 230, 207, 0.28);
}
@media (prefers-color-scheme: light) {
  :root {
    --tour-present: #59a616;
    --tour-arriving: #e8895f;
    --tour-time: #d99a26;
    --tour-card: #ffffff;
    --tour-card-line: #d3cbb6;
    --tour-card-ink: #332a1e;
    --tour-card-muted: #8f8471;
    --tour-card-shadow: 0 14px 34px rgba(120, 100, 70, 0.22);
    --tour-dash: #c9bfa9;
  }
}

/* the dormant placeholder — dashed + quiet so the beat-1 wake reads as an event */
.ch-tour-dormant {
  max-width: 270px;
  margin: 0 auto;
  text-align: center;
  border: 1.5px dashed var(--tour-dash);
  border-radius: 18px;
  padding: 18px 20px;
  background: color-mix(in srgb, var(--tour-card) 55%, transparent);
}
.ch-tour-dormant .t1 {
  font-weight: 700;
  font-size: 15px;
  color: var(--tour-present);
  margin-bottom: 4px;
}
.ch-tour-dormant .t2 {
  font-weight: 700;
  font-size: 19px;
  color: var(--tour-card-ink);
  line-height: 1.2;
}
.ch-tour-dormant .t3 {
  font-size: 13px;
  color: var(--tour-card-muted);
  margin-top: 5px;
}

/* board cards — the tour builds the everyday board out of these */
.ch-tour-card {
  max-width: 280px;
  margin: 14px auto 0;
  background: var(--tour-card);
  border: 1.5px solid var(--tour-card-line);
  border-radius: 18px;
  padding: 15px 17px 13px;
  text-align: left;
}
.ch-tour-card.seed {
  border-left: 5px solid var(--tour-present);
  transform: rotate(-2deg);
  box-shadow: var(--tour-card-shadow);
}
.ch-tour-card.time {
  border-color: var(--tour-time);
  transform: rotate(2deg);
  box-shadow: var(--tour-card-shadow);
}
.ch-tour-card .lab {
  display: flex;
  align-items: center;
  gap: 7px;
  font-family: ui-monospace, "Space Mono", monospace;
  font-size: 10px;
  letter-spacing: 2px;
  color: var(--tour-card-muted);
  margin-bottom: 7px;
  text-transform: uppercase;
}
.ch-tour-card.seed .lab::before {
  content: "";
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--tour-present);
}
.ch-tour-card.time .lab {
  color: var(--tour-time);
}
.ch-tour-card .ttl {
  font-weight: 700;
  font-size: 20px;
  color: var(--tour-card-ink);
  line-height: 1.14;
}
.ch-tour-card .sub {
  font-size: 12.5px;
  margin-top: 6px;
  color: var(--tour-card-muted);
}
.ch-tour-card.seed .sub {
  color: var(--tour-present);
}
.ch-tour-card.time .sub {
  color: var(--tour-time);
}

/* the materialize — a thought forming (spec: .45s soft overshoot) */
.ch-tour-materialize {
  animation: ch-tour-materialize 0.45s cubic-bezier(0.2, 0.8, 0.2, 1.2) both;
}
@keyframes ch-tour-materialize {
  from {
    opacity: 0;
    transform: scale(0.9) rotate(0deg);
  }
}

/* beat 2 contrast budget: board dims to ~half while the call is the hero */
.ch-tour-dim {
  opacity: 0.45;
  transition: opacity 0.5s ease;
}

/* call-state marker — peach owns "arriving" */
.ch-tour-callchip {
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  background: color-mix(in srgb, var(--tour-arriving) 18%, var(--tour-card));
  border: 1.5px solid var(--tour-arriving);
  border-radius: 999px;
  padding: 7px 14px;
  font-size: 13px;
  font-weight: 800;
  color: var(--tour-arriving);
  margin: 4px 0;
}
.ch-tour-callchip::before {
  content: "";
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--tour-arriving);
}

/* the SMS bubble — a white card with the peach "arriving" edge, OS-notification feel */
.ch-bubble.ch-tour-sms {
  background: var(--tour-card);
  border: 1.5px solid var(--tour-arriving);
  border-left: 6px solid var(--tour-arriving);
  border-radius: 18px;
  box-shadow: var(--tour-card-shadow);
}
.ch-tour-sms-h {
  font-family: ui-monospace, "Space Mono", monospace;
  font-size: 11px;
  letter-spacing: 1px;
  color: color-mix(in srgb, var(--tour-arriving) 80%, var(--tour-card-ink));
  margin-bottom: 5px;
}
.ch-tour-sms-t {
  color: var(--tour-card-ink);
}

/* beat 5 curtain — the counter hero + a bebert-scale celebration */
.ch-tour-counterhero {
  max-width: 300px;
  margin: 4px auto 14px;
  text-align: center;
  background: var(--tour-card);
  border: 2px solid var(--tour-present);
  border-radius: 18px;
  padding: 15px 18px;
  box-shadow: 0 14px 34px color-mix(in srgb, var(--tour-present) 25%, transparent);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  animation: ch-tour-bounce 0.6s cubic-bezier(0.2, 0.8, 0.2, 1.2) 0.2s both;
}
.ch-tour-counterhero .num {
  font-weight: 800;
  font-size: 40px;
  line-height: 1;
  color: var(--tour-present);
}
.ch-tour-counterhero .what {
  text-align: left;
}
.ch-tour-counterhero .what .a {
  font-weight: 700;
  font-size: 16px;
  line-height: 1.1;
  color: var(--tour-card-ink);
}
.ch-tour-counterhero .what .b {
  font-size: 12.5px;
  color: var(--tour-card-muted);
}
@keyframes ch-tour-bounce {
  from {
    opacity: 0;
    transform: scale(0.92);
  }
  60% {
    transform: scale(1.04);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}

/* ── FABLE spill: the chat channel wears the SPEAKING mate's tint — the only
   surface where identity varies (board stays the same white/black chalkboard
   for all four). data-persona on :root is applyTurn's one write. ── */
:root[data-persona="peachy"] {
  --sheet: #1a1410;
}
:root[data-persona="wendy"] {
  --sheet: #101715;
}
:root[data-persona="frank"] {
  --sheet: #191012;
}
@media (prefers-color-scheme: light) {
  :root[data-persona="peachy"] {
    --sheet: #f6e2d2;
  }
  :root[data-persona="wendy"] {
    --sheet: #dcebe4;
  }
  :root[data-persona="frank"] {
    --sheet: #f3dcd8;
  }
}

/* beat 4 — the gang, as tiles in the chat (each glows its own hue) */
.ch-tour-crewrow {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 8px;
  margin: 6px 0 2px;
}
.ch-tour-crewtile {
  position: relative;
  background: var(--tour-card);
  border: 1.5px solid color-mix(in srgb, var(--hue) 55%, var(--tour-card-line));
  border-radius: 14px;
  padding: 10px 4px 8px;
  text-align: center;
  box-shadow: 0 8px 22px -10px color-mix(in srgb, var(--hue) 45%, transparent);
  animation: ch-tour-materialize 0.45s cubic-bezier(0.2, 0.8, 0.2, 1.2) both;
}
.ch-tour-crewrow .ch-tour-crewtile:nth-child(2) {
  animation-delay: 70ms;
}
.ch-tour-crewrow .ch-tour-crewtile:nth-child(3) {
  animation-delay: 140ms;
}
.ch-tour-crewrow .ch-tour-crewtile:nth-child(4) {
  animation-delay: 210ms;
}
.ch-tour-crewtile img {
  border-radius: 50%;
}
.ch-tour-crewtile .nm {
  font-weight: 700;
  font-size: 12.5px;
  color: var(--tour-card-ink);
  margin-top: 4px;
}
.ch-tour-crewtile .rl {
  font-size: 9.5px;
  line-height: 1.15;
  color: var(--tour-card-muted);
  min-height: 22px;
}

/* ══════════════════════════════════════════════════════════════════════════
   THE SUNRISE (view "casefile") — part two's board. SHOW above, SAY below.

   KP 2026-07-31: the board ILLUSTRATES what the chat verbalizes, and for the
   warm welcome that is "a bebert landscape, with a fraction of a rising sun w
   rays, as if the beginning is here", carrying wellbeing, progress and safety
   through all four steps of the journey.

   THREE THINGS THIS FILE IS DOING, and none of them is decoration:

   1. THE SUN IS THE PROGRESS AXIS. Its elevation is bound to her four real
      landmarks (case-file.ts landmarks()), so it moves exactly four times in a
      sitting and is dead still between. `[data-state]` is the ONLY switch, which
      is what lets the module flip one attribute and get a transition instead of
      rebuilding the DOM. The board that lived here before re-fired every
      element's entrance animation on every turn AND could never transition
      anything on purpose, because a brand-new element renders at its final
      position. Both halves of that are fixed by this being CSS state.

   2. THE DRAG REVEALS THE WORLD. The svg is authored once at ~3x the reference
      strip and pinned to the TOP of an overflow-hidden box (preserveAspectRatio
      xMidYMin slice, set in the module). Growing the box shows more land and
      re-renders nothing.

   3. IT IS DRAWN ON THE BOARD, not instead of it. Every land band is a
      translucent wash of --ink, so #ch-panel's own dot grid reads THROUGH the
      scene and the whiteboard never stops being a working surface. That is why
      there is no opaque background here and no grid overlay: the surface is
      already underneath.

   The palette is derived from the shell's tokens, never literals, so both rooms
   and the persona tint are honoured for free. Opacity does the shading rather
   than color-mix, so nothing here needs a fallback on an older engine.
   ══════════════════════════════════════════════════════════════════════════ */
.ch-sun {
  position: absolute;
  inset: 0;
  overflow: hidden;
  /* deliberately NO background: the board's dot grid is the paper */
  /*brand-keep*/
  /* peachy is the MATE whose board this is, and --persona-peachy is on brand.py's
     PROTECT list, so the token survives the deploy's name sweep. */
  --sun-peach: var(--persona-peachy);
  /*brand-keep-end*/
  --sun-tree: var(--ink);
  --sun-water: var(--persona-peachy);
}
.ch-sun > svg {
  position: absolute;
  left: 0;
  top: 0;
  display: block;
}

/* ── type. Sizes are set per instance in device px by the module. ─────────── */
.ch-sun-hl {
  font-family: var(--font-display);
  font-weight: 600;
  fill: var(--ink);
}
.ch-sun-gl {
  font-family: var(--font-body);
  fill: var(--ink);
  fill-opacity: 0.88;
}

/* Only the headline for the CURRENT state is visible; the others are drawn and
   waiting, so a state change cross-fades instead of reflowing text. */
.ch-sun-hlwrap > text {
  opacity: 0;
  transition: opacity 620ms ease;
}
.ch-sun[data-state="1"] .ch-sun-hlwrap > text[data-s="1"],
.ch-sun[data-state="2"] .ch-sun-hlwrap > text[data-s="2"],
.ch-sun[data-state="3"] .ch-sun-hlwrap > text[data-s="3"],
.ch-sun[data-state="4"] .ch-sun-hlwrap > text[data-s="4"] {
  opacity: 1;
}

/* ── the land. Washes of ink, so the dot grid shows through. ──────────────── */
.ch-sun-band {
  fill: var(--ink);
}
.ch-sun-band.b1 {
  fill-opacity: 0.055;
}
.ch-sun-band.b2 {
  fill-opacity: 0.085;
}
.ch-sun-band.b3 {
  fill-opacity: 0.125;
}
.ch-sun-band.b4 {
  fill-opacity: 0.175;
}
.ch-sun-ridge {
  fill: none;
  stroke: var(--ink);
  stroke-opacity: 0.28;
  stroke-width: 1.7;
  stroke-linecap: round;
}
.ch-sun-egg {
  opacity: 0.3;
}
.ch-sun-mate {
  opacity: 0.95;
}

/* ── the sun. The ONLY thing in the scene that ever moves on its own. ─────── */
.ch-sun-move {
  transform: translateY(var(--cy));
  transition: transform 1500ms cubic-bezier(0.22, 0.72, 0.2, 1);
}
.ch-sun-disc {
  fill: var(--sun-peach);
  fill-opacity: 0.12;
  stroke: var(--sun-peach);
  stroke-width: 2.2;
  transition: fill-opacity 900ms ease;
}
.ch-sun-rays {
  transform-box: fill-box;
  transform-origin: center;
  transition: transform 1500ms cubic-bezier(0.22, 0.72, 0.2, 1), opacity 900ms ease;
}
.ch-sun-ray {
  stroke: var(--sun-peach);
  stroke-width: 2;
  stroke-linecap: round;
}
/* The five spokes nearest vertical are the "first rays" that show while the disc
   is still mostly under the horizon; the rest arrive with state 2. */
.ch-sun-ray:not(.up) {
  opacity: 0;
  transition: opacity 700ms ease;
}
.ch-sun[data-state="2"] .ch-sun-ray,
.ch-sun[data-state="3"] .ch-sun-ray,
.ch-sun[data-state="4"] .ch-sun-ray {
  opacity: 1;
}
/* The glow is off in the drawn-on treatment: a bloom would make it a photograph
   and the surface has to stay a whiteboard. Kept in the markup so the treatment
   is a CSS decision rather than a re-render. */
.ch-sun-glow {
  opacity: 0;
}

/* ── the four states ─────────────────────────────────────────────────────── */
.ch-sun[data-state="1"] .ch-sun-move {
  --cy: var(--c1);
}
.ch-sun[data-state="2"] .ch-sun-move {
  --cy: var(--c2);
}
.ch-sun[data-state="3"] .ch-sun-move {
  --cy: var(--c3);
}
.ch-sun[data-state="4"] .ch-sun-move {
  --cy: var(--c4);
}
.ch-sun[data-state="1"] .ch-sun-rays {
  transform: scale(0.8);
  opacity: 0.7;
}
.ch-sun[data-state="2"] .ch-sun-rays {
  transform: scale(0.93);
  opacity: 0.86;
}
.ch-sun[data-state="3"] .ch-sun-rays {
  transform: scale(1.05);
  opacity: 1;
}
.ch-sun[data-state="4"] .ch-sun-rays {
  transform: scale(1.15);
  opacity: 1;
}
.ch-sun[data-state="2"] .ch-sun-disc {
  fill-opacity: 0.22;
}
.ch-sun[data-state="3"] .ch-sun-disc {
  fill-opacity: 0.45;
}
.ch-sun[data-state="4"] .ch-sun-disc {
  fill-opacity: 0.82;
}

/* THE LIGHT WALKS FORWARD. It reaches the far bank first and the ground at her
   feet last, so opening the board at state 4 is a different world from opening it
   at state 1. This is what carries states 3 and 4 apart: by then there is barely
   any sky left to climb, so elevation alone cannot say "further along". */
.ch-sun-lit {
  opacity: 0;
  fill: var(--sun-peach);
  transition: opacity 1300ms ease;
}
.ch-sun[data-state="2"] .l1 {
  opacity: 0.2;
}
.ch-sun[data-state="3"] .l1 {
  opacity: 0.3;
}
.ch-sun[data-state="3"] .l2 {
  opacity: 0.16;
}
.ch-sun[data-state="4"] .l1 {
  opacity: 0.38;
}
.ch-sun[data-state="4"] .l2 {
  opacity: 0.26;
}
.ch-sun[data-state="4"] .l3 {
  opacity: 0.15;
}
.ch-sun[data-state="4"] .l4 {
  opacity: 0.08;
}

/* Below the fold: the water only catches the light once the sun is properly up. */
.ch-sun-glint {
  opacity: 0;
  transition: opacity 1300ms ease;
}
.ch-sun[data-state="2"] .ch-sun-glint {
  opacity: 0.3;
}
.ch-sun[data-state="3"] .ch-sun-glint {
  opacity: 0.5;
}
.ch-sun[data-state="4"] .ch-sun-glint {
  opacity: 0.68;
}

/* The light room needs the land a touch heavier to hold against paper white, and
   the ridge a touch softer so it reads as drawn rather than ruled. */
@media (prefers-color-scheme: light) {
  .ch-sun-band.b1 {
    fill-opacity: 0.07;
  }
  .ch-sun-band.b2 {
    fill-opacity: 0.105;
  }
  .ch-sun-band.b3 {
    fill-opacity: 0.15;
  }
  .ch-sun-band.b4 {
    fill-opacity: 0.205;
  }
  .ch-sun-ridge {
    stroke-opacity: 0.22;
  }
  .ch-sun-egg {
    opacity: 0.34;
  }
}

/* KP's standing rule for the first sitting: no decorative motion. The sun's climb
   is the one exception because it is an EVENT, not decoration, so under reduced
   motion it still lands on the right state, instantly. */
@media (prefers-reduced-motion: reduce) {
  .ch-sun-move,
  .ch-sun-rays,
  .ch-sun-disc,
  .ch-sun-lit,
  .ch-sun-glint,
  .ch-sun-ray,
  .ch-sun-hlwrap > text {
    transition: none !important;
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   PEACHY'S HELLO (.ch-phello) — the divider between the quiz chat and part two.
   KP 2026-07-30 after KK's first warm walk: an svg bebert and a hello, inline in
   the chat, BEFORE the arrival message. "A warm, happy, ux/ui beautiful greeting
   from the entity that the user will be talking to... it makes the user feel
   good. it makes us more human."
   Reads as an EVENT, not chrome: it appears once, at the moment part two begins,
   and it is the visual answer to "who am i talking to now".
   ══════════════════════════════════════════════════════════════════════════ */
.ch-phello {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 6px;
  margin: 22px auto 14px;
  padding: 0 18px;
  max-width: 34ch;
  animation: chph-rise .5s cubic-bezier(0.2, 0.8, 0.3, 1) both;
}
/* The seam itself: a hairline that fades out at both ends, so the old
   conversation visibly ENDS rather than just scrolling away. */
.ch-phello-rule {
  width: 100%;
  height: 1px;
  margin-bottom: 12px;
  background: linear-gradient(90deg, transparent, var(--line) 22%, var(--line) 78%, transparent);
}
.ch-phello-face {
  width: 64px;
  height: 64px;
  animation: chph-pop .5s cubic-bezier(0.2, 1.3, 0.4, 1) .1s both;
}
.ch-phello-hi {
  font-family: var(--font-display);
  font-size: 19px;
  font-weight: 700;
  color: var(--ink);
  animation: chph-rise .45s ease .18s both;
}
.ch-phello-sub {
  font-size: 13px;
  line-height: 1.5;
  color: var(--muted);
  animation: chph-rise .45s ease .26s both;
}
@keyframes chph-rise {
  from {
    opacity: 0;
    transform: translateY(8px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
@keyframes chph-pop {
  from {
    opacity: 0;
    transform: scale(0.7);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
@media (prefers-reduced-motion: reduce) {
  .ch-phello,
  .ch-phello-face,
  .ch-phello-hi,
  .ch-phello-sub {
    animation: none !important;
  }
}
