/* BY MARGO — light UI. Flat fills, hairline rules, one accent. No gradients. */

/* The hidden attribute, made to mean it.
   `[hidden]` is only `display: none` from the user-agent stylesheet, so any
   author rule that sets display on the same element beats it — and this file
   sets display on nearly everything. That has been patched five separate times
   already, once per class, each time after something refused to disappear:
   .restore, .arrow, .dots, .dp-confirm, .filter-btn. The sixth was an import
   button that stayed on screen saying "Adding…" over the message announcing
   the import had finished.

   One rule, once. The five below it are now redundant and harmless. */
[hidden] { display: none !important; }


/* Two palettes, three states.
 *
 * A reader picks a story at eleven at night, and a page that arrives the colour
 * of paper is the one thing this app does that physically hurts. So the
 * palettes are a pair, and everything below is written against tokens rather
 * than colours — the rule the whole file already followed, which is the only
 * reason a second palette was an afternoon rather than a rewrite.
 *
 * The three states are the fiddly part. An explicit choice stamps data-theme on
 * <html>; the default setting stamps nothing at all, and then only the media
 * query separates light from dark. So: bare :root is the complete light
 * palette, the media query redefines tokens for people who have chosen nothing,
 * guarded so an explicit "light" still wins on a dark OS, and the [data-theme]
 * block redefines them again so the toggle wins in both directions.
 *
 * Nothing below this block may set a colour inside a media query or a
 * [data-theme] block. A colour defined only there does not exist in the
 * unstamped state, which is most people, and the page renders one theme's text
 * on the other theme's ground.
 */
:root {
  color-scheme: light;

  --bg: #f7f6f4;
  --panel: #ffffff;
  --sunken: #efedea;
  --ink: #16151a;        /* 16.8:1 on bg */
  --ink-2: #4d4a55;      /*  8.0:1 — secondary text */
  --ink-3: #6f6b77;      /*  4.8:1 — labels, placeholder. Never body copy. */
  --narr: #46434e;       /*  9.0:1 — narration is story, not chrome */
  --rule: #dedad5;
  --rule-firm: #c6c1bb;
  --accent: #8e2a3f;
  --accent-ink: #ffffff;
  --accent-wash: #f7ecee;

  /* The reader's own message. Its own pair rather than --ink, because the two
     want opposite things in the dark: --ink inverts to near-white, and a white
     bubble in a dark thread is the brightest thing on a screen somebody is
     reading in bed. */
  --me-bg: #16151a;
  --me-ink: #f4f2f0;

  --plate: #e8e5e1;      /* media that has not loaded, or does not exist */
  --hover: rgba(0, 0, 0, 0.035);
  --scrim: rgba(22, 21, 26, 0.4);
  --arrow: rgba(255, 255, 255, 0.92);   /* sits over a photograph, not the page */

  --display: "Hoefler Text", Didot, "Bodoni 72", Garamond, Georgia, serif;
  --ui: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, system-ui, sans-serif;

  --r: 10px;
  --shadow: 0 1px 2px rgba(22, 21, 26, 0.04), 0 4px 16px rgba(22, 21, 26, 0.04);
}

/* Chosen nothing: follow the system, unless they have explicitly chosen light. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
  color-scheme: dark;

  --bg: #131216;
  --panel: #1b191f;
  --sunken: #232028;
  --ink: #f2f0ee;        /* 16.4:1 on bg */
  --ink-2: #b8b3bd;      /*  9.1:1 — secondary text */
  --ink-3: #8f8a97;      /*  5.6:1 — labels only, as in the light palette */
  --narr: #c4bfc9;       /*  9.7:1 on panel — narration is story, not chrome */
  --rule: #2c2932;
  --rule-firm: #3d3946;
  /* The wine reads as near-black on a dark ground, so the accent lifts to the
     rose it already is in the film in tools/demo-video.mjs. Same hue, enough
     lightness to carry a link. */
  --accent: #d4738a;     /*  5.9:1 */
  --accent-ink: #1a1016; /*  5.9:1 on the accent */
  --accent-wash: #241a1f;

  --me-bg: #322c3a;
  --me-ink: #f2f0ee;

  --plate: #232028;
  --hover: rgba(255, 255, 255, 0.05);
  --scrim: rgba(8, 7, 10, 0.6);
  --arrow: rgba(27, 25, 31, 0.92);

  /* A shadow is light failing to reach somewhere. There is no light here, so
     depth comes from the border instead and the shadow only deepens the ground. */
  --shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 4px 16px rgba(0, 0, 0, 0.24);
  }
}

/* Chosen dark: wins even on a light system. */
:root[data-theme="dark"] {
  color-scheme: dark;

  --bg: #131216;
  --panel: #1b191f;
  --sunken: #232028;
  --ink: #f2f0ee;        /* 16.4:1 on bg */
  --ink-2: #b8b3bd;      /*  9.1:1 — secondary text */
  --ink-3: #8f8a97;      /*  5.6:1 — labels only, as in the light palette */
  --narr: #c4bfc9;       /*  9.7:1 on panel — narration is story, not chrome */
  --rule: #2c2932;
  --rule-firm: #3d3946;
  /* The wine reads as near-black on a dark ground, so the accent lifts to the
     rose it already is in the film in tools/demo-video.mjs. Same hue, enough
     lightness to carry a link. */
  --accent: #d4738a;     /*  5.9:1 */
  --accent-ink: #1a1016; /*  5.9:1 on the accent */
  --accent-wash: #241a1f;

  --me-bg: #322c3a;
  --me-ink: #f2f0ee;

  --plate: #232028;
  --hover: rgba(255, 255, 255, 0.05);
  --scrim: rgba(8, 7, 10, 0.6);
  --arrow: rgba(27, 25, 31, 0.92);

  /* A shadow is light failing to reach somewhere. There is no light here, so
     depth comes from the border instead and the shadow only deepens the ground. */
  --shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 4px 16px rgba(0, 0, 0, 0.24);
}

* { box-sizing: border-box; margin: 0; padding: 0; }

html, body { height: 100%; }

body {
  background: var(--bg);
  color: var(--ink);
  font-family: var(--ui);
  font-size: 16px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  overflow: hidden;
}

button { font: inherit; color: inherit; cursor: pointer; border: 0; background: none; }
img { display: block; max-width: 100%; }

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

/* ---------- shell ---------- */

#app { height: 100%; display: flex; flex-direction: column; }

.view {
  flex: 1;
  min-height: 0;
  display: none;
  flex-direction: column;
  animation: rise 0.32s cubic-bezier(0.2, 0.7, 0.3, 1);
}
.view.on { display: flex; }

@keyframes rise {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .view { animation: none; }
  * { transition: none !important; }
}

.wrap {
  width: 100%;
  max-width: 460px;
  margin: 0 auto;
  padding: 0 20px;
}

.imprint {
  font-family: var(--ui);
  font-size: 10px;
  letter-spacing: 0.32em;
  text-transform: uppercase;
  color: var(--ink-3);
  text-align: center;
  padding: 22px 0 0;
  flex: none;
}

/* ---------- media plates ---------- */

.plate {
  background: var(--plate);
  border: 1px solid var(--rule);
  border-radius: var(--r);
  overflow: hidden;
  position: relative;
  display: grid;
  place-items: center;
}
.plate::after {
  content: attr(data-label);
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.plate.loaded::after { content: none; }
.plate img { width: 100%; height: 100%; object-fit: cover; }

/* ---------- 1. card ---------- */

#card { overflow-y: auto; position: relative; }
#card .wrap {
  flex: 1;
  display: flex;
  flex-direction: column;
  /* `safe` so the top is never clipped once the detail below outgrows a short
     window — plain centring scrolls the portrait off above the scroll origin. */
  justify-content: safe center;
  gap: 14px;
  padding-top: 8px;
  padding-bottom: 28px;
}
#card .plate { aspect-ratio: 3 / 4; }
#card .traits { margin-top: 14px; }
#card .card-line { margin-top: 12px; }

/* One box for everything beside the portrait, so the two-column layout is two
   grid items rather than five — the portrait then spans the detail's height on
   its own, with no row-span count to keep in sync with the markup. */
.card-side { display: flex; flex-direction: column; gap: 14px; }

/* The detail under the portrait. Hairline rows, no panels — the card is
   already a panel, and boxes inside boxes read as a form. */
.facts { border-top: 1px solid var(--rule); }
.fact {
  display: flex;
  gap: 16px;
  padding: 11px 0;
  border-bottom: 1px solid var(--rule);
}
.fact dt {
  flex: none;
  width: 62px;
  padding-top: 3px;
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.fact dd { font-size: 14px; line-height: 1.5; color: var(--ink-2); }
.fact dd b { display: block; color: var(--ink); font-weight: 550; }
.fact dd b.q { display: inline; color: var(--accent); font-weight: 550; }

/* The flex gap already spaces this; its own margin would double it. */
#card .fine { margin-top: 0; text-align: center; }
#card .fine b { color: var(--ink); font-weight: 600; }

/* Once there is width for it: portrait left, everything else beside it, so the
   landing fits without scrolling. The portrait stretches to whatever height the
   detail column needs rather than setting its own — that is what keeps the two
   sides equal and the page still. Below this it stays stacked. */
@media (min-width: 760px) {
  #card .wrap {
    max-width: 840px;
    display: grid;
    grid-template-columns: minmax(0, 340px) minmax(0, 1fr);
    column-gap: 40px;
    row-gap: 14px;
    align-content: center;
    /* Inherited from the stacked layout, where it centres the flex children.
       On a grid it centres the two tracks instead, so the second one stops
       filling and the portrait floats off the left edge. */
    justify-content: normal;
    padding-top: 0;
  }
  #card .plate {
    align-self: stretch;
    aspect-ratio: auto;
  }
  /* The image is `height: 100%` of an auto-height box, so its natural aspect
     ratio feeds back into row sizing and inflates the first row to the full
     height of the portrait. Taking it out of flow lets the text column decide
     the height and the portrait simply match it. */
  #card .plate img { position: absolute; inset: 0; }
  #card .card-name { font-size: 48px; }
  #card .fine { text-align: left; }
}

/* What kind of story this is, above whose story it is.
   Same treatment as a section label because it is doing the same job: naming
   the thing that follows without competing with it. Hidden when a character has
   no category rather than reserving an empty line — most will not have one, and
   a gap above the name reads as a mistake. */
.card-category {
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent);
  margin: 0 0 8px;
}

.card-name {
  font-family: var(--display);
  font-size: 42px;
  line-height: 1;
  letter-spacing: -0.02em;
}
.card-meta {
  font-size: 13px;
  color: var(--ink-2);
  margin-top: 8px;
}
.card-line {
  font-family: var(--display);
  font-size: 19px;
  line-height: 1.45;
  color: var(--ink-2);
  margin-top: 14px;
}

.btn {
  display: block;
  width: 100%;
  background: var(--accent);
  color: var(--accent-ink);
  padding: 15px;
  border-radius: var(--r);
  font-size: 15px;
  font-weight: 550;
  letter-spacing: 0.01em;
  text-align: center;
  transition: opacity 0.15s;
}
.btn:hover { opacity: 0.88; }
.btn:active { opacity: 0.76; }
.btn.ghost {
  background: transparent;
  color: var(--ink-2);
  box-shadow: inset 0 0 0 1px var(--rule-firm);
  font-weight: 450;
}
.btn.ghost:hover { background: var(--panel); opacity: 1; }
.btn[disabled] { opacity: 0.45; pointer-events: none; }

/* ---------- 2. profile ---------- */

#profile { overflow-y: auto; }
#profile .wrap { padding-top: 24px; padding-bottom: 40px; }

.back {
  font-size: 13px;
  color: var(--ink-3);
  padding: 4px 0;
  margin-bottom: 16px;
}
.back:hover { color: var(--ink); }

.p-head { display: flex; gap: 16px; align-items: center; }
.p-head .plate { width: 84px; height: 84px; border-radius: 50%; flex: none; }
.p-head .plate::after { font-size: 9px; }
.p-name { font-family: var(--display); font-size: 30px; line-height: 1.05; }

.traits { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 18px; }
.trait {
  font-size: 12px;
  color: var(--ink-2);
  background: var(--panel);
  border: 1px solid var(--rule);
  border-radius: 100px;
  padding: 5px 11px;
}

.strip {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 6px;
  margin-top: 20px;
}
.strip .plate { aspect-ratio: 1; border-radius: 6px; }
.strip .plate::after { font-size: 0; }

.section-label {
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin: 28px 0 10px;
}

.panel {
  background: var(--panel);
  border: 1px solid var(--rule);
  border-radius: var(--r);
  padding: 16px 18px;
}

.arc-title { font-family: var(--display); font-size: 21px; line-height: 1.2; }
.arc-line { font-size: 14px; color: var(--ink-2); margin-top: 7px; }

.voice-row { display: flex; align-items: center; gap: 13px; }
.play {
  width: 42px; height: 42px; flex: none;
  border-radius: 50%;
  background: var(--accent);
  color: var(--accent-ink);
  display: grid; place-items: center;
  font-size: 13px;
}
.voice-sub { font-size: 13px; color: var(--ink-2); }

/* ---------- 3. cold open ---------- */

/* Fixed, not tokenised. This is a darkened room before a story starts, and it
   is that in both palettes — on --ink it would invert to a white flash. */
#open {
  background: #16151a;
  color: #f2f0ee;
  justify-content: center;
  position: relative;          /* anchor for .skip */
}
#open .wrap { text-align: center; }

.rain {
  font-size: 11px;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: #9a95a0;
}
.open-line {
  font-family: var(--display);
  font-size: 26px;
  line-height: 1.45;
  margin: 26px 0 0;
  min-height: 3.6em;
  text-wrap: balance;
}

/* Out of the flow, in the corner — the scene is the screen, and the way out of
   it should be available without competing with it. The label carries the
   contrast (7.4:1); the outline only has to be findable (3.4:1). */
.skip {
  position: absolute;
  right: 20px;
  bottom: max(20px, env(safe-area-inset-bottom, 0px));
  padding: 7px 15px;
  border: 1px solid #6a6573;
  border-radius: 8px;
  font-size: 12px;
  letter-spacing: 0.04em;
  color: #b4afbb;
  transition: color 0.15s, border-color 0.15s, background 0.15s;
}
.skip:hover { color: #f2f0ee; border-color: #8d8798; background: #211f26; }

/* ---------- 4. chat ---------- */

#chat { background: var(--bg); }

.bar {
  flex: none;
  background: var(--panel);
  border-bottom: 1px solid var(--rule);
  padding: 11px 0;
}
.bar .wrap { display: flex; align-items: center; gap: 11px; }
.bar .plate { width: 34px; height: 34px; border-radius: 50%; flex: none; }
.bar .plate::after { font-size: 0; }
.bar-name { font-size: 15px; font-weight: 550; }
.bar-sub { font-size: 12px; color: var(--ink-2); }

.meter { display: flex; gap: 3px; margin-left: auto; }
.pip { width: 5px; height: 5px; border-radius: 50%; background: var(--rule-firm); transition: background 0.4s; }
.pip.on { background: var(--accent); }

.thread { flex: 1; overflow-y: auto; padding: 22px 0 8px; }
.thread .wrap { display: flex; flex-direction: column; gap: 12px; }

.msg {
  max-width: 82%;
  padding: 11px 15px;
  border-radius: 16px;
  font-size: 15.5px;
  line-height: 1.5;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  animation: pop 0.25s cubic-bezier(0.2, 0.7, 0.3, 1);
}
@keyframes pop { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }

/* One reply from her: narration lines and speech bubbles stacked together. */
.turn {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 9px;
}

/* Narration — bare and quieter than speech, but it IS the story, so it reads
   at content contrast. Set slightly larger than dialogue: italic serif needs
   the size back that the slant takes away. */
.narr {
  font-family: var(--display);
  font-size: 16.5px;
  font-style: italic;
  line-height: 1.55;
  color: var(--narr);
  padding: 0 4px;
  max-width: 92%;
  animation: pop 0.25s cubic-bezier(0.2, 0.7, 0.3, 1);
}

.msg.them {
  align-self: flex-start;
  background: var(--panel);
  border: 1px solid var(--rule);
  border-bottom-left-radius: 5px;
}
/* Her speech runs to the same measure as her narration, so the block reads as
   one voice rather than two columns. User messages stay narrower on the right. */
.turn .msg.them { max-width: 92%; border-bottom-left-radius: 16px; }
.turn .msg.them:last-child { border-bottom-left-radius: 5px; }
.msg.me {
  align-self: flex-end;
  background: var(--me-bg);
  color: var(--me-ink);
  border-bottom-right-radius: 5px;
}

.stage {
  align-self: center;
  font-size: 10.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
  padding: 10px 0 2px;
}

.dots { display: flex; gap: 4px; padding: 4px 2px; }
.dots i {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--ink-3);
  animation: blink 1.3s infinite;
}
.dots i:nth-child(2) { animation-delay: 0.18s; }
.dots i:nth-child(3) { animation-delay: 0.36s; }
@keyframes blink { 0%, 60%, 100% { opacity: 0.22; } 30% { opacity: 0.9; } }

.composer {
  flex: none;
  background: var(--panel);
  border-top: 1px solid var(--rule);
  padding: 11px 0 calc(11px + env(safe-area-inset-bottom));
}
.composer .wrap { display: flex; gap: 9px; align-items: flex-end; }

.composer textarea {
  flex: 1;
  font: inherit;
  font-size: 15.5px;
  color: var(--ink);
  background: var(--bg);
  border: 1px solid var(--rule);
  border-radius: 20px;
  padding: 10px 15px;
  resize: none;
  max-height: 116px;
  line-height: 1.45;
}
.composer textarea:focus { outline: none; border-color: var(--rule-firm); }
.composer textarea::placeholder { color: var(--ink-3); }

.send {
  width: 40px; height: 40px; flex: none;
  border-radius: 50%;
  background: var(--accent);
  color: var(--accent-ink);
  display: grid; place-items: center;
  transition: opacity 0.15s;
}
.send[disabled] { opacity: 0.3; pointer-events: none; }

/* ---------- 5. unlock ---------- */

/* First run. Uses the sheet the rest of the app already uses, so it reads as
   part of the application rather than as something bolted to the front of it. */
.hello-step {
  font-size: 10px; letter-spacing: .16em; text-transform: uppercase;
  color: var(--ink-3); margin: 0 0 12px;
}
.hello-dots { display: flex; gap: 7px; justify-content: center; margin: 22px 0 4px; }
.hello-dots i {
  width: 6px; height: 6px; border-radius: 50%; background: var(--rule-firm);
  transition: background .2s;
}
.hello-dots i.on { background: var(--accent); }
.sheet.hello .sheet-sub { min-height: 5.4em; }

.scrim {
  position: fixed;
  inset: 0;
  background: var(--scrim);
  display: none;
  align-items: flex-end;
  justify-content: center;
  z-index: 40;
  backdrop-filter: blur(2px);
}
.scrim.on { display: flex; animation: fade 0.25s; }
@keyframes fade { from { opacity: 0; } to { opacity: 1; } }

.sheet {
  background: var(--panel);
  border-radius: 18px 18px 0 0;
  width: 100%;
  max-width: 460px;
  padding: 30px 24px calc(26px + env(safe-area-inset-bottom));
  text-align: center;
  box-shadow: var(--shadow);
  animation: up 0.32s cubic-bezier(0.2, 0.7, 0.3, 1);
  /* The sheet grew a terms line and a restore form; on a short window it now
     runs off the bottom, taking the buttons with it. Scroll inside the sheet
     rather than letting the offer leave the screen. */
  max-height: 92dvh;
  overflow-y: auto;
}
@keyframes up { from { transform: translateY(100%); } to { transform: none; } }

.sheet-line {
  font-family: var(--display);
  font-size: 25px;
  line-height: 1.35;
  text-wrap: balance;
}
.sheet-sub { font-size: 14px; color: var(--ink-2); margin: 12px 0 22px; }
.sheet .btn + .btn { margin-top: 9px; }
.price {
  display: inline-block;
  background: var(--accent-wash);
  color: var(--accent);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.04em;
  padding: 4px 10px;
  border-radius: 100px;
  margin-bottom: 16px;
}

.fine {
  font-size: 11.5px;
  color: var(--ink-2);
  margin-top: 16px;
  line-height: 1.5;
}

/* ---------- age gate ---------- */

#gate {
  position: fixed; inset: 0; z-index: 60;
  background: var(--bg);
  display: none;
  align-items: center;
  justify-content: center;
}
#gate.on { display: flex; }
#gate .wrap { text-align: center; }
#gate .card-name { font-size: 30px; }

/* ---------- contact card ---------- */

.avatar-btn { padding: 0; cursor: pointer; }
.avatar-btn:hover { opacity: .85; }
.bar-id { text-align: left; cursor: pointer; padding: 0; }
.bar-id:hover .bar-name { color: var(--accent); }

.scrim.dp { align-items: center; padding: 20px; }

.dp-card {
  background: var(--panel);
  border-radius: 16px;
  width: 100%;
  max-width: 430px;
  max-height: 88vh;
  overflow-y: auto;
  position: relative;
  box-shadow: var(--shadow);
  animation: dpIn .3s cubic-bezier(.2,.7,.3,1);
}
@keyframes dpIn { from { opacity:0; transform: scale(.94); } to { opacity:1; transform:none; } }

.dp-close {
  position: absolute; top: 12px; right: 12px; z-index: 2;
  width: 30px; height: 30px; border-radius: 50%;
  background: rgba(22,21,26,.5); color: #fff;
  display: grid; place-items: center; font-size: 13px;
  backdrop-filter: blur(4px);
}
.dp-close:hover { background: rgba(22,21,26,.72); }

.dp-photo {
  aspect-ratio: 1;
  border-radius: 16px 16px 0 0;
  border: 0;
  border-bottom: 1px solid var(--rule);
}
.dp-body { padding: 20px 24px 24px; }
.dp-name { font-family: var(--display); font-size: 29px; line-height: 1.1; font-weight: 400; }
.dp-meta { font-size: 13px; color: var(--ink-2); margin-top: 5px; }

.dp-rows { margin-top: 18px; border-top: 1px solid var(--rule); }
.dp-row {
  display: flex; align-items: center; gap: 12px;
  padding: 9px 0; border-bottom: 1px solid var(--rule);
  font-size: 13.5px;
}
.dp-row span:first-child {
  font-size: 10px; letter-spacing: .14em; text-transform: uppercase;
  color: var(--ink-3); width: 96px; flex: none;
}
.dp-row b { font-weight: 500; }
.dp-row .meter { margin-left: 0; }

.dp-note {
  font-family: var(--display);
  font-style: italic;
  font-size: 15px;
  line-height: 1.5;
  color: var(--narr);
  margin-top: 16px;
  padding-left: 12px;
  border-left: 2px solid var(--rule-firm);
}
.dp-body .fine { margin-top: 16px; }

/* ---------- restore purchase ---------- */

.restore-link {
  display: block;
  width: 100%;
  margin-top: 14px;
  font-size: 13px;
  color: var(--ink-3);
  text-decoration: underline;
  text-underline-offset: 3px;
}
.restore-link:hover { color: var(--ink); }

.restore { display: flex; flex-direction: column; gap: 8px; margin-top: 12px; }
/* `display` on the class beats the [hidden] attribute's own display:none, so
   without this the form is open before anyone asks for it. */
.restore[hidden] { display: none; }
.restore input {
  width: 100%;
  padding: 12px 13px;
  font: inherit;
  font-size: 15px;
  color: var(--ink);
  background: var(--panel);
  border: 1px solid var(--rule-firm);
  border-radius: var(--r);
}
.restore input::placeholder { color: var(--ink-3); }
.restore input:focus { outline: 2px solid var(--accent); outline-offset: 1px; }
.restore-done { font-size: 13px; line-height: 1.5; color: var(--ink-2); margin-top: 12px; }

/* Subscription terms. Quiet, but above the fold of the sheet and never behind
   a disclosure — the amount and the way out belong next to the button. */
.terms {
  font-size: 12px;
  line-height: 1.5;
  color: var(--ink-2);
  text-align: center;
  margin-top: 10px;
}

/* ---------- browsing between stories ---------- */

/* Outside the card's own column so they never crowd the portrait, and pinned to
   the viewport rather than the content — the card changes height per character
   and the arrows should not move when it does. */
.arrow {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 5;
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  font-size: 26px;
  line-height: 1;
  color: var(--ink-2);
  background: var(--panel);
  border: 1px solid var(--rule-firm);
  border-radius: 50%;
  transition: color 0.15s, border-color 0.15s, transform 0.15s;
}
.arrow:hover { color: var(--ink); border-color: var(--ink-3); }
.arrow:active { transform: translateY(-50%) scale(0.94); }
.arrow[hidden] { display: none; }
.arrow.prev { left: 12px; }
.arrow.next { right: 12px; }

@media (min-width: 760px) {
  .arrow { width: 52px; height: 52px; font-size: 30px; }
  .arrow.prev { left: 32px; }
  .arrow.next { right: 32px; }
}

/* Small screens: the arrows sit over the portrait, so they need to read against
   a photograph rather than the page. */
@media (max-width: 759px) {
  .arrow {
    background: var(--arrow);
    backdrop-filter: blur(4px);
  }
}

.dots { display: flex; gap: 7px; justify-content: center; margin-top: -4px; }
.dots[hidden] { display: none; }
.dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--rule-firm);
  transition: background 0.2s, transform 0.2s;
}
.dot:hover { background: var(--ink-3); }
.dot.on { background: var(--accent); transform: scale(1.25); }

/* One short move on change, so a swap reads as navigation and not a repaint. */
@keyframes slide-in {
  from { opacity: 0; transform: translateX(10px); }
  to   { opacity: 1; transform: none; }
}
#card.slid .wrap { animation: slide-in 0.28s cubic-bezier(0.2, 0.7, 0.3, 1); }
@media (prefers-reduced-motion: reduce) {
  #card.slid .wrap { animation: none; }
}

/* ---------- image viewer ---------- */

/* Its own scrim: the sheet variant docks to the bottom and this has to centre,
   and it goes darker than the rest because the photograph is the whole content. */
.scrim.lb {
  align-items: center;
  justify-content: center;
  background: rgba(12, 11, 14, 0.94);
  backdrop-filter: blur(6px);
  z-index: 60;
}
.lb-stage {
  max-width: min(94vw, 760px);
  max-height: 88dvh;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  animation: dpIn 0.28s cubic-bezier(0.2, 0.7, 0.3, 1);
}
.lb-stage img {
  max-width: 100%;
  max-height: 80dvh;
  width: auto;
  border-radius: 12px;
  object-fit: contain;
}
.lb-count {
  font-size: 12px;
  letter-spacing: 0.16em;
  color: rgba(255, 255, 255, 0.62);
  font-variant-numeric: tabular-nums;
}
.lb-close {
  position: absolute;
  top: max(16px, env(safe-area-inset-top));
  right: 16px;
  z-index: 2;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-size: 15px;
  color: #fff;
  background: rgba(255, 255, 255, 0.12);
}
.lb-close:hover { background: rgba(255, 255, 255, 0.22); }

.lb-arrow {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 2;
  width: 48px;
  height: 48px;
  display: grid;
  place-items: center;
  font-size: 30px;
  line-height: 1;
  color: #fff;
  background: rgba(255, 255, 255, 0.12);
  border-radius: 50%;
  transition: background 0.15s;
}
.lb-arrow:hover { background: rgba(255, 255, 255, 0.24); }
.lb-arrow.prev { left: 12px; }
.lb-arrow.next { right: 12px; }
@media (min-width: 760px) {
  .lb-arrow.prev { left: 28px; }
  .lb-arrow.next { right: 28px; }
}

/* The strip is the way in, so it has to look like one. */
.strip .plate { cursor: zoom-in; transition: transform 0.15s, border-color 0.15s; }
.strip .plate:hover { transform: translateY(-2px); border-color: var(--rule-firm); }

/* ---------- conversations ---------- */

#inbox { overflow-y: auto; }
#inbox .wrap { padding-top: 6px; padding-bottom: 40px; }

.inbox-title {
  font-family: var(--display);
  font-size: 34px;
  line-height: 1;
  letter-spacing: -0.02em;
  margin: 10px 0 18px;
}

.rows { border-top: 1px solid var(--rule); margin-bottom: 22px; }

/* A row is a button, because the whole row is the target — a name-sized tap
   area is the classic way an inbox feels broken on a phone. */
.row {
  display: flex;
  align-items: center;
  gap: 13px;
  width: 100%;
  padding: 13px 2px;
  border-bottom: 1px solid var(--rule);
  text-align: left;
}
.row:hover { background: var(--panel); }
.row .plate { width: 46px; height: 46px; border-radius: 50%; flex: none; }
.row .plate::after { font-size: 0; }

.row-main { flex: 1; min-width: 0; }
.row-top { display: flex; align-items: baseline; gap: 8px; }
.row-name { font-size: 15px; font-weight: 550; }
.row-stage {
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-left: auto;
  flex: none;
}
.row-line {
  font-size: 13.5px;
  color: var(--ink-2);
  margin-top: 2px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.row-line.none { color: var(--ink-3); font-style: italic; }
.row-line b { font-weight: 400; color: var(--ink-3); }

.row-more {
  flex: none;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  color: var(--ink-3);
  font-size: 17px;
  line-height: 1;
  display: grid;
  place-items: center;
}
.row-more:hover { background: var(--sunken); color: var(--ink); }

/* Confirm in the row rather than in a dialog: a browser confirm() blocks the
   page, and a modal for one destructive line is heavier than the line. */
.row-confirm {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 2px;
  border-bottom: 1px solid var(--rule);
  font-size: 13.5px;
  color: var(--ink-2);
}
.row-confirm span { flex: 1; }
.row-confirm button {
  font-size: 13px;
  padding: 7px 12px;
  border-radius: 8px;
  border: 1px solid var(--rule-firm);
  color: var(--ink-2);
}
.row-confirm button.go { border-color: var(--accent); color: var(--accent); font-weight: 550; }
.row-confirm button:hover { background: var(--panel); }

/* Back out of a conversation. */
.bar-back {
  flex: none;
  width: 30px;
  margin-left: -6px;
  font-size: 24px;
  line-height: 1;
  color: var(--ink-3);
}
.bar-back:hover { color: var(--ink); }

/* The way in, from the browse card. */
.corner {
  position: absolute;
  top: 18px;
  right: 16px;
  z-index: 6;
  font-size: 12px;
  color: var(--ink-3);
  padding: 6px 10px;
  border: 1px solid var(--rule);
  border-radius: 100px;
  background: var(--panel);
}
.corner:hover { color: var(--ink); border-color: var(--rule-firm); }

/* ---------- where you are, and when it moves ---------- */

.bar-pos {
  margin-left: 7px;
  font-variant-numeric: tabular-nums;
  color: var(--ink-3);
}

/* The meter used to change with a 0.4s fade in the corner while the reader was
   mid-sentence in the middle of the screen — the feedback existed and was
   invisible. This says it in a word, once, and gets out of the way. */
.meter-said {
  font-size: 11px;
  letter-spacing: 0.04em;
  color: var(--ink-3);
  margin-left: auto;
  padding-right: 8px;
  opacity: 0;
  transition: opacity 0.35s;
  white-space: nowrap;
}
.meter-said.show { opacity: 1; }
.meter-said + .meter { margin-left: 0; }

@keyframes pip-pulse {
  0%   { transform: scale(1); }
  40%  { transform: scale(2.1); }
  100% { transform: scale(1); }
}
.pip.moved { animation: pip-pulse 0.55s cubic-bezier(0.2, 0.7, 0.3, 1); }
@media (prefers-reduced-motion: reduce) { .pip.moved { animation: none; } }

/* Destructive, so it sits at the bottom of the contact card and looks like a
   link rather than a button you could hit on the way to something else. */
.dp-reset {
  display: block;
  width: 100%;
  margin-top: 18px;
  padding: 10px;
  font-size: 13px;
  color: var(--ink-3);
  text-decoration: underline;
  text-underline-offset: 3px;
}
.dp-reset:hover { color: var(--accent); }
.dp-confirm {
  margin-top: 14px;
  font-size: 13px;
  line-height: 1.5;
  color: var(--ink-2);
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
}
.dp-confirm[hidden] { display: none; }
.dp-confirm span { flex: 1 1 100%; }
.dp-confirm button {
  flex: 1;
  padding: 8px 12px;
  font-size: 13px;
  border: 1px solid var(--rule-firm);
  border-radius: 8px;
  color: var(--ink-2);
}
.dp-confirm button.go { border-color: var(--accent); color: var(--accent); font-weight: 550; }

/* ---------- account ---------- */
/* Deliberately quiet and at the bottom of the list: the reason to sign in is
   the conversations sitting directly above it, and a prompt that arrives before
   there is anything to lose is just a wall. */
.account {
  margin-top: 26px;
  padding-top: 16px;
  border-top: 1px solid var(--rule);
  text-align: center;
}
.account-line { font-size: 13px; color: var(--ink-3); }
.account-btn {
  margin-top: 6px;
  font: inherit;
  font-size: 13px;
  color: var(--accent);
  background: none;
  border: 0;
  padding: 4px;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 3px;
}
#signin-email {
  width: 100%;
  margin: 14px 0 10px;
  padding: 11px 13px;
  font: inherit;
  font-size: 15px;
  color: var(--ink);
  background: var(--bg);
  border: 1px solid var(--rule-firm);
  border-radius: 9px;
}
#signin-email:focus { outline: 2px solid var(--accent); outline-offset: -1px; }

/* The contact card's strip.
   Smaller than the profile's — it sits under the portrait in a modal rather
   than on a page of its own, and its job is to say "there are more" rather
   than to be browsed. Tapping one closes the card and opens the viewer there. */
.dp-strip {
  margin: 10px 14px 0;
  gap: 6px;
}
.dp-strip .plate { border-radius: 5px; }

/* ---------- category filter ---------- */
/* Above the card, not beside it: the card is the thing being browsed and the
   filter is how you narrow the browsing. Hidden entirely when nothing is
   tagged, so a catalogue without categories does not grow an empty control. */
.filters {
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
  justify-content: center;
  /* The wordmark above has top padding and no bottom, so its text sits on the
     edge of its own box — measured, the two were flush at 37.5px and the chips
     read as a second line of the masthead. */
  margin: 18px auto 22px;
  padding: 0 20px;
  max-width: 660px;
}

/* On a phone the row becomes one button.
   Wrapped, eleven chips cost four rows and 147px before the reader saw a
   single character. Scrolling them sideways traded that for a row that was
   clipped and half-hidden. A button costs one line, and the sheet shows every
   option at once without competing with the card. */
.filter-btn {
  display: none;
  margin: 16px auto 20px;
  padding: 7px 18px;
  font: inherit;
  font-size: 13px;
  color: var(--ink-3);
  background: transparent;
  border: 1px solid var(--rule-firm);
  border-radius: 20px;
  cursor: pointer;
}
.filter-btn.on { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }

@media (max-width: 700px) {
  .filter-btn[hidden] { display: none; }
  .filter-btn:not([hidden]) { display: block; }
}

@media (max-width: 700px) {
  .filters { display: none; }
}

.filter-sheet { text-align: left; }
.filter-sheet .sheet-line { text-align: center; }
.filter-group { margin: 18px 0 0; }
.filter-kind {
  font: 10px var(--mono, ui-monospace, monospace);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-bottom: 8px;
}
.filter-chips { display: flex; flex-wrap: wrap; gap: 7px; }
.filter-chips button {
  font: inherit;
  font-size: 13.5px;
  padding: 7px 13px;
  border-radius: 20px;
  border: 1px solid var(--rule-firm);
  background: var(--bg);
  color: var(--ink-2);
  cursor: pointer;
}
.filter-chips button.on { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.filter-chips i { font-style: normal; opacity: 0.55; font-size: 11px; margin-left: 3px; }
.filter-sheet .btn { margin-top: 22px; }
.filters button {
  font: inherit;
  font-size: 12.5px;
  padding: 5px 12px;
  border-radius: 20px;
  border: 1px solid var(--rule-firm);
  background: transparent;
  color: var(--ink-3);
  cursor: pointer;
}
.filters button:hover { border-color: var(--accent); color: var(--accent); }
.filters button.on {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
}
.filters .clear { border-style: dashed; }
.filter-note {
  text-align: center;
  color: var(--ink-3);
  font-size: 13px;
  margin: -12px auto 16px;
  padding: 0 20px;
}

/* ---------- unread ---------- */
/* One dot on the row, one count on the way back to the inbox. The count is the
   useful one — with six characters "you have messages" says less than how many. */
.unread-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent);
  flex: none;
  margin-left: 8px;
}
.row-line.unread { color: var(--ink); font-weight: 500; }

.badge {
  display: inline-grid;
  place-items: center;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  margin-left: 7px;
  border-radius: 9px;
  background: var(--accent);
  color: var(--accent-ink);
  font: 600 11px/1 var(--ui);
  font-style: normal;
  font-variant-numeric: tabular-nums;
  vertical-align: 1px;
}
/* The chat bar's back arrow is a tight target; the badge rides its top corner
   rather than widening it. */
#to-inbox { position: relative; overflow: visible; }
#to-inbox .badge {
  position: absolute;
  top: -2px;
  right: -6px;
  margin: 0;
}

/* ---------- the next night ---------- */
/* Sits where the composer is, because when an arc is over the thing to offer is
   not another message into a finished story. */
.next-arc {
  border-top: 1px solid var(--rule);
  padding: 20px 22px calc(20px + env(safe-area-inset-bottom));
  text-align: center;
  background: var(--bg);
}
.next-arc p {
  font-size: 14px;
  color: var(--ink-3);
  margin: 0 auto 12px;
  max-width: 40ch;
}
.next-arc .btn { max-width: 320px; margin: 0 auto; }

/* ---------- diagrams ---------- */
/* Sits in the thread like a message, because that is what it is — an answer to
   something they asked. currentColor so it follows the page rather than
   arriving as a white rectangle in a dark room. */
.figure {
  margin: 14px 0 18px;
  padding: 18px 16px;
  border: 1px solid var(--rule);
  border-radius: 10px;
  background: var(--panel);
  color: var(--ink-2);
}
.figure svg { display: block; width: 100%; height: auto; max-height: 340px; }
.figure.loading {
  height: 96px;
  background: linear-gradient(90deg, var(--panel), var(--bg), var(--panel));
  background-size: 200% 100%;
  animation: figure-wait 1.4s ease-in-out infinite;
}
@keyframes figure-wait {
  from { background-position: 200% 0; }
  to   { background-position: -200% 0; }
}
@media (prefers-reduced-motion: reduce) { .figure.loading { animation: none; } }

/* A photograph she sent, rather than an illustration of the scene. Squarer
   frame, no inner padding — it should read as a picture in a message. */
.figure.photo { padding: 0; overflow: hidden; }
.figure.photo img { display: block; width: 100%; height: auto; }
.figure.photo figcaption {
  padding: 9px 13px;
  font-size: 12.5px;
  color: var(--ink-3);
  border-top: 1px solid var(--rule);
}

/* The card's corner controls, as a row rather than three things stacked on the
   same absolute position. Studio and import only appear for someone who can
   actually use them, which locally is whoever is sitting at the machine. */
.corners {
  position: absolute;
  top: 18px;
  right: 16px;
  z-index: 6;
  display: flex;
  gap: 8px;
  align-items: center;
  flex-wrap: wrap;
  justify-content: flex-end;
}
.corners .corner-item {
  font: inherit;
  font-size: 12px;
  color: var(--ink-3);
  padding: 6px 10px;
  border: 1px solid var(--rule);
  border-radius: 100px;
  background: var(--panel);
  text-decoration: none;
  cursor: pointer;
}
.corners .corner-item:hover { color: var(--ink); border-color: var(--rule-firm); }

/* The import preview is prose about a file, not code: left-aligned so it reads
   as a description, wrapped rather than clipped, and scrollable so a long
   collision warning cannot end up hidden behind the confirm button. */
#import-detail {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  text-align: left;
  font-size: 13px;
  line-height: 1.5;
  margin: 0 0 14px;
  max-height: 38vh;
  overflow-y: auto;
  background: var(--hover);
  border-radius: 8px;
  padding: 12px 14px;
}
#import-scrim .sheet { max-height: 86vh; overflow-y: auto; }


/* The card's definition list holds a fixed left column. A long dt used to wrap
   down it and run through the paragraph alongside; app.js clamps the text, and
   this stops any remaining overflow from moving the layout rather than itself. */
.fact dt {
  overflow-wrap: anywhere;
  hyphens: auto;
}
.fact dd {
  min-width: 0;
}

/* A time in the thread, where a gap was. Deliberately quiet — it is punctuation,
   not data, and a conversation with a stamp on every line reads as a log. */
.timemark {
  text-align: center;
  font-size: 11px;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink-3);
  opacity: .75;
  margin: 22px 0 14px;
}

/* A time in the thread, where a gap was. Quiet on purpose — punctuation, not
   data. A conversation stamped on every line reads as a log. */
.timemark {
  text-align: center;
  font-size: 11px;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink-3);
  opacity: .75;
  margin: 22px 0 14px;
}

/* Per-conversation actions. */
.row-menu {
  display: flex;
  flex-direction: column;
  border-top: 1px solid var(--rule);
}
.row-menu button {
  font: inherit;
  text-align: left;
  background: none;
  border: 0;
  border-bottom: 1px solid var(--rule);
  padding: 13px 16px;
  cursor: pointer;
  color: var(--ink);
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.row-menu button:last-child { border-bottom: 0; }
.row-menu button:hover { background: var(--hover); }
.row-menu button small { color: var(--ink-3); font-size: 12px; }
.row-menu button.danger { color: var(--accent); }
