/*
 * The debug panel's own stylesheet, imported by the component rather than by
 * `src/styles/app.css` — so it can never collide with the app's, and deleting
 * `src/debug/` deletes the whole of it.
 *
 * Every colour comes from `src/styles/tokens.css`. Nothing here is a new
 * decision about how the product looks: it is the same dark surface, one step
 * quieter, with a monospace where the content is machine output.
 *
 * Scoped under the creator's wrapper for the same reason the app's stylesheet
 * is: the creator is mounted inside the CMS now, and a fixed-position pill
 * styled by a global `.dbg-pill` would sit on the host's chrome. The tokens it
 * reads live on that wrapper too, so nesting is also what keeps them resolving.
 */

[data-scenecast-creator] {
  .dbg-pill {
    position: fixed;
    right: 14px;
    bottom: 14px;
    z-index: 60;
    padding: 5px 11px;
    border-radius: 999px;
    border: 1px solid var(--rule);
    background: var(--panel);
    color: var(--ink3);
    font-size: 11px;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    opacity: 0.55;
  }
  .dbg-pill:hover {
    opacity: 1;
    color: var(--ink);
    border-color: var(--accent);
  }

  .dbg {
    position: fixed;
    top: 0;
    right: 0;
    z-index: 60;
    width: min(480px, 100vw);
    height: 100%;
    display: grid;
    grid-template-rows: auto minmax(0, 1fr);
    background: var(--panel);
    border-left: 1px solid var(--rule);
    box-shadow: -24px 0 60px rgba(0, 0, 0, 0.45);
    font-size: 12px;
    color: var(--ink2);
  }

  .dbg > header {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 0 10px 0 14px;
    height: var(--bar-h);
    border-bottom: 1px solid var(--rule);
  }
  .dbg > header > b {
    color: var(--ink);
    font-family: var(--display);
    font-size: 13px;
    letter-spacing: 0.01em;
  }
  .dbg > header nav {
    display: flex;
    gap: 2px;
    margin-left: auto;
  }
  .dbg > header nav button {
    padding: 4px 9px;
    border-radius: 999px;
    color: var(--ink3);
    font-size: 11.5px;
  }
  .dbg > header nav button:hover {
    color: var(--ink);
  }
  .dbg > header nav button[aria-pressed='true'] {
    background: var(--accent-soft);
    color: var(--ink);
  }
  .dbg-x {
    color: var(--ink3);
    padding: 4px 6px;
    font-size: 12px;
  }
  .dbg-x:hover {
    color: var(--ink);
  }

  .dbg-body {
    overflow: auto;
    padding: 12px 14px 40px;
  }

  .dbg-btn {
    padding: 5px 11px;
    border-radius: 999px;
    border: 1px solid var(--rule);
    background: rgba(255, 255, 255, 0.04);
    color: var(--ink);
    font-size: 11.5px;
    text-decoration: none;
  }
  .dbg-btn:hover:not(:disabled) {
    background: rgba(255, 255, 255, 0.1);
    border-color: rgba(255, 255, 255, 0.2);
  }
  .dbg-btn:disabled {
    opacity: 0.4;
    cursor: default;
  }
  .dbg-btn.go {
    background: var(--accent-deep);
    border-color: var(--accent-deep);
    color: #fff;
  }
  .dbg-btn.wide {
    display: block;
    width: 100%;
    margin-top: 10px;
  }

  .dbg-actions {
    display: flex;
    gap: 6px;
    margin-bottom: 12px;
  }

  /* ------------------------------------------------------------- the log */

  .dbg-log h4 {
    margin: 18px 0 6px;
    font-family: var(--display);
    font-size: 11px;
    font-weight: 500;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--ink3);
  }
  .dbg-mono {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 11px;
  }
  .dbg-empty {
    margin: 6px 0;
    color: var(--ink3);
  }
  .dbg-error {
    margin: 8px 0;
    padding: 7px 10px;
    border-radius: 8px;
    border: 1px solid rgba(240, 66, 138, 0.35);
    color: var(--ink);
    background: rgba(240, 66, 138, 0.08);
  }

  .dbg-stats {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 1px;
    background: var(--rule2);
    border: 1px solid var(--rule);
    border-radius: 10px;
    overflow: hidden;
  }
  .dbg-stat {
    background: var(--bg2);
    padding: 8px 9px;
  }
  .dbg-stat b {
    display: block;
    color: var(--ink);
    font-family: var(--display);
    font-size: 15px;
    font-weight: 500;
  }
  .dbg-stat span {
    font-size: 10.5px;
    color: var(--ink3);
  }

  .dbg-facts {
    display: grid;
    grid-template-columns: 78px minmax(0, 1fr);
    gap: 2px 10px;
    margin: 12px 0 0;
  }
  .dbg-facts dt {
    color: var(--ink3);
  }
  .dbg-facts dd {
    margin: 0;
    color: var(--ink);
    overflow-wrap: anywhere;
  }

  .dbg-errors p {
    margin: 8px 0 0;
    padding: 7px 10px;
    border-radius: 8px;
    background: rgba(240, 66, 138, 0.08);
    border: 1px solid rgba(240, 66, 138, 0.3);
    color: var(--ink);
  }

  .dbg-table {
    width: 100%;
    border-collapse: collapse;
  }
  .dbg-table th {
    text-align: left;
    font-weight: 400;
    color: var(--ink3);
    font-size: 10.5px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    padding: 0 0 4px;
    border-bottom: 1px solid var(--rule);
  }
  .dbg-table td {
    padding: 6px 0;
    border-bottom: 1px solid var(--rule2);
    vertical-align: top;
    color: var(--ink2);
  }
  .dbg-table td b {
    display: block;
    color: var(--ink);
    font-weight: 500;
  }
  .dbg-table td span {
    display: block;
    color: var(--ink3);
    font-size: 11px;
  }
  .dbg-table .r {
    text-align: right;
    padding-left: 8px;
    white-space: nowrap;
  }

  .dbg-kinds {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    margin-bottom: 8px;
  }
  .dbg-kinds button {
    padding: 3px 8px;
    border-radius: 999px;
    border: 1px solid var(--rule);
    color: var(--ink3);
    font-size: 10.5px;
  }
  .dbg-kinds button:hover {
    color: var(--ink);
  }
  .dbg-kinds button.on {
    background: var(--accent-soft);
    border-color: var(--accent);
    color: var(--ink);
  }

  .dbg-more {
    display: block;
    width: 100%;
    margin-bottom: 8px;
    padding: 5px;
    border-radius: 8px;
    border: 1px dashed var(--rule);
    color: var(--ink3);
    font-size: 11px;
  }

  .dbg-stream {
    list-style: none;
    margin: 0;
    padding: 0;
  }
  .dbg-stream > li {
    border-bottom: 1px solid var(--rule2);
  }
  .dbg-stream > li > button {
    display: grid;
    grid-template-columns: 54px 92px minmax(0, 1fr);
    gap: 8px;
    width: 100%;
    text-align: left;
    padding: 4px 0;
    align-items: baseline;
  }
  .dbg-stream > li > button:hover {
    background: rgba(255, 255, 255, 0.03);
  }
  .dbg-at {
    color: var(--ink3);
    font-size: 10.5px;
    text-align: right;
  }
  .dbg-kind {
    color: var(--accent);
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 10.5px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .dbg-gist {
    color: var(--ink2);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .dbg-stream pre {
    margin: 0 0 8px;
    padding: 8px 10px;
    max-height: 340px;
    overflow: auto;
    background: var(--bg);
    border: 1px solid var(--rule);
    border-radius: 8px;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 10.5px;
    line-height: 1.45;
    color: var(--ink2);
    white-space: pre-wrap;
    overflow-wrap: anywhere;
  }

  /* --------------------------------------------------------- the stories */

  .dbg-paste {
    display: flex;
    gap: 6px;
    margin-bottom: 12px;
  }
  .dbg-paste input {
    flex: 1;
    padding: 6px 10px;
    border-radius: 999px;
    border: 1px solid var(--rule);
    background: var(--bg);
    color: var(--ink);
    font: inherit;
    font-size: 11.5px;
  }
  .dbg-paste input:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
  }

  .dbg-rows {
    list-style: none;
    margin: 0;
    padding: 0;
  }
  .dbg-rows > li > button {
    display: block;
    width: 100%;
    text-align: left;
    padding: 8px 10px;
    border-radius: 10px;
    border: 1px solid transparent;
  }
  .dbg-rows > li > button:hover:not(:disabled) {
    background: var(--bg2);
    border-color: var(--rule);
  }
  .dbg-rows b {
    display: block;
    color: var(--ink);
    font-family: var(--display);
    font-size: 13px;
    font-weight: 500;
  }
  .dbg-sub {
    display: block;
    color: var(--ink2);
    margin-top: 1px;
  }
  .dbg-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 4px;
    color: var(--ink3);
    font-size: 10.5px;
    align-items: center;
  }
  .dbg-meta em {
    font-style: normal;
    padding: 1px 6px;
    border-radius: 999px;
    background: var(--accent-soft);
    color: var(--ink);
  }
  .dbg-meta em.fx {
    background: rgba(242, 184, 75, 0.14);
    color: var(--warn);
  }

  /* ------------------------------------------------------- a loaded file */

  .dbg-drop p {
    margin: 0 0 10px;
    color: var(--ink2);
  }
  .dbg-drop input[type='file'] {
    width: 100%;
    font-size: 11.5px;
    color: var(--ink3);
  }
  .dbg-loaded {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding-bottom: 10px;
    border-bottom: 1px solid var(--rule);
    color: var(--ink);
  }
}
/* Lifted from the Scene Builder prototype, which embedded them as base64. */
@font-face {
  font-family: 'Jokker';
  font-weight: 500;
  font-display: swap;
  src: url('../fonts/Jokker-500.woff2') format('woff2');
}
@font-face {
  font-family: 'InterDisplay';
  font-weight: 400;
  font-display: swap;
  src: url('../fonts/InterDisplay-400.woff2') format('woff2');
}
@font-face {
  font-family: 'InterDisplay';
  font-weight: 500;
  font-display: swap;
  src: url('../fonts/InterDisplay-500.woff2') format('woff2');
}
@font-face {
  font-family: 'InterDisplay';
  font-weight: 600;
  font-display: swap;
  src: url('../fonts/InterDisplay-600.woff2') format('woff2');
}
/*
 * The serif the outline and the quoted lines are set in. Bundled rather than
 * named from the system (it was 'Iowan Old Style', which only macOS has), so
 * every viewer gets the same letters and the same line breaks. Charis SIL — a
 * Charter, the nearest free face to Iowan — SIL OFL 1.1
 * (public/fonts/CharisSIL-OFL.txt), from @fontsource/charis-sil 5.3.0.
 */
@font-face {
  font-family: 'Charis SIL';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('../fonts/CharisSIL-latin-ext-400-normal.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Charis SIL';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('../fonts/CharisSIL-latin-400-normal.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Charis SIL';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url('../fonts/CharisSIL-latin-ext-400-italic.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Charis SIL';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url('../fonts/CharisSIL-latin-400-italic.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Charis SIL';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('../fonts/CharisSIL-latin-ext-700-normal.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Charis SIL';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('../fonts/CharisSIL-latin-700-normal.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Charis SIL';
  font-style: italic;
  font-weight: 700;
  font-display: swap;
  src: url('../fonts/CharisSIL-latin-ext-700-italic.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Charis SIL';
  font-style: italic;
  font-weight: 700;
  font-display: swap;
  src: url('../fonts/CharisSIL-latin-700-italic.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/*
 * The creator is dark and carries its own token set rather than
 * `@scene/config-tailwind`, the same way `apps/ai-gen-workbench` does.
 *
 * On the wrapper rather than `:root`, because the creator is injected into a
 * host page now: the CMS mounts it full-bleed above the dashboard, and a
 * `:root` block would repaint the light app underneath it. Custom properties
 * inherit, so everything inside the wrapper still reads them; `color-scheme`
 * is likewise scoped, so the host's form controls stay light.
 *
 * `html[data-scenecast]` is the other home: the CMS stamps it while the
 * SceneCast layer is open. The layer is opaque and full-bleed, so nothing of
 * the host shows, and popovers and dialogs portalled to <body> (outside the
 * wrapper) get the same palette. theme.css maps the design system onto it.
 */
[data-scenecast-creator],
html[data-scenecast] {
  /*
   * How work moves, everywhere it shows: one beat for a thing that is busy
   * (a dot, a glyph) and one sweep for words or paper being worked on. There
   * were six — dots at 1s, 1.6s and 1.8s, sheens at 1.8s, 1.9s and 2.8s — so
   * the chat, a card and the stage head each ticked at their own rate on one
   * screen, and the fastest read as urgency nothing on it meant.
   */
  --work-beat: 1.8s;
  --work-sweep: 2.4s;
  /*
   * How a thing comes and goes (`src/motion/`, which mirrors these): quick
   * for what fades, base for a box opening or closing, stage for something
   * arriving. One out-curve for what enters, one in-and-out for a box that
   * changes size. Reduced motion takes them all to nothing, below.
   */
  --m-quick: 140ms;
  --m-base: 220ms;
  --m-stage: 320ms;
  --ease-out: cubic-bezier(0.2, 0.8, 0.2, 1);
  --ease-io: cubic-bezier(0.4, 0, 0.2, 1);
  --bg: #0f0f0e;
  --bg2: #141412;
  --stage: #1b1a17;
  --panel: #161615;
  --ink: #f1efe9;
  --ink2: #a9a59c;
  /* the quietest text; still 4.5:1 on --stage, the darkest ground it sits on */
  --ink3: #87837c;
  --rule: rgba(255, 255, 255, 0.09);
  --rule2: rgba(255, 255, 255, 0.05);
  /* the edge of a control whose edge is the only thing marking it (an empty
     input): 3:1 on every ground (WCAG 1.4.11). --rule stays for dividers. */
  --rule-strong: #676662;
  --accent: #9793fb;
  --accent-deep: #5e56f9;
  --pink: #f0428a;
  --accent-soft: rgba(151, 147, 251, 0.14);
  /* The mark for "this is the one": a focused field's border, a picked
     thumbnail, a keyboard focus. One colour everywhere. --ring-w is the
     picked thumbnail's line; a field keeps its 1px border, and the keyboard
     outline is 2px. theme.css hands both to the design-system screens. */
  --ring: var(--accent);
  --ring-w: 1.5px;
  --ok: #7bd389;
  --warn: #f2b84b;
  /* errors: its own role rather than --warn, so a failure never reads as a caution */
  --danger: #ff6b6b;
  --display: 'Jokker', 'InterDisplay', 'Helvetica Neue', Arial, sans-serif;
  --body: 'InterDisplay', 'Helvetica Neue', Arial, sans-serif;
  --serif: 'Charis SIL', Georgia, 'Times New Roman', serif;
  --bar-h: 56px;
  --chat-w: 400px;
  /* The least the stage keeps beside the conversation: the story's board
     draws its rail, and the rail's "add a section" between cards, from 640px
     (`withRail` in Placeholders.tsx), inside the pane's 24px either side.
     Narrower, the story loses the rail without a word. */
  --stage-min: 688px;
  /* fills and lines over the ground: white on dark, black on light */
  --fill-025: rgba(255, 255, 255, 0.025);
  --fill-03: rgba(255, 255, 255, 0.03);
  --fill-04: rgba(255, 255, 255, 0.04);
  --fill-05: rgba(255, 255, 255, 0.05);
  --fill-06: rgba(255, 255, 255, 0.06);
  --fill-07: rgba(255, 255, 255, 0.07);
  --fill-08: rgba(255, 255, 255, 0.08);
  --fill-09: rgba(255, 255, 255, 0.09);
  --fill-1: rgba(255, 255, 255, 0.1);
  --fill-15: rgba(255, 255, 255, 0.15);
  --fill-2: rgba(255, 255, 255, 0.2);
  --fill-22: rgba(255, 255, 255, 0.22);
  --fill-28: rgba(255, 255, 255, 0.28);
  /* text on an ink-filled control */
  --on-ink: #121212;
  /* the veil over the page while a file is dragged in */
  --veil: rgba(15, 15, 14, 0.8);
  /* the ground a slide-shaped thing sits on before it is a slide. A deck is
     light by default and a slide's ground is the deck's, never the app's:
     these do not change with the theme. */
  --slide-bg: #f6f4ef;
  --slide-ink: #1c1b18;
  --slide-ink2: #57544d;
  --slide-ink3: #85817a;
  --slide-rule: rgba(0, 0, 0, 0.08);
  color-scheme: dark;
}
/*
 * Light, when the host says so. The CMS stamps the resolved appearance on
 * <html> (`data-scene-theme`); the creator follows it rather than keeping a
 * dark room inside a light app. Same tokens, other values.
 */
html[data-scene-theme='light'] [data-scenecast-creator],
html[data-scene-theme='light'][data-scenecast] {
  --bg: #f7f6f2;
  --bg2: #f1f0eb;
  --stage: #e9e7e1;
  --panel: #ffffff;
  --ink: #1c1b18;
  --ink2: #57544d;
  --ink3: #696660;
  --rule: rgba(0, 0, 0, 0.09);
  --rule2: rgba(0, 0, 0, 0.05);
  --rule-strong: #868480;
  --accent: #574fe7;
  --accent-deep: #4a42e0;
  --accent-soft: rgba(94, 86, 249, 0.12);
  --ok: #20753d;
  --warn: #8e5c14;
  --danger: #bd3333;
  --pink: #bb2d68;
  --fill-025: rgba(0, 0, 0, 0.025);
  --fill-03: rgba(0, 0, 0, 0.03);
  --fill-04: rgba(0, 0, 0, 0.04);
  --fill-05: rgba(0, 0, 0, 0.05);
  --fill-06: rgba(0, 0, 0, 0.06);
  --fill-07: rgba(0, 0, 0, 0.07);
  --fill-08: rgba(0, 0, 0, 0.08);
  --fill-09: rgba(0, 0, 0, 0.09);
  --fill-1: rgba(0, 0, 0, 0.1);
  --fill-15: rgba(0, 0, 0, 0.15);
  --fill-2: rgba(0, 0, 0, 0.2);
  --fill-22: rgba(0, 0, 0, 0.22);
  --fill-28: rgba(0, 0, 0, 0.28);
  --on-ink: #ffffff;
  --veil: rgba(247, 246, 242, 0.85);
  color-scheme: light;
}
/* Asked for less motion: things are there or not, at once. */
@media (prefers-reduced-motion: reduce) {
  [data-scenecast-creator],
  html[data-scenecast] {
    --m-quick: 0ms;
    --m-base: 0ms;
    --m-stage: 0ms;
  }
}
/*
 * More contrast, when the person has asked the OS for it: dividers become the
 * 3:1 control edge and the quietest text steps up to the secondary one. Both
 * selectors, so it outranks the light block above in either theme.
 */
@media (prefers-contrast: more) {
  [data-scenecast-creator],
  html[data-scenecast],
  html[data-scene-theme='light'] [data-scenecast-creator],
  html[data-scene-theme='light'][data-scenecast] {
    --rule: var(--rule-strong);
    --ink3: var(--ink2);
  }
}
/*
 * Scenecast wears the Scene brand palette (tokens.css), not the design
 * system's older neutral greys. Its surfaces outside the creator (the bar's
 * host, the visual identity builder, the kit's popovers and dialogs) are
 * written in design-system classes, and every one of those reads a
 * `--tw-color-*` variable. So rather than rewrite those classes, this file
 * points the variables at the brand tokens while the SceneCast layer is open
 * (`html[data-scenecast]`, stamped by the CMS) and inside the creator itself
 * (the visual identity's question card is an island of host classes).
 *
 * The design system's own file is untouched: outside Scenecast, nothing here
 * applies.
 *
 * `body` is named as well as `html` because the design system sets its dark
 * values on `body[data-scene-theme='dark']` too, and a value set on <body>
 * would win over one inherited from <html>. `:root` rather than `html` to
 * outrank the design system's `html[data-scene-theme='dark']` whatever order
 * the two stylesheets arrive in.
 */
:root[data-scenecast],
:root[data-scenecast] body,
[data-scenecast-creator] {
  --tw-color-bg-surface: var(--panel);
  --tw-color-neutral-bg-container: var(--bg);
  --tw-color-neutral-on-surface: var(--ink);
  --tw-color-neutral-on-surface-alt: var(--ink3);
  --tw-color-neutral-border: var(--rule);
  --tw-color-neutral-border-alt: var(--ink);
  --tw-color-neutral-state-l1: var(--fill-06);
  --tw-color-neutral-state-l2: var(--fill-1);
  --tw-color-neutral-bg-inverse: var(--ink);
  --tw-color-neutral-on-inverse: var(--on-ink);
  --tw-color-neutral-on-inverse-alt: color-mix(
    in srgb,
    var(--on-ink) 75%,
    transparent
  );

  /* brand: the accent, and tints of it over the card ground */
  --tw-color-brand-on-surface: var(--accent);
  /* a selected option's label: the accent pulled toward the ink, so it reads
     at 9:1+ on the brand container in both themes */
  --tw-color-brand-on-surface-alt: color-mix(
    in srgb,
    var(--accent) 35%,
    var(--ink)
  );
  --tw-color-brand-bg-container: color-mix(
    in srgb,
    var(--accent) 12%,
    var(--panel)
  );
  --tw-color-brand-border: color-mix(in srgb, var(--accent) 40%, var(--panel));
  --tw-color-brand-border-alt: var(--ring);
  --tw-color-brand-state-l1: color-mix(in srgb, var(--accent) 10%, transparent);
  --tw-color-brand-state-l2: color-mix(in srgb, var(--accent) 16%, transparent);
  --tw-color-brand-bg-inverse: var(--accent);
  --tw-color-brand-on-inverse: var(--on-ink);
  --tw-color-brand-bg-inverse-hover: color-mix(
    in srgb,
    var(--accent) 82%,
    var(--ink)
  );

  /* status: the creator's own, each with a tinted container and border */
  --tw-color-green-on-surface: var(--ok);
  --tw-color-green-on-surface-alt: var(--ok);
  --tw-color-green-bg-container: color-mix(in srgb, var(--ok) 12%, var(--panel));
  --tw-color-green-border: color-mix(in srgb, var(--ok) 35%, var(--panel));
  --tw-color-red-on-surface: var(--danger);
  --tw-color-red-on-surface-alt: var(--danger);
  --tw-color-red-bg-container: color-mix(
    in srgb,
    var(--danger) 12%,
    var(--panel)
  );
  --tw-color-red-border: color-mix(in srgb, var(--danger) 35%, var(--panel));
  --tw-color-yellow-on-surface: var(--warn);
  --tw-color-yellow-on-surface-alt: var(--warn);
  --tw-color-yellow-bg-container: color-mix(
    in srgb,
    var(--warn) 12%,
    var(--panel)
  );
  --tw-color-yellow-border: color-mix(in srgb, var(--warn) 35%, var(--panel));
}
/*
 * Shapes and type, not only colours. The visual identity page is written in
 * the design system's components, and with the palette alone it still read
 * as another product under Scene's bar: square purple-filled buttons where
 * the creator has ink pills, the app font where the creator sets
 * InterDisplay and Jokker, a three-pixel halo round a focused field where
 * the creator draws one accent line. So inside Scene the kit takes the
 * creator's shapes as well — through its own variables where it has them
 * (fonts, radii), and on its components' `data-slot`s where it does not.
 *
 * Unlayered, so it outranks the kit's utilities (which sit in Tailwind's
 * utilities layer) without an `!important`. Outside Scene nothing here
 * applies.
 */
:root[data-scenecast] {
  --tw-font-sans: var(--body);
  --tw-font-display: var(--display);
  /* the creator's cards are 12–14px round, its small chips 8px */
  --tw-radius-sm: 8px;
  --tw-radius-small: 8px;
  --tw-radius: 12px;
  --tw-radius-md: 12px;
  --tw-radius-large: 14px;
  --tw-radius-lg: 14px;
  --tw-radius-xl: 18px;
}
:root[data-scenecast] body {
  font-family: var(--body);
  -webkit-font-smoothing: antialiased;
}
/* headings in the creator's display face, set a touch tighter */
:root[data-scenecast]
  :is(
    .tw\:text-headline-large,
    .tw\:text-headline-medium,
    .tw\:text-headline-small
  ) {
  font-family: var(--display);
  letter-spacing: -0.015em;
}
/* Buttons: the creator's `.btn` — a pill, 13px, a quiet fill with a hairline,
   and the one that matters filled in ink, let down to the quieter ink on
   hover (the accent means where you are, which a hovered button is not). */
:root[data-scenecast] [data-slot='button']:not([data-variant='link']) {
  border-radius: 999px;
  box-shadow: none;
}
:root[data-scenecast]
  [data-slot='button']:is([data-size='md'], [data-size='default']):not(
    [data-variant='icon']
  ) {
  padding-inline: 14px;
  font-size: 13px;
}
:root[data-scenecast]
  [data-slot='button']:is([data-variant='primary'], [data-variant='default']) {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--on-ink);
}
:root[data-scenecast]
  [data-slot='button']:is(
    [data-variant='primary'],
    [data-variant='default']
  ):hover:not(:disabled) {
  background: var(--ink2);
  border-color: var(--ink2);
  color: var(--on-ink);
}
:root[data-scenecast]
  [data-slot='button']:is(
    [data-variant='ghost'],
    [data-variant='destructive'],
    [data-variant='danger'],
    [data-variant='outline']
  ) {
  background: var(--fill-04);
  border-color: var(--rule);
}
:root[data-scenecast]
  [data-slot='button']:is(
    [data-variant='ghost'],
    [data-variant='destructive'],
    [data-variant='danger'],
    [data-variant='outline']
  ):hover:not(:disabled) {
  background: var(--fill-1);
  border-color: var(--fill-2);
}
:root[data-scenecast] [data-slot='button'][data-variant='outline'],
:root[data-scenecast] [data-slot='button'][data-variant='secondary'] {
  color: var(--ink);
}
:root[data-scenecast] [data-slot='button'][data-variant='secondary'] {
  background: var(--fill-06);
  border-color: transparent;
}
:root[data-scenecast] [data-slot='button'][data-variant='text'] {
  color: var(--ink);
}
:root[data-scenecast] [data-slot='button']:disabled {
  opacity: 0.45;
}
/* one keyboard outline, the creator's, instead of a halo per variant */
:root[data-scenecast] [data-slot='button']:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 2px;
  box-shadow: none;
}
/* Fields: the composer's ground and hairline, and one accent line when in
   use — no halo outside the border, which reads as a second border. */
:root[data-scenecast] :is([data-slot='input'], [data-slot='textarea']) {
  background: var(--fill-04);
  border-color: var(--rule-strong);
  box-shadow: none;
}
:root[data-scenecast] [data-slot='input'] {
  border-radius: 10px;
}
:root[data-scenecast]
  :is([data-slot='input'], [data-slot='textarea']):focus-visible {
  border-color: var(--ring);
  box-shadow: none;
  --tw-ring-shadow: 0 0 #0000;
}
:root[data-scenecast]
  :is([data-slot='input'], [data-slot='textarea'])::placeholder {
  color: var(--ink3);
}
/* a box whose edge is the only thing marking it: the 3:1 control edge */
:root[data-scenecast] [data-slot='checkbox'] {
  border-color: var(--rule-strong);
  background: transparent;
  box-shadow: none;
}
:root[data-scenecast] [data-slot='checkbox'][data-checked] {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--on-ink);
}
/*
 * The motion layer's rules (`src/motion/`). Scoped like app.css, with the
 * keyframes hoisted out of the nesting block: their names are global.
 * Every duration is a token from tokens.css, so reduced motion, which takes
 * them to 0ms, turns all of it off in one place.
 */
[data-scenecast-creator] {
  /*
   * <Reveal>: a box that opens and closes. Its one row goes from 0fr to 1fr,
   * which a browser can interpolate where it cannot interpolate `auto`.
   * Opening, the box and its contents come up together; closing, the
   * contents fade first (--m-quick) and the box folds after them (--m-base,
   * held back by the fade). Each state carries the transition into it.
   */
  .reveal {
    flex: none;
    display: grid;
    grid-template-rows: 0fr;
    transition: grid-template-rows var(--m-base) var(--ease-io)
      var(--m-quick);
  }
  .reveal[data-open] {
    grid-template-rows: 1fr;
    transition-delay: 0ms;
  }
  .reveal > .reveal-in {
    min-height: 0;
    overflow: clip;
    opacity: 0;
    transition: opacity var(--m-quick) var(--ease-out);
  }
  .reveal[data-open] > .reveal-in {
    opacity: 1;
    transition-duration: var(--m-base);
  }
  /* at rest open, nothing is clipped: a focus ring, a popover from a line */
  .reveal[data-settled] > .reveal-in {
    overflow: visible;
  }

  /*
   * Something arriving: it fades and rises into place, a little after the
   * one before it (`--i`, from `arrive()`), never more than eight steps
   * late. `--arrive-step` is 40ms unless the row says otherwise.
   */
  .arrive {
    animation: motion-arrive var(--m-stage) var(--ease-out) both;
    animation-delay: calc(min(var(--i, 0), 8) * var(--arrive-step, 40ms));
  }
  @media (prefers-reduced-motion: reduce) {
    .arrive {
      animation: none;
    }
  }

  /*
   * The card's vocabulary, one meaning each (`vocabulary.ts`). Every
   * duration is `--m-stage` times a count, so reduced motion takes them to
   * nothing; what must still be seen there is held still instead, for as
   * long as the script keeps it on (`HOLD_MS`).
   */

  /*
   * You are here, a section: a 2px accent edge that fades as the card
   * settles from a slight lift. Drawn inside the card's edge: the card
   * clips everything outside itself (its fold is a clip-path), and a ring
   * outside it was never seen.
   */
  .note-here {
    position: absolute;
    inset: 0;
    z-index: 3;
    border-radius: inherit;
    pointer-events: none;
    animation: here-ring calc(var(--m-stage) * 3) var(--ease-out) both;
  }
  .note.here {
    animation: here-lift calc(var(--m-stage) * 3) var(--ease-out) both;
  }

  /*
   * You are here, a line: a pen stroke across it, left to right, that
   * fades. The stroke is a background, so nothing in the paragraph moves.
   */
  .note [data-pen] {
    background-image: linear-gradient(var(--pen), var(--pen));
    background-repeat: no-repeat;
    background-size: 100% 100%;
    -webkit-box-decoration-break: clone;
    box-decoration-break: clone;
    border-radius: 3px;
    animation: pen-stroke calc(var(--m-stage) * 5) var(--ease-out) both;
  }

  /*
   * Changed: the words the agent changed, washed where they stand and
   * fading. The washes are placed by `tintWords`, over the words.
   */
  .note-tints {
    position: absolute;
    inset: 0;
    z-index: 2;
    pointer-events: none;
  }
  .note-tints b {
    position: absolute;
    border-radius: 3px;
    background: color-mix(in srgb, var(--accent) 24%, transparent);
    animation: tint-fade calc(var(--m-stage) * 4.5) var(--ease-io) both;
  }

  /*
   * A section's words landing: copies of the paragraph, one line each,
   * fading up one after another over the real words, which wait unseen
   * underneath (still read by a screen reader) until the last has come.
   */
  .landing {
    position: relative;
  }
  .landing.on > :first-child {
    opacity: 0;
  }
  .landing-lines {
    position: absolute;
    inset: 0;
    pointer-events: none;
  }
  .landing-line {
    position: absolute;
    inset: 0;
    margin: 0;
    clip-path: inset(
      calc(var(--k) * 22px) -4px calc(100% - (var(--k) + 1) * 22px) -4px
    );
    animation: line-land var(--m-stage) var(--ease-out) both;
    animation-delay: var(--land-at, 0ms);
  }

  @media (prefers-reduced-motion: reduce) {
    .note-here {
      animation: none;
      box-shadow: inset 0 0 0 2px var(--accent);
    }
    .note.here {
      animation: none;
    }
    .note [data-pen] {
      animation: none;
      --pen: color-mix(in srgb, var(--accent) 22%, transparent);
    }
    .note-tints b {
      animation: none;
    }
  }
}
@keyframes motion-arrive {
  from {
    opacity: 0;
    transform: translateY(6px);
  }
}
@property --pen {
  syntax: '<color>';
  inherits: false;
  initial-value: transparent;
}
@keyframes here-ring {
  from {
    box-shadow: inset 0 0 0 2px var(--accent);
  }
  to {
    box-shadow: inset 0 0 0 2px transparent;
  }
}
@keyframes here-lift {
  from {
    transform: scale(1.008);
  }
  to {
    transform: none;
  }
}
@keyframes pen-stroke {
  0% {
    background-size: 0% 100%;
    --pen: color-mix(in srgb, var(--accent) 26%, transparent);
  }
  25%,
  55% {
    background-size: 100% 100%;
    --pen: color-mix(in srgb, var(--accent) 26%, transparent);
  }
  100% {
    background-size: 100% 100%;
    --pen: transparent;
  }
}
@keyframes tint-fade {
  0%,
  30% {
    opacity: 1;
  }
  100% {
    opacity: 0;
  }
}
@keyframes line-land {
  from {
    opacity: 0;
    transform: translateY(4px);
  }
}
/*
 * Everything below is scoped to the creator's own wrapper.
 *
 * The app used to own a whole viewport and wrote plain global selectors. It is
 * now also mounted inside the CMS, above the dashboard, where a stray `button`
 * or `*` rule would restyle the host. One nesting block puts every selector
 * behind `[data-scenecast-creator]` without touching any of them, so the rules
 * below still read as the rules that were written.
 *
 * `@keyframes` are hoisted out: animation names are global whatever they are
 * nested in, and nesting them only hides that.
 */
[data-scenecast-creator] {
  /*
   * The reset, and the surface's own ground.
   *
   * These were `*`, `html, body` and `body` when the creator owned a whole
   * viewport. Mounted inside the CMS it owns a layer, so the reset applies to
   * the wrapper and its descendants, and what was on `body` is on the wrapper:
   * a host page must not inherit this app's background, type or `overflow`.
   */
  &,
  & * {
    box-sizing: border-box;
    min-width: 0;
  }
  & {
    margin: 0;
    background: var(--bg);
    color: var(--ink);
    font-family: var(--body);
    font-size: 14px;
    line-height: 1.5;
    -webkit-font-smoothing: antialiased;
    overflow: hidden;
    /*
     * The wrapper has to have a height, or nothing below it can fill.
     *
     * `.b` is `height: 100%`, and when this app owned the viewport that
     * resolved against `html, body { height: 100% }`. Scoping took those
     * rules away and put nothing in their place, so the wrapper sized to its
     * content: the app drew only as tall as what was in it, and the host page
     * showed through the rest of the layer it was supposed to cover.
     */
    height: 100%;
    width: 100%;
  }
  /* The reset stops at an island: a card the host draws with its own
     stylesheet (the visual identity's questions) keeps its own buttons,
     type and icons. `:where()` keeps the reset at its old weight, so every
     styled button in here still wins over it. */
  button:where(:not(.sc-island *)) {
    font: inherit;
    color: inherit;
    background: none;
    border: 0;
    padding: 0;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
  }
  button:focus-visible:where(:not(.sc-island *)),
  input:focus-visible:where(:not(.sc-island *)),
  textarea:focus-visible:where(:not(.sc-island *)),
  select:focus-visible:where(:not(.sc-island *)),
  a:focus-visible:where(:not(.sc-island *)) {
    /* 2px, not --ring-w: only a keyboard shows it, and it has to be found */
    outline: 2px solid var(--ring);
    outline-offset: 2px;
  }
  /* the host's selection colour is tuned for its own ground, not this one */
  ::selection {
    background: color-mix(in srgb, var(--accent) 32%, transparent);
    color: var(--ink);
  }
  /* Windows High Contrast drops box-shadow, which is all some focus states
     here are drawn with: give every focused control a system-coloured outline */
  @media (forced-colors: active) {
    :focus-visible {
      outline: 2px solid Highlight;
      outline-offset: 2px;
    }
  }
  svg:where(:not(.sc-island *)) {
    display: block;
  }
  [hidden] {
    display: none !important;
  }
  /* thin, quiet scrollbars everywhere in here */
  & *,
  & {
    scrollbar-width: thin;
    scrollbar-color: var(--fill-2) transparent;
  }
  & *::-webkit-scrollbar,
  &::-webkit-scrollbar {
    width: 8px;
    height: 8px;
  }
  & *::-webkit-scrollbar-thumb,
  &::-webkit-scrollbar-thumb {
    background: var(--fill-2);
    border-radius: 999px;
    border: 2px solid transparent;
    background-clip: padding-box;
  }
  & *::-webkit-scrollbar-track,
  &::-webkit-scrollbar-track {
    background: transparent;
  }
  .num {
    font-variant-numeric: tabular-nums;
  }
  .sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
  }

  .btn {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 7px 13px;
    border-radius: 999px;
    font-size: 13px;
    font-weight: 500;
    border: 1px solid var(--rule);
    background: var(--fill-04);
    color: var(--ink);
    white-space: nowrap;
    transition:
      background 0.15s,
      border-color 0.15s;
    min-height: 32px;
  }
  .btn:hover {
    background: var(--fill-1);
    border-color: var(--fill-2);
  }
  /* ──────────────────────────────── what you do with a finished deck
     Two actions, one object: a tray holding the quiet one and the one that
     matters. Side by side as separate pills they were two rings, two
     shadows and a gap — three edges to read where the deck itself is the
     thing worth looking at. */
  .deck-acts {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    padding: 2px;
    border-radius: 999px;
    background: var(--fill-04);
    box-shadow: inset 0 0 0 1px var(--rule);
  }
  .deck-act {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 5px 12px;
    min-height: 28px;
    border: 0;
    border-radius: 999px;
    background: none;
    color: var(--ink2);
    font-size: 13px;
    font-weight: 500;
    white-space: nowrap;
    cursor: pointer;
    transition: background 0.15s, color 0.15s;
  }
  .deck-act:hover:not(:disabled, [aria-disabled='true']) {
    background: var(--fill-06);
    color: var(--ink);
  }
  .deck-act:disabled,
  .deck-act[aria-disabled='true'] {
    opacity: 0.45;
    cursor: default;
  }
  /* the one that matters, filled: a pill inside the tray */
  .deck-act.share {
    background: var(--ink);
    color: var(--bg);
    box-shadow: 0 1px 2px rgb(0 0 0 / 0.16);
  }
  /* under the pointer the ink is let down, as every filled button's is */
  .deck-act.share:hover:not(:disabled, [aria-disabled='true']) {
    background: var(--ink2);
    color: var(--bg);
  }
  .share-wrap {
    position: relative;
    display: inline-flex;
  }
  /* Preview at the head of the Slides tab: a phone's (see below). From
     768px it is in the bar's tray beside Share. */
  .stage-preview {
    display: none;
  }
  /* Share while something else is next (the voice, the deck catching up):
     quiet, as Preview is, so the one filled button on the page is that */
  .deck-act.share.quiet {
    background: none;
    color: var(--ink2);
    box-shadow: inset 0 0 0 1px var(--rule);
  }
  .deck-act.share.quiet:hover:not(:disabled, [aria-disabled='true']) {
    background: var(--fill-06);
    color: var(--ink);
  }
  /* why Share waits, in one line beside it: "1 slide needs a voice ·
     Update voice". A wide screen's; a phone's bar has no room for it */
  .share-reason {
    font-size: 12.5px;
    color: var(--ink3);
    white-space: nowrap;
  }
  .share-reason .link {
    padding: 0;
    border: 0;
    background: none;
    font: inherit;
    color: var(--ink);
    cursor: pointer;
    text-decoration: underline;
    text-decoration-color: color-mix(in srgb, currentColor 35%, transparent);
    text-underline-offset: 3px;
  }
  .share-reason .link:hover {
    text-decoration-color: currentColor;
  }
  @media (max-width: 767px) {
    .share-reason {
      display: none;
    }
  }
  /* why Share cannot be pressed yet, said when it is pressed anyway */
  .share-note {
    position: absolute;
    top: calc(100% + 8px);
    right: 0;
    z-index: 40;
    width: max-content;
    max-width: min(260px, calc(100vw - 24px));
    padding: 8px 12px;
    border-radius: 10px;
    background: var(--bg);
    color: var(--ink2);
    font-size: 13px;
    line-height: 1.4;
    white-space: normal;
    box-shadow:
      0 0 0 1px var(--rule),
      0 18px 44px -20px rgb(0 0 0 / 0.55);
    animation: ease-in-line 0.16s ease-out both;
  }
  /* where giving the deck to somebody is arranged */
  .share-pop {
    position: absolute;
    top: calc(100% + 8px);
    right: 0;
    z-index: 40;
    min-width: 260px;
    padding: 6px;
    border-radius: 12px;
    background: var(--bg);
    box-shadow:
      0 0 0 1px var(--rule),
      0 18px 44px -20px rgb(0 0 0 / 0.55);
    animation: ease-in-line 0.16s ease-out both;
  }
  /* the public link: its state, its address, its lock, its way down — and,
     last, how it was engaged with. The page does not scroll (the creator's
     root is overflow: hidden), so a panel opened to every slide and the
     latest opens scrolls — inside the panel, not the panel itself: Firefox
     does not round a scroller's own scrollbar to its corners, but does clip
     what is inside a rounded box. */
  .share-panel {
    width: min(320px, calc(100vw - 24px));
    padding-inline: 0;
    overflow: hidden;
  }
  /* Reset stays within reach. Where scrollbars take room (Firefox, Windows,
     a mouse plugged in) that room is kept from the start, and on both sides,
     so the panel neither narrows — its content jumping — the moment it
     begins to scroll, nor sits off-centre while it does not. It runs to the
     panel's sides, so the scrollbar is at the edge; its padding is the 6px
     the panel's sides had, less the room the scrollbar keeps
     (`fitGutter` measures it), so the content stands where it always did.
     (The panel's own 6px top and bottom are outside: 100vh less 96px, less
     12px.) */
  .share-scroll {
    max-height: calc(100vh - 108px);
    overflow-y: auto;
    scrollbar-gutter: stable both-edges;
    padding-inline: max(0px, calc(6px - var(--gutter, 0px)));
  }
  /* the switch that shares the deck to the web, and its line */
  .share-web-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 10px 10px 0;
  }
  .share-web-title {
    font-size: 14px;
    font-weight: 600;
    color: var(--ink);
  }
  .share-web .share-status {
    padding-top: 4px;
  }
  /* turning sharing off asks first: links already given out stop working */
  .share-confirm,
  .engagement-confirm {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px 8px;
    margin: 0 10px 8px;
    padding: 8px 10px;
    border-radius: 8px;
    background: var(--fill-04);
    font-size: 12.5px;
    color: var(--ink);
  }
  .share-confirm > span {
    flex: 1 1 100%;
  }
  .share-mini-danger {
    color: var(--danger);
  }
  /* how the people the deck was given to engaged with it: a folded row in
     the Share panel, standing off its rule as far as Page metadata stands
     off the password's, so neither's hover touches a line; with no opens
     yet there is nothing to unfold */
  .eng {
    padding-top: 4px;
  }
  .eng .share-meta-toggle:disabled {
    cursor: default;
  }
  .eng .share-meta-toggle:disabled:hover {
    background: none;
  }
  .eng .share-meta-toggle:disabled svg {
    visibility: hidden;
  }
  .eng-body {
    padding-bottom: 4px;
  }
  .eng-line {
    margin: 0;
    padding: 0 10px 8px;
    font-size: 12.5px;
    color: var(--ink3);
  }
  .eng-totals {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 6px;
    padding: 0 10px 10px;
  }
  .eng-tile {
    display: flex;
    flex-direction: column;
    gap: 1px;
    padding: 8px;
    border-radius: 8px;
    background: var(--fill-04);
  }
  /* sized so a long value ("12m 30s", "12.5 / 20") still fits a third of
     the panel once a scrollbar has taken its room; one that does not
     breaks at its space inside the tile rather than running out of it */
  .eng-tile b {
    font-size: 16px;
    font-weight: 600;
    line-height: 1.2;
    color: var(--ink);
    font-variant-numeric: tabular-nums;
  }
  .eng-tile span {
    font-size: 11.5px;
    color: var(--ink3);
  }
  .eng-slides,
  .eng-recent {
    margin: 0;
    padding: 4px 10px 8px;
    list-style: none;
    border-top: 1px solid var(--rule);
  }
  .eng-slides li {
    display: grid;
    grid-template-columns: 1.4rem 1fr 3rem 3.5rem;
    align-items: center;
    gap: 6px;
    padding: 5px 0;
    font-size: 12.5px;
    color: var(--ink2);
  }
  .eng-n {
    color: var(--ink3);
    font-variant-numeric: tabular-nums;
  }
  .eng-slide {
    display: flex;
    flex-direction: column;
    gap: 3px;
    min-width: 0;
  }
  .eng-title {
    overflow: hidden;
    color: var(--ink);
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .eng-bar {
    display: block;
    height: 4px;
    border-radius: 2px;
    background: var(--fill-06);
    overflow: hidden;
  }
  .eng-bar > span {
    display: block;
    width: calc(var(--eng-f) * 100%);
    height: 100%;
    border-radius: inherit;
    background: var(--ink2);
  }
  .eng-num {
    text-align: right;
    font-variant-numeric: tabular-nums;
  }
  .eng-recent li {
    display: flex;
    flex-direction: column;
    gap: 1px;
    padding: 5px 0;
    font-size: 12.5px;
    color: var(--ink2);
  }
  .eng-recent b {
    font-weight: 600;
    color: var(--ink);
  }
  .eng-visit-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
  }
  .eng-visit-head time {
    flex: none;
    color: var(--ink3);
  }
  /* the viewer and what they pressed in that visit, as quiet tags; on a
     narrow panel the tags give way before the time does */
  .eng-visit-who {
    display: flex;
    align-items: baseline;
    gap: 6px;
    min-width: 0;
    overflow: hidden;
  }
  .eng-visit-who b {
    flex: none;
  }
  .eng-did {
    flex: none;
    padding: 1px 6px;
    border-radius: 999px;
    background: var(--fill-06);
    font-size: 11.5px;
    color: var(--ink2);
    white-space: nowrap;
  }
  .eng-visit-detail {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .eng-foot {
    padding: 8px 10px 4px;
    border-top: 1px solid var(--rule);
  }
  .eng-foot .engagement-confirm {
    margin: 0;
  }
  .share-copy {
    display: inline-flex;
    align-items: center;
    gap: 4px;
  }
  .share-status,
  .share-alert {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 10px 10px;
    font-size: 13px;
    color: var(--ink2);
  }
  .share-status-words {
    flex: 1;
  }
  .share-dot {
    flex: none;
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--ink3);
  }
  .share-status[data-status='live'] .share-dot {
    background: var(--ok);
  }
  .share-status[data-status='updating'] .share-dot,
  .share-status[data-status='stale'] .share-dot {
    background: var(--warn);
  }
  .share-status[data-status='updating'] .share-dot {
    animation: share-pulse 1.2s ease-in-out infinite;
  }
  @media (prefers-reduced-motion: reduce) {
    .share-status[data-status='updating'] .share-dot {
      animation: none;
    }
  }
  .share-alert {
    align-items: flex-start;
    margin-bottom: 4px;
    border-radius: 8px;
    background: var(--fill-04);
    color: var(--ink);
  }
  .share-alert > span {
    flex: 1;
  }
  .share-alert b {
    display: block;
    font-size: 13px;
    font-weight: 500;
    color: var(--danger);
  }
  .share-alert small {
    display: block;
    margin-top: 2px;
    font-size: 12px;
    color: var(--ink3);
  }
  /* a small secondary action beside what it acts on */
  .share-mini {
    flex: none;
    padding: 3px 9px;
    border: 0;
    border-radius: 999px;
    background: var(--fill-06);
    color: var(--ink);
    font: inherit;
    font-size: 12px;
    font-weight: 500;
    text-decoration: none;
    white-space: nowrap;
    cursor: pointer;
    transition: background 0.14s;
  }
  .share-mini:hover:not(:disabled) {
    background: var(--fill-1);
  }
  .share-mini:disabled {
    opacity: 0.5;
    cursor: default;
  }
  .share-url,
  .share-pass-row,
  .share-lock-head {
    display: flex;
    align-items: center;
    gap: 8px;
  }
  .share-url {
    margin: 2px 10px 6px;
    padding: 6px 6px 6px 8px;
    border-radius: 8px;
    background: var(--fill-04);
    font-size: 12px;
    color: var(--ink2);
  }
  .share-url svg,
  .share-lock-head svg {
    flex: none;
    color: var(--ink3);
  }
  .share-url-text {
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    user-select: all;
  }
  /* the same height shared or not, editing or not: nothing jumps */
  .share-url {
    min-height: 36px;
  }
  /* an address that is not live yet reads as a promise, not a link */
  .share-url:not([data-live]) .share-url-text {
    color: var(--ink3);
  }
  .share-url .share-icon {
    width: 22px;
    height: 22px;
  }
  .share-url-hint {
    flex: none;
    padding: 0 4px;
    font-size: 11.5px;
    color: var(--ink3);
    white-space: nowrap;
  }
  /* editing the address: the fixed part quiet, the slug in a box */
  /* while the slug is edited it gets the row: the fixed part before it
     shrinks to its last characters (the whole address is its tooltip), cut
     from the left so what shows is what sits next to the slug — rtl only
     places the ellipsis, the text inside reads ltr — and the link mark
     steps aside */
  .share-url[data-editing] > svg {
    display: none;
  }
  .share-url-prefix {
    flex: 0 0 auto;
    max-width: 3.5em;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    direction: rtl;
    color: var(--ink3);
  }
  .share-url-input {
    flex: 1 1 auto;
    min-width: 0;
    height: 22px;
    margin-left: -6px;
    padding: 0 6px;
    border: 1px solid var(--rule);
    border-radius: 5px;
    background: var(--bg);
    color: var(--ink);
    font: inherit;
    font-size: 12px;
    transition: opacity 0.12s;
  }
  .share-url-input:focus-visible {
    outline: 2px solid var(--accent-deep);
    outline-offset: -1px;
  }
  .share-url-input:disabled {
    opacity: 0.6;
    transition: opacity 0.12s 0.2s;
  }
  .share-lock {
    margin: 4px 0;
    padding: 8px 10px;
    border-top: 1px solid var(--rule);
    border-bottom: 1px solid var(--rule);
  }
  .share-lock-head {
    font-size: 13px;
    color: var(--ink);
  }
  .share-lock-head > span {
    flex: 1;
  }
  /* an on/off switch, the button's own state (aria-checked) drawn */
  .share-switch {
    position: relative;
    flex: none;
    width: 30px;
    height: 18px;
    padding: 0;
    border: 0;
    border-radius: 999px;
    background: var(--fill-15);
    cursor: pointer;
    transition: background 0.15s;
  }
  .share-switch::after {
    content: '';
    position: absolute;
    top: 2px;
    left: 2px;
    width: 14px;
    height: 14px;
    border-radius: 50%;
    background: var(--bg);
    box-shadow: 0 1px 2px rgb(0 0 0 / 0.25);
    transition: transform 0.15s;
  }
  .share-switch[aria-checked='true'] {
    background: var(--accent-deep);
  }
  .share-switch[aria-checked='true']::after {
    transform: translateX(12px);
  }
  .share-switch:disabled {
    opacity: 0.5;
    cursor: default;
  }
  .share-pass-row {
    margin-top: 8px;
  }
  .share-pass {
    flex: 1;
    padding: 5px 8px;
    border-radius: 6px;
    background: var(--fill-04);
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 12.5px;
    color: var(--ink);
    user-select: all;
  }
  .share-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: 26px;
    height: 26px;
    padding: 0;
    border: 0;
    border-radius: 6px;
    background: none;
    color: var(--ink2);
    cursor: pointer;
  }
  .share-icon:hover:not(:disabled) {
    background: var(--fill-06);
    color: var(--ink);
  }
  .share-icon:disabled {
    opacity: 0.5;
    cursor: default;
  }
  .share-line {
    margin: 6px 10px 2px;
    font-size: 12px;
    line-height: 1.4;
    color: var(--ink3);
  }
  .share-line.warn {
    color: var(--warn);
  }
  .share-line.error {
    color: var(--danger);
  }
  /* what the link's page says of itself where it is pasted: folded away */
  .share-meta {
    margin: 4px 0;
    border-top: 1px solid var(--rule);
  }
  /* straight after the password, whose own rule already divides them */
  .share-lock + .share-meta {
    margin-top: 0;
    border-top: 0;
  }
  .share-meta-toggle {
    display: flex;
    align-items: center;
    gap: 6px;
    width: 100%;
    padding: 8px 10px;
    border: 0;
    border-radius: 8px;
    background: none;
    color: var(--ink);
    font: inherit;
    font-size: 13px;
    text-align: left;
    cursor: pointer;
  }
  .share-meta-toggle:hover {
    background: var(--fill-06);
  }
  .share-meta-toggle svg {
    flex: none;
    color: var(--ink3);
    transition: transform 0.15s;
  }
  .share-meta-toggle[aria-expanded='true'] svg {
    transform: rotate(90deg);
  }
  .share-meta-toggle > span {
    flex: 1;
  }
  .share-meta-toggle small {
    font-size: 12px;
    color: var(--ink3);
  }
  .share-meta-body {
    display: grid;
    gap: 8px;
    padding: 2px 10px 8px;
  }
  .share-meta-body .share-line {
    margin: 0;
  }
  .share-meta-field {
    display: grid;
    gap: 3px;
  }
  .share-meta-label {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
    font-size: 12px;
    font-weight: 500;
    color: var(--ink2);
  }
  .share-meta-count {
    font-size: 11px;
    font-weight: 400;
    font-variant-numeric: tabular-nums;
    color: var(--ink3);
  }
  .share-meta-count[data-over] {
    color: var(--warn);
  }
  .share-meta-field input,
  .share-meta-field textarea {
    width: 100%;
    box-sizing: border-box;
    padding: 5px 8px;
    border: 1px solid var(--rule);
    border-radius: 6px;
    background: var(--fill-04);
    color: var(--ink);
    font: inherit;
    font-size: 12.5px;
    line-height: 1.4;
    resize: none;
  }
  .share-meta-field input::placeholder,
  .share-meta-field textarea::placeholder {
    color: var(--ink3);
  }
  .share-meta-field input:focus-visible,
  .share-meta-field textarea:focus-visible {
    outline: 2px solid var(--accent-deep);
    outline-offset: -1px;
  }
  .share-meta-field input:disabled,
  .share-meta-field textarea:disabled {
    opacity: 0.6;
  }
  /* The panel's controls go quiet while a request is out. One that answers
     quickly should not blink them: dimming waits a moment, coming back is
     immediate (a transition takes the state it is going to). */
  .share-mini {
    transition: background 0.14s, opacity 0.12s;
  }
  .share-switch {
    transition: background 0.15s, opacity 0.12s;
  }
  .share-icon,
  .share-meta-field input,
  .share-meta-field textarea {
    transition: opacity 0.12s;
  }
  .share-mini:disabled {
    transition: background 0.14s, opacity 0.12s 0.2s;
  }
  .share-switch:disabled {
    transition: background 0.15s, opacity 0.12s 0.2s;
  }
  .share-icon:disabled,
  .share-meta-field input:disabled,
  .share-meta-field textarea:disabled {
    transition: opacity 0.12s 0.2s;
  }
  .share-meta-image {
    display: flex;
    align-items: flex-start;
    gap: 10px;
  }
  /* the picture as the link shows it: 1200 by 630, cropped to fill */
  .share-meta-thumb {
    position: relative;
    flex: none;
    width: 112px;
    aspect-ratio: 1200 / 630;
    overflow: hidden;
    border-radius: 6px;
    background: var(--fill-06);
    box-shadow: 0 0 0 1px var(--rule);
  }
  .share-meta-thumb img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
  }
  .share-meta-thumb[data-pending] img {
    opacity: 0.35;
  }
  .share-meta-thumb-note {
    position: absolute;
    inset: 0;
    display: grid;
    place-items: center;
    padding: 4px;
    font-size: 11px;
    line-height: 1.2;
    text-align: center;
    color: var(--ink2);
  }
  .share-meta-image-side {
    display: grid;
    gap: 2px;
    min-width: 0;
  }
  .share-meta-image-side > small {
    font-size: 12px;
    color: var(--ink3);
  }
  .share-meta-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin-top: 4px;
  }
  /* for a screen reader only */
  .share-sr {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
  /* an icon that says its name when pointed at (or focused): the word
     unfolds from the icon's right edge, so the button grows in place */
  .btn.reveal {
    gap: 0;
    padding: 7px 8px;
  }
  .btn.reveal span {
    max-width: 0;
    opacity: 0;
    overflow: hidden;
    white-space: nowrap;
    transition:
      max-width 0.2s ease,
      opacity 0.15s ease,
      margin-left 0.2s ease;
  }
  .btn.reveal:hover span,
  .btn.reveal:focus-visible span {
    max-width: 12ch;
    opacity: 1;
    margin-left: 7px;
  }
  /* With no hover to reveal it, a phone keeps the icon and gives the words
     up for the title; a tablet has the room, so it says what it is. */
  @media (hover: none) and (max-width: 767px) {
    .btn.reveal > span:not(.needs-mark) {
      display: none;
    }
  }
  .btn.reveal > span:not(.needs-mark) {
    max-width: 0;
  }
  .btn.reveal:hover > span:not(.needs-mark),
  .btn.reveal:focus-visible > span:not(.needs-mark) {
    max-width: 16ch;
  }
  @media (hover: none) and (min-width: 768px) {
    .btn.reveal > span:not(.needs-mark) {
      max-width: 16ch;
      opacity: 1;
      margin-left: 7px;
    }
  }
  /* In the bar it stands in the row with Share's tray: the same height,
     and round while it is only its icon (a pill once its word is out). */
  .bar .needs-btn {
    height: 34px;
    min-height: 0;
    min-width: 34px;
    padding: 0 8px;
    justify-content: center;
  }
  .btn.reveal .needs-mark {
    max-width: none;
    opacity: 1;
    overflow: visible;
    margin: 0;
  }
  /* the visual identity's state, on its icon */
  .needs-mark {
    position: relative;
    display: inline-flex;
  }
  /* the button is only there while something is waiting, so the dot has
     one job: say that this is a question, not an offer */
  .needs-dot {
    position: absolute;
    top: -3px;
    right: -3px;
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--warn);
    box-shadow: 0 0 0 2px var(--bg);
  }
  /* the start screen's one line about it: a website, and it is prepared
     beside the story */
  /* The brand, as one line under the composer: there to be got on with
     or left alone, and never the size of the thing somebody came to do. */
  .vi-nudge-wrap {
    position: relative;
  }
  .vi-nudge {
    display: flex;
    align-items: baseline;
    gap: 7px;
    margin: 14px max(20px, calc(50% - 300px)) 0;
    font-size: 13px;
    line-height: 20px;
    color: var(--ink3);
  }
  .vi-nudge svg {
    align-self: center;
    flex: none;
    opacity: 0.8;
  }
  .vi-nudge span {
    flex: 1;
    min-width: 0;
  }
  .vi-nudge button {
    flex: none;
    border: 1px solid var(--rule);
    background: var(--bg);
    border-radius: 999px;
    padding: 2px 11px;
    font-size: 12.5px;
    line-height: 18px;
    color: var(--ink);
    cursor: pointer;
    transition:
      border-color 0.15s,
      background 0.15s;
  }
  .vi-nudge button:hover {
    border-color: var(--ink3);
    background: var(--fill-06);
  }
  /* the deck without a visual identity: a decision, so it reads as one —
     quiet text, never the button that stands out */
  .brand-prompt .vi-without-line {
    margin: 10px 0 0;
  }
  .brand-prompt button.vi-without {
    border: 0;
    background: none;
    padding: 0;
    font: inherit;
    font-size: 12.5px;
    color: var(--ink3);
    text-decoration: underline;
    text-underline-offset: 3px;
    cursor: pointer;
  }
  .brand-prompt button.vi-without:hover {
    color: var(--ink);
  }
  /* the way to put the ask away: quiet, beside the way to answer it */
  .vi-nudge button.vi-nudge-later {
    border-color: transparent;
    background: none;
    color: var(--ink3);
  }
  .vi-nudge button.vi-nudge-later:hover {
    border-color: transparent;
    background: var(--fill-06);
    color: var(--ink);
  }
  /* The site the brand probably lives at, asked as a question with its
     answer ready. On the conversation's one kind of panel (the question
     sheet's surface), set to the composer's edges just under it, so it
     reads as the next thing to settle rather than something left lying
     under the field: the question, the tick the button waits on, then the
     two ways on, the answer on the right. */
  .vi-guess {
    margin: -6px 20px 16px;
    padding: 12px 14px;
    border-radius: 12px;
    background: var(--fill-03);
    box-shadow: inset 0 0 0 1px var(--rule);
    font-size: 13px;
    line-height: 20px;
    color: var(--ink2);
    animation: qstep-in 0.28s cubic-bezier(0.2, 0.7, 0.2, 1);
  }
  .b:not([data-phase='build']) .vi-guess {
    margin-left: max(20px, calc(50% - 360px));
    margin-right: max(20px, calc(50% - 360px));
  }
  .vi-guess p {
    margin: 0;
  }
  .vi-guess-head {
    display: flex;
    align-items: flex-start;
    gap: 10px;
  }
  .vi-guess-head svg {
    flex: none;
    margin-top: 3px;
    color: var(--ink3);
  }
  .vi-guess-q {
    color: var(--ink);
    font-weight: 500;
  }
  .vi-guess-q b {
    font-weight: 600;
  }
  .vi-guess-why {
    color: var(--ink3);
    font-size: 12.5px;
  }
  .vi-guess .vi-guess-rights {
    margin: 10px 0 0 24px;
    font-size: 12px;
    color: var(--ink2);
  }
  .vi-guess-acts {
    display: flex;
    justify-content: flex-end;
    align-items: center;
    gap: 4px;
    margin: 12px 0 0;
  }
  .vi-guess-acts .btn {
    padding: 6px 14px;
  }
  .vi-guess-other {
    height: 30px;
    padding: 0 12px;
    border: 0;
    border-radius: 999px;
    background: none;
    font: inherit;
    font-size: 13px;
    color: var(--ink2);
    cursor: pointer;
    transition:
      color 0.15s,
      background 0.15s;
  }
  .vi-guess-other:hover:not(:disabled) {
    color: var(--ink);
    background: var(--fill-06);
  }
  /* In place, under the line that asked for it: in the flow, no card, no
     shadow, nothing floating over the page. */
  .vi-nudge-wrap.inline .vi-kickoff {
    margin: 14px max(20px, calc(50% - 300px)) 0;
    position: static;
    padding: 0;
    border: 0;
    border-radius: 0;
    background: none;
    box-shadow: none;
    animation: none;
  }
  /* inside the gate's own box, which already has its padding */
  .brand-prompt .vi-guess,
  .brand-prompt .vi-nudge-wrap.inline .vi-kickoff {
    margin: 0;
  }
  .brand-prompt .vi-guess {
    padding: 0;
    background: none;
    box-shadow: none;
    animation: none;
  }
  /* the gate's line already wears the brand's mark: the question under it
     keeps the mark's room, so the two lines start at one edge, not a second
     mark */
  .brand-prompt .vi-guess-head > svg {
    visibility: hidden;
  }
  /* What it needs is asked in a popover over that line: it covers the
     line it came from, moves nothing under it, and goes away on Escape
     or on a click anywhere else. The first step is never a page. */
  .vi-kickoff {
    position: absolute;
    z-index: 31;
    top: 4px;
    left: max(20px, calc(50% - 300px));
    right: max(20px, calc(50% - 300px));
    padding: 14px 16px;
    border: 1px solid var(--rule);
    border-radius: 14px;
    background: var(--bg);
    box-shadow:
      0 24px 60px -24px rgba(0, 0, 0, 0.45),
      0 6px 16px -10px rgba(0, 0, 0, 0.25);
    transform-origin: top right;
    animation: vi-pop-in 0.18s cubic-bezier(0.2, 0.7, 0.2, 1);
  }
  .vi-kickoff-lead {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    margin: 0 0 10px;
    font-size: 13px;
    color: var(--ink2);
    line-height: 1.45;
  }
  .vi-kickoff-lead svg {
    flex: none;
    margin-top: 2px;
    color: var(--ink3);
  }
  .vi-kickoff-row {
    display: flex;
    gap: 8px;
  }
  .vi-kickoff-row input {
    flex: 1;
    min-width: 0;
    height: 34px;
    padding: 0 12px;
    border-radius: 999px;
    border: 1px solid var(--rule);
    background: var(--fill-04);
    color: var(--ink);
    font: inherit;
    font-size: 13px;
    outline: none;
  }
  .vi-kickoff-row input:focus-visible {
    border-color: var(--ring);
  }
  .vi-kickoff-rights {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    margin: 10px 0 0;
    font-size: 12px;
    color: var(--ink3);
    line-height: 1.4;
    cursor: pointer;
  }
  .vi-kickoff-rights input {
    margin: 2px 0 0;
    accent-color: var(--accent);
  }
  .vi-kickoff-error {
    margin: 8px 0 0;
    font-size: 12px;
    color: var(--ink);
  }
  .vi-kickoff-else {
    margin: 8px 0 0;
    font-size: 12px;
    color: var(--ink3);
  }
  .vi-kickoff-else button {
    color: var(--ink);
    text-decoration: underline;
    text-underline-offset: 3px;
  }
  /* the workspace's Scenes, under the composer: cards. The cover is the
     card; everything else is quiet and reads once the eye has landed. */
  .stories {
    margin: 44px max(24px, calc(50% - 600px)) 32px;
  }
  /* A phone: one column of cards the width of the composer above them, not
     a 300px column adrift in the middle, and room under the last one to
     scroll it clear of a browser bar that floats over the page (iOS 26). */
  @media (max-width: 599px) {
    .stories {
      margin: 36px 20px calc(96px + env(safe-area-inset-bottom, 0px));
    }
  }
  .stories-head {
    grid-column: 1 / -1;
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    margin: 0 0 -12px;
  }
  .stories-head h2,
  .stories-window-bar h2 {
    margin: 0;
    font-size: 12px;
    font-weight: 500;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink3);
  }
  .stories-head button {
    font-size: 13px;
    color: var(--ink2);
    text-decoration: underline;
    text-underline-offset: 3px;
  }
  .stories-head button:hover {
    color: var(--ink);
  }
  /* the grid: tracks of one width, as many as fit, centred — so a page
     with two Scenes is a composition, not a row that stops short */
  .story-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(250px, 300px));
    justify-content: center;
    gap: 34px 26px;
  }
  .story-cards {
    list-style: none;
    margin: 0;
    padding: 0;
    display: contents;
  }
  ul.story-cards.story-grid {
    display: grid;
  }
  /* on a phone, one card to a row across the whole column (see .stories) */
  @media (max-width: 599px) {
    .story-grid {
      grid-template-columns: minmax(0, 1fr);
      gap: 28px;
    }
  }
  .story-card {
    position: relative;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 12px;
  }
  .story-card-cover {
    position: relative;
  }
  .story-card-open {
    display: block;
    width: 100%;
    padding: 0;
    border-radius: 12px;
    text-align: left;
  }
  /* the tile: a 16:9 with one hairline edge and a soft lift under the hand */
  .story-card-tile {
    position: relative;
    display: block;
    aspect-ratio: 16 / 9;
    border-radius: 12px;
    overflow: hidden;
    background: var(--panel) center / cover no-repeat;
    box-shadow:
      inset 0 0 0 1px var(--rule),
      0 1px 2px rgb(0 0 0 / 0.12);
    transition:
      transform 0.25s cubic-bezier(0.2, 0.7, 0.2, 1),
      box-shadow 0.25s;
    container-type: inline-size;
  }
  .story-card-open:hover .story-card-tile,
  .story-card-open:focus-visible .story-card-tile {
    transform: translateY(-3px);
    box-shadow:
      inset 0 0 0 1px var(--fill-28),
      0 18px 40px -18px rgb(0 0 0 / 0.55),
      0 4px 10px -6px rgb(0 0 0 / 0.3);
  }
  .story-card-tile.has-cover .mini {
    border-radius: 0;
    height: 100%;
  }
  /* a real slide comes up over the ground rather than snapping in */
  .mini.deck iframe {
    display: block;
    border: 0;
    background: transparent;
    opacity: 0;
    transition: opacity 0.35s ease-out;
  }
  .mini.deck iframe.is-loaded {
    opacity: 1;
  }
  /* A slide replacing one already on screen: no fade, because a fade means
     a moment with nothing solid on the card. */
  .mini.deck iframe.at-once {
    transition: none;
  }
  /* A Scene still being written: where it stands, not its title again (the
     title is under the tile). The stage and the road it is on, centred on
     the app's own quiet ground, in either theme; a finished Scene shows its
     cover, so the two read as the same thing at two moments. */
  .story-card-tile.draft {
    background:
      radial-gradient(120% 90% at 100% 0%, var(--fill-06), transparent 60%),
      var(--panel);
  }
  .story-card-draft {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 12px;
    padding: 8cqw;
  }
  .story-card-kicker {
    font-size: clamp(10px, 3.6cqw, 12px);
    letter-spacing: 0.1em;
    text-transform: uppercase;
    text-align: center;
    color: var(--ink2);
  }
  /* how far it has come: a rail of six under the stage */
  .story-card-rail {
    display: flex;
    gap: 3px;
    width: 44%;
    height: 2px;
  }
  .story-card-rail i {
    flex: 1;
    border-radius: 1px;
    background: var(--fill-1);
  }
  .story-card-rail i.on {
    background: var(--ink2);
  }
  /* on the identity's own ground once the workspace has one: the pack's
     background with a breath of its accent, the title in its ink, the rail
     in its accent (pulled toward the ink so a white accent still shows).
     The same in either theme: it is the brand's colour, not the app's. */
  .story-card-tile.draft.branded {
    background:
      radial-gradient(
        120% 90% at 100% 0%,
        color-mix(in srgb, var(--tile-accent) 22%, transparent),
        transparent 60%
      ),
      linear-gradient(
        165deg,
        var(--tile-bg),
        color-mix(in srgb, var(--tile-bg) 90%, var(--tile-ink))
      );
  }
  .branded .story-card-kicker {
    color: color-mix(in srgb, var(--tile-ink) 62%, var(--tile-bg));
  }
  .branded .story-card-rail i {
    background: color-mix(in srgb, var(--tile-ink) 14%, transparent);
  }
  .branded .story-card-rail i.on {
    background: color-mix(in srgb, var(--tile-accent) 70%, var(--tile-ink));
  }
  .story-card-text {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    text-align: left;
    /* flush with the tile's edge above it */
    padding: 0;
    min-width: 0;
  }
  .story-card-title {
    font-size: 15.5px;
    font-weight: 500;
    line-height: 1.3;
    color: var(--ink);
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    overflow: hidden;
    text-wrap: balance;
  }
  .story-card-meta {
    margin-top: 4px;
    font-size: 13px;
    color: var(--ink3);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  /* what a deck can do, on its cover: quiet until the card is pointed at
     or reached, always there for a touch screen */
  .story-card-actions {
    position: absolute;
    right: 10px;
    top: 10px;
    display: flex;
    align-items: center;
    gap: 6px;
    opacity: 0;
    transform: translateY(-4px);
    transition:
      opacity 0.18s,
      transform 0.18s;
  }
  .story-card:hover .story-card-actions,
  .story-card:focus-within .story-card-actions {
    opacity: 1;
    transform: none;
  }
  @media (hover: none) {
    .story-card-actions {
      opacity: 1;
      transform: none;
    }
  }
  .story-card-actions > button {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: 30px;
    padding: 0 12px;
    border-radius: 999px;
    font-size: 13px;
    line-height: 1;
    color: var(--ink);
    background: color-mix(in srgb, var(--bg) 82%, transparent);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    box-shadow:
      inset 0 0 0 1px var(--rule),
      0 2px 8px rgb(0 0 0 / 0.18);
    white-space: nowrap;
    transition: background 0.15s;
  }
  .story-card-actions > button:hover {
    background: var(--bg);
  }
  /* the card's "⋯": one round pill beside the deck's, and its menu hung
     under it, over the cards below */
  .story-card-actions {
    z-index: 2;
  }
  .story-card-more {
    position: relative;
    display: inline-flex;
  }
  .story-card-actions > .story-card-more > .story-card-more-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    padding: 0;
    border-radius: 999px;
    font-size: 16px;
    line-height: 1;
    letter-spacing: 0.04em;
    color: var(--ink);
    background: color-mix(in srgb, var(--bg) 82%, transparent);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    box-shadow:
      inset 0 0 0 1px var(--rule),
      0 2px 8px rgb(0 0 0 / 0.18);
    transition: background 0.15s;
  }
  .story-card-more-btn:hover,
  .story-card-more-btn[aria-expanded='true'] {
    background: var(--bg);
  }
  /* the menu stays put while it is open, even with the pointer gone */
  .story-card:has(.story-card-menu) .story-card-actions {
    opacity: 1;
    transform: none;
  }
  .story-card-menu {
    position: absolute;
    top: calc(100% + 6px);
    right: 0;
    z-index: 20;
    min-width: 148px;
    display: flex;
    flex-direction: column;
    padding: 4px;
    border-radius: 10px;
    background: var(--bg);
    box-shadow:
      inset 0 0 0 1px var(--rule),
      0 12px 32px -12px rgb(0 0 0 / 0.45);
  }
  .story-card-menu [role='menuitem'] {
    display: block;
    width: 100%;
    padding: 7px 10px;
    border-radius: 6px;
    font: inherit;
    font-size: 13px;
    text-align: left;
    color: var(--ink);
    background: none;
    cursor: pointer;
  }
  .story-card-menu [role='menuitem']:hover,
  .story-card-menu [role='menuitem']:focus-visible {
    background: var(--fill-06);
    outline: none;
  }
  .story-card-menu [role='menuitem'].danger {
    color: var(--danger);
  }
  /* deleting asks first, over the cover it would take away */
  .story-card-ask {
    position: absolute;
    inset: 0;
    z-index: 3;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 12px;
    padding: 12px 16px;
    border-radius: 12px;
    text-align: center;
    background: color-mix(in srgb, var(--bg) 90%, transparent);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    box-shadow: inset 0 0 0 1px var(--rule);
  }
  .story-card-ask-line {
    margin: 0;
    font-size: 13.5px;
    line-height: 1.35;
    color: var(--ink);
    text-wrap: balance;
  }
  .story-card-ask-line > span + span {
    color: var(--ink2);
  }
  .story-card-ask-acts {
    display: flex;
    gap: 8px;
  }
  /* a name being written where the title was, on the card and in the bar */
  .title-field {
    display: flex;
    flex-direction: column;
    gap: 4px;
    min-width: 0;
  }
  .title-field input {
    width: 100%;
    min-width: 0;
    margin: 0;
    padding: 2px 6px;
    border: 1px solid var(--rule-strong);
    border-radius: 6px;
    font: inherit;
    color: var(--ink);
    background: var(--panel);
    outline: none;
  }
  .title-field input:focus-visible {
    border-color: var(--ink2);
  }
  /* in the title's own line: the meta under it does not move */
  .story-card-text .title-field input {
    margin: -1px 0 -1px -6px;
    padding: 0 5px;
    width: calc(100% + 6px);
  }
  .title-field-refused,
  .story-card-refused {
    margin: 0;
    font-size: 12.5px;
    color: var(--danger);
  }
  .story-card-refused {
    padding: 0 2px;
  }
  /* the open story's name, pressed to be renamed */
  button.story-title {
    font: inherit;
    font-size: 13px;
    font-weight: 500;
    text-align: left;
    cursor: text;
  }
  button.story-title:hover,
  button.story-title:focus-visible {
    background: var(--fill-06);
  }
  .story-title.title-field {
    padding: 0;
    overflow: visible;
  }
  .story-title.title-field input {
    padding: 3px 11px;
    border-radius: 999px;
    font-size: 13px;
    font-weight: 500;
  }
  /* every Scene, with a search and filters */
  .stories-window {
    position: absolute;
    inset: 0;
    z-index: 5;
    overflow: auto;
    background: var(--bg);
    padding: 0 max(24px, calc(50% - 600px)) 48px;
  }
  /* the window's cards fill its column, so the bar above and the rows below
     share their edges; the start page keeps its narrower, centred tracks */
  .stories-window .story-grid {
    grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
    justify-content: stretch;
    gap: 36px 28px;
  }
  .stories-window-bar {
    position: sticky;
    top: 0;
    z-index: 1;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 16px 0 14px;
    background: var(--bg);
    border-bottom: 1px solid var(--rule);
    margin-bottom: 28px;
  }
  .stories-window-bar h2 {
    flex: none;
    margin-right: 6px;
  }
  .stories-window-bar input,
  .stories-window-bar select {
    height: 32px;
    padding: 0 12px;
    border-radius: 999px;
    border: 1px solid var(--rule);
    background: var(--fill-04);
    color: var(--ink);
    font: inherit;
    font-size: 13px;
    outline: none;
  }
  .stories-window-bar input {
    flex: 1;
    min-width: 120px;
  }
  .stories-window-bar input:focus-visible,
  .stories-window-bar select:focus-visible {
    border-color: var(--ring);
  }
  /* one height for the whole bar: the button's padding and border made it
     a few pixels taller than the search and the filters beside it */
  .stories-window-bar .btn {
    margin-left: auto;
    height: 32px;
    min-height: 0;
    padding-block: 0;
  }
  /* On a phone the bar folds: the title and Close on the first row, the
     search the whole width under them, the filters sharing the last row. */
  @media (max-width: 599px) {
    .stories-window-bar {
      flex-wrap: wrap;
      row-gap: 10px;
      padding-top: 12px;
    }
    .stories-window-bar h2 {
      flex: 1 1 auto;
    }
    .stories-window-bar .btn {
      order: 1;
    }
    .stories-window-bar input {
      order: 2;
      flex: 1 1 100%;
      min-width: 0;
    }
    .stories-window-bar select {
      order: 3;
      flex: 1 1 0;
      min-width: 0;
    }
  }
  .stories-window-empty {
    color: var(--ink3);
    font-size: 14px;
  }
  .b:not([data-phase='start']) .stories {
    display: none;
  }
  /* the chat column's width is the person's: a handle on its edge */
  .chat-resize {
    position: absolute;
    top: 0;
    bottom: 0;
    right: -4px;
    width: 8px;
    cursor: col-resize;
    z-index: 3;
    touch-action: none;
  }
  /* a finger finds a wider edge; the line it lights stays where it was */
  @media (pointer: coarse) {
    .chat-resize {
      right: -12px;
      width: 24px;
    }
    .chat-resize::after {
      left: 11px;
    }
  }
  .chat-resize::after {
    content: '';
    position: absolute;
    top: 0;
    bottom: 0;
    left: 3px;
    width: 2px;
    background: var(--accent);
    opacity: 0;
    transition: opacity 0.15s;
  }
  .chat-resize:hover::after,
  .chat-resize[data-dragging]::after {
    opacity: 0.7;
  }
  .b:not([data-phase='build']) .chat-resize {
    display: none;
  }
  /* the visual identity's question card, in the story */
  .vi-ask {
    margin: 0 20px 10px;
    border: 1px solid var(--rule);
    border-radius: 14px;
    background: var(--fill-025);
    overflow: hidden;
    /* a flex item with overflow hidden may be squeezed to nothing when the
       thread wants the room — it was, to two pixels of border, which read
       as a stray line above the composer. It keeps its height. */
    flex: none;
  }
  /* the guide with nothing left to ask draws nothing: no empty well under
     the head */
  .vi-ask .sc-island:empty {
    display: none;
  }
  .b:not([data-phase='build']) .vi-ask {
    margin: 12px max(20px, calc(50% - 300px)) 10px;
  }
  .vi-ask-head {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 9px 14px;
    border-bottom: 1px solid var(--rule);
    font-size: 12px;
    color: var(--ink3);
  }
  .vi-ask-head svg {
    flex: none;
  }
  .vi-ask-head span {
    flex: 1;
    min-width: 0;
  }
  .vi-ask-head button {
    color: var(--ink);
    text-decoration: underline;
    text-underline-offset: 3px;
  }
  .sc-island {
    padding: 12px 14px 14px;
    /* the host's type, not the creator's */
    font-family: var(--body);
  }
  /*
   * An internal affordance, dressed as one: present for the people who need
   * it, never competing with the thing the page is actually for.
   */
  .btn.quiet {
    background: transparent;
    border: 1px solid var(--rule);
    color: var(--ink3);
    text-decoration: none;
  }
  .btn.quiet:hover {
    color: var(--ink);
    border-color: var(--ink3);
  }

  .btn.primary {
    background: var(--ink);
    color: var(--on-ink);
    border-color: var(--ink);
  }
  /* the ink let down, not the accent: the accent means where you are,
     and a button under the pointer is not that */
  .btn.primary:hover:not(:disabled) {
    background: var(--ink2);
    border-color: var(--ink2);
    color: var(--on-ink);
  }
  .btn svg {
    width: 15px;
    height: 15px;
  }
  .btn:disabled {
    opacity: 0.45;
    cursor: default;
  }

  /* ---------------------------------------------------------------- frame */

  .b {
    height: 100%;
    display: grid;
    grid-template-rows: var(--bar-h) minmax(0, 1fr);
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: 'bar' 'chat';
    /* The page is drawn edge to edge (viewport-fit=cover), so what is on it
       keeps clear of the notch, the rounded corners and the status bar
       itself; the ground runs on under them. All four are 0 on a screen
       without them. The bottom is left to whatever sits there. */
    padding: env(safe-area-inset-top, 0px) env(safe-area-inset-right, 0px) 0
      env(safe-area-inset-left, 0px);
  }
  /* The conversation is as wide as the person made it, as far as the stage
     keeps --stage-min beside it. The width is remembered by the browser, not
     the window, so one set in a wide window (a docked laptop, a full screen)
     must not squeeze the story out of its rail in a narrower one. Where even
     the narrowest chat leaves the stage less than that (a window under about
     1010px), the chat holds 320px and the stage takes the rest. */
  .b[data-phase='build'] {
    grid-template-columns:
      clamp(320px, var(--chat-w), 100% - var(--stage-min))
      minmax(0, 1fr);
    grid-template-areas: 'bar bar' 'chat stage';
  }
  /*
   * A story on its way in (see isOpening and OpeningSkeleton). The page is
   * laid out as the story from the first frame — the conversation's column
   * and the stage beside it — and holds an outline of both until the story
   * has landed in the same places. The story itself is laid out underneath,
   * hidden, so it is whole the moment it shows.
   */
  .b[data-phase='opening'] {
    grid-template-columns:
      clamp(320px, var(--chat-w), 100% - var(--stage-min))
      minmax(0, 1fr);
    grid-template-areas: 'bar bar' 'chat stage';
  }
  .b[data-opening] > :not(.bar):not(.skel-chat):not(.skel-stage) {
    visibility: hidden;
  }
  /* The bar lands once, with the story. Its way back and the account hold
     still from the first frame; the title and what sits at the right (Needs
     you, the deck's Preview and Share) arrive one by one as the story is read
     back, each shoving the next along, so they wait unseen and come up
     together, in the places they keep, as the outline gives way. */
  .bar :is(.story-title, .bar-side, .bar-actions) {
    transition: opacity 0.26s ease-out;
  }
  .b[data-opening] .bar :is(.story-title, .bar-side, .bar-actions) {
    opacity: 0;
    pointer-events: none;
    transition: none;
  }
  .skel-chat {
    grid-area: chat;
    display: flex;
    flex-direction: column;
    min-height: 0;
    border-right: 1px solid var(--rule);
    background: var(--bg2);
    pointer-events: none;
  }
  /* the conversation alone, while the brief is still being got: centred in
     the width the conversation takes there, on the page's own ground */
  .skel-chat.alone {
    border-right: 0;
    background: none;
  }
  .skel-chat.alone .skel-thread {
    padding: 32px max(20px, calc(50% - 360px)) 12px;
  }
  .skel-chat.alone .skel-composer {
    margin: 10px max(20px, calc(50% - 360px))
      calc(24px + env(safe-area-inset-bottom));
  }
  .skel-stage {
    grid-area: stage;
    display: flex;
    flex-direction: column;
    min-height: 0;
    background: var(--bg);
    pointer-events: none;
  }
  /* the outline gives way as the story takes its place */
  .b:not([data-opening]) :is(.skel-chat, .skel-stage) {
    animation: fade-out 0.26s ease-out both;
  }
  .skel-chat i,
  .skel-stage i {
    display: block;
    background: var(--fill-05);
    border-radius: 6px;
    animation: breathe var(--work-sweep) ease-in-out infinite;
  }
  .skel-thread {
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: 22px;
    padding: 28px 20px 12px;
    overflow: hidden;
  }
  .skel-said {
    align-self: flex-end;
    height: 34px;
    border-radius: 17px !important;
  }
  .skel-reply {
    display: flex;
    flex-direction: column;
    gap: 9px;
  }
  .skel-reply i {
    height: 11px;
  }
  .skel-composer {
    height: 76px;
    margin: 10px 20px calc(16px + env(safe-area-inset-bottom));
    border-radius: 18px !important;
  }
  .skel-head {
    display: flex;
    align-items: center;
    height: 44px;
    padding: 0 24px;
    border-bottom: 1px solid var(--rule);
    flex: none;
  }
  .skel-head i {
    height: 10px;
  }
  .skel-cards,
  .skel-slides {
    width: 100%;
    max-width: 880px;
    margin: 24px auto 0;
    padding: 0 24px;
    display: grid;
    gap: 12px;
    box-sizing: border-box;
  }
  .skel-cards {
    grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  }
  .skel-cards i {
    height: 132px;
    border-radius: 12px;
  }
  .skel-cards .skel-lead {
    grid-column: 1 / -1;
    height: 64px;
  }
  .skel-slides {
    gap: 56px;
  }
  .skel-slides i {
    aspect-ratio: 16 / 9;
    border-radius: 12px;
  }
  @media (prefers-reduced-motion: reduce) {
    .skel-chat i,
    .skel-stage i {
      animation: none;
    }
  }

  .bar {
    grid-area: bar;
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 0 16px;
    border-bottom: 1px solid var(--rule);
    background: var(--bg);
  }
  .brand {
    font-family: var(--display);
    font-size: 17px;
    letter-spacing: -0.01em;
    display: flex;
    align-items: center;
    gap: 8px;
  }
  .brand .scene-mark,
  .brand .scene-logo {
    flex: none;
    display: block;
    color: var(--ink);
  }
  /* the mark as the way back: quiet until pointed at */
  button.brand {
    border-radius: 6px;
    padding: 4px 6px;
    margin-left: -6px;
    transition: background 0.15s;
  }
  button.brand:hover {
    background: var(--fill-06);
  }
  .crumb {
    color: var(--ink3);
    font-size: 13px;
  }
  .story-title {
    font-size: 13px;
    font-weight: 500;
    color: var(--ink);
    /* a pill, as every pressable thing in the bar is */
    padding: 4px 12px;
    border-radius: 999px;
    min-width: 60px;
    max-width: 40vw;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .story-title:empty::before {
    content: 'Untitled story';
    color: var(--ink3);
  }
  /* not while it is opening: its title is on the way, and a placeholder
     there is one more thing that changes as it lands */
  .b[data-phase='opening'] .story-title:empty::before {
    content: none;
  }
  .bar .status {
    margin-left: auto;
    font-size: 12px;
    color: var(--ink3);
    display: flex;
    align-items: center;
    gap: 8px;
    white-space: nowrap;
    /* A failure message can be a sentence; it may not push the actions off. */
    max-width: 320px;
  }
  .bar .status span {
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .bar .status i {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--ink3);
    flex: none;
  }
  .bar .status.ok i {
    background: var(--ok);
  }
  .bar .status.busy i {
    background: var(--accent);
    animation: pulse var(--work-beat) ease-in-out infinite;
  }
  .bar .status.bad i {
    background: var(--pink);
  }

  .bar-actions {
    display: flex;
    gap: 8px;
    align-items: center;
  }
  /* the way to the slide pack: on the right in every phase, including the
     start, where the status that usually pushes things right is hidden */
  .bar-side {
    display: flex;
    gap: 8px;
    align-items: center;
    margin-left: auto;
  }
  /* the identity icon and what hangs off it */
  .needs-anchor {
    position: relative;
    display: inline-flex;
  }
  .vi-pop {
    position: absolute;
    top: calc(100% + 10px);
    right: 0;
    width: min(460px, calc(100vw - 32px));
    z-index: 40;
    background: var(--panel);
    border: 1px solid var(--rule);
    border-radius: 14px;
    box-shadow:
      0 24px 60px -24px rgb(0 0 0 / 0.6),
      0 6px 16px -10px rgb(0 0 0 / 0.35);
    overflow: hidden;
    text-align: left;
    animation: vi-pop-in 0.18s cubic-bezier(0.2, 0.7, 0.2, 1);
    transform-origin: top right;
  }
  .vi-pop .vi-ask-head {
    background: var(--fill-025);
    padding: 10px 14px;
  }
  /* the bar zeroes its islands' padding (the account sits flush); the
     popover is inside the bar, and its island is a page, not a control */
  .bar .vi-pop .sc-island {
    padding: 14px 16px 16px;
  }
  .vi-pop-close {
    flex: none;
    width: 24px;
    height: 24px;
    margin: -4px -6px -4px 2px;
    border-radius: 6px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--ink3);
  }
  .vi-pop-close:hover {
    color: var(--ink);
    background: var(--fill-06);
  }
  @media (prefers-reduced-motion: reduce) {
    .vi-pop {
      animation: none;
    }
  }
  .bar .status + .bar-side {
    margin-left: 0;
  }
  .b[data-phase='start'] .bar-side {
    margin-left: auto;
  }
  /* The right of the bar is a group, and whichever of its parts comes
     first holds the edge: the status while something is running, else the
     side group, else the actions. Any of them can be absent — the side
     group only exists while something is waiting on the person, the
     actions only once there is a deck — and when the one that was holding
     the edge goes, the next takes it. Without this the account slides back
     against the Scene mark and the bar reads as broken. */
  .b[data-phase='start'] .bar:not(:has(.bar-side)) .bar-account,
  .b[data-phase='intake'] .bar:not(:has(.bar-side)) .bar-account {
    margin-left: auto;
  }
  .b:not([data-phase='start']):not([data-phase='intake'])
    .bar:not(:has(.status)):not(:has(.bar-side))
    .bar-actions {
    margin-left: auto;
  }
  /* the bar on its own above a host page (see SceneCastBarOnly): the class
     sits on the scoped wrapper itself, so `&` rather than a descendant */
  &.sc-bar-only {
    height: var(--bar-h);
    flex: none;
  }
  &.sc-bar-only .bar {
    height: 100%;
  }
  /* no status to push them: the page's actions go to the right on their own */
  &.sc-bar-only .bar-actions {
    margin-left: auto;
  }
  .bar-slot {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
  }
  .bar-account {
    display: inline-flex;
    align-items: center;
    margin-left: 4px;
  }
  /* The host's account button takes the bar's shape: the row's height, and
     a pill around the avatar and whatever rides with it, the avatar as far
     from the left edge as from the top. Where the avatar is all it shows
     (a story on a phone) it is a circle; see below. */
  .bar-account > button {
    height: 34px;
    padding: 0 10px 0 4px;
    border-radius: 999px;
  }
  /* the workspace's name rides with the account on the start page, where
     the Scenes are listed; inside a story it would only be a way out */
  .b:not([data-phase='start']) .bar-account .ws-name {
    display: none;
  }
  .bar .sc-island {
    padding: 0;
  }
  /* on the start screen the actions are hidden, and the account keeps the
     right edge */
  .b[data-phase='start'] .bar-account,
  .b[data-phase='intake'] .bar-account {
    margin-left: 4px;
  }
  /* the way back, on the left where the trail is */
  .back {
    color: var(--ink2);
    font-size: 13px;
    padding: 4px 12px;
    border-radius: 999px;
    white-space: nowrap;
    /* a story's title is the label: long ones stop short of the page name */
    max-width: 260px;
    overflow: hidden;
    text-overflow: ellipsis;
    transition:
      color 0.15s,
      background 0.15s;
  }
  .back:hover {
    color: var(--ink);
    background: var(--fill-06);
  }
  /* From 768px the way back reads as the trail it is: ‹ Scenes / the story.
     The mark at the far left is the same way home, as it is everywhere.
     The chevron stays beside the label: without it, a first-time eye reads
     "Scenes" after the mark as the product's name, not as a way back. On a
     phone the chevron is all there is (the trail has no room). The slash
     sits in the gap, outside the link's own hover, so it reads as the
     trail's and not as part of it. */
  @media (min-width: 768px) {
    .back {
      position: relative;
      display: inline-flex;
      align-items: center;
      gap: 2px;
      overflow: visible;
      margin-right: 6px;
      /* the chevron's own box carries air on its left: less padding there
         keeps the pill's two ends looking even */
      padding-left: 8px;
    }
    .back-chev {
      display: inline-flex;
      flex: none;
    }
    .back-chev svg {
      width: 16px;
      height: 16px;
    }
    .back-label {
      min-width: 0;
      /* the slash below needs the button's overflow, so a long story title
         (the pack's way back) ends in an ellipsis inside the label instead */
      display: block;
      overflow: hidden;
      text-overflow: ellipsis;
    }
    .back::after {
      content: '/';
      position: absolute;
      right: -12px;
      top: 50%;
      transform: translateY(-52%);
      color: var(--ink3);
      opacity: 0.7;
      pointer-events: none;
    }
    .b[data-phase='intake'] .back::after {
      content: none;
    }
  }
  .b[data-phase='start'] .bar-actions,
  .b[data-phase='start'] .bar .status,
  .b[data-phase='start'] .story-title,
  .b[data-phase='start'] .crumb,
  .b[data-phase='intake'] .bar-actions,
  .b[data-phase='intake'] .story-title,
  .b[data-phase='intake'] .crumb {
    display: none;
  }

  /* ----------------------------------------------------------------- chat */

  .chat {
    grid-area: chat;
    display: flex;
    flex-direction: column;
    min-height: 0;
    position: relative;
  }
  .b[data-phase='build'] .chat {
    border-right: 1px solid var(--rule);
    background: var(--bg2);
  }
  .thread {
    flex: 1;
    overflow: auto;
    padding: 24px 20px 12px;
    display: flex;
    flex-direction: column;
    gap: 18px;
    /*
     * Deliberately not `scroll-behavior: smooth`. The thread follows the reply
     * as it is written, and an animation that restarts on every delta never
     * arrives — it reads as lag, and a backgrounded tab never finishes it at all.
     */
    overflow-anchor: none;
    /*
     * A wheel that reaches the end of the thread stops there. Without this
     * the scroll chains to whatever holds the creator — in the CMS, the
     * dashboard the layer is covering — and the page behind moves instead.
     */
    overscroll-behavior: contain;
  }
  .b:not([data-phase='build']) .thread {
    padding: 32px max(20px, calc(50% - 360px)) 12px;
  }
  /* The start page is pinned from the top, not centred: centring moved the
     greeting and the composer up the moment the Scenes arrived under them,
     and everything jumped. The greeting sits at one height whether there
     are Scenes below it or not; the Scenes fade in beneath. */
  .b[data-phase='start'] .thread {
    justify-content: flex-start;
    gap: 0;
    flex: none;
    overflow: visible;
    padding-top: clamp(40px, 10vh, 108px);
    padding-bottom: 8px;
  }
  .b[data-phase='start'] .chat {
    justify-content: flex-start;
    overflow-y: auto;
  }
  /* on the way in: the greeting, then the composer, then the Scenes — each a
     beat behind the last, none of them moving what came before */
  .b[data-phase='start'] .hero {
    animation: rise-in 0.55s cubic-bezier(0.2, 0.7, 0.2, 1) both;
  }
  .b[data-phase='start'] .composer {
    animation: rise-in 0.55s cubic-bezier(0.2, 0.7, 0.2, 1) 0.12s both;
  }
  .b[data-phase='start'] .stories {
    animation: fade-in 0.5s ease-out 0.25s both;
  }
  @media (prefers-reduced-motion: reduce) {
    .b[data-phase='start'] .hero,
    .b[data-phase='start'] .composer,
    .b[data-phase='start'] .stories {
      animation: none;
    }
  }

  .hero {
    display: flex;
    flex-direction: column;
    gap: 10px;
    text-align: center;
    align-items: center;
    padding-bottom: 22px;
  }
  .hero h1 {
    font-family: var(--display);
    font-weight: 500;
    font-size: clamp(30px, 4.5vw, 44px);
    letter-spacing: -0.03em;
    line-height: 1;
    margin: 0;
    text-wrap: balance;
  }
  .hero p em {
    font-style: italic;
    color: var(--ink3);
  }
  .hero p {
    margin: 0;
    color: var(--ink2);
    font-size: 15px;
    max-width: 30em;
    /* three even lines rather than two and a stub: the definition at the
       end must not be left hanging on a line of its own */
    text-wrap: balance;
  }

  .msg {
    display: flex;
    flex-direction: column;
    gap: 8px;
    max-width: 100%;
    animation: up 0.3s ease both;
  }
  /* One breath, or a turn of its own. A message that carries on from the
     one before it — same side, close in time — sits with it; a turn that
     changes hands gets the air. The same rule holds the name and the hour
     back, so the spacing and the label never disagree. */
  .msg.on {
    margin-top: -10px;
  }

  .msg.user {
    align-items: flex-end;
  }
  .msg.user .bubble {
    background: var(--fill-07);
    border: 1px solid var(--rule);
    border-radius: 16px 16px 4px 16px;
    padding: 10px 14px;
    max-width: min(560px, 92%);
    font-size: 14.5px;
    line-height: 22px;
    white-space: pre-wrap;
  }
  /* Answers to a set of questions, each beside the number of the question
     it answers: the number set as it is on the question sheet, in the
     gutter, and the words after it */
  .msg.user .bubble.answers {
    list-style: none;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: 4px;
    white-space: normal;
  }
  .bubble.answers li {
    display: flex;
    align-items: flex-start;
    gap: 10px;
  }
  .bubble.answers li.more {
    margin-top: -2px;
  }
  .bubble.answers .an {
    flex: none;
    width: 10px;
    font-size: 12px;
    font-weight: 500;
    font-variant-numeric: tabular-nums;
    text-align: center;
    color: var(--ink3);
  }
  /* picked rather than written: the same list, in the quieter voice a
     picked option has */
  .msg.user.act .bubble.answers {
    background: var(--fill-04);
    color: var(--ink2);
    font-size: 13.5px;
    line-height: 20px;
  }
  /* What they picked, quoted back. The words of the option and nothing
     else: no tick, no fill. A choice is a quieter thing than a sentence
     they wrote, and it used to be the loudest thing on the page. */
  .msg.user.act {
    flex-direction: row;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: 6px;
    max-width: 92%;
    align-self: flex-end;
  }
  .msg.user.act .chip {
    display: inline-flex;
    align-items: center;
    font-size: 13px;
    line-height: 1.4;
    padding: 4px 11px;
    border-radius: 999px;
    border: 1px solid var(--rule);
    background: var(--fill-04);
    color: var(--ink2);
    text-align: left;
  }
  /* An answer picked with one press, in the moment before it goes: the
     way back, quiet, on the side the eye comes from */
  .msg.user.act .change-answer {
    align-self: center;
    padding: 0 2px;
    border: 0;
    background: none;
    font: inherit;
    font-size: 12.5px;
    color: var(--ink3);
    cursor: pointer;
    text-decoration: underline;
    text-decoration-color: color-mix(in srgb, currentColor 35%, transparent);
    text-underline-offset: 3px;
    animation: fade-in 0.2s ease both;
  }
  .msg.user.act .change-answer:hover {
    color: var(--ink);
  }
  /* The hold, seen: a hairline under the word that runs out as the answer
     goes (`--hold` is CHANGE_MS, set beside it). Without it the five
     seconds read as the page hanging. */
  /* The underline IS the timer: it runs out as the answer goes. Two lines
     under one word (the link's underline and a bar below it) read as a
     rendering fault, so the link's own underline steps aside while the
     hairline drains in its place, and comes back where motion is off. */
  .msg.user.act .change-answer {
    position: relative;
    text-decoration: none;
  }
  .msg.user.act .change-answer::after {
    content: '';
    position: absolute;
    left: 2px;
    right: 2px;
    bottom: -1px;
    height: 1px;
    background: currentColor;
    opacity: 0.45;
    transform-origin: left center;
    animation: change-drain var(--hold, 5s) linear both;
  }
  @media (prefers-reduced-motion: reduce) {
    .msg.user.act .change-answer {
      text-decoration: underline;
    }
    .msg.user.act .change-answer::after {
      display: none;
    }
  }
  /* Something done on a card the chat put up: what was done, quietly, on
     their side — and a photo chosen, small, above it, in the frame the
     card showed it in */
  .msg.user.action .card-action {
    margin: 0;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 4px;
  }
  .msg.user.action .card-action a,
  .msg.user.action .card-action-gone {
    display: block;
    width: 168px;
    aspect-ratio: 16 / 10;
    border: 1px solid var(--rule);
    border-radius: 12px 12px 4px 12px;
    overflow: hidden;
    background: var(--fill-05);
  }
  .msg.user.action .card-action img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    animation: fade-in 0.2s ease both;
  }
  .msg.user.action figcaption {
    font-size: 12px;
    line-height: 1.4;
    color: var(--ink3);
    text-align: right;
  }
  /* Something done to the story, not said: a line in the margin between
     two turns. Reading back, these are the decisions, and the eye finds
     them because nothing else on the page is shaped like this. */
  .msg.done {
    align-items: center;
    margin: 2px 0;
  }
  .msg.done button.done-line {
    border: 0;
    background: none;
    /* the family only: \`font: inherit\` took the thread's size back, and
       the one line a press away read larger than the lines beside it */
    font-family: inherit;
    cursor: pointer;
  }
  .msg.done button.done-line:hover {
    color: var(--ink2);
  }
  .msg.done .done-line {
    display: inline-flex;
    align-items: baseline;
    gap: 8px;
    max-width: 100%;
    padding: 0 10px;
    font-size: 12px;
    line-height: 1.5;
    color: var(--ink3);
    text-align: center;
  }
  /* The time never breaks: a long line squeezed it to "2:4 / 1 PM" on a
     phone. The words take the squeeze instead, wrapping in their own box. */
  .msg.done .done-line small {
    flex: none;
    white-space: nowrap;
    font-size: 11px;
    opacity: 0.7;
  }
  .msg.done .done-line .words {
    min-width: 0;
    overflow-wrap: anywhere;
  }
  .msg.done .done-line::before,
  .msg.done .done-line::after {
    content: '';
    flex: 1 0 18px;
    height: 1px;
    align-self: center;
    background: var(--rule);
  }
  /* a draft landed: one quiet line, the draft itself is on the right */
  .msg.agent .bubble.drafted {
    font-size: 13px;
    color: var(--ink3);
  }
  /* One edge for everything the agent says. The rule is there on every
     message and coloured on the one that wants an answer, so a question
     lights up where it stands rather than stepping in from the others. */
  .msg.agent .bubble {
    font-size: 14.5px;
    line-height: 22px;
    max-width: min(640px, 100%);
    color: var(--ink);
    border-left: 1px solid transparent;
    padding-left: 13px;
    margin-left: 1px;
  }
  .msg.agent .bubble p {
    margin: 0 0 10px;
  }
  .msg.agent .bubble p:last-child {
    margin: 0;
  }
  .msg.agent .bubble b,
  .msg.agent .bubble strong {
    font-weight: 500;
    color: var(--ink);
  }
  /* A gap the material did not fill, shown where it falls in the sentence. */
  /* a fact the material does not give, named where the part needs it:
     the same mark as on the story line */
  /* Words, not a pill: a gap is part of the sentence it falls in, so it
     keeps the sentence's size and flow and only reads as not yet settled —
     quieter ink on the dashed line the cards use for the same thing. A
     pill round a whole phrase broke the paragraph into lozenges. */
  .msg.agent .bubble .needs {
    color: var(--ink3);
    border-bottom: 1px dashed var(--rule);
    cursor: help;
  }
  .msg.agent .bubble code {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 0.9em;
    color: var(--ink2);
  }
  /* the one word that says a reply was an offered answer, clicked */
  .msg .meta .meta-verb {
    font-style: normal;
    color: var(--ink3);
  }
  /* who and when: the person's name over theirs, a mark over what came
     back — it is the only other voice here and needs no name */
  .msg .meta {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-bottom: 4px;
    font-size: 11.5px;
    color: var(--ink3);
    font-variant-numeric: tabular-nums;
  }
  .msg.user .meta {
    justify-content: flex-end;
    width: 100%;
    margin-right: 4px;
  }
  /* The dot that marks the agent's turn. It was in the accent, which put
     the loudest colour on the page twenty-four times over — once per
     message — while the one thing that wants an answer wore it once. The
     dot keeps the rhythm; the colour goes where the meaning is. */
  .msg .meta i {
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: var(--ink3);
    opacity: 0.55;
    flex: none;
  }
  /* a turn that is asking: the dot says so, with the rule down its side */
  .msg.agent:has(.bubble.asked) .meta i {
    background: var(--accent);
    opacity: 1;
  }
  .msg.sys {
    align-items: center;
  }
  .msg.sys .bubble {
    font-size: 12px;
    color: var(--ink3);
    text-align: center;
  }
  /* The agent's own lines after a reply (what it read, what is missing,
     what it marked, what it changed) are its words, not an event between
     turns: they sit on its text's edge, a footnote close under the reply,
     and wrap there. Their numbers and "Stick to my material" keep one left
     edge to be found by, which a centred line moved on every wrap. Only
     what the person did to the story ("Moved card 3 after card 1") stays
     centred: that is an event between turns, owned by nobody. */
  .msg.sys:not(.failed) {
    align-items: flex-start;
  }
  .msg.sys:not(.failed) > .bubble,
  .msg.sys > .missing-line,
  .msg.sys > .still-open,
  .msg.sys > .srcs {
    margin-left: 15px;
    max-width: min(560px, calc(100% - 15px));
    text-align: left;
  }
  .msg.sys:not(.failed) > .bubble {
    padding: 0;
    background: none;
    border: 0;
  }
  /* a footnote: close under what it notes, the turn's air after it */
  .msg.agent + .msg.sys:not(.failed),
  .msg.worked + .msg.sys:not(.failed),
  .msg.sys:not(.failed) + .msg.sys:not(.failed) {
    margin-top: -8px;
  }

  .file {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    background: var(--fill-05);
    border: 1px solid var(--rule);
    border-radius: 10px;
    padding: 8px 12px;
    font-size: 13px;
  }
  .file b {
    font-weight: 500;
  }
  .file small {
    color: var(--ink3);
  }
  .file .ft {
    width: 26px;
    height: 30px;
    border-radius: 5px;
    /* the kind is written on it: one quiet square for every kind, as the
       link beside it has, rather than one red that made a sheet look like
       a PDF */
    background: var(--fill-1);
    color: var(--ink2);
    font-size: 8.5px;
    font-weight: 700;
    display: inline-flex;
    align-items: flex-end;
    justify-content: center;
    padding-bottom: 3px;
    letter-spacing: 0.04em;
    flex: none;
  }
  /* a link handed over in the chat: the file card's shape, with the link's
     glyph in a quiet square where a file shows its kind */
  a.file.link {
    color: var(--ink);
    text-decoration: none;
    transition: border-color 0.15s;
  }
  a.file.link:hover {
    border-color: var(--rule-strong);
  }
  .file.link .ft {
    height: 26px;
    background: var(--fill-1);
    color: var(--ink2);
    font-size: 12px;
    font-weight: 400;
    align-items: center;
    padding: 0;
  }

  /* The live activity line: what the agent is doing, in its own words. */
  /* Where the work has got to: what it is doing now, what that step is for,
     what it has already been through, and which of the four phases that is.
     A turn is a minute or two of waiting, and one line that changes now and
     then cannot say whether anything is moving. */
  .doing-now {
    display: flex;
    align-items: flex-start;
    gap: 10px;
  }
  /* The words themselves are what moves: a light passes along them, slowly,
     the way the card being written is lit. Nothing blinks, and nothing
     beside the words competes with them. */
  .shimmer {
    background: linear-gradient(
      90deg,
      var(--ink2) 0%,
      var(--ink2) 38%,
      var(--ink) 50%,
      var(--ink2) 62%,
      var(--ink2) 100%
    );
    background-size: 220% 100%;
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    animation: sheen var(--work-sweep) linear infinite;
  }
  @media (prefers-reduced-motion: reduce) {
    .shimmer {
      animation: none;
      background: none;
      color: var(--ink2);
    }
  }
  /* the clock, and the way to the steps behind it */
  .doing-more {
    flex: none;
    display: inline-flex;
    align-items: center;
    gap: 3px;
    margin: 1px -4px 0 auto;
    padding: 1px 4px;
    border-radius: 6px;
    color: var(--ink3);
    cursor: pointer;
    transition:
      color 0.15s,
      background-color 0.15s;
  }
  .doing-more:hover,
  .doing-more:focus-visible {
    color: var(--ink2);
    background: var(--fill-04);
  }
  .doing .doing-more small {
    margin: 0;
    align-self: center;
  }
  .doing-more svg,
  .worked-line svg {
    transition: transform 0.18s cubic-bezier(0.2, 0.7, 0.2, 1);
  }
  .doing-more[aria-expanded='true'] svg,
  .worked-line[aria-expanded='true'] svg {
    transform: rotate(90deg);
  }
  .doing-detail {
    animation: ease-in-line 0.18s ease-out both;
  }
  .doing-done {
    list-style: none;
    margin: 8px 0 0;
    padding: 0 0 0 16px;
    display: flex;
    flex-direction: column;
    gap: 3px;
  }
  .doing-done li {
    display: flex;
    align-items: baseline;
    gap: 8px;
    font-size: 12.5px;
    color: var(--ink3);
  }
  .doing-done li::before {
    content: '✓';
    color: var(--ok);
    font-size: 11px;
  }
  .doing-done li span {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .doing-done li small {
    margin-left: auto;
    font-variant-numeric: tabular-nums;
    opacity: 0.8;
  }
  .doing-phases {
    list-style: none;
    display: flex;
    flex-wrap: wrap;
    gap: 4px 10px;
    margin: 10px 0 0;
    padding: 0 0 0 16px;
    font-size: 11px;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--ink3);
  }
  .doing-phases li {
    opacity: 0.45;
  }
  .doing-phases li.at {
    opacity: 1;
    color: var(--accent);
  }
  /* what it is doing, and what that part is for */
  .doing-what {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
  }
  .doing-what em {
    font-style: normal;
    font-size: 12.5px;
    color: var(--ink3);
    line-height: 1.45;
  }
  /* the card the line is about, as a way to it: the same words in the
     same place, underlined only when the pointer is on them */
  .doing-what .doing-card {
    align-self: flex-start;
    padding: 0;
    border: 0;
    background: none;
    font: inherit;
    color: inherit;
    text-align: left;
    cursor: pointer;
    text-decoration: underline;
    text-decoration-color: transparent;
    text-underline-offset: 3px;
    transition: text-decoration-color 0.15s;
  }
  .doing-what .doing-card:hover,
  .doing-what .doing-card:focus-visible {
    text-decoration-color: color-mix(in srgb, currentColor 55%, transparent);
  }
  /* "card 3" in a reply: the words as written, a hairline under them */
  .card-ref {
    display: inline;
    padding: 0;
    border: 0;
    background: none;
    font: inherit;
    color: inherit;
    cursor: pointer;
    text-decoration: underline;
    text-decoration-color: color-mix(in srgb, currentColor 30%, transparent);
    text-underline-offset: 3px;
    transition: text-decoration-color 0.15s;
  }
  .card-ref:hover,
  .card-ref:focus-visible {
    text-decoration-color: currentColor;
  }
  /* the cards still short of something: one quiet line, each number a way
     to its card */
  .missing-line {
    margin: 0;
    font-size: 12.5px;
    line-height: 20px;
    color: var(--ink2);
    text-wrap: pretty;
  }
  .missing-line button {
    padding: 0;
    border: 0;
    background: none;
    font: inherit;
    color: var(--ink);
    cursor: pointer;
    text-decoration: underline;
    text-decoration-color: color-mix(in srgb, currentColor 35%, transparent);
    text-underline-offset: 3px;
    transition: text-decoration-color 0.15s;
  }
  .missing-line button:hover,
  .missing-line button:focus-visible {
    text-decoration-color: currentColor;
  }
  .missing-line .keep {
    white-space: nowrap;
  }
  .missing-line button:disabled {
    color: var(--ink3);
    cursor: default;
    text-decoration-color: transparent;
  }

  /* What a turn left open, section by section: a footnote with a hairline
     on its edge, each section named over what it is short of. Quieter than
     the check's panel: it is a list to come back to, not a finding. */
  .still-open {
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 2px 0 2px 12px;
    border-left: 1px solid var(--rule);
  }
  .still-open section {
    display: flex;
    flex-direction: column;
    gap: 4px;
  }
  .still-open-of {
    margin: 0;
    font-size: 12.5px;
    line-height: 20px;
    color: var(--ink2);
    text-wrap: pretty;
  }
  .still-open-of button,
  .still-open-more {
    padding: 0;
    border: 0;
    background: none;
    font: inherit;
    color: var(--ink);
    cursor: pointer;
    text-decoration: underline;
    text-decoration-color: color-mix(in srgb, currentColor 35%, transparent);
    text-underline-offset: 3px;
    transition: text-decoration-color 0.15s;
  }
  .still-open-of button:hover,
  .still-open-of button:focus-visible,
  .still-open-more:hover,
  .still-open-more:focus-visible {
    text-decoration-color: currentColor;
  }
  .still-open ul {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 3px;
  }
  .still-open li {
    position: relative;
    padding-left: 14px;
    font-size: 14px;
    line-height: 1.5;
    color: var(--ink);
    text-wrap: pretty;
  }
  .still-open li::before {
    content: '';
    position: absolute;
    left: 2px;
    top: calc(0.75em - 2px);
    width: 4px;
    height: 4px;
    border-radius: 50%;
    background: var(--ink3);
  }
  .still-open-more {
    align-self: flex-start;
    font-size: 12.5px;
    line-height: 20px;
    color: var(--ink2);
  }

  /* What is happening, what the step is for, what is already behind it, and
     which phase that is: four things down the page. It was a row, which is
     what it had to be when it held one line — and left the dot, the words
     and the count strung across each other once it held more. */
  .doing {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    font-size: 13.5px;
    color: var(--ink2);
    animation: ease-in-line 0.25s 0.3s both;
  }
  .doing-now > i {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--accent);
    animation: pulse var(--work-beat) ease-in-out infinite;
    flex: none;
    /* on the first line's own middle, wherever the lines under it go */
    margin-top: 7px;
  }
  .doing-now > small {
    margin-left: auto;
  }
  .doing small {
    align-self: flex-start;
    margin-top: 2px;
    font-size: 12px;
    color: var(--ink3);
    font-variant-numeric: tabular-nums;
    opacity: 0.8;
  }
  /* What a turn that took a while leaves behind: one quiet line, the
     length of it, that opens onto the steps. Set in the working line's own
     type and indent, because it is that line, finished. */
  .msg.worked {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    margin-top: 2px;
    font-size: 12.5px;
    color: var(--ink3);
  }
  .msg.worked + .msg.agent {
    margin-top: 6px;
  }
  .worked-line {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    margin-left: -6px;
    padding: 2px 6px;
    border-radius: 6px;
    font-variant-numeric: tabular-nums;
  }
  button.worked-line {
    cursor: pointer;
    transition:
      color 0.15s,
      background-color 0.15s;
  }
  button.worked-line:hover,
  button.worked-line:focus-visible {
    color: var(--ink2);
    background: var(--fill-04);
  }
  .msg.worked .doing-done {
    align-self: stretch;
    margin-top: 4px;
    animation: ease-in-line 0.18s ease-out both;
  }

  /* A turn that failed: said plainly, with the way back beside it.
     No amber. On this page amber means the story is short of something,
     and a turn that did not come back is not that — nothing is missing
     from the story, the story is exactly as it was. It is a sheet like
     any other, with the words carrying it. */
  /* in the agent's column, where its answer would have been */
  .msg.sys.failed {
    align-items: flex-start;
  }
  .msg.sys.failed .bubble {
    display: inline-flex;
    align-items: center;
    gap: 12px;
    font-size: 13.5px;
    line-height: 20px;
    color: var(--ink2);
    text-align: left;
    padding: 9px 10px 9px 14px;
    border-radius: 12px;
    border: 1px solid var(--rule);
    background: var(--fill-03);
  }
  /* the way back, set as the quiet ways out beside Send are: its words and
     the circular arrow every "again" carries, no pill of its own */
  .msg.sys.failed .bubble button {
    flex: none;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: 28px;
    margin: -4px -4px -4px 0;
    padding: 0 10px;
    border: 0;
    border-radius: 999px;
    background: none;
    font: inherit;
    font-size: 13px;
    color: var(--ink);
    cursor: pointer;
    transition:
      color 0.15s,
      background 0.15s;
  }
  .msg.sys.failed .bubble button:hover {
    background: var(--fill-06);
  }

  /* the question, asked in the composer: its options over the field.
     It opens and closes in a <Reveal> (`.ask-room`), so the rule between
     it and the field is its own, and leaves with it: on the line, it
     stayed a frame after the question had gone. */
  .composer .ask {
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 6px 0 8px;
    margin-bottom: 10px;
    border-bottom: 1px solid var(--rule2);
    transition: opacity 0.2s ease;
  }
  /* closed, the room takes no gap from the box either */
  .box > .ask-room {
    margin-bottom: -4px;
  }
  /* the question is still being said: its answers hold their room, unseen */
  .composer .ask.held {
    opacity: 0;
  }
  /* what "this" is: in the tools row, which is there anyway, so it takes
     no room; the words shrink to fit, never the row */
  .composer .tools .about {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    min-width: 0;
    max-width: 60%;
    margin-right: auto;
    padding: 0 4px 0 8px;
    height: 24px;
    border-radius: 999px;
    background: var(--fill-06);
    font-size: 12px;
    color: var(--ink2);
  }
  .composer .tools .about > span {
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
  }
  .composer .tools .about button {
    display: inline-grid;
    place-items: center;
    width: 18px;
    height: 18px;
    border: 0;
    border-radius: 999px;
    background: none;
    color: var(--ink3);
    cursor: pointer;
  }
  .composer .tools .about button:hover {
    color: var(--ink);
    background: var(--fill-06);
  }
  /* spent: the section is still open but the next message is not about it
     until pressed — an outline, no fill, the quietest ink */
  .composer .tools .about.spent {
    padding: 0 8px;
    border: 0;
    background: none;
    box-shadow: inset 0 0 0 1px var(--rule);
    color: var(--ink3);
    font: inherit;
    font-size: 12px;
    cursor: pointer;
    transition:
      color 0.15s,
      background 0.15s;
  }
  .composer .tools .about.spent:hover {
    color: var(--ink2);
    background: var(--fill-06);
  }
  .composer .ask .fields {
    display: flex;
    flex-direction: column;
    gap: 10px;
  }
  /* The options never take the conversation's room: in the split view's
     narrow column each wraps to a row of its own, and a long list would
     leave the question they answer a line or two above them. Past this
     they scroll, inside the box. */
  .composer .ask .fields,
  .composer .ask .qsteps {
    max-height: 32vh;
    overflow-y: auto;
    overscroll-behavior: contain;
    /* room for a focused option's ring, which the scroll box would clip */
    margin: -2px;
    padding: 2px;
  }
  /* a set of questions: only the step being answered is drawn, and it
     comes in rather than snapping */
  .composer .ask .qsteps > .qstep {
    display: flex;
    flex-direction: column;
    gap: 8px;
    animation: qstep-in 0.22s cubic-bezier(0.2, 0.7, 0.2, 1);
  }
  /* the question on screen heads the box; where it sits and the way either
     side of it on the right, quiet until wanted */
  .composer .ask .step-head {
    display: flex;
    align-items: flex-start;
    gap: 12px;
  }
  .composer .ask .step-q {
    flex: 1;
    min-width: 0;
    margin: 0;
    padding-top: 2px;
    font-size: 13.5px;
    line-height: 20px;
    font-weight: 500;
    color: var(--ink);
    animation: qstep-in 0.22s cubic-bezier(0.2, 0.7, 0.2, 1);
  }
  .composer .ask .step-nav {
    flex: none;
    display: inline-flex;
    align-items: center;
    gap: 2px;
    margin-right: -4px;
  }
  .composer .ask .step-count {
    margin-right: 6px;
    font-size: 12px;
    font-variant-numeric: tabular-nums;
    color: var(--ink3);
  }
  .composer .ask .step-arrow {
    display: inline-grid;
    place-items: center;
    width: 24px;
    height: 24px;
    padding: 0;
    border: 0;
    border-radius: 6px;
    background: none;
    color: var(--ink2);
    cursor: pointer;
    transition:
      color 0.15s,
      background 0.15s;
  }
  .composer .ask .step-arrow.back svg {
    transform: scaleX(-1);
  }
  .composer .ask .step-arrow:hover:not(:disabled) {
    color: var(--ink);
    background: var(--fill-06);
  }
  .composer .ask .step-arrow:disabled {
    color: var(--ink3);
    opacity: 0.4;
    cursor: default;
  }
  .composer .ask .field label {
    font-size: 13px;
    color: var(--ink2);
  }
  .composer .ask .chips button {
    background: var(--fill-06);
    border-color: transparent;
  }
  .composer .ask .chips button.on {
    border-color: var(--accent);
    background: var(--accent-soft);
  }
  /* the box holds the options and the field as one surface */
  .box .line {
    display: flex;
    align-items: flex-end;
    gap: 6px;
    min-width: 0;
  }
  .composer .ask .cost {
    margin: 0;
    font-size: 12.5px;
    color: var(--warn);
  }
  /* the ways out of a request for content — give a picture, say there is
     none — sit by Send as the other way of finishing: quiet text, and the
     one that IS the way on (anything else? the doubts) filled */
  .composer .tools .skip {
    display: flex;
    align-items: center;
    gap: 4px;
    margin-left: auto;
    margin-right: 6px;
    transition: opacity 0.2s ease;
  }
  .composer .tools .skip.held {
    opacity: 0;
  }
  .composer .tools .skip + .send {
    margin-left: 0;
  }
  .composer .tools .skip button {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: 30px;
    padding: 0 12px;
    border: 0;
    border-radius: 999px;
    background: none;
    font: inherit;
    font-size: 13px;
    color: var(--ink2);
    cursor: pointer;
    transition:
      color 0.15s,
      background 0.15s;
  }
  .composer .tools .skip button:hover {
    color: var(--ink);
    background: var(--fill-06);
  }
  .composer .tools .skip button.go {
    color: var(--bg);
    background: var(--ink);
    font-weight: 500;
  }
  .composer .tools .skip button.go:hover {
    color: var(--bg);
    background: var(--ink2);
  }
  /* the picture given as the answer */
  .box .picture {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 4px 0 8px;
    font-size: 13px;
    color: var(--ink2);
  }
  .box .picture img {
    width: 56px;
    height: 40px;
    object-fit: cover;
    border-radius: 4px;
    background: var(--fill-06);
  }
  .box .picture span {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .box .picture button {
    color: var(--ink3);
    padding: 4px;
  }
  /* a figure or a name: a short field, the unit written after it */
  .box input.short {
    flex: 1;
    min-width: 0;
    background: none;
    border: 0;
    color: var(--ink);
    font: inherit;
    font-size: 15px;
    padding: 6px 0;
    outline: none;
  }
  .box input.short::placeholder {
    color: var(--ink3);
  }
  .box input.num {
    font-variant-numeric: tabular-nums;
  }
  .box .unit {
    align-self: center;
    color: var(--ink2);
    font-size: 14px;
    padding-right: 4px;
    white-space: nowrap;
  }
  /* the question as it reads back in the transcript */
  /* The one message that wants something from you. What it asks sits on
     a quiet sheet under what it says — one row for one question, a
     numbered row each for several — and the sheet is the whole of how it
     says "here": no accent down the edge, which shouted the explanation
     along with the question. The accent is kept for where you are: the
     dot by the time, the number being answered. */
  .bubble.asked p {
    margin: 0;
  }
  /* asked and answered: what was asked, set back, so the answer under it
     carries the weight */
  .msg.agent .bubble .q {
    margin: 6px 0 0;
    color: var(--ink2);
  }
  .msg.agent .bubble .qs-was {
    list-style: none;
    margin: 8px 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
  }
  .msg.agent .bubble .qs-was .q {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    margin: 0;
  }
  .msg.agent .bubble .qs-was .an {
    flex: none;
    width: 10px;
    font-size: 12px;
    font-weight: 500;
    font-variant-numeric: tabular-nums;
    text-align: center;
    color: var(--ink3);
  }
  .msg.agent .bubble.asked > .q {
    margin-top: 8px;
    color: var(--ink);
    font-weight: 500;
  }
  .bubble.asked .qs {
    list-style: none;
    max-width: 560px;
    margin: 14px 0 0;
    padding: 4px;
    display: flex;
    flex-direction: column;
    border-radius: 12px;
    background: var(--fill-03);
    box-shadow: inset 0 0 0 1px var(--rule);
    animation: qstep-in 0.28s cubic-bezier(0.2, 0.7, 0.2, 1);
  }
  .bubble.asked .qs .q {
    margin: 0;
  }
  /* asked on a card: the row is the way there */
  /* the question, and under it the way to the card: side by side, the
     link took the row's right side and pushed a short question onto two
     lines in the split view's column */
  .bubble.asked .qs .to-card {
    flex-direction: column;
    align-items: flex-start;
    gap: 2px;
    color: var(--ink);
  }
  .bubble.asked .qs .q-go {
    flex: none;
    display: inline-flex;
    align-items: center;
    gap: 2px;
    font-size: 12.5px;
    color: var(--ink3);
    transition: color 0.15s;
  }
  .bubble.asked .qs .to-card:hover .q-go {
    color: var(--ink);
  }
  .bubble.asked .qs button {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    width: 100%;
    padding: 9px 12px;
    border: 0;
    border-radius: 8px;
    background: none;
    font: inherit;
    line-height: 22px;
    font-weight: 500;
    text-align: left;
    color: var(--ink2);
    cursor: pointer;
    transition:
      color 0.15s,
      background 0.15s;
  }
  .bubble.asked .qs button:hover {
    background: var(--fill-03);
    color: var(--ink);
  }
  /* the number is set as type, not drawn as a badge: a small tabular
     numeral in the gutter, the current one in the accent, a given one
     swapped for a check */
  .bubble.asked .qs .qn {
    flex: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 14px;
    height: 22px;
    font-size: 12px;
    font-weight: 500;
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.02em;
    color: var(--ink3);
    transition: color 0.2s;
  }
  .bubble.asked .qs .q.on button {
    color: var(--ink);
    background: var(--fill-06);
  }
  .bubble.asked .qs .q.on .qn {
    color: var(--accent);
    font-weight: 600;
  }
  .bubble.asked .qs .q.done button {
    color: var(--ink3);
  }
  .bubble.asked .qs .q.done .qn {
    color: var(--ok);
  }

  /* ------------------------------------------------------ forms in chat */

  .form {
    display: flex;
    flex-direction: column;
    gap: 12px;
    background: var(--fill-03);
    border: 1px solid var(--rule);
    border-radius: 14px;
    padding: 14px;
    max-width: min(640px, 100%);
  }
  .form.done {
    opacity: 0.55;
    pointer-events: none;
  }
  .form .reading {
    font-size: 13px;
    color: var(--ink2);
    margin: 0;
  }
  .form .cost {
    font-size: 12px;
    color: var(--warn);
    margin: 0;
  }
  .field {
    display: flex;
    flex-direction: column;
    gap: 6px;
  }
  .field label {
    font-size: 13px;
    color: var(--ink2);
    line-height: 1.45;
  }
  /* Says the ticks are a list, not a choice of one. */
  .field label .many {
    color: var(--ink3);
    font-size: 12px;
  }
  .chips {
    display: flex;
    gap: 6px;
    flex-wrap: wrap;
  }
  .chips button {
    font-size: 13px;
    padding: 7px 12px;
    border-radius: 999px;
    border: 1px solid var(--rule);
    color: var(--ink2);
    background: var(--fill-03);
    transition:
      background 0.15s,
      color 0.15s,
      border-color 0.15s;
    min-height: 32px;
    display: inline-flex;
    align-items: center;
    gap: 6px;
  }
  .chips button:hover {
    color: var(--ink);
    border-color: var(--fill-22);
  }
  .chips button.on {
    color: var(--ink);
    border-color: var(--accent);
    background: var(--accent-soft);
  }
  /* Picked is said by colour alone — the accent edge, its tint, the words
     at full ink — never by anything that takes room. A dot put in front of
     the words made a picked chip 12px wider, so the row shifted at the
     click. */
  .form .free {
    width: 100%;
    resize: none;
    background: var(--fill-05);
    border: 1px solid var(--rule);
    border-radius: 10px;
    padding: 9px 12px;
    color: var(--ink);
    font: inherit;
    font-size: 14px;
    outline: none;
  }
  .form .free:focus {
    border-color: var(--ring);
  }
  .form .row {
    display: flex;
    gap: 8px;
    justify-content: flex-end;
    flex-wrap: wrap;
    margin-top: 2px;
  }

  .quick {
    display: flex;
    gap: 6px;
    flex-wrap: wrap;
  }
  .quick button {
    font-size: 12.5px;
    padding: 6px 11px;
    border-radius: 999px;
    border: 1px solid var(--rule);
    color: var(--ink2);
    background: var(--fill-03);
    min-height: 30px;
  }
  .quick button:hover {
    color: var(--ink);
    border-color: var(--fill-22);
  }
  .quick button.primary {
    color: var(--ink);
    border-color: var(--accent);
    background: var(--accent-soft);
  }

  /* ------------------------------------------------------------ composer */

  .composer {
    padding: 10px 20px calc(16px + env(safe-area-inset-bottom));
    display: flex;
    flex-direction: column;
    gap: 8px;
  }
  .b:not([data-phase='build']) .composer {
    padding: 10px max(20px, calc(50% - 360px))
      calc(24px + env(safe-area-inset-bottom));
  }
  .b[data-phase='start'] .composer {
    flex: 0 0 auto;
    padding-top: 0;
    margin-top: -2px;
  }
  .box {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 4px;
    background: var(--fill-05);
    border: 1px solid var(--rule);
    border-radius: 18px;
    padding: 8px 8px 4px 14px;
    transition:
      border-color 0.15s,
      box-shadow 0.15s;
  }
  /* one line in the accent, no halo round it: a halo outside a border
     reads as a second border */
  .box:focus-within {
    border-color: var(--ring);
  }
  /* the composer is focused most of the time it is on screen, so its ring
     is the accent let down into the edge rather than the full line: there,
     not shouting */
  .composer .box:focus-within {
    border-color: color-mix(in srgb, var(--ring) 45%, var(--rule-strong));
  }
  .b[data-phase='start'] .box {
    padding: 12px 10px 12px 18px;
    border-radius: 22px;
    position: relative;
  }
  /* the box draws itself once, on the way in: a light runs the border and
     settles. Nothing loops; a person who has turned motion off gets the
     box at rest. */
  .b[data-phase='start'] .box::after {
    content: '';
    position: absolute;
    inset: -1px;
    border-radius: inherit;
    padding: 1px;
    background: conic-gradient(
      from var(--sweep, 0deg),
      transparent 0 70%,
      var(--accent) 86%,
      transparent 100%
    );
    -webkit-mask:
      linear-gradient(#000 0 0) content-box,
      linear-gradient(#000 0 0);
    -webkit-mask-composite: xor;
    mask:
      linear-gradient(#000 0 0) content-box,
      linear-gradient(#000 0 0);
    mask-composite: exclude;
    pointer-events: none;
    opacity: 0;
    animation: box-sweep 2.4s cubic-bezier(0.4, 0, 0.2, 1) 0.3s 1 forwards;
  }
  @media (prefers-reduced-motion: reduce) {
    .b[data-phase='start'] .box::after {
      animation: none;
    }
  }
  /* the mic: quiet like the clip; a dot while it listens */
  .ib.mic.on {
    color: var(--accent);
    background: var(--accent-soft);
  }
  .ib.mic.on::after {
    content: '';
    position: absolute;
    right: 5px;
    top: 5px;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--accent);
    animation: mic-pulse 1.2s ease-in-out infinite;
  }
  .ib.mic {
    position: relative;
  }
  .box textarea {
    flex: 1;
    resize: none;
    background: none;
    border: 0;
    color: var(--ink);
    font: inherit;
    font-size: 15px;
    line-height: 1.45;
    max-height: 160px;
    padding: 6px 0 2px;
    outline: none;
  }
  .box textarea::placeholder {
    color: var(--ink3);
  }
  /* a fact that is a date: picked, in the field's place */
  /* the date ask: our text field, and our calendar button beside it. The
     browser's own date input stays in the DOM, sized to nothing, so its
     picker has something to open from and anchors on the button. */
  .box .pick-date {
    position: relative;
    display: inline-flex;
    align-self: center;
    flex: none;
  }
  .box .pick-date input[type='date'] {
    position: absolute;
    right: 0;
    bottom: 0;
    width: 1px;
    height: 1px;
    opacity: 0;
    pointer-events: none;
    border: 0;
    padding: 0;
    color-scheme: inherit;
  }
  /* the start page's box gives the words two lines of room from the start */
  .b[data-phase='start'] .box textarea {
    font-size: 17px;
    min-height: 50px;
  }
  /* the tools row: attach and speak on the left, pulled out by the
     button's own inset so the glyphs sit on the words' left edge; send on
     the right */
  .box .tools {
    display: flex;
    align-items: center;
    gap: 2px;
    margin-left: -8px;
  }
  .box .tools .send {
    margin-left: auto;
  }
  .ib {
    width: 34px;
    height: 34px;
    border-radius: 50%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--ink2);
    flex: none;
    transition:
      background 0.15s,
      color 0.15s;
  }
  .ib:hover {
    background: var(--fill-08);
    color: var(--ink);
  }
  .ib.send {
    background: var(--ink);
    color: var(--on-ink);
  }
  /* Pressed, the pointer stays on it — through every step of a set of
     questions — so its hover is the ink let down, not the accent: in the
     conversation the accent means where you are, and a send button is
     not that. */
  .ib.send:hover:not(:disabled) {
    background: var(--ink2);
    color: var(--on-ink);
  }
  .ib.send:disabled {
    background: var(--fill-1);
    color: var(--ink3);
  }

  /* Files staged on the composer, before they are sent. */
  .staged {
    display: flex;
    gap: 6px;
    flex-wrap: wrap;
  }
  .staged span {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    font-size: 12.5px;
    color: var(--ink2);
    background: var(--fill-05);
    border: 1px solid var(--rule);
    border-radius: 999px;
    padding: 4px 6px 4px 11px;
  }
  /* a link waiting to be sent: its glyph where a file has none */
  .staged span.link svg {
    color: var(--ink3);
    margin-right: -3px;
  }
  .staged button {
    width: 22px;
    height: 22px;
    border-radius: 50%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--ink3);
  }
  .staged button:hover {
    color: var(--ink);
    background: var(--fill-08);
  }

  .hint {
    font-size: 12px;
    color: var(--ink3);
    text-align: center;
  }
  .brand-hint {
    margin-top: 6px;
  }
  .brand-hint button {
    color: var(--ink);
    text-decoration: underline;
    text-underline-offset: 3px;
  }
  .brand-prompt {
    margin: 0 20px 8px;
    padding: 12px 14px;
    border: 1px solid var(--rule);
    border-radius: 12px;
    background: var(--fill-03);
  }
  /* The gate's own lead line, icon then words. Only its own: written as
     `.brand-prompt p` it also laid out the website question nested in the
     gate ("Is <b>site</b> your company's website?") as three flex items,
     which broke the sentence into columns and lost the space after the
     site on a phone. */
  .brand-prompt > p {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    margin: 0 0 10px;
    font-size: 13px;
    color: var(--ink);
    line-height: 1.45;
  }
  .brand-prompt > p svg {
    flex: none;
    margin-top: 2px;
    color: var(--ink3);
  }
  .vi-ready-shots {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 6px;
    margin: 0 0 10px;
  }
  .vi-ready-shots img {
    display: block;
    width: 100%;
    aspect-ratio: 16 / 9;
    object-fit: cover;
    border-radius: 6px;
    border: 1px solid var(--rule);
    background: var(--fill-03);
  }
  .b:not([data-phase='build']) .brand-prompt {
    margin: 0 max(20px, calc(50% - 360px)) 8px;
  }
  .brand-prompt .actions,
  .b:not([data-phase='build']) .brand-prompt .actions {
    padding: 0;
  }
  .b:not([data-phase='start']) .hint {
    display: none;
  }
  /* ...but not the brand's line: App.tsx draws it while the story is being
     built, which is when it matters (a build that failed, things to
     confirm), and this rule had been hiding it there all along */
  .b:not([data-phase='start']) .hint.brand-hint {
    display: block;
    margin-bottom: 10px;
  }
  .starters {
    display: flex;
    gap: 6px;
    flex-wrap: wrap;
    justify-content: center;
  }
  .b:not([data-phase='start']) .starters {
    display: none;
  }
  .starters button {
    font-size: 13px;
    padding: 7px 12px;
    border-radius: 999px;
    border: 1px solid var(--rule);
    color: var(--ink2);
    background: transparent;
    min-height: 32px;
  }
  .starters button:hover {
    color: var(--ink);
    border-color: var(--fill-22);
  }
  /* Wrapped by chance on a phone, four chips fall three and one, and the one
     reads as left behind. Two by two, each pair the same width, they read
     as a set whatever the screen's font makes of their words. */
  @media (max-width: 599px) {
    .starters {
      display: grid;
      grid-template-columns: repeat(2, minmax(0, 1fr));
      gap: 8px;
    }
    .starters button {
      min-height: 40px;
      padding: 8px 10px;
      line-height: 1.25;
      text-wrap: balance;
    }
  }

  /* Files dragged over the page: the page dims, and one quiet card says
     what letting go does and what it takes. */
  .dropveil {
    position: fixed;
    inset: 0;
    z-index: 60;
    background: var(--veil);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 16px;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.18s ease-out;
  }
  .dropveil.on {
    opacity: 1;
  }
  .dropcard {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    min-width: min(360px, 100%);
    padding: 28px 32px;
    border-radius: 16px;
    background: var(--panel);
    box-shadow:
      0 0 0 1px var(--rule),
      0 24px 60px rgb(0 0 0 / 0.28);
    text-align: center;
    transform: translateY(6px) scale(0.98);
    transition: transform 0.22s cubic-bezier(0.2, 0.8, 0.2, 1);
  }
  .dropveil.on .dropcard {
    transform: none;
  }
  .dropcard svg {
    margin-bottom: 6px;
    color: var(--ink2);
  }
  .dropcard b {
    font-size: 16px;
    font-weight: 500;
    color: var(--ink);
  }
  .dropcard span {
    font-size: 13px;
    color: var(--ink3);
  }

  /* ---------------------------------------------------------------- stage */
  /*
   * The right-hand half of the split. It shows the story while the story is
   * being made, and the deck once the story is settled — never both, because
   * only one of them is the thing being worked on.
   */

  .stage {
    grid-area: stage;
    display: none;
    flex-direction: column;
    min-height: 0;
    background: var(--bg);
  }
  .b[data-phase='build'] .stage {
    display: flex;
  }

  .stage-head {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 24px;
    border-bottom: 1px solid var(--rule);
    font-size: 12px;
    color: var(--ink3);
    flex: none;
    /* the height of its tallest occupant, the narrator on the slides, held
       in every state: the head no longer grows when the deck arrives or
       its voice button comes */
    min-height: 51px;
  }
  .stage-head .sp {
    flex: 1;
  }
  /* the next step, in the head of the column: small enough that the head
     is the same height with it or without it. Quiet: the filled "Make the
     deck" is the bar's (`state/primary.ts`), and this says it is next */
  .stage-head .stage-go {
    border: 1px solid var(--rule);
    border-radius: 999px;
    padding: 0 12px;
    height: 24px;
    margin: -3px 0;
    font: inherit;
    font-size: 12.5px;
    font-weight: 500;
    white-space: nowrap;
    background: none;
    color: var(--ink);
    cursor: pointer;
    transition: border-color 0.15s;
  }
  .stage-head .stage-go span {
    font-weight: 400;
    color: var(--ink3);
  }
  .stage-head .stage-go:hover:not(:disabled) {
    border-color: var(--ink3);
  }
  /* Make the deck carries what the story is still short of, the one place
     it is said: "· 4 open", with "Look at them" beside it, quiet */
  .stage-head .stage-go-wrap {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 10px;
  }
  .stage-head .stage-look {
    border: 0;
    background: none;
    padding: 0;
    font: inherit;
    font-size: 12.5px;
    color: var(--ink2);
    text-decoration: underline;
    text-decoration-color: color-mix(in srgb, var(--ink2) 35%, transparent);
    text-underline-offset: 3px;
    cursor: pointer;
    white-space: nowrap;
  }
  .stage-head .stage-look:hover:not(:disabled) {
    color: var(--ink);
  }
  .stage-head .stage-go .open {
    color: var(--ink2);
  }
  /* asked once, with gaps open: what the deck will say, and the two ways on */
  .stage-head .stage-ask {
    position: absolute;
    top: calc(100% + 8px);
    right: 0;
    z-index: 20;
    width: min(320px, calc(100vw - 32px));
    padding: 12px 14px;
    border: 1px solid var(--rule);
    border-radius: 10px;
    background: var(--panel);
    box-shadow: 0 8px 28px rgba(0, 0, 0, 0.18);
    font-size: 13.5px;
    line-height: 1.45;
    white-space: normal;
    animation: fade-in var(--m-quick) var(--ease-out) both;
  }
  .stage-head .stage-ask p {
    margin: 0 0 10px;
    color: var(--ink);
  }
  .stage-head .stage-ask .row {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
  }
  .stage-head .stage-ask button {
    border: 1px solid var(--rule);
    border-radius: 999px;
    background: none;
    color: var(--ink);
    font: inherit;
    font-size: 12.5px;
    padding: 4px 12px;
    cursor: pointer;
  }
  .stage-head .stage-ask button.go {
    background: var(--ink);
    border-color: var(--ink);
    color: var(--bg);
  }
  .stage-head .stage-go:disabled {
    opacity: 0.5;
    cursor: default;
  }
  .stage-head .count {
    color: var(--ink);
    font-weight: 500;
    font-size: 13px;
    white-space: nowrap;
  }
  /* The story moved on after its slides were made: one line over the
     slides, the sections that changed and the way to catch up. A strip
     under the head, not a box: it goes when the slides are made again. */
  .stage-behind {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px 12px;
    padding: 9px 24px;
    border-bottom: 1px solid var(--rule);
    /* a quiet panel, as the conversation's are: the accent means where you
       are, and a notice that the deck is behind is not that */
    background: var(--fill-03);
    font-size: 13px;
    color: var(--ink);
    flex: none;
  }
  .stage-behind p {
    margin: 0;
    flex: 1 1 240px;
  }
  .stage-behind .link {
    padding: 0;
    border: 0;
    background: none;
    font: inherit;
    color: inherit;
    cursor: pointer;
    text-decoration: underline;
    text-decoration-color: color-mix(in srgb, currentColor 35%, transparent);
    text-underline-offset: 3px;
  }
  .stage-behind .link:hover {
    text-decoration-color: currentColor;
  }
  .stage-behind > button {
    border: 1px solid var(--rule);
    border-radius: 999px;
    padding: 0 12px;
    height: 26px;
    font: inherit;
    font-size: 12.5px;
    font-weight: 500;
    white-space: nowrap;
    background: var(--panel);
    color: var(--ink);
    cursor: pointer;
  }
  .stage-behind > button.primary {
    border-color: transparent;
    background: var(--ink);
    color: var(--bg);
  }
  .stage-behind > button:hover:not(:disabled) {
    border-color: var(--rule-strong);
  }
  .stage-behind > button.primary:hover:not(:disabled) {
    border-color: transparent;
    background: var(--ink2);
  }
  .stage-behind > button:disabled {
    opacity: 0.5;
    cursor: default;
  }
  @media (max-width: 767px) {
    .stage-behind {
      padding: 9px 16px;
    }
  }
  /* The deck being updated in place: the same strip, saying what is being
     written again, with the busy dot every working line has. */
  .stage-updating p {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 0 6px;
  }
  .stage-updating p > i {
    flex: none;
    align-self: center;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--ink2);
    animation: breathe var(--work-beat) ease-in-out infinite;
  }
  .stage-updating p > span {
    color: var(--ink2);
  }
  /* a slide being written again: the old slide stays readable under a
     quiet light passing over it; nothing else on the deck moves */
  .slide-slot.rewriting::after {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 5;
    border-radius: inherit;
    pointer-events: none;
    background: linear-gradient(
      100deg,
      transparent 30%,
      color-mix(in srgb, var(--bg) 45%, transparent) 50%,
      transparent 70%
    );
    background-size: 200% 100%;
    animation: sheen var(--work-sweep) linear infinite;
  }
  .slide-slot.leaving {
    opacity: 0.45;
    transition: opacity 0.3s ease-out;
  }
  @media (prefers-reduced-motion: reduce) {
    .slide-slot.rewriting::after {
      animation: none;
      background: color-mix(in srgb, var(--bg) 25%, transparent);
    }
    .stage-updating p > i {
      animation: none;
    }
  }
  /* how far the writing has got, in the count's place */
  .stage-head .count.following {
    display: inline-flex;
    align-items: center;
    gap: 7px;
  }
  .stage-head .count.following i {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--accent);
    animation: pulse var(--work-beat) ease-in-out infinite;
  }

  /* The section being written, or the ones changed, when they are out of
     view: a pill held at the column's edge, taking no room of its own. */
  .follow-pill {
    position: sticky;
    z-index: 3;
    height: 0;
    display: flex;
    justify-content: center;
    pointer-events: none;
  }
  .follow-pill.above {
    top: 12px;
  }
  .follow-pill.below {
    bottom: 16px;
  }
  /* One skin for every pill: a quiet panel, not ink (it says where
     something is, not what to do next); a dot per section, the section's
     title, which way. */
  .follow-pill button,
  .follow-pill .follow-done {
    pointer-events: auto;
    display: inline-flex;
    align-items: center;
    gap: 9px;
    max-width: min(420px, calc(100% - 32px));
    height: 30px;
    margin: 0;
    padding: 0 12px 0 11px;
    border-radius: 15px;
    border: 1px solid var(--rule);
    background: var(--panel);
    color: var(--ink2);
    font: inherit;
    font-size: 12.5px;
    white-space: nowrap;
    box-shadow: 0 6px 20px rgba(0, 0, 0, 0.25);
    cursor: pointer;
    animation: fade-in var(--m-base) var(--ease-out) both;
  }
  .follow-pill .follow-done {
    pointer-events: none;
    cursor: default;
  }
  .follow-pill.below button,
  .follow-pill.below .follow-done {
    transform: translateY(-100%);
  }
  .follow-pill .pill-title {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    color: var(--ink);
  }
  .follow-pill .pill-way {
    flex: none;
    color: var(--ink3);
  }
  .pill-dots {
    flex: none;
    display: inline-flex;
    align-items: center;
    gap: 3px;
  }
  .pill-dots i {
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: var(--rule-strong);
  }
  .pill-dots i.done {
    background: var(--ink2);
  }
  /* the section being written beats at the one rate everything busy
     beats at */
  .pill-dots i.now {
    background: var(--accent);
    animation: pulse var(--work-beat) ease-in-out infinite;
  }
  /* a firmer edge and a lift of fill, as the quiet buttons have; not the
     accent, which means where you are */
  .follow-pill button:hover {
    border-color: var(--rule-strong);
    background: color-mix(in srgb, var(--panel) 92%, var(--ink));
  }
  @media (prefers-reduced-motion: reduce) {
    .stage-head .count.following i,
    .follow-pill button,
    .follow-pill .follow-done,
    .pill-dots i.now {
      animation: none;
    }
  }

  /* The two-step switch: Story, then Slides. */
  .steps {
    display: inline-flex;
    background: var(--fill-05);
    border: 1px solid var(--rule);
    border-radius: 999px;
    padding: 2px;
    gap: 1px;
  }
  .steps button {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 5px 12px;
    border-radius: 999px;
    font-size: 12px;
    color: var(--ink3);
    min-height: 28px;
    transition:
      background 0.15s,
      color 0.15s;
  }
  .steps button[aria-selected='true'] {
    background: var(--ink);
    color: var(--on-ink);
  }
  .steps button:disabled {
    opacity: 0.45;
    cursor: default;
  }
  .steps button .tick {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: currentColor;
    opacity: 0.4;
    flex: none;
  }
  .steps button.busy .tick {
    opacity: 1;
    background: var(--accent);
    animation: pulse var(--work-beat) ease-in-out infinite;
  }
  .steps button.done .tick {
    opacity: 1;
    background: var(--ok);
  }

  .scroller {
    flex: 1;
    overflow: auto;
    padding: 0 24px;
    /* Not `scroll-behavior: smooth`: every move into this pane is the
       glide (glide.ts `glideTo`), and a smooth default here was cancelled
       halfway by the next scroll to land, or made a move asked to be
       instant into a slow one. */
    overscroll-behavior: contain;
  }
  .scroller-in {
    max-width: 680px;
    margin: 0 auto;
    width: 100%;
    display: flex;
    flex-direction: column;
    padding: 20px 0 120px;
  }
  /* 680 is a measure for prose. A slide is a picture: on a wide screen it
     held a third of the window in a sea of margin. It grows with the
     window, so a laptop is unchanged and a large screen shows the slide at
     the size it will be watched at. */
  .scroller[data-view='slides'] .scroller-in {
    max-width: clamp(680px, 55vw, 1100px);
  }
  /* the brief over them is still prose, and keeps its measure */
  .scroller[data-view='slides'] .spine-glide {
    width: 100%;
    max-width: 680px;
    margin-inline: auto;
  }
  /* On a phone the brief over the slides was a screen of serif before the
     first slide. Three lines say what it is; the toggle under it opens the
     rest, and opened it reads in full. */
  @media (max-width: 599px) {
    .scroller[data-view='slides'] .spine:not(.open) .spine-aim {
      display: -webkit-box;
      -webkit-box-orient: vertical;
      -webkit-line-clamp: 3;
      line-clamp: 3;
      overflow: hidden;
    }
  }
  .stage-empty {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--ink3);
    font-size: 14px;
    text-align: center;
    padding: 40px;
  }

  /* ------------------------------------------------- the story: the spine */

  /* the brief: a document, not a slide — the aim in body type, the reader,
     the shift in two lines, one line of why */
  /* a block that follows its content's height, gliding (useGlide); the
     head of the outline is one, so the summary landing mid-turn moves the
     cards under it the way a card's own words do, not in one frame */
  .glide {
    overflow: clip;
    overflow-clip-margin: 6px;
    transition: height 0.35s cubic-bezier(0.2, 0.7, 0.2, 1);
  }
  @media (prefers-reduced-motion: reduce) {
    .glide {
      transition: none;
    }
  }
  /* the head's gap under it belongs to the box that glides, where a margin
     on the measured inside would be cut off */
  .spine-glide {
    margin-bottom: 20px;
  }
  .spine-glide > .spine {
    margin-bottom: 0;
  }
  .spine {
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 6px 0 22px;
    margin-bottom: 20px;
    border-bottom: 1px solid var(--rule);
    /* the brief reads as prose: a serif, set for reading */
    font-family: var(--serif);
  }
  .spine-aim {
    margin: 0;
    font-size: 19px;
    line-height: 1.45;
    letter-spacing: -0.005em;
    color: var(--ink);
    max-width: none;
    text-wrap: pretty;
  }
  .spine-more {
    display: flex;
    flex-direction: column;
    gap: 8px;
    /* the column is the measure */
    max-width: none;
  }
  .spine-more p {
    margin: 0;
    font-size: 15.5px;
    line-height: 1.55;
    color: var(--ink2);
  }
  /* the run-in label: what this sentence is, in the app's own small caps */
  .spine-k {
    font-family: var(--body);
    font-size: 11px;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--ink3);
    margin-right: 10px;
    position: relative;
    top: -1px;
  }
  .spine-why {
    color: var(--ink3) !important;
    font-style: italic;
  }
  .spine-toggle {
    align-self: flex-start;
    font-family: var(--body);
    font-size: 12.5px;
    color: var(--ink3);
    text-decoration: underline;
    text-underline-offset: 3px;
  }
  .spine-toggle:hover {
    color: var(--ink);
  }

  /*
   * The citation inside a sentence: `[src](…)`.
   *
   * Small, raised a little and quiet, so it reads as a mark on the claim
   * rather than as a word in it — and only colours up when pointed at.
   */
  .src-inline {
    font-size: 0.72em;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    vertical-align: 0.35em;
    margin-left: 3px;
    color: var(--ink3);
    text-decoration: none;
    border-bottom: 1px solid color-mix(in srgb, var(--ink3) 40%, transparent);
  }
  .src-inline:hover {
    color: var(--ink);
    border-bottom-color: currentColor;
  }

  /* --------------------------------------------------- src, in the thread */

  /*
   * What the agent just read, on its own line in the conversation.
   *
   * It sits where it happened — under the message whose claims it backs —
   * because "where does this come from" is a question about a sentence, not
   * about the session. A list beside the story answered the wrong one.
   *
   * Quiet: a run-in "src" in the app's small caps, then the pages as links.
   * It is there to be checked, not read.
   */
  .srcs {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    align-items: baseline;
    column-gap: 10px;
    margin: 2px 0 0;
  }
  /* the pages after the label, wrapping under the first of them: the
     label hangs in its own column */
  .srcs-list {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 2px 12px;
    min-width: 0;
  }
  .srcs-k {
    font-family: var(--body);
    font-size: 10.5px;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink3);
    opacity: 0.75;
  }
  .srcs .src {
    max-width: 40ch;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 12.5px;
    line-height: 1.5;
    color: var(--ink3);
    text-decoration: underline;
    text-decoration-thickness: 1px;
    text-underline-offset: 3px;
    text-decoration-color: color-mix(in srgb, var(--ink3) 45%, transparent);
  }
  .srcs .src:hover {
    color: var(--ink);
    text-decoration-color: currentColor;
  }

  /* ------------------------------------------------- the story: the parts */

  /* the parts as placeholders, in the player's page rhythm: one column,
     a full-width frame, a small meta line, the text beneath */
  /* stickies: three up, each about square at rest, the one being asked or
     opened across the row. The row keeps its rhythm as cards open. */
  .slots {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
    gap: 12px;
    width: 100%;
    max-width: 880px;
    margin: 12px auto 0;
    align-items: start;
  }
  /* One rule between every two slides in the deck, whatever holds them —
     a part's group of slides, or a slide on its own: 40px, a hairline, 40px.
     It used to be drawn between single slides and inside a group, but not
     where a group met a single slide, so the deck read as ruled in places. */
  .slots:not(.flow) > .slot + .slot {
    position: relative;
    padding-top: 40px;
    margin-top: 28px;
  }
  .slots:not(.flow) > .slot + .slot::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 1px;
    background: var(--rule);
    opacity: 0.55;
  }
  /* a folded card that opens takes the row; a sticky note keeps its columns */
  .slot:not(.flow):has(.note.open),
  .slot:not(.flow):has(.note[data-state='asking']),
  .slot.deck,
  .slot:has(.slot-slides) {
    grid-column: 1 / -1;
  }
  /* sticky notes: a column in story order, each note the width the person
     gave it (a third of the board otherwise), as tall as its lines. Nothing
     ever moves but downward: a card that grows pushes the ones under it */
  .slots.flow {
    --gutter: 36px;
    --rail: 40px;
    --col: calc((100% - 2 * var(--gutter)) / 3);
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--gutter);
    padding-left: var(--rail);
  }
  /* on the string between two cards, a card could go: a plus that shows
     when the hand is over the board and starts the sentence in the chat */
  /* Where a card could go. The string is quiet; the plus is not — it is
     the one thing on the rail you can act on, so it reads as a button:
     a filled disc on the paper, the plus in ink, a shadow to lift it. */
  .rail-add {
    position: absolute;
    z-index: 3;
    width: 20px;
    height: 20px;
    margin: -10px 0 0 -10px;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 1px solid color-mix(in srgb, var(--ink) 16%, transparent);
    border-radius: 50%;
    background: var(--bg);
    color: var(--ink2);
    box-shadow: 0 1px 3px rgb(0 0 0 / 0.08);
    cursor: pointer;
    /* only while the hand is over the board: always drawn, a plus between
       every two sections was a row of buttons nobody had reached for */
    opacity: 0;
    transition:
      opacity 0.18s,
      color 0.18s,
      border-color 0.18s,
      box-shadow 0.18s;
  }
  .slots:hover .rail-add,
  .rail-add:focus-visible {
    opacity: 1;
  }
  @media (hover: none) {
    .rail-add {
      opacity: 1;
    }
  }
  .rail-add svg {
    width: 10px;
    height: 10px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.5;
    stroke-linecap: round;
  }
  .rail-add:hover,
  .rail-add:focus-visible {
    color: var(--ink);
    border-color: color-mix(in srgb, var(--ink) 32%, transparent);
    box-shadow: 0 2px 8px rgb(0 0 0 / 0.12);
  }
  /* the overlay's box is pinned to the board's: a host page's own rules for
     svg (display, padding, margin) must not shift where the string draws */
  .slots.flow .thread {
    position: absolute;
    inset: 0;
    display: block;
    box-sizing: border-box;
    width: 100%;
    height: 100%;
    margin: 0;
    padding: 0;
    border: 0;
    pointer-events: none;
    overflow: visible;
    z-index: 0;
  }
  /* The string is the quietest thing on the board: it says the cards are
     in an order, and nothing else. A hairline in the rule colour rather
     than in ink, at the weight of the rules elsewhere on the page. */
  .slots.flow .thread path {
    fill: none;
    stroke: var(--rule);
    stroke-width: 1;
    stroke-linecap: round;
    stroke-linejoin: round;
    opacity: 1;
  }
  /* the head of the arrow, the same hairline and a little smaller, so it
     lands on the card rather than pointing at it */
  .slots.flow .thread marker path {
    fill: none;
    stroke: var(--rule);
    stroke-width: 1;
    stroke-linecap: round;
    stroke-linejoin: round;
  }
  /* the string lies over the two cards it joins */
  .slots.flow .thread {
    z-index: 2;
  }
  .slots.flow .slot {
    position: relative;
    z-index: 1;
  }
  .slot.flow {
    flex: 0 0 auto;
    width: calc(var(--col) * var(--cols, 1) + var(--gutter) * (var(--cols, 1) - 1));
    max-width: 100%;
    transition: transform 0.35s cubic-bezier(0.2, 0.7, 0.2, 1);
  }
  /* A card being written keeps up with its own words. The height is driven
     in pixels and the sheet clips what it cannot hold, so an animation on
     it means the box arrives nearly four tenths of a second after the words
     it is meant to show — and every burst starts it again, so a card can
     spend the whole of its writing behind itself, its own title cut in
     half. The growing is worth watching when a settled card changes shape;
     while it is being written it is only a card that cannot keep up. */
  .note[data-state='writing'] {
    transition: none;
  }
  .slot.flow[data-lit] .note {
    --paper: color-mix(in srgb, var(--paper-tone) 11%, transparent);
    --paper-edge: color-mix(in srgb, var(--paper-tone) 22%, transparent);
  }
  .note-scratch li.relates {
    color: var(--ink3);
    font-size: 12.5px;
    cursor: pointer;
  }
  .note-scratch li.relates::before {
    content: '↗';
  }
  .note-scratch li.relates:hover {
    color: var(--ink);
  }
  .slots.flow {
    max-width: 1120px;
  }
  /* On a phone the rail the string runs down takes a ninth of the width
     and pushes every card right of centre. The order reads from the stack
     itself there: no rail, no string, the cards the column's full width
     with the same margin either side, a little closer together. */
  @media (max-width: 767px) {
    .slots.flow {
      --gutter: 20px;
      padding-left: 0;
    }
    .slots.flow .thread {
      display: none;
    }
    .slot.flow .note.open {
      padding-left: 16px;
      padding-right: 16px;
    }
  }
  .slot.flow .note {
    height: auto;
    cursor: default;
  }
  /* Words arriving: the newest fade in. They rose in, by two pixels, and
     the card is sized to what it holds and clips the rest — so the newest
     words spent their animation cut off at the foot of the card that was
     showing them. */
  .note-text .fresh {
    display: inline;
    animation: line-in 0.35s ease-out both;
  }
  /* A line arriving as it is written: it is the line it will be, so when
     the settled lines land in its place nothing on the card moves.
     It fades rather than rising: the card is sized to what it holds and
     clips the rest, so a line that starts two pixels lower than it ends
     spends the animation cut off at the foot of its own card. */
  .note-scratch li.arriving {
    animation: line-in 0.3s ease-out both;
  }
  /* a card not worked out yet: what it is about, reading as the one line
     it has, until its own lines arrive and take its place */
  .note-scratch li.unworked {
    color: var(--ink2);
  }
  /* what a line still lacks, named in it: the words on the dashed line
     that stands for something not settled, never the mark itself */
  /* a fact the card is still short of, in its lines: in the caution colour
     the same gap wears once the words are written (`mark.missing`), not the
     grey of text that is merely quiet — which read as a styling choice, with
     a tooltip as the only word on what it was */
  .note-scratch .line-gap {
    color: color-mix(in srgb, var(--warn) 65%, var(--ink));
    border-bottom: 1px dashed
      color-mix(in srgb, var(--warn) 60%, transparent);
  }
  @media (hover: hover) {
    .note-scratch li .line-text.editable:hover {
      text-decoration: underline dashed var(--ink3);
      text-underline-offset: 4px;
    }
  }
  .slot.flow .note-text.short:hover {
    color: var(--ink);
  }
  .slot {
    display: flex;
    flex-direction: column;
    gap: 12px;
    /* a fade, not a rise: a column of cards each sliding up reads as
       motion for its own sake, and on a story coming back the page is
       already fading up as one */
    animation: fade-in 0.3s ease-out both;
    scroll-margin-top: 24px;
  }
  /* ---- the card, as a row that opens -----------------------------------
     A card at rest is one line: a glyph for where it stands, its title,
     the first line of what it will say. Open, it holds everything about
     its part. Rows are separated by a hairline, not boxed; the card being
     asked or written opens by itself and carries a thin accent rule. */
  .note {
    /* a piece of paper, lightly: a warm tint at the top edge that fades
       to the ground down the card, so it reads as a sheet laid on the
       board and not a box drawn on it; a dog-ear at its top right corner */
    /* neutral, not warm: a sheet a few percent lighter than the ground,
       a faint falloff down the card, a crisp top edge, the fold. Hue on a
       dark ground read as khaki; the paper is in the shape and the type. */
    --paper-tone: #ffffff;
    --paper: color-mix(in srgb, var(--paper-tone) 6%, transparent);
    --paper-low: color-mix(in srgb, var(--paper-tone) 3%, transparent);
    --paper-edge: color-mix(in srgb, var(--paper-tone) 12%, transparent);
    --paper-fold: color-mix(in srgb, var(--paper-tone) 16%, transparent);
    --ear: 16px;
    --sticky: 250px;
    position: relative;
    margin: 0;
    height: var(--sticky);
    box-sizing: border-box;
    padding: 14px 20px 16px;
    overflow: hidden;
    /* the sheet's height is set in pixels by the board (see Sheet): the
       folded size at rest, the measured content open, never less; the
       transition runs pixel to pixel both ways */
    transition: height 0.38s cubic-bezier(0.2, 0.7, 0.2, 1);
    border-radius: 10px 0 10px 10px;
    background: linear-gradient(180deg, var(--paper) 0%, var(--paper-low) 100%);
    /* the corner is cut for the fold */
    clip-path: polygon(
      0 0,
      calc(100% - var(--ear)) 0,
      100% var(--ear),
      100% 100%,
      0 100%
    );
  }
  /* a folded sheet trails off at the foot rather than cutting a line; the
     fade dissolves as the sheet opens and returns as it folds */
  .note::before {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 64px;
    background: linear-gradient(
      180deg,
      transparent,
      color-mix(in srgb, var(--paper-tone) 4%, var(--bg))
    );
    pointer-events: none;
    z-index: 1;
    opacity: 1;
    transition: opacity 0.3s ease;
  }
  .note.open::before {
    opacity: 0;
  }
  /* the dog-ear: the folded flap, a shade stronger than the sheet */
  .note::after {
    content: '';
    position: absolute;
    top: 0;
    right: 0;
    width: var(--ear);
    height: var(--ear);
    background: linear-gradient(
      225deg,
      transparent 0 50%,
      var(--paper-fold) 50% 100%
    );
    border-bottom-left-radius: 3px;
    pointer-events: none;
  }
  /* folded, the whole sheet is the handle: a pointer, and a lift on hover */
  .note:not(.open) {
    cursor: pointer;
  }
  .note:not(.open):hover {
    --paper: color-mix(in srgb, var(--paper-tone) 8%, transparent);
    --paper-low: color-mix(in srgb, var(--paper-tone) 4%, transparent);
  }
  .note.open {
    --paper: color-mix(in srgb, var(--paper-tone) 8%, transparent);
    --paper-low: color-mix(in srgb, var(--paper-tone) 4%, transparent);
    --paper-edge: color-mix(in srgb, var(--paper-tone) 16%, transparent);
  }
  html[data-scene-theme='light'] & .note {
    --paper-tone: #000000;
    --paper: color-mix(in srgb, var(--paper-tone) 4%, transparent);
    --paper-low: color-mix(in srgb, var(--paper-tone) 2%, transparent);
    --paper-edge: color-mix(in srgb, var(--paper-tone) 9%, transparent);
    --paper-fold: color-mix(in srgb, var(--paper-tone) 12%, transparent);
  }
  html[data-scene-theme='light'] & .note.open {
    --paper: color-mix(in srgb, var(--paper-tone) 5.5%, transparent);
    --paper-low: color-mix(in srgb, var(--paper-tone) 3%, transparent);
    --paper-edge: color-mix(in srgb, var(--paper-tone) 12%, transparent);
  }
  .note-head {
    display: flex;
    align-items: center;
    gap: 12px;
    width: 100%;
    padding: 2px 0;
    text-align: left;
    color: var(--ink);
    cursor: pointer;
    border-radius: 6px;
  }
  .note-head:hover .note-title {
    color: var(--ink);
  }
  /* Every state of the glyph is the same mark at the same weight; what
     changes is how much ink it carries. Queued is the faintest, done a
     shade up from it, the card being asked is in full ink, and only the
     card the agent is on takes the accent — one coloured thing on the
     board at a time. */
  .note-glyph {
    flex: none;
    width: 15px;
    height: 15px;
    fill: none;
    stroke: var(--rule);
    stroke-width: 1.2;
    stroke-linecap: round;
    stroke-linejoin: round;
    transition:
      stroke 0.2s,
      color 0.2s;
  }
  .note-glyph .fill {
    fill: currentColor;
    stroke: none;
  }
  .note[data-state='done'] .note-glyph {
    stroke: var(--ink3);
  }
  .note[data-state='asking'] .note-glyph {
    stroke: var(--ink2);
    color: var(--ink2);
  }
  .note[data-state='writing'] .note-glyph {
    stroke: var(--accent);
    color: var(--accent);
  }
  /* the ring it turns on: present, not loud */
  .note[data-state='writing'] .note-glyph .ring {
    stroke: color-mix(in srgb, var(--accent) 24%, transparent);
  }
  .note[data-state='writing'] .note-glyph .arc {
    /* `pathLength` makes the ring 100 long whatever its radius, so the arc
       is a fifth of it and stays a fifth wherever the glyph is drawn */
    stroke-dasharray: 20 80;
    transform-origin: 50% 50%;
    animation: glyph-turn var(--work-sweep) linear infinite;
  }
  @media (prefers-reduced-motion: reduce) {
    .note[data-state='writing'] .note-glyph .arc {
      animation: none;
      transform: rotate(-54deg);
    }
  }
  /* The agent is on this card: the paper is lit, slowly, from one side to
     the other, and its edge carries a trace of the accent. One movement
     the whole time it is working, rather than a spinner or a word that
     blinks — the card is busy, not the interface. */
  .note-work {
    position: absolute;
    inset: 0;
    z-index: 2;
    border-radius: inherit;
    background: linear-gradient(
      100deg,
      transparent 32%,
      color-mix(in srgb, var(--accent) 9%, transparent) 50%,
      transparent 68%
    );
    background-size: 220% 100%;
    opacity: 1;
    pointer-events: none;
    animation: sheen var(--work-sweep) linear infinite;
  }
  .slot.flow .note[data-state='writing'] {
    --paper-edge: color-mix(in srgb, var(--accent) 26%, transparent);
  }
  @media (prefers-reduced-motion: reduce) {
    .note-work {
      animation: none;
      background: color-mix(in srgb, var(--accent) 5%, transparent);
    }
  }
  .note[data-state='open'] .note-glyph {
    stroke: var(--warn);
  }
  /* the section's heading, in the same sans as everything under it: the
     italic serif it was set in made the head one voice and the body
     another, and the page read as two things stacked */
  .note-title {
    flex: 1;
    min-width: 0;
    font-size: 15.5px;
    font-weight: 500;
    line-height: 1.3;
    color: var(--ink);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .note[data-state='queued'] .note-title {
    color: var(--ink);
  }
  .note-word {
    flex: none;
    font-size: 12px;
    color: var(--ink3);
    cursor: pointer;
  }
  /* "2 assumed": what the card says without the material behind it, in the
     colour the marks themselves wear, quiet beside the head's other word */
  .note-assumes {
    flex: none;
    font-size: 12px;
    color: color-mix(in srgb, var(--warn) 65%, var(--ink));
    cursor: pointer;
  }
  .note-assumes:hover {
    text-decoration: underline dashed;
    text-underline-offset: 3px;
  }
  .note-assumes + .note-word::before {
    content: '·';
    margin-right: 6px;
    color: var(--ink3);
  }
  .note-word:hover {
    color: var(--ink);
    text-decoration: underline dashed;
    text-underline-offset: 3px;
  }
  .note[data-state='asking'] .note-word {
    color: var(--ink);
  }
  .note[data-state='writing'] .note-word {
    color: var(--accent);
  }
  .note[data-state='open'] .note-word {
    color: var(--warn);
  }
  /* at rest: what was answered so far, on one quiet line */
  .note-sum {
    margin: 0;
    font-family: var(--serif);
    font-style: italic;
    font-size: 15px;
    line-height: 1.4;
    color: var(--ink2);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  /* at rest the words are the same words, clipped by the sheet and its
     fade rather than cut mid-line, so opening only reveals more of them */
  .note-text.short {
    color: var(--ink2);
  }
  /* what appears with a change of state comes in with a small rise — once
     the board has settled; the first draw arrives still */
  .slots.settled .note-facts,
  .slots.settled .note-gap,
  .slots.settled .note-sum {
    animation: note-in 0.32s cubic-bezier(0.2, 0.7, 0.2, 1) both;
  }
  /* open: everything about the part, in reading sizes, on the same column */
  /* one text edge: the body's lines start where the title starts */
  .note-body {
    margin: 4px 0 0 24px;
    display: flex;
    flex-direction: column;
    gap: 10px;
    /* the column is the measure */
    max-width: none;
  }
  /* the card being asked is the one pulled forward: a shade more paper,
     a soft lift off the board, the highlighter on its blank */
  .note[data-state='asking'] {
    --paper: color-mix(in srgb, var(--paper-tone) 10%, transparent);
    --paper-low: color-mix(in srgb, var(--paper-tone) 5%, transparent);
    --paper-edge: color-mix(in srgb, var(--paper-tone) 20%, transparent);
    filter: drop-shadow(0 12px 20px rgba(0, 0, 0, 0.4));
  }
  html[data-scene-theme='light'] & .note[data-state='asking'] {
    --paper: color-mix(in srgb, var(--paper-tone) 7%, transparent);
    --paper-low: color-mix(in srgb, var(--paper-tone) 4%, transparent);
    --paper-edge: color-mix(in srgb, var(--paper-tone) 14%, transparent);
    filter: drop-shadow(0 10px 18px rgba(0, 0, 0, 0.12));
  }
  .note-job {
    margin: 0;
    font-size: 14px;
    line-height: 1.5;
    color: var(--ink2);
  }
  .note-reading {
    margin: 0;
    font-size: 14px;
    line-height: 1.5;
    color: var(--ink2);
  }
  /* the facts, as blanks filled by hand: what was asked in a small label,
     the answer on a hairline in the display face's italic; the one still
     empty carries the accent line and says where to answer */
  .note-facts {
    margin: 2px 0 0;
    display: grid;
    grid-template-columns: minmax(0, 38%) 1fr;
    column-gap: 14px;
    row-gap: 9px;
    align-items: end;
  }
  .note-facts > div {
    display: contents;
  }
  .note-facts dt {
    font-size: 12.5px;
    line-height: 1.4;
    color: var(--ink3);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    padding-bottom: 2px;
  }
  .note-facts dd {
    margin: 0;
    min-width: 0;
    font-family: var(--serif);
    font-style: italic;
    font-size: 16px;
    line-height: 1.4;
    color: var(--ink);
    padding-bottom: 2px;
    border-bottom: 1px solid var(--paper-edge);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  /* a long question stacks over its line instead of squeezing beside it */
  .note-facts .stack {
    display: flex;
    flex-direction: column;
    grid-column: 1 / -1;
    gap: 2px;
  }
  .note-facts .stack dt {
    white-space: normal;
  }
  /* open, there is room for the whole answer — including the lines the
     person broke in it. At rest the row is one clipped line and a break
     reads as a space, which is what a summary wants. */
  .note.open .note-facts dd {
    white-space: pre-wrap;
  }
  /* a point the agent wrote as a sentence: the answer sits in its blank,
     and the row reads as a note rather than a label and a value */
  .note-facts .line {
    display: block;
    grid-column: 1 / -1;
  }
  .note-facts .line dt.sr {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
  }
  .note-facts .line dd {
    font-family: inherit;
    font-style: normal;
    font-size: 13.5px;
    line-height: 1.75;
    color: var(--ink2);
    border-bottom: 0;
    white-space: normal;
    overflow: visible;
  }
  .note-facts .line dd em {
    font-family: var(--serif);
    font-style: italic;
    font-size: 16px;
    color: var(--ink);
    padding: 0 1px 1px;
    border-bottom: 1px solid var(--paper-edge);
  }
  /* a note that names a file put on the card */
  .note-facts .line dd.jotted.file::before {
    content: 'file';
    font-family: var(--body, inherit);
    font-style: normal;
    font-size: 10px;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink3);
    margin-right: 8px;
    vertical-align: 2px;
  }
  /* what the person jotted: their hand, on its own line */
  .note-facts .line dd.jotted {
    font-family: var(--serif);
    font-style: italic;
    font-size: 16px;
    line-height: 1.4;
    color: var(--ink);
  }
  /* the blank still empty: the line where the answer goes, dashed in ink */
  .note-facts .empty dd {
    font-family: inherit;
    font-style: normal;
    font-size: 13px;
    color: var(--ink3);
    border-bottom: 1px dashed var(--ink3);
  }
  .note-facts .empty dt {
    color: var(--ink2);
  }
  /* A photograph from the person's own material, on the card it is about.
     A miniature, not the picture: a card is its lines, and a photograph the
     size of the card turns the lines into its caption. Click it to see it.
     The box is a fixed size and the picture covers it, so the card is the
     size it will be before the picture has loaded — nothing grows under the
     hand a second after the card was read. */
  .note-pics {
    margin: 4px 0 0;
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
  }
  /* One attachment: what it looks like, and what it is called. A chip
     rather than an inlined picture — the picture came off a document the
     person handed over, and a photograph sitting in the card's own column
     reads as something the card is made of rather than something clipped
     to it. */
  .note-pic {
    max-width: 100%;
    display: inline-flex;
    align-items: center;
    gap: 9px;
    padding: 0 12px 0 0;
    border-radius: 8px;
    background: var(--fill-04);
    box-shadow: inset 0 0 0 1px var(--rule);
    font-size: 12.5px;
    line-height: 1.3;
    color: var(--ink2);
    text-align: left;
    cursor: pointer;
    transition:
      box-shadow 0.18s,
      color 0.18s,
      transform 0.18s;
  }
  .note-pic img {
    flex: none;
    display: block;
    width: 46px;
    height: 34px;
    border-radius: 7px 0 0 7px;
    object-fit: cover;
    background: var(--fill-06);
  }
  .note-pic span {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .note-pic:hover,
  .note-pic:focus-visible {
    color: var(--ink);
    box-shadow:
      inset 0 0 0 1px var(--ink3),
      0 4px 12px -5px rgb(0 0 0 / 0.5);
    transform: translateY(-1px);
  }
  /* the picture, open: over everything, and out of the card that clips it */
  .pic-open {
    position: fixed;
    inset: 0;
    z-index: 70;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 12px;
    padding: 48px 24px 32px;
    background: rgb(0 0 0 / 0.78);
    cursor: zoom-out;
    animation: line-in 0.16s ease-out both;
  }
  .pic-open img {
    max-width: min(1100px, 92vw);
    max-height: 82vh;
    max-height: 82dvh;
    object-fit: contain;
    border-radius: 8px;
    box-shadow: 0 24px 60px -20px rgb(0 0 0 / 0.8);
    cursor: default;
  }
  .pic-open p {
    margin: 0;
    max-width: 92vw;
    font-size: 12.5px;
    color: var(--ink3);
    text-align: center;
  }
  .pic-close {
    position: absolute;
    top: 14px;
    right: 16px;
    padding: 5px 12px;
    border-radius: 999px;
    font-size: 12.5px;
    color: var(--ink2);
    background: var(--fill-06);
    box-shadow: inset 0 0 0 1px var(--rule);
    cursor: pointer;
  }
  .pic-close:hover {
    color: var(--ink);
  }
  /* the words, a second layer under the key points: a quiet rule marks
     where the points end and the prose begins, the label names it. The
     rule sits in the middle of the air between them — the same above as
     below — whether or not the list ends in the empty bullet, whose room
     is kept at rest while its ink is not: counted as air here, or the rule
     read as sunk towards the words. */
  .note-draft {
    /* The air either side of the rule between the points and the words.
       It was 42px, sized so that an invisible empty bullet over it read as
       air: at rest a card showed a band of nothing under its points and as
       much again under the rule. The bullet is shown now (faint), so the
       air is only air. */
    --draft-air: 24px;
    /* .note-body's 10px gap is already above it */
    margin-top: calc(var(--draft-air) - 10px);
    padding-top: var(--draft-air);
    border-top: 1px solid var(--paper-edge);
  }
  /* under the empty bullet, a little less: its own row is lighter than a
     written line and already reads as space */
  .note-scratch:has(> li.new) + .note-draft {
    margin-top: calc(var(--draft-air) - 14px);
  }
  /* the name of each layer — Key points, What you'll say — as a quiet line
     over it, the one on the words also saying how long they take */
  .note-layer-label {
    display: block;
    font-size: 11.5px;
    line-height: 16px;
    color: var(--ink3);
    margin-bottom: 6px;
  }
  .note-layer-label + .note-scratch {
    margin-top: 0;
  }
  /* the line over the story: what it is, how long it takes to say, and
     what becomes of it — a quiet sentence, not a stamp */
  .note-draft small {
    display: block;
    font-size: 11.5px;
    line-height: 16px;
    color: var(--ink3);
    margin-bottom: 6px;
  }
  .note-draft-label {
    display: flex !important;
    flex-wrap: wrap;
    align-items: baseline;
    column-gap: 6px;
  }
  .note-draft-label .what {
    color: var(--ink2);
    font-weight: 500;
  }
  .note-draft-label .how::before {
    content: '·';
    margin-right: 6px;
  }
  /* What you'll say, in the chat's own type: the words are the person's to
     say out loud, not a quotation of someone else, and an italic serif
     read as the second */
  .note-text {
    margin: 0;
    font-size: 14.5px;
    line-height: 22px;
    color: var(--ink2);
    /* The air between paragraphs is kept, and kept the same whether the
       words are being read or written. `pre-line` folds a blank line away
       while the caret's own rendering keeps it, so clicking into the words
       opened them out and the card grew under the hand. */
    white-space: pre-wrap;
  }
  .note.open .note-text {
    color: var(--ink);
  }
  .note-text mark {
    display: inline-block;
    vertical-align: baseline;
    margin: 0 2px 0 4px;
    padding: 0 6px;
    border-radius: 999px;
    background: transparent;
    font-size: 10.5px;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    line-height: 1.6;
    color: var(--ink3);
    border: 1px solid var(--rule);
    cursor: help;
  }
  .note-text mark.missing {
    text-transform: none;
    letter-spacing: 0;
    font-size: 12px;
    color: var(--warn);
    border-color: color-mix(in srgb, var(--warn) 40%, transparent);
  }
  /* a sentence the writer assumed: set as a suggestion is — a dotted
     underline and the faintest edge, and under the hand a word on why */
  .note-text .assumed {
    position: relative;
    text-decoration: underline dotted;
    text-decoration-color: color-mix(in srgb, var(--warn) 70%, transparent);
    text-decoration-thickness: 1px;
    text-underline-offset: 4px;
    border-radius: 3px;
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--warn) 22%, transparent);
    padding: 0 2px;
    margin: 0 -2px;
    cursor: help;
    transition: background 0.15s;
  }
  .note-text .assumed:hover {
    background: color-mix(in srgb, var(--warn) 9%, transparent);
  }
  .note-text .assumed::after {
    content: attr(data-tip);
    position: absolute;
    left: 0;
    bottom: calc(100% + 6px);
    z-index: 5;
    max-width: 320px;
    width: max-content;
    padding: 5px 9px;
    border-radius: 6px;
    border: 1px solid var(--rule);
    background: var(--bg);
    box-shadow: 0 6px 20px rgba(0, 0, 0, 0.12);
    font-family: var(--body, inherit);
    font-style: normal;
    font-size: 12px;
    line-height: 1.4;
    color: var(--ink);
    white-space: normal;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.12s;
  }
  .note-text .assumed:hover::after,
  .note-text .assumed[data-open]::after,
  .note-text .assumed:has(+ .assumed-why:focus-visible)::after {
    opacity: 1;
  }
  .note-text .assumed[data-open] {
    background: color-mix(in srgb, var(--warn) 9%, transparent);
  }
  /* The note's own button: a small mark at the end of the sentence, in the
     note's amber, that the note opens from. A pointer has the hover and
     never sees it; a finger always does, and a keyboard when it gets there. */
  .note-text .assumed-why {
    position: relative;
    display: inline-grid;
    place-items: center;
    width: 16px;
    height: 16px;
    margin: 0 0 0 5px;
    padding: 0;
    border: 0;
    vertical-align: 1px;
    border-radius: 50%;
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--warn) 55%, transparent);
    color: var(--warn);
    font: 600 10px/1 var(--body);
    cursor: pointer;
  }
  .note-text .assumed-why::before {
    content: '?';
  }
  .note-text .assumed[data-open] + .assumed-why,
  .note-text .assumed-why:active {
    background: color-mix(in srgb, var(--warn) 14%, transparent);
  }
  @media (hover: hover) {
    .note-text .assumed-why:not(:focus-visible) {
      position: absolute;
      width: 1px;
      height: 1px;
      margin: 0;
      overflow: hidden;
      clip-path: inset(50%);
    }
  }
  /* its reach is a finger's, round a mark the size of a letter */
  @media (pointer: coarse) {
    .note-text .assumed-why::after {
      content: '';
      position: absolute;
      inset: -12px;
    }
  }
  /* A card clips what reaches its edge — it has to, its corner is cut for
     the fold — so a note hanging off a sentence near that edge was served
     cut in half. Anchored to the sentence and taken out of the card's flow,
     it is bound by the window instead, and turns to the other side rather
     than running off one. */
  @supports (anchor-name: --a) and (position-area: block-start) {
    .note-text .assumed {
      anchor-name: --assumed;
      /* every marked sentence carries the same name, so each one keeps it to
         itself: without this they would all point at whichever came last */
      anchor-scope: --assumed;
    }
    .note-text .assumed::after {
      position: fixed;
      position-anchor: --assumed;
      position-area: block-start span-inline-end;
      position-try-fallbacks:
        block-start span-inline-start,
        block-end span-inline-end,
        block-end span-inline-start;
      position-visibility: anchors-visible;
      left: auto;
      bottom: auto;
      margin: 0 0 6px;
      max-width: min(320px, 90vw);
    }
    /* A pane too narrow for a note beside where the sentence starts: the
       note takes the pane's width, over the sentence or under it, so it is
       read whole rather than run off the edge. */
    @container pane (max-width: 560px) {
      .note-text .assumed::after {
        position-area: block-start span-all;
        position-try-fallbacks: block-end span-all;
        justify-self: stretch;
        width: auto;
        max-width: none;
        margin: 0 12px 8px;
        padding: 8px 12px;
        font-size: 13px;
      }
    }
  }
  /* A sentence the check marked: a quiet dotted underline, and the
     faintest wash under the hand or while its note is open. Colour and
     decoration only — no padding, border or weight — so the paragraph is
     exactly the same shape marked, looked at, opened and unmarked. */
  .note-text .said-mark {
    text-decoration: underline dotted;
    text-decoration-color: color-mix(in srgb, var(--warn) 65%, transparent);
    text-decoration-thickness: 1px;
    text-underline-offset: 4px;
    border-radius: 2px;
    cursor: help;
    outline: none;
    transition:
      background-color 0.15s,
      text-decoration-color 0.15s;
  }
  /* A sentence that says a gap and holds its blank ("we haven't set the
     salary budget yet [salary budget]") is the one gap: the blank says it,
     the sentence keeps no underline of its own. */
  .note-text .said-mark:has(mark.missing),
  .note-text .said-mark.gap-open-mark:has(mark.missing) {
    text-decoration: none;
  }
  .note-text .said-mark:hover,
  .note-text .said-mark.open,
  .note-text .said-mark:focus-visible {
    background-color: color-mix(in srgb, var(--warn) 10%, transparent);
    text-decoration-color: var(--warn);
  }
  /* its note: hung off the sentence, fixed over everything, so opening it
     pushes nothing down */
  .mark-note {
    position: fixed;
    z-index: 60;
    width: max-content;
    max-width: 320px;
    padding: 9px 11px 8px;
    border-radius: 8px;
    border: 1px solid var(--rule);
    background: var(--bg);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.14);
    font-size: 12.5px;
    line-height: 18px;
    color: var(--ink);
    animation: line-in 0.12s ease-out both;
  }
  .mark-note.above {
    transform: translateY(-100%);
  }
  .mark-why {
    margin: 0;
  }
  .mark-acts {
    display: flex;
    gap: 14px;
    margin-top: 7px;
  }
  .mark-acts button {
    padding: 0;
    border: 0;
    background: none;
    font: inherit;
    font-weight: 500;
    color: var(--ink2);
    cursor: pointer;
    text-decoration: underline;
    text-decoration-color: transparent;
    text-underline-offset: 3px;
    transition:
      color 0.15s,
      text-decoration-color 0.15s;
  }
  .mark-acts button:hover,
  .mark-acts button:focus-visible {
    color: var(--ink);
    text-decoration-color: currentColor;
    outline: none;
  }
  /* the judge's point: one line and its chip, no box */
  .note-point {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 8px 0 0;
    font-size: 13.5px;
    line-height: 1.4;
    color: var(--ink);
    border-top: 1px dashed color-mix(in srgb, var(--warn) 45%, transparent);
  }
  .note-point > span {
    flex: 1;
    min-width: 0;
  }
  .note-point button {
    flex: none;
    font-size: 12.5px;
    padding: 4px 11px;
    border-radius: 999px;
    border: 1px solid var(--rule);
    background: var(--fill-06);
    color: var(--ink);
  }
  /* what the person said about the card, pinned: a quiet list */
  .slot-frame-notes {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
    /* the column is the measure */
    max-width: none;
  }
  .slot-frame-notes li {
    position: relative;
    padding-left: 14px;
    font-size: 13.5px;
    line-height: 1.5;
    color: var(--ink2);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .slot-frame-notes li::before {
    content: '';
    position: absolute;
    left: 0;
    top: 0.75em;
    width: 8px;
    height: 1px;
    background: var(--ink3);
  }
  /* a line that can be changed in place: a faint field while it is */
  .editable,
  /* an answer standing in for an editable one while the note around it is
     being rewritten: the same box, so nothing reflows on the way in */
  .answer.held {
    cursor: text;
    border-radius: 4px;
    margin-left: -4px;
    padding-left: 4px;
    padding-right: 4px;
  }
  /* paper you can write on: a hand-drawn rule appears under the line —
     under a pointer only. A finger's tap leaves :hover stuck on what it
     touched, and on a phone the section just tapped kept a dashed rule
     under every line of it, a third look beside the blank and the
     underline. */
  @media (hover: hover) {
    .editable:hover {
      text-decoration: underline dashed var(--ink3);
      text-decoration-thickness: 1px;
      text-underline-offset: 4px;
    }
  }
  .editable.editing:hover {
    text-decoration-style: solid;
    text-decoration-color: var(--ink3);
  }
  /* editing: no box, only the caret and a hairline under the words, the
     way a line on paper is written on */
  .editable.editing {
    outline: none;
    background: none;
    /* `pre-wrap` is on in both states, at rest and here. It was on in
       neither, then in one: whichever way round, the words that are read
       and the words that are written have to be set the same, or they move
       at the click. */
    text-decoration: underline;
    text-decoration-color: var(--ink3);
    text-decoration-thickness: 1px;
    text-underline-offset: 4px;
  }
  /*
   * The story's words are a paragraph, not a line: a rule under every line
   * of it, while it is written, turned it into ruled paper and made it hard
   * to read. So in the story the words are written on as they stand — the
   * caret says where, and they come up to full ink — while a line (a
   * bullet, an answer) keeps its hairline. The dashed rule on hover stays:
   * it says the words can be written on before they are. Only colour and
   * decoration change, never the box, so nothing moves at the click.
   */
  .note-text.editable.editing,
  .note-text.editable.editing:hover {
    text-decoration: none;
    color: var(--ink);
    caret-color: var(--ink);
  }
  /* the gap on the card being asked */
  .note-gap {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin-top: 4px;
  }
  .note-gap-q {
    font-size: 14px;
    font-weight: 500;
    line-height: 1.5;
    color: var(--ink);
  }
  /* where in the run this question is: at the end of it, and quiet */
  .note-gap-q small {
    margin-left: 6px;
    font-size: 11.5px;
    font-weight: 400;
    color: var(--ink3);
    white-space: nowrap;
  }
  .note-gap-line input {
    width: 100%;
    max-width: 48ch;
    background: none;
    border: 0;
    border-bottom: 1px dashed var(--ink3);
    color: var(--ink);
    font-family: var(--serif);
    font-style: italic;
    font-size: 16px;
    padding: 2px 0 3px;
    outline: none;
  }
  .note-gap-line input::placeholder {
    font-family: var(--body, inherit);
    font-style: normal;
    font-size: 13px;
    color: var(--ink3);
  }
  .note-gap-line input:focus {
    border-bottom-color: var(--ink);
  }
  /* the ask as a sentence, the field sitting in its blank */
  .note-gap-fill {
    display: block;
    font-size: 13.5px;
    line-height: 2;
    color: var(--ink2);
  }
  .note-gap-fill input {
    display: inline-block;
    width: auto;
    max-width: 100%;
    margin: 0 3px;
    vertical-align: baseline;
    background: none;
    border: 0;
    border-bottom: 1px dashed var(--ink3);
    color: var(--ink);
    font-family: var(--serif);
    font-style: italic;
    font-size: 16px;
    padding: 0 2px 1px;
    outline: none;
  }
  /* the example in the blank: the same hand, only quieter, so nothing
     shifts when the person starts to write */
  .note-gap-fill input::placeholder {
    color: var(--ink3);
    opacity: 1;
  }
  .note-gap-fill input:focus {
    border-bottom-color: var(--ink);
  }
  /* the agent's note: what the card will say, a blank for each fact it
     still lacks; an answer sits in its blank in the person's hand */
  .note-agent {
    margin: 0;
    font-size: 14px;
    line-height: 1.8;
    color: var(--ink);
  }
  .note-agent em {
    font-family: var(--serif);
    font-style: italic;
    font-size: 16px;
    padding: 0 1px 1px;
    border-bottom: 1px solid var(--paper-edge);
  }
  .note-agent .blank {
    color: var(--ink3);
    padding: 0 2px 1px;
    border-bottom: 1px dashed var(--ink3);
  }
  .note-agent input {
    display: inline-block;
    width: auto;
    max-width: 100%;
    margin: 0 3px;
    vertical-align: baseline;
    background: none;
    border: 0;
    border-bottom: 1px dashed var(--ink3);
    color: var(--ink);
    font-family: var(--serif);
    font-style: italic;
    font-size: 16px;
    padding: 0 2px 1px;
    outline: none;
  }
  .note-agent input::placeholder {
    color: var(--ink3);
    opacity: 1;
  }
  .note-agent input:focus {
    border-bottom-color: var(--ink);
  }
  /* the note as the person's to rewrite: a dashed frame on hover rather
     than a rule under every line; typing, it keeps the note's measure */
  .note-own.editable:hover {
    text-decoration: none;
    outline: 1px dashed var(--ink3);
    outline-offset: 6px;
    border-radius: 2px;
  }
  .note-own.editing {
    outline: none;
  }
  .note-own.editing .note-agent {
    white-space: pre-wrap;
  }
  /* a token in the note: the caret goes round it, not into it */
  .note-own.editing .note-agent em,
  .note-own.editing .note-agent .blank {
    user-select: none;
    -webkit-user-modify: read-only;
  }
  .note-own.mine .note-agent {
    border-left: 2px solid var(--paper-edge);
    padding-left: 12px;
    margin-left: -14px;
  }
  /* a sticky note: short lines, hand-set, nothing that reads as a form */
  .note-scratch {
    list-style: none;
    margin: 4px 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 4px;
    font-size: 14px;
    line-height: 22px;
    color: var(--ink);
  }
  .note-scratch li {
    padding-left: 14px;
    text-indent: -14px;
  }
  .note-scratch li::before {
    content: '•';
    display: inline-block;
    width: 14px;
    text-indent: 0;
    color: var(--ink3);
    font-size: 12px;
    vertical-align: 1px;
  }
  .note-scratch li.unsure {
    color: var(--ink2);
  }
  /* asked for by name: the lines the card is short of.
     The same ring a question wears, and in the same colour: one dashed
     hairline round the line, 1px on a 3/3 dash, a 6px corner, in the
     accent. The board says "here" one way. A question's ring travels,
     because it is arriving; this one is simply there, because it was
     asked for. */
  .note-scratch li.wanted {
    position: relative;
  }
  .note-scratch li.wanted::after {
    content: '';
    position: absolute;
    inset: -3px -8px -4px -6px;
    border: 1px dashed var(--accent);
    border-radius: 6px;
    pointer-events: none;
    animation: wanted-ring 1.9s ease-out both;
  }
  .note-scratch li small.fill {
    cursor: pointer;
    text-decoration: underline dotted;
    text-underline-offset: 3px;
  }
  .note-scratch li small.fill:hover {
    color: var(--ink);
  }
  .note-scratch li small {
    font-size: 11px;
    letter-spacing: 0.04em;
    color: var(--ink3);
    margin-left: 8px;
  }
  /* a line the person put on the card, kept as they broke it: once it is
     a bullet it sits with the others, in the list's hand */
  .note-scratch li.yours em {
    white-space: pre-wrap;
    font-style: normal;
    font-size: 14px;
    line-height: 22px;
    color: var(--ink);
  }
  /* files on the card, seen as what they are: small chips */
  .note-scratch li.attached {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    padding-left: 14px;
    text-indent: 0;
  }
  .note-scratch li.attached::before {
    margin-left: -14px;
  }
  /* A link on the card: a line of the list, not a pill. Its glyph stands
     where the bullet does, the title carries a hairline underline (the
     weight of the dotted edit underline), and its site and what it gave the
     card follow in the source tag's quiet voice. */
  .note-scratch li.linked {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    column-gap: 0;
    padding-left: 14px;
    text-indent: 0;
  }
  .note-scratch li.linked::before {
    content: none;
  }
  .note-scratch li.linked .link-glyph {
    width: 14px;
    margin-left: -14px;
    flex: none;
    align-self: center;
    color: var(--ink3);
  }
  /* the link's own mark: a small chain, so it is not read as the arrow the
     card uses for "builds on card 2" */
  .note-scratch li.linked .link-glyph svg,
  .file.link .ft svg,
  .staged span.link svg {
    display: block;
    width: 11px;
    height: 11px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.4;
    stroke-linecap: round;
  }
  .file.link .ft svg {
    width: 13px;
    height: 13px;
  }
  .note-scratch a.link-line {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--ink);
    text-decoration: underline;
    text-decoration-color: var(--rule-strong);
    text-decoration-thickness: 1px;
    text-underline-offset: 3px;
    transition: text-decoration-color 0.15s;
  }
  .note-scratch a.link-line:hover {
    text-decoration-color: var(--ink2);
  }
  .note-scratch li.linked small.link-state {
    margin-left: 6px;
  }
  .note-scratch li.linked small.link-state:empty {
    display: none;
  }
  .note-scratch .chip {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    max-width: 100%;
    padding: 1px 9px 1px 7px;
    border: 1px solid var(--rule);
    border-radius: 999px;
    background: var(--fill-04);
    font-size: 12.5px;
    line-height: 18px;
    color: var(--ink);
    text-decoration: none;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .note-scratch .chip svg {
    flex: none;
    width: 12px;
    height: 12px;
    fill: none;
    stroke: var(--ink3);
    stroke-width: 1.3;
    stroke-linecap: round;
    stroke-linejoin: round;
  }
  .note-scratch a.chip:hover {
    border-color: var(--ink3);
    background: var(--fill-08);
  }
  .note-scratch a.chip span {
    color: var(--ink3);
    font-size: 11px;
  }
  /* the empty bullet at the end: written into in the person's hand */
  .note-scratch li.new input {
    width: 100%;
    background: none;
    border: 0;
    outline: none;
    padding: 0;
    color: var(--ink);
    font: inherit;
  }
  .note-scratch li.new input::placeholder {
    font-size: 13px;
    color: var(--ink3);
    opacity: 0.7;
  }
  /* The line to write on is always there, faint, and full under the hand
     or the keyboard. Hidden at rest it still kept its room — so a section
     never changed size as the pointer crossed it — and that room read as a
     gap with nothing in it, most of all on a card with no points yet. Its
     room is kept either way; now it says what it is for. The corner's
     tools still show only under the hand. */
  .note-scratch li.new {
    /* a shorter row than a written line */
    line-height: 18px;
    opacity: 0.55;
    transition: opacity 0.15s;
  }
  .note-scratch li.new::before {
    opacity: 0.5;
  }
  .note:hover .note-scratch li.new,
  .note:focus-within .note-scratch li.new {
    opacity: 1;
  }
  @media (hover: none) {
    .note-scratch li.new {
      opacity: 0.7;
    }
  }
  .note-scratch li.ask::before {
    content: '';
  }
  /* the question, where it is asked: one more line of the note, its bullet
     a question mark in the accent; the blank a hairline as wide as the
     answer. Nothing boxed: a note is not a form */
  .note-scratch li.ask {
    position: relative;
    /* the marker in the gutter, the question beside it: a row, so the
       question's first line sits on the "?" the way a bullet's first line
       sits on its dot, and its wrapped lines keep the same left edge */
    display: flex;
    gap: 0;
    padding-left: 0;
    text-indent: 0;
    /* the question is the last thing on the card, under a hairline: the
       list above it ends where it always ends, and what is being asked
       reads as asked of that list rather than as one of its lines. The
       rule runs the full width, bullet gutter included. */
    margin: 12px 0 2px;
    padding-top: 12px;
    /* dashed, like every other line on this card that is waiting to be
       written on: the blank under an answer, the hover rule under a line */
    border-top: 1px dashed var(--rule);
  }
  .slots.settled .note-scratch li.ask {
    animation: note-in 0.26s cubic-bezier(0.2, 0.7, 0.2, 1) both;
  }
  @media (prefers-reduced-motion: reduce) {
    .slots.settled .note-scratch li.ask input {
      animation: none;
    }
  }
  /* the answer, the moment it goes: the same line, now in the person's
     hand. It comes up as the card settles round it rather than landing
     before the card has moved */
  .slots.settled .note-scratch li.sent {
    animation: note-in 0.42s cubic-bezier(0.2, 0.7, 0.2, 1) both;
  }
  /* the answer has gone and the card is waiting on what it means: the
     card's glyph breathes, and the line it went from is held light until
     the card comes back with it */
  .note.taking .note-glyph {
    stroke: var(--accent);
    animation: pulse var(--work-beat) ease-in-out infinite;
  }
  .note.taking .note-scratch li.sent em {
    color: var(--ink2);
  }
  .note.taking .note-word {
    color: var(--ink2);
  }
  .note-scratch li.ask::before {
    content: '?';
    flex: none;
    width: 14px;
    color: var(--accent);
    /* the same gutter and the same optical weight as the bullets above it:
       a question in the list's hand, not a heading over it */
    font-size: 12px;
    font-weight: 600;
    vertical-align: 1px;
  }
  /* one bullet line: the question, then the blank in it, wrapping as a
     bullet wraps; answered, the same place holds the answer as a bullet in
     the person's hand */
  .note-scratch li.ask .note-gap {
    display: block;
    flex: 1;
    min-width: 0;
    margin: 0;
    line-height: 22px;
  }
  .note-scratch li.ask .note-gap-q {
    display: block;
    font-size: 14px;
    font-weight: 400;
    color: var(--ink);
    white-space: normal;
  }
  .note-scratch li.ask .note-gap-q small {
    margin-left: 6px;
    font-size: 11px;
    color: var(--ink3);
  }
  .note-scratch li.ask .note-gap-line {
    display: block;
    margin-top: 1px;
  }
  .note-scratch li.ask .note-gap-line .note-gap-send {
    margin-left: 6px;
  }
  .note-scratch li.ask .note-gap-line input {
    width: auto;
    max-width: 100%;
    padding: 1px 0 2px;
  }
  .note-scratch li.ask input {
    font-size: 15px;
    line-height: 20px;
  }
  .note-scratch li.ask .note-gap-line input::placeholder {
    font-family: var(--serif);
    font-style: italic;
    font-size: 15px;
    color: var(--ink3);
    opacity: 1;
  }
  /* the blank's own line is the gesture: a dashed accent underline that
     draws itself in from the left when the question lands, and stays. No
     ring round the question — whatever the question is made of, this is
     the thing to write on. */
  .note-scratch li.ask input {
    border-bottom: 0;
    background-image: repeating-linear-gradient(
      to right,
      var(--accent) 0 3px,
      transparent 3px 6px
    );
    background-repeat: no-repeat;
    background-position: 0 100%;
    background-size: 100% 1px;
  }
  .slots.settled .note-scratch li.ask input {
    animation: blank-in 0.62s cubic-bezier(0.22, 0.72, 0.24, 1) 0.1s both;
  }
  .note-scratch li.ask .note-gap-fill {
    display: inline;
    font-size: 14px;
    line-height: 22px;
    color: var(--ink);
  }
  /* the answers on offer: words to pick, not buttons on a form */
  .note-scratch li.ask .note-gap-chips {
    /* the options hang off the question: closer to it than the question is
       to the list, so the two read as one thing being asked */
    margin-top: 8px;
    gap: 6px;
    row-gap: 6px;
  }
  .note-scratch li.ask .note-gap-chips button {
    padding: 2px 9px;
    border-radius: 999px;
    border: 1px solid var(--rule);
    background: none;
    font-size: 12.5px;
    line-height: 18px;
    color: var(--ink2);
    transition:
      color 0.15s,
      border-color 0.15s,
      background 0.15s;
  }
  .note-scratch li.ask .note-gap-chips button:hover {
    color: var(--ink);
    border-color: var(--ink3);
    background: var(--fill-04);
  }
  /* the card being asked: a trace of the accent on its top edge, and its
     glyph's dot filled in the accent. Still: "needs you" is not the app
     being busy, and a breathing dot read as if it were (motion/vocabulary) */
  .slot.flow .note[data-state='asking'] {
    --paper-edge: color-mix(in srgb, var(--accent) 30%, transparent);
  }
  .note[data-state='asking'] .note-glyph .fill {
    fill: var(--accent);
  }
  /* how the answer goes: a word at the end of the blank, there once there
     is something to send, gone otherwise; Enter or a click on it */
  .note-gap-send {
    display: inline-flex;
    align-items: baseline;
    gap: 3px;
    margin: 0;
    padding: 0;
    vertical-align: baseline;
    font-family: var(--body, inherit);
    font-style: normal;
    font-size: 11px;
    line-height: 1.5;
    letter-spacing: 0.02em;
    color: var(--ink3);
    background: none;
    border: 0;
    opacity: 0;
    cursor: default;
    transition:
      opacity 0.2s,
      color 0.15s;
  }
  .note-gap-send kbd {
    font: inherit;
  }
  .note-gap-send.ready {
    opacity: 1;
    cursor: pointer;
  }
  .note-gap-send.ready:hover {
    color: var(--ink);
  }
  .note-gap-fill .note-gap-send {
    margin-left: 4px;
  }
  /* A section being written: bars as long as the paragraph will be, each
     row the height of a line of the words (`.note-text`'s own 22px), so
     the card keeps the room and the words land into it (`Waiting`,
     `Landing`). One bar stood for a paragraph, and the card grew by the
     paragraph in the frame the words came. */
  .note-text.waiting {
    display: block;
    padding: 0;
  }
  .note-text.waiting span {
    display: block;
    position: relative;
    height: 22px;
  }
  .note-text.waiting span::before {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    top: 7px;
    height: 8px;
    border-radius: 5px;
    background: linear-gradient(
      90deg,
      var(--fill-04) 0%,
      var(--fill-08) 50%,
      var(--fill-04) 100%
    );
    background-size: 200% 100%;
    animation: sheen var(--work-sweep) linear infinite;
  }
  @media (prefers-reduced-motion: reduce) {
    .note-text.waiting span::before {
      animation: none;
    }
  }
  /* the card as a working note: four short lines, one voice */
  .note-lines {
    margin: 2px 0 0;
    display: grid;
    grid-template-columns: max-content 1fr;
    column-gap: 16px;
    row-gap: 7px;
    align-items: baseline;
  }
  .note-lines > div {
    display: contents;
  }
  .note-lines dt {
    font-size: 10.5px;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink3);
    padding-top: 3px;
  }
  .note-lines dd {
    margin: 0;
    min-width: 0;
    font-size: 14px;
    line-height: 1.6;
    color: var(--ink2);
  }
  .note-lines dd.say {
    color: var(--ink);
    font-size: 15px;
  }
  .note-lines .fact,
  .note-lines .need {
    display: inline;
  }
  .note-lines .fact + .fact::before,
  .note-lines .need + .need::before {
    content: ' · ';
    color: var(--ink3);
  }
  .note-lines .fact small {
    font-size: 10.5px;
    letter-spacing: 0.04em;
    color: var(--ink3);
    margin-left: 6px;
  }
  .note-lines .fact em {
    font-family: var(--serif);
    font-style: italic;
    font-size: 16px;
    color: var(--ink);
  }
  .note-lines .blank {
    color: var(--ink3);
    border-bottom: 1px dashed var(--ink3);
    padding: 0 6px 1px;
  }
  .note-lines .note-gap {
    margin-top: 0;
  }
  .note-sum.gist.say {
    font-family: inherit;
    font-style: normal;
    font-size: 14.5px;
    line-height: 1.55;
    color: var(--ink);
    white-space: normal;
    display: -webkit-box;
    -webkit-line-clamp: 5;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }
  /* the story summed up above the cards, the same four lines */
  .sum-up {
    margin: 8px 0 0;
    display: grid;
    grid-template-columns: max-content 1fr;
    column-gap: 16px;
    row-gap: 4px;
    align-items: baseline;
    /* the column is the measure */
    max-width: none;
  }
  .sum-up > div {
    display: contents;
  }
  .sum-up dt {
    font-size: 10.5px;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink3);
  }
  .sum-up dd {
    margin: 0;
    font-size: 13.5px;
    color: var(--ink2);
  }
  /* what the agent found in the material, and where */
  .note-found {
    list-style: none;
    margin: 6px 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 3px;
    font-size: 13px;
    line-height: 1.5;
    color: var(--ink2);
  }
  .note-found li {
    display: flex;
    align-items: baseline;
    gap: 8px;
    min-width: 0;
  }
  .note-found li::before {
    content: '';
    flex: none;
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: var(--ink3);
    transform: translateY(-2px);
  }
  .note-found span {
    min-width: 0;
  }
  .note-found small {
    flex: none;
    font-size: 11px;
    letter-spacing: 0.02em;
    color: var(--ink3);
  }
  /* words the person wrote themselves: a rule in the margin, in their hand */
  .note-text.mine {
    border-left: 2px solid var(--paper-edge);
    padding-left: 12px;
    margin-left: -14px;
  }
  /* The words the agent replaced: one quiet line under a section's words,
     and, opened, the change read as a tracked change */
  .note-earlier {
    margin-top: 8px;
  }
  .note-earlier-bar {
    display: flex;
    flex-wrap: wrap;
    gap: 4px 14px;
  }
  .note-earlier-bar button {
    padding: 0;
    border: 0;
    background: none;
    font: inherit;
    font-size: 12.5px;
    color: var(--ink3);
    cursor: pointer;
    text-decoration: underline;
    text-decoration-color: color-mix(in srgb, currentColor 35%, transparent);
    text-underline-offset: 3px;
  }
  .note-earlier-bar button:hover {
    color: var(--ink);
  }
  .note-text.note-diff {
    margin-top: 8px;
    padding-left: 12px;
    border-left: 2px solid var(--rule);
  }
  .note-diff del {
    color: var(--ink3);
    text-decoration: line-through;
    text-decoration-color: color-mix(in srgb, currentColor 60%, transparent);
  }
  .note-diff ins {
    text-decoration: none;
    background: color-mix(in oklab, var(--accent) 18%, transparent);
    border-radius: 3px;
  }
  /* a card the chat just changed: the word stays until the card is opened.
     What changed on it is tinted where it stands (`.note-tints`); the ring
     that faded round it was drawn outside the card's clip, and never seen. */
  .note.changed .note-word {
    color: var(--accent);
  }

  /* A card taken off the board: one quiet line where it stood, the way back
     beside it. The line is no taller than a line of the card's own, so the
     board closes up by the card and nothing else moves. */
  /* in the slot of the card before the gap, a gutter under that card */
  .slot > .removed-line,
  .slot > .slot-adding {
    margin-top: var(--gutter, 36px);
  }
  .removed-line {
    display: flex;
    align-items: baseline;
    gap: 10px;
    margin: 0;
    padding: 0 0 0 2px;
    font-size: 12.5px;
    line-height: 20px;
    color: var(--ink3);
  }
  .removed-line button {
    padding: 0;
    border: 0;
    background: none;
    font: inherit;
    color: var(--ink2);
    text-decoration: underline;
    text-decoration-color: color-mix(in srgb, var(--ink3) 50%, transparent);
    text-underline-offset: 3px;
    cursor: pointer;
    transition:
      color 0.15s,
      text-decoration-color 0.15s;
  }
  .removed-line button:hover,
  .removed-line button:focus-visible {
    color: var(--ink);
    text-decoration-color: currentColor;
  }

  /* A card being added: the paper of a card with nothing on it yet, the
     number it will have and one line to name it. Dashed until it is named;
     named, the same box reads "Adding…" until the card itself lands. */
  .slot-adding {
    /* as wide as a card at rest, so the card that takes its place does not
       change the width of what was there */
    width: 100%;
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 14px 20px 16px;
    border: 1px dashed color-mix(in srgb, var(--ink) 18%, transparent);
    border-radius: 4px;
    background: color-mix(in srgb, #ffffff 3%, transparent);
  }
  .slot-adding-n {
    font-size: 11.5px;
    letter-spacing: 0.02em;
    color: var(--ink3);
  }
  .slot-adding-title {
    display: flex;
    align-items: baseline;
    gap: 10px;
    width: 100%;
    margin: 0;
    padding: 0;
    border: 0;
    outline: 0;
    background: none;
    color: var(--ink);
    font-family: var(--serif);
    font-style: italic;
    font-size: 17px;
    line-height: 1.3;
  }
  /* a line on a card, not a form field: the caret says where you are */
  .slot-adding .slot-adding-title:focus-visible {
    outline: none;
  }
  .slot-adding-title::placeholder {
    color: var(--ink3);
  }
  .slot-adding-title small {
    font-family: inherit;
    font-style: normal;
    font-size: 12px;
    color: var(--ink3);
  }
  .slot-adding:has(.sent) {
    border-style: solid;
    border-color: var(--rule);
  }
  /* at rest the note is the gist, a few lines of it */
  .note-sum.gist {
    font-family: inherit;
    font-style: normal;
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
  }
  .note-sum.gist .note-agent {
    font-size: 13.5px;
    line-height: 1.6;
    color: var(--ink2);
    display: -webkit-box;
    -webkit-line-clamp: 5;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }
  .note-sum.gist .note-agent em {
    font-size: 15px;
  }
  .note-gap-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
  }
  .note-gap-chips button {
    font-size: 12.5px;
    padding: 4px 10px;
    border-radius: 999px;
    border: 1px solid var(--rule);
    background: var(--fill-04);
    color: var(--ink);
  }
  .note-gap-chips button:hover {
    background: var(--fill-08);
  }
  /* the jot line */
  /* the card's corner, in from the fold: the two things done to a card
     as a whole, quiet but in view, clearer under the hand */
  /* centred on the head row: the row's middle is 27px down the card */
  .note-corner {
    position: absolute;
    top: 16px;
    right: 24px;
    display: flex;
    gap: 2px;
  }
  .note-tool {
    width: 22px;
    height: 22px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    border: 0;
    border-radius: 6px;
    background: none;
    color: var(--ink3);
    opacity: 0;
    cursor: pointer;
    transition:
      opacity 0.15s,
      color 0.15s,
      background 0.15s;
  }
  .note-tool svg {
    width: 14px;
    height: 14px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.3;
    stroke-linecap: round;
    stroke-linejoin: round;
  }
  .note:hover .note-tool,
  .note:focus-within .note-tool {
    opacity: 0.75;
  }
  @media (hover: none) {
    .note-tool {
      opacity: 0.5;
    }
  }
  /* under the hand, full ink: scoped to the note so it outranks the
     note's own hover, which otherwise held the tool at three quarters */
  .note .note-tool:hover,
  .note .note-tool:focus-visible {
    opacity: 1;
    color: var(--ink);
    background: var(--fill-08);
  }
  /* room for the two tools and air before them */
  .slot.flow .note-head {
    padding-right: 80px;
  }
  /* a file held over the card: the sheet says it will take it */
  .note.dropping {
    outline: 1.5px dashed var(--accent);
    outline-offset: -8px;
  }
  .note.dropping .note-tool:first-child {
    opacity: 1;
    color: var(--accent);
  }
  .note-pulse {
    position: absolute;
    left: 28px;
    right: 0;
    bottom: -1px;
    height: 1px;
    background: linear-gradient(90deg, transparent, var(--accent), transparent);
    animation: sweep var(--work-sweep) linear infinite;
  }
  .slot-frame-pulse {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 2px;
    background: linear-gradient(90deg, transparent, var(--accent), transparent);
    animation: sweep var(--work-sweep) linear infinite;
  }
  /* the meta line, as the player sets it */
  .slot-meta {
    display: flex;
    /* centred, not on a baseline: the grip and the tools are icons beside
       words of two sizes, and on a baseline each sat at its own height */
    align-items: center;
    /* one height, open or closed: this line carries a voice control at rest
       and an editing control while a slide is open, and a card that changes
       height between them moves every card below it */
    min-height: 24px;
    gap: 12px;
    /* the same quiet label as "Speaker notes" under it: two lines that
       belong to one slide read as one set, not as a caption in capitals
       over a label in sentence case at a third size */
    font-size: 12.5px;
    color: var(--ink3);
    font-variant-numeric: tabular-nums;
  }
  .slot-meta b {
    color: var(--ink);
    font-weight: 500;
    font-size: 14px;
    min-width: 0;
  }
  .slot-meta .mark {
    margin-left: auto;
  }
  .slot.writing .slot-meta .mark,
  .slot.asking .slot-meta .mark {
    color: var(--accent);
  }
  .slot.done .slot-meta .mark {
    color: var(--ok);
  }
  /* A slide whose neighbour moved: its words were written for that one.
     The accent, as a card the chat changed is marked: somewhere to look. */
  .slot-meta .mark.reread {
    color: var(--accent);
  }
  /* A slide's own controls — up, down, a copy, out — at the right of the
     line that names it, quiet until the slide is under the hand, as a
     card's corner tools are. */
  .slide-tools {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    margin-left: auto;
    align-self: center;
    letter-spacing: 0;
    text-transform: none;
  }
  .slot-meta .mark + .slide-tools {
    margin-left: 0;
  }
  .slide-tool {
    width: 24px;
    height: 24px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    border: 0;
    border-radius: 6px;
    background: none;
    color: var(--ink3);
    opacity: 0;
    cursor: pointer;
    transition:
      opacity 0.15s,
      color 0.15s,
      background 0.15s;
  }
  .slide-tool svg {
    width: 14px;
    height: 14px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.3;
    stroke-linecap: round;
    stroke-linejoin: round;
  }
  .slot-slide:hover .slide-tool,
  .slot-slide:focus-within .slide-tool {
    opacity: 0.75;
  }
  @media (hover: none) {
    .slide-tool {
      opacity: 0.5;
    }
  }
  .slot-slide .slide-tool:hover:not(:disabled),
  .slot-slide .slide-tool:focus-visible {
    opacity: 1;
    color: var(--ink);
    background: var(--fill-03);
  }
  .slot-slide .slide-tool:disabled {
    opacity: 0.25;
    cursor: default;
  }
  /* taking a slide out asks first, in the controls' own place */
  .slide-tools.confirming {
    gap: 8px;
    font-size: 12.5px;
    color: var(--ink);
  }
  .slide-confirm {
    border: 1px solid var(--rule);
    border-radius: 999px;
    padding: 0 10px;
    height: 24px;
    font: inherit;
    font-weight: 500;
    white-space: nowrap;
    background: var(--panel);
    color: var(--ink);
    cursor: pointer;
  }
  .slide-confirm:hover:not(:disabled) {
    border-color: var(--rule-strong);
  }
  .slide-confirm.danger {
    border-color: transparent;
    background: var(--danger);
    color: var(--bg);
  }
  .slide-confirm:disabled {
    opacity: 0.5;
    cursor: default;
  }
  /* what the host said when it would not make the move */
  .slide-refused {
    margin: 0;
    font-size: 12.5px;
    line-height: 18px;
    color: var(--danger);
  }
  .slot-text {
    margin: 0;
    font-size: 16px;
    line-height: 1.6;
    color: var(--ink2);
    max-width: 36em;
  }
  /* the writer's marks, read for a person: an assumption, a missing fact */
  .slot-text mark {
    display: inline-block;
    vertical-align: baseline;
    margin: 0 2px 0 4px;
    padding: 0 6px;
    border-radius: 999px;
    background: transparent;
    font-size: 11px;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    line-height: 1.6;
    color: var(--ink3);
    border: 1px solid var(--rule);
    cursor: help;
  }
  .slot-text mark.missing {
    text-transform: none;
    letter-spacing: 0;
    font-size: 12.5px;
    color: var(--warn);
    border-color: color-mix(in srgb, var(--warn) 40%, transparent);
  }
  .slot-ask,
  .slot-issues,
  .slot-answers {
    max-width: 36em;
  }
  /* the narration sits under its slide, as wide as the slide */
  /* ---- one slide, opened large ----------------------------------------
     The list is the whole deck at a glance; a slide is looked at, and
     written on, here. The conversation folds away for as long as it is
     open, so the slide has the room. */
  .b[data-focusing='true'] {
    grid-template-columns: 0 minmax(0, 1fr);
  }
  .b[data-focusing='true'] .chat,
  .b[data-focusing='true'] .chat-resize {
    visibility: hidden;
    overflow: hidden;
  }
  /* the stage's column opens up to the slide, and what sits above the
     slides — the brief, the section label — steps aside while one is open */
  .b[data-focusing='true'] .scroller-in {
    max-width: 1240px;
    padding-top: 0;
  }
  .b[data-focusing='true'] .scroller-in > .spine,
  .b[data-focusing='true'] .slots {
    max-width: none;
  }
  .focus {
    /* as big as the pane allows while its words stay in view under it: the
       slide never grows past the height that would push the narration off
       the bottom of the screen */
    width: min(100%, 1240px, calc((100vh - 280px) * 16 / 9));
    width: min(100%, 1240px, calc((100dvh - 280px) * 16 / 9));
    margin: 0 auto;
    padding: 18px 0 48px;
    display: flex;
    flex-direction: column;
    gap: 12px;
    animation: fade-in 0.2s ease both;
  }
  /* A phone on its side has no room for the words under the slide as well:
     the slide takes the height between the bars, and the words follow it
     down the pane. */
  @media (max-height: 500px) {
    .focus {
      width: min(100%, calc((100dvh - 150px) * 16 / 9));
      padding-top: 8px;
    }
  }
  .focus-bar {
    display: flex;
    align-items: center;
    gap: 14px;
    font-size: 12px;
    color: var(--ink3);
  }
  .focus-back {
    border: 0;
    background: none;
    padding: 4px 0;
    font-size: 13px;
    color: var(--ink2);
    cursor: pointer;
  }
  .focus-back:hover {
    color: var(--ink);
  }
  .focus-where {
    letter-spacing: 0.06em;
    text-transform: uppercase;
    font-variant-numeric: tabular-nums;
  }
  .focus-step {
    margin-left: auto;
    display: inline-flex;
    gap: 4px;
  }
  .focus-step button {
    width: 30px;
    height: 30px;
    border: 1px solid var(--rule);
    border-radius: 50%;
    background: var(--bg);
    color: var(--ink2);
    font-size: 16px;
    line-height: 1;
    cursor: pointer;
  }
  .focus-step button:hover:not(:disabled) {
    color: var(--ink);
    border-color: var(--ink3);
  }
  .focus-step button:disabled {
    opacity: 0.35;
    cursor: default;
  }
  .focus-slide {
    position: relative;
    border-radius: 10px;
    overflow: hidden;
    box-shadow: 0 1px 2px rgb(0 0 0 / 0.06), 0 12px 32px -18px rgb(0 0 0 / 0.25);
  }
  .focus-meta:empty {
    display: none;
  }
  /* the slide open to edit: the same card as the list's, holding the
     counter that follows the words being written */
  .edit-slide {
    display: flex;
    flex-direction: column;
    position: relative;
  }
  .edit-frame {
    position: relative;
  }
  /* ─────────────────────────────────────────────── a slide being written on
     Everything about editing that is not the words themselves lives here,
     in the line that already says which slide this is. Drawn on the slide,
     it was a counter over a photograph and an instruction across the words
     underneath — chrome on the artwork. */
  .edit-rail:empty {
    display: none;
  }
  /* While a slide is open, this line is the editing line: what it says the
     rest of the time — which slide, its voice — is not what the person is
     doing, and carrying both wraps the row and makes the card taller.
     The name is `edit-line` rather than `writing` because a card being
     written is `.slot.writing`, and a bare `.writing` rule in here took
     that card's height — 26px, this line's height, on a card holding a
     hundred — so every card was cut off for exactly as long as it was
     being written. A class in this file is global to the app; a word as
     general as "writing" is not a name. */
  .slot-meta:has(.edit-line) > :not(.edit-rail) {
    display: none;
  }
  /* a box, not an inline span: an inline wrapper adds a line box of its own
     round the line, a pixel taller than it, and the row grows by that */
  .slot-meta:has(.edit-line) > .edit-rail {
    display: flex;
    width: 100%;
  }
  .slot-meta:has(.edit-line) {
    flex-wrap: nowrap;
    /* top, not baseline: alone on the row, the line is set by its own box,
       which lands its words where the caption's were and the row at 26px */
    align-items: flex-start;
  }
  .edit-line {
    display: inline-flex;
    /* its words on the same line as the caption they replace: centred in
       the 26px row they sat 4px lower than it, and read as the row dropping */
    align-items: flex-start;
    gap: 10px;
    width: 100%;
    /* the line's own height, so opening a slide moves nothing */
    height: 26px;
    letter-spacing: 0.06em;
    color: var(--ink2);
    /* a fade, not a rise: anything that moves on the way in is a jump */
    animation: fade-in-line 0.2s ease-out both;
  }
  .edit-line-dot {
    flex: none;
    /* centred on the words' 18px line */
    margin-top: 6px;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--accent);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent);
  }
  .edit-line.over .edit-line-dot {
    background: var(--warn);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--warn) 18%, transparent);
  }
  .edit-line.kept .edit-line-dot {
    background: var(--ok);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--ok) 18%, transparent);
  }
  .edit-line-what {
    text-transform: uppercase;
  }
  .edit-line.over .edit-line-what {
    color: var(--warn);
    text-transform: none;
    letter-spacing: 0.01em;
  }
  /* next to the part it counts, not across the slide from it: on a wide
     slide the far end is a long way from the word it belongs to */
  .edit-line-count {
    /* centred on the label's 18px line (the pill is 16px) */
    margin-top: 1px;
    font-style: normal;
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.02em;
    font-size: 11.5px;
    line-height: 14px;
    padding: 1px 8px;
    border-radius: 999px;
    color: var(--ink3);
    background: var(--fill-06);
    box-shadow: inset 0 0 0 1px var(--rule);
  }
  .edit-line.over .edit-line-count {
    color: var(--warn);
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--warn) 45%, transparent);
  }
  /* full is said as the last letter goes in, not one letter later — the
     place is at its limit then, and the next letter will be refused — and
     a refused letter washes the count for a moment (`at-cap`; the shake is
     played from the editor, and not under reduced motion) */
  .edit-line-count.full,
  .edit-line-count.at-cap {
    color: var(--warn);
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--warn) 45%, transparent);
  }
  .edit-line-count.at-cap {
    background: color-mix(in srgb, var(--warn) 16%, transparent);
  }
  /* a letter typed at the slide before any words were clicked: the line
     that says what to click lights up, and fades back */
  .edit-line-what {
    transition: color 0.4s ease-out;
  }
  .edit-line.nudge .edit-line-what {
    color: var(--ink);
    transition-duration: 0.08s;
  }
  .edit-line-dot {
    transition: box-shadow 0.4s ease-out;
  }
  .edit-line.nudge .edit-line-dot {
    box-shadow: 0 0 0 5px color-mix(in srgb, var(--accent) 32%, transparent);
    transition-duration: 0.08s;
  }
  /* Whether the words are in the deck: said where it happens, quietly, and
     gone again. The row keeps its height whether it is there or not. */
  /* whether the words are in the deck: the far end, apart from the part */
  .edit-line-kept {
    margin-left: auto;
    margin-top: 1px;
    font-size: 11px;
    line-height: 16px;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    /* on its way: quiet. What lands is `.done`, said apart from it */
    color: var(--ink3);
    animation: fade-in-line 0.2s ease-out both;
  }
  .edit-line-kept.done,
  .edit-rail-saved {
    color: var(--ok);
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.01em;
    text-transform: none;
  }
  .edit-line-kept.done i,
  .edit-rail-saved i {
    font-style: normal;
  }
  /* "Saved" on the card's own line, after a slide closed with words in it */
  .edit-rail-saved {
    white-space: nowrap;
    line-height: 16px;
    animation: fade-in-line 0.2s ease-out both;
  }
  /* a slide in the list: the card it sits in, and the way to see it large */
  .slide-slot {
    position: relative;
    border-radius: 12px;
    transition: box-shadow 0.2s ease-out;
  }
  /* the finished deck coming in, row after row (see `arriving` in
     Placeholders). Its own keyframes, named for the stage, and backwards
     fill only: once played, nothing of it stays on the row to fight a
     slide being dragged. */
  .slot-slide.arrive {
    animation: stage-arrive 0.32s cubic-bezier(0.2, 0.8, 0.2, 1) backwards;
    animation-delay: calc(var(--i, 0) * 60ms);
  }
  .slide-zoom {
    position: absolute;
    right: 12px;
    top: 12px;
    z-index: 4;
    width: 30px;
    height: 30px;
    border: 0;
    border-radius: 50%;
    background: rgb(16 16 20 / 0.38);
    backdrop-filter: blur(12px) saturate(150%);
    -webkit-backdrop-filter: blur(12px) saturate(150%);
    box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0.18);
    color: rgb(255 255 255 / 0.92);
    font-size: 13px;
    line-height: 1;
    cursor: pointer;
    opacity: 0;
    transition: opacity 0.18s ease-out, background 0.18s, transform 0.18s;
  }
  .slide-slot:hover .slide-zoom,
  .slide-zoom:focus-visible {
    opacity: 1;
  }
  .slide-zoom:hover {
    background: rgb(16 16 20 / 0.62);
    transform: scale(1.06);
  }
  /* Where the pane has no room past the column there is nothing larger to
     see: the slide already runs the width of it (a phone, a tablet with the
     chat wide), and a button that changes nothing is worse than none. */
  @container pane (max-width: 759px) {
    .slide-zoom {
      display: none;
    }
  }
  /* Without a hover to bring it up, it stays: the one way to see a slide
     large must not wait for a gesture a finger cannot make. A little bigger
     under a finger, and its reach bigger still. */
  @media (hover: none) {
    .slide-zoom {
      opacity: 1;
      width: 36px;
      height: 36px;
      font-size: 15px;
    }
    .slide-zoom::before {
      content: '';
      position: absolute;
      inset: -6px;
    }
    .slide-zoom:hover {
      background: rgb(16 16 20 / 0.38);
      transform: none;
    }
  }
  /* The slide being written on says so, and has to say it loudly enough to
     be told from its own picture: the frame it opens in draws the same
     slide, so without this the card looks exactly as it did before the
     click. */
  .slide-slot.editing {
    border-radius: 12px;
    /* no accent line round it: the slide is the content being worked on,
       not one choice among several. It keeps exactly the edge hover gave it,
       so the click that opens it changes nothing about the card: the line
       under it saying what to do is the whole change. */
    box-shadow:
      0 0 0 1px var(--rule),
      0 10px 26px -16px rgb(0 0 0 / 0.3);
  }
  /* The picture: what fills the card, and what sits under the frame a slide
     opens in — the whole time it is open, not only until it has drawn.
     Taking it away, even by fading it, means the card is empty for as long
     as that takes when the slide closes again. It is never unmounted and
     never moved either, because either one reloads it, and a reloading
     frame is an empty card. */
  .slide-under {
    position: relative;
  }
  .slide-slot.editing .slide-under {
    position: absolute;
    inset: 0 0 auto 0;
    z-index: 1;
    pointer-events: none;
  }
  /* A slide made ready while the pointer is over it: drawn, out of sight,
     under its own picture, so the click that opens it has nothing left to
     wait for. It takes no room and no clicks — the way in stays over it. */
  .edit-holder.warm {
    position: absolute;
    inset: 0 0 auto 0;
    z-index: 0;
    opacity: 0;
    pointer-events: none;
  }
  .edit-holder.warm .mini.deck iframe {
    pointer-events: none;
  }
  .slide-slot.editing .edit-frame {
    position: relative;
    z-index: 2;
    /* and nothing of its own over the picture beneath it: a card's ground
       painted here is a white card for as long as the frame takes to draw,
       which is the flash itself */
    background: transparent;
    box-shadow: none;
  }
  /* a slide in the list is the way into it: a layer over the picture,
     rather than the thing that holds it */
  .slide-open {
    position: absolute;
    inset: 0;
    z-index: 2;
    width: 100%;
    padding: 0;
    border: 0;
    background: none;
    cursor: text;
    border-radius: 12px;
    transition: box-shadow 0.18s, transform 0.18s;
  }
  .slide-open iframe {
    pointer-events: none;
  }
  .slide-open:hover,
  .slide-open:focus-visible {
    box-shadow: 0 0 0 1px var(--rule), 0 10px 26px -16px rgb(0 0 0 / 0.3);
  }
  .slot-script {
    width: 100%;
  }
  /* the notes' name, and their voice at the right of the same line: one
     quiet line over the box, the height of the voice's own row whether or
     not there is a voice yet, so a voice arriving moves nothing */
  .script-head {
    display: flex;
    align-items: center;
    gap: 8px;
    min-height: 26px;
    margin-bottom: 4px;
  }
  .script-label {
    font-size: 12.5px;
    line-height: 16px;
    color: var(--ink3);
  }
  /* A slide's links, under what it says: each a line — its words as the
     link, where it goes in small, a quiet way to take it off — and the way
     to add one last, in words. */
  /* ──────────────────────────────────────────── the links beside a slide
     Drawn as what they become: the player raises them as chips next to the
     slide, so they are chips here. Each one carries its own controls, which
     is where a person looks for them — the way to take one off used to sit
     at the far edge of the line, an inch of travel from the thing it was
     about. */
  .slide-links {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    margin-top: 10px;
    font-size: 13px;
  }
  .link-chip {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    max-width: 100%;
    padding: 3px 4px 3px 3px;
    border: 0;
    border-radius: 999px;
    background: var(--fill-06);
    box-shadow: inset 0 0 0 1px var(--rule);
    color: var(--ink);
    font: inherit;
    transition: background 0.16s, box-shadow 0.16s;
  }
  .link-chip:hover {
    background: var(--fill-1);
    box-shadow: inset 0 0 0 1px var(--fill-28);
  }
  .link-chip > a {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    min-width: 0;
    padding-right: 4px;
    color: inherit;
    text-decoration: none;
  }
  .link-chip b {
    font-weight: 500;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .link-chip small {
    color: var(--ink3);
    font-size: 12px;
    white-space: nowrap;
  }
  /* the one letter a link wears, so a row of them can be told apart */
  /*
   * A plus in a circle, drawn rather than typed. The font's "+" sits on its
   * maths axis, below the middle of the line, so in a 20px circle it read a
   * pixel or two low and to one side. Two bars of the text colour, centred
   * on the circle, are centred on the circle.
   */
  .b :is(.link-chip.open .link-mark, .link-chip.adding .link-mark, .pic-ask i, .photo-ask:not(.weak) i) {
    font-size: 0 !important;
    background-image:
      linear-gradient(currentColor, currentColor),
      linear-gradient(currentColor, currentColor);
    background-size:
      9px 1.5px,
      1.5px 9px;
    background-position: center;
    background-repeat: no-repeat;
  }
  .link-mark {
    display: grid;
    place-items: center;
    width: 20px;
    height: 20px;
    flex: none;
    border-radius: 50%;
    background: var(--fill-1);
    color: var(--ink2);
    font-size: 11px;
    line-height: 1;
    font-weight: 600;
  }
  /*
   * A chip's own controls: drawn icons, not typed ✎ and ×, and only there
   * once the chip is asked about — hovered, or tabbed into. At rest they take
   * no room, so the chip ends where its name does rather than on an empty
   * tail; asked about, they open out behind a hairline that says they act on
   * the link rather than being part of it.
   */
  .link-chip:not(.open):not(.adding):not(.naming) {
    padding-right: 12px;
    transition:
      background 0.16s,
      box-shadow 0.16s,
      padding 0.16s;
  }
  .link-chip:not(.open):not(.adding):not(.naming):is(:hover, :focus-within) {
    padding-right: 4px;
  }
  .link-name,
  .link-drop {
    width: 0;
    height: 22px;
    flex: none;
    display: grid;
    place-items: center;
    overflow: hidden;
    border: 0;
    border-radius: 50%;
    background: none;
    color: var(--ink3);
    padding: 0;
    cursor: pointer;
    opacity: 0;
    transition:
      width 0.16s,
      opacity 0.16s,
      color 0.14s,
      background 0.14s;
  }
  .link-name {
    margin-left: 6px;
  }
  /* the hairline between the link and what acts on it, on the link's edge */
  .link-chip > a {
    position: relative;
  }
  .link-chip:is(:hover, :focus-within) > a::after {
    content: '';
    position: absolute;
    right: -3px;
    top: 4px;
    bottom: 4px;
    width: 1px;
    background: var(--rule);
  }
  .link-chip:is(:hover, :focus-within) :is(.link-name, .link-drop) {
    width: 22px;
    opacity: 1;
  }
  .link-name:hover,
  .link-drop:hover {
    background: var(--fill-1);
    color: var(--ink);
  }
  .link-drop:hover {
    color: var(--warn);
  }
  /* adding one, and naming one: the chip itself, open to be written in */
  .link-chip.adding,
  .link-chip.naming {
    gap: 8px;
    padding-right: 14px;
  }
  .link-chip input {
    min-width: 120px;
    width: 14ch;
    border: 0;
    background: none;
    color: var(--ink);
    font: inherit;
    outline: none;
  }
  .link-chip.adding input {
    width: 18ch;
  }
  /* A chip with no controls at its end needs its own room there: the
     leading circle weighs one side, and four pixels on the other left the
     words against the edge. */
  .link-chip.open {
    gap: 8px;
    padding: 3px 14px 3px 3px;
    cursor: pointer;
    background: none;
    box-shadow: inset 0 0 0 1px var(--rule);
    color: var(--ink3);
  }
  .link-chip.open:hover {
    color: var(--ink);
    background: var(--fill-06);
  }
  /* A button drawn on the slide: its mark is a small button outline rather
     than a site's letter, and a quiet tag says the link lives in the slide. */
  .link-mark.button-mark {
    background: none;
    box-shadow: none;
  }
  .link-mark.button-mark::before {
    content: '';
    width: 13px;
    height: 8px;
    border-radius: 3px;
    box-shadow: inset 0 0 0 1.5px currentColor;
  }
  .in-slide-tag {
    font-style: normal;
    font-size: 11px;
    color: var(--ink3);
    white-space: nowrap;
    padding-left: 6px;
    border-left: 1px solid var(--rule);
    margin-left: 1px;
  }
  .slide-links-error {
    flex-basis: 100%;
    margin: 0;
    font-size: 12px;
    color: var(--danger);
  }
  .slot-script textarea {
    /* a block, not inline: inline, it sat on a text line 6px taller than
       itself, and anything placed from the box's foot (the voice's mark)
       landed on its border */
    display: block;
    width: 100%;
    resize: none;
    border-radius: 8px;
    border: 1px solid var(--rule);
    background: var(--fill-03);
    color: var(--ink2);
    font: inherit;
    font-size: 14.5px;
    line-height: 1.55;
    padding: 8px 10px;
    outline: none;
    /* sized to its words from the start, not only after an edit */
    field-sizing: content;
    min-height: 3.4em;
  }
  /* the words' box, holding the voice's action in its corner */
  .script-field {
    position: relative;
  }
  .script-field:has(.voice-redo) textarea {
    padding-bottom: 30px;
  }
  /*
   * The voice's action: a round mark at rest, its words opening out to the
   * left of it when it is pointed at or tabbed to. A pill of words in every
   * box was one more thing to read on every slide.
   */
  .voice-redo {
    position: absolute;
    right: 7px;
    bottom: 7px;
    display: inline-flex;
    flex-direction: row-reverse;
    align-items: center;
    gap: 0;
    height: 24px;
    padding: 0 6px;
    border: 0;
    border-radius: 999px;
    background: var(--fill-06);
    color: var(--ink2);
    font: inherit;
    font-size: 12px;
    white-space: nowrap;
    cursor: pointer;
    transition:
      opacity 0.2s,
      background 0.15s,
      color 0.15s,
      gap 0.2s;
  }
  .voice-redo svg {
    flex: none;
  }
  .voice-redo span {
    max-width: 0;
    overflow: hidden;
    opacity: 0;
    transition:
      max-width 0.22s cubic-bezier(0.2, 0.7, 0.2, 1),
      opacity 0.18s;
  }
  .voice-redo:is(:hover, :focus-visible):not(:disabled) {
    gap: 6px;
    padding: 0 8px 0 10px;
    background: var(--fill-1);
    color: var(--ink);
  }
  .voice-redo:is(:hover, :focus-visible):not(:disabled) span {
    max-width: 14em;
    opacity: 1;
  }
  /*
   * Under a finger nothing here may change on hover. A tap is a hover first
   * on an iPhone, and where the hover brings something into view (the chip's
   * pencil and cross, the voice's words) Safari stops there: the tap only
   * showed them, and a second tap did what the first was for. So a touch
   * screen shows the chip's own controls from the start — the only way it
   * ever gets them — and the voice's action stays its round mark, named by
   * its label for anyone who cannot see it.
   */
  @media (hover: none) {
    .link-chip:not(.open):not(.adding):not(.naming),
    .link-chip:not(.open):not(.adding):not(.naming):is(:hover, :focus-within) {
      padding-right: 4px;
    }
    .link-chip :is(.link-name, .link-drop) {
      width: 22px;
      opacity: 1;
      transition: none;
    }
    .link-chip:not(.open):not(.adding):not(.naming) > a::after {
      content: '';
      position: absolute;
      right: -3px;
      top: 4px;
      bottom: 4px;
      width: 1px;
      background: var(--rule);
    }
    .voice-redo:is(:hover, :focus-visible):not(:disabled) {
      gap: 0;
      padding: 0 6px;
    }
    .voice-redo:is(:hover, :focus-visible):not(:disabled) span {
      max-width: 0;
      opacity: 0;
    }
  }
  .voice-redo:disabled {
    opacity: 0.45;
    cursor: default;
  }
  /* and never while the words are still being typed */
  .voice-redo.waiting,
  .script-field:is(:hover, :focus-within) .voice-redo.waiting {
    opacity: 0;
    pointer-events: none;
  }
  .slot-script textarea:focus-visible {
    border-color: var(--ring);
    color: var(--ink);
  }
  .slot-ask {
    margin-top: 2px;
  }
  .slot-ask .form .reading,
  .slot-ask .form .field label {
    font-size: 14.5px;
  }
  .slot-ask .form .chips button {
    font-size: 13.5px;
  }
  /* a part's slides, once cast: the frames, each with its meta */
  .slot-slides {
    display: flex;
    flex-direction: column;
    /* slides need room to read as separate things rather than a stack */
    gap: 40px;
  }
  .slot-slide {
    display: flex;
    flex-direction: column;
    gap: 10px;
  }
  /* One slide ends and the next begins: a hairline across the space between
     them, quiet enough to be furniture. It belongs to the slide below it,
     so the first one in a part has none — a part's slides begin under its
     own title. */
  .slot-slide + .slot-slide {
    position: relative;
    padding-top: 40px;
  }
  .slot-slide + .slot-slide::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 1px;
    background: var(--rule);
    opacity: 0.55;
  }
  /*
   * The finished deck in its own sections (see deckMode), grouped by space
   * alone. A section's slides sit close — 32px, a faint rule, 32px — and a
   * section stands apart from what is around it by 128px of nothing, no
   * rule and no ground. Close things read as together and far things as
   * apart without a box to say so, and the deck keeps its margins. The
   * difference is large on purpose: a 96px gap against 40px went unseen.
   * The cover, agenda and close are the deck's frame, spaced as slides.
   */
  .slots:not(.flow) > .deck-section,
  .slots:not(.flow) > .deck-frame {
    grid-column: 1 / -1;
    display: flex;
    flex-direction: column;
    gap: 32px;
  }
  .slots:not(.flow) > .deck-section .slot-slide + .slot-slide {
    padding-top: 32px;
  }
  /* frame to frame: the ordinary step between two slides */
  .slots:not(.flow) > .deck-frame + .deck-frame {
    margin-top: 20px;
    padding-top: 32px;
  }
  /* into a section and out of one: the long pause, and nothing drawn */
  .slots:not(.flow) > .deck-section:not(:first-child),
  .slots:not(.flow) > .deck-section + .slot {
    margin-top: 116px;
    padding-top: 0;
  }
  .slots:not(.flow) > .deck-section::before,
  .slots:not(.flow) > .deck-section + .slot::before {
    content: none;
  }
  /*
   * A slide seen larger, in place (see toggleExpand): it widens past the
   * column to the pane — up to 1080px — centred on the column, while its
   * words and everything else keep their width. The browser carries it
   * between the two sizes; this only says where it ends up.
   */
  .scroller {
    container: pane / inline-size;
  }
  .slot-slide.expanded .slide-slot {
    /* and no taller than the pane shows, less its head: a slide seen larger
       is seen whole, never scrolled through */
    --grow: min(100cqw, 1080px, max(100%, (100dvh - 200px) * 16 / 9));
    width: var(--grow);
    max-width: none;
    margin-left: calc((100% - var(--grow)) / 2);
  }

  /*
   * A slide being moved (see slideDrag). The grip sits at the head of the
   * slide's line, as quiet as its other tools until the slide is under the
   * hand. Taking hold folds the deck into a strip — each slide a thumbnail
   * beside its title, the notes put away, the long pauses between sections
   * closed up — so a slide is carried across rows rather than screens. The
   * one held lifts; the others step aside as it passes.
   */
  .slide-grip {
    width: 18px;
    height: 24px;
    /* the dots on the column's edge, and close to the label they move */
    margin: 0 -6px 0 -4px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    padding: 0;
    border: 0;
    border-radius: 6px;
    background: none;
    color: var(--ink3);
    opacity: 0;
    cursor: grab;
    touch-action: none;
    transition:
      opacity 0.15s,
      color 0.15s,
      background 0.15s;
  }
  .slide-grip svg {
    width: 10px;
    height: 16px;
    fill: currentColor;
  }
  .slot-slide:hover .slide-grip,
  .slide-grip:focus-visible,
  .slots.reordering .slide-grip {
    opacity: 0.75;
  }
  @media (hover: none) {
    .slide-grip {
      opacity: 0.5;
    }
  }
  .slide-grip:hover:not(:disabled),
  .slide-grip:focus-visible {
    opacity: 1;
    color: var(--ink);
    background: var(--fill-03);
  }
  .slide-grip:disabled {
    opacity: 0.25;
    cursor: default;
  }
  /* With room beside the column, the grip stands in the margin, as a
     block's handle does in a document: at rest the slide's name lines up
     with its notes under it, and the grip appears beside the slide under
     the hand. A narrow pane keeps it on the line. */
  @container pane (min-width: 640px) {
    .slot-meta {
      position: relative;
    }
    .slots:not(.reordering) .slide-grip {
      position: absolute;
      left: -26px;
      top: 50%;
      margin: 0;
      transform: translateY(-50%);
    }
  }
  .slots.reordering {
    user-select: none;
  }
  .slots.reordering .slot-slide {
    flex-direction: row;
    align-items: center;
    gap: 14px;
    transition: transform 0.22s cubic-bezier(0.2, 0.8, 0.2, 1);
  }
  .slots.reordering .slot-slide + .slot-slide,
  .slots.reordering > .deck-section .slot-slide + .slot-slide {
    padding-top: 8px;
  }
  .slots.reordering .slot-slide + .slot-slide::before {
    content: none;
  }
  .slots.reordering > .deck-section,
  .slots.reordering > .deck-frame {
    gap: 8px;
  }
  .slots.reordering > .deck-section:not(:first-child),
  .slots.reordering > .deck-section + .slot,
  .slots.reordering > .deck-frame + .deck-frame {
    margin-top: 8px;
    padding-top: 0;
  }
  .slots.reordering .slide-slot {
    width: 128px;
    flex: none;
    pointer-events: none;
  }
  .slots.reordering .slot-meta {
    flex: 1;
    min-width: 0;
  }
  .slots.reordering .slot-script,
  .slots.reordering .slide-refused,
  .slots.reordering .slide-tools,
  .slots.reordering .slide-zoom,
  .slots.reordering .edit-rail {
    display: none;
  }
  .slots.reordering .slot-slide.lifted {
    position: relative;
    z-index: 5;
    transition: none;
    cursor: grabbing;
  }
  .slots.reordering .slot-slide.lifted .slide-slot {
    box-shadow:
      0 14px 32px rgb(0 0 0 / 0.32),
      0 0 0 2px var(--accent);
  }
  .slots.reordering .slot-slide.lifted .slide-grip {
    cursor: grabbing;
  }
  /* a placeholder reads as one: a dashed edge until it is a slide */
  .slot.queued .slot-frame:not(.asks),
  .slot.writing .slot-frame:not(.asks) {
    box-shadow: none;
    outline: 1px dashed color-mix(in srgb, var(--slide-ink) 28%, transparent);
    outline-offset: -1px;
  }
  /* the judge's point on the part it is about */
  .slot-issues {
    display: flex;
    flex-direction: column;
    gap: 6px;
  }
  .slot-issue {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 8px 10px;
    border-radius: 8px;
    background: var(--fill-04);
    border-left: 2px solid var(--warn);
    font-size: 14px;
    line-height: 1.45;
    color: var(--ink2);
  }
  .slot-issue p {
    margin: 0;
    flex: 1;
    min-width: 0;
  }
  .slot-issue button {
    flex: none;
    font-size: 13px;
    padding: 5px 12px;
    border-radius: 999px;
    border: 1px solid var(--rule);
    background: var(--fill-06);
    color: var(--ink);
  }
  /* the story's own decisions, at the stage head */
  /* the voiceover's word, beside the count: a dot that says how it goes */
  .stage-note {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    margin-left: 12px;
    font-size: 12.5px;
    color: var(--ink2);
    white-space: nowrap;
  }
  .stage-note i {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--ink3);
    flex: none;
  }
  .stage-note.busy i {
    background: var(--accent);
    animation: pulse var(--work-beat) ease-in-out infinite;
  }
  .stage-note.bad {
    color: var(--warn);
  }
  .stage-note.bad i {
    background: var(--warn);
  }
  .stage-title {
    font-size: 13px;
    font-weight: 500;
    color: var(--ink);
    /* "Deck" and "Story line" take the same room, so what follows holds
       still when the column changes */
    display: inline-grid;
    letter-spacing: 0.02em;
  }
  .stage-title > span {
    grid-area: 1 / 1;
  }
  .stage-title .ghost {
    visibility: hidden;
  }
  .stage-actions {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-left: 12px;
  }
  .stage-actions button {
    font-size: 13.5px;
    padding: 6px 14px;
    border-radius: 999px;
    border: 1px solid var(--rule);
    color: var(--ink2);
    background: var(--fill-03);
    min-height: 30px;
    white-space: nowrap;
  }
  .stage-actions button:hover {
    background: var(--fill-08);
    color: var(--ink);
  }
  .stage-actions button.primary {
    background: var(--ink);
    color: var(--bg);
    border-color: transparent;
  }
  .slot-answers {
    display: flex;
    flex-direction: column;
    gap: 6px;
  }
  /* what a part still needs, listed on its card until it is written */
  .slot-needs {
    display: flex;
    flex-direction: column;
    gap: 4px;
    max-width: 36em;
  }
  .slot-needs-k {
    font-size: 11px;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--ink3);
  }
  .slot-needs ul {
    margin: 0;
    padding: 0 0 0 1.1em;
    display: flex;
    flex-direction: column;
    gap: 2px;
    font-size: 14px;
    line-height: 1.45;
    color: var(--ink2);
  }
  .slot-answer {
    display: flex;
    flex-direction: column;
    gap: 2px;
    font-size: 15px;
    line-height: 1.5;
  }
  .slot-answer small {
    font-size: 13px;
    color: var(--ink3);
  }
  .slot-answer span {
    color: var(--ink);
  }
  /* the arc: the story's line across the top of the board. The two ends
     say what the reader thinks now and should think after; the line fills
     as far as the story is written; a node per card sits on it with its
     title and where it stands under. A node brings its card into view. */
  .arc {
    display: flex;
    flex-direction: column;
    margin: 16px 0 26px;
  }
  .arc-ends {
    display: flex;
    justify-content: space-between;
    gap: 32px;
    margin-bottom: 14px;
  }
  .arc-end {
    max-width: 36%;
    min-width: 0;
  }
  .arc-end small {
    display: block;
    font-size: 10.5px;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink3);
    margin-bottom: 4px;
  }
  .arc-end p {
    margin: 0;
    font-family: var(--display);
    font-size: 15px;
    line-height: 1.35;
    color: var(--ink);
    text-wrap: pretty;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    overflow: hidden;
  }
  .arc-to {
    text-align: right;
  }
  .arc-track {
    position: relative;
    height: 2px;
    margin: 6px 11px 0;
    background: var(--rule2);
    border-radius: 1px;
  }
  .arc-done {
    position: absolute;
    left: 0;
    top: 0;
    height: 2px;
    background: var(--ink2);
    border-radius: 1px;
    transition: width 0.4s ease-out;
  }
  .arc-nodes {
    display: flex;
    justify-content: space-between;
    list-style: none;
    margin: -12px 0 0;
    padding: 0;
  }
  .arc-node {
    flex: 1 1 0;
    min-width: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    padding: 0 6px;
  }
  .arc-node:first-child {
    align-items: flex-start;
    text-align: left;
    padding-left: 0;
  }
  .arc-node:last-child {
    align-items: flex-end;
    text-align: right;
    padding-right: 0;
  }
  .arc-dot {
    width: 22px;
    height: 22px;
    border-radius: 50%;
    border: 1.5px solid var(--rule2);
    background: var(--bg);
    color: var(--ink3);
    font-size: 11px;
    font-variant-numeric: tabular-nums;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    transition:
      background 0.2s,
      border-color 0.2s,
      color 0.2s,
      box-shadow 0.2s;
  }
  .arc-dot:hover {
    border-color: var(--ink2);
    color: var(--ink);
  }
  .arc-node[data-state='done'] .arc-dot {
    border-color: var(--ink2);
    background: var(--ink2);
    color: var(--bg);
  }
  .arc-node[data-state='writing'] .arc-dot {
    border-color: var(--accent);
    background: var(--accent);
    color: var(--on-ink);
  }
  .arc-node[data-state='asking'] .arc-dot {
    border-color: var(--accent);
    color: var(--ink);
    box-shadow: 0 0 0 4px var(--accent-soft);
  }
  .arc-node[data-state='open'] .arc-dot {
    border-color: var(--warn);
    color: var(--ink);
    box-shadow: inset 0 0 0 3px color-mix(in srgb, var(--warn) 35%, transparent);
  }
  .arc-label {
    margin-top: 8px;
    max-width: 100%;
    font-size: 12.5px;
    font-weight: 500;
    line-height: 1.3;
    color: var(--ink);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .arc-node[data-state='queued'] .arc-label {
    color: var(--ink3);
    font-weight: 400;
  }
  .arc-state {
    margin-top: 2px;
    font-size: 11.5px;
    line-height: 1.3;
    color: var(--ink3);
  }
  .arc-node[data-state='asking'] .arc-state,
  .arc-node[data-state='writing'] .arc-state {
    color: var(--accent);
  }
  .arc-node[data-state='open'] .arc-state {
    color: var(--warn);
  }

  .part {
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 14px 0 18px;
    border-top: 1px solid var(--rule);
    /* a fade, not a rise: a column of cards each sliding up reads as
       motion for its own sake, and on a story coming back the page is
       already fading up as one */
    animation: fade-in 0.3s ease-out both;
  }
  .part:first-of-type {
    border-top: 0;
  }
  .part-top {
    display: flex;
    align-items: center;
    gap: 10px;
    min-height: 28px;
  }
  .part-top .n {
    width: 22px;
    height: 22px;
    border-radius: 50%;
    border: 1px solid var(--rule);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-family: var(--display);
    font-size: 11px;
    color: var(--ink3);
    flex: none;
    transition:
      border-color 0.3s,
      color 0.3s;
  }
  .part.writing .n {
    border-color: var(--accent);
    color: var(--accent);
  }
  .part.done .n {
    border-color: var(--ok);
    color: var(--ok);
  }
  .part-top .title {
    font-size: 14px;
    font-weight: 500;
    color: var(--ink);
    flex: 0 1 auto;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .part.queued .part-top .title {
    color: var(--ink2);
  }
  .part-top .sp {
    flex: 1;
  }
  .part-top .mark {
    font-size: 11.5px;
    color: var(--ink3);
    white-space: nowrap;
    display: inline-flex;
    align-items: center;
    gap: 6px;
  }
  .part-top .mark::before {
    content: '';
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--ink3);
    flex: none;
  }
  .part.writing .mark::before {
    background: var(--accent);
    animation: pulse var(--work-beat) ease-in-out infinite;
  }
  .part.done .mark::before {
    background: var(--ok);
  }
  .part.open .mark::before {
    background: var(--warn);
  }
  .part .job {
    margin: 0;
    font-size: 13px;
    color: var(--ink3);
    padding-left: 32px;
  }
  .part .text {
    margin: 0 0 0 32px;
    font-size: 15px;
    line-height: 1.6;
    color: var(--ink);
    white-space: pre-wrap;
  }
  .part.queued .text {
    color: var(--ink3);
  }
  /* The line being written, before its text lands. */
  .part .ghost {
    margin-left: 32px;
    display: flex;
    flex-direction: column;
    gap: 7px;
  }
  .part .ghost i {
    height: 11px;
    border-radius: 4px;
    background: linear-gradient(
      100deg,
      var(--fill-04) 30%,
      var(--fill-09) 50%,
      var(--fill-04) 70%
    );
    background-size: 200% 100%;
    animation: sh var(--work-sweep) linear infinite;
  }
  .part .ghost i:nth-child(2) {
    width: 88%;
  }
  .part .ghost i:nth-child(3) {
    width: 62%;
  }

  /* What a part still needs. Shown, never blocking. */
  .points {
    margin-left: 32px;
    display: flex;
    flex-direction: column;
    gap: 8px;
  }
  .point {
    border: 1px solid color-mix(in srgb, var(--warn) 30%, transparent);
    background: color-mix(in srgb, var(--warn) 6%, transparent);
    border-radius: 10px;
    padding: 10px 12px;
    font-size: 13px;
    display: flex;
    flex-direction: column;
    gap: 4px;
  }
  .point b {
    font-weight: 500;
    color: var(--ink);
  }
  .point small {
    color: var(--ink3);
  }
  .point .answer {
    color: var(--ok);
  }
  .point.answered {
    border-color: color-mix(in srgb, var(--ok) 28%, transparent);
    background: color-mix(in srgb, var(--ok) 5%, transparent);
  }

  /* The part the agent is working out right now. */
  .part.asking .n {
    border-color: var(--accent);
    color: var(--accent);
  }
  .part.asking .mark::before {
    background: var(--accent);
    animation: pulse var(--work-beat) ease-in-out infinite;
  }
  .part.asking .title {
    color: var(--ink);
  }

  /* --------------------------------------------------- the deck: a slide */

  .deck-slide {
    display: flex;
    flex-direction: column;
    gap: 12px;
    padding: 16px 0 28px;
    border-top: 1px solid var(--rule);
    /* a fade, not a rise: a column of cards each sliding up reads as
       motion for its own sake, and on a story coming back the page is
       already fading up as one */
    animation: fade-in 0.3s ease-out both;
  }
  .deck-slide:first-of-type {
    border-top: 0;
  }

  .mini {
    position: relative;
    aspect-ratio: 16 / 9;
    width: 100%;
    border-radius: 12px;
    overflow: hidden;
    background:
      radial-gradient(
        90% 70% at 15% 10%,
        rgba(94, 86, 249, 0.28),
        transparent 60%
      ),
      radial-gradient(
        60% 50% at 95% 100%,
        rgba(240, 66, 138, 0.16),
        transparent 60%
      ),
      var(--slide-bg);
    color: var(--slide-ink);
    container-type: size;
    box-shadow: 0 0 0 1px var(--slide-rule);
  }
  .mini::before {
    content: '';
    position: absolute;
    inset: 0;
    background:
      repeating-linear-gradient(
        0deg,
        var(--fill-025) 0 1px,
        transparent 1px 28px
      ),
      repeating-linear-gradient(
        90deg,
        var(--fill-025) 0 1px,
        transparent 1px 28px
      );
    mask-image: radial-gradient(80% 80% at 50% 50%, #000 30%, transparent 100%);
    pointer-events: none;
  }
  /* a part of the story the deck gave no slide of its own: named at the
     foot of the deck, in the deck's own quiet, rather than drawn as the
     story-line card it came from */
  .slots-left {
    margin: 4px 0 0;
    padding-top: 14px;
    border-top: 1px solid var(--rule);
    font-size: 12.5px;
    line-height: 1.5;
    color: var(--ink3);
  }
  /* ──────────────────────────────────────── photos a slide is waiting for
     A stand-in panel is a designed graphic and reads as finished, so the
     editor says what it is waiting for: on the card, on the panel itself
     once the slide is open, and once for the whole deck at its head. Glass
     over the slide, in the same language as the way to see one large. */
  .photo-ask,
  .pic-ask,
  .pic-weak,
  .pic-replace {
    position: absolute;
    z-index: 4;
    display: inline-flex;
    align-items: center;
    gap: 8px;
    border: 0;
    border-radius: 999px;
    background: rgb(16 16 20 / 0.5);
    backdrop-filter: blur(12px) saturate(150%);
    -webkit-backdrop-filter: blur(12px) saturate(150%);
    box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0.2), 0 8px 22px -12px rgb(0 0 0 / 0.5);
    color: rgb(255 255 255 / 0.95);
    font: inherit;
    font-size: 12.5px;
    line-height: 1.25;
    text-align: left;
    cursor: pointer;
    transition: background 0.18s, transform 0.18s, opacity 0.18s;
  }
  .photo-ask:hover,
  .pic-ask:hover,
  .pic-weak:hover,
  .pic-replace:hover {
    background: rgb(16 16 20 / 0.7);
  }
  .photo-ask:disabled,
  .pic-ask:disabled,
  .pic-weak:disabled,
  .pic-replace:disabled {
    opacity: 0.6;
    cursor: progress;
  }
  /* The controls on the picture pointed at: ONE pill, in the zoom button's
     glass (`.slide-zoom`) — the way to replace the picture and, for a
     background, a hairline and the way to take it off. Things done to the
     picture read as one control; the zoom beside it, a card's own, stands
     apart by a clearer gap (`controlSpot`'s keep-off around it). */
  .pic-controls {
    position: absolute;
    z-index: 4;
    display: inline-flex;
    align-items: stretch;
    height: 30px;
    border-radius: 999px;
    overflow: hidden;
    background: rgb(16 16 20 / 0.38);
    backdrop-filter: blur(12px) saturate(150%);
    -webkit-backdrop-filter: blur(12px) saturate(150%);
    box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0.18);
    /* it is mounted when a picture is pointed at, so it fades in the way
       the card's zoom button does on hover (`.slide-zoom`) rather than
       arriving a beat ahead of it */
    animation: fade-in-line 0.18s ease-out backwards;
  }
  .pic-controls > .pic-replace,
  .pic-controls > .pic-remove {
    position: static;
    height: auto;
    border-radius: 0;
    background: transparent;
    box-shadow: none;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
  .pic-controls > .pic-replace:hover {
    background: rgb(255 255 255 / 0.12);
  }
  /* measured before it is placed: there, but not yet seen */
  .pic-controls.placing {
    left: 0;
    top: 0;
    visibility: hidden;
    pointer-events: none;
    animation: none;
  }
  /* taking a background off: a bin, behind a hairline, red only under the
     pointer — the one thing here that removes something */
  .pic-remove {
    display: grid;
    place-items: center;
    width: 34px;
    padding: 0 2px 0 0;
    border: 0;
    border-left: 1px solid rgb(255 255 255 / 0.2);
    color: rgb(255 255 255 / 0.86);
    cursor: pointer;
    transition: background 0.18s, color 0.18s, opacity 0.18s;
  }
  .pic-controls > .pic-remove:hover {
    background: rgb(200 48 48 / 0.85);
    color: #fff;
  }
  .pic-remove:disabled {
    opacity: 0.6;
    cursor: progress;
  }
  /* the way back from a background just taken off: the card's own chip, in
     its lower left, with the word that does it set apart */
  .photo-ask.undo {
    padding: 5px 14px;
  }
  .photo-ask.undo b {
    font-weight: 600;
  }
  /* The way to replace a picture: the zoom button's height, so the two read
     as one row of controls where they sit side by side. Where it goes is
     worked out from the picture and what is on it (`controlSpot`): a corner
     clear of the slide's words, its credit and the card's own controls. */
  .pic-replace {
    height: 30px;
    padding: 0 12px 0 10px;
    gap: 6px;
    /* placed by its measured width, so it never breaks into two lines */
    white-space: nowrap;
  }
  .pic-replace-mark {
    flex: none;
    opacity: 0.9;
  }
  /* only its mark, where the words do not fit clear of the slide's: round,
     like the zoom button beside it */
  .pic-controls.compact > .pic-replace {
    width: 30px;
    padding: 0;
    justify-content: center;
  }
  .pic-controls.compact > .pic-remove {
    width: 30px;
    padding: 0;
  }
  @media (hover: none) {
    .pic-replace,
    .pic-controls {
      height: 36px;
    }
    .pic-remove,
    .pic-controls.compact > .pic-replace {
      width: 36px;
    }
  }
  .photo-ask i,
  .pic-ask i {
    flex: none;
    display: grid;
    place-items: center;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    background: var(--accent);
    color: var(--on-ink);
    font-style: normal;
    font-size: 15px;
    line-height: 1;
  }
  /* on the card: its lower left corner, always there, never over the words
     more than a line's worth */
  .photo-ask {
    left: 12px;
    bottom: 12px;
    max-width: calc(100% - 24px);
    padding: 5px 12px 5px 5px;
  }
  .photo-ask span {
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .slide-slot.editing .photo-ask {
    display: none;
  }
  /* on the open slide: centred on the panel it fills */
  .pic-ask {
    transform: translate(-50%, -50%);
    padding: 6px 14px 6px 6px;
  }
  .pic-ask:hover {
    transform: translate(-50%, -50%) scale(1.03);
  }
  .pic-ask span {
    display: flex;
    flex-direction: column;
    min-width: 0;
  }
  .pic-ask b {
    font-weight: 600;
  }
  .pic-ask small {
    font-size: 11.5px;
    color: rgb(255 255 255 / 0.72);
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }
  .pic-ask.small {
    padding: 4px;
  }
  /* A picture that is there but could be better: the same glass, said more
     quietly than a gap — its mark is not the accent's "add" but a muted
     amber "!", and it sits in the picture's corner rather than over its
     middle, so the picture itself stays in view. */
  .photo-ask.weak i,
  .pic-weak i {
    flex: none;
    display: grid;
    place-items: center;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    background: rgb(240 170 90 / 0.9);
    color: rgb(30 20 10);
    font-style: normal;
    font-size: 13px;
    font-weight: 700;
    line-height: 1;
  }
  .photo-ask.weak {
    background: rgb(16 16 20 / 0.38);
  }
  .pic-weak {
    padding: 6px 12px 6px 6px;
    align-items: flex-start;
  }
  .pic-weak span {
    display: flex;
    flex-direction: column;
    min-width: 0;
  }
  .pic-weak b {
    font-weight: 600;
  }
  .pic-weak small {
    font-size: 11.5px;
    color: rgb(255 255 255 / 0.72);
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }
  .pic-weak.small {
    padding: 4px;
  }
  /* the deck's own line: how many, and each a way to its slide */
  .photo-asks {
    grid-column: 1 / -1;
    margin: 0 0 4px;
    padding: 14px 16px;
    border-radius: 12px;
    background: var(--fill-04);
    box-shadow: inset 0 0 0 1px var(--rule);
    font-size: 13px;
    line-height: 1.45;
  }
  .photo-asks p {
    margin: 0 0 8px;
    display: flex;
    flex-wrap: wrap;
    gap: 2px 10px;
    align-items: baseline;
  }
  .photo-asks p b {
    font-weight: 600;
    color: var(--ink);
  }
  .photo-asks p span {
    color: var(--ink3);
    font-size: 12.5px;
  }
  .photo-asks ul {
    margin: 0;
    padding: 0;
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: 2px;
  }
  .photo-asks li button {
    display: flex;
    gap: 10px;
    width: 100%;
    padding: 5px 8px;
    margin: 0 -8px;
    border: 0;
    border-radius: 8px;
    background: none;
    color: var(--ink2);
    font: inherit;
    text-align: left;
    cursor: pointer;
  }
  .photo-asks li button:hover {
    background: var(--fill-06);
    color: var(--ink);
  }
  .photo-asks-n {
    flex: none;
    min-width: 58px;
    color: var(--ink3);
    font-variant-numeric: tabular-nums;
  }
  .photo-asks-weak {
    margin-top: 10px;
    padding-top: 10px;
    border-top: 1px solid var(--rule);
  }
  .photo-asks-weak p b {
    font-weight: 500;
    color: var(--ink2);
  }
  .photo-asks-what {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  /* the deck being worked out, before it has said what it holds */
  .making {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 10px;
    padding: 56px 24px 72px;
  }
  .making-shapes {
    display: flex;
    gap: 10px;
    margin-bottom: 14px;
  }
  /* Three slides, not three blocks. An edge and the two lines a slide puts
     its words on are the difference between a placeholder that reads as the
     thing being made and one that reads as loading. */
  .making-shapes span {
    position: relative;
    width: 92px;
    aspect-ratio: 16 / 9;
    border-radius: 6px;
    box-shadow: inset 0 0 0 1px var(--rule);
    background: linear-gradient(
      90deg,
      var(--fill-04) 0%,
      var(--fill-08) 50%,
      var(--fill-04) 100%
    );
    background-size: 200% 100%;
    animation: sheen var(--work-sweep) linear infinite;
  }
  .making-shapes span::before,
  .making-shapes span::after {
    content: '';
    position: absolute;
    left: 11px;
    border-radius: 2px;
    background: var(--fill-08);
  }
  .making-shapes span::before {
    top: 15px;
    width: 44%;
    height: 4px;
  }
  .making-shapes span::after {
    top: 26px;
    width: 66%;
    height: 3px;
    background: var(--fill-06);
  }
  .making-shapes span:nth-child(2) {
    animation-delay: 0.25s;
  }
  .making-shapes span:nth-child(3) {
    animation-delay: 0.5s;
  }
  .making-what {
    margin: 0;
    font-family: var(--serif);
    font-style: italic;
    font-size: 17px;
    color: var(--ink);
  }
  .making-note {
    margin: 0;
    font-size: 13.5px;
    color: var(--ink2);
  }
  .making-wait {
    margin: 2px 0 0;
    max-width: 44ch;
    font-size: 12px;
    line-height: 1.5;
    color: var(--ink3);
  }
  @media (prefers-reduced-motion: reduce) {
    .making-shapes span {
      animation: none;
    }
  }
  .mini.skel {
    background: var(--stage);
  }
  /* A slide still being drawn: a still ground that breathes, slowly, rather
     than a highlight streaking across it. A streak on every waiting slide at
     once is busy, and the loudest thing on the page is then the thing that
     is not there yet. */
  .mini.skel::after {
    content: '';
    position: absolute;
    inset: 0;
    background: var(--fill-04);
    animation: breathe var(--work-sweep) ease-in-out infinite;
  }
  @media (prefers-reduced-motion: reduce) {
    .mini.skel::after {
      animation: none;
    }
  }

  .slide {
    position: absolute;
    inset: 0;
    padding: 5.5cqw 6cqw;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    gap: clamp(6px, 1.6cqw, 18px);
  }
  .s-label {
    font-size: clamp(9px, 1.6cqw, 14px);
    color: var(--slide-ink3);
    letter-spacing: 0.07em;
    text-transform: uppercase;
  }
  .s-big {
    font-family: var(--display);
    font-size: clamp(18px, 6.2cqw, 60px);
    font-weight: 500;
    letter-spacing: -0.03em;
    line-height: 0.98;
    max-width: 11em;
    margin: 0;
    text-wrap: balance;
  }
  .s-big em {
    font-style: normal;
    color: var(--accent);
  }
  .s-sub {
    font-size: clamp(10px, 1.9cqw, 18px);
    color: var(--slide-ink2);
  }
  .s-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: clamp(3px, 0.9cqw, 10px);
    font-size: clamp(10px, 2.2cqw, 20px);
  }
  .s-list li {
    padding-left: 2.2cqw;
    position: relative;
  }
  .s-list li::before {
    content: '';
    position: absolute;
    left: 0;
    top: 0.55em;
    width: 0.9cqw;
    height: 0.9cqw;
    border-radius: 50%;
    background: var(--accent);
  }
  .s-stats {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 1.6cqw;
  }
  .s-stats div {
    background: var(--fill-04);
    border: 1px solid var(--rule);
    border-radius: 1.2cqw;
    padding: 2cqw 2cqw 2.2cqw;
    display: flex;
    flex-direction: column;
    gap: 0.3cqw;
  }
  .s-stats b {
    font-family: var(--display);
    font-weight: 500;
    font-size: clamp(14px, 4.6cqw, 48px);
    letter-spacing: -0.03em;
    line-height: 1;
    font-variant-numeric: tabular-nums;
  }
  .s-stats span {
    font-size: clamp(8px, 1.6cqw, 13px);
    color: var(--ink3);
  }
  .s-stats i {
    display: block;
    height: 0.5cqw;
    border-radius: 1cqw;
    background: var(--fill-08);
    margin-top: 1.3cqw;
    position: relative;
    overflow: hidden;
  }
  .s-stats i::after {
    content: '';
    position: absolute;
    inset: 0;
    background: var(--accent);
    transform: scaleX(var(--w));
    transform-origin: left;
  }
  .s-chart {
    display: flex;
    align-items: flex-end;
    gap: 2.4cqw;
    height: 34cqw;
    padding-top: 2cqw;
  }
  .s-chart div {
    flex: 1;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    gap: 0.8cqw;
    height: 100%;
  }
  .s-chart i {
    display: block;
    height: calc(var(--w) * 100%);
    background: linear-gradient(to top, var(--accent-deep), var(--accent));
    border-radius: 0.8cqw 0.8cqw 0.3cqw 0.3cqw;
    min-height: 2cqw;
  }
  .s-chart b {
    font-family: var(--display);
    font-weight: 500;
    font-size: clamp(12px, 3.2cqw, 30px);
    letter-spacing: -0.02em;
    line-height: 1;
  }
  .s-chart span {
    font-size: clamp(8px, 1.5cqw, 12px);
    color: var(--ink3);
  }

  /* The narration, under its slide. */
  .script {
    display: flex;
    flex-direction: column;
    gap: 8px;
  }
  .script-box {
    display: flex;
    flex-direction: column;
    background: var(--fill-03);
    border: 1px solid var(--rule);
    border-radius: 12px;
    transition:
      border-color 0.15s,
      background 0.15s;
  }
  .script-box:focus-within {
    border-color: var(--ring);
    background: var(--fill-05);
  }
  .script textarea {
    display: block;
    width: 100%;
    resize: none;
    background: transparent;
    border: 0;
    padding: 12px 14px;
    color: var(--ink);
    font: inherit;
    font-size: 15px;
    line-height: 1.55;
    min-height: 72px;
    overflow: hidden;
  }
  .script textarea:focus {
    outline: none;
  }
  .script .meta {
    display: flex;
    align-items: center;
    gap: 10px;
    font-size: 12px;
    color: var(--ink3);
  }
  .script .meta .key {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 11px;
    border: 1px solid var(--rule);
    border-radius: 5px;
    padding: 2px 6px;
    color: var(--ink2);
  }

  /* --------------------------------------------------------- voice picker */

  .voice-picker {
    position: relative;
  }
  /* quiet, like the rest of the stage head: a glyph, what it is, who */
  .voice-trigger {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    height: 30px;
    padding: 0 8px;
    border-radius: 8px;
    border: 0;
    background: transparent;
    color: var(--ink);
    font-size: 13px;
    white-space: nowrap;
    cursor: pointer;
  }
  .voice-trigger:hover {
    background: var(--fill-06);
  }
  .voice-glyph {
    color: var(--ink3);
    flex: none;
  }
  .voice-trigger:disabled {
    opacity: 0.5;
    cursor: default;
  }
  .voice-trigger-name {
    display: inline-flex;
    align-items: baseline;
    gap: 6px;
    min-width: 0;
    max-width: 240px;
  }
  /* only the name gives way: a long one ends in an ellipsis, never over the kicker */
  .voice-trigger-label {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .voice-trigger-name small {
    flex: none;
    font-size: 11px;
    color: var(--ink3);
  }
  /* what the control is, before who is picked */
  .voice-kicker {
    flex: none;
    font-size: 11px;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--ink3);
    margin-right: 2px;
  }
  /* the narrator and the voicing action, at the stage head's right */
  .stage-voice {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    margin-left: 12px;
  }
  /* the voicing action, filled or quiet: no taller than the narrator beside
     it, so the head is the same height with it or without it (as
     `.stage-go`) */
  .stage-voice .btn {
    height: 28px;
    min-height: 0;
    margin: -3px 0;
  }
  /* a finger's size, still inside the switch's height beside it */
  @media (pointer: coarse) {
    .stage-voice .btn {
      height: 40px;
      margin: -4px 0;
    }
  }
  .voice-menu {
    position: absolute;
    top: calc(100% + 8px);
    right: 0;
    z-index: 40;
    width: 320px;
    max-height: min(70vh, 520px);
    overflow-y: auto;
    padding: 6px;
    border-radius: 12px;
    border: 1px solid var(--rule);
    background: var(--bg2);
    box-shadow: 0 12px 32px -12px rgba(0, 0, 0, 0.35);
  }
  .voice-group + .voice-group {
    margin-top: 4px;
    padding-top: 4px;
    border-top: 1px solid var(--rule2);
  }
  .voice-group-label {
    padding: 8px 10px 4px;
    font-size: 10.5px;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink3);
  }
  .voice-row {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 7px 10px;
    border: 0;
    border-radius: 8px;
    background: transparent;
    color: var(--ink);
    text-align: left;
    cursor: pointer;
  }
  .voice-row:hover {
    background: var(--fill-06);
  }
  .voice-row[aria-current='true'] {
    background: var(--fill-05);
  }
  .voice-row > svg {
    flex: none;
    color: var(--accent);
  }
  .voice-row-text {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 1px;
  }
  .voice-row-name {
    font-size: 13.5px;
    font-weight: 500;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .voice-row-traits {
    font-size: 12px;
    line-height: 1.35;
    color: var(--ink3);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .voice-empty {
    padding: 8px 10px 10px;
    font-size: 12px;
    line-height: 1.45;
    color: var(--ink3);
  }
  /* the way to a voice of one's own, while there is none: quiet, and
     plainly somewhere else */
  .voice-train {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 7px 10px;
    border: 0;
    border-radius: 8px;
    background: transparent;
    color: var(--ink2);
    text-align: left;
    cursor: pointer;
  }
  .voice-train:hover {
    background: var(--fill-06);
    color: var(--ink);
  }
  .voice-train .voice-row-name {
    color: var(--accent);
  }
  .voice-train > svg {
    flex: none;
    color: var(--ink3);
  }
  /* the narrator's row, when it is a trained voice: a cog beside it that
     unfolds its delivery settings beneath, in one selected block */
  .voice-row-item {
    border-radius: 8px;
    background: var(--fill-05);
  }
  .voice-row-item .voice-row[aria-current='true'] {
    background: transparent;
  }
  .voice-row-line {
    display: flex;
    align-items: center;
  }
  .voice-row-line > .voice-row {
    flex: 1;
    min-width: 0;
  }
  .voice-row-cog {
    position: relative;
    flex: none;
    display: grid;
    place-items: center;
    width: 26px;
    height: 26px;
    margin-right: 5px;
    border: 0;
    border-radius: 6px;
    background: transparent;
    color: var(--ink3);
    cursor: pointer;
    transition:
      background 0.15s,
      color 0.15s;
  }
  .voice-row-cog:hover,
  .voice-row-cog[aria-expanded='true'] {
    background: var(--fill-08);
    color: var(--ink);
  }
  /* set away from the voice as trained */
  .voice-row-cog[data-tuned]::after {
    content: '';
    position: absolute;
    top: 4px;
    right: 4px;
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: var(--accent);
  }
  /* how a trained voice says the deck: two small segmented controls, in the
     stage switch's quiet pill */
  .voice-delivery {
    padding: 10px 10px 8px;
    border-top: 1px solid var(--rule2);
    display: flex;
    flex-direction: column;
    gap: 8px;
  }
  .voice-delivery-row {
    display: flex;
    align-items: center;
    gap: 10px;
  }
  .voice-delivery-label {
    flex: none;
    width: 58px;
    font-size: 12px;
    color: var(--ink2);
  }
  .voice-seg {
    flex: 1;
    min-width: 0;
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    border: 1px solid var(--rule);
    border-radius: 999px;
    padding: 2px;
    gap: 2px;
  }
  .voice-seg button {
    min-width: 0;
    padding: 3px 6px;
    border: 0;
    border-radius: 999px;
    background: transparent;
    font-size: 12px;
    color: var(--ink3);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    cursor: pointer;
    transition:
      background 0.15s,
      color 0.15s;
  }
  .voice-seg button:hover:not(:disabled) {
    color: var(--ink);
  }
  .voice-seg button[aria-checked='true'] {
    background: var(--fill-08);
    color: var(--ink);
  }
  .voice-seg button:disabled {
    opacity: 0.5;
    cursor: default;
  }
  .voice-delivery-hint {
    margin: 2px 0 0;
    font-size: 11.5px;
    line-height: 1.4;
    color: var(--ink3);
  }
  /* A tablet upright has the chat beside the slides: the narrator keeps its
     name and gives up the word "Narrator", and a long name ends in "…",
     so the head never runs to the edge (seen at 768px: "4 slides · NARRATOR
     Mock narrator ˅" against the edge). */
  /* under a finger, the grip is a 32px target, its dots where they were */
  @media (hover: none) {
    .slide-grip {
      width: 32px;
      height: 32px;
      margin: -4px -13px -4px -11px;
    }
  }
  @media (min-width: 768px) and (max-width: 1023px) {
    .voice-kicker {
      display: none;
    }
    .voice-trigger-label {
      max-width: 14ch;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }
  }
  @media (max-width: 767px) {
    /* the glyph and the chevron: the name costs width a phone does not have */
    .voice-trigger-name {
      display: none;
    }
    .voice-trigger {
      width: 34px;
      height: 34px;
      padding: 0;
      gap: 0;
      justify-content: center;
    }
    .voice-menu {
      position: fixed;
      top: calc(var(--bar-h) + 6px);
      left: 12px;
      right: 12px;
      width: auto;
    }
  }

  /* ---------------------------------------------------- per-slide voice */

  /* The controls sit in a strip of their own under the words, inside the box
     and to the right — never over the text, however long it runs. */
  /* on the slide's own line, at its right; the line's small caps are not
     the voice's, so it resets them */
  .slide-voice {
    margin-left: auto;
    align-self: center;
    display: inline-flex;
    align-items: center;
    gap: 2px;
    letter-spacing: 0;
    text-transform: none;
  }
  .slide-voice .voice-dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--ink3);
    margin-right: 4px;
    flex: none;
  }
  .slide-voice.ok .voice-dot {
    background: var(--ok);
  }
  .slide-voice.warn .voice-dot {
    background: var(--warn);
  }
  .slide-voice.bad .voice-dot {
    background: var(--pink);
  }
  .slide-voice.busy .voice-dot {
    background: var(--accent);
    animation: pulse var(--work-beat) ease-in-out infinite;
  }
  /* the voice itself: one control that names what it plays */
  .vo-play {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    min-height: 26px;
    padding: 3px 10px 3px 8px;
    border: 0;
    border-radius: 999px;
    background: transparent;
    color: var(--ink2);
    font-size: 12.5px;
    cursor: pointer;
    transition: background 0.15s, color 0.15s;
  }
  .vo-play:hover:not(:disabled) {
    background: var(--fill-06);
    color: var(--ink);
  }
  .vo-play:disabled {
    cursor: default;
  }
  .vo-play svg {
    width: 12px;
    height: 12px;
    flex: none;
  }
  .slide-voice .vo-play .voice-dot {
    margin-right: 0;
  }
  /* the one action that costs something: said in words, quiet until wanted,
     louder when the voice no longer matches the words above it */
  .vo-make {
    border: 0;
    background: none;
    padding: 3px 6px;
    font-size: 12.5px;
    color: var(--ink3);
    cursor: pointer;
    text-decoration: underline;
    text-decoration-color: transparent;
    text-underline-offset: 3px;
    transition: color 0.15s, text-decoration-color 0.15s;
  }
  .vo-make:hover:not(:disabled) {
    color: var(--ink);
    text-decoration-color: currentColor;
  }
  .slide-voice.warn .vo-make,
  .slide-voice.bad .vo-make {
    color: var(--ink);
    text-decoration-color: var(--rule);
  }
  .vo-make:disabled {
    opacity: 0.4;
    cursor: default;
  }
  /* On a phone the pill is tapped, not clicked: 44px targets, roomier icons. */
  @media (max-width: 767px) {
    .slide-voice {
      gap: 4px;
    }
    .slide-voice .voice-dot {
      width: 8px;
      height: 8px;
    }
    .vo-play,
    .vo-make {
      min-height: 44px;
      font-size: 14px;
      touch-action: manipulation;
    }
    .vo-play svg {
      width: 16px;
      height: 16px;
    }
  }

  /* -------------------------------------------------------------- preview */

  .preview {
    position: fixed;
    inset: 0;
    z-index: 60;
    display: flex;
    flex-direction: column;
    background: #000;
  }
  .preview-head {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 10px 16px;
    font-size: 13px;
    color: var(--ink2);
    background: var(--bg2);
    border-bottom: 1px solid var(--rule);
  }
  .preview-head .sp {
    flex: 1;
  }
  .preview-frame {
    flex: 1;
    width: 100%;
    border: 0;
    background: #000;
  }
  .preview-loading {
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--ink2);
  }
  /* --------------------------------------------------------------- mobile */

  .tabs {
    display: none;
  }
  /* the sheet's handle: a grip to read as one, and the word to say what it does */
  .composer-tuck {
    display: none;
    align-self: center;
    flex-direction: column;
    align-items: center;
    gap: 4px;
    min-height: 32px;
    padding: 2px 16px 0;
    border: 0;
    background: none;
    color: var(--ink3);
    font: inherit;
    font-size: 12px;
    cursor: pointer;
  }
  .composer-tuck .grip {
    width: 36px;
    height: 4px;
    border-radius: 2px;
    background: var(--rule);
  }
  /* a question in the chat while the outline is in front (phone only) */
  .ask-waiting {
    display: none;
    position: fixed;
    left: 50%;
    bottom: calc(52px + env(safe-area-inset-bottom) + 14px);
    transform: translateX(-50%);
    z-index: 5;
    align-items: center;
    gap: 6px;
    height: 36px;
    padding: 0 16px;
    border-radius: 18px;
    border: 1px solid var(--accent);
    background: var(--panel);
    color: var(--ink);
    font: inherit;
    font-size: 13.5px;
    font-weight: 500;
    white-space: nowrap;
    box-shadow: 0 6px 20px rgba(0, 0, 0, 0.25);
    cursor: pointer;
    animation: fade-in 0.2s ease both;
  }
  @media (max-width: 767px) {
    /* The tab row is the tabs plus the strip the home indicator sits in:
       the tabs keep their 52px above it rather than giving it up. */
    .b[data-phase='build'] {
      grid-template-columns: minmax(0, 1fr);
      grid-template-rows:
        var(--bar-h) minmax(0, 1fr)
        calc(52px + env(safe-area-inset-bottom, 0px));
      grid-template-areas: 'bar' 'main' 'tabs';
    }
    /* A story opening on a phone lands in its chat, over the tabs: the
       outline holds that one column and the row the tabs will take. */
    .b[data-phase='opening'] {
      grid-template-columns: minmax(0, 1fr);
      grid-template-rows:
        var(--bar-h) minmax(0, 1fr)
        calc(52px + env(safe-area-inset-bottom, 0px));
      grid-template-areas: 'bar' 'chat' '.';
    }
    .b[data-phase='opening'] .skel-chat {
      border-right: 0;
    }
    .b[data-phase='opening'] .skel-stage {
      display: none;
    }
    /* …and while it gives way to the story. The phone's story has no chat or
       stage area, and an outline still fading out in one that is not there
       made the grid add columns for it: for a moment the bar sat in a
       column 0px wide, its title gone and its buttons thrown to the left. */
    .b[data-phase='build'] .skel-chat {
      grid-area: main;
    }
    .b[data-phase='build'] .skel-stage {
      display: none;
    }
    /* above the tabs, which already keep clear of the home indicator */
    .b[data-phase='build'] .composer {
      padding-bottom: 10px;
    }
    .b[data-phase='opening'] .skel-composer {
      margin-bottom: 10px;
    }
    /* the handle that sizes the conversation beside the stage: there is no
       beside on a phone */
    .chat-resize {
      display: none;
    }
    .b[data-phase='build'] .chat,
    .b[data-phase='build'] .stage {
      grid-area: main;
    }
    .b[data-phase='build'][data-tab='chat'] .stage {
      display: none;
    }
    .b[data-phase='build'][data-tab='stage'] .chat {
      display: none;
    }
    .b[data-phase='build'] .tabs {
      grid-area: tabs;
      display: grid;
      grid-auto-flow: column;
      grid-auto-columns: 1fr;
      border-top: 1px solid var(--rule);
      background: var(--bg);
      padding-bottom: env(safe-area-inset-bottom);
    }
    /* A question waiting in the chat, said over the outline */
    .b[data-phase='build'][data-tab='stage'] .ask-waiting {
      display: inline-flex;
    }
    /* …or risen over it as a sheet: the chat's own composer, the question
       and its answers, with the rest of the chat still out of sight. The
       outline stays where it was and keeps its taps above the sheet. */
    .b[data-phase='build'][data-tab='stage'][data-sheet='open'] .chat {
      display: flex;
      visibility: hidden;
      pointer-events: none;
    }
    .b[data-sheet='open'] .chat .composer {
      visibility: visible;
      pointer-events: auto;
      position: fixed;
      left: 0;
      right: 0;
      bottom: calc(52px + env(safe-area-inset-bottom, 0px));
      z-index: 6;
      max-height: 62dvh;
      overflow-y: auto;
      padding-top: 6px;
      background: var(--panel);
      border-top: 1px solid var(--rule);
      border-radius: 16px 16px 0 0;
      box-shadow: 0 -12px 32px rgba(0, 0, 0, 0.22);
      animation: sheet-up 0.26s cubic-bezier(0.2, 0.8, 0.2, 1) both;
    }
    /* the outline's foot scrolls clear of the sheet: room inside the pane
       that scrolls, not around it — padding the stage shrank the pane to a
       third of the screen, and a section to bring into sight above the
       sheet was measured against that */
    .b[data-sheet='open'] .stage .scroller {
      padding-bottom: 45dvh;
    }
    .b[data-sheet='open'] .composer-tuck {
      display: flex;
    }
    @media (prefers-reduced-motion: reduce) {
      .b[data-sheet='open'] .chat .composer {
        animation: none;
      }
    }
    /* the bar at the foot is the switch on a phone */
    .stage-head .stage-switch {
      display: none;
    }
    .tabs button {
      display: flex;
      align-items: center;
      justify-content: center;
      gap: 8px;
      font-size: 13px;
      color: var(--ink3);
      min-height: 44px;
    }
    .tabs button[aria-pressed='true'] {
      color: var(--ink);
    }
    .tabs button .dot {
      width: 6px;
      height: 6px;
      border-radius: 50%;
      background: var(--accent);
      display: none;
    }
    .tabs button.new .dot {
      display: block;
    }
    .bar {
      padding: 0 10px;
      gap: 6px;
    }
    .bar .status span {
      display: none;
    }
    .crumb {
      display: none !important;
    }
    .brand {
      font-size: 15px;
    }
    /* the words go; the mark that says what the button is stays (the
       waiting dot's icon is a span too, and went with them) */
    .bar-actions .btn > span:not(.needs-mark),
    .bar-side .btn > span:not(.needs-mark) {
      display: none;
    }
    .bar-actions .btn,
    .bar-side .btn {
      padding: 0;
      width: 34px;
      height: 34px;
      flex: none;
      justify-content: center;
    }
    /* The head on a phone: the way back is its chevron, Preview its play
       mark, and the title takes what they give up. Share keeps its word —
       it is the one that matters.
       Inside a story the chevron is the way back, so the mark, which went
       to the same place, gives its room to the title. Everything but the
       title keeps its size: the title is the one thing that gives, and a
       long one ends in an ellipsis rather than pushing Share off the
       screen or the account under it. */
    .b:not([data-phase='start']) .bar .brand,
    &.sc-bar-only .bar .brand {
      display: none;
    }
    .bar > :not(.story-title):not(.bar-account) {
      flex: none;
    }
    /* In a story the account is an avatar and keeps its size. On the start
       page there is no title, and the workspace's name is what gives: it
       ends in an ellipsis rather than pushing the account off a small
       phone. */
    .b:not([data-phase='start']) .bar-account,
    &.sc-bar-only .bar-account {
      flex: none;
    }
    .b[data-phase='start'] .bar-account {
      flex: 0 1 auto;
      min-width: 0;
    }
    /* The visual identity's bar is a story's: its title is the way back,
       and the account is the avatar alone. */
    .b:not([data-phase='start']) .bar-account .ws-caret,
    &.sc-bar-only .bar-account .ws-caret,
    &.sc-bar-only .bar-account .ws-name {
      display: none;
    }
    /* the avatar alone: round, as wide as the row is high */
    .b:not([data-phase='start']) .bar-account > button,
    &.sc-bar-only .bar-account > button {
      aspect-ratio: 1;
      padding: 0;
      justify-content: center;
    }
    /* A phone keeps the avatar alone on the start page too. Most people
       have one workspace, so its name tells them nothing, and on a small
       phone it was an ellipsis anyway; the menu names it, one tap away. */
    @media (max-width: 599px) {
      .b[data-phase] .bar-account {
        flex: none;
      }
      .b[data-phase] .bar-account .ws-name,
      .b[data-phase] .bar-account .ws-caret {
        display: none;
      }
      .b[data-phase] .bar-account > button {
        aspect-ratio: 1;
        padding: 0;
        justify-content: center;
      }
    }
    .back {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      min-width: 32px;
      height: 32px;
      padding: 0;
      font-size: 18px;
      flex: none;
    }
    .back-label {
      display: none;
    }
    .story-title {
      max-width: none;
      min-width: 0;
      flex: 1 1 auto;
      padding: 4px 10px;
    }
    .deck-acts {
      flex: none;
    }
    .deck-act {
      padding: 5px 10px;
    }
    .deck-act-label {
      display: none;
    }
    /* On a phone Preview is at the head of the Slides tab, beside the
       slides it plays, and the bar keeps its room for the story's title:
       Share stands alone, with no tray around one button. */
    .bar .deck-act:not(.share) {
      display: none;
    }
    .deck-acts {
      padding: 0;
      background: none;
      box-shadow: none;
    }
    .stage-preview {
      display: inline-flex;
      align-items: center;
      gap: 6px;
      /* a finger's size; the voice button beside it keeps the head's height */
      height: 36px;
      margin: -2px 0;
      padding: 0 14px 0 11px;
      border: 1px solid var(--rule);
      border-radius: 999px;
      background: var(--bg);
      color: var(--ink);
      font: inherit;
      font-size: 13px;
      font-weight: 500;
      white-space: nowrap;
      cursor: pointer;
    }
    .stage-preview > svg {
      flex: none;
    }
    /* the narrowest phones: Share keeps its mark and gives its word to the
       title, which would otherwise be a word and an ellipsis */
    @media (max-width: 359px) {
      .deck-act.share .share-label {
        display: none;
      }
      /* only its mark: round, not a pill squeezed to an egg */
      .deck-act.share {
        width: 34px;
        height: 34px;
        min-height: 0;
        padding: 0;
        justify-content: center;
      }
    }
    .b[data-phase='build'] .chat {
      border-right: 0;
    }
    .thread {
      padding: 16px 14px 8px;
    }
    .composer {
      padding: 8px 12px calc(10px + env(safe-area-inset-bottom));
    }
    .scroller {
      padding: 0 16px;
    }
    .stage-head {
      padding: 8px 16px;
    }
    .part .job,
    .part .text,
    .part .ghost,
    .points {
      margin-left: 0;
      padding-left: 0;
    }
  }

  @media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
      animation-duration: 0.01ms !important;
      animation-iteration-count: 1 !important;
      transition-duration: 0.01ms !important;
      scroll-behavior: auto !important;
    }
  }

  /* ------------------------------------------------------- the action bar */
  /*
   * What the person can do right now, pinned above the composer rather than
   * left in the transcript — it is a function of the current view, so it has to
   * be able to disappear when the state that justified it does.
   */
  /* a decision put over the composer: the question, then its answers */
  .actions.asking p {
    flex-basis: 100%;
    margin: 0 0 2px;
    font-size: 13.5px;
    color: var(--ink2);
  }
  /* Folded away while a question stands: one quiet word, so the question
     under it is plainly the thing to answer. */
  .actions.folded {
    justify-content: flex-start;
  }
  .actions.folded button {
    border-color: transparent;
    background: none;
    color: var(--ink3);
    padding-left: 0;
  }
  .actions.folded button:hover {
    color: var(--ink);
  }
  .acts-fold {
    order: 99;
    width: 26px;
    padding: 0;
    border-color: transparent;
    background: none;
    color: var(--ink3);
  }
  .acts-fold:hover {
    color: var(--ink);
  }

  .actions {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
    padding: 10px 20px 2px;
  }
  /* the judge's points, in the thread */
  /*
   * What the check found, as a report rather than a paragraph: a panel of its
   * own, a head that says where the draft stands (amber when it does not yet
   * do what was asked), then one group per thing it is about — the story, or
   * a card, named once in small type with its mark — and its findings as a
   * list under that name. "And 2 more like it" is set apart as the note it
   * is, not a finding of its own.
   */
  /* `.msg.agent .bubble` is written with more weight and gives every agent
     bubble a transparent left edge (the gutter's rule lives there); this
     panel names both classes so its own edge and padding win on all sides */
  .msg.agent .bubble.points {
    padding: 14px 16px 12px;
    border: 0;
    border-radius: 12px;
    /* the question sheet's surface: one kind of panel in the conversation */
    background: var(--fill-03);
    box-shadow: inset 0 0 0 1px var(--rule);
  }
  .bubble.points .points-head {
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 0 0 10px;
  }
  .bubble.points .points-dot {
    flex: none;
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--ink3);
  }
  .bubble.points.failing .points-dot {
    background: var(--warn);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--warn) 16%, transparent);
  }
  .bubble.points .points-lead {
    margin: 0;
    font-weight: 500;
    color: var(--ink);
  }
  .bubble.points ul {
    list-style: none;
    margin: 0;
    padding: 0;
  }
  .bubble.points li.finding {
    padding: 10px 0 2px;
    border-top: 1px solid var(--rule2);
  }
  .bubble.points li.finding + li.finding {
    margin-top: 8px;
  }
  .bubble.points .finding-of {
    display: flex;
    align-items: center;
    gap: 7px;
    margin: 0 0 6px;
    font-size: 12px;
    letter-spacing: 0.01em;
    color: var(--ink3);
  }
  .bubble.points .finding-of svg {
    flex: none;
    color: var(--ink3);
    stroke: var(--ink3);
  }
  .bubble.points .finding-lines {
    display: flex;
    flex-direction: column;
    gap: 5px;
  }
  .bubble.points .finding-lines li {
    position: relative;
    padding-left: 20px;
    font-size: 14px;
    line-height: 1.5;
    color: var(--ink);
  }
  /* a small dot on the first line's middle, under the mark above it */
  .bubble.points .finding-lines li::before {
    content: '';
    position: absolute;
    left: 5px;
    top: calc(0.75em - 2px);
    width: 4px;
    height: 4px;
    border-radius: 50%;
    background: var(--ink3);
  }
  .bubble.points .finding-more {
    margin: 6px 0 0;
    padding-left: 20px;
    font-size: 12.5px;
    color: var(--ink3);
  }
  /* a card's name: a quiet link to it, not a pill */
  .bubble.points .finding-of .part-tag {
    display: inline;
    margin: 0;
    padding: 0;
    border: 0;
    background: none;
    font-size: 12px;
    letter-spacing: 0.01em;
    color: var(--ink3);
    text-align: left;
    white-space: normal;
  }
  .bubble.points .finding-of .part-tag:hover {
    color: var(--ink);
    text-decoration: underline;
    text-underline-offset: 3px;
  }
  /* which part a line is about; clicking shows it on the right */
  /* Which card a message is about: a line over it that is a way to the
     card, not a pill. Small, quiet, and a link on hover. */
  .part-tag {
    display: block;
    margin: 0 0 4px;
    padding: 0;
    border: 0;
    background: none;
    font-size: 12px;
    letter-spacing: 0.01em;
    color: var(--ink3);
    text-align: left;
    cursor: pointer;
  }
  .part-tag:hover {
    color: var(--ink);
    text-decoration: underline;
    text-underline-offset: 3px;
  }
  /* slides or parts, once there is a deck */
  .stage-switch {
    display: inline-flex;
    /* beside the label rather than a gap away from it: the label is one or
       two short words and the switch is what you reach for next */
    margin-left: 10px;
    border: 1px solid var(--rule);
    border-radius: 999px;
    padding: 2px;
    gap: 2px;
  }
  /* once there is a deck the switch stands in for the label: nothing to
     sit beside, so it lines up with the column under it */
  .stage-switch:first-child {
    margin-left: 0;
  }
  .stage-switch button {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-size: 12px;
    padding: 3px 10px 3px 8px;
    border-radius: 999px;
    color: var(--ink3);
  }
  .stage-switch button svg {
    width: 13px;
    height: 13px;
  }
  .stage-switch button[aria-selected='true'] {
    background: var(--fill-08);
    color: var(--ink);
  }
  /* what the chips answer: the judge's points, each with its part */
  .actions-lead {
    flex: 1 0 100%;
    font-size: 13px;
    color: var(--ink2);
    margin-bottom: 4px;
  }
  .actions-lead ul {
    list-style: none;
    margin: 6px 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 4px;
  }
  .actions-lead li {
    display: flex;
    gap: 8px;
    align-items: baseline;
    padding: 6px 10px;
    border-radius: 8px;
    background: var(--fill-04);
    border-left: 2px solid var(--warn);
    line-height: 1.4;
  }
  .actions-lead li b {
    flex: none;
    font-weight: 500;
    color: var(--ink);
  }
  .actions-lead li span {
    min-width: 0;
  }
  .b:not([data-phase='build']) .actions {
    padding: 10px max(20px, calc(50% - 360px)) 2px;
    justify-content: flex-start;
  }
  .actions button {
    font-size: 12.5px;
    padding: 6px 11px;
    border-radius: 999px;
    border: 1px solid var(--rule);
    color: var(--ink2);
    background: var(--fill-03);
    min-height: 30px;
    transition:
      background 0.15s,
      color 0.15s,
      border-color 0.15s;
  }
  .actions button:hover {
    color: var(--ink);
    border-color: var(--fill-22);
  }
  /* The one to press is filled in ink, as every primary button is (Send,
     "Use this site"), and lets its ink down under the pointer. It wore the
     accent's ring and wash, and the accent means where you are. */
  .actions button.primary {
    color: var(--on-ink);
    border-color: var(--ink);
    background: var(--ink);
    font-weight: 500;
  }
  .actions button.primary:hover {
    color: var(--on-ink);
    border-color: var(--ink2);
    background: var(--ink2);
  }
  .actions button.primary > svg {
    opacity: 1;
  }
  /*
   * Other layouts for one slide, offered by the agent: a card over the chat
   * box, its choices the real slide each would be. One primary press
   * updates that slide alone.
   */
  /* The tray: the one place over the composer for what the chat put up
     (Tray.tsx). One card shows; it never takes more than half the column
     (less on a phone, where the keyboard takes the rest), and scrolls in
     itself rather than pushing the composer off the screen. */
  /* The tray is a <Reveal>, and so are the card and its folded line in
     it: each opens and closes rather than coming and going in a frame. The
     card's room gives way under the cap, so the card scrolls and what
     waits under it stays in view. */
  .tray {
    max-height: min(50%, 48dvh);
  }
  .tray > .reveal-in,
  .tray-card-room > .reveal-in {
    display: flex;
    flex-direction: column;
  }
  .tray-card-room {
    flex: 0 1 auto;
    min-height: 0;
  }
  .tray-card {
    flex: 0 1 auto;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
  }
  /* the brand's website prompt opens a small popover from its line: a
     scrolling tray would cut it off, and it is short enough not to need to */
  .tray-card:has(.vi-nudge-wrap) {
    overflow: visible;
  }
  .tray .vi-guess,
  .tray .vi-ready {
    margin: 10px 20px 2px;
  }
  @media (max-width: 600px) {
    .tray .vi-guess,
    .tray .vi-ready {
      margin: 8px 12px 2px;
    }
  }
  /* a card taller than the tray scrolls, and its choice stays in reach:
     the row of what to do is pinned to the tray's foot, on the card's own
     ground (the chat's, under the card's fill), so nothing reads through */
  .tray-card .layout-actions {
    position: sticky;
    bottom: -12px;
    margin: 0 -14px -12px;
    padding: 10px 14px 12px;
    background:
      linear-gradient(var(--fill-03), var(--fill-03)),
      var(--bg2);
    border-radius: 0 0 12px 12px;
  }
  /* the card in one line while they write or answer something else */
  .tray-fold {
    margin: 8px 20px 2px;
    padding: 8px 14px;
    display: flex;
    align-items: center;
    gap: 12px;
    border: 1px solid var(--rule);
    border-radius: 12px;
    background: var(--fill-03);
    font-size: 13px;
    color: var(--ink2);
    animation: line-in 0.16s ease-out both;
  }
  .tray-fold-what {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .tray-fold button,
  .tray-more button {
    flex: none;
    padding: 0;
    border: 0;
    background: none;
    font: inherit;
    color: var(--ink);
    text-decoration: underline;
    text-decoration-color: var(--rule);
    text-underline-offset: 3px;
    cursor: pointer;
  }
  .tray-fold button:hover,
  .tray-more button:hover {
    text-decoration-color: currentColor;
  }
  /* the cards waiting their turn, each a press away */
  .tray-more {
    flex: none;
    margin: 6px 20px 0;
    display: flex;
    flex-wrap: wrap;
    gap: 4px 12px;
    font-size: 12.5px;
    color: var(--ink3);
  }
  .tray-more button {
    color: var(--ink2);
  }
  @media (max-width: 767px) {
    .tray-fold,
    .tray-more {
      margin-inline: 12px;
    }
  }
  @media (pointer: coarse) {
    .tray-fold button,
    .tray-more button {
      position: relative;
    }
    .tray-fold button::after,
    .tray-more button::after {
      content: '';
      position: absolute;
      inset: -10px -6px;
    }
  }
  .layout-card {
    margin: 10px 20px 2px;
    padding: 12px 14px;
    border: 1px solid var(--rule);
    border-radius: 12px;
    background: var(--fill-03);
    display: flex;
    flex-direction: column;
    gap: 10px;
    animation: line-in 0.16s ease-out both;
  }
  .layout-card p {
    margin: 0;
  }
  .layout-head {
    font-size: 13px;
    color: var(--ink2);
  }
  /* the slide's name is the way to it, quiet as the chat's other links */
  .layout-head .layout-slide {
    padding: 0;
    border: 0;
    background: none;
    font: inherit;
    font-weight: 600;
    color: var(--ink);
    cursor: pointer;
    text-decoration: underline;
    text-decoration-color: color-mix(in srgb, currentColor 35%, transparent);
    text-underline-offset: 3px;
    transition: text-decoration-color 0.15s;
  }
  .layout-head .layout-slide:hover,
  .layout-head .layout-slide:focus-visible {
    text-decoration-color: currentColor;
  }
  /* The choices: the real slide each would be, two to a row, one alone
     when only one fits. Picked is the accent edge, as a chip's is. */
  .layout-grid {
    display: grid;
    gap: 10px;
    grid-template-columns: 1fr;
  }
  .layout-grid.n2 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .layout-choice {
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 6px 6px 8px;
    border: 1px solid var(--rule);
    border-radius: 10px;
    background: var(--fill-03);
    color: var(--ink2);
    text-align: left;
    font: inherit;
    cursor: pointer;
    transition:
      border-color 0.15s,
      background 0.15s;
  }
  .layout-choice:hover:not(:disabled):not(.waiting):not(.on) {
    border-color: var(--fill-22);
  }
  .layout-choice.on {
    border-color: var(--ring);
    background: var(--accent-soft);
    color: var(--ink);
  }
  .layout-choice.waiting {
    cursor: default;
  }
  .layout-choice:disabled {
    cursor: default;
  }
  .layout-choice.applying {
    cursor: progress;
  }
  .layout-thumb .mini,
  .layout-thumb.mini {
    border-radius: 6px;
    box-shadow: 0 0 0 1px var(--rule);
  }
  /* the frame is a picture of a slide, not something to click into */
  .layout-thumb iframe {
    pointer-events: none;
  }
  .layout-name {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 12.5px;
    font-weight: 500;
    color: var(--ink);
    padding: 0 2px;
  }
  .layout-why {
    font-size: 12px;
    line-height: 17px;
    color: var(--ink2);
    padding: 0 2px;
  }
  .layout-pic {
    flex: none;
    opacity: 0.7;
  }
  .layout-about,
  .layout-hint,
  .layout-wait {
    font-size: 12.5px;
    line-height: 18px;
    color: var(--ink2);
  }
  .layout-hint {
    color: var(--ink3);
  }
  /* What went wrong, in the conversation's own way: said plainly in ink,
     not in the colour of an alarm (the thread's failed turn is the same).
     What the check found about a change that worked is a note, with the
     attention mark, quieter still (cardParts.tsx). */
  .layout-error {
    font-size: 12.5px;
    line-height: 18px;
    color: var(--ink);
  }
  .layout-note {
    display: flex;
    align-items: flex-start;
    gap: 6px;
    font-size: 12.5px;
    line-height: 18px;
    color: var(--ink2);
  }
  .layout-note svg {
    flex: none;
    margin-top: 2.5px;
    color: var(--ink3);
  }
  /* Try again: the circular arrow before the words */
  .layout-actions button.again {
    display: inline-flex;
    align-items: center;
    gap: 6px;
  }
  .layout-actions button.again svg {
    flex: none;
    opacity: 0.85;
  }
  /* Undo at work: it says so and keeps its ink */
  .layout-actions button[aria-disabled] {
    cursor: progress;
  }
  .layout-said {
    font-size: 13px;
    line-height: 19px;
    color: var(--ink);
  }
  /* Photos found for a slide: a row of small pictures, three to a row,
     one press each. */
  .images-grid {
    display: grid;
    gap: 6px;
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
  .image-choice {
    position: relative;
    padding: 0;
    border: 1px solid var(--rule);
    border-radius: 8px;
    overflow: hidden;
    background: var(--fill-03);
    aspect-ratio: 16 / 10;
    cursor: pointer;
    transition: border-color 0.15s;
  }
  .image-choice img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
  }
  /* The site a picture from the web came from, over its corner */
  .image-credit {
    position: absolute;
    left: 4px;
    bottom: 4px;
    max-width: calc(100% - 8px);
    padding: 1px 5px;
    border-radius: 4px;
    background: rgb(0 0 0 / 0.6);
    color: #fff;
    font-size: 10px;
    line-height: 15px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    pointer-events: none;
  }
  .image-choice:hover:not(:disabled) {
    border-color: var(--fill-22);
  }
  .image-choice.on {
    border-color: var(--ring);
  }
  /* New words for a slide: one rewrite a row, what goes struck through
     above what comes, where it comes from under them. */
  .wording-list {
    display: flex;
    flex-direction: column;
    gap: 6px;
  }
  .wording-choice {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 8px 10px;
    border: 1px solid var(--rule);
    border-radius: 10px;
    background: var(--fill-03);
    color: var(--ink);
    text-align: left;
    font: inherit;
    cursor: pointer;
    transition: border-color 0.15s;
  }
  .wording-choice:hover:not(:disabled) {
    border-color: var(--fill-22);
  }
  .wording-choice.on {
    border-color: var(--ring);
    background: var(--accent-soft);
  }
  .wording-choice:disabled {
    cursor: default;
  }
  .wording-edit {
    display: flex;
    flex-direction: column;
    font-size: 13px;
    line-height: 18px;
  }
  .wording-edit s {
    color: var(--ink3);
    text-decoration-thickness: 1px;
  }
  /* the last place: their own pictures, not one more photo */
  .image-choice.browse {
    display: grid;
    place-items: center;
    border-style: dashed;
    background: transparent;
    color: var(--ink2);
    font: inherit;
    font-size: 12px;
    font-weight: 500;
    align-content: center;
    gap: 4px;
  }
  .browse-plus {
    display: grid;
    place-items: center;
    width: 26px;
    height: 26px;
    border-radius: 999px;
    background: var(--fill-03);
    box-shadow: 0 0 0 1px var(--rule);
  }
  .image-choice.browse:hover:not(:disabled) {
    color: var(--ink);
  }
  .image-choice:disabled {
    cursor: default;
    opacity: 0.7;
  }
  .layout-actions {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
  }
  .layout-actions button {
    font-size: 12.5px;
    padding: 6px 12px;
    border-radius: 999px;
    border: 1px solid var(--rule);
    color: var(--ink2);
    background: var(--fill-03);
    min-height: 30px;
    cursor: pointer;
  }
  .layout-actions button:hover:not(:disabled) {
    color: var(--ink);
    border-color: var(--fill-22);
  }
  .layout-actions button.primary {
    color: var(--on-ink);
    border-color: var(--ink);
    background: var(--ink);
    font-weight: 500;
  }
  .layout-actions button.primary:hover:not(:disabled) {
    color: var(--on-ink);
    border-color: var(--ink2);
    background: var(--ink2);
  }
  .layout-actions button:disabled {
    opacity: 0.55;
    cursor: default;
  }
  .layout-actions button.ghost {
    border-color: transparent;
    background: none;
  }
  /* Changing a slide, or putting it back: the light the app passes over
     anything at work crosses the button, and the thumbnail being put in,
     for as long as it takes. A working button keeps its full ink: it is
     busy, not unavailable. */
  .layout-actions button.working {
    position: relative;
    overflow: hidden;
    cursor: progress;
  }
  .layout-actions button.working:disabled {
    opacity: 1;
  }
  .layout-actions button.primary.working::after {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: inherit;
    background: linear-gradient(
      100deg,
      transparent 30%,
      color-mix(in srgb, var(--on-ink) 24%, transparent) 50%,
      transparent 70%
    );
    background-size: 220% 100%;
    pointer-events: none;
    animation: sheen var(--work-sweep) linear infinite;
  }
  .layout-choice.applying .layout-thumb {
    position: relative;
  }
  .layout-choice.applying .layout-thumb::after {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: 6px;
    background: linear-gradient(
      100deg,
      transparent 32%,
      color-mix(in srgb, var(--ink) 16%, transparent) 50%,
      transparent 68%
    );
    background-size: 220% 100%;
    pointer-events: none;
    animation: sheen var(--work-sweep) linear infinite;
  }
  @media (prefers-reduced-motion: reduce) {
    .layout-actions button.primary.working::after,
    .layout-choice.applying .layout-thumb::after {
      animation: none;
      background: none;
    }
  }
  @media (max-width: 600px) {
    .layout-card {
      margin: 8px 12px 2px;
    }
  }
  /* A making action carries its mark before its words (see icons.tsx): the
     deck's spark or the voice's wave the first time, the circular arrow
     after. Same size, same gap, wherever the button is. */
  .actions button:has(> svg),
  .stage-head .stage-go:has(> svg),
  .stage-behind button:has(> svg),
  .slide-voice .vo-make:has(> svg) {
    display: inline-flex;
    align-items: center;
    gap: 6px;
  }
  .actions button > svg,
  .stage-head .stage-go > svg,
  .stage-behind button > svg,
  .slide-voice .vo-make > svg {
    flex: none;
    opacity: 0.85;
  }
  @media (max-width: 767px) {
    .actions {
      padding: 0 12px 2px;
    }
  }

  /* A long reply the stage is already showing, folded away. */
  .bubble .disclose {
    display: flex;
    flex-direction: column;
    gap: 2px;
    align-items: flex-start;
    text-align: left;
    width: 100%;
    border: 1px solid var(--rule);
    border-radius: 12px;
    padding: 10px 12px;
    background: var(--fill-03);
    color: var(--ink2);
    font-size: 13.5px;
    transition:
      background 0.15s,
      border-color 0.15s;
  }
  .bubble .disclose:hover {
    background: var(--fill-06);
    border-color: var(--fill-2);
    color: var(--ink);
  }
  .bubble .disclose small {
    color: var(--ink3);
    font-size: 12px;
  }
  .bubble .disclose[aria-expanded='true'] {
    margin-bottom: 10px;
  }
  .msg.agent .bubble h4 {
    margin: 14px 0 6px;
    font-size: 13px;
    font-weight: 500;
    color: var(--ink);
    letter-spacing: 0.01em;
  }
  .msg.agent .bubble h4:first-child {
    margin-top: 0;
  }

  /* The ground a slide sits on. The cast uses these to punctuate the deck. */
  .mini.s-tint {
    background:
      radial-gradient(
        90% 70% at 15% 10%,
        rgba(94, 86, 249, 0.42),
        transparent 62%
      ),
      radial-gradient(
        60% 50% at 95% 100%,
        rgba(240, 66, 138, 0.24),
        transparent 60%
      ),
      #211f2c;
  }
  .mini.s-inverse {
    background:
      radial-gradient(
        80% 70% at 80% 0%,
        rgba(151, 147, 251, 0.28),
        transparent 60%
      ),
      /* a slide's ground is the deck's, never the app's: fixed, not --ink,
         which flips to near-black in the light theme under #121212 text */
      #f1efe9;
  }
  .mini.s-inverse .s-big {
    color: #121212;
  }
  .mini.s-inverse .s-label,
  .mini.s-inverse .s-sub {
    color: rgba(18, 18, 17, 0.6);
  }
  .mini.s-inverse::before {
    opacity: 0.35;
  }

  /* A real slide from the rendered deck, scaled into its card. */
  .mini.deck {
    background: var(--stage);
    box-shadow: 0 0 0 1px var(--fill-06);
  }
  .mini.deck::before {
    content: none;
  }
  .mini.deck iframe {
    border: 0;
    display: block;
    /* The artboard is 1600×900 and gets scaled; it must not drive the card. */
    position: absolute;
    top: 0;
    left: 0;
    /* A slide in the list is a picture of a slide: the card around it is what
       takes the click, so a click anywhere on it opens the slide rather than
       disappearing into a frame. */
    pointer-events: none;
  }
  /* Except the one a slide is open to be written on, which is the one thing
     here you reach into: without this, a click inside an open slide lands on
     nothing, the words lose the caret, and the slide stops taking writing
     after the first word — it looks like editing simply stopped working. */
  .mini.deck.edit-frame iframe {
    pointer-events: auto;
  }

  /*
   * Turn it upright (UprightPrompt). Over the page, not instead of it: the
   * page behind is inert and exactly as it was. The page's own ground, one
   * picture, a line to read and a way to decline, centred in whatever is
   * left between the notch and the home indicator.
   */
  .upright {
    position: fixed;
    inset: 0;
    z-index: 60;
    display: grid;
    place-items: center;
    padding: max(20px, env(safe-area-inset-top, 0px))
      max(24px, env(safe-area-inset-right, 0px))
      max(20px, env(safe-area-inset-bottom, 0px))
      max(24px, env(safe-area-inset-left, 0px));
    background: var(--bg);
    animation: fade-in 0.2s ease both;
  }
  .upright:focus {
    outline: none;
  }
  .upright-card {
    display: grid;
    justify-items: center;
    gap: 10px;
    max-width: 340px;
    text-align: center;
  }
  .upright-art {
    margin-bottom: 4px;
    color: var(--ink);
    fill: none;
    stroke: currentColor;
    stroke-width: 1.5;
    stroke-linecap: round;
    stroke-linejoin: round;
  }
  .upright-arc {
    color: var(--accent);
    stroke: var(--accent);
  }
  /* the phone turns from its side to upright, rests there, and turns again */
  .upright-phone {
    transform-box: fill-box;
    transform-origin: center;
    animation: upright-turn 2.8s cubic-bezier(0.65, 0, 0.35, 1) infinite;
  }
  .upright h2 {
    margin: 0;
    font-family: var(--display);
    font-size: 20px;
    font-weight: 500;
    letter-spacing: -0.01em;
    line-height: 1.2;
    text-wrap: balance;
    color: var(--ink);
  }
  .upright p {
    margin: 0;
    font-size: 14px;
    line-height: 1.5;
    color: var(--ink2);
    text-wrap: pretty;
  }
  .upright button {
    margin-top: 6px;
    min-height: 40px;
    padding: 0 16px;
    border-radius: 999px;
    color: var(--ink3);
    font-size: 13px;
    text-decoration: underline;
    text-decoration-color: color-mix(in srgb, currentColor 35%, transparent);
    text-underline-offset: 3px;
  }
  .upright button:focus-visible {
    outline: 2px solid var(--ring);
    outline-offset: 2px;
  }
  @media (prefers-reduced-motion: reduce) {
    .upright,
    .upright-phone {
      animation: none;
    }
  }

  /*
   * Under a finger.
   *
   * Everything above is drawn for a pointer, where 22px is plenty. A finger
   * needs about 40, so on a coarse pointer each control reaches that far:
   * a pill or a round button grows to it, an icon inside keeps its size,
   * and a word that is a link takes the reach in padding it gives straight
   * back in margin, so the line it sits in does not move.
   */
  @media (pointer: coarse) {
    /* iOS Safari zooms the page into any field under 16px the moment it is
       focused, and does not zoom back out. The chat box was 15px once a
       story was open, so every tap to type zoomed the whole page in. Under
       a finger every field takes 16px; the start screen's larger box keeps
       its size. */
    :is(
        textarea,
        select,
        input:not(
            [type='checkbox'],
            [type='radio'],
            [type='range'],
            [type='file']
          )
      ):where(:not(.sc-island *)) {
      font-size: 16px !important;
    }
    .b[data-phase='start'] .box textarea {
      font-size: 17px !important;
    }
    /* one line of the chat box was 31px to aim at: it takes a finger's
       reach in padding and gives it straight back in margin, so the box
       around it is drawn exactly as before. The start page's box is
       tall enough already (its min-height would only take the margin) */
    .b:not([data-phase='start']) .box textarea {
      padding-block: 10px 7px;
      margin-block: -4px -5px;
    }
    /* The rest of the conversation's small controls, measured on a phone:
       the words that act take an unseen reach, the pills grow. */
    .layout-head .layout-slide,
    .doing-more,
    .vi-without,
    .msg.sys.failed button,
    .srcs .src,
    .brand-hint button {
      position: relative;
    }
    .layout-head .layout-slide::after,
    .doing-more::after,
    .vi-without::after,
    .msg.sys.failed button::after,
    .srcs .src::after,
    .brand-hint button::after {
      content: '';
      position: absolute;
      inset: -10px -4px;
    }
    .vi-guess-other,
    .vi-guess .btn {
      min-height: 40px;
    }
    .acts-fold {
      min-width: 40px;
    }
  }
  @media (pointer: coarse) {
    /* The small ones the first pass missed. Pills that are pressed grow to
       36; the icons and the words that act keep their drawn size and take
       an unseen reach around them, so nothing they sit in moves. */
    .slide-confirm,
    .stage-behind > button,
    .focus-step button,
    .follow-pill button,
    .composer .tools .skip button,
    .vi-nudge button {
      min-height: 36px;
    }
    .staged button,
    .composer .tools .about button,
    .link-chip :is(.link-name, .link-drop),
    .mark-acts button,
    .note-gap-send,
    .missing-line button,
    .card-ref,
    .part-tag,
    .removed-line button,
    .note-earlier-bar button,
    .src-inline,
    .voice-seg button,
    .note-scratch li.relates {
      position: relative;
    }
    .staged button::after,
    .composer .tools .about button::after,
    .link-chip :is(.link-name, .link-drop)::after,
    .voice-redo::after {
      content: '';
      position: absolute;
      inset: -8px;
    }
    .voice-row-cog::before {
      content: '';
      position: absolute;
      inset: -7px;
    }
    .mark-acts button::after,
    .note-gap-send::after,
    .missing-line button::after,
    .card-ref::after,
    .part-tag::after,
    .removed-line button::after,
    .note-earlier-bar button::after,
    .src-inline::after,
    .voice-seg button::after,
    .note-scratch li.relates::after {
      content: '';
      position: absolute;
      inset: -10px -4px;
    }
    .voice-redo {
      height: 32px;
    }
    .script-field:has(.voice-redo) textarea {
      padding-bottom: 42px;
    }
    /*
     * A press answers. The page takes away the phone's own grey flash on
     * every button (it drew a box around round and pill shapes alike) and
     * nothing had taken its place, so a tap on something slow to answer —
     * Share, Prepare, a link being added — looked like a tap that missed.
     * Whatever is pressed dims while the finger is on it, as a phone's own
     * controls do, and comes back as it lifts.
     */
    :is(button, a[href], [role='button'], [role='tab']):where(
        :not(.sc-island *)
      ):active:not(:disabled, [aria-disabled='true']) {
      opacity: 0.62;
      transition: none;
    }
    .deck-act,
    .vo-play,
    .quick button,
    .actions button,
    .chips button,
    .link-chip,
    .story-card-actions button {
      min-height: 40px;
    }
    .deck-act:not(.share) {
      min-width: 40px;
    }
    .story-card-actions > .story-card-more > .story-card-more-btn {
      width: 40px;
      height: 40px;
    }
    .story-card-menu [role='menuitem'] {
      min-height: 40px;
    }
    /* One height across the bar under a finger, the back button's: the
       icon-only ones round, the worded ones pills. In the tray (from
       768px) the pills give its padding back, so the tray is that height. */
    .bar .needs-btn {
      height: 40px;
      min-width: 40px;
    }
    .bar-account > button {
      height: 40px;
      padding: 0 12px 0 7px;
    }
    .stage-preview {
      height: 40px;
      margin: -4px 0;
    }
    @media (min-width: 768px) {
      .deck-acts .deck-act {
        min-height: 36px;
      }
    }
    @media (max-width: 359px) {
      .deck-act.share {
        width: 40px;
        height: 40px;
      }
    }
    .voice-trigger {
      height: 40px;
    }
    .stage-switch button {
      min-height: 36px;
      padding-inline: 12px 14px;
    }
    .ib,
    .composer .ask .step-arrow,
    .back {
      min-width: 40px;
      height: 40px;
    }
    /* the story's name, pressed to rename it, is as tall as the way back
       beside it: one trail, one height. It is one line (nowrap), so the
       line itself is the height and the ellipsis still ends it */
    .bar button.story-title {
      padding-block: 0;
      line-height: 40px;
    }
    .bar .story-title.title-field input {
      min-height: 40px;
    }
    .note-tool,
    .slide-tool {
      width: 36px;
      height: 36px;
      border-radius: 8px;
    }
    .stage-head .stage-go {
      min-height: 40px;
    }
    .vi-pop-close {
      width: 40px;
      height: 40px;
      margin: -12px -14px -12px 0;
    }
    button.brand {
      padding: 10px;
      margin: -6px -4px;
    }
    .msg.user.act .change-answer {
      padding: 11px 8px;
      margin: -11px -6px;
    }
    /* the drain stays under the word, inside the bigger target */
    .msg.user.act .change-answer::after {
      left: 8px;
      right: 8px;
      bottom: 10px;
    }
    .note-scratch li.new input {
      padding-block: 11px;
      margin-block: -11px;
    }
    .note-head {
      padding-block: 10px;
      margin-block: -8px;
    }
    .spine-toggle,
    .worked-line {
      padding-block: 11px;
      margin-block: -9px;
    }
    /* marks in a row that are read as a row: they keep their size, and
       reach a finger's width round it */
    .arc-dot {
      position: relative;
    }
    .arc-dot::after,
    .rail-add::after {
      content: '';
      position: absolute;
      inset: -10px;
    }
  }

  /*
   * Gaps: Assumed · Missing · Open, one look for each, the same on every
   * surface (the section head, the stage head, the thread, the composer).
   *
   * - Assumed: the caution colour. A dotted underline with a faint wash
   *   under the words: said without their material.
   * - Missing: a hole, pink and dashed, the shape of the fact it wants.
   * - Open: the accent, outlined: a decision not made yet.
   *
   * Qualifiers on a line ("vs our US engineers") stay plain grey: they are
   * not gaps, and must not look like one.
   */
  & {
    --gap-miss: #e98bb4;
    --gap-miss-soft: rgba(240, 66, 138, 0.12);
  }
  html[data-scene-theme='light'] &,
  html[data-scene-theme='light'][data-scenecast] & {
    --gap-miss: #b02f68;
    --gap-miss-soft: rgba(187, 45, 104, 0.1);
  }
  .gap-dot,
  .gap-kind > i,
  .gap-tag > i {
    display: inline-block;
    flex: none;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--ink3);
  }
  .gap-dot.assumed,
  .gap-kind.assumed > i,
  .gap-tag.assumed > i {
    background: var(--warn);
  }
  .gap-dot.missing,
  .gap-kind.missing > i,
  .gap-tag.missing > i {
    background: var(--gap-miss);
  }
  .gap-dot.open,
  .gap-kind.open > i,
  .gap-tag.open > i {
    background: var(--accent);
  }
  .missing-line > .gap-dot {
    margin-right: 7px;
    vertical-align: 1px;
  }

  /* Assumed, in the words: stronger than the faint dotted line it was,
     still colour and decoration only, so the paragraph keeps its shape */
  .note-text .said-mark {
    text-decoration-thickness: 1.5px;
    text-decoration-color: var(--warn);
    background-image: linear-gradient(
      transparent 62%,
      color-mix(in srgb, var(--warn) 13%, transparent) 62%
    );
  }
  /* Open, in the words: a sentence that says something is not settled */
  .note-text .said-mark.gap-open-mark {
    text-decoration-style: dashed;
    text-decoration-color: color-mix(in srgb, var(--accent) 70%, transparent);
    background-image: linear-gradient(
      transparent 62%,
      color-mix(in srgb, var(--accent) 10%, transparent) 62%
    );
  }
  .note-text .said-mark.gap-open-mark:hover,
  .note-text .said-mark.gap-open-mark.open,
  .note-text .said-mark.gap-open-mark:focus-visible {
    background-color: var(--accent-soft);
    text-decoration-color: var(--accent);
  }
  /* Missing, in the words and in the key points: the words it wants, set
     in the line's own type and a quieter ink, a soft rose underline the
     only colour on it — calm at rest, one piece across a wrap (never a
     dashed box per line fragment, which read as two cheap pills). A tint
     comes only under the hand, and held on the one being asked. Pressed,
     it is asked about (the review starts there). */
  .note-text mark.missing,
  .note-scratch .line-gap {
    display: inline;
    margin: 0;
    padding: 0 0.1em;
    border: 0;
    border-radius: 3px;
    background: transparent;
    color: var(--ink2);
    font: inherit;
    letter-spacing: 0;
    text-transform: none;
    text-decoration: underline;
    text-decoration-thickness: 1.5px;
    text-underline-offset: 0.24em;
    text-decoration-color: color-mix(in srgb, var(--gap-miss) 55%, transparent);
    -webkit-box-decoration-break: clone;
    box-decoration-break: clone;
    cursor: pointer;
    transition:
      background-color var(--m-quick) var(--ease-out),
      color var(--m-quick) var(--ease-out),
      text-decoration-color var(--m-quick) var(--ease-out);
  }
  /* before every section is written a gap is not something to press: the
     words on its line, edited like the rest of it */
  .slots:not(.written) .note-scratch .line-gap {
    cursor: inherit;
  }
  .slots:not(.written) .note-scratch .line-gap:hover {
    background: transparent;
    color: var(--ink2);
    text-decoration-color: color-mix(in srgb, var(--gap-miss) 55%, transparent);
  }
  .note-text mark.missing:hover,
  .note-scratch .line-gap:hover {
    background: color-mix(in srgb, var(--gap-miss) 9%, transparent);
    color: var(--ink);
    text-decoration-color: var(--gap-miss);
  }
  /* the one being asked: the same, held */
  .note-text mark.missing.gap-focus,
  .note-scratch li.gap-focus .line-gap {
    background: color-mix(in srgb, var(--gap-miss) 12%, transparent);
    color: var(--ink);
    text-decoration-color: var(--gap-miss);
    box-shadow: none;
  }
  /* the line's own hover underline would run through the gap's: a line
     that is a gap is pressed to be asked about, not to edit its words */
  .note-scratch li .line-text.editable:has(.line-gap):hover {
    text-decoration: none;
  }

  /* a question about a gap: its section, and in a review where it is with
     the ways on, over the question in the chat (calm gaps, decision 6) */
  .gap-step {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    font-size: 12px;
    color: var(--ink3);
  }
  .gap-step .gap-where {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .gap-step .gap-at {
    font-variant-numeric: tabular-nums;
  }
  .gap-step .gap-ways {
    display: inline-flex;
    gap: 12px;
    flex: none;
  }
  .gap-step .gap-ways button {
    border: 0;
    background: none;
    padding: 0;
    font: inherit;
    color: var(--ink2);
    cursor: pointer;
  }
  .gap-step .gap-ways button:hover {
    color: var(--ink);
  }

  /* ---- Calm gaps: two looks, and only in the words ---------------------
     A missing fact is a blank in its sentence; a sentence to check
     (assumed, or saying something is open) has one soft underline in the
     ink. No amber, no accent, no dashes: what kind it is the question
     says. On key points a gap reads "To add: …", grey. */
  .note-text mark.missing,
  .note-text mark.missing:hover {
    color: var(--ink3);
    background: none;
    text-decoration: none;
    border-bottom: 1.5px solid color-mix(in srgb, var(--gap-miss) 55%, transparent);
    padding: 0 2px;
    border-radius: 0;
  }
  .note-text mark.missing:hover {
    color: var(--ink2);
    border-bottom-color: var(--gap-miss);
  }
  .note-text mark.missing.gap-focus {
    color: var(--ink);
    background: color-mix(in srgb, var(--gap-miss) 12%, transparent);
    border-bottom-color: var(--gap-miss);
    border-radius: 2px 2px 0 0;
  }
  .note-text .said-mark,
  .note-text .said-mark.gap-open-mark {
    text-decoration: underline solid;
    text-decoration-thickness: 1px;
    text-underline-offset: 4px;
    text-decoration-color: color-mix(in srgb, var(--ink) 35%, transparent);
    background-image: none;
    cursor: pointer;
  }
  .note-text .said-mark:hover,
  .note-text .said-mark.open,
  .note-text .said-mark:focus-visible,
  .note-text .said-mark.gap-open-mark:hover,
  .note-text .said-mark.gap-open-mark.open,
  .note-text .said-mark.gap-open-mark:focus-visible {
    background-color: color-mix(in srgb, var(--ink) 6%, transparent);
    text-decoration-color: var(--ink2);
  }
  .note-text .said-mark.gap-focus {
    background-color: color-mix(in srgb, var(--ink) 8%, transparent);
    text-decoration-color: var(--ink);
  }
  .note-scratch .line-gap,
  .note-scratch .line-gap:hover {
    color: var(--ink3);
    background: none;
    text-decoration: none;
  }
  .note-scratch .line-gap::before {
    content: 'To add: ';
  }
  .note-scratch .line-gap:hover {
    color: var(--ink2);
    text-decoration: underline;
    text-decoration-color: color-mix(in srgb, var(--ink2) 40%, transparent);
    text-underline-offset: 3px;
  }
  .note-scratch li.gap-focus .line-gap {
    color: var(--ink);
  }
  .note-scratch li.unsure .line-text {
    text-decoration: underline solid;
    text-decoration-thickness: 1px;
    text-underline-offset: 4px;
    text-decoration-color: color-mix(in srgb, var(--ink) 35%, transparent);
  }
  /* a blank is pressed to be asked, never typed into: over words that are
     otherwise edited in place, it says so */
  .slots .note-text mark.missing[data-gap],
  .slots .note-scratch li .line-gap[data-gap],
  .slots .note-scratch li.unsure[data-gap] .line-text {
    cursor: pointer;
  }
  .note-scratch li.unsure small {
    color: var(--ink3);
  }
  /* ---- Calm progress: the work in ink, not purple ----------------------
     A section being written was lit in the accent four ways at once — a
     purple sheen sweeping the card, a purple edge and rule, a purple ring
     turning by its title, and purple dots by "2 of 4 written" and
     "Writing card 3…" — read as cheap. The skeleton lines under "Writing
     what you'll say" already say it is being written; the ring turns in
     ink, the card keeps its paper, and the accent is left for what waits
     on the person (a question). */
  .note-work {
    display: none;
  }
  .note[data-state='writing'] .note-glyph {
    stroke: var(--ink2);
    color: var(--ink2);
  }
  .note[data-state='writing'] .note-glyph .ring {
    stroke: color-mix(in srgb, var(--ink) 14%, transparent);
  }
  .note[data-state='writing'] .note-word {
    color: var(--ink2);
  }
  .slot.flow .note[data-state='writing'] {
    --paper-edge: color-mix(in srgb, var(--paper-tone) 12%, transparent);
  }
  .stage-head .count.following i,
  .doing-now > i {
    background: var(--ink3);
  }

  /* Open, after a key point: a chip that starts the review at it */
  .gap-open {
    display: inline-flex;
    align-items: center;
    margin-left: 6px;
    padding: 0 8px;
    border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
    border-radius: 999px;
    background: none;
    color: var(--accent);
    font: inherit;
    font-size: 11.5px;
    line-height: 1.6;
    white-space: nowrap;
    cursor: pointer;
    vertical-align: 1px;
  }
  button.gap-open:hover {
    background: var(--accent-soft);
  }
  .gap-open.quiet {
    cursor: default;
  }
  /* a line hangs its bullet with a negative indent, which a chip would
     inherit and lose its own padding to */
  .note-scratch li .gap-open,
  .note-scratch li .answering-tag {
    margin-left: 8px;
    font-style: normal;
    text-indent: 0;
  }

  /* The section head's counts, one per kind: real buttons, quiet */
  .note-kinds {
    display: flex;
    flex-wrap: wrap;
    gap: 2px;
    margin: 2px 0 0 21px;
  }
  .gap-kind {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 2px 7px;
    border: 0;
    border-radius: 999px;
    background: none;
    color: var(--ink2);
    font: inherit;
    font-size: 12px;
    line-height: 18px;
    cursor: pointer;
  }
  .gap-kind:hover {
    background: var(--fill-06);
    color: var(--ink);
  }
  /* What the card is doing, in the same badge: the question it is asking
     ("Missing · in chat", the kind's dot), or what happened to it
     ("Changed", "Update waiting"). Said, not pressed. */
  span.gap-kind {
    cursor: default;
  }
  span.gap-kind:hover {
    background: none;
  }
  span.gap-kind:not(.asking):hover {
    color: var(--ink2);
  }
  .gap-kind.asking {
    color: var(--ink);
    box-shadow: inset 0 0 0 1px var(--rule);
  }
  .gap-kind.asking:not(.missing):not(.open):not(.assumed) > i {
    background: var(--accent);
  }
  .gap-kind.state.changed > i {
    background: var(--accent);
  }

  /* The stage head's tally, and the way into the review */
  .gap-tally {
    display: inline-flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 2px;
    padding: 2px;
    border: 1px solid var(--rule);
    border-radius: 999px;
  }
  .gap-tally .review-start {
    margin-left: 4px;
  }
  .review-start {
    padding: 3px 10px;
    border: 1px solid var(--rule-strong);
    border-radius: 999px;
    background: none;
    color: var(--ink);
    font: inherit;
    font-size: 12px;
    line-height: 18px;
    cursor: pointer;
  }
  /* One way in at a time: beside the conversation, while the outline's
     head carries the tally and its own Review, the bar's copy steps aside.
     Over the slides (no tally) and on a phone (one half at a time) the
     bar's is the way in. */
  @media (min-width: 768px) {
    .b:has(.gap-tally) .actions .review-start {
      display: none;
    }
  }
  .review-start:hover:not(:disabled) {
    background: var(--fill-06);
  }
  .review-start:disabled {
    opacity: 0.5;
    cursor: default;
  }

  /* The pass under way, where the count was: where it is, Skip, Stop */
  .gap-tally.reviewing .review-at {
    padding: 0 8px 0 10px;
    color: var(--ink2);
    font-size: 12px;
    line-height: 22px;
    font-variant-numeric: tabular-nums;
  }
  .gap-tally .review-step {
    padding: 2px 10px;
    border: 0;
    border-radius: 999px;
    background: none;
    color: var(--ink);
    font: inherit;
    font-size: 12px;
    line-height: 18px;
    cursor: pointer;
  }
  .gap-tally .review-step:hover {
    background: var(--fill-06);
  }
  /* one line on a phone: "1 of 3 · Skip · Stop", and the deck button
     without its time, so neither wraps or runs off the edge */
  .gap-tally.reviewing {
    flex-wrap: nowrap;
    white-space: nowrap;
  }
  @media (max-width: 600px) {
    .gap-tally.reviewing .review-at .wide,
    .stage-go .time,
    .stage-go .open .wide {
      display: none;
    }
    /* the counts only point; on a phone the head keeps the one way in */
    .gap-tally {
      flex-wrap: nowrap;
      white-space: nowrap;
    }
    .gap-tally:has(.review-start) .gap-kind {
      display: none;
    }
    .gap-tally .review-start {
      margin-left: 0;
    }
  }

  /* The rail: the pass's progress, said once, at the top of the stage */
  .review-rail {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 12px;
    margin: 10px 24px 0;
    padding: 7px 12px;
    border: 1px solid var(--rule);
    border-radius: 12px;
    background: var(--panel);
    font-size: 12.5px;
    color: var(--ink2);
    flex: none;
  }
  .review-rail strong {
    color: var(--ink);
    font-weight: 500;
  }
  .review-rail .sp {
    flex: 1;
  }
  .review-rail button {
    padding: 2px 4px;
    border: 0;
    background: none;
    color: var(--ink2);
    font: inherit;
    text-decoration: underline;
    text-decoration-color: transparent;
    text-underline-offset: 3px;
    cursor: pointer;
  }
  .review-rail button:hover {
    color: var(--ink);
    text-decoration-color: currentColor;
  }

  /* The line being answered: a static tint, and the read-only tag that
     points to the chat. The other cards step back, lightly. */
  .gap-focus {
    border-radius: 5px;
    background-color: var(--accent-soft);
  }
  .note-text .said-mark.gap-focus {
    background-color: var(--accent-soft);
  }
  /* Calm: the line being asked is lit in its own words, quietly — never a
     purple row behind the whole key point (seen on dev-v4 on "To add: what
     the app does"), and a sentence to check keeps the ink tint its two
     looks give it. */
  .note-scratch li.gap-focus {
    background-color: transparent;
  }
  .note-scratch li.gap-focus .line-gap {
    background: color-mix(in srgb, var(--gap-miss) 10%, transparent);
    border-radius: 2px;
  }
  .note-text .said-mark.gap-focus {
    background-color: color-mix(in srgb, var(--ink) 8%, transparent);
  }
  .answering-tag {
    display: inline-flex;
    align-items: center;
    margin-left: 6px;
    padding: 0 8px;
    border: 1px solid var(--rule);
    border-radius: 999px;
    color: var(--ink3);
    font-family: var(--body);
    font-size: 11.5px;
    font-style: normal;
    line-height: 1.6;
    white-space: nowrap;
    user-select: none;
    vertical-align: 1px;
  }
  .answering-tag .narrow {
    display: none;
  }
  /* under the paragraph: what is said about one of its sentences, on a
     line of its own so the prose above keeps its shape */
  .prose-tail {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 8px;
  }
  .prose-tail .answering-tag {
    margin-left: 0;
  }
  .note-scratch li.ask {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px;
    padding: 2px 4px;
  }
  /* on the sheet, not the slot: the slot's own fade-in holds its opacity
     (`both`), and would hold it over this */
  .slot.gap-dim > .note {
    opacity: 0.6;
    transition: opacity var(--m-base) var(--ease-out);
  }
  @media (max-width: 767px) {
    .slot.gap-dim > .note {
      opacity: 1;
    }
    .answering-tag .wide {
      display: none;
    }
    .answering-tag .narrow {
      display: inline;
    }
    .review-rail {
      margin: 8px 16px 0;
    }
  }

  /* After a Keep: said where the sentence was, with the way back */
  /* a line's own way off the card: an × at its end, seen on the way past
     or with the keyboard on it, faintly always under a finger */
  .note-scratch li .line-remove {
    display: inline-grid;
    place-items: center;
    width: 22px;
    height: 22px;
    margin: -3px 0 -3px 4px;
    padding: 0;
    border: 0;
    border-radius: 6px;
    background: none;
    color: var(--ink3);
    vertical-align: middle;
    cursor: pointer;
    opacity: 0;
    transition: opacity var(--m-quick) var(--ease-out),
      background var(--m-quick) var(--ease-out);
  }
  .note-scratch li:hover .line-remove,
  .note-scratch li:focus-within .line-remove,
  .note-scratch li .line-remove:focus-visible {
    opacity: 1;
  }
  .note-scratch li .line-remove:hover {
    background: var(--fill-06);
    color: var(--ink);
  }
  .note-scratch li .line-remove svg {
    width: 12px;
    height: 12px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.5;
    stroke-linecap: round;
  }
  /* Touch has no hover: the × shows on the line being written on (the one
     tapped), not on every line at once (calm gaps, decision 11). A 32px
     target where it shows. */
  @media (hover: none) {
    .note-scratch li .line-remove {
      opacity: 0;
      visibility: hidden;
      width: 32px;
      height: 32px;
      margin: -8px -6px -8px 0;
      /* it goes a moment after the line lets go of the focus: a tap on it
         ends the line's writing first (Safari gives a button no focus),
         and it has to be there still for the tap to land */
      transition:
        opacity var(--m-quick) var(--ease-out) 0.35s,
        visibility 0s linear 0.35s;
    }
    .note-scratch li:focus-within .line-remove {
      opacity: 0.7;
      visibility: visible;
      transition-delay: 0s;
    }
  }
  /* the line just taken off, where it was, with the way back */
  .note-scratch li.line-gone {
    list-style: none;
    font-size: 12.5px;
    color: var(--ink3);
  }
  .note-scratch li.line-gone button {
    padding: 0;
    border: 0;
    background: none;
    font: inherit;
    color: var(--ink);
    text-decoration: underline;
    text-underline-offset: 3px;
    cursor: pointer;
  }
  .kept-note {
    margin-left: 6px;
    font-family: var(--body);
    font-size: 12px;
    color: var(--ink3);
    white-space: nowrap;
    user-select: none;
  }
  .kept-note button {
    padding: 0;
    border: 0;
    background: none;
    color: var(--ink);
    font: inherit;
    text-decoration: underline;
    text-underline-offset: 3px;
    cursor: pointer;
  }

  /* The note on a marked sentence: its kind, why, and each way with what
     it does said under it */
  .mark-kind {
    display: flex;
    align-items: center;
    gap: 6px;
    margin: 0 0 3px;
    font-size: 11.5px;
    color: var(--ink2);
  }
  .mark-acts {
    flex-direction: column;
    gap: 6px;
  }
  .mark-act {
    display: flex;
    align-items: baseline;
    gap: 8px;
  }
  .mark-act small {
    font-size: 11.5px;
    color: var(--ink3);
  }

  /* A question of the review pass in the thread and the composer: its
     kind and place, then the question */
  .gap-tag {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 1px 8px;
    border: 1px solid var(--rule);
    border-radius: 999px;
    background: none;
    color: var(--ink2);
    font: inherit;
    font-size: 11.5px;
    line-height: 18px;
  }
  button.gap-tag {
    cursor: pointer;
    margin-bottom: 6px;
  }
  button.gap-tag:hover {
    color: var(--ink);
    border-color: var(--rule-strong);
  }
  .composer .gap-head {
    display: grid;
    justify-items: start;
    gap: 6px;
    margin-bottom: 8px;
  }
  .composer .gap-head .step-q {
    margin: 0;
    font-size: 13.5px;
    font-weight: 500;
    color: var(--ink);
  }
  .composer .gap-head .gap-why {
    margin: 0;
    font-size: 12.5px;
    color: var(--ink3);
  }
  .composer .ask.decide .chips {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
  }
  .composer .ask.decide .chips button.quiet {
    background: none;
    border-color: var(--rule);
  }

  /* every new control: focus seen from the keyboard */
  .gap-kind:focus-visible,
  .gap-open:focus-visible,
  .gap-tag:focus-visible,
  .review-start:focus-visible,
  .review-rail button:focus-visible,
  .kept-note button:focus-visible,
  .mark-act button:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
  }
  @media (prefers-reduced-motion: reduce) {
    .slot.gap-dim > .note {
      transition: none;
    }
  }
}
/* a step of a set of questions, and the sheet they sit on, coming in */
/* a slide row arriving (`.slot-slide.arrive`). Kept out here with the
   others: nested in the scoped block it builds in dev, but the production
   minifier rejects an @keyframes inside a style rule. */
@keyframes stage-arrive {
  from {
    opacity: 0;
    transform: translateY(8px);
  }
}
@keyframes qstep-in {
  from {
    opacity: 0;
    transform: translateY(3px);
  }
}
@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}
/* the arc on the glyph of a card being worked on */
@keyframes glyph-turn {
  to {
    transform: rotate(360deg);
  }
}
@keyframes fade-in-line {
  from {
    opacity: 0;
  }
}
@keyframes share-pulse {
  50% {
    opacity: 0.35;
  }
}
@keyframes ease-in-line {
  from {
    opacity: 0;
    transform: translateY(2px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
@keyframes pulse {
  50% {
    opacity: 0.3;
  }
}
@keyframes up {
  from {
    opacity: 0;
    transform: translateY(6px);
  }
}
@keyframes sh {
  from {
    background-position: 200% 0;
  }
  to {
    background-position: -200% 0;
  }
}
@keyframes fade-out {
  to {
    opacity: 0;
  }
}
@keyframes breathe {
  0%,
  100% {
    opacity: 0.55;
  }
  50% {
    opacity: 1;
  }
}
@keyframes swipe {
  from {
    transform: translateX(-100%);
  }
  to {
    transform: translateX(100%);
  }
}
@keyframes sweep {
  from {
    transform: translateX(-100%);
  }
  to {
    transform: translateX(100%);
  }
}
@property --sweep {
  syntax: '<angle>';
  inherits: false;
  initial-value: 0deg;
}
@keyframes box-sweep {
  0% {
    --sweep: 0deg;
    opacity: 0;
  }
  15% {
    opacity: 1;
  }
  85% {
    opacity: 1;
  }
  100% {
    --sweep: 360deg;
    opacity: 0;
  }
}
@keyframes mic-pulse {
  0%,
  100% {
    transform: scale(1);
    opacity: 1;
  }
  50% {
    transform: scale(0.6);
    opacity: 0.5;
  }
}
@keyframes vi-pop-in {
  from {
    opacity: 0;
    transform: translateY(-4px) scale(0.98);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
@keyframes rise-in {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
/* the held answer's Change draining over the hold (`.change-answer`) */
@keyframes change-drain {
  from {
    transform: scaleX(1);
  }
  to {
    transform: scaleX(0);
  }
}
@keyframes sheet-up {
  from {
    transform: translateY(24px);
    opacity: 0;
  }
}
@keyframes fade-in {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}
/* a line landing inside a box that clips: no rise, or it lands cut off */
@keyframes line-in {
  from {
    opacity: 0;
  }
}
@keyframes fresh-in {
  from {
    opacity: 0;
    transform: translateY(2px);
  }
}
@keyframes blank-in {
  from {
    background-size: 0% 1px;
  }
  to {
    background-size: 100% 1px;
  }
}
@keyframes wanted-ring {
  0% {
    opacity: 0;
    transform: scale(0.99);
  }
  12%,
  62% {
    opacity: 1;
    transform: none;
  }
  100% {
    opacity: 0;
    transform: none;
  }
}
@keyframes sheen {
  from {
    background-position: 200% 0;
  }
  to {
    background-position: -200% 0;
  }
}
@keyframes note-in {
  from {
    opacity: 0;
    transform: translateY(4px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
/*
 * The first send (see leavingStart): the composer is the one thing that
 * travels; everything else cross-fades behind it. Named only on the box,
 * which is on the page once, and outside the creator's scope because the
 * transition's pseudo-elements belong to the document.
 */
[data-scenecast-creator] .composer .box {
  view-transition-name: sc-composer;
}
::view-transition-group(sc-composer) {
  animation-duration: 0.42s;
  animation-timing-function: cubic-bezier(0.2, 0.7, 0.2, 1);
}
::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 0.26s;
}
/* a slide seen larger, carried between its sizes (see toggleExpand) */
::view-transition-group(sc-slide-grow) {
  animation-duration: 0.36s;
  animation-timing-function: cubic-bezier(0.2, 0.7, 0.2, 1);
}
/* the outline and the slides, faded one into the other (see StagePanel
   `switchTo`): quick, since it is a switch the person pressed */
::view-transition-group(sc-stage-view),
::view-transition-old(sc-stage-view),
::view-transition-new(sc-stage-view) {
  animation-duration: 0.16s;
  animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}
/* the story's own pictures, offered first when a slide asks for a photo */
.story-pics-scrim {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: grid;
  place-items: center;
  padding: 24px;
  background: rgb(0 0 0 / 0.55);
}
.story-pics {
  width: min(880px, 100%);
  max-height: min(720px, calc(100vh - 48px));
  max-height: min(720px, calc(100dvh - 48px));
  display: flex;
  flex-direction: column;
  border-radius: 16px;
  background: var(--panel);
  box-shadow:
    inset 0 0 0 1px var(--rule),
    0 24px 60px -20px rgb(0 0 0 / 0.7);
  overflow: hidden;
}
.story-pics-bar {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  padding: 18px 20px 14px;
  border-bottom: 1px solid var(--rule);
}
.story-pics-bar h2 {
  margin: 0;
  font: 600 16px/1.3 var(--display);
  color: var(--ink);
}
.story-pics-needs {
  margin: 4px 0 0;
  font-size: 13px;
  color: var(--ink2);
}
.story-pics-close {
  border: 0;
  background: none;
  color: var(--ink2);
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
}
.story-pics-grid {
  list-style: none;
  margin: 0;
  padding: 16px 20px;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 14px;
  overflow: auto;
}
.story-pic {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 0 0 10px;
  border: 0;
  border-radius: 10px;
  background: var(--fill-04);
  color: var(--ink);
  text-align: left;
  font: inherit;
  cursor: pointer;
  overflow: hidden;
}
.story-pic:hover,
.story-pic:focus-visible {
  outline: var(--ring-w) solid var(--ring);
}
.story-pic img {
  width: 100%;
  aspect-ratio: 16 / 10;
  object-fit: cover;
  background: var(--fill-06);
}
.story-pic-says {
  padding: 0 10px;
  font-size: 13px;
  line-height: 1.35;
}
.story-pic-where {
  padding: 0 10px;
  font-size: 12px;
  color: var(--ink3);
}
.story-pics-foot {
  padding: 12px 20px 16px;
  border-top: 1px solid var(--rule);
}
.story-pics-files {
  border: 1px solid var(--rule);
  border-radius: 999px;
  padding: 7px 14px;
  background: var(--fill-04);
  color: var(--ink);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
}
@keyframes upright-turn {
  0%,
  12% {
    transform: rotate(-90deg);
  }
  46%,
  88% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(-90deg);
  }
}
