/*
 * Stage 5 — the demo's face.
 *
 * Design intent, in one paragraph: the character stage is the hero and everything else
 * stays quiet — Peter's brief is "the chat should feel unremarkable". The one deliberate
 * device is typographic: roleplay has two registers, and the type encodes them. Narration
 * (the story's opening, the *asterisk* prose inside replies) is set in a serif italic —
 * book voice. Spoken dialogue and all UI chrome are the system sans — chat voice. Palette
 * is a deep aubergine-black with one soft violet accent: adjacent to the character-bot
 * world this demos into, without borrowing anyone's brand.
 */

:root {
  --ink: #16131c;        /* page */
  --panel: #201b2b;      /* raised surfaces */
  --panel-2: #282136;    /* interactive surfaces */
  --line: #352c48;       /* hairlines */
  --text: #ece9f2;
  --muted: #9d94b0;
  --accent: #b48eff;     /* violet — interaction + the user's own voice */
  --accent-ink: #1d1329; /* text on accent */
  --danger: #ff9b9b;

  --serif: "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
  --sans: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --mono: ui-monospace, SFMono-Regular, Menlo, monospace;
}

* { box-sizing: border-box; }

html, body { height: 100%; }

body {
  margin: 0;
  background: var(--ink);
  color: var(--text);
  font-family: var(--sans);
  font-size: 15px;
  line-height: 1.55;
}

button {
  font: inherit;
  cursor: pointer;
  border: none;
  border-radius: 8px;
}

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* `display: grid` on .chat-grid would defeat the hidden attribute without this. */
.view[hidden] { display: none !important; }

/* ── Picker ─────────────────────────────────────────────────────────────── */

#pick {
  max-width: 1060px;
  margin: 0 auto;
  padding: 48px 24px 64px;
}

.eyebrow {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 18px;
}

.pick-head h1 {
  font-family: var(--serif);
  font-weight: 500;
  font-size: clamp(28px, 4.5vw, 40px);
  margin: 0 0 6px;
}

.sub {
  color: var(--muted);
  margin: 0 0 34px;
}

.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: 18px;
  align-items: start;
}

.card {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 12px;
  overflow: hidden;
}

/* The clickable face of a card. A plain button so keyboards get it for free. */
.card-face {
  display: block;
  width: 100%;
  padding: 0;
  background: none;
  color: inherit;
  text-align: left;
  border-radius: 0;
}

.card-face img {
  display: block;
  width: 100%;
  aspect-ratio: 3 / 4;
  object-fit: cover;
  object-position: top;
  background: #0e0b14;
}

.card-body { padding: 12px 14px 14px; }

.card-body h2 {
  font-family: var(--serif);
  font-weight: 600;
  font-size: 20px;
  margin: 0;
}

.card-body .tagline {
  color: var(--muted);
  font-size: 13px;
  margin: 4px 0 0;
}

/* ── Character builder card ─────────────────────────────────────────────────
   The grid's last card: a dropzone at rest, a stage checklist while a build
   runs. Same card chrome as the cast so it reads as "the next character",
   not as a form. */

.build-card { display: flex; flex-direction: column; }

.dropzone {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  width: 100%;
  flex: 1;
  min-height: 280px;
  padding: 24px 16px;
  background: none;
  color: var(--muted);
  border: 2px dashed var(--line);
  border-radius: 12px;
  cursor: pointer;
}

.dropzone:hover,
.dropzone.drag {
  color: var(--accent);
  border-color: var(--accent);
  background: var(--panel-2);
}

.dz-plus {
  font-size: 44px;
  line-height: 1;
  font-weight: 200;
}

.dz-title {
  font-family: var(--serif);
  font-weight: 600;
  font-size: 20px;
  color: inherit;
}

.dz-hint { font-size: 13px; }

.build-title {
  font-family: var(--serif);
  font-weight: 600;
  font-size: 18px;
  padding: 12px 14px 0;
}

.build-face { padding: 12px 14px 0; }

.build-portrait {
  display: block;
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  object-position: top;
  border-radius: 8px;
  background: #0e0b14;
}

.build-progress {
  list-style: none;
  margin: 0;
  padding: 10px 14px 14px;
  font-size: 13px;
  color: var(--muted);
}

.build-progress li { padding: 3px 0; }
.build-progress li .mark { display: inline-block; width: 16px; }
.build-progress li[data-status='done'] { color: var(--text); }
.build-progress li[data-status='running'] { color: var(--accent); }
.build-progress li[data-status='failed'] { color: var(--danger); }

.build-error {
  color: var(--danger);
  font-size: 12px;
  padding: 4px 0 0 16px;
  overflow-wrap: anywhere;
}

.build-retry { margin: 0 14px 14px; }

/* ── Character sheet modal ──────────────────────────────────────────────────
   The extended card a picker card opens into: portrait left, sheet right,
   tabbed Scenes | About. <dialog> supplies ESC and the backdrop; padding
   stays 0 so a backdrop click is unambiguous (see app.js). */

.char-modal {
  width: min(880px, calc(100vw - 32px));
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--panel);
  color: var(--text);
  overflow: hidden;
}

.char-modal::backdrop {
  background: rgba(8, 6, 12, .65);
  backdrop-filter: blur(3px);
}

.modal-split {
  display: grid;
  grid-template-columns: minmax(240px, 2fr) 3fr;
  /* A definite height is what lets the sheet column scroll instead of the row
     growing to its content and overflowing the dialog. */
  height: min(620px, 86vh);
  grid-template-rows: minmax(0, 1fr);
}

/* The live portrait: viseme layers + blink overlay stacked by app.js, all sharing one
   cover-crop so the mouth swaps stay pixel-aligned. Same opacity mechanics as .stage —
   including the specificity trick that lets ONLY the blink layer ease. */
.modal-stage {
  position: relative;
  overflow: hidden;
  background: #0e0b14;
}

.modal-stage img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top;
  opacity: 0;
  transition: none; /* discrete swaps ARE the animation */
}

.modal-stage img.on { opacity: 1; }

.modal-stage img.blink { transition: opacity 45ms linear; }

/* The intro play button — .play-btn's family, sized up to a tap target and filled:
   it sits on artwork, where a hairline ghost button would disappear. A pill, not a
   circle: the glyph rides with a "preview" label so the play reads as a sample of
   the character, not the start of a scene. */
.modal-intro-btn {
  position: absolute;
  left: 14px;
  bottom: 14px;
  height: 40px;
  padding: 0 16px 0 14px;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  border-radius: 999px;
  background: var(--accent);
  color: var(--accent-ink);
  box-shadow: 0 2px 14px rgba(8, 6, 12, .5);
}

.modal-intro-btn svg {
  width: 13px;
  height: auto;
}

.modal-intro-btn .intro-label {
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
}

.modal-intro-btn:hover:not([disabled]) { filter: brightness(1.08); }

.modal-intro-btn[disabled] {
  opacity: .55;
  cursor: default;
}

.modal-sheet {
  min-height: 0;
  overflow-y: auto;
  padding: 0 24px 24px;
}

/* The sheet's floating top: name bar plus the tab toggle ride together on top
   of the scroll. Negative margins bleed the block to the column edges so
   content slides under it full-width. */
.modal-top {
  position: sticky;
  top: 0;
  z-index: 1;
  margin: 0 -24px 0;
  background: var(--panel);
  border-bottom: 1px solid var(--line);
}

.modal-bar {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 16px 14px 12px 24px;
}

/* Scenes | About. Scenes first — the scene buttons are the CTAs; the sheet
   prose is reference material. */
.modal-tabs {
  display: flex;
  gap: 6px;
  padding: 0 24px 12px;
}

.modal-tab {
  padding: 3px 10px;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: .1em;
  text-transform: uppercase;
  background: var(--panel-2);
  color: var(--muted);
  border: 1px solid var(--line);
  border-radius: 999px;
}

.modal-tab[aria-selected="true"] {
  color: var(--accent-ink);
  background: var(--accent);
  border-color: var(--accent);
}

.modal-bar-id { flex: 1; min-width: 0; }

.modal-bar h2 {
  font-family: var(--serif);
  font-weight: 600;
  font-size: 24px;
  margin: 0;
}

.modal-bar .tagline {
  color: var(--muted);
  font-size: 13px;
  margin: 2px 0 0;
}

.modal-edit {
  text-decoration: none;
  font-size: 13px;
  white-space: nowrap;
}

.modal-label {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 18px 0 6px;
}

.modal-about {
  font-size: 13.5px;
  line-height: 1.55;
  margin: 0;
}

.modal-quirks {
  margin: 0;
  padding-left: 18px;
  font-size: 13.5px;
  line-height: 1.5;
}

.modal-quirks li + li { margin-top: 4px; }

.modal-voice {
  font-size: 13.5px;
  margin: 0;
}

/* Stacked, and the FLEXIBLE row is the portrait, not the sheet: the sheet takes the
   height its open tab actually needs (capped so About still scrolls) and the character
   gets every pixel left over. With the fr on the sheet instead, a short Scenes tab
   pooled its spare height as dead space under the buttons while the portrait sat
   cropped at a fixed 240px strip. */
@media (max-width: 640px) {
  .modal-split {
    grid-template-columns: 1fr;
    grid-template-rows: minmax(0, 1fr) auto;
    height: 92vh; /* fallback */
    height: 92dvh; /* dynamic viewport: honest about mobile browser chrome */
  }

  .modal-sheet {
    max-height: 46vh;
    max-height: 46dvh;
  }
}

.story-btn {
  display: block;
  width: 100%;
  text-align: left;
  background: var(--panel-2);
  color: var(--text);
  border: 1px solid var(--line);
  padding: 9px 12px;
  font-size: 13.5px;
}

.story-btn + .story-btn { margin-top: 6px; }

.story-btn:hover { border-color: var(--accent); }

/* Only the modal's scene list gets the chevron: those buttons NAVIGATE (close the
   sheet, enter the chat), while the editor reuses .story-btn for in-place controls
   where an arrow would promise a move that never happens. */
#modalStories .story-btn {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

#modalStories .story-btn::after {
  content: '';
  flex: none;
  width: 7px;
  height: 7px;
  border-top: 1.5px solid var(--muted);
  border-right: 1.5px solid var(--muted);
  transform: rotate(45deg);
}

#modalStories .story-btn:hover::after { border-color: var(--accent); }

@media (prefers-reduced-motion: no-preference) {
  .card-face img { transition: opacity .15s ease; }
  .card-face:hover img { opacity: .88; }
}

/* ── Chat layout ────────────────────────────────────────────────────────── */

.chat-grid {
  height: 100dvh;
  display: flex;
  flex-direction: column;
}

/* iOS-style navigation bar: back on the left, title dead centre, pinned to the
   viewport top on both breakpoints. The empty third grid cell balances the back
   button so the title truly centres. */
.chat-head {
  flex-shrink: 0;
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  padding: 8px 12px;
  padding-top: max(8px, env(safe-area-inset-top));
  background: var(--panel);
  border-bottom: 1px solid var(--line);
}

.chat-head .ghost { justify-self: start; }

/* The open book: two pages centered in the space under the bar, sharing top and
   bottom edges. The portrait sets the height; the chat page matches it. */
.book {
  display: flex;
  gap: 12px;
  height: min(86dvh, 768px);
  max-width: 100%;
  margin: auto; /* centers within the leftover viewport, both axes */
}

/* ── Stage (left page) — the hero ───────────────────────────────────────── */

/* Sized by the art itself: the aspect ratio is set per character from its manifest
   dimensions (see enterChat), so the page IS the portrait, edge to edge. */
.stage-col {
  position: relative;
  height: 100%;
  aspect-ratio: 1 / 1; /* placeholder — overwritten per character */
  max-width: 62vw;
}

.stage {
  position: absolute;
  inset: 0;
  border-radius: 14px;
  overflow: hidden;
  /* The spotlight: if the box ever letterboxes (narrow screens), the art sits on
     this instead of a hard edge. */
  background: radial-gradient(120% 85% at 50% 30%, #2c2440 0%, #191423 62%, #120e1a 100%);
}

.stage img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  opacity: 0;
  /* Discrete swaps ARE the animation — easing here would smear the mouth shapes. */
  transition: none;
}

.stage img.on { opacity: 1; }

/* The blink overlay is the ONE layer that eases. A real blink is motion-blurred, so a
   hard cut reads as a glitch — but that same easing on a viseme swap would smear the
   mouth shapes, which is why the rule above stays transition:none. This selector's
   specificity (0,2,1) has to beat that one's (0,1,1), or the crossfade silently never
   happens and the blink pops. */
.stage img.blink { transition: opacity 45ms linear; }

/* Emotion overlays ease longer than the blink: an expression change is a face moving,
   not a lip position — 140ms reads as intent where a hard cut reads as a glitch. */
.stage img.emotion { transition: opacity 140ms ease; }

/* ── Chat (right) ───────────────────────────────────────────────────────── */

/* Right page — pure chat: the log and the composer, nothing else. */
.chat-col {
  display: flex;
  flex-direction: column;
  min-height: 0;
  min-width: 0;
  height: 100%;
  width: clamp(360px, 41vw, 504px);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 14px;
  padding: 14px 16px;
}

.chat-head h2 {
  font-family: var(--serif);
  font-weight: 500;
  font-size: 17px;
  margin: 0;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.chat-head .story-title { color: var(--muted); }

.ghost {
  background: none;
  color: var(--muted);
  padding: 4px 8px;
  flex-shrink: 0;
}

.ghost:hover { color: var(--text); }

.log {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 18px 6px 8px 0;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

/* The narrator opens the scene in the book voice — prose, not a bubble. */
.narration {
  font-family: var(--serif);
  font-style: italic;
  color: var(--muted);
  border-left: 2px solid var(--line);
  padding-left: 14px;
  max-width: 62ch;
}

.narration p { margin: 0 0 .7em; }
.narration p:last-child { margin-bottom: 0; }
.narration .bubble-foot { margin-top: 10px; }

.bubble {
  max-width: 78%;
  padding: 10px 14px;
  border-radius: 14px;
  overflow-wrap: break-word;
}

.bubble.user {
  align-self: flex-end;
  background: var(--accent);
  color: var(--accent-ink);
  border-bottom-right-radius: 4px;
}

.bubble.char {
  align-self: flex-start;
  background: var(--panel-2); /* one step above the page it sits on */
  border: 1px solid var(--line);
  border-bottom-left-radius: 4px;
}

/* Inside a reply the registers switch mid-sentence, and each one matches a voice:
   narration is the narrator's, dialogue is hers, thought is hers unspoken. */
.bubble.char em {
  font-family: var(--serif);
  color: var(--muted);
}

/* A thought is heard in her own voice but never said out loud, so it belongs to neither of
   the other two registers — not the narrator's serif, not her upright dialogue. Same sans
   as her speech, because it is her; italic and set back, because nobody else can hear it.
   The brackets are kept rather than styled away: they are how the convention reads. */
.bubble.char .thought {
  font-family: var(--sans);
  font-style: italic;
  color: var(--accent);
  opacity: 0.72;
}

.bubble-foot {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 6px;
}

.play-btn {
  margin-left: auto; /* bottom-right corner of the bubble, meta or no meta */
  width: 24px;
  height: 24px;
  flex-shrink: 0;
  padding: 0;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: none;
  border: 1px solid var(--line);
  color: var(--muted);
}

.play-btn svg {
  width: 9px;
  height: auto;
  /* A play triangle centred by its box reads left-heavy; a hair of right shift
     optically centres it in the circle. */
  margin-left: 1px;
}

.play-btn:hover {
  color: var(--accent);
  border-color: var(--accent);
}

.bubble.error {
  align-self: flex-start;
  background: none;
  border: 1px dashed var(--line);
  color: var(--danger);
  font-size: 13.5px;
}

/* ── Composer ───────────────────────────────────────────────────────────── */

.composer {
  display: flex;
  gap: 8px;
  padding-top: 12px;
  border-top: 1px solid var(--line);
}

.composer input {
  flex: 1;
  min-width: 0;
  font: inherit;
  color: var(--text);
  background: var(--ink); /* recessed into the page */
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 10px 14px;
}

.composer input::placeholder { color: var(--muted); }

.composer input:focus {
  outline: none;
  border-color: var(--accent);
}

.composer button {
  background: var(--accent);
  color: var(--accent-ink);
  font-weight: 600;
  padding: 10px 18px;
  border-radius: 10px;
}

.composer button:disabled,
.composer input:disabled {
  opacity: .55;
  cursor: default;
}

/* ── Small screens: full-bleed messenger, not a book ────────────────────── */
/* Portrait pinned to the top, composer pinned to the bottom, messages scrolling
   between them. No position: fixed needed — only .log scrolls, so the flex column
   pins both ends for free. dvh units track the keyboard/URL-bar resizes. */

@media (max-width: 760px) {
  .book {
    flex-direction: column;
    flex: 1;
    min-height: 0;
    height: auto;
    width: 100%;
    gap: 0;
    margin: 0; /* fills the space under the bar instead of floating in it */
  }

  .stage-col {
    height: 32dvh;
    flex-shrink: 0;
    aspect-ratio: auto; /* full width; the art letterboxes on the spotlight */
    max-width: none;
  }

  .stage { border-radius: 0; } /* full-bleed against the screen edges */

  .chat-col {
    width: auto;
    flex: 1;
    min-height: 0;
    background: none;
    border: none;
    border-radius: 0;
    padding: 0 12px;
  }

  .back-label { display: none; } /* just the arrow — the word is desktop-only */

  .log { padding: 12px 2px 8px 0; }

  /* Clears the iPhone home indicator (viewport-fit=cover exposes the inset). */
  .composer { padding-bottom: max(10px, env(safe-area-inset-bottom)); }

  .bubble.char { background: var(--panel); } /* back on ink, one step is enough */

  .bubble { max-width: 88%; }
}

/* ── Character editor ───────────────────────────────────────────────────────
   /web/editor.html — same chrome as the chat (chat-head bar, ghost back), but
   the body is a full-viewport two-column form: identity left, stories right.
   Fields recess into the page like the composer input; labels are the modal's
   mono eyebrows. */

a.ghost { text-decoration: none; }

.edit-actions {
  justify-self: end;
  display: flex;
  align-items: center;
  gap: 12px;
  min-width: 0;
}

.save-status {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: .05em;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.save-status.error { color: var(--danger); }

.save-btn {
  background: var(--accent);
  color: var(--accent-ink);
  font-weight: 600;
  padding: 7px 18px;
}

.save-btn:disabled { opacity: .5; cursor: default; }

/* The top bar floats so Save (and the dirty status) never scroll away. */
.editor-head {
  position: sticky;
  top: 0;
  z-index: 2;
}

.edit-title-group {
  display: flex;
  align-items: center;
  gap: 9px;
  min-width: 0;
}

/* The character's face rides the bar — viseme_X is the resting frame, and
   object-position: top finds it in every framing from bust to full body. */
.edit-thumb {
  width: 28px;
  height: 28px;
  border-radius: 6px;
  object-fit: cover;
  object-position: top;
  background: #0e0b14;
  border: 1px solid var(--line);
  flex-shrink: 0;
}

.editor {
  display: grid;
  grid-template-columns: 190px minmax(0, 940px);
  gap: 34px;
  align-items: start;
  padding: 22px clamp(16px, 3vw, 44px) 70px;
}

.edit-nav {
  position: sticky;
  top: 72px; /* clears the floating bar */
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.nav-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  text-align: left;
  background: none;
  color: var(--muted);
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: .1em;
  text-transform: uppercase;
  padding: 9px 12px;
}

.nav-item:hover { color: var(--text); }

.nav-item.active {
  background: var(--panel-2);
  color: var(--text);
}

.nav-count {
  font-size: 10px;
  color: var(--muted);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 1px 7px;
}

.nav-rule {
  border: none;
  border-top: 1px solid var(--line);
  margin: 8px 4px;
}

/* ── Asset preview panes (visemes, blink) ───────────────────────────────────
   The same layer mechanics as the chat stage: stacked frames swapped by
   opacity with NO easing — except the blink overlay, the one layer that fades. */

.preview-stage {
  position: relative;
  width: min(360px, 100%);
  aspect-ratio: 1 / 1; /* placeholder — overwritten from the manifest */
  border-radius: 14px;
  overflow: hidden;
  background: radial-gradient(120% 85% at 50% 30%, #2c2440 0%, #191423 62%, #120e1a 100%);
}

.preview-stage img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  opacity: 0;
  transition: none;
}

.preview-stage img.on { opacity: 1; }

.preview-stage img.blink { transition: opacity 45ms linear; }

/* Emotion overlays ease like the chat stage's: an expression is a face moving. */
.preview-stage img.emotion { transition: opacity 140ms ease; }

.preview-controls {
  display: flex;
  align-items: center;
  gap: 12px;
  width: min(360px, 100%);
  margin: 10px 0 4px;
}

.preview-controls .story-btn { width: auto; }

.preview-controls a { font-size: 13px; text-decoration: none; }

.asset-meta {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: .05em;
  color: var(--muted);
  margin: 6px 0 0;
}

/* One stage, three drivers: the shape list on the left parks the stage on a
   frame; play and babble animate the same stage. No per-shape thumbnails — at
   list size nothing reads anyway; the stage IS the preview. */
.visemes-split {
  display: grid;
  grid-template-columns: minmax(190px, 250px) minmax(0, 1fr);
  gap: 24px;
  align-items: start;
}

@media (max-width: 700px) {
  .visemes-split { grid-template-columns: 1fr; }
  .viseme-stage-col { order: -1; } /* stage first when stacked */
}

.viseme-stage-col { min-width: 0; }

.viseme-stage-col .preview-stage,
.viseme-stage-col .preview-controls {
  width: min(520px, 100%);
}

.viseme-row {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  width: 100%;
  text-align: left;
  background: var(--panel-2);
  color: var(--muted);
  border: 1px solid var(--line);
  padding: 8px 11px;
  font-size: 12px;
}

.viseme-row + .viseme-row { margin-top: 6px; }

.viseme-row:hover { border-color: var(--accent); }

.viseme-row.active {
  border-color: var(--accent);
  color: var(--text);
}

.viseme-row strong {
  font-family: var(--mono);
  font-size: 13px;
  color: var(--text);
  min-width: 14px;
}

.viseme-row .row-body {
  display: flex;
  flex-direction: column;
  gap: 1px;
  flex: 1;
  min-width: 0;
}

.asset-meta.error { color: var(--danger); }

.tile-audit {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: .03em;
}

.tile-audit.bad { color: var(--danger); }

.audit-list {
  list-style: none;
  margin: 16px 0 0;
  padding: 0;
  max-width: 360px;
}

.audit-list li {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding: 7px 2px;
  border-bottom: 1px solid var(--line);
  font-size: 13px;
  color: var(--muted);
}

.audit-list strong {
  font-family: var(--mono);
  font-weight: 500;
  font-size: 12px;
  color: var(--text);
}

@media (max-width: 900px) {
  .editor { grid-template-columns: 1fr; gap: 18px; }
  .edit-nav { position: static; flex-direction: row; }
}

.edit-content { min-width: 0; }

.edit-head {
  font-family: var(--serif);
  font-weight: 600;
  font-size: 20px;
  margin: 0 0 14px;
}

.field { margin: 0 0 14px; }

.field .modal-label { margin: 0 0 6px; }

.field input,
.field textarea,
.rule-row input {
  width: 100%;
  font: inherit;
  color: var(--text);
  background: var(--ink); /* recessed, like the composer */
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 9px 12px;
}

.field textarea {
  resize: vertical;
  line-height: 1.5;
}

.field input:focus,
.field textarea:focus,
.rule-row input:focus {
  outline: none;
  border-color: var(--accent);
}

.field .hint {
  font-size: 12px;
  color: var(--muted);
  margin: 5px 0 0;
}

.rule-row {
  display: flex;
  gap: 6px;
  margin-bottom: 8px;
}

.rule-row input { flex: 1; }

.remove-btn {
  background: none;
  color: var(--muted);
  font-size: 12px;
  padding: 4px 8px;
  flex-shrink: 0;
}

.remove-btn:hover { color: var(--danger); }

/* Stories master–detail: pick on the left, edit on the right. */
.stories-split {
  display: grid;
  grid-template-columns: minmax(170px, 230px) minmax(0, 1fr);
  gap: 18px;
  align-items: start;
}

.story-pick { color: var(--muted); }

.story-pick:hover { color: var(--text); }

.story-pick.active {
  color: var(--text);
  border-color: var(--accent);
}

.story-list .add-btn {
  margin-top: 10px;
  padding: 10px;
  font-size: 13px;
}

@media (max-width: 700px) {
  .stories-split { grid-template-columns: 1fr; }
}

.story-card {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 14px 16px 6px;
}

.story-head {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 12px;
}

.story-head .remove-btn { margin-left: auto; }

.story-id {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: .05em;
  color: var(--muted);
}

.audio-badge {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--muted);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 2px 8px;
  white-space: nowrap;
}

.audio-badge.baked { color: var(--accent); }

.add-btn {
  width: 100%;
  padding: 12px;
  background: none;
  border: 2px dashed var(--line);
  border-radius: 12px;
  color: var(--muted);
  font-size: 13.5px;
}

.add-btn:hover {
  color: var(--accent);
  border-color: var(--accent);
}

.editor-error {
  color: var(--danger);
  text-align: center;
  padding: 48px 16px;
}

/* Deployed-commit label — deliberately faint; it exists to answer "which build
   am I looking at", not to be part of the page. */
.version {
  position: fixed;
  right: 10px;
  bottom: 8px;
  font: 11px var(--mono);
  color: var(--accent);
  opacity: 0.35;
  pointer-events: none;
  user-select: none;
}
