/* ═══════════════════════════════════════════════════════════════════════════
 * Voice Infra console — the MAGI design language, cut for a white canvas.
 *
 * MAGI (magi/web/src/index.css) is an operator instrument. This is a
 * customer-facing sandbox. Same company, same vocabulary, different room:
 * the type scale, the single hairline weight, the 2–4px radii, the two
 * semantic colours, the ease curve and the press feedback are all MAGI's.
 * What is NOT ported is MAGI's dark mode — this surface stays paper-white on
 * purpose, so the light values are chosen against paper rather than inverted
 * out of the dark ones.
 *
 * THREE RULES CARRIED OVER VERBATIM, because they are what makes MAGI legible:
 *
 *   1. ONE HAIRLINE. Every edge in the UI is 1px of --c-line (or --c-line-strong
 *      when it is a thing you can press). There are no shadows outside the two
 *      modals. Depth is drawn with a line and a fill, never with a blur.
 *
 *   2. MEANING IS NEVER IN HUE ALONE. The two semantic colours — alarm (red,
 *      "this is broken") and open (amber, "unresolved / not settled") — are
 *      indistinguishable to a deuteranope at these lightnesses. So every state
 *      also carries a SHAPE: ○ hollow ring = fine, ▨ amber hatch = borderline,
 *      ■ solid = bad. Learned once in the latency table, reused everywhere.
 *
 *   3. NO AMBIENT MOTION. Nothing loops, nothing pulses, nothing performs the
 *      page on arrival. Motion is reserved for a thing that actually just
 *      happened: a transcript line landing, a modal opening, a button being
 *      pressed. This is a screen people leave open while they test.
 * ═══════════════════════════════════════════════════════════════════════════ */

:root {
  /* ── MAGI tokens · the source of truth ─────────────────────────────────
   * Lifted from magi/web/src/index.css. Anything below this block that needs
   * a colour references one of these; a hex code appearing twice in this file
   * is a bug. */

  --c-paper: #fbfaf8;          /* the canvas — warm, not blue-white */
  --c-surface: #ffffff;        /* anything that sits ON the canvas */
  --c-sunk: #f4f2ee;           /* row hover, bot bubbles, inert fills */
  --c-line: #e7e4dd;           /* the hairline */
  --c-line-strong: #d3cec4;    /* the hairline on something pressable */

  --c-ink: #1b1a18;            /* 15.4:1 on paper */
  --c-ink-2: #45433d;          /*  9.5:1 — body copy */
  /* MAGI's ink-3 is #7a7770, which is 4.28:1 on paper — under AA. It is fine
   * there because MAGI uses it for decoration; here it carries real text
   * (labels, help copy, timestamps), so it is darkened to 5.0:1. */
  --c-ink-3: #6f6c65;
  --c-ink-4: #a8a49c;          /* 2.4:1 — DECORATION ONLY. Never text. */

  --c-alarm: #ab2317;
  --c-alarm-ink: #8d1d13;      /* 8.7:1 */
  --c-alarm-bg: #fdf2f0;
  --c-alarm-bg-hover: #fbe8e5;
  --c-alarm-line: rgba(171, 35, 23, 0.4);

  --c-open: #8a5900;
  --c-open-ink: #7a4f00;       /* 6.8:1 */
  --c-open-bg: #fdf7ea;
  --c-open-line: rgba(138, 89, 0, 0.45);
  --c-open-hatch: rgba(138, 89, 0, 0.2);

  /* The dark rail and the founder's note are MAGI's DARK-mode surface values,
   * not an invented dark green. Two halves of one palette on one page. */
  --c-dark: #151513;
  --c-dark-sunk: #1f1e1b;
  --c-dark-line: #2b2a24;
  --c-on-dark: #f1efe9;
  --c-on-dark-2: #c4c0b6;
  --c-on-dark-3: #8b877d;
  --c-alarm-on-dark: #ff8073;
  /* The amber sibling of the line above. `open`/unresolved has had an on-dark
   * value on the light surfaces since the sleeping cat; the dark column only
   * ever needed the alarm one until the support cat had to say "a call is
   * running and costing money" from inside the nav. 9.4:1 on --c-dark. */
  --c-open-on-dark: #e8b866;

  /* The one brand colour that survives the port, and it survives in exactly
   * two places: the ✳ mark and the brand pill. MAGI has no accent at all;
   * green here says "mirai minds", never "good", never "primary". */
  --accent: #0f6a44;           /* 6.1:1 on paper */
  --accent-on-dark: #4fc08a;
  --accent-bg: #edf5f0;
  --accent-line: #c6ded1;

  /* The sheet scrim. Same channels as --c-ink at a third opacity — the only
   * dimming surface in the build, and it exists so a bottom sheet reads as a
   * layer over the app rather than a second page. Declared here for the same
   * reason --c-alarm-line is: an rgba() of a token is still the token. */
  --c-scrim: rgba(27, 26, 22, 0.34);

  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
  --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);

  /* ── phone shell metrics ────────────────────────────────────────────────
   * The phone layout is three fixed edges (title bar, call dock, tab bar) and
   * one scrolling middle. The call pane is absolutely positioned between them,
   * so its insets have to be stated somewhere both it and the chrome can read.
   * --dock-h is a fallback: core.js measures the real dock with a
   * ResizeObserver, because it grows when the dial group is opened, when the
   * queue position wraps and when the download link appears. */
  --mtop-h: 46px;
  --tabbar-h: calc(54px + env(safe-area-inset-bottom));
  --dock-h: 152px;
  /* The height of the VISIBLE part of the screen. Starts as dvh and is
   * overwritten by core.js from visualViewport, which is the only number that
   * shrinks when the iOS keyboard comes up — dvh does not. */
  --vvh: 100dvh;

  /* Radii. MAGI's scale was 2–4px. 2026-09-23 the console moved to the
   * softer, concentric geometry of a Mac app: a control inside a card inside
   * a panel steps 6 → 10 → 16 so the corners nest instead of fighting. Bars
   * and meters stay square-ish — they are marks, not objects. */
  --r-xs: 2px;                 /* bars, meters, swatches */
  --r-sm: 6px;                 /* buttons, inputs, pills */
  --r-md: 10px;                /* cards, panes, modals */

  /* ── the Apple-grade layer ─────────────────────────────────────────────
   * Materials and depth for the sidebar, Agent Studio and the agent list.
   * Depth is still mostly a line and a fill; shadows exist only to lift the
   * two things that genuinely float — panels, and the ONE selection idiom
   * (a raised white pill on a tinted track: sidebar item, segmented control,
   * tool list). Fills are ink at low alpha so they tint whatever they sit on
   * rather than painting a second grey. */
  --ui-r-control: 8px;         /* buttons, fields, rows */
  --ui-r-card: 12px;           /* grouped cards */
  --ui-r-panel: 16px;          /* sidebar, test panel, sheets */
  --ui-fill: rgba(118, 110, 96, 0.12);     /* segmented track, search field */
  --ui-fill-2: rgba(118, 110, 96, 0.07);   /* hover wash */
  --ui-fill-3: rgba(118, 110, 96, 0.17);   /* pressed, scope-bar selection */
  --ui-sep: rgba(27, 26, 22, 0.09);        /* separator on any surface */
  --ui-edge: rgba(27, 26, 22, 0.16);       /* the edge of a field */
  --ui-glass: rgba(251, 250, 247, 0.82);   /* sidebar / panel material */
  --ui-raise: 0 1px 2px rgba(27, 26, 22, 0.10), 0 0 0 0.5px rgba(27, 26, 22, 0.08);
  --ui-card: 0 0 0 1px rgba(27, 26, 22, 0.06), 0 1px 2px rgba(27, 26, 22, 0.04),
             0 4px 14px rgba(27, 26, 22, 0.035);
  --ui-float: 0 0 0 0.5px rgba(27, 26, 22, 0.12), 0 4px 12px rgba(27, 26, 22, 0.08),
              0 18px 44px rgba(27, 26, 22, 0.14);
  --ui-ring: 0 0 0 3.5px rgba(27, 26, 22, 0.09);  /* a field being typed in */
  /* The same idiom on the dark sidebar: a tinted track, and a pill one step
   * lighter than the column that lifts off it. */
  --ui-dark-fill: rgba(241, 239, 233, 0.07);
  --ui-dark-raise: #2a2925;
  --ui-dark-lift: 0 1px 2px rgba(0, 0, 0, 0.35), inset 0 0 0 0.5px rgba(241, 239, 233, 0.07);
  /* iOS's sheet curve: quick to leave, long soft settle. */
  --ui-spring: cubic-bezier(0.32, 0.72, 0, 1);

  --sans: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto,
          'Helvetica Neue', Arial, sans-serif;
  --mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas,
          'Liberation Mono', monospace;

  /* ── aliases ───────────────────────────────────────────────────────────
   * The rules below were written against these names. They now resolve to
   * MAGI tokens, so the palette has exactly one definition site. */
  --canvas: var(--c-paper);
  --surface: var(--c-surface);
  --surface-soft: var(--c-sunk);
  --surface-strong: var(--c-line);
  --hairline: var(--c-line);
  --hairline-soft: var(--c-line);   /* ONE hairline weight — see rule 1 */
  --ink: var(--c-ink);
  --body: var(--c-ink-2);
  --muted: var(--c-ink-3);
  --dark: var(--c-dark);
  --on-dark: var(--c-on-dark);
  --on-dark-muted: var(--c-on-dark-3);
  --warn: var(--c-open-ink);
  --bad: var(--c-alarm-ink);
  --ease: var(--ease-out);
  --ease-hover: var(--ease-out);
  --serif: var(--sans);        /* no webfont: see the type note below */
}

/* TYPE. MAGI runs on the system sans and nothing else, so this console does
 * too — which also removes the two Google Fonts requests that used to sit in
 * the <head>. Newsreader and Space Grotesk are gone; the display treatment
 * they were carrying (large, tight, tabular) is now done with tracking and
 * weight on the system face, which is exactly how MAGI does it. */

* { box-sizing: border-box; }

html { background: var(--canvas); }

body {
  font-family: var(--sans);
  background: var(--canvas); color: var(--c-ink);
  margin: 0; min-height: 100vh; font-size: 13px; line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* Every figure that sits in a column has to line up. Proportional digits make
 * a latency table unreadable. */
.tnum, .hist-stat strong, .hist-when, .hist-to, #cap, #phone {
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
}

/* Tracking is size-specific — large figures read loose at default spacing,
 * small uppercase labels read tight. Never one value for all. */
.display { letter-spacing: -0.025em; line-height: 0.95; }

/* A label that exists for the screen reader and the accessibility tree only.
 * Used on the icon-only phone buttons; clip-path rather than display:none so
 * the name is actually announced. */
.vh { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
      overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }

/* ── the state marks ───────────────────────────────────────────────────────
 * RULE 2 LIVES HERE. alarm and open are near-identical to a deuteranope, so
 * each is pinned to a shape as well as a colour, and the calm case gets its
 * own shape too rather than being "the one with no mark":
 *
 *   ○  hollow ink-4 ring   — fine. A real answer, quietly stated.
 *   ▨  amber diagonal hatch — borderline / unresolved. Never a calm grey.
 *   ■  solid alarm square   — bad. The darkest, loudest mark available.
 *
 * The hatch is MAGI's, gradient for gradient, so somebody who has seen the
 * fleet board already knows what it means here. It never animates. */
.mark-ok::before,
.mark-open::before,
.mark-bad::before {
  content: ''; display: inline-block; width: 8px; height: 8px;
  margin-right: 6px; vertical-align: 0;
  border: 1px solid; flex-shrink: 0;
}
.mark-ok::before {
  border-color: var(--c-ink-4); border-radius: 50%; background: transparent;
}
.mark-open::before {
  border-color: var(--c-open-line); border-radius: 1px;
  background-color: var(--c-open-bg);
  background-image: repeating-linear-gradient(45deg,
    var(--c-open-hatch) 0 3px, transparent 3px 7px);
}
.mark-bad::before {
  border-color: var(--c-alarm); border-radius: 1px;
  background: var(--c-alarm);
}

/* ── app shell: nav · content · test rail ─────────────── */
/* EXPLICIT PLACEMENT, not auto-flow. Column 3 holds two things now — the test
 * rail and the call inspector, one painted over the other — and with automatic
 * placement the second of them takes the next free cell, which is row 2, and
 * the rail drops under the page. Naming all three columns and pinning them to
 * row 1 is what keeps the inspector ON the rail rather than beneath it. */
.app { --insp-w: min(520px, 44vw);
       display: grid; grid-template-columns: 216px minmax(0, 1fr) 388px;
       gap: 16px; padding: 16px clamp(12px, 2vw, 24px);
       max-width: 1760px; margin: 0 auto; align-items: start;
       /* The content column NARROWS for the inspector rather than being
        * covered by it — a panel that hides what you were reading is a modal
        * wearing a sidebar's clothes. */
       transition: grid-template-columns 220ms var(--ease-out); }
.app > .nav { grid-column: 1; grid-row: 1; }
.app > #content { grid-column: 2; grid-row: 1; }
.app > .rail, .app > .insp { grid-column: 3; grid-row: 1; }
.app.inspecting { grid-template-columns: 216px minmax(0, 1fr) var(--insp-w); }

/* ── left nav · the sidebar ────────────────────────────────────────────────
 * MAGI's dark surface, drawn as a Mac sidebar (2026-09-23): a panel floating
 * inset from the window edge, sentence-case group headers, and a selection
 * that is a raised pill a step lighter than the column rather than a painted
 * bar — the same idiom as every segmented control on the paper side.
 *
 * No entrance animation. Opening a console should show you its state
 * instantly, not perform it. dvh, not vh: on a phone-sized window vh is
 * measured against the URL bar being collapsed. */
.nav { position: sticky; top: 16px; height: calc(100vh - 32px);
       height: calc(100dvh - 32px);
       display: flex; flex-direction: column; gap: 1px;
       background: var(--c-dark); border: 1px solid var(--c-dark-line);
       border-radius: var(--ui-r-panel); padding: 14px 10px 10px; }
.logo { display: flex; align-items: center; gap: 10px; padding: 2px 8px 16px; }
/* The product's mark, at a fixed HEIGHT with auto width — it is 174×112, and
 * constraining the other axis is how a logo ends up squashed on one screen and
 * not another. `flex: none` because a flex item with an intrinsic size will
 * otherwise give it up to the wordmark on a narrow rail. */
.logo .mark { height: 20px; width: auto; flex: none; display: block; }
.logo-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.logo-text b { font-size: 14px; font-weight: 650; letter-spacing: -0.016em;
               line-height: 1.2; color: var(--c-on-dark); }
.logo-text span { font-size: 11px; line-height: 1.3; color: var(--c-on-dark-3); }

/* ── the environment switch ────────────────────────────────────────────────
 * A segmented control: a tinted track and one raised thumb that sits under
 * the selected half. The thumb is the track's ::before so it can slide
 * without either label moving; each half keeps a 44px hit area through its
 * own ::after, which is why the row does not clip its overflow.
 *
 * The thumb moves only for the one confirmed activation celebration. */
.envwrap { display: flex; flex-direction: column; gap: 6px; padding: 0 2px 16px; }
.envwrap[hidden] { display: none; }
.envrow { display: grid; grid-template-columns: 1fr 1fr; position: relative;
          isolation: isolate; padding: 2px; border-radius: 9px;
          background: var(--ui-dark-fill); }
.envrow::before { content: ''; position: absolute; top: 2px; bottom: 2px; left: 2px;
  width: calc(50% - 2px); background: var(--ui-dark-raise); border-radius: 7px;
  box-shadow: var(--ui-dark-lift); pointer-events: none; z-index: -1; }
.envrow[data-env="live"]::before { transform: translateX(100%); }
.envrow:has(.envtab[hidden])::before { width: calc(100% - 4px); transform: none; }
.envrow.is-activating:not(:has(.envtab[hidden]))::before { animation: env-go-live 520ms var(--ui-spring) both; }
.envrow.is-activating #env-live { animation: env-live-label 280ms var(--ease-out) 180ms both; }
@keyframes env-go-live { from { transform: translateX(0); } to { transform: translateX(100%); } }
@keyframes env-live-label { from { opacity: .5; transform: translateY(3px); } to { opacity: 1; transform: none; } }
.envtab { position: relative; min-height: 28px; padding: 6px 8px;
          display: flex; align-items: center; justify-content: center; gap: 6px;
          background: transparent; border: 0; border-radius: 7px;
          color: var(--c-on-dark-3); font-family: inherit; font-size: 12px;
          font-weight: 500; line-height: 1; letter-spacing: -0.003em;
          white-space: nowrap; cursor: pointer; transition: color 120ms ease; }
/* A finger is wider than the control it is aiming at. This makes the target
 * 44px tall without making the switch 44px tall. */
.envtab::after { content: ''; position: absolute; left: 0; right: 0;
                 top: 50%; height: 44px; transform: translateY(-50%); }
.envtab.on { color: var(--c-on-dark); font-weight: 600; }
/* Live carries a lit dot — the one place the brand green says "on air". */
.envrow[data-env="live"] .envtab.on::before { content: ''; width: 6px; height: 6px;
  border-radius: 50%; background: var(--accent-on-dark); flex: none; }
.envtab:disabled, .envtab[aria-disabled="true"] { cursor: not-allowed; opacity: 0.45; }
.envtab:active { transform: none; }
.envtab[hidden] { display: none; }
.envrow:has(.envtab[hidden]) { grid-template-columns: 1fr; }
@media (hover: hover) and (pointer: fine) {
  .envtab:not(.on):not(:disabled):not([aria-disabled="true"]):hover { color: var(--c-on-dark-2); }
}
/* The reason a half cannot be pressed, where a finger and a screen reader can
 * both find it — never a tooltip. */
.envwhy { margin: 0; padding: 0 4px; font-size: 11px; line-height: 1.45;
          color: var(--c-on-dark-3); }
.envwhy[hidden] { display: none; }

/* Group headers are small, sentence-case and quiet — the Finder's
 * "Favorites", not a shouted eyebrow. The gap sits on the header rather than
 * on the list so a hidden section (Admin, for a customer) takes its space
 * with it. */
.nav-sec { font-size: 11px; font-weight: 600; letter-spacing: 0;
           text-transform: none; color: var(--c-on-dark-3); line-height: 1;
           padding: 0 10px 7px; }
.nav-items + .nav-sec { padding-top: 18px; }
.nav-items { display: flex; flex-direction: column; gap: 1px; }
/* "New", beside the item it applies to. A WORD, not a coloured dot: a dot on
 * a nav item is indistinguishable from an unread badge. */
.nav-new { margin-left: auto; font-size: 10px; letter-spacing: 0.01em;
           text-transform: capitalize; font-weight: 600; line-height: 1;
           padding: 3px 6px; border-radius: 999px;
           color: var(--c-on-dark-2); box-shadow: inset 0 0 0 1px var(--c-dark-line); }
.nav-item.on .nav-new { color: var(--c-on-dark); box-shadow: inset 0 0 0 1px var(--c-on-dark-3); }
.nav-item { position: relative; display: flex; align-items: center; gap: 10px;
            width: 100%; min-height: 32px; padding: 6px 10px; text-align: left;
            background: none; border: 0; border-radius: var(--ui-r-control);
            color: var(--c-on-dark-2); font-family: inherit; font-size: 13px;
            font-weight: 500; line-height: 1.2; letter-spacing: -0.006em;
            cursor: pointer;
            transition: background-color 120ms ease, color 120ms ease; }
.nav-item svg { width: 17px; height: 17px; flex-shrink: 0; color: var(--c-on-dark-3);
                transition: color 120ms ease; }
@media (hover: hover) and (pointer: fine) {
  .nav-item:hover { background: var(--c-dark-sunk); color: var(--c-on-dark); }
}
/* Pressed deepens the wash; it does not squash — a row is not a button. */
.nav-item:active { transform: none; background: var(--c-dark-line); }
.nav-item.on { background: var(--ui-dark-raise); color: var(--c-on-dark); font-weight: 600;
               box-shadow: var(--ui-dark-lift); }
.nav-item.on svg { color: var(--c-on-dark); }

.nav-foot { margin-top: auto; display: flex; flex-direction: column; gap: 8px;
            padding: 12px 2px 0; border-top: 1px solid var(--c-dark-line); }
.nav-cap { display: flex; flex-direction: column; gap: 6px; padding: 0 8px; }
.nav-cap-row { display: flex; justify-content: space-between; align-items: baseline;
               gap: 8px; font-size: 11.5px; color: var(--c-on-dark-3); }
.nav-cap-row b { color: var(--c-on-dark); font-size: 12px; font-weight: 600;
                 font-variant-numeric: tabular-nums; }
/* At capacity the number changes colour AND weight AND gains a solid mark —
 * three axes, per rule 2, because "10/10" is the moment a call will not start. */
.nav-cap-row b.full { color: var(--c-alarm-on-dark); font-weight: 700; }
.nav-cap-row b.full::before {
  content: ''; display: inline-block; width: 7px; height: 7px; border-radius: 1px;
  margin-right: 5px; background: var(--c-alarm-on-dark);
}
.nav-cap-row.sub { font-size: 11px; min-height: 13px; }
.nav-cap-row.sub span:last-child { color: var(--c-on-dark-2); }
/* The plan the minutes are billed at, under the month. A hairline above it so
 * it reads as a fact about the account, not a third figure about the month. */
.nav-cap-row.plan { padding-top: 6px; border-top: 1px solid var(--c-dark-line);
                    color: var(--c-on-dark-2); }
.nav-cap-row.plan[hidden] { display: none; }
.capbar { height: 4px; background: var(--ui-dark-fill);
          border-radius: 999px; overflow: hidden; }
.capbar i { display: block; height: 100%; width: 0%; background: var(--c-on-dark-2);
            border-radius: 999px; transition: width 240ms var(--ui-spring); }
.capbar i.full { background: var(--c-alarm-on-dark); }
.meetbtn { display: flex; align-items: center; justify-content: center; gap: 7px;
           min-height: 30px; background: transparent; color: var(--c-on-dark);
           border: 1px solid var(--c-dark-line);
           font-size: 12.5px; font-weight: 500; line-height: 1; padding: 8px 12px;
           border-radius: var(--ui-r-control); text-decoration: none;
           transition: background-color 120ms ease; }
.meetbtn:hover { background: var(--c-dark-sunk); }
/* An <svg> with no width defaults to 300×150 and detonates the row. */
.meetbtn svg { width: 14px; height: 14px; flex-shrink: 0; color: var(--c-on-dark-3); }
.nav-me { display: flex; align-items: center; justify-content: space-between;
          gap: 8px; padding: 0 6px; }
.nav-me #brandpill { font-size: 11px; font-weight: 600; line-height: 1;
                     color: var(--accent-on-dark); background: transparent;
                     border: 1px solid rgba(79, 192, 138, 0.4); border-radius: 999px;
                     padding: 4px 8px;
                     min-width: 0; overflow: hidden; text-overflow: ellipsis;
                     white-space: nowrap; }
.nav-me .signout { color: var(--c-on-dark-3); }
.nav-me .signout:hover { color: var(--c-on-dark); }

/* ── pills ────────────────────────────────────────────── */
/* MAGI's pill: a 3px rectangle, 11px medium, hairline border. Not a capsule —
 * capsules read as chips in a consumer app, rectangles read as an instrument. */
.pill { font-size: 11px; font-weight: 500; line-height: 1; color: var(--c-ink-2);
        background: var(--c-surface); border: 1px solid var(--c-line-strong);
        border-radius: var(--r-sm); padding: 4px 7px;
        font-variant-numeric: tabular-nums; }
.pill.cap::before { content: ''; display: inline-block; width: 7px; height: 7px;
                    border-radius: 50%; background: var(--c-ink-2);
                    margin-right: 6px; }
.pill.cap.full { color: var(--c-alarm-ink); border-color: var(--c-alarm-line);
                 background: var(--c-alarm-bg); font-weight: 600; }
.pill.cap.full::before { background: var(--c-alarm); border-radius: 1px; }
.pill.brand, .pill.ctx { color: var(--c-ink-2); background: var(--c-surface);
                         border-color: var(--c-line-strong); }

.signout { font-size: 11px; color: var(--c-ink-3); cursor: pointer;
           background: none; border: none; padding: 4px; font-family: inherit; }
.signout:hover { color: var(--c-ink); }

/* ── main content ─────────────────────────────────────── */
#content { min-width: 0; padding-bottom: 48px; }
.view-head { margin: 2px 0 18px; }
.view-head h1 { font-weight: 600; font-size: 22px; letter-spacing: -0.025em;
                margin: 0 0 5px; line-height: 1.12; color: var(--c-ink); }
.view-head p { margin: 0; color: var(--c-ink-2); font-size: 12.5px;
               max-width: 60ch; line-height: 1.6; }
.block, .card { background: var(--c-surface); border: 1px solid var(--c-line);
                border-radius: var(--r-md); padding: 16px 18px; }
.block { margin-bottom: 14px; }
/* MAGI's `.eyebrow`: 10px, 600, 0.08em, uppercase, ink-3. One definition,
 * reused for every label on the page. */
.block-title, label, th, #chatpane h2, .latwrap summary,
.hist-stat span, .founder-kicker, .rail-fact-k {
  font-size: 10px; letter-spacing: 0.08em; text-transform: uppercase;
  font-weight: 600; line-height: 1; color: var(--c-ink-3);
}
.block-title { margin-bottom: 12px; }
.block-sub { margin: -4px 0 12px; font-size: 12px; color: var(--c-ink-3);
             line-height: 1.55; }
.card h3 { margin: 0 0 4px; font-size: 13px; font-weight: 600;
           letter-spacing: -0.01em; }
.context-strip { display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
                 margin-bottom: 14px; }

/* ── form primitives ──────────────────────────────────── */
select, input[type=text], input[type=tel], input[type=email] {
  background: var(--c-surface); color: var(--c-ink);
  border: 1px solid var(--c-line-strong);
  border-radius: var(--r-sm); padding: 7px 9px; font-size: 12.5px;
  font-family: inherit; line-height: 1.3;
  transition: border-color 90ms ease;
}
input::placeholder, textarea::placeholder { color: var(--c-ink-3); }
textarea { width: 100%; background: var(--c-surface); color: var(--c-ink);
           border: 1px solid var(--c-line-strong); border-radius: var(--r-sm);
           padding: 9px 11px; font-size: 12.5px; resize: vertical;
           min-height: 72px; font-family: inherit; line-height: 1.6;
           transition: border-color 90ms ease; }
select:hover, input:hover, textarea:hover { border-color: var(--c-ink-4); }

/* One focus treatment for everything, MAGI's: a 2px ink outline, offset 2.
 * It is the same on a button, a link and a textarea, so it is learned once. */
:where(button, [role='button'], a, [tabindex], select, input, textarea,
       summary):focus-visible {
  outline: 2px solid var(--c-ink);
  outline-offset: 2px;
}

/* Primary action = MAGI's `solid` tone: paper type on the darkest ink there
 * is. Green was the old primary; it is now the brand mark and nothing else,
 * because a colour that means both "mirai" and "press this" means neither. */
button { padding: 8px 12px; font-size: 12px; font-weight: 500; line-height: 1;
         border: 1px solid var(--c-ink); font-family: inherit;
         border-radius: var(--r-sm); cursor: pointer;
         background: var(--c-ink); color: var(--c-paper);
         transition: transform 140ms var(--ease-out), background-color 90ms ease; }
button:disabled { pointer-events: none; opacity: 0.45; }
/* Press, not hover. MAGI lifts nothing on hover — a 1px rise under the cursor
 * is decoration; a 3% squash under the finger is the interface confirming it
 * heard you. */
button:active { transform: scale(0.97); }
button.ghost { background: var(--c-surface); color: var(--c-ink-2);
               border-color: var(--c-line-strong); }
button.ghost:hover { color: var(--c-ink); border-color: var(--c-ink-4); }
button.small { padding: 6px 9px; font-size: 11px; margin-top: 6px; }
button.big { padding: 10px 16px; font-size: 13px; border-radius: var(--r-sm); }
/* The one red thing on the page. MAGI's `danger`: tinted, bordered, never a
 * solid slab — a solid red button in a page of quiet ones is a dare. */
button.hang, button.dial.live {
  background: var(--c-alarm-bg); color: var(--c-alarm-ink);
  border-color: var(--c-alarm); font-weight: 600;
}
button.hang:hover, button.dial.live:hover { background: var(--c-alarm-bg-hover); }
/* The second way to place a call, and it reads as the second way: hairline,
 * not slab. `.dial.live` above outranks this on specificity, so hanging up a
 * dialled call is still the red control. */
button.dial { background: var(--c-surface); color: var(--c-ink-2);
              border-color: var(--c-line-strong); }
button.dial:hover { color: var(--c-ink); border-color: var(--c-ink-4); }
.chip { padding: 6px 10px; font-size: 12px; border: 1px solid var(--c-line-strong);
        border-radius: var(--r-sm); background: var(--c-surface);
        color: var(--c-ink-2); cursor: pointer; font-weight: 500; line-height: 1;
        transition: border-color 90ms ease, color 90ms ease; }
.chip:hover { border-color: var(--c-ink-4); color: var(--c-ink); }
.chip.on { background: var(--c-ink); border-color: var(--c-ink);
           color: var(--c-paper); }

/* ══ AGENT VIEW ════════════════════════════════════════════════════════════
 * ONE AGENT, ONE CARD. This was two cards — an agent bar, then "Your agent's
 * script" — with a hairline and 14px of nothing between the name of a thing
 * and the thing itself. Two cards is two objects and there is only one object
 * here, so the header names which agent and everything under the rule belongs
 * to it. See the note at the top of agent.html.
 *
 * The header is the only part about the SET of agents (switch, add, delete);
 * Save sits with them because it saves the whole card, and because a Save
 * button at the bottom of a 3,000-character script is a Save button nobody
 * finds. */
.agent-card { padding: 0; }
.agent-head { display: flex; align-items: center; justify-content: space-between;
              gap: 12px 16px; flex-wrap: wrap;
              padding: 13px 18px; border-bottom: 1px solid var(--c-line);
              background: var(--c-sunk); border-radius: var(--r-md) var(--r-md) 0 0; }
/* The agent strip. Replaces a <select> that hid the whole list behind a click —
 * see the note over .agent-tabs in agent.html. It scrolls sideways rather than
 * wrapping: a header that grows to three rows when a workspace reaches nine
 * agents pushes the script down the page every time somebody adds one.
 *
 * No scrollbar-hiding tricks. On the platforms that draw one it is the only
 * thing telling you there are more agents off the right-hand edge. */
.agent-tabs { display: flex; align-items: center; gap: 6px;
              min-width: 0; flex: 1 1 auto;
              overflow-x: auto; overscroll-behavior-x: contain;
              padding: 2px 0; }
.agent-tab { flex: 0 0 auto; max-width: 220px; overflow: hidden;
             text-overflow: ellipsis; white-space: nowrap; }
/* "+ New agent" is the last chip and reads as an empty slot rather than a
 * fourth agent: dashed edge, quiet ink, and it never takes the `on` fill. */
.agent-tab-new { flex: 0 0 auto; white-space: nowrap;
                 border-style: dashed; color: var(--c-ink-3); }
.agent-tab-new:hover { color: var(--c-ink); }
.agent-acts { display: flex; gap: 8px; align-items: center; flex: 0 0 auto; }
.agent-acts button.ghost { background: var(--c-surface); }
/* Delete is a link, not a button: it is the one destructive action on the view
 * and it must not carry the same weight as "New agent". It turns alarm on
 * hover only — a permanently red word on a page you open every day is a
 * warning nobody reads by the third visit. */
.agent-del { background: none; border: 0; padding: 6px 2px; cursor: pointer;
             font-family: inherit; font-size: 11.5px; font-weight: 500;
             color: var(--c-ink-3); text-decoration: underline;
             text-decoration-style: dotted; text-underline-offset: 3px; }
.agent-del:hover { color: var(--c-alarm-ink); background: none; transform: none; }
.agent-del:disabled { pointer-events: none; opacity: 0.45; }

.agent-body { padding: 4px 18px 18px; }
/* The name IS the subject of the card, so it is set at heading weight. A 12px
 * box in a form row would have made the page's subject look like a setting. */
.agent-name { font-size: 16px; font-weight: 600; letter-spacing: -0.018em;
              padding: 9px 11px; width: 100%; max-width: 360px;
              color: var(--c-ink); }
.agent-nameblock { padding-top: 14px; }
.agent-sec { margin-top: 16px; padding-top: 14px;
             border-top: 1px solid var(--c-line); }

/* Opened by "New agent", inside the card, directly under the header it belongs
 * to — so the field appears where the button that summoned it is.
 *
 * Sunk, not amber. This shipped on --c-open-bg with --c-open-ink copy, which
 * broke rule 2: amber is a SEMANTIC in this system — "unresolved", something
 * the customer still has to deal with — and not a highlight for whatever the
 * page most wants you to look at. A create form is neither unresolved nor a
 * warning; it is a drawer that opened. Spend the same amber on a real open
 * state and it stops meaning anything. The recessed fill plus the hairline
 * under it says "this belongs to the header above" without borrowing a colour
 * that has a job. */
.agent-new { display: none; gap: 8px; align-items: center; flex-wrap: wrap;
             padding: 13px 18px; border-bottom: 1px solid var(--c-line);
             background: var(--c-sunk); }
.agent-new.on { display: flex; }
.agent-new input { flex: 1 1 220px; min-width: 0; background: var(--c-surface); }
.agent-new-hint { flex: 1 1 100%; margin: 0; font-size: 11.5px;
                  line-height: 1.55; color: var(--c-ink-3); }

/* ── the script ───────────────────────────────────────── */
.tools-label { display: block; margin-bottom: 6px; }
.script-tools { display: flex; gap: 6px; margin: 0 0 4px; flex-wrap: wrap; }
.script-tools select { min-width: 190px; }
.script-tools input { flex: 1; min-width: 210px; }

/* ── the starting point ────────────────────────────────────────────────────
 * A fold, not a permanent row — see the note over #startpoint in agent.html.
 * It borrows the .docs treatment rather than inventing a second disclosure:
 * this console has exactly one way a <details> looks, and a customer who has
 * opened "How this sandbox works" has already learned it. The difference is
 * that this one sits INSIDE the card it acts on, so it is sunk rather than
 * raised — it is part of the agent, not a note beside it. */
.startpoint { background: var(--c-sunk); border: 1px solid var(--c-line);
              border-radius: var(--r-md); padding: 0 14px;
              margin-top: 16px; }
.startpoint summary { cursor: pointer; font-size: 12.5px; font-weight: 500;
                      padding: 11px 0; color: var(--c-ink-2);
                      list-style-position: inside; }
.startpoint summary:hover { color: var(--c-ink); }
.startpoint[open] summary { border-bottom: 1px solid var(--c-line); }
.sp-body { padding: 12px 0 14px; }
.sp-row .script-tools { margin-bottom: 0; }
.sp-row input, .sp-row select { background: var(--c-surface); }
/* The rule-and-word divider from the rail, reused: two ways to do one thing,
 * with the word that says they are alternatives. */
.sp-body .rail-div { margin: 14px 0; }
.two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 12px;
           margin-top: 12px; }

/* ── the script editor ────────────────────────────────────────────────────
 * Customers paste two to four thousand characters of system prompt in here.
 * The box used to open at ten lines, which made a real script look like it had
 * been truncated. It now opens at seventeen, grows with the text to a
 * viewport-bound ceiling and scrolls past that, and the character count sits
 * in the label row so nobody has to guess whether the paste landed whole.
 * Growth is a height write on input, not an animation — the caret must not
 * move on a timing curve while you are typing at it. */
.editor { margin-top: 14px; }
.two-col .editor { margin-top: 0; }
.editor-head { display: flex; align-items: baseline; gap: 10px;
               justify-content: space-between; margin-bottom: 6px; }
.editor-head label { line-height: 1.3; }
.charcount { font-size: 10.5px; color: var(--c-ink-3); white-space: nowrap;
             font-variant-numeric: tabular-nums; }
.editor-body { display: block; width: 100%; }
/* A script is read by a machine and scanned by a human for structure, so the
 * long one gets the mono face and a looser leading. Devanagari falls back to
 * the system Indic face per glyph, which is correct — there is no monospaced
 * Devanagari worth forcing. */
textarea.editor-lg { font-family: var(--mono); font-size: 12.5px;
                     line-height: 1.65; min-height: 360px; tab-size: 2; }
textarea.editor-sm { min-height: 88px; line-height: 1.6; }
.docs { background: var(--c-surface); border: 1px solid var(--c-line);
        border-radius: var(--r-md); padding: 2px 18px 4px; }
.docs summary { cursor: pointer; font-size: 12.5px; font-weight: 500;
                padding: 11px 0; color: var(--c-ink-2); list-style-position: inside; }
.docs summary:hover { color: var(--c-ink); }
.docs ol { margin: 2px 0 12px; padding-left: 20px; color: var(--c-ink-2);
           font-size: 12px; line-height: 1.75; }

/* ── right rail · test ────────────────────────────────── */
/* top AND height both carry --banner-h; see measureBanner() in core.js for why
 * each of the two scroll states needs a different one of them and neither is
 * wrong in the other. It is 0px until core.js measures, so a browser with no
 * ResizeObserver gets exactly the frame this had before. */
.rail { position: sticky; top: calc(16px + var(--banner-h, 0px));
        height: calc(100vh - 32px - var(--banner-h, 0px));
        height: calc(100dvh - 32px - var(--banner-h, 0px));
        display: flex; flex-direction: column; gap: 10px; min-width: 0; }
.rail-head { display: flex; align-items: baseline; justify-content: space-between;
             gap: 10px; padding: 2px 2px 0; flex-wrap: wrap; }
.rail-head h2 { font-weight: 650; font-size: 15px; letter-spacing: -0.014em;
                margin: 0; color: var(--c-ink); }
.statusline { font-size: 11px; color: var(--c-ink-3); padding: 0 2px;
              min-height: 15px; }
.rail-card { background: var(--c-surface); border: 1px solid var(--c-line);
             border-radius: var(--r-md); padding: 12px 14px; }
/* The tier picker that used to share this row is gone, and so is the voice
 * dropdown that replaced it — both were controls with nothing to choose (see
 * the comments in index.html). What is left states what you are about to hear
 * and then gives you one button to hear it: label left, value right, one
 * hairline between the statement and the action. */
/* A labelled control in the rail. The label uses the same eyebrow treatment as
 * .rail-fact-k so a picker and a stated fact read as the same kind of thing. */
.rail-field { display: flex; align-items: center; justify-content: space-between;
              gap: 10px; padding: 7px 0; }
/* 66, not 60: agent names are the longest values these pickers ever hold — a
 * customer types "EMI reminder — Hindi" and the label beside it is one short
 * word, so the six points come off whitespace nothing was using. */
.rail-field select { flex: 1 1 auto; min-width: 0; max-width: 66%; }
.rail-field[hidden] { display: none; }
/* Labels in the panel are words, not eyebrows: "Agent", "Voice". */
.rail-field .rail-fact-k { font-size: 12.5px; font-weight: 500; letter-spacing: 0;
                           text-transform: none; color: var(--c-ink-2); }
.rail #chatpane h2, .rail .latwrap summary { font-size: 12.5px; font-weight: 600;
  letter-spacing: -0.004em; text-transform: none; color: var(--c-ink); }

/* ── the test panel, as a panel ───────────────────────────────────────────
 * 2026-09-23: on a wide window the rail is the window's inspector — the same
 * floating material as the sidebar, so the two read as chrome around the page
 * and the page as the document between them. Inside it, cards are white and
 * lifted rather than outlined. */
@media (min-width: 1081px) {
  .rail { padding: 14px; overflow: auto; overscroll-behavior: contain;
          background: var(--ui-glass);
          -webkit-backdrop-filter: blur(24px) saturate(170%);
          backdrop-filter: blur(24px) saturate(170%);
          border: 1px solid var(--ui-sep); border-radius: var(--ui-r-panel);
          box-shadow: var(--ui-card); }
  .rail .rail-card, .rail .pane { border: 0; border-radius: var(--ui-r-card);
                                  box-shadow: var(--ui-raise); }
}
@media (prefers-reduced-transparency: reduce) {
  .rail { background: var(--c-paper); -webkit-backdrop-filter: none;
          backdrop-filter: none; }
}

.rail-fact { display: flex; align-items: baseline; justify-content: space-between;
             gap: 10px; padding-bottom: 10px; margin-bottom: 12px;
             border-bottom: 1px solid var(--c-line); }
.rail-fact-v { font-size: 12.5px; font-weight: 600; color: var(--c-ink);
               text-align: right; min-width: 0; }

/* THE PRIMARY ACTION. There is exactly one solid full-width control in the
 * console and this is it — dialling a phone sits below a divider in the ghost
 * treatment, because two equally-weighted dark buttons side by side is the
 * same as having none. */
.rail-cta { display: flex; flex-direction: column; gap: 8px; }
#btn { width: 100%; padding: 12px 14px; font-size: 13.5px; font-weight: 600;
       letter-spacing: -0.005em;
       display: flex; align-items: center; justify-content: center; gap: 9px; }

/* ── the sleeping cat ──────────────────────────────────────────────────────
 * The GPU fleet being asleep is a fact about THIS BUTTON — press it and the
 * agent connects, thinks, says "speaking", and makes no sound — and it was
 * being reported by an amber banner three feet away at the top of the page.
 * So the state lives on the control it is about.
 *
 * It is a joke because the alternative is a customer thinking the product is
 * broken. A cat asleep on the button, and a hint under it naming the hours,
 * says "come back at ten" in the time it takes to glance at it; a red error
 * says "this does not work". The fleet is on a schedule, not on fire.
 *
 * The button STAYS PRESSABLE. Disabling it would be the console deciding for
 * someone whether their call is worth trying, and a configured cloud backup
 * can carry it while the fleet sleeps — same reasoning as
 * markVoiceAvailability() in agent.js.
 *
 * Inline SVG, not a GIF: no request, no fixed raster size, it inherits
 * currentColor so it works on the dark button, on the amber sleep state and in
 * the phone dock, and it can honour prefers-reduced-motion — which a GIF, by
 * construction, cannot. */
/* --cat-cut is what the eyes and muzzle are cut out IN: the button's own fill,
 * so the features are holes in the silhouette rather than darker paint on dark
 * paint. Declared here (and inherited by the SVG) so the one place that knows
 * what colour the sleeping button is stays the one place that sets it. */
.btn-cat { display: none; width: 32px; height: 20px; flex: 0 0 auto;
           --cat-cut: var(--c-open-bg); }
#btn.asleep .btn-cat { display: block; }
/* Amber = `open`/unresolved, the same semantic as the banner and the latency
 * chart's middle band. Never alarm red: nothing has broken. */
#btn.asleep { background: var(--c-open-bg); color: var(--c-open-ink);
              border-color: var(--c-open-line); }
#btn.asleep:hover { background: var(--c-open-bg); }
/* THE ONE PIECE OF AMBIENT MOTION IN THE BUILD BESIDES #speaking, and it earns
 * the exception the same way: it exists only while a specific state is true,
 * it is the thing that communicates that state, and it stops the moment the
 * fleet answers. Slow enough to read as breathing rather than as a spinner. */
@keyframes cat-breathe { 0%, 100% { transform: scaleY(1); }
                         50% { transform: scaleY(1.06); } }
@keyframes cat-z { 0%   { opacity: 0; transform: translate(0, 3px); }
                   30%  { opacity: 1; }
                   100% { opacity: 0; transform: translate(3px, -5px); } }
#btn.asleep .cat-body { transform-origin: 50% 100%;
                        animation: cat-breathe 3.4s ease-in-out infinite; }
#btn.asleep .cat-z text { animation: cat-z 3.4s ease-in-out infinite; }
#btn.asleep .cat-z .z2 { animation-delay: 0.5s; }
#btn.asleep .cat-z .z3 { animation-delay: 1s; }

/* ── the cat that waits with you ───────────────────────────────────────────
 * At capacity a call joins a global FIFO and this button becomes "In queue ·
 * #3 — tap to leave". The wait is real, it is nobody's fault and it ends by
 * itself, so the cat sits down for it.
 *
 * NEUTRAL INK, NOT AMBER, and that is the whole design decision here. Amber in
 * this system means unresolved — something you are going to have to deal with.
 * A queue is not that. It is the product working correctly while busy, so the
 * button keeps its ordinary fill and the cut-outs are cut in that fill.
 *
 * .asleep beats .queued if both are somehow true: a fleet that cannot speak is
 * a stronger fact about pressing this button than a place in a line for it. */
.btn-cat-wait { display: none; width: 32px; height: 20px; flex: 0 0 auto;
                --cat-cut: var(--c-ink); }
#btn.queued .btn-cat-wait { display: block; }
#btn.asleep .btn-cat-wait { display: none; }
/* Same licence as the sleeping cat's breathing: it exists only while the state
 * is true, it IS the state, and it stops the instant the queue does. A few
 * degrees, hinged where the tail meets the rump — a tail tip moving 3° over
 * two and a half seconds is a cat waiting, not a spinner. transform-box is
 * view-box for SVG, so the origin is in the drawing's own coordinates. */
@keyframes cat-tail { 0%, 100% { transform: rotate(0deg); }
                      45%      { transform: rotate(-3.5deg); }
                      70%      { transform: rotate(1.5deg); } }
#btn.queued .cat-tail { transform-origin: 28.8px 24.4px;
                        animation: cat-tail 2.5s ease-in-out infinite; }

/* ── the cat gets up ───────────────────────────────────────────────────────
 * One shot on a real event — the fleet came back while somebody was looking.
 * Inserted by _gpuPaintButton in index.html and removed 1.6s later, so there is
 * nothing here to stop: the element leaves. `both` holds the last frame for the
 * few milliseconds between the animation ending and the node going. */
.btn-cat-stretch { width: 32px; height: 20px; flex: 0 0 auto;
                   --cat-cut: var(--c-ink);
                   animation: cat-stretch 1.6s var(--ease-out) both; }
/* ONE CAT AT A TIME, from the other side. _catStretch refuses to insert one
 * while the button is queued, but the reverse race is real and one second and a
 * half wide: the fleet wakes, the stretch goes in, the customer presses the
 * button straight away and is put in the queue — leaving a cat stretching next
 * to a cat sitting. Whatever the button's CURRENT state is outranks a leftover
 * of the state before it. */
#btn.queued .btn-cat-stretch, #btn.asleep .btn-cat-stretch { display: none; }
@keyframes cat-stretch {
  0%   { opacity: 0; transform: scaleY(0.78) translateY(2px); }
  22%  { opacity: 1; }
  55%  { transform: scaleY(1.08) translateY(-1.5px); }
  100% { opacity: 1; transform: scaleY(1) translateY(0); } }

/* ── the cat that listens ──────────────────────────────────────────────────
 * The two empty states: a transcript with no call in it yet, and a history with
 * no calls in it yet. Both are the same sentence in different words — nothing
 * has happened, and something is ready for it to.
 *
 * STATIC. No breathing, no flick, nothing. The other two poses animate because
 * they are reporting a live state that is currently changing; an empty list is
 * not changing, and a cat idling on it would be the decoration this family is
 * not allowed to be. --c-ink-4 is the decoration ramp, which is exactly what
 * this is: furniture beside a sentence, never a thing to read. */
.cat-still { flex: 0 0 auto; color: var(--c-ink-4);
             --cat-cut: var(--c-surface); }
.sys.hint { display: flex; align-items: center; justify-content: center;
            gap: 9px; text-align: left; }
.sys.hint .cat-still { width: 40px; height: 25px; }
.hist-empty { display: flex; align-items: center; gap: 11px; }
.hist-empty .cat-still { width: 34px; height: 21px; }
/* The hint under the button becomes the schedule while the fleet is down. */
.rail-hint.asleep { color: var(--c-open-ink); }
.rail-cta #meter { flex: 0 0 auto; width: 100%; }
.rail-hint { margin: 0; font-size: 11px; line-height: 1.5; color: var(--c-ink-3); }
.rail-div { display: flex; align-items: center; gap: 10px; margin: 12px 0;
            color: var(--c-ink-3); font-size: 10.5px; }
.rail-div::before, .rail-div::after { content: ''; flex: 1; height: 1px;
                                      background: var(--c-line); }
/* ── how to call ───────────────────────────────────────────────────────────
 * Two ways, both on screen. This replaced a <details> whose arrow you had to
 * find before the product's second capability existed for you.
 *
 * It is a switch, so it is drawn as one surface split in two, not as two
 * buttons: a single hairline box with a hairline between the halves. The
 * selected half is the paper the panel below is drawn on — same fill, no
 * bottom rule — so the switch and its panel read as one object. Rule 2:
 * selection carries weight AND ink AND the joined edge, never hue alone. */
/* 2026-09-23: drawn as a segmented control — a tinted track and a raised white
 * thumb under the selected half — the one selection idiom the console uses
 * everywhere now. The panel below is its own surface, no longer joined. */
.howrow { display: grid; grid-template-columns: 1fr 1fr; margin: 12px 0 0;
          padding: 2px; border: 0; border-radius: 9px;
          background: var(--ui-fill); overflow: hidden; }
.howtab { display: flex; align-items: center; justify-content: center; gap: 7px;
          min-height: 30px; padding: 6px 6px; border: 0; background: none;
          border-radius: 7px; cursor: pointer; font-family: inherit;
          font-size: 12.5px; font-weight: 500; color: var(--c-ink-2);
          letter-spacing: -0.004em;
          transition: background-color 150ms ease, color 150ms ease; }
.howtab:active { transform: none; }
.howtab svg { width: 15px; height: 15px; flex: 0 0 auto; opacity: 0.85; }
.howtab.on { background: var(--c-surface); color: var(--c-ink); font-weight: 600;
             box-shadow: var(--ui-raise); }
.howtab.on svg { opacity: 1; stroke-width: 2; }
.howtab:disabled { cursor: not-allowed; opacity: 0.5; }
@media (hover: hover) and (pointer: fine) {
  /* aria-disabled as well as :disabled — the environment switch and the
     call-method tabs mark an option they cannot take the first way (it still
     takes a press) and a hover that brightens it would read as an offer. */
  .howtab:not(.on):not(:disabled):not([aria-disabled="true"]):hover {
    color: var(--c-ink-2); }
}
/* The panel is the same paper as the selected half, joined to it. */
#cta-web, #dialbody { border: 0; border-radius: 0; background: none;
                      padding: 12px 0 0; }
.howtab[hidden], #cta-web[hidden], #dialbody[hidden] { display: none; }
.howrow:has(#how-phone[hidden]) { grid-template-columns: 1fr; }
/* ── voice or chat ─────────────────────────────────────────────────────────
 * The switch at the top of the card, then per mode only what that mode uses. In chat
 * the card is the switch and Start chat; the pane is the conversation and the
 * composer — nothing about audio, price per minute or turn latency bars. */
.howrow.modeswitch { margin: 0 0 12px; }
.rail-cta[hidden] { display: none; }
.app.chat-mode #setupbody, .app.chat-mode .howrow:not(.modeswitch),
.app.chat-mode #cta-web, .app.chat-mode #dialbody, .app.chat-mode #latpane,
.app.chat-mode #railcost,
.app.chat-mode #testctx { display: none !important; }   /* the GPU/voice pill */
.app:not(.chat-mode) #cta-chat, .app:not(.chat-mode) #composer { display: none !important; }
/* The typed chat's input, under the conversation it adds to. */
.composer { display: flex; align-items: flex-end; gap: 8px; margin: 0 12px 12px; padding: 6px 6px 6px 12px;
  border: 1px solid var(--c-line-strong); border-radius: var(--r-md); background: var(--c-surface); }
.composer:focus-within { border-color: var(--c-ink-4); box-shadow: var(--ui-ring); }
.composer[hidden] { display: none; }
.composer textarea { flex: 1; min-height: 22px; max-height: 120px; padding: 5px 0; border: 0; background: none;
  resize: none; font: 400 13px/1.45 var(--sans); color: var(--c-ink); box-shadow: none; }
.composer textarea:focus { outline: none; }
.composer button { flex: none; width: 30px; height: 30px; padding: 0; border-radius: 50%;
  display: flex; align-items: center; justify-content: center; }
.composer button svg { width: 16px; height: 16px; }
@media (max-width: 760px) { .composer textarea { font-size: 16px; } }
.dialwrap .rail-hint { margin-top: 8px; }
/* MAGI's disclosure trigger: a dotted underline, the oldest "there is more
 * here" affordance there is. Ink, not a link colour — there is no link colour. */
.linkbtn { background: none; border: none; color: var(--c-ink-2);
           font-size: 11.5px; font-weight: 500; padding: 8px 0 0; cursor: pointer;
           text-decoration: underline; text-decoration-style: dotted;
           text-underline-offset: 3px; }
.linkbtn:hover { color: var(--c-ink); background: none; transform: none; }
.action-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
input#phone { flex: 1; min-width: 130px; }
#meter { flex: 1; min-width: 60px; height: 4px; background: var(--c-line);
         border-radius: var(--r-xs); overflow: hidden; }
#meter i { display: block; height: 100%; width: 0%; background: var(--c-ink-2);
           transition: width 80ms linear; }

.pane { background: var(--c-surface); border: 1px solid var(--c-line);
        border-radius: var(--r-md); display: flex; flex-direction: column;
        min-height: 0; }
#chatpane { flex: 1; }
#chatpane h2 { margin: 0; padding: 11px 14px 8px; }
#chat { flex: 1; overflow-y: auto; padding: 2px 12px 12px; display: flex;
        flex-direction: column; gap: 8px;
        scrollbar-width: thin; scrollbar-color: var(--c-line-strong) transparent; }
/* Speaker is carried by ALIGNMENT and FILL, not by hue: the bot sits left in
 * the sunk fill, you sit right on surface behind a hairline. It survives
 * greyscale, which a green-tinted "you" bubble does not. */
.msg { max-width: 88%; padding: 8px 10px; border-radius: var(--r-md);
       font-size: 12.5px; line-height: 1.55; white-space: pre-wrap;
       animation: msg-in 200ms var(--ease-out) both; }
.msg .who { display: block; font-size: 10px; color: var(--c-ink-3);
            margin-bottom: 3px; letter-spacing: 0.04em; }
.bot  { align-self: flex-start; background: var(--c-sunk);
        border: 1px solid var(--c-line); border-bottom-left-radius: var(--r-xs);
        color: var(--c-ink); }
.user { align-self: flex-end; background: var(--c-surface);
        border: 1px solid var(--c-line-strong);
        border-bottom-right-radius: var(--r-xs); color: var(--c-ink); }
.sys  { align-self: center; color: var(--c-ink-3); font-size: 11px; padding: 2px 0;
        text-align: center; animation: fade-in 200ms var(--ease-out) both; }
.sys a { color: var(--c-ink-2); font-weight: 500;
         text-decoration: underline; text-decoration-style: dotted;
         text-underline-offset: 3px; }
.sys a:hover { color: var(--c-ink); }
.sys.hint { padding: 12px 0; }
/* A tool run, in the transcript where it happened. Left-aligned and full width
 * rather than a centred note: it opens to show what the tool returned. */
.sys.tool { align-self: stretch; text-align: left; padding: 5px 8px;
            background: var(--ui-fill-2); border-radius: var(--r-sm); font-size: 11.5px; }
.sys.tool > summary { cursor: pointer; list-style: none; display: flex; align-items: baseline;
                      gap: 6px; min-width: 0; color: var(--c-ink-2); }
.sys.tool > summary::-webkit-details-marker { display: none; }
.sys.tool > summary::before { content: ""; flex: none; width: 6px; height: 6px; border-radius: 50%;
                              background: var(--c-ink-4); transform: translateY(-1px); }
.sys.tool[data-status="running"] > summary::before { background: var(--c-open); animation: tool-pulse 1s ease-in-out infinite; }
.sys.tool[data-status="bad"] > summary::before { background: var(--c-alarm); }
.sys.tool[data-status="skipped"] > summary::before { background: var(--c-line-strong); }
.sys.tool[data-status="bad"] .tool-meta { color: var(--c-alarm-ink); }
.sys.tool b { font: 600 11.5px/1.4 var(--mono); color: var(--c-ink); }
.tool-meta { color: var(--c-ink-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tool-body { margin: 5px 0 1px 12px; font: 11px/1.55 var(--mono); color: var(--c-ink-2);
             overflow-wrap: anywhere; }
.tool-body span { color: var(--c-ink-3); }
.sys.tool-wait { font-size: 11px; }
/* The after-call results of a test call: summary, verdict, extracted fields. */
.sys.result-card { align-self: stretch; text-align: left; padding: 10px 12px; border-radius: var(--r-md);
  background: var(--c-surface); border: 1px solid var(--c-line); color: var(--c-ink-2); font-size: 12px; }
.rc-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.rc-head b { font-size: 12px; font-weight: 600; color: var(--c-ink); }
.rc-verdict { font-size: 11.5px; font-weight: 600; color: var(--c-ink-3); }
.rc-verdict[data-tone="ok"] { color: var(--c-ink); }
.rc-verdict[data-tone="ok"]::before { content: "✓ "; }
.rc-verdict[data-tone="bad"] { color: var(--c-alarm-ink); }
.rc-body p { margin: 6px 0 0; line-height: 1.5; }
.rc-summary { color: var(--c-ink); }
.rc-criteria, .rc-wait { color: var(--c-ink-3); font-size: 11.5px; }
.result-card:not([data-state="completed"]):not([data-state="done"]):not([data-state="failed"]) .rc-wait::before {
  content: ""; display: inline-block; width: 6px; height: 6px; margin-right: 6px; border-radius: 50%;
  background: var(--c-open); vertical-align: 1px; animation: tool-pulse 1s ease-in-out infinite; }
.rc-error { color: var(--c-alarm-ink); }
.rc-data { display: grid; grid-template-columns: auto 1fr; gap: 3px 12px; margin: 8px 0 0; padding-top: 8px;
  border-top: 1px solid var(--c-line); }
.rc-data dt { font: 11.5px/1.5 var(--mono); color: var(--c-ink-3); }
.rc-data dd { margin: 0; color: var(--c-ink); overflow-wrap: anywhere; }
.rc-data dd.rc-unknown { color: var(--c-ink-3); font-style: italic; }
.result-card button { margin-top: 8px; }
@media (prefers-reduced-motion: reduce) { .result-card .rc-wait::before { animation: none; } }
@keyframes tool-pulse { 50% { opacity: .35; } }
@media (prefers-reduced-motion: reduce) { .sys.tool[data-status="running"] > summary::before { animation: none; } }
#speaking { align-self: flex-start; color: var(--c-ink-2); font-size: 11.5px;
            padding: 0 2px; display: none; }
/* THE ONE LOOPING ANIMATION IN THE BUILD, and it is allowed because it is not
 * ambient: it exists only while audio is actually playing and it is the only
 * thing on screen that says so. It stops the instant the turn ends. */
#speaking::after { content: '●●●'; letter-spacing: 3px; margin-left: 7px;
                   font-size: 7px; vertical-align: 2px;
                   animation: pulse 1.4s ease-in-out infinite; }

/* ── response speed · the turn chart ──────────────────────────────────────
 * This was a six-column table of milliseconds. It is now one bar per turn,
 * drawn to a shared scale, because "1240" and "980" are two numbers a customer
 * has to compare by reading, while two bars against the same 1 s rule are one
 * glance. The bar is the reply — you stop talking → the agent starts — split
 * into the three stages that produced it, in pipeline order, left to right.
 *
 * RULE 2, all three axes:
 *   LENGTH is the primary encoding. Every bar crosses the same two vertical
 *     rules (1 s ink, 2 s alarm), so the verdict is a position, readable with
 *     no colour vision at all and no legend.
 *   SHAPE is second: ○ a hollow bar in the quiet neutral ramp under a second,
 *     ▨ the amber hatch between one and two, ■ a solid alarm slab over two —
 *     the same three marks the rest of the console uses, and the figure at the
 *     end of the row carries the matching swatch.
 *   HUE is last and always redundant.
 * Under 1 s stays deliberately quiet: a panel that celebrates every turn is a
 * panel nobody reads on the turn that matters.
 *
 * The stage divisions survive all three states as paper hairlines, so a slow
 * turn still shows you WHICH stage ate the second. Stage times are measured
 * independently and overlap slightly; the segments are their shares of the
 * reply, not a claim that they sum to it. */
.latwrap { padding: 0 0 4px; max-height: 274px; overflow: hidden;
           display: flex; flex-direction: column; }
.latwrap summary { cursor: pointer; padding: 11px 14px 8px;
                   list-style-position: inside; }
/* one definition of the row grid + the two threshold positions; call.js writes
 * --t1 / --t2 here and the axis and every bar inherit them */
#latpane { --lat-lead: 52px; --lat-tail: 58px; --t1: 40%; --t2: 80%; }

.lat-sum { display: flex; align-items: baseline; gap: 8px; padding: 0 14px 9px; }
.lat-sum:empty { display: none; }
.lat-sum-fig { font-size: 19px; font-weight: 600; letter-spacing: -0.025em;
               line-height: 1; color: var(--c-ink);
               font-variant-numeric: tabular-nums; }
.lat-sum-note { font-size: 11px; color: var(--c-ink-3); }

.lat-axis, .lat-row {
  display: grid; align-items: center; gap: 9px;
  grid-template-columns: var(--lat-lead) minmax(0, 1fr) var(--lat-tail);
}
.lat-axis { display: none; padding: 0 14px 3px; align-items: end; }
#latpane.has-data .lat-axis { display: grid; }
.lat-axis-lab { font-size: 9.5px; letter-spacing: 0.06em; line-height: 1;
                text-transform: uppercase; color: var(--c-ink-3); }
.lat-scale { position: relative; height: 11px; }
.lat-scale i { position: absolute; top: 0; transform: translateX(-50%);
               font-style: normal; font-size: 9.5px; line-height: 1;
               color: var(--c-ink-3); font-variant-numeric: tabular-nums; }
.lat-scale i.t1 { left: var(--t1); }
.lat-scale i.t2 { left: var(--t2); }

#lat { overflow: auto; padding: 0 14px 8px;
       scrollbar-width: thin; scrollbar-color: var(--c-line-strong) transparent; }
.lat-row { padding: 3px 0; }
@media (hover: hover) and (pointer: fine) {
  .lat-row { transition: background-color 90ms ease; }
  .lat-row:hover { background: var(--c-sunk); }
}
.lat-n { font-size: 10px; letter-spacing: 0.06em; text-transform: uppercase;
         font-weight: 600; color: var(--c-ink-3); white-space: nowrap;
         overflow: hidden; text-overflow: ellipsis;
         font-variant-numeric: tabular-nums; }
.lat-track { position: relative; height: 13px; background: var(--c-sunk);
             border-radius: var(--r-xs); }
.lat-bar { position: absolute; top: 0; bottom: 0; left: 0; display: flex;
           min-width: 4px; overflow: hidden; border-radius: var(--r-xs);
           border: 1px solid var(--c-ink-4); background: var(--c-surface); }
.lat-seg { display: block; height: 100%; min-width: 0; }
.lat-seg + .lat-seg { border-left: 1px solid var(--c-surface); }
/* stage identity is LIGHTNESS on the neutral ink ramp — no hue at all, so it
 * is legible to everyone and it never competes with the verdict */
.g-stt { background: var(--c-line); }
.g-llm { background: var(--c-line-strong); }
.g-tts { background: var(--c-ink-4); }
.g-all { background: var(--c-line-strong); }
/* the fourth stage, a tool the turn waited on: same neutral ramp, told apart
 * by texture rather than hue */
.g-tool { background: repeating-linear-gradient(90deg, var(--c-ink-4) 0 2px,
                                                 var(--c-line) 2px 4px); }
.lat-bar.warn { border-color: var(--c-open-line); }
.lat-bar.warn .lat-seg {
  background-color: var(--c-open-bg);
  background-image: repeating-linear-gradient(45deg,
    var(--c-open-hatch) 0 3px, transparent 3px 7px); }
.lat-bar.bad { border-color: var(--c-alarm); }
.lat-bar.bad .lat-seg { background: var(--c-alarm); }
/* the thresholds cross the bar rather than sitting behind it — which side of
 * the 1 s rule a bar ends on is the whole reading */
.lat-ticks { position: absolute; top: -2px; bottom: -2px; left: 0; right: 0;
             pointer-events: none;
             background-image: linear-gradient(var(--c-ink-4), var(--c-ink-4)),
                               linear-gradient(var(--c-alarm-line), var(--c-alarm-line));
             background-size: 1px 100%, 1px 100%;
             background-position: var(--t1) 0, var(--t2) 0;
             background-repeat: no-repeat; }

/* The figure at the end of the row. `good` / `warn` / `bad` come from cls() in
 * core.js — the names are load-bearing, the treatment is not. */
.lat-v { text-align: right; font-size: 11px; color: var(--c-ink-2);
         white-space: nowrap; font-variant-numeric: tabular-nums; }
.lat-v.warn, .lat-sum-fig.warn { color: var(--c-open-ink); font-weight: 500; }
.lat-v.bad,  .lat-sum-fig.bad  { color: var(--c-alarm-ink); font-weight: 600; }
.lat-v.warn::before, .lat-v.bad::before,
.lat-sum-fig.warn::before, .lat-sum-fig.bad::before {
  content: ''; display: inline-block; width: 7px; height: 7px;
  margin-right: 5px; vertical-align: 0; border: 1px solid;
}
.lat-v.warn::before, .lat-sum-fig.warn::before {
  border-color: var(--c-open-line); border-radius: 1px;
  background-color: var(--c-open-bg);
  background-image: repeating-linear-gradient(45deg,
    var(--c-open-hatch) 0 3px, transparent 3px 7px); }
.lat-v.bad::before, .lat-sum-fig.bad::before {
  border-color: var(--c-alarm); border-radius: 1px; background: var(--c-alarm); }
.lat-empty { color: var(--c-ink-3); font-size: 11.5px; line-height: 1.6;
             padding: 2px 0 10px; }

.legend { padding: 4px 14px 10px; font-size: 10.5px; color: var(--c-ink-3);
          line-height: 1.9; }
.lat-key { display: inline-flex; align-items: center; margin-right: 11px; }
.lat-key i { width: 15px; height: 8px; margin-right: 5px; border-radius: 1px;
             border: 1px solid var(--c-line-strong); }
.legend .good, .legend .warn, .legend .bad {
  display: inline-flex; align-items: center; white-space: nowrap;
}
.legend .good { color: var(--c-ink-2); }
.legend .warn { color: var(--c-open-ink); font-weight: 500; }
.legend .bad  { color: var(--c-alarm-ink); font-weight: 600; }
.legend .good::before, .legend .warn::before, .legend .bad::before {
  content: ''; display: inline-block; width: 8px; height: 8px;
  margin-right: 5px; border: 1px solid;
}
.legend .good::before { border-color: var(--c-ink-4); border-radius: 50%; }
.legend .warn::before { border-color: var(--c-open-line); border-radius: 1px;
                        background-color: var(--c-open-bg);
                        background-image: repeating-linear-gradient(45deg,
                          var(--c-open-hatch) 0 3px, transparent 3px 7px); }
.legend .bad::before  { border-color: var(--c-alarm); border-radius: 1px;
                        background: var(--c-alarm); }

/* ── home view ─────────────────────────────────────────────────────────────
 * A checklist, not three paragraphs of how-it-works — see the note at the top
 * of home.html. The cards became live, so they grew two things: a figure
 * (the workspace's own count) and, on the first one, the way in.
 *
 * The dark founder panel that used to sit under them is gone from this page
 * and its CSS with it; .founder-kicker and .founder-sign survive because the
 * sign-in gate still sets the note in them. */
.home-steps { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
.how { background: var(--c-surface); border: 1px solid var(--c-line);
       border-radius: var(--r-md); padding: 14px 16px;
       display: flex; flex-direction: column; align-items: flex-start; }
/* The badge is a fixed square so the copy under it does not shift by a pixel
 * when "01" becomes "✓" — a checklist that reflows as you tick it is a
 * checklist that looks broken. */
.how .n { display: inline-flex; align-items: center; justify-content: center;
          min-width: 18px; height: 16px;
          font-size: 11px; font-weight: 600; letter-spacing: 0.08em;
          color: var(--c-ink-3); font-variant-numeric: tabular-nums; }
.how b { display: block; font-size: 13px; font-weight: 600;
         letter-spacing: -0.01em; margin: 6px 0 4px; }
.how p { margin: 0; font-size: 12px; color: var(--c-ink-2); line-height: 1.6; }
/* Pushed to the bottom edge so three cards of unequal copy still line their
 * figures up — the numbers are meant to be read across, not hunted for. */
.how-fig { margin-top: auto; padding-top: 10px;
           font-size: 11.5px; font-weight: 600; color: var(--c-ink);
           font-variant-numeric: tabular-nums; letter-spacing: -0.01em; }
.how-act { margin: 10px 0 0; padding: 0; background: none; border: 0;
           font-family: inherit; font-size: 11.5px; font-weight: 500;
           color: var(--c-ink-2); cursor: pointer; text-decoration: underline;
           text-decoration-style: dotted; text-underline-offset: 3px; }
.how-act:hover { color: var(--c-ink); background: none; transform: none; }
/* DONE IS TOLD THREE WAYS, never by hue alone (rule 2): the glyph changes from
 * a numeral to a tick, the weight goes up, and only then does the colour move
 * to the accent. Read with the colour stripped out — by a colour-blind eye, or
 * printed — a bold ✓ still says done next to a light 02. */
.how.done { border-color: var(--accent-line); }
.how.done .n { color: var(--accent); font-weight: 700; font-size: 12px; }
.how.done .how-fig { color: var(--accent); }

/* The last call, as one line. Not a second history table: everything a person
 * would do with it is on the page it links to, and the only job here is to say
 * that something happened and to be the door. */
.home-recent { margin-top: 16px; background: var(--c-surface);
               border: 1px solid var(--c-line); border-radius: var(--r-md);
               padding: 12px 14px; }
.home-recent[hidden] { display: none; }
.home-recent-head { display: flex; align-items: baseline;
                    justify-content: space-between; gap: 10px; }
.home-recent-link { padding: 0; background: none; border: 0;
                    font-family: inherit; font-size: 11.5px; font-weight: 500;
                    color: var(--c-ink-2); cursor: pointer;
                    text-decoration: underline; text-decoration-style: dotted;
                    text-underline-offset: 3px; white-space: nowrap; }
.home-recent-link:hover { color: var(--c-ink); background: none; transform: none; }
/* The values are separated by the row's own gap rather than by punctuation —
 * four spans, one line, nothing between them to align or to translate. */
.home-recent-row { display: flex; align-items: baseline; gap: 10px;
                   flex-wrap: wrap; margin-top: 7px; font-size: 12px; }
.hr-kind { font-weight: 600; color: var(--c-ink); }
.hr-agent, .hr-dur, .hr-when { color: var(--c-ink-3); font-size: 11.5px;
                               min-width: 0; overflow-wrap: anywhere; }
.hr-when { margin-left: auto; font-variant-numeric: tabular-nums; }

.home-ask { margin: 16px 0 0; font-size: 12px; line-height: 1.7;
            color: var(--c-ink-3); }
.home-ask a { color: var(--c-ink-2); font-weight: 500;
              text-decoration: underline; text-decoration-style: dotted;
              text-underline-offset: 3px; }
.home-ask a:hover { color: var(--c-ink); }

/* ── history view ─────────────────────────────────────── */
.hist-stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px;
              margin-bottom: 14px; }
.hist-stat { background: var(--c-surface); border: 1px solid var(--c-line);
             border-radius: var(--r-md); padding: 12px 14px; display: flex;
             flex-direction: column-reverse; gap: 6px; align-items: flex-start; }
/* MAGI's hero-figure treatment: the largest type on the block, tight tracking,
 * tabular, and NOT coloured. The label sits above it as an eyebrow. */
.hist-stat strong { font-size: 24px; font-weight: 600; letter-spacing: -0.025em;
                    line-height: 0.95; color: var(--c-ink); }
/* ── a history row ─────────────────────────────────────────────────────────
 * One circular control on the left and two lines of text beside it. It used to
 * be two dotted text links at the end of the second line — see the note over
 * the row's markup in views.js for what those did.
 *
 * The circle is deliberately the SAME object as .sc-play on the sign-in page:
 * 34px, one hairline, a triangle that inverts to a fill when it is the thing
 * currently open. A customer meets it before they have an account, playing a
 * sample; meeting it again here should cost them nothing to learn. */
.hist-row { display: grid; grid-template-columns: auto minmax(0, 1fr);
            align-items: center; gap: 0 12px; padding: 10px 4px;
            border-bottom: 1px solid var(--c-line); font-size: 12px;
            cursor: pointer; transition: background-color 90ms ease; }
.hist-lines { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
@media (hover: hover) and (pointer: fine) {
  .hist-row:hover { background: var(--c-sunk); }
}
.hist-row:last-child { border-bottom: none; }
/* The row whose call is open in the inspector. Two marks, because a fill alone
 * is what HOVER means and the two would be indistinguishable the moment the
 * pointer is anywhere near the list: the sunk fill AND a 2px ink rule down the
 * left edge. The rule is a border with the padding taken back out of the same
 * side, so the row's text does not step sideways as you walk the list. */
.hist-row.sel { background: var(--c-sunk);
                border-left: 2px solid var(--c-ink); padding-left: 2px; }
.hist-play { flex: none; width: 34px; height: 34px; border-radius: 50%;
             padding: 0; display: flex; align-items: center;
             justify-content: center;
             border: 1px solid var(--c-line-strong); background: var(--c-surface);
             color: var(--c-ink); }
.hist-play svg { width: 13px; height: 13px; }
.hist-play .ico-pause { display: none; }
.hist-play:hover { border-color: var(--c-ink-4); }
.hist-play.on { border-color: var(--c-ink); background: var(--c-ink);
                color: var(--c-paper); }
.hist-play.on:hover { border-color: var(--c-ink); background: var(--c-ink); }
.hist-play.on .ico-play { display: none; }
.hist-play.on .ico-pause { display: block; }
.hist-top { display: flex; justify-content: space-between; align-items: baseline;
            gap: 8px; }
.hist-when { color: var(--c-ink-3); font-size: 11px; flex-shrink: 0; }
.hist-kind { font-weight: 600; color: var(--c-ink); }
/* Which agent took the call, once a workspace holds several. Deliberately the
 * same quiet treatment as .hist-when: the kind of call is what you scan a
 * history for and the agent is what you check afterwards, so it sits beside the
 * heading without competing with it. The separator is a text node written in
 * views.js rather than a ::before — a generated dot is not selectable and does
 * not survive a copy of the row. Rows recorded before agents were named carry
 * no name at all, and no placeholder either.
 *
 * margin-right: auto is what keeps it beside the kind rather than adrift: this
 * is a third item in a `space-between` row, and without the auto margin the
 * free space would be split around it and park the agent's name in the middle
 * of the row, between two things it has nothing to do with. */
.hist-agent { color: var(--c-ink-3); font-size: 11px; font-weight: 400;
              min-width: 0; overflow-wrap: anywhere; margin-right: auto; }
.hist-bot { display: flex; justify-content: space-between; align-items: baseline;
            gap: 8px; flex-wrap: wrap; }
.hist-to { color: var(--c-ink-2); }
.hist-duration { color: var(--c-ink-3); }
/* A dash means "no value", never a zero. MAGI's Nil. */
.hist-empty { color: var(--c-ink-3); font-size: 12.5px; padding: 18px 4px; }

/* ── admin view ───────────────────────────────────────── */
.howrow.admin-tabs { grid-template-columns: repeat(4, 1fr); margin: 0 0 20px; }
.admin-tabs .howtab { min-height: 44px; font-size: 14px; }
.admin-tabs .howtab.on { box-shadow: var(--ui-raise); }
.admin-panel[hidden], .brand-row[hidden], .admin-view [hidden] { display: none; }
.admin-panel .card { padding: 20px; min-width: 0; }
.admin-panel .use-head { align-items: flex-start; flex-wrap: wrap; }
.admin-panel .use-head h3 { margin-bottom: 4px; }
.admin-panel .use-note { font-size: 13px; max-width: 75ch; }
.admin-mint { display: flex; flex-direction: column; gap: 8px; }
.admin-mint label, .admin-field label { color: var(--c-ink-2); font-size: 13px; font-weight: 500; }
.admin-mint label:not(:first-child) { margin-top: 8px; }
.admin-mint input, .admin-field input, .admin-field select { min-height: 38px; font-size: 14px; width: 100%; }
.admin-mint #mintbtn { margin-top: 8px; min-height: 40px; }
.admin-filters { display: flex; flex-wrap: wrap; gap: 12px; padding: 16px 0; }
.admin-field { display: flex; flex-direction: column; gap: 6px; flex: 1 1 140px; min-width: 0; }
.admin-company, .admin-search { flex: 2 1 210px; }
.admin-log-toolbar { display: flex; justify-content: space-between; align-items: center; gap: 12px;
                     padding: 0 0 16px; flex-wrap: wrap; }
.admin-view .use-r { padding: 6px 12px; font-size: 13px; }
.admin-view .ghost { min-height: 34px; padding: 6px 12px; font-size: 13px; }
.admin-view .brand-row { padding: 12px 0; font-size: 14px; }
.admin-view .brand-row .b-exp, .admin-view .b-rot, .admin-view .b-imp,
.admin-view .b-live, .admin-view .b-ext, .admin-view .b-contact { font-size: 12px; }
.admin-view .brand-row .b-code { font-size: 12px; margin-top: 3px; }
.admin-view .b-side { flex-wrap: wrap; justify-content: flex-end; }
.admin-view .b-rot, .admin-view .b-imp, .admin-view .b-live, .admin-view .b-ext,
.admin-view .b-contact { min-height: 30px; }
.admin-pagination { display: flex; justify-content: space-between; align-items: center; gap: 14px;
                    flex-wrap: wrap; border-top: 1px solid var(--c-line); margin-top: 12px;
                    padding-top: 16px; color: var(--c-ink-3); font-size: 13px; }
.admin-page-buttons, .admin-page-size { display: flex; align-items: center; gap: 8px; }
.admin-page-buttons > span { margin-right: 8px; font-variant-numeric: tabular-nums; }
.admin-page-size select { min-height: 34px; font-size: 13px; padding: 4px 8px; }
.admin-view .tts-reqtable { min-width: 880px; }
.admin-view .tts-reqtable td { padding-top: 12px; padding-bottom: 12px; }
.admin-view .admin-log-note { margin: 12px 0 0; }
.admin-view .use-empty { padding: 28px 0; }
@media (max-width: 720px) {
  .admin-panel .card { padding: 16px; }
  .admin-brand-filters { flex-direction: column; }
  .admin-brand-filters .admin-field { flex-basis: auto; }
  .admin-view .brand-row .b-top { align-items: flex-start; flex-direction: column; }
  .admin-view .brand-row .b-side { justify-content: flex-start; }
  .b-live-form, .b-live-link { flex-direction: column; align-items: stretch; }
  .admin-tabs .howtab { font-size: 13px; }
}
.admin-grid { display: grid; grid-template-columns: 300px 1fr; gap: 12px;
              align-items: start; }
.code-out { font-family: var(--mono); font-size: 12px; color: var(--c-ink);
            user-select: all; min-height: 0; word-break: break-all;
            padding-top: 6px; }
/* The minted code is shown exactly once, so it gets the weight of a thing you
   must act on now rather than a line of status text you skim past. */
.mint-label { font-family: var(--font); font-size: 11px; color: var(--c-ink-3);
              text-transform: uppercase; letter-spacing: .04em; }
.mint-code { font-family: var(--mono); font-size: 16px; font-weight: 600;
             color: var(--c-ink); user-select: all; word-break: break-all;
             margin: 4px 0 6px; }
.mint-warn { font-family: var(--font); font-size: 11px; color: var(--c-alarm-ink);
             line-height: 1.4; margin-bottom: 8px; }
.mint-copy { font-size: 11px; padding: 4px 10px; }
/* Sending the code on is the step that follows copying it, so it sits below
   the Copy button behind the same hairline the rest of this card uses — one
   more action on the card, not a second card. */
/* .code-out sets `user-select: all` so the code can be grabbed in one click.
   Inherited into a text field that means clicking to place a cursor selects
   the whole line instead, so it is undone here. */
.mint-mail { margin-top: 12px; padding-top: 10px;
             border-top: 1px solid var(--c-line);
             user-select: text; word-break: normal; }
/* Which of the three notes goes out. It sits ABOVE the address field because
   it is the decision you make first — picking it after typing the addresses
   means re-reading the row you already filled in to check what you are about
   to send them. */
.mint-mail-focus { display: flex; gap: 6px; align-items: center;
                   margin-bottom: 6px; }
.mint-mail-focuslab { font-family: var(--font); font-size: 11px;
                      color: var(--c-ink-3); text-transform: uppercase;
                      letter-spacing: .04em; flex: 0 0 auto; }
.mint-mail-sel { flex: 1 1 auto; min-width: 0; font-family: var(--font);
                 font-size: 11.5px; padding: 4px 8px; }
.mint-mail-row { display: flex; gap: 6px; align-items: center;
                 flex-wrap: wrap; }
.mint-mail-input { flex: 1 1 150px; min-width: 0; font-family: var(--font);
                   font-size: 11.5px; padding: 4px 8px; }
.mint-mail-btn { font-size: 11px; padding: 4px 10px; white-space: nowrap; }
.mint-mail-note { font-family: var(--font); font-size: 11px; line-height: 1.4;
                  color: var(--c-ink-3); margin-top: 6px; }
.mint-mail-note:empty { display: none; }
.mint-mail-note.good { color: var(--c-ink-2); }
.mint-mail-note.bad { color: var(--c-alarm-ink); }
.brand-row { display: flex; flex-direction: column; gap: 2px; padding: 9px 0;
             border-top: 1px solid var(--c-line); font-size: 12px; }
.brand-row:first-child { border-top: none; }
.brand-row .b-top { display: flex; justify-content: space-between;
                    align-items: center; gap: 8px; }
.brand-row .b-name { font-weight: 600; color: var(--c-ink); }
.brand-row .b-side { display: flex; align-items: center; gap: 8px; }
.brand-row .b-exp { color: var(--c-ink-3); font-size: 11px; white-space: nowrap; }
/* Expiring soon is the same "unresolved" fact the latency table calls `warn`,
 * so it gets the same amber hatch mark rather than a lone amber word. */
.brand-row .b-exp.soon { color: var(--c-open-ink); font-weight: 600; }
.brand-row .b-exp.soon::before {
  content: ''; display: inline-block; width: 7px; height: 7px; border-radius: 1px;
  margin-right: 5px; border: 1px solid var(--c-open-line);
  background-color: var(--c-open-bg);
  background-image: repeating-linear-gradient(45deg,
    var(--c-open-hatch) 0 3px, transparent 3px 7px);
}
/* Extend: a small panel under the row, like going live. */
.b-ext-panel { display: flex; align-items: center; flex-wrap: wrap; gap: 8px 12px;
               margin-top: 8px; padding: 10px 12px; border-radius: var(--r-sm);
               background: var(--c-sunk); font-size: 12px; color: var(--c-ink-2); }
.b-ext-days { display: flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 500;
              letter-spacing: 0; text-transform: none; color: var(--c-ink); }
.b-ext-days input { width: 64px; padding: 4px 6px; font-size: 12px; }
.b-ext-note { flex: 1 1 220px; color: var(--c-ink-3); }
.b-ext-acts { display: flex; gap: 6px; margin-left: auto; }
.b-ext-acts button { font-size: 12px; padding: 5px 10px; margin: 0; }
.brand-row .b-code { font-family: var(--mono); color: var(--c-ink-2);
                     font-size: 11.5px; user-select: all; word-break: break-all; }
.b-del { background: none; border: none; color: var(--c-ink-3); cursor: pointer;
         padding: 0 3px; font-size: 12px; line-height: 1; }
.b-del:hover { color: var(--c-alarm-ink); background: none; transform: none; }
.b-ext[hidden] { display: none; }
.b-imp, .b-rot, .b-live, .b-ext, .b-promo, .b-contact { background: none; border: 1px solid var(--c-line-strong);
         color: var(--c-ink-2); cursor: pointer; border-radius: 3px;
         padding: 2px 7px; font-size: 11px; line-height: 1.4;
         white-space: nowrap; }
.b-imp:hover { color: var(--accent); border-color: var(--accent-line);
               background: var(--accent-bg); transform: none; }
/* Amber, not green: rotating invalidates a code somebody is holding. */
.b-ext:hover, .b-contact:hover { color: var(--c-ink); border-color: var(--c-ink-4); background: none; transform: none; }
/* A self-serve brand's arrival (lane, confirmed, campaign) and the last contact. */
.brand-row .b-meta { font-size: 11.5px; line-height: 1.5; color: var(--c-ink-3); overflow-wrap: anywhere; }
.brand-row .b-meta[hidden] { display: none; }
.b-contact-note { flex: 1 1 220px; min-width: 0; padding: 4px 8px; font-size: 12px; }
.b-contact-channel { padding: 3px 6px; font-size: 12px; }
.b-rot:hover { color: var(--c-open-ink); border-color: var(--c-open-line);
               background: var(--c-open-bg); transform: none; }
/* Accent, like "view as" and unlike "new code": nothing is taken away from
   anybody here. This is the button that wins a customer. */
.b-live:hover, .b-promo:hover { color: var(--accent); border-color: var(--accent-line);
                background: var(--accent-bg); transform: none; }
/* The row's answer to "have we taken them live already?", readable with every
   panel shut. Mono, because the workspace id in it gets quoted into tickets. */
.b-live-chip { font-family: var(--mono); color: var(--c-ink-3);
               font-size: 11px; margin-top: 3px; user-select: all;
               word-break: break-all; }
/* Inside the row on purpose: the search box and the pager hide rows, so a
   panel that lived anywhere else would be stranded on page two with its
   invitation still on screen. */
.b-live-panel { margin-top: 8px; padding: 10px 12px;
                border: 1px solid var(--c-line); border-radius: 4px;
                background: var(--c-sunk); user-select: text; }
.b-imp-panel { margin-top: 12px; padding: 14px 16px; border: 1px solid var(--c-line); border-radius: var(--r-sm); background: var(--c-sunk); }
.b-imp-help { margin: 0 0 10px; font-size: 13px; color: var(--c-ink-3); }
.b-imp-choices { display: flex; gap: 8px; flex-wrap: wrap; }
.b-imp-choices button { display: flex; flex-direction: column; align-items: flex-start; gap: 2px; text-align: left; }
.b-imp-choices span { font: 12px/1.3 var(--mono); color: var(--c-ink-3); overflow-wrap: anywhere; }
.b-promotion-panel { margin-top: 12px; padding: 16px; border: 1px solid var(--c-line); border-radius: var(--r-sm); background: var(--c-sunk); }
.b-promotion-panel h4 { margin: 0 0 8px; font-size: 14px; }
.b-promotion-panel label { display: flex; flex-direction: column; gap: 5px; margin: 10px 0; font-size: 12px; }
.b-promotion-panel input, .b-promotion-panel select { width: 100%; min-height: 36px; }
.b-promotion-panel p { font-size: 12px; line-height: 1.5; color: var(--c-ink-2); }
.b-promo-actions { display: flex; justify-content: flex-end; gap: 8px; flex-wrap: wrap; }
.b-promo-actions button { margin: 0; min-height: 36px; }
.b-promo-actions button[hidden] { display: none; }
.b-promo-result:empty { display: none; }
/* The offer's deadline and who hears about it. Fieldsets for grouping (and so
   one `disabled` takes them out of the form in Wallet credit mode), with the
   browser's frame removed. */
.b-promotion-panel fieldset { border: 0; margin: 0; padding: 0; min-width: 0; }
.b-promotion-panel fieldset[hidden] { display: none; }
.b-promotion-panel legend { padding: 0; margin: 0 0 5px; font-size: 12px; font-weight: 600;
                            line-height: 1; color: var(--c-ink-3); }  /* as the panel's labels */
.b-promotion-panel .b-promo-people { margin-top: 12px; }
.b-promo-chips { display: flex; flex-wrap: wrap; gap: 6px; margin: -4px 0 0; }
.b-promo-chips button { background: none; border: 1px solid var(--c-line-strong); color: var(--c-ink-2);
                        border-radius: 3px; padding: 2px 7px; margin: 0; font-size: 11px; line-height: 1.4;
                        min-height: 26px; white-space: nowrap; cursor: pointer; }
.b-promo-chips button:hover { color: var(--accent); border-color: var(--accent-line);
                              background: var(--accent-bg); transform: none; }
.b-promotion-panel .b-promo-when { margin: 6px 0 0; color: var(--c-ink-3); font-variant-numeric: tabular-nums; }
.b-promo-when:empty { display: none; }
.b-promo-contact { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr) auto;
                   gap: 6px; align-items: center; margin-bottom: 6px; }
.b-promo-cdel { background: none; border: none; color: var(--c-ink-3); cursor: pointer;
                padding: 0 6px; margin: 0; min-height: 36px; font-size: 12px; line-height: 1; }
.b-promo-cdel:hover { color: var(--c-alarm-ink); background: none; transform: none; }
.b-promotion-panel .b-promo-add { margin: 0; min-height: 30px; padding: 4px 10px; font-size: 12px; }
.b-promo-add[hidden] { display: none; }
/* What conductor reports about the saved offer. Amber once it has ended (the
   same "needs a decision" tone as a code about to expire), green once taken. */
.b-promo-status { margin: 0 0 8px; padding: 10px 12px; border: 1px solid var(--c-line);
                  border-radius: var(--r-sm); background: var(--c-surface); font-size: 12px; }
.b-promo-status:empty, .b-promo-status[hidden] { display: none; }
.b-promo-status.ended { border-color: var(--c-open-line); background: var(--c-open-bg); }
.b-promo-status.accepted { border-color: var(--accent-line); background: var(--accent-bg); }
.b-promotion-panel .b-promo-state { margin: 0; color: var(--c-ink); font-weight: 600; }
.b-promo-status.ended .b-promo-state { color: var(--c-open-ink); }
.b-promo-status.accepted .b-promo-state { color: var(--accent); }
.b-promo-status ul { margin: 6px 0 0; padding: 0; list-style: none; color: var(--c-ink-2);
                     line-height: 1.6; font-variant-numeric: tabular-nums; }
.b-promo-status li.bad { color: var(--c-alarm-ink); }
/* "offer ends in 5 h" beside the promotion button, once this page has read it. */
.brand-row .b-offer-hint { color: var(--c-ink-3); font-size: 11px; white-space: nowrap; }
.brand-row .b-offer-hint.soon { color: var(--c-open-ink); font-weight: 600; }
.admin-view .brand-row .b-offer-hint { font-size: 12px; }
@media (max-width: 720px) {
  .b-promo-contact { grid-template-columns: minmax(0, 1fr) auto; }
  .b-promo-contact .b-promo-cname { grid-area: 1 / 1; }
  .b-promo-contact .b-promo-cemail { grid-area: 2 / 1; }
  .b-promo-contact .b-promo-cdel { grid-area: 1 / 2 / 3 / 3; }
}
.b-live-lede { margin: 0 0 8px; font-size: 11.5px; line-height: 1.45;
               color: var(--c-ink-2); }
.b-live-form { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
.b-live-lab { font-family: var(--font); font-size: 11px; color: var(--c-ink-3);
              text-transform: uppercase; letter-spacing: .04em;
              flex: 0 0 auto; margin-top: 10px; }
.b-live-form .b-live-lab { margin-top: 0; }
.b-live-tier { flex: 1 1 180px; min-width: 0; font-family: var(--font);
               font-size: 11.5px; padding: 4px 8px; }
.b-live-go, .b-live-copy { font-size: 11px; padding: 4px 10px;
                           white-space: nowrap; }
.b-live-note { font-family: var(--font); font-size: 11px; line-height: 1.4;
               color: var(--c-ink-3); margin-top: 6px; }
.b-live-note:empty, .b-live-out:empty { display: none; }
.b-live-note.good { color: var(--c-ink-2); }
.b-live-note.bad { color: var(--c-alarm-ink); }
.b-live-link { display: flex; gap: 6px; align-items: flex-start;
               flex-wrap: wrap; margin-top: 4px; }
.b-live-link a { font-family: var(--mono); font-size: 11.5px; color: var(--c-ink);
                 word-break: break-all; flex: 1 1 200px; }
.b-live-mono { font-family: var(--mono); font-size: 11.5px; color: var(--c-ink-2);
               margin-top: 4px; user-select: all; word-break: break-all; }
.b-live-plan { font-size: 12px; font-weight: 600; color: var(--c-ink);
               margin-top: 4px; }
/* The one sentence on this panel that expires. */
.b-live-warn { font-size: 11px; line-height: 1.45; color: var(--c-alarm-ink);
               margin: 10px 0 6px; }

/* ── admin: API usage ─────────────────────────────────────
 * A grid rather than a <table>, like every other list on this console: the
 * columns have to collapse to a stack on a phone, and a table cannot. The
 * counts are tabular-nums and right-aligned, because the reason to read this
 * block at all is to compare one company's row against another's. */
.use-card { margin-top: 12px; }
/* Billing: current contract and spendable credit first, reconciliation below. */
.howrow.bill-tabs { grid-template-columns: repeat(3, 1fr); margin: 0 0 22px; }
.bill-tabs .howtab { min-height: 44px; font-size: 14px; }
.bill-tabs .howtab.on { box-shadow: var(--ui-raise); }
.bill-panel[hidden], .bill-cancel-step[hidden], .bill-cancel-progress[hidden], .bill-cancelled[hidden] { display: none; }
.bill-overview { display: grid; grid-template-columns: minmax(0, 1.6fr) minmax(250px, 1fr); gap: 16px; margin: 0 0 16px; }
.bill-cancelled { margin-bottom: 20px; border: 1px solid var(--c-line); border-radius: 4px; background: var(--c-sunk); }
.bill-cancelled summary { display: flex; gap: 14px; align-items: center; padding: 18px 22px; cursor: pointer; list-style: none; }
.bill-cancelled summary::-webkit-details-marker { display: none; }
.bill-cancelled summary strong { display: block; font-size: 14px; margin-bottom: 5px; }
.bill-cancelled summary span span { display: block; color: var(--c-ink-3); font-size: 13px; line-height: 1.6; }
.bill-confirm-icon { display: grid; place-items: center; width: 30px; height: 30px; flex: 0 0 auto; border: 1px solid var(--c-line); border-radius: 50%; }
.bill-receipt-link { margin-left: auto; font-size: 12px; white-space: nowrap; text-decoration: underline; text-underline-offset: 3px; }
.bill-cancelled-details { border-top: 1px solid var(--c-line); padding: 12px 22px; font-size: 12px; line-height: 1.7; overflow-wrap: anywhere; }
.bill-cancelled-details p { margin: 4px 0; white-space: pre-wrap; }
.bill-cancel-progress { display: flex; gap: 22px; margin-bottom: 24px; color: var(--c-ink-3); font-size: 12px; }
.bill-cancel-progress .on { color: var(--c-ink); font-weight: 600; }
.bill-cancel-step { display: flex; flex-direction: column; min-height: 380px; }
.bill-cancel-step h3 { margin: 0 0 10px; font-size: 22px; font-weight: 500; letter-spacing: -.035em; }
.bill-cancel-step > p { margin: 0 0 16px; font-size: 13px; line-height: 1.7; }
.bill-cancel-step label { display: block; margin: 12px 0 8px; }
.bill-cancel-step label span { margin-left: 8px; font-weight: 400; }
.bill-cancel-step textarea { resize: vertical; min-height: 80px; max-height: 180px; }
.bill-cancel-step .modal-acts { margin-top: auto; padding-top: 24px; }
.bill-cancel-summary { margin: 4px 0 18px; padding: 10px 14px; border: 1px solid var(--c-line); border-radius: 4px; background: var(--c-sunk); }
.bill-cancel-summary > div { display: flex; justify-content: space-between; gap: 18px; padding: 7px 0; font-size: 12px; line-height: 1.6; }
.bill-cancel-summary dt { color: var(--c-ink-3); }
.bill-cancel-summary dd { margin: 0; text-align: right; font-variant-numeric: tabular-nums; }
.bill-cancel-effects { padding-left: 18px; margin: 0 0 16px; font-size: 13px; line-height: 1.7; }
.bill-cancel-effects li + li { margin-top: 7px; }
button.bill-cancel-confirm { background: var(--c-alarm-ink); color: var(--c-surface); border-color: var(--c-alarm-ink); }
.bill-cancel-success { justify-content: center; align-items: center; text-align: center; padding: 12px 0 0; animation: bill-confirm-in 180ms ease-out; }
.bill-success-icon { display: grid; place-items: center; width: 56px; height: 56px; border: 1px solid var(--c-line); border-radius: 50%; margin-bottom: 24px; color: var(--c-ink); background: var(--c-sunk); }
.bill-success-icon svg { width: 28px; height: 28px; }
.bill-cancel-success .modal-acts { width: 100%; }
.bill-cancel-success .modal-acts button { width: 100%; }
@keyframes bill-confirm-in { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } }
@media (prefers-reduced-motion: reduce) { .bill-cancel-success { animation: none; } }
@media (max-width: 520px) {
  .bill-cancelled summary { flex-wrap: wrap; padding: 16px; }
  .bill-receipt-link { margin-left: 44px; }
  .bill-cancel-step select, .bill-cancel-step textarea { font-size: 16px; }
  .bill-cancel-step .modal-acts { flex-wrap: wrap; }
}
.bill-overview[hidden], .bill-payments[hidden] { display: none; }
.bill-overview .card { margin: 0; padding: 24px; }
.bill-card-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 18px; }
.bill-eyebrow { font: 11px/1.4 var(--mono); letter-spacing: .08em; color: var(--c-ink-3); }
.bill-plan h2 { margin: 0 0 9px; font-size: 24px; line-height: 1.2; letter-spacing: -.035em; font-weight: 600; }
.bill-rate { margin: 0 0 6px; font-size: 16px; font-weight: 500; font-variant-numeric: tabular-nums; }
.bill-meta { margin: 0; font-size: 12px; line-height: 1.6; color: var(--c-ink-3); }
.bill-pricing-detail { display: grid; gap: 5px; margin-top: 14px; }
.bill-pricing-detail .workspace-switch { margin-top: 7px; }
.bill-pricing-detail [hidden] { display: none; }
.bill-plan-facts { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 18px; margin: 24px 0; padding-top: 20px; border-top: 1px solid var(--c-line); }
.bill-plan-facts dt { margin-bottom: 7px; color: var(--c-ink-3); font-size: 11px; }
.bill-plan-facts dd { margin: 0; font-size: 14px; font-weight: 600; font-variant-numeric: tabular-nums; }
.bill-plan-foot { display: flex; align-items: center; justify-content: space-between; gap: 18px; }
.bill-plan-foot p { max-width: 330px; }
button.bill-cancel { flex: 0 0 auto; padding: 8px 0 8px 12px; font-size: 12px; background: transparent; border: 0; color: var(--c-ink-3); }
@media (hover: hover) { button.bill-cancel:hover { color: var(--c-alarm-ink); background: transparent; } }
.bill-wallet { display: flex; flex-direction: column; background: var(--c-sunk); }
.bill-balance { display: block; margin: 1px 0 9px; font-size: clamp(28px, 3.4vw, 40px); line-height: 1.1; letter-spacing: -.055em; font-weight: 500; font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }
.bill-wallet-bottom { margin-top: auto; padding-top: 24px; }
.bill-wallet-bottom button { display: flex; justify-content: space-between; align-items: center; width: 100%; margin-top: 14px; }
.bill-contact { display: block; margin-top: 12px; font-size: 12px; line-height: 1.5; color: var(--c-ink); text-underline-offset: 3px; }
.bill-contact[hidden] { display: none; }
.bill-feedback:not([hidden]), .bill-load-error:not([hidden]) { margin: 16px 0; padding: 12px 16px; border: 1px solid var(--c-line); background: var(--c-sunk); font-size: 13px; line-height: 1.6; }
.bill-load-error { display: flex; align-items: center; gap: 16px; }
.bill-load-error[hidden] { display: none; }
.bill-load-error p { margin: 0; }
.bill-form-error { color: var(--c-alarm-ink); }
.bill-topup-dialog { --modal-w: 450px; }
.bill-topup-dialog input { font-size: 24px; font-variant-numeric: tabular-nums; padding: 10px 12px; min-height: 48px; }
.bill-amount-label { display: block; margin: 4px 0 8px; }
.bill-amount-hint { margin-top: 8px; }
.bill-checkout-total { margin: 24px 0 0; }
.bill-checkout-total > div { display: flex; justify-content: space-between; gap: 16px; padding: 7px 0; font-size: 13px; }
.bill-checkout-total dt { color: var(--c-ink-3); }
.bill-checkout-total dd { margin: 0; font-variant-numeric: tabular-nums; }
.bill-checkout-total .bill-payable { border-top: 1px solid var(--c-line); margin-top: 10px; padding-top: 15px; font-weight: 600; }
.bill-payable dt { color: var(--c-ink); }
.bill-topup-dialog .modal-acts button { width: 100%; min-height: 40px; }
@media (max-width: 1050px) {
  .bill-overview { grid-template-columns: minmax(0, 1fr); }
  .bill-plan-foot { flex-wrap: wrap; gap: 10px; }
  button.bill-cancel { padding-left: 0; }
}
@media (max-width: 520px) {
  .bill-overview .card { padding: 18px; }
  .bill-plan-facts { grid-template-columns: minmax(0, 1fr); gap: 12px; }
  .bill-plan-facts > div { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
  .bill-plan-facts dt { margin: 0; }
}
.use-head { display: flex; align-items: baseline; justify-content: space-between;
            gap: 10px; }
.use-range { display: flex; gap: 4px; }
.use-r { background: none; border: 1px solid var(--c-line-strong);
         color: var(--c-ink-3); cursor: pointer; border-radius: 3px;
         padding: 2px 8px; font-size: 11px; line-height: 1.4; }
.use-r:hover { color: var(--c-ink); transform: none; }
.use-r.is-on { color: var(--accent); border-color: var(--accent-line);
               background: var(--accent-bg); }
.use-note { font-size: 11.5px; color: var(--c-ink-3); line-height: 1.5;
            margin: 4px 0 10px; max-width: 62ch; }
.use-row { display: grid; align-items: baseline; gap: 8px;
           grid-template-columns: minmax(0, 2.2fr) repeat(6, minmax(0, 1fr))
                                  minmax(0, 1.2fr) minmax(0, 1.1fr);
           padding: 8px 0; border-top: 1px solid var(--c-line); font-size: 12px; }
.use-hd { border-top: none; font-size: 10px; letter-spacing: 0.06em;
          text-transform: uppercase; color: var(--c-ink-3); padding-bottom: 2px; }
.use-hd span + span, .use-row .u-n, .use-row .u-ago { text-align: right; }
.use-row .u-n { font-variant-numeric: tabular-nums; color: var(--c-ink); }
.use-row .u-ago { color: var(--c-ink-3); font-size: 11px; white-space: nowrap; }
.use-row .u-who { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
/* A button, not a span with a click handler: renaming is an action, and it has
   to be reachable from a keyboard like every other action on this page. */
.u-name { background: none; border: none; padding: 0; text-align: left;
          font: inherit; font-weight: 600; color: var(--c-ink); cursor: pointer;
          border-bottom: 1px dashed transparent; align-self: start; }
.u-name:hover { color: var(--accent); border-bottom-color: var(--accent-line);
                background: none; transform: none; }
.u-keys { font-family: var(--mono); font-size: 10.5px; color: var(--c-ink-3);
          overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.u-err { font-size: 10.5px; color: var(--c-alarm-ink); }
/* The concurrency ceiling, editable where it is read. A button rather than a
   number with a click handler, for the reason .u-name is one: changing a
   company's limit is an action and has to be reachable from a keyboard. */
.use-row .u-cap { text-align: right; }
.u-capval { background: none; border: 0; padding: 0; font: inherit;
            font-variant-numeric: tabular-nums; color: var(--c-ink);
            cursor: pointer; border-bottom: 1px dashed var(--c-line-strong); }
.u-capval:hover { color: var(--accent); border-bottom-color: var(--accent-line);
                  background: none; transform: none; }
.u-capedit { width: 4.5em; padding: 2px 4px; font: inherit;
             font-variant-numeric: tabular-nums; text-align: right; }
.u-capsave, .u-capreset { padding: 2px 6px; margin-left: 4px; font-size: 10.5px;
                          line-height: 1.5; }
.u-capreset { background: none; border: 1px solid var(--c-line-strong);
              color: var(--c-ink-2); }
.u-capreset[disabled] { opacity: 0.45; cursor: not-allowed; }
/* The requests card's company filter. One row, so the label and the select
   read as one control rather than as a heading above a menu. */
.use-filter { display: flex; align-items: center; gap: 8px; margin: 4px 0 12px;
              font-size: 11.5px; color: var(--c-ink-3); }
.use-filter select { font-size: 12px; padding: 3px 6px; max-width: 22em; }
.use-filter label + select { margin-right: 6px; }
/* The admin log's own column. Ellipsised rather than wrapped: a long brand
   name must not make one row twice the height of its neighbours. */
.tts-reqws { max-width: 14em; overflow: hidden; text-overflow: ellipsis;
             white-space: nowrap; font-weight: 500; color: var(--c-ink); }
.use-tot { border-top: 1px solid var(--c-line-strong); font-weight: 600; }
.use-empty { font-size: 12.5px; color: var(--c-ink-3); padding: 14px 4px; }
/* On a phone the eight columns become two: the company, and its numbers under
   it as labelled pairs. Anything narrower than this and the counts wrap
   mid-figure, which is the one way a number can lie. */
@media (max-width: 720px) {
  .use-row { grid-template-columns: 1fr auto auto; }
  .use-row .u-cap { text-align: left; }
  .use-row .u-who { grid-column: 1 / -1; }
  .use-hd { display: none; }
}

/* ── impersonation bar ────────────────────────────────────────────────────
 * Amber, because in this palette amber is "unresolved" and a session that is
 * an admin wearing a customer's workspace is the definition of unresolved. It
 * carries the ring as well as the hue (rule 2): meaning is never in colour
 * alone, and this is the one state on the page that must not be missed. */
.impbar { position: sticky; top: 0; z-index: 60;
          display: flex; align-items: center; gap: 9px;
          padding: 7px clamp(12px, 2vw, 24px);
          background: var(--c-open-bg);
          border-bottom: 1px solid var(--c-open-line);
          color: var(--c-open-ink); font-size: 12px; }
.impbar .imp-dot { width: 8px; height: 8px; border-radius: 50%;
                   border: 2px solid var(--c-open); flex: none; }
.impbar .imp-text { min-width: 0; }
.impbar b { font-weight: 600; }
.impbar button { margin-left: auto; flex: none;
                 background: var(--c-surface); color: var(--c-open-ink);
                 border: 1px solid var(--c-open-line); border-radius: 3px;
                 padding: 3px 11px; font-size: 11.5px; cursor: pointer; }
.impbar button:hover { background: var(--c-open-bg); transform: none; }
.impbar button:disabled { opacity: .55; cursor: default; }

/* ── a campaign that is somebody else's wave ──────────────────────────────
 * Its detail pane says so once, under the name. The list row's demotion is
 * with the campaign list (.camp-list-row.cmp-wave). */
.cmp-wavenote { margin: -2px 0 10px; }


/* ── developers view ──────────────────────────────────────────────────────
 * Four cards in a two-column grid: keys and workspace on top, the two things
 * you copy underneath at full width, because a wrapped shell command is a
 * shell command someone pastes wrong.
 *
 * The secret reveal is the only place in this build that draws attention to
 * itself, and it earns it: the key is on screen once and never again. It uses
 * `open` amber with the hatch — unresolved, "you still have to do something
 * with this" — never alarm red, which belongs to a thing that broke. */
/* Two sub-tabs — APIs, SDK — flipped client-side by dev.js. There were three;
 * TTS is a view of its own now, because it is a product with a price rather
 * than a section of the page about credentials. Built from .howrow/.howtab,
 * which the comment above them calls a generic switch: one hairline box split
 * into equal parts, the selected part
 * carrying weight AND ink AND the paper. Nothing new is invented here.
 *
 * They shipped as a .chip strip and it was wrong: three small pills floating
 * above a grid of cards read as filters ON those cards rather than as the
 * choice of which page you are looking at. Edge to edge, they cannot.
 *
 * `.howrow.devrow`, not `.devrow` — the base .howrow is declared later in this
 * file, so at equal specificity it would win and this rule's margin would be
 * lost (the test panel's old tier strip spent a release wrapped onto two rows
 * that way). Do not simplify it, and do not delete the column count just
 * because it happens to match the base again today.
 *
 * Hidden panels use [hidden], but .dev-grid sets `display: grid`, which beats
 * the UA's `display: none` on [hidden] — so it has to be said again here. */
.howrow.devrow { grid-template-columns: repeat(2, 1fr); margin: 0 0 12px; }
.devrow .howtab { min-height: 42px; padding: 8px 4px; line-height: 1.2;
                  white-space: nowrap; }
.devrow .howtab b { font-size: 13px; font-weight: 600; letter-spacing: 0.01em; }
.dev-panel[hidden] { display: none; }

.dev-grid { display: grid; grid-template-columns: minmax(0, 1fr) 300px;
            gap: 12px; align-items: start; }
.dev-wide { grid-column: 1 / -1; }
.dev-cardhead { display: flex; align-items: baseline; justify-content: space-between;
                gap: 10px; }
.dev-ws { font-family: var(--mono); font-size: 11px; color: var(--c-ink-3);
          user-select: all; }
.dev-help { color: var(--c-ink-3); font-size: 12px; margin: 6px 0 0; }
.dev-help code { font-family: var(--mono); font-size: 11.5px;
                 color: var(--c-ink-2); background: var(--c-sunk);
                 padding: 1px 4px; border-radius: var(--r-xs); }
.dev-empty { color: var(--c-ink-3); font-size: 12.5px; padding: 14px 2px; }

.dev-mintrow { display: flex; gap: 8px; margin-top: 12px; }
.dev-mintrow input { flex: 1; min-width: 0; }
.dev-mintrow button { white-space: nowrap; }

.dev-reveal { margin-top: 12px; padding: 11px 12px; border-radius: var(--r-sm);
              background: var(--c-open-bg); border: 1px solid var(--c-open-line); }
.dev-revealhead { font-size: 12px; color: var(--c-open-ink); }
.dev-revealhead::before {
  content: ''; display: inline-block; width: 7px; height: 7px; border-radius: 1px;
  margin-right: 6px; border: 1px solid var(--c-open-line);
  background-color: var(--c-surface);
  background-image: repeating-linear-gradient(45deg,
    var(--c-open-hatch) 0 3px, transparent 3px 7px);
}
.dev-secret { display: flex; align-items: center; gap: 8px; margin-top: 9px; }
.dev-seclabel { font-size: 10.5px; letter-spacing: 0.06em; text-transform: uppercase;
                color: var(--c-ink-3); flex: 0 0 96px; }
.dev-secret code { flex: 1; min-width: 0; font-family: var(--mono);
                   font-size: 12px; color: var(--c-ink); user-select: all;
                   word-break: break-all; }

.dev-fact { display: flex; justify-content: space-between; align-items: baseline;
            gap: 10px; padding: 7px 0; border-top: 1px solid var(--c-line);
            font-size: 12px; }
.dev-fact:first-of-type { border-top: none; }
.dev-fact span { color: var(--c-ink-3); }
.dev-fact b { color: var(--c-ink); font-weight: 600; }
.dev-fact b.dev-mono { font-family: var(--mono); font-size: 11px; font-weight: 500;
                       word-break: break-all; text-align: right; }
.dev-docs { display: inline-flex; align-items: center; gap: 7px; margin-top: 12px;
            font-size: 12px; font-weight: 600; color: var(--c-ink);
            text-decoration: none; }
.dev-docs svg { width: 15px; height: 15px; color: var(--c-ink-3); }
.dev-docs:hover { color: var(--accent); }
.dev-docs:hover svg { color: var(--accent); }

/* The two Postman files. A download is a file, so the row is shaped like one:
 * the filename in mono at the top and the sentence that says what it is
 * underneath — the same statement/description stacking .dev-keyrow uses, so a
 * key and a file read as the same kind of listed thing. */
.dev-dl { display: flex; flex-direction: column; gap: 2px; margin-top: 10px;
          padding: 10px 12px; border: 1px solid var(--c-line);
          border-radius: var(--r-sm); background: var(--c-sunk);
          text-decoration: none; }
.dev-dlname { font-family: var(--mono); font-size: 12px; color: var(--c-ink);
              font-weight: 500; word-break: break-all; }
.dev-dlname::after { content: " ↓"; color: var(--c-ink-3); }
.dev-dlnote { font-size: 11.5px; line-height: 1.45; color: var(--c-ink-3); }
@media (hover: hover) and (pointer: fine) {
  .dev-dl:hover { border-color: var(--c-ink-4); background: var(--c-surface); }
  .dev-dl:hover .dev-dlname::after { color: var(--accent); }
}

/* The copy blocks. The card does not scroll — the code inside it does. Put the
 * overflow on the wrapper instead and the Copy button, being positioned
 * against it, slides out of view the moment a wide line is scrolled. Content
 * never wraps: a soft-wrapped snippet reads as broken code, and this one is
 * meant to run exactly as pasted. */
.dev-cmd { position: relative; margin-top: 10px; padding: 10px 12px;
           background: var(--c-dark); border-radius: var(--r-sm);
           font-family: var(--mono); font-size: 12px; color: var(--c-on-dark);
           overflow: hidden; }
.dev-cmd code, .dev-cmd pre {
  display: block; margin: 0; padding-right: 62px; overflow-x: auto;
  white-space: pre; line-height: 1.55; color: var(--c-on-dark);
  user-select: all;
  scrollbar-width: thin; scrollbar-color: var(--c-dark-line) transparent; }
.dev-copy { position: absolute; top: 8px; right: 8px; z-index: 1;
            background: var(--c-dark-sunk); color: var(--c-on-dark-2);
            border: 1px solid var(--c-dark-line); border-radius: var(--r-xs);
            font-size: 11px; padding: 4px 9px; cursor: pointer; }
.dev-copy:hover { background: var(--c-dark-line); color: var(--c-on-dark);
                  transform: none; }
.dev-copy.done { color: var(--accent-on-dark); }

.dev-keyrow { display: flex; flex-direction: column; gap: 3px; padding: 10px 0;
              border-top: 1px solid var(--c-line); font-size: 12px; }
.dev-keyrow .k-top { display: flex; justify-content: space-between;
                     align-items: center; gap: 8px; }
.dev-keyrow .k-label { font-weight: 600; color: var(--c-ink); }
.dev-keyrow .k-side { display: flex; align-items: center; gap: 4px; }
.dev-keyrow .k-bot { display: flex; justify-content: space-between;
                     align-items: baseline; gap: 8px; }
.dev-keyrow .k-mask { font-family: var(--mono); font-size: 11.5px;
                      color: var(--c-ink-2); }
.dev-keyrow .k-when { color: var(--c-ink-3); font-size: 11px; white-space: nowrap; }
/* A revoked key is kept on screen — it is the record of a rotation — but it is
 * struck through and dimmed so it can never be mistaken for a live one. */
.dev-keyrow.gone .k-label, .dev-keyrow.gone .k-mask {
  color: var(--c-ink-3); text-decoration: line-through; }
.k-act { background: none; border: 1px solid var(--c-line-strong);
         border-radius: var(--r-xs); color: var(--c-ink-2);
         font-size: 11px; padding: 3px 8px; cursor: pointer; }
.k-act:hover { background: var(--c-sunk); color: var(--c-ink); transform: none; }
.k-act.danger:hover { color: var(--c-alarm-ink); background: var(--c-alarm-bg);
                      border-color: var(--c-alarm-line); }

/* The card that says the synthesiser moved out. It is a ROW, not another
 * stacked card: it is a signpost, and a signpost that looks like the cards
 * around it gets read as more of the same content. The button is a real
 * button because core.js routes [data-view] through navTo() — an <a href> here
 * would reload the whole console to reach a view that is one swap away. */
.dev-pointer { display: flex; align-items: center; justify-content: space-between;
               gap: 14px; flex-wrap: wrap; }
.dev-pointerbody { flex: 1 1 260px; min-width: 0; }
.dev-pointergo { display: inline-flex; align-items: center; gap: 7px;
                 flex: 0 0 auto; white-space: nowrap; }
.dev-pointergo svg { width: 15px; height: 15px; }

/* ── text to speech view ──────────────────────────────────────────────────
 * The synthesiser sold as itself. Everything in here was either moved out of
 * the developers section above — the playground, renamed out of the `dev`
 * namespace on the way — or is new furniture for the four sections that were
 * built around it: the facts strip, the quickstart switch and the languages
 * promise. The catalogue borrows .sc-item from the gate wholesale and adds
 * nothing, which is the point of having a row pattern.
 *
 * One column, deliberately. .dev-grid's 300px sidebar exists because the
 * Developers page has a genuine aside (the wallet); this page is an argument
 * read top to bottom and a second column would let the eye skip the order it
 * is being made in. */
.tts-grid { display: flex; flex-direction: column; gap: 12px; }

/* The three things you need before you can send a request. Same number-over-
 * label shape as the measurements below, and that repetition is intentional:
 * they are the same kind of statement, and one of them is measured live a card
 * later.
 *
 * Nothing in here is tinted. The price used to lead this strip in accent ink,
 * and accent ink on the one placeholder figure among three settled facts read
 * as the commitment on the page. The price is gone; the emphasis went with
 * it, rather than being handed to whichever fact happened to be first. */
.tts-facts { display: flex; flex-wrap: wrap; gap: 10px 26px; margin: -8px 0 16px;
             padding: 11px 14px; border: 1px solid var(--c-line);
             border-radius: var(--r-md); background: var(--c-surface); }
.tts-facts span { display: flex; flex-direction: column; gap: 2px; }
.tts-facts b { font-family: var(--mono); font-size: 14px; font-weight: 600;
               color: var(--c-ink); font-variant-numeric: tabular-nums; }
.tts-facts i { font-style: normal; font-size: 11px; color: var(--c-ink-3); }

/* The playground. One row under the textarea: which voice says it, the button
 * that says it, then what you can do with what it said — left to right, in the
 * order the thing actually happens.
 *
 * The textarea is the point of the card and is sized like it: a paragraph of
 * Devanagari fits without scrolling, because a three-row box invites a
 * three-word demo and three words is exactly the input that makes our own
 * synthesiser sound bad. `resize: vertical` only — a horizontally resizable
 * box inside a grid column can push the card past its own gutter. */
.tts-text { width: 100%; margin-top: 12px; resize: vertical;
            min-height: 140px; font-family: inherit; line-height: 1.5; }
.tts-bar { display: flex; align-items: center; gap: 8px; margin-top: 10px;
           flex-wrap: wrap; }
.tts-bar select { flex: 0 0 auto; }
.tts-bar button { white-space: nowrap; }
/* The word-count nudge, in the row rather than under it: it is the reason the
 * button beside it is disabled, so it has to sit where the eye already is. */
.tts-hint { flex: 1 1 160px; min-width: 0; font-size: 11.5px;
            color: var(--c-ink-3); }

/* Starter lines, ABOVE the box rather than below it: they are what you press
 * instead of typing, so they have to be seen before the empty textarea has
 * been stared at, not offered as a consolation afterwards. Wraps rather than
 * scrolls — a horizontal scroller hides the last option, and the last option
 * here is the long sentence that shows prosody. */
.tts-pills { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 12px; }
.tts-pills .chip { cursor: pointer; }

/* The measurements. A row of number-over-label pairs, mono for the figures so
 * a run that is 40 ms slower does not also re-flow the row and look like a
 * different layout. Wraps to two lines on a phone; nothing is truncated,
 * because a half-shown latency figure is worse than a wrapped one. */
.tts-stats { display: flex; flex-wrap: wrap; gap: 8px 22px; margin-top: 12px;
             padding: 10px 12px; border: 1px solid var(--c-line);
             border-radius: var(--r-sm); background: var(--c-surface); }
.tts-stats span { display: flex; flex-direction: column; gap: 1px; }
.tts-stats b { font-family: var(--mono); font-size: 13px; font-weight: 600;
               color: var(--c-ink); font-variant-numeric: tabular-nums; }
.tts-stats i { font-style: normal; font-size: 11px; color: var(--c-ink-3); }
/* The headline of the five. Everything else is context for this one. */
.tts-stats span:first-child b { color: var(--accent); }

/* The quickstart switch. Three columns, and `.howrow.qsrow` rather than
 * `.qsrow` for exactly the reason .devrow documents: the base
 * .howrow is declared later in this file and would win at equal specificity,
 * wrapping the third tab onto its own row. The labels are code (`curl`,
 * `POST /v2/tts`), so they are allowed to be mono and they are not allowed to
 * wrap — a route name broken across two lines stops being a route name. */
.howrow.qsrow { grid-template-columns: repeat(3, 1fr); margin: 12px 0; }
.qsrow .howtab { min-height: 42px; padding: 8px 4px; line-height: 1.2;
                 white-space: nowrap; }
.qsrow .howtab b { font-family: var(--mono); font-size: 12px; font-weight: 600;
                   letter-spacing: 0; }

/* What is live and what is coming, as two rows of different weight rather than
 * two sections. The live ones are cards: the solid mark AND the word "live"
 * AND the ink, never colour alone (rule 2), so the state survives greyscale
 * and a half-loaded stylesheet. `flex: 1 1 180px` puts the three of them on
 * one line on a desktop and stacks them cleanly when there is not room.
 *
 * The six in evaluation are NAMES ONLY, at label size, on one line. They are
 * announced — the docs page lists them — so printing them is honest; giving
 * them the same card as a language you can call today would not be. No dates,
 * on purpose and as policy: each is announced when it clears evaluation. */
.tts-langs { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
.tts-lang { display: flex; flex-direction: column; gap: 3px; flex: 1 1 180px;
            padding: 10px 12px; border: 1px solid var(--c-line);
            border-radius: var(--r-sm); background: var(--c-sunk); }
.tts-lang b { font-size: 12.5px; font-weight: 600; color: var(--c-ink-3); }
.tts-lang i { font-style: normal; font-size: 11.5px; line-height: 1.45;
              color: var(--c-ink-3); }
.tts-lang.on { background: var(--c-surface); border-color: var(--c-line-strong); }
.tts-lang.on b { color: var(--c-ink); }
.tts-lang.on b::before {
  content: ''; display: inline-block; width: 7px; height: 7px; border-radius: 1px;
  margin-right: 6px; background: var(--c-ink);
}

.tts-soon { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 8px;
            margin-top: 10px; padding-top: 10px;
            border-top: 1px solid var(--c-line); }
/* The label is the same eyebrow every other section header in the build uses,
 * so the row reads as a list with a heading rather than as seven equal chips
 * one of which happens to be English. */
.tts-soon b { font-size: 10px; letter-spacing: 0.08em; text-transform: uppercase;
              font-weight: 600; line-height: 1; color: var(--c-ink-3);
              margin-right: 2px; }
/* Hollow, not filled: the live cards above carry a solid mark, and these carry
 * the absence of one. Same distinction, one glance apart. */
.tts-soon span { font-size: 11.5px; line-height: 1; color: var(--c-ink-2);
                 padding: 5px 8px; border-radius: var(--r-xs);
                 border: 1px dashed var(--c-line-strong); }

/* ── language model view ──────────────────────────────────────────────────
 * THE CHAT IS THE PAGE. Everything else on this view is arranged around that
 * one decision, and the decision is not decoration: a synthesiser proves
 * itself in two seconds and a language model does not, so the control that
 * does the proving has to be big enough to keep somebody in it.
 *
 * The frame is the one every model page shares — .model-bench / .model-main /
 * .model-side in models.css: the chat on the left, one sticky rail of settings
 * on the right, stacked in the same order when the page is too narrow. What
 * follows is only what is particular to a conversation. */

/* The conversation column. Height from the VIEWPORT, not from the content —
 * that is what stops the composer wandering down the page as the transcript
 * grows, which is the single thing that makes a chat feel like a chat rather
 * than a form with a log under it. clamp() so it cannot collapse on a laptop
 * in a short window or run away on a 4K display; the same shape #campreport
 * uses, and the same --vvh arithmetic on a phone further down.
 *
 * `padding: 0` and the parts inset themselves: the transcript needs to scroll
 * edge to edge under the footer, and a card's own padding would leave a strip
 * of paper above the scrollbar that reads as a rendering bug. */
.llm-chatcol { display: flex; flex-direction: column; padding: 0;
               height: clamp(460px, calc(100dvh - 232px), 780px);
               overflow: hidden; }

/* The transcript. The ONLY thing that grows; min-height: 0 because a flex
 * child will not shrink below its content without it and the scrollbar would
 * end up on the page instead of in here. */
.llm-chat { flex: 1 1 auto; min-height: 0; overflow-y: auto;
            display: flex; flex-direction: column; gap: 12px;
            padding: 14px 16px; scrollbar-width: thin; }

/* The empty state. One line of instruction and the page's own glyph, greyed —
 * it is a prompt, not a placeholder graphic, and it says the one thing a
 * visitor needs to know before typing (that the tools are armed). */
.llm-empty { margin: auto; display: flex; flex-direction: column;
             align-items: center; text-align: center; gap: 7px;
             max-width: 44ch; padding: 12px 0; color: var(--c-ink-3); }
.llm-empty svg { width: 26px; height: 26px; color: var(--c-line-strong); }
.llm-empty b { font-size: 13px; font-weight: 600; color: var(--c-ink-2); }
.llm-empty span { font-size: 11.5px; line-height: 1.55; }

/* A turn: the speaker's name above, the bubble under it. The row is the flex
 * column that aligns — the model's to the left, yours to the right — and the
 * bubble itself (fill, radius, ink, entrance) is drawn in models.css. The
 * classes are .is-bot / .is-user, NOT .bot / .user: those are the test panel's
 * global bubbles, and inheriting them is how this transcript once drew the
 * model's reply as a square grey box. */
.llm-msg { display: flex; flex-direction: column; align-items: flex-start;
           max-width: 82%; }
.llm-msg.is-user { align-self: flex-end; align-items: flex-end; }
.llm-msg.is-bot { align-self: flex-start; }
.llm-who { font-size: 11.5px; line-height: 1.2; color: var(--c-ink-3); }
.llm-msg.is-bot .llm-who { color: var(--accent); }
.llm-body { margin: 0; font-size: 14px; line-height: 1.6; color: var(--c-ink);
            overflow-wrap: anywhere; }
/* Typed text keeps its own line breaks; the model's are drawn by llmRender. */
.llm-msg.is-user .llm-body { white-space: pre-wrap; }

/* The footer: the flex item that does NOT grow. Its top hairline is the only
 * thing separating it from the transcript, so a message scrolled to the bottom
 * meets an edge rather than dissolving into the composer. */
.llm-foot { flex: 0 0 auto; padding: 10px 16px 14px;
            border-top: 1px solid var(--c-line); background: var(--c-surface); }
.llm-statrow { margin: 0 0 8px; }
/* The explanation, as a disclosure. Four lines of standing prose inside a chat
 * window is four lines the conversation does not get. */
.llm-note { margin: 0 0 8px; }
.llm-note summary { font-size: 11px; color: var(--c-ink-3); cursor: pointer;
                    user-select: none; }
.llm-note summary:hover { color: var(--accent); }
.llm-note .dev-help { margin-top: 6px; }
.llm-starters { margin: 0 0 9px; }
.llm-input { width: 100%; resize: none; min-height: 58px; max-height: 160px;
             font-family: inherit; font-size: 13px; line-height: 1.5; }
/* Send on the right, where the eye ends: the hint takes the slack so the two
 * buttons stay put as it appears and disappears. */
.llm-send { display: flex; align-items: center; gap: 8px; margin-top: 8px; }
.llm-send .tts-hint { flex: 1 1 auto; }
.llm-send button { white-space: nowrap; }

/* ── the settings rail ─────────────────────────────────────────────────────
 * Beside the chat and never in front of it: one card, three sections split by
 * hairlines (models.css). Flat tool rows with a switch, not a stack of boxed
 * cards — these are the second thing anybody touches, and the rail should
 * read as quiet next to the conversation. */
.llm-sysbox { width: 100%; resize: vertical; min-height: 0;
              font-family: inherit; font-size: 13px; line-height: 1.5; }

/* ── a tool call, as it happens ────────────────────────────────────────────
 * The centrepiece of the transcript, so it is the one thing in it that is
 * drawn rather than typeset: a bordered block with the function, the arguments
 * the model extracted, and what went back. The left edge is a 2px rule in ink
 * — the same "this is a step, not a message" signal the campaign report uses.
 *
 * "simulated" is a WORD on the chip rather than a footnote under the card,
 * because this is the exact spot where somebody screenshots the page. */
.llm-tool-call { border: 1px solid var(--ui-sep);
                 border-left: 2px solid var(--c-ink);
                 border-radius: var(--ui-r-card); background: var(--c-surface);
                 padding: 10px 12px; }
.llm-tc-head { display: flex; align-items: baseline; justify-content: space-between;
               gap: 10px; }
.llm-tc-fn { font-family: var(--mono); font-size: 12.5px; font-weight: 600;
             color: var(--c-ink); }
.llm-tc-tag { font-size: 9.5px; letter-spacing: 0.06em; text-transform: uppercase;
              font-weight: 600; line-height: 1; padding: 3px 5px;
              border-radius: var(--r-xs); color: var(--c-ink-3);
              border: 1px dashed var(--c-line-strong); white-space: nowrap; }
.llm-tc-args { margin: 7px 0 0; font-family: var(--mono); font-size: 11.5px;
               line-height: 1.5; color: var(--c-ink-2); white-space: pre-wrap;
               overflow-wrap: anywhere; }
/* Arguments that would not parse. A real failure mode, shown rather than
 * hidden — and marked on two axes, since colour alone is not a state. */
.llm-tc-args.bad { color: var(--c-alarm-ink); text-decoration: underline wavy; }
.llm-tc-result { margin-top: 9px; padding-top: 8px;
                 border-top: 1px solid var(--c-line); }
.llm-tc-rlabel { font-size: 10px; letter-spacing: 0.08em; text-transform: uppercase;
                 font-weight: 600; line-height: 1; color: var(--c-ink-3); }

/* The disclosure. For this audience the wire IS the product, so the raw JSON
 * is one click from every call — closed by default because it is the second
 * question, never the first. Dark, like every other code block in the build. */
.llm-wire { margin-top: 9px; }
.llm-wire summary { font-size: 11px; color: var(--c-ink-3); cursor: pointer;
                    user-select: none; }
.llm-wire summary:hover { color: var(--accent); }
.llm-wirelab { margin-top: 8px; font-size: 10px; letter-spacing: 0.08em;
               text-transform: uppercase; font-weight: 600; line-height: 1;
               color: var(--c-ink-3); }
.llm-wirepre { margin: 5px 0 0; padding: 9px 11px; border-radius: var(--r-sm);
               background: var(--c-dark); color: var(--c-on-dark);
               font-family: var(--mono); font-size: 11px; line-height: 1.5;
               overflow-x: auto; white-space: pre;
               scrollbar-width: thin;
               scrollbar-color: var(--c-dark-line) transparent; }

/* ── GPU banner ───────────────────────────────────────────────────────────
 * The fleet being off is UNRESOLVED, not broken — it is a node to bring back,
 * not a call that failed — so it is `open` amber with the hatch, never `alarm`
 * red. Red belongs to a call that failed.
 *
 * The amber used to be justified by there still being a vendor-voiced tier the
 * sandbox could serve without a GPU. That tier is gone (owner, 2026-08-20) and
 * a dark fleet now means no tier can speak, which is a stronger statement than
 * this banner makes. Left amber deliberately: the colour is a product call, and
 * the sentence the banner prints already says every tier is down.
 *
 * It sits first in the document and is sticky rather than fixed, so when it is
 * shown it takes its own row instead of covering the page heading.
 *
 * NO LAYOUT JUMP. It used to toggle `display`, which snapped the whole page
 * down 33px on a poll that runs every thirty seconds. It is now always in the
 * flow with a collapsed 0fr row, so showing it is one 220ms settle and hiding
 * it is the same in reverse. That transition fires on a state change only —
 * nothing here loops, per rule 3. The clip/in/row nesting is what makes the
 * amber ground and the bottom hairline collapse WITH the height instead of
 * leaving a 1px band across the top of an otherwise fine console.
 *
 * The copy is left-aligned to the app grid rather than centred: a centred
 * sentence spanning 1700px is a marketing banner, and this is an instrument
 * telling you one fact about your session. It carries the ▨ hatch mark — the
 * same one the response chart uses for "unresolved" — instead of the ⚠️ emoji
 * it used to open with. */
#gpu-status-banner {
  position: sticky; top: 0; z-index: 60;
  display: grid; grid-template-rows: 0fr;
  transition: grid-template-rows 220ms var(--ease-out);
}
#gpu-status-banner.on { grid-template-rows: 1fr; }
/* RULE 3, the arrival case. The fleet is probed after first paint, so without
 * this the banner SETTLES OPEN in front of someone who has just loaded the
 * page — the console performing its own state instead of showing it. `.instant`
 * is on the element in the markup and is dropped one frame after the first
 * probe lands, so the opening state is painted and every state change after it
 * is the 220ms settle. */
#gpu-status-banner.instant,
#gpu-status-banner.instant .gpu-banner-in { transition: none; }
.gpu-banner-clip { min-height: 0; overflow: hidden; }
/* A 0fr row is invisible but NOT gone: without this the mailto link inside it
 * stays in the tab order and in the accessibility tree, so the first Tab on a
 * healthy console lands on a link about a problem nobody can see. `visibility`
 * removes it from both. The delay holds it visible for the length of the
 * collapse so the text does not vanish before the row finishes closing —
 * a discrete step at the end of the settle, not a second animation. */
.gpu-banner-in { background: var(--c-open-bg);
                 border-bottom: 1px solid var(--c-open-line);
                 padding: calc(9px + env(safe-area-inset-top)) 0 9px;
                 visibility: hidden;
                 transition: visibility 0s linear 220ms; }
#gpu-status-banner.on .gpu-banner-in { visibility: visible;
                                       transition-delay: 0s; }
.gpu-banner-row { max-width: 1760px; margin: 0 auto;
                  padding: 0 clamp(12px, 2vw, 24px);
                  font-size: 12px; line-height: 1.6; color: var(--c-ink-2); }
.gpu-banner-row b { font-weight: 600; color: var(--c-open-ink); }
.gpu-banner-row a { color: var(--c-open-ink); font-weight: 500;
                    text-decoration: underline; text-underline-offset: 3px;
                    white-space: nowrap; }
@media (prefers-reduced-motion: reduce) {
  #gpu-status-banner { transition: none; }
  .gpu-banner-in { transition: none; }
}

/* ══ ACCESS GATE ═══════════════════════════════════════════════════════════
 * Two halves, one job each. LEFT is the dark editorial surface the founder's
 * note already uses on the Welcome view — same values, so the two screens read
 * as one product rather than as a landing page bolted onto a console. RIGHT is
 * paper, and it carries the sign-in card for access codes and email.
 *
 * Rule 3 still holds — nothing here loops or performs on arrival. The card
 * settles once, and only because it is a modal-class surface (MAGI's dialog).
 */
#gate { position: fixed; inset: 0; background: var(--c-paper); z-index: 100;
        overflow-y: auto; overscroll-behavior: contain; }
#gate.hidden { display: none; }

.gate-grid { min-height: 100%; display: grid;
             grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
             align-items: stretch; }

/* ── left · the hero, then the argument ─────────────────────────────────────
 * .gate-left is the dark column on a desktop and `display: contents` on a
 * phone — see the note in index.html. The photograph lives on whichever of the
 * two is currently the box being drawn, which is why the background is
 * declared twice rather than once on a wrapper.
 *
 * THE SCRIM IS NOT DECORATION. The image is bright and busy across its lower
 * third (flowers, in full sun) and the copy sits over exactly that. Two layers
 * do the work: a wash that lifts the panel back toward --c-dark, and a
 * vertical gradient heaviest where the image is lightest. Measured over the
 * brightest pixel under the text, body copy holds ~9:1 — the same contrast it
 * has on the Welcome view, which is the point: this is the same surface with a
 * picture behind it, not a new one.
 *
 * background-color UNDER the image, so a slow or failed load is the dark panel
 * we already ship rather than white paper carrying white text. */
.gate-left { position: relative; isolation: isolate;
             background-color: var(--c-dark); color: var(--c-on-dark);
             background-image: url('/static/hero-login.jpg');
             background-size: cover; background-position: 50% 38%;
             background-repeat: no-repeat;
             border-right: 1px solid var(--c-dark-line);
             padding: clamp(28px, 4.4vw, 60px) clamp(24px, 4vw, 56px);
             display: flex; flex-direction: column; gap: 26px;
             justify-content: center; }
/* Light enough that the photograph is a photograph. The first version put a
 * 0.78-to-0.94 wash over the whole panel to make the founder's note legible,
 * and legible it was — over a picture you could no longer see, which is the
 * one thing a hero image has to do. Contrast is bought locally instead: this
 * gradient only anchors the top and the bottom edges, and the note carries its
 * own surface (below).
 *
 * ONE SCRIM PER BREAKPOINT, and that is why this selector is alone. The same
 * declaration exists again on .gate-hero::before inside the ≤940px block, and
 * the two must never be one shared rule: on a desktop .gate-hero is a CHILD of
 * .gate-left that `.gate-left > *` has already made positioned, so a shared
 * selector painted a second full gradient over the top of the column. The hero
 * came out a stop darker than the copy beneath it — same image, two washes.
 * Whichever element is the box carrying the photograph is the one that scrims
 * it: .gate-left here, .gate-hero there. */
.gate-left::before { content: ''; position: absolute;
  inset: 0; z-index: -1;
  background:
    linear-gradient(to bottom,
      rgba(21, 21, 19, 0.62) 0%,
      rgba(21, 21, 19, 0.28) 38%,
      rgba(21, 21, 19, 0.46) 66%,
      rgba(21, 21, 19, 0.90) 100%),
    linear-gradient(to right,
      rgba(21, 21, 19, 0.62) 0%,
      rgba(21, 21, 19, 0.26) 62%,
      rgba(21, 21, 19, 0.10) 100%); }
.gate-left > * { position: relative; }

.gate-hero { display: flex; flex-direction: column; gap: 22px; }
.gate-story { display: flex; flex-direction: column; gap: 26px; }

.gate-brand { display: flex; align-items: center; gap: 13px; }
/* Fixed HEIGHT, auto width. The mark is 174×112 and every place it appears is
 * height-constrained, so one rule keeps its proportions everywhere and no
 * layout has to know its aspect ratio. */
.gate-logo { height: 40px; width: auto; display: block; }
.gate-word { display: flex; flex-direction: column; gap: 2px; }
.gate-word b { font-size: 17px; font-weight: 600; letter-spacing: -0.02em;
               color: var(--c-on-dark); line-height: 1.1; }
.gate-word span { font-style: italic; font-size: 12px; color: var(--c-on-dark-3); }

.gate-lede { margin: 0; max-width: 30ch;
             font-size: clamp(19px, 2vw, 25px); line-height: 1.32;
             letter-spacing: -0.022em; font-weight: 500;
             color: var(--c-on-dark);
             /* the type sits on a photograph, so it carries its own separation
              * rather than relying on the scrim alone */
             text-shadow: 0 1px 14px rgba(21, 21, 19, 0.5); }
.gate-lede b { font-weight: 600; color: var(--accent-on-dark); }

/* The note sits on its own surface rather than directly on the photograph.
 * Three paragraphs of 12.5px body copy need a steady ground under every line,
 * and a photograph is by definition not steady — the alternative was darkening
 * the whole image until it stopped being one. Blur where the browser has it,
 * a flat translucent fill where it does not; both are legible, one is nicer. */
.gate-note { max-width: 52ch; padding: 20px 22px;
             border: 1px solid rgba(241, 239, 233, 0.14);
             border-radius: var(--r-md);
             background: rgba(21, 21, 19, 0.72); }
@supports (backdrop-filter: blur(1px)) {
  .gate-note { background: rgba(21, 21, 19, 0.44);
               backdrop-filter: blur(14px) saturate(120%);
               -webkit-backdrop-filter: blur(14px) saturate(120%); }
}
.gate-note .founder-kicker { color: var(--c-on-dark-3); margin-bottom: 13px; }
.gate-note p { margin: 0 0 11px; font-size: 12.5px; line-height: 1.72;
               color: var(--c-on-dark-2); }
.gate-note b { font-weight: 600; color: var(--c-on-dark); }
.gate-note .founder-sign { font-style: italic; font-size: 12.5px;
                           color: var(--c-on-dark-3); margin-top: 15px; }

/* Three facts, as a definition list, because that is what they are. The
 * figures sit on the ink ramp rather than in the accent: green means "mirai",
 * and three green numbers in a row would make it mean "good" instead. */
.gate-facts { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
              gap: 16px; margin: 0; padding-top: 20px; max-width: 620px;
              border-top: 1px solid rgba(241, 239, 233, 0.20); }
.gate-facts dt, .gate-facts dd, .gate-brand, .gate-note .founder-kicker {
  text-shadow: 0 1px 10px rgba(21, 21, 19, 0.65); }
.gate-facts dt { font-size: 14px; font-weight: 600; letter-spacing: -0.015em;
                 color: var(--c-on-dark); line-height: 1.2; }
/* on-dark-2, not -3: these sit over the brightest part of the photograph (the
 * flowers, in full sun) and the quiet grey that reads fine on a flat dark panel
 * does not survive being laid over a sunlit image. */
.gate-facts dd { margin: 5px 0 0; font-size: 11.5px; line-height: 1.5;
                 color: var(--c-on-dark-2); }

/* ── right · the sign-in card ──────────────────────────────────────
 * align-items: center is load-bearing, not tidiness. A column flex container
 * defaults to `stretch`, and a child with `width:100%; max-width:440px` under
 * `stretch` is capped at 440 and then parked at the START of the cross axis —
 * so the card hugged the left edge of its half and the whole page read as
 * off-centre, on a screen whose entire job is one field. */
.gate-panel { padding: clamp(28px, 4.4vw, 60px) clamp(20px, 3.4vw, 48px);
              display: flex; flex-direction: column; gap: 20px;
              align-items: center; justify-content: center; min-width: 0; }

/* MAGI's `.dialog`: 200ms, scale from 0.97, strong ease-out. Left-aligned now —
 * a centred label over a centred field over a centred button is a login form
 * out of a template, and it makes the error line jump about under the input. */
.gate-card { background: var(--c-surface); border: 1px solid var(--c-line);
             border-radius: var(--r-md); padding: 26px 26px 22px;
             width: 100%; max-width: 440px;
             display: flex; flex-direction: column; gap: 10px;
             animation: dialog-in 200ms var(--ease-out) both;
             box-shadow: 0 8px 28px rgba(27, 26, 22, 0.07); }
.gate-card h1 { font-size: 19px; margin: 0; font-weight: 600;
                letter-spacing: -0.022em; line-height: 1.2; }
.gate-card .sub { color: var(--c-ink-3); font-size: 12px; margin: 0 0 4px;
                  line-height: 1.6; }
.gate-card input { font-size: 15px; padding: 11px 12px; letter-spacing: 0.05em;
                   width: 100%; }
.gate-card button { padding: 11px 14px; font-size: 13px; font-weight: 600; }
/* A failed code is `alarm`, and it carries the solid mark so the state is not
 * a red sentence alone. */
.gate-card .err { color: var(--c-alarm-ink); font-size: 11.5px; min-height: 16px;
                  font-weight: 500; line-height: 1.4; }
.gate-card .err:not(:empty)::before {
  content: ''; display: inline-block; width: 7px; height: 7px; border-radius: 1px;
  margin-right: 5px; background: var(--c-alarm); }
.gate-ask { margin: 6px 0 0; font-size: 11.5px; line-height: 1.6;
            color: var(--c-ink-3); }
.gate-ask a { color: var(--c-ink-2); font-weight: 500;
              text-decoration: underline; text-decoration-style: dotted;
              text-underline-offset: 3px; }
.gate-ask a:hover { color: var(--c-ink); }

/* ── shared audio rows · voice catalogue ─────────────────────────────────── */
.sc-list { display: flex; flex-direction: column;
           border: 1px solid var(--c-line); border-radius: var(--r-md);
           background: var(--c-surface); overflow: hidden; }
/* The whole voice row is selectable; its play button previews the voice. */
.sc-item { display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
           grid-template-areas: 'play title meta' 'play blurb blurb';
           align-items: center; gap: 2px 11px; padding: 11px 13px;
           cursor: pointer; }
.sc-item + .sc-item { border-top: 1px solid var(--c-line); }
@media (hover: hover) and (pointer: fine) {
  .sc-item { transition: background-color 90ms ease; }
  .sc-item:hover { background: var(--c-sunk); }
}
/* One circular control per row — the only round thing in the build, and round
 * because every audio transport on earth is. This is the one place where
 * borrowing a convention beats being consistent with ourselves. */
.sc-play { grid-area: play; flex: none; width: 34px; height: 34px;
           border-radius: 50%; padding: 0; display: flex;
           align-items: center; justify-content: center;
           border: 1px solid var(--c-line-strong); background: var(--c-surface);
           color: var(--c-ink); }
.sc-play svg { width: 13px; height: 13px; }
.sc-play .ico-pause { display: none; }
.sc-item.playing .sc-play { border-color: var(--c-ink); background: var(--c-ink);
                            color: var(--c-paper); }
.sc-item.playing .sc-play .ico-play { display: none; }
.sc-item.playing .sc-play .ico-pause { display: block; }
.sc-play:hover { border-color: var(--c-ink-4); }
.sc-title { grid-area: title; font-size: 12.5px; font-weight: 600;
            color: var(--c-ink); letter-spacing: -0.005em; min-width: 0; }
.sc-meta { grid-area: meta; font-size: 10.5px; color: var(--c-ink-3);
           white-space: nowrap; font-variant-numeric: tabular-nums; }
.sc-blurb { grid-area: blurb; margin: 2px 0 0; font-size: 11.5px;
            line-height: 1.5; color: var(--c-ink-3); }
#log { display: none; }

/* ── notices ───────────────────────────────────────────────────────────────
 * The visible half of core.js's notice(), which until now had none: the
 * function has always opened with `const host = $("toasts"); if (!host)
 * return;` and that element was never in the markup, so every actionable
 * failure in the build took the early return and went nowhere.
 *
 * One stack, bottom-right on a desktop and top-of-screen on a phone where the
 * bottom belongs to the dock. Same two semantics and the same two marks as
 * everywhere else (rule 2): `bad` is broken, `open` is unresolved. */
#toasts { position: fixed; z-index: 200; right: 16px; bottom: 16px;
          display: flex; flex-direction: column; gap: 8px;
          width: min(360px, calc(100vw - 32px)); pointer-events: none; }
.toast { pointer-events: auto; display: grid;
         grid-template-columns: minmax(0, 1fr) auto; gap: 4px 10px;
         background: var(--c-surface); border: 1px solid var(--c-line-strong);
         border-radius: var(--r-md); padding: 11px 12px;
         box-shadow: 0 6px 22px rgba(27, 26, 22, 0.10);
         animation: toast-in 180ms var(--ease-out) both; }
.toast.bad { border-color: var(--c-alarm-line); background: var(--c-alarm-bg); }
.toast.open { border-color: var(--c-open-line); background: var(--c-open-bg); }
.toast-msg { font-size: 12px; line-height: 1.55; color: var(--c-ink-2);
             display: flex; align-items: flex-start; }
.toast.bad .toast-msg { color: var(--c-alarm-ink); }
.toast.open .toast-msg { color: var(--c-open-ink); }
.toast.ok .toast-msg { color: var(--c-ink); }
/* the shared marks are inline-block with a baseline nudge; in a flex row they
 * need top alignment instead, so a three-line notice keeps its mark on line one */
.toast-msg::before { margin-top: 4px; }
.toast-x { grid-column: 2; align-self: start; background: none; border: 0;
           color: var(--c-ink-3); font-size: 12px; line-height: 1;
           padding: 2px 4px; }
.toast-x:hover { color: var(--c-ink); background: none; transform: none; }
.toast-act { grid-column: 1 / -1; justify-self: start; margin-top: 4px;
             background: var(--c-surface); color: var(--c-ink);
             border-color: var(--c-line-strong); font-size: 11.5px;
             padding: 6px 10px; }

/* ── boot ──────────────────────────────────────────────────────────────────
 * What #content holds before the first view lands. #content used to be an
 * empty <main>, and everything that fills it is downstream of three round
 * trips — /me, /config, then the view partial — so on anything slower than a
 * laptop on wifi the console opened as a blank white column beside a
 * live-looking rail. That reads as broken, not as loading.
 *
 * The shapes are the shapes of the page that is coming, so nothing jumps when
 * the real thing arrives. NO SHIMMER: a sweeping gradient is ambient motion
 * (rule 3) and it also implies progress the page cannot measure. */
.boot { display: flex; flex-direction: column; gap: 12px; padding-top: 2px; }
.boot-line { background: var(--c-sunk); border-radius: var(--r-xs); }
.boot-h1 { height: 24px; width: min(280px, 60%); }
.boot-p { height: 11px; width: min(520px, 92%); }
.boot-p.short { width: min(380px, 68%); }
.boot-card { height: 210px; margin-top: 6px;
             border: 1px solid var(--c-line); border-radius: var(--r-md);
             background: var(--c-surface); }
/* The failure case, and it is a real one: any of those three round trips can
 * fail, and before this the whole outcome was a white column and no way to
 * find out why. */
.boot-fail { max-width: 60ch; }
.boot-fail h2 { margin: 0 0 6px; font-size: 15px; font-weight: 600;
                letter-spacing: -0.015em; }
.boot-fail p { margin: 0 0 12px; font-size: 12.5px; line-height: 1.6;
               color: var(--c-ink-2); }
.boot-fail .why { font-family: var(--mono); font-size: 11.5px;
                  color: var(--c-ink-3); word-break: break-word; }

/* ── motion ───────────────────────────────────────────────────────────────
 * Everything here is under 210ms and every one of them fires on a real event.
 * There is no page-entrance animation: the shell used to fade-and-rise on
 * every load, which is the console performing itself before it shows you
 * anything. State first, always. */
@keyframes fade-in  { from { opacity: 0; } to { opacity: 1; } }
@keyframes msg-in   { from { opacity: 0; transform: translateY(6px); }
                      to   { opacity: 1; transform: none; } }
@keyframes dialog-in{ from { opacity: 0; transform: scale(0.97); }
                      to   { opacity: 1; transform: none; } }
/* A notice arrives from the edge it is docked to, which is the one thing that
 * distinguishes it from content that was always there. */
@keyframes toast-in { from { opacity: 0; transform: translateY(8px); }
                      to   { opacity: 1; transform: none; } }
@keyframes pulse    { 0%, 100% { opacity: 0.3; } 50% { opacity: 1; } }

/* Reduced motion keeps the opacity cue — that is what tells you a line landed —
 * and drops everything that moves. Fewer and gentler, not zero. */
@media (prefers-reduced-motion: reduce) {
  .msg { animation: fade-in 160ms var(--ease-out) both; }
  .gate-card { animation: fade-in 160ms var(--ease-out) both; }
  .toast { animation: fade-in 160ms var(--ease-out) both; }
  /* The cat stays — it is what says "asleep" — it just stops moving. The Zzz
   * are held visible rather than dropped, so the state still reads. */
  #btn.asleep .cat-body { animation: none; }
  #btn.asleep .cat-z text { animation: none; opacity: 0.75; }
  /* The poses stay, the motion goes — every one of these cats is carrying a
     state, so removing the drawing would remove the information with it. The
     waiting cat simply sits still. The stretch is skipped in JS before it is
     ever inserted (see _catStretch in index.html); this covers the person who
     turns the preference on while one is mid-flight. */
  #btn.queued .cat-tail { animation: none; }
  .btn-cat-stretch { animation: none; }
  button:active { transform: none; }
  button { transition: background-color 90ms ease; }
  #speaking::after { animation: none; opacity: 0.7; }
  /* The support panel arrives without sliding. The talking cat's mouth is
   * frozen in JS rather than here (makeTalkingCat reads the preference) — it
   * keeps the drawing and simply holds it shut, because unlike the transcript
   * the corner has no sentence beside it doing the mouth's job. */
  .capbar i { transition: none; }
}

/* ── phone chrome · declared once, drawn only under 760px ─────────────────
 * The title bar, the call dock, the tab bar, the setup sheet and the pane's
 * close button are in the markup on every viewport and are hidden here, so
 * "these five are not part of the desktop instrument" is one statement in one
 * place rather than five overrides scattered through the media queries. */
.mtop, .dock, .tabbar, .rail-x, dialog.sheet { display: none; }

/* ── responsive ───────────────────────────────────────── */
@media (max-width: 1240px) {
  .app { grid-template-columns: 196px minmax(0, 1fr) 352px; }
  .app.inspecting { grid-template-columns: 196px minmax(0, 1fr) var(--insp-w); }
}
@media (max-width: 1080px) {
  .app { grid-template-columns: 1fr; }
  /* ONE column here, so the placements above have to go with the layout they
   * were written for. `grid-column: 3` against a single-column template does
   * not clamp — it creates two implicit columns and pushes the page off the
   * right edge. */
  .app > .nav, .app > #content, .app > .rail, .app > .insp {
    grid-column: auto; grid-row: auto; }
  .app.inspecting { grid-template-columns: 1fr; }
  .nav { position: static; height: auto; flex-direction: row; align-items: center;
         flex-wrap: wrap; gap: 10px; padding: 10px 12px; }
  .logo { padding: 0 8px 0 0; }
  .nav-sec { display: none; }
  /* THREE .nav-items groups now, laid side by side in one row with their
     headings dropped — so `flex: 1` each would hand a third of the bar to the
     Admin group, which is empty for every customer. Content-width instead, and
     the LAST group takes the slack so .nav-foot still ends up hard right.
     `:last-of-type` rather than a class because that is exactly the rule: the
     final group, whichever section it turns out to be. */
  .nav-items { flex-direction: row; flex: 0 0 auto; }
  .nav-items:last-of-type { flex: 1 1 auto; }
  .nav-item { width: auto; padding: 7px 10px; }
  /* The nav is one row of labels here, and the badge is the widest thing on
     the item it sits on. The nav item still says "Text to Speech"; the "new"
     is a rail affordance and this is no longer the rail. */
  .nav-new { display: none; }
  .nav-item.on::before { display: none; }
  .nav-item.on { border-color: var(--c-on-dark-3); }
  .nav-foot { margin: 0; flex-direction: row; align-items: center; border-top: none;
              padding: 0; gap: 10px; }
  .nav-cap { min-width: 130px; }
  .rail { position: static; height: auto; }
  #chat { max-height: 340px; }
}
@media (max-width: 900px) {
  .home-steps { grid-template-columns: 1fr; }
}

/* ── the gate, as ONE COLUMN ────────────────────────────────────────────────
 * Under 940px the two halves become a single column, and the pieces reorder
 * into what a phone sign-in actually is:
 *
 *   ┌──────────────────────┐
 *   │  ▓▓ hero photograph  │  the mark and one line of pitch, over the image.
 *   │  ✳ Voice Infra       │  A phone expects the brand at the top, and this
 *   │  ₹1 a minute…        │  is the only thing on the page that is a picture.
 *   ├──────────────────────┤
 *   │  Open your sandbox   │  the field, above the fold, full width
 *   │  [ access code     ] │
 *   │  [ Enter           ] │
 *   ├──────────────────────┤
 *   │  A note from the …   │  the essay, last, where it costs nobody anything
 *   └──────────────────────┘
 *
 * The stacked version used to be the desktop grid with `order` flipped, which
 * put a screen and a half of founder's note in front of someone holding a code
 * and left the photograph off the phone entirely. `display: contents` on
 * .gate-left is what makes the three real siblings, so each can be placed on
 * its own — the hero takes the image with it, and the note keeps the dark
 * surface without pretending to be a column. */
@media (max-width: 940px) {
  .gate-grid { display: flex; flex-direction: column;
               grid-template-columns: none; min-height: 0; }
  /* The wrapper stops being a box; its two children become flex items here.
     `content: none` is NOT optional. A `display: contents` element generates no
     box of its own, but browsers still generate its ::before — and that one is
     `position: absolute; inset: 0`, so with its parent gone it resolves against
     #gate (the nearest positioned ancestor, and a fixed full-screen one) and
     lays the dark scrim over the ENTIRE page. The symptom is the code card
     sitting on grey instead of paper. */
  .gate-left { display: contents; }
  .gate-left::before { content: none; display: none; }

  .gate-hero { position: relative; isolation: isolate; order: 0;
               background-color: var(--c-dark); color: var(--c-on-dark);
               background-image: url('/static/hero-login.jpg');
               background-size: cover; background-position: 50% 34%;
               /* Tall enough for the photograph to read as a photograph and
                * short enough that the code field is still on screen under it.
                * Capped in px as well as vh: on a landscape phone 34vh is a
                * strip, and on a tablet it is half the screen. */
               min-height: clamp(210px, 34vh, 300px);
               justify-content: flex-end; gap: 16px;
               padding: calc(env(safe-area-inset-top) + 26px)
                        20px 24px; }
  /* The scrim follows the photograph. Up there it belongs to .gate-left, the
     box that carries the image on a desktop; down here .gate-left generates no
     box at all and .gate-hero carries it instead, so the identical gradient is
     declared against the hero. Two scopes, one at a time — see the note over
     .gate-left::before for what a single shared selector did to the desktop. */
  .gate-hero::before { content: ''; position: absolute; inset: 0; z-index: -1;
    background:
      linear-gradient(to bottom,
        rgba(21, 21, 19, 0.62) 0%,
        rgba(21, 21, 19, 0.28) 38%,
        rgba(21, 21, 19, 0.46) 66%,
        rgba(21, 21, 19, 0.90) 100%),
      linear-gradient(to right,
        rgba(21, 21, 19, 0.62) 0%,
        rgba(21, 21, 19, 0.26) 62%,
        rgba(21, 21, 19, 0.10) 100%); }
  .gate-hero > * { position: relative; }
  .gate-lede { font-size: clamp(18px, 4.6vw, 23px); max-width: 22ch; }

  .gate-panel { order: 1; align-items: center; width: 100%;
                padding: 22px 16px 6px; }

  .gate-story { order: 2; position: relative; isolation: isolate;
                background: var(--c-dark); color: var(--c-on-dark);
                border-right: 0; margin-top: 22px;
                padding: 26px 20px calc(26px + env(safe-area-inset-bottom));
                gap: 22px; }
  .gate-story > * { width: 100%; max-width: 480px; margin-inline: auto; }
  .gate-note { padding-top: 0; border-top: 0; max-width: 480px; }
  .gate-facts { grid-template-columns: 1fr; gap: 12px; }
  .gate-facts > div { display: flex; align-items: baseline; gap: 10px; }
  .gate-facts dt { flex: 0 0 auto; }
  .gate-facts dd { margin: 0; }
}

/* Landscape phone: the photograph yields to the field. 34vh of a 390px-tall
 * viewport is only 133px, so the clamp floor of 210px wins and the hero takes a
 * fixed 210 out of a screen that has to hold the lede, the card, the input and
 * the Enter button — which lands below the fold. The fold is the constraint
 * that wins on this page: someone is holding a code and looking for somewhere
 * to type it, and a hero they have to scroll past is a hero that has failed.
 *
 * MIN-HEIGHT WAS ONLY A FLOOR, which is why lowering it on its own did nothing:
 * measured at 844×390 the hero still came out 258px tall, because its CONTENT
 * is what sets the height there — 50px of padding, a 40px brand row, a 16px
 * gap, and a 23px lede wrapping to four lines inside a 22ch measure. No floor
 * can shrink a box that is already taller than it. So the content is what
 * yields: the padding halves, the mark comes down to 24px, and the lede drops
 * to 15px over a WIDER measure.
 *
 * The wider measure is the counter-intuitive half. 22ch is right on a portrait
 * phone, where the screen is narrow and a long line has nowhere to go. Landscape
 * is the opposite shape — 844 across and 390 tall — so the constraint moves from
 * width to height, and the same sentence set at 52ch lands on two lines instead
 * of five. Every line saved is a line the Enter button moves up, which is the
 * only thing this breakpoint is for.
 *
 * 52ch and not more: 48 is the narrowest cap that still wraps the current pitch
 * to two lines, and everything from 48 to 72 measures identically because the
 * TEXT is what sets the line, not the cap. The four characters of slack are for
 * the copy changing; going wider than that would buy nothing and only leave room
 * for a future sentence to run at a measure nobody wants to read.
 *
 * Measured at 844×390: hero 258px → 114px, and the Enter button's bottom edge
 * from 463px (73px past the fold) to 345px, which leaves room for the browser
 * chrome a real phone has and this viewport does not. */
@media (max-width: 940px) and (max-height: 520px) {
  .gate-hero { min-height: 0; gap: 8px;
               padding-top: calc(env(safe-area-inset-top) + 14px);
               padding-bottom: 14px; }
  .gate-hero .gate-logo { height: 24px; }
  .gate-hero .gate-lede { font-size: 15px; line-height: 1.3; max-width: 52ch; }
}

/* ═══════════════════════════════════════════════════════════════════════════
 * PHONE — a layout, not a reflow.
 *
 * Above 760px this file draws a three-column instrument. Below it the same
 * markup is a phone app, and the difference is structural rather than a
 * narrower version of the same page:
 *
 *   ┌───────────────────────────┐  title bar   — brand + the setup sheet
 *   │ ✳ Voice Infra          ⚙ │                (fixed, dark, safe-area top)
 *   ├───────────────────────────┤
 *   │                           │  #content    — the ONLY thing that scrolls
 *   │   the view                │
 *   │   (the call pane covers    │  .rail      — transcript + turn chart,
 *   │    this while it is open) │                an overlay between the bars
 *   ├───────────────────────────┤
 *   │ ⌃ Live transcript  in call│  the dock    — handle + status
 *   │ [  Talk to your agent   ] │                the ONE primary control
 *   │ ▁▁▁▁▁▁ mic level          │                the mic meter, next to it
 *   │ or have it call a phone ⌄ │                the dial group, folded
 *   ├───────────────────────────┤
 *   │  ⌂     ✎     ◷     ⚿      │  tab bar     — thumb reach, safe-area bottom
 *   └───────────────────────────┘
 *
 * THE BODY DOES NOT SCROLL — #content does. That is what stops the URL bar
 * from chasing the dock around, and it is why there is not one `vh` below:
 * the shell is sized from --vvh (the visual viewport, written by core.js when
 * the soft keyboard opens) with 100dvh as the fallback.
 *
 * NOTHING IS REORDERED. The old build used `order:` to float the test rail
 * above the content, which left the keyboard walking the page in a sequence
 * that did not match the screen. The phone-only elements now sit in reading
 * order in the markup and the desktop-only ones are display:none, so tab order
 * is visual order by construction.
 * ═══════════════════════════════════════════════════════════════════════════*/
@media (max-width: 760px) {

  /* ── shell ─────────────────────────────────────────────── */
  html { height: 100%; }
  body { height: var(--vvh, 100dvh); min-height: 0; overflow: hidden;
         display: flex; flex-direction: column;
         padding: env(safe-area-inset-top) env(safe-area-inset-right)
                  0 env(safe-area-inset-left); }
  /* the banner carries the notch inset on desktop; on a phone the body does,
     otherwise the two of them inset the same 47px twice */
  .gpu-banner-in { padding-top: 9px; }
  #gpu-status-banner { position: static; flex: 0 0 auto; }
  /* Same reason as the banner above it: the phone shell is a fixed-height flex
     column, so a sticky bar inside it has nothing to stick to. It is a row. */
  .impbar { position: static; flex: 0 0 auto; padding: 7px 14px; }
  .gpu-banner-row { padding: 0 14px; font-size: 11.5px; }

  /* align-items:start is the desktop grid's — carried into a column flex
     container it becomes the CROSS axis and every bar shrinks to its own text */
  .app { flex: 1 1 auto; min-height: 0; position: relative;
         display: flex; flex-direction: column; align-items: stretch;
         max-width: none; margin: 0; padding: 0; gap: 0; }
  .nav { display: none; }

  /* ── title bar ─────────────────────────────────────────── */
  /* Dark, because the nav it replaces is the one dark surface in the build and
     <meta name="theme-color"> is already that value — the iOS status bar and
     this bar are then literally the same colour with no seam. */
  .mtop { display: flex; align-items: center; gap: 9px; flex: 0 0 auto;
          height: var(--mtop-h); padding: 0 4px 0 14px; z-index: 40;
          background: var(--c-dark); color: var(--c-on-dark);
          border-bottom: 1px solid var(--c-dark-line); }
  /* The mark and the wordmark are one button now — the way back to Welcome,
     which no longer has a tab. It has to look exactly like the two static
     elements it replaced: no border, no background, no padding, and the same
     flex behaviour, so the only thing that changed for a customer is that
     tapping it does something. The 44px min-height is the touch target the
     rest of the phone chrome holds to. */
  .mtop-home { display: flex; align-items: center; gap: 9px; flex: 1 1 auto;
               min-width: 0; min-height: 44px; margin: 0; padding: 0;
               background: none; border: 0; text-align: left;
               font: inherit; color: inherit; }
  .mtop-home:active { opacity: 0.6; }
  .mtop-home:hover { background: none; transform: none; }
  .mtop-mark { height: 19px; width: auto; flex: none; display: block; }
  .mtop-title { flex: 1 1 auto; font-size: 14px; font-weight: 600;
                letter-spacing: -0.02em; }
  .mtop-btn { width: 44px; height: 44px; flex: 0 0 auto; padding: 0;
              display: flex; align-items: center; justify-content: center;
              background: none; border: 0; color: var(--c-on-dark-2); }
  .mtop-btn svg { width: 20px; height: 20px; }
  .mtop-btn:active { color: var(--c-on-dark); transform: none; }
  /* the one ink outline is invisible on the dark bar */
  .mtop-btn:focus-visible { outline-color: var(--c-on-dark); outline-offset: -4px; }

  /* ── the scrolling view ────────────────────────────────── */
  #content { --pad-x: 14px;
             flex: 1 1 auto; min-height: 0; overflow-y: auto;
             overscroll-behavior: contain; -webkit-overflow-scrolling: touch;
             padding: 14px var(--pad-x) 28px; scroll-padding-bottom: 28px; }

  /* ── the call pane ─────────────────────────────────────────────────────
   * The transcript is what you look at during a call, so on a phone it is not
   * a box in a sidebar — it is a surface that covers the view from under the
   * title bar to the top of the dock. #chat is the only thing inside it that
   * scrolls, which is what keeps a 600-character agent turn from pushing the
   * End call button off the bottom of the screen.
   * Closed it is `visibility: hidden`, not translated-away: hidden removes it
   * from the tab order and the accessibility tree, which a transform does not,
   * while still leaving #chat a real height to scroll to the bottom of. */
  .rail { position: absolute; z-index: 20; left: 0; right: 0;
          top: var(--mtop-h); bottom: calc(var(--dock-h) + var(--tabbar-h));
          height: auto; gap: 6px; padding: 0 12px 8px;
          background: var(--c-paper); border-top: 1px solid var(--c-line);
          overflow: hidden;
          visibility: hidden; pointer-events: none;
          opacity: 0; transform: translateY(10px);
          transition: opacity 170ms var(--ease-out),
                      transform 170ms var(--ease-out),
                      visibility 0s linear 170ms; }
  .app.callopen .rail { visibility: visible; pointer-events: auto;
                        opacity: 1; transform: none; transition-delay: 0s; }

  .rail-head { display: grid; grid-template-columns: minmax(0, 1fr) auto;
               align-items: center; gap: 2px 8px; padding: 6px 0 2px;
               flex: 0 0 auto; }
  .rail-head h2 { grid-column: 1; font-size: 14px; }
  #testctx { grid-column: 1; justify-self: start; }
  .rail-x { display: flex; align-items: center; justify-content: center;
            grid-column: 2; grid-row: 1 / span 2;
            width: 44px; height: 44px; margin-right: -10px; padding: 0;
            background: none; border: 0; color: var(--c-ink-3); }
  .rail-x svg { width: 18px; height: 18px; }
  .rail-x:active { color: var(--c-ink); transform: none; }

  /* basis 0, not auto: with `auto` the transcript's own content height enters
     the flex negotiation and a long call squeezes the chart out of its natural
     size. The transcript scrolls, so what it wants is irrelevant — it takes
     whatever is left after the chart, and never less than four lines. */
  #chatpane { flex: 1 1 0; min-height: 110px; }
  #chatpane h2 { display: none; }      /* the pane header above already names it */
  #chat { max-height: none; padding: 10px 10px 12px; gap: 9px;
          overscroll-behavior: contain; }
  /* a transcript is prose being read on a phone, not a data cell */
  .msg { max-width: 90%; font-size: 14px; }
  .sys { font-size: 12px; }
  #speaking { font-size: 12.5px; }

  /* ── the turn chart, as cards that snap ────────────────────────────────
   * A 52px label + a bar + a 58px figure in a 340px row leaves the bar too
   * short to read a threshold off. So on a phone each turn becomes a card that
   * fills most of the screen and the strip snaps one turn at a time, which is
   * also the only honest way to scroll a chart with a finger. The axis is
   * hidden: it spans the pane, the cards do not, and an axis whose 1 s mark
   * does not sit over the bars' 1 s tick is worse than no axis at all — the
   * ticks still cross every bar, and the legend still names the three states. */
  /* The chart may not eat the transcript. It takes what it needs up to 46% of
     the pane and scrolls inside that; the transcript keeps the rest and never
     drops under 110px, which is three lines and the newest one. */
  .latwrap { flex: 0 1 auto; max-height: min(52%, 268px); overflow-y: auto;
             overscroll-behavior: contain; padding: 0 0 2px; }
  .latwrap summary { display: flex; align-items: center; min-height: 44px;
                     padding: 4px 12px; }
  #latpane.has-data .lat-axis { display: none; }
  .lat-sum { padding: 0 12px 8px; }
  #lat { display: flex; gap: 8px; overflow-x: auto; overflow-y: hidden;
         padding: 0 12px 10px; scroll-snap-type: x mandatory;
         scroll-padding-left: 12px; overscroll-behavior-x: contain; }
  .lat-row { flex: 0 0 min(70%, 230px); scroll-snap-align: start;
             grid-template-columns: minmax(0, 1fr) auto;
             grid-template-areas: 'n v' 't t'; gap: 7px 8px;
             padding: 9px 10px; border: 1px solid var(--c-line);
             border-radius: var(--r-md); background: var(--c-surface); }
  .lat-n { grid-area: n; }
  .lat-v { grid-area: v; }
  .lat-track { grid-area: t; height: 16px; }
  .lat-empty { flex: 1 1 auto; padding: 2px 0 8px; }
  .legend { padding: 2px 12px 10px; line-height: 1.85; }

  /* ── the dock ──────────────────────────────────────────────────────────
   * One control is what this product is for, and on a phone it is never more
   * than a thumb away: docked above the tab bar, full width, with the mic
   * level directly under it instead of in a sidebar the phone does not have.
   * #railcard is moved in here by core.js at this breakpoint and moved back
   * into the rail above it, so there is exactly one of each control. */
  .dock { display: flex; flex-direction: column; gap: 6px; flex: 0 0 auto;
          z-index: 30; background: var(--c-surface);
          border-top: 1px solid var(--c-line); padding: 2px 12px 10px; }
  .dock-grip { display: flex; align-items: center; gap: 10px; min-height: 44px; }
  .dock-handle { flex: 1 1 auto; display: flex; align-items: center; gap: 7px;
                 justify-content: flex-start; min-height: 44px; padding: 0 2px;
                 background: none; border: 0; color: var(--c-ink-2);
                 font-size: 12px; font-weight: 500; }
  .dock-handle svg { width: 15px; height: 15px; flex: 0 0 auto;
                     transition: transform 170ms var(--ease-out); }
  .app.callopen .dock-handle svg { transform: rotate(180deg); }
  .dh-open { display: none; }
  .app.callopen .dh-closed { display: none; }
  .app.callopen .dh-open { display: inline; }
  .dock-handle:active { color: var(--c-ink); transform: none; }
  .dock #status { flex: 0 1 auto; min-height: 0; padding: 0; text-align: right;
                  font-size: 11.5px; white-space: nowrap; overflow: hidden;
                  text-overflow: ellipsis; }
  .dock .rail-card { border: 0; background: none; padding: 0; border-radius: 0; }
  .dock .rail-cta { gap: 6px; }
  .dock #btn { min-height: 52px; font-weight: 600; }
  .dock #meter { height: 5px; }
  .dock .rail-hint { display: none; }  /* said once, at the first call, not forever */
  /* The switch sits above the button, thumb-sized, and both panels are
   * borderless down here — the dock already is the surface. */
  .dock .howrow { margin: 0 0 8px; }
  .dock .howtab { min-height: 44px; font-size: 12px; }
  .dock #cta-web, .dock #dialbody { border: 0; border-radius: 0;
                                    background: none; padding: 0; }
  .dock .action-row { margin-top: 0; }
  .dock input#phone { width: 100%; flex: 1 1 100%; }
  .dock #dialbtn { flex: 1 1 100%; }
  .dock .linkbtn { min-height: 44px; padding: 4px 0 0; }

  /* ── the tab bar ───────────────────────────────────────────────────────
   * Same buttons and the same navTo() as the desktop nav. Selected carries a
   * 3px top rule (the same 3px edge the desktop nav uses on its left), a
   * weight change and ink colour — three axes, so the state survives greyscale
   * and a deuteranope reads it off the rule, per rule 2. */
  .tabbar { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(60px, 1fr);
            overflow-x: auto; scrollbar-width: none;
            flex: 0 0 auto; z-index: 30; background: var(--c-surface);
            border-top: 1px solid var(--c-line);
            padding-bottom: env(safe-area-inset-bottom); }
  .tabbar .nav-item { flex-direction: column; justify-content: center;
                      align-items: center; gap: 4px; width: auto;
                      min-height: 54px; padding: 8px 4px; text-align: center;
                      border: 0; border-radius: 0; color: var(--c-ink-3);
                      font-size: 10.5px; font-weight: 500; letter-spacing: 0.01em;
                      line-height: 1; }
  /* The desktop sidebar tints its icons and lifts its selection for a dark
     column; the tab bar is paper, so both come back to the label's ink. */
  .tabbar .nav-item svg { width: 21px; height: 21px; opacity: 1; color: inherit; }
  .tabbar .nav-item:active { background: var(--c-sunk); transform: none; }
  .tabbar .nav-item.on { background: none; color: var(--c-ink); font-weight: 600; box-shadow: none; }
  .tabbar .nav-item.on svg { stroke-width: 2.1; }
  .tabbar .nav-item.on::before { content: ''; position: absolute;
    top: 0; bottom: auto; left: 50%; transform: translateX(-50%);
    width: 28px; height: 3px; background: var(--c-ink); }

  /* ── the setup sheet ───────────────────────────────────────────────────
   * A real <dialog> opened with showModal(): focus trap, Escape, scrim and an
   * inert page, all from the platform. The element is full-screen and the
   * panel is the bottom of it, so a tap on the dimmed area lands on the
   * <dialog> itself and core.js closes it.
   *
   * THREE THINGS ANCHOR IT, and it took losing all three on an iPhone to learn
   * that any one of them alone is a guess:
   *
   *   height: var(--vvh) — NOT 100%. A fixed element's percentage height comes
   *     off the layout viewport, which on iOS is the tall one, the one that
   *     includes the strip the URL bar sits over. The dialog was therefore
   *     taller than the screen, its bottom edge was under the browser chrome,
   *     and the panel anchored to an edge nobody can see. Everything else in
   *     this shell is sized from --vvh; so is this.
   *   a COLUMN with justify-content: flex-end — `align-items: flex-end` on a
   *     default-row container puts the panel at the bottom by way of the cross
   *     axis, which is one `flex-direction` away from silently becoming a
   *     horizontal alignment. Bottom-anchoring should be said on the main axis.
   *   z-index — core.js falls back to setAttribute("open") where showModal is
   *     missing, and a dialog outside the top layer is just a fixed box that
   *     the dock and the tab bar (both z-index 30) paint straight over. 60
   *     clears them and the title bar (40). */
  dialog.sheet { display: none; position: fixed; inset: 0; overflow: hidden;
                 width: 100%; max-width: none;
                 height: var(--vvh, 100dvh); max-height: none;
                 margin: 0; padding: 0; border: 0; background: transparent;
                 z-index: 60; flex-direction: column;
                 justify-content: flex-end; align-items: stretch; }
  dialog.sheet[open] { display: flex; }
  dialog.sheet::backdrop { background: var(--c-scrim); }
  .sheet-panel { width: 100%; max-height: 88%; overflow-y: auto;
                 overscroll-behavior: contain;
                 background: var(--c-surface);
                 border-top: 1px solid var(--c-line);
                 border-radius: var(--r-md) var(--r-md) 0 0;
                 padding: 6px 16px calc(16px + env(safe-area-inset-bottom));
                 animation: sheet-in 210ms var(--ease-out) both; }
  /* the panel is the dialog's focus parking spot, not a control */
  .sheet-panel:focus, .sheet-panel:focus-visible { outline: none; }
  .sheet-grab { width: 36px; height: 4px; border-radius: var(--r-xs);
                background: var(--c-line-strong); margin: 4px auto 8px; }
  .sheet-head { display: flex; align-items: center; justify-content: space-between;
                gap: 10px; }
  .sheet-head h2 { margin: 0; font-size: 15px; font-weight: 600;
                   letter-spacing: -0.01em; color: var(--c-ink); }
  .sheet-x { width: 44px; height: 44px; margin-right: -10px; padding: 0;
             display: flex; align-items: center; justify-content: center;
             background: none; border: 0; color: var(--c-ink-3); }
  .sheet-x svg { width: 18px; height: 18px; }
  .sheet-x:active { color: var(--c-ink); transform: none; }
  .sheet-body { display: flex; flex-direction: column; }
  .sheet-body .rail-field { padding: 12px 0; min-height: 56px;
                            border-bottom: 1px solid var(--c-line); }
  /* same reasoning as the rail's 66% — agent names are the longest values here,
     and a sheet is the narrowest place one has to fit */
  .sheet-body .rail-field select { max-width: 62%; }
  /* The nav footer is drawn for the dark rail. In a paper sheet every one of
     its values has to come back to the paper side of the same palette — this
     is the port, not a second theme. */
  /* the 1080px rule laid this out as a row for the horizontal nav bar; in a
     sheet it is a stack again */
  .sheet-body .nav-foot { display: flex; flex-direction: column;
                          align-items: stretch; margin: 0; gap: 14px;
                          padding: 14px 0 0; border-top: 1px solid var(--c-line); }
  .sheet-body .nav-cap { display: flex; padding: 0; min-width: 0; }
  .sheet-body .nav-cap-row { color: var(--c-ink-3); }
  .sheet-body .nav-cap-row b { color: var(--c-ink); }
  .sheet-body .nav-cap-row b.full { color: var(--c-alarm-ink); }
  .sheet-body .nav-cap-row b.full::before { background: var(--c-alarm); }
  .sheet-body .nav-cap-row.sub span:last-child { color: var(--c-ink-2); }
  .sheet-body .capbar { background: var(--c-line); }
  .sheet-body .capbar i { background: var(--c-ink-3); }
  .sheet-body .capbar i.full { background: var(--c-alarm); }
  .sheet-body .meetbtn { color: var(--c-ink); border-color: var(--c-line-strong);
                         min-height: 44px; }
  .sheet-body .meetbtn:active { background: var(--c-sunk); }
  .sheet-body .nav-me { padding: 0; }
  .sheet-body #brandpill { color: var(--accent); border-color: var(--accent-line); }
  .sheet-body .signout { color: var(--c-ink-3); min-height: 44px; padding: 4px 8px; }

  /* ── soft keyboard ─────────────────────────────────────────────────────
   * core.js sets .kbd when the visual viewport shrinks. The tab bar always
   * goes — no native app keeps its tabs up while you type — and the dock goes
   * with it UNLESS the field being typed into is the dock's own number box,
   * which is the one case where hiding it would take away what you are
   * looking at. The metrics zero out with them so the call pane refills the
   * space instead of leaving a hole where the dock was. */
  body.kbd .dock, body.kbd .tabbar, body.kbd-dock .tabbar { display: none; }
  body.kbd { --dock-h: 0px; --tabbar-h: 0px; }
  body.kbd-dock { --tabbar-h: 0px; }

  /* ── the gate ──────────────────────────────────────────────────────────
     The one-column arrangement is set at 940px above; this is only what
     changes at phone size. #gate is the scroller and the body's own overflow
     rule (hidden, for the app shell) must not apply to it — the sign-in page
     is a page you scroll, not a shell with fixed chrome. */
  #gate { height: var(--vvh, 100dvh); bottom: auto; overflow-y: auto;
          padding: 0; }
  .gate-panel { padding: 20px 14px 4px; gap: 16px; }
  .gate-card { max-width: none; padding: 20px 17px; }
  .gate-card h1 { font-size: 18px; }
  .gate-story { padding-left: 17px; padding-right: 17px; }
  .gate-hero { padding-left: 17px; padding-right: 17px; }
  .gate-logo { height: 32px; }
  /* The phone rules give every button a 44px minimum height, which turned the
     one round control in the build into an ellipse — 34 wide by 44 tall. It
     gets the 44 on BOTH axes instead: a proper thumb target that is still a
     circle. */
  .sc-play { width: 44px; height: 44px; min-height: 44px; }
  .sc-play svg { width: 15px; height: 15px; }
  .sc-item { gap: 3px 12px; padding: 13px; }

  /* ── notices, on a phone ───────────────────────────────────────────────
     The bottom is the dock and the tab bar, so a toast anchored there covers
     the one control this product exists for. It comes down from the top
     instead, under the safe-area inset. */
  #toasts { right: 10px; left: 10px; width: auto; bottom: auto;
            top: calc(env(safe-area-inset-top) + 8px); }
  .toast { animation: toast-in-top 180ms var(--ease-out) both; }


  /* ── views ─────────────────────────────────────────────── */
  /* A chat column a screen tall, less the view head. The chat still owns a screenful; the quickstart below it
     is reached by scrolling #content, exactly as every other view is read. */
  .llm-chatcol { height: calc(var(--vvh) - var(--mtop-h) - var(--tabbar-h) - 108px);
                 min-height: 340px; }
  .llm-chat { padding: 12px; }
  .llm-foot { padding: 9px 12px 12px; }

  .view-head { margin: 0 0 14px; }
  .view-head h1 { font-size: 20px; }
  .block, .card { padding: 14px; }
  .two-col, .admin-grid, .dev-grid { grid-template-columns: 1fr; }
  /* `1fr` is minmax(auto, 1fr): the key card's one-line `Authorization:
     Bearer sk_live_…` set the column's floor at ~620px and pushed the card
     off a 390px screen. The Developers page is where the developer lane
     lands, so its column may shrink to the screen. */
  .dev-grid { grid-template-columns: minmax(0, 1fr); }
  /* Same reason as the agent chips: on a phone these are the control that
     picks the section, and a 26px chip is not a touch target. */
  .devrow .howtab, .qsrow .howtab { min-height: 44px; }
  /* Three mono labels, one of which is a route, in a 390px viewport: they will
     not fit side by side and `white-space: nowrap` would clip them rather than
     admit it. Stacked, the switch is three full-width rows and each one is a
     44px target. */
  .howrow.qsrow { grid-template-columns: 1fr; }
  .qsrow .howtab { white-space: normal; }
  .qsrow .howtab + .howtab { border-left: 0; border-top: 1px solid var(--c-line); }
  .tts-facts { gap: 10px 18px; }
  .dev-mintrow { flex-direction: column; }
  .hist-stats { grid-template-columns: repeat(2, 1fr); gap: 8px; }
  .script-tools { flex-direction: column; }
  .script-tools select, .script-tools input, .script-tools button {
    width: 100%; min-width: 0; }
  /* ── the agent card, on a phone ────────────────────────────────────────
     The header stops being a row: the agent strip takes the full width and the
     two actions become a row under it with Save carrying its own weight.

     The strip swipes, with the same manners as the home page's cards — it
     bleeds to the card's edges so a chip half off the right side is the thing
     that says there are more, and it snaps so a swipe lands on a chip rather
     than between two. Chips are 44px tall here because they are the primary
     control of this view and a 26px chip is not a touch target. */
  .agent-head { flex-direction: column; align-items: stretch; gap: 10px;
                padding: 12px 14px; }
  .agent-tabs { margin: 0 -14px; padding: 2px 14px 4px;
                scroll-snap-type: x proximity; scroll-padding-left: 14px; }
  .agent-tab, .agent-tab-new { min-height: 44px; padding: 6px 12px;
                               scroll-snap-align: start; }
  .agent-tab { max-width: 62vw; }
  .agent-acts { width: 100%; }
  .agent-acts button.ghost { flex: 1 1 auto; }
  .agent-acts .agent-del { flex: 0 0 auto; padding: 6px 8px; min-height: 44px; }
  .agent-body { padding: 2px 14px 14px; }
  .agent-name { max-width: none; }
  .agent-new { flex-direction: column; align-items: stretch; padding: 12px 14px; }
  .agent-new input, .agent-new button { width: 100%; }
  .docs summary { display: flex; align-items: center; min-height: 44px; }
  .startpoint { padding: 0 14px; }
  .startpoint summary { display: flex; align-items: center; min-height: 44px; }
  /* The circle is the row's only control and it is now the only thing to hit,
     so it takes the full touch target rather than the 34px it draws at on a
     desktop — the same size the gate's sample rows give it. */
  .hist-row { padding: 12px 4px; gap: 0 10px; }
  .hist-play { width: 44px; height: 44px; }
  .hist-play svg { width: 15px; height: 15px; }

  /* The three how-it-works cards were a 3-up grid stacked into a 3-screen
     column on a phone. As a snapping strip they are one screen and one swipe,
     and the strip bleeds to both edges so the second card peeking is the
     affordance — nothing else has to say "scroll me". */
  .home-steps { display: flex; gap: 10px; overflow-x: auto;
                margin: 0 calc(var(--pad-x) * -1);
                padding: 2px var(--pad-x) 8px;
                scroll-snap-type: x mandatory;
                scroll-padding-left: var(--pad-x);
                overscroll-behavior-x: contain; }
  .how { flex: 0 0 78%; scroll-snap-align: start; }

  /* ── touch + input ─────────────────────────────────────── */
  /* 16px, not 15px: iOS zooms any focused field under 16px, which is the whole
   * reason this rule exists. The editor classes are listed explicitly because a
   * class beats a bare element selector and would otherwise reinstate the zoom. */
  button, select, input[type=text], input[type=tel], textarea,
  textarea.editor-lg, textarea.editor-sm { font-size: 16px; }
  button { min-height: 44px; }
  select, input[type=text], input[type=tel] { min-height: 44px; }
  /* the grey flash lands on the wrong box on every one of these */
  a, button, summary, .nav-item, .chip { -webkit-tap-highlight-color: transparent; }
  /* the phone is the one place a 17-line box is a wall — it still grows, and
     agent.js caps it at 42% of the viewport instead of 62% */
  textarea.editor-lg { min-height: 180px; }
}

/* A 5.4" screen (568–640 tall) has ~290px between the chrome once the dock is
 * up, so the chrome gives some back: 4px off the title bar, 4px off each tab,
 * a shorter primary button. Never off the 44px hit areas. */
@media (max-width: 760px) and (max-height: 660px) {
  :root { --mtop-h: 42px; --tabbar-h: calc(50px + env(safe-area-inset-bottom)); }
  .tabbar .nav-item { min-height: 50px; padding: 6px 4px; }
  .tabbar .nav-item svg { width: 19px; height: 19px; }
  .dock { gap: 4px; padding-bottom: 7px; }
  .dock #btn { min-height: 48px; }
  .latwrap { max-height: 40%; }
  #chatpane { min-height: 96px; }
  #content { padding-top: 12px; }
  .view-head { margin-bottom: 12px; }
}

@media (max-width: 430px) {
  #content { --pad-x: 12px; }
  #latpane { --lat-lead: 38px; --lat-tail: 52px; }
  .lat-sum, .legend { padding-left: 12px; padding-right: 12px; }
  .view-head h1 { font-size: 19px; }
  .block, .card { padding: 12px; }
  .docs { padding: 2px 14px 4px; }
  .home-steps { gap: 8px; }
  .how { padding: 12px 14px; flex-basis: 82%; }
  .mtop { padding-left: 12px; }
  .dock { padding-left: 10px; padding-right: 10px; }
  .tabbar .nav-item { font-size: 10px; }
}

/* The sheet arriving is a thing that just happened — a tap — so it is allowed
 * to move (rule 3). Everything else phone-side settles with opacity only. */
@keyframes sheet-in { from { transform: translateY(100%); }
                      to   { transform: none; } }
/* Same 8px as toast-in, from the edge the phone stack is docked to. */
@keyframes toast-in-top { from { opacity: 0; transform: translateY(-8px); }
                          to   { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) {
  .rail, .dock-handle svg, .rail-div-chev { transition: none; }
  .sheet-panel { animation: fade-in 160ms var(--ease-out) both; }
}

/* ── call playback ─────────────────────────────────────────────────────────
   Listening back to a call in the inspector, with the transcript alongside. */
.player {
  position: relative;            /* the follow pill hangs off the bottom of it */
  border: 1px solid var(--c-line); border-radius: 10px;
  background: var(--c-surface); overflow: hidden;
}
.pl-bar {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 12px; border-bottom: 1px solid var(--c-line);
  background: var(--c-sunk);
}
/* THE SAME CIRCLE AS .hist-play AND .sc-play, down to the SVG inside it. It
   used to be a text glyph that swapped ▶ for ❚❚, which is a different-looking
   control in the same eight pixels — the thing the history row was rebuilt to
   stop doing. Learned once on the sign-in page, recognised everywhere. */
.pl-play {
  flex: none; width: 34px; height: 34px; border-radius: 50%; padding: 0;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--c-line-strong); background: var(--c-surface);
  color: var(--c-ink); line-height: 1; cursor: pointer;
}
.pl-play svg { width: 13px; height: 13px; }
.pl-play .ico-pause { display: none; }
.pl-play:hover:not(:disabled) { border-color: var(--c-ink-4); }
.pl-play.on { border-color: var(--c-ink); background: var(--c-ink);
              color: var(--c-paper); }
.pl-play.on:hover { border-color: var(--c-ink); background: var(--c-ink); }
.pl-play.on .ico-play { display: none; }
.pl-play.on .ico-pause { display: block; }
.pl-play:disabled { opacity: .45; cursor: not-allowed; }
.pl-time, .pl-dur {
  flex: none; font-variant-numeric: tabular-nums; font-size: 12px;
  color: var(--c-ink-soft, #6b6862); min-width: 34px;
}
.pl-dur { text-align: right; }
.pl-seek { flex: 1; min-width: 60px; accent-color: var(--accent); cursor: pointer; }
.pl-rate {
  flex: none; min-width: 42px; padding: 4px 8px; cursor: pointer;
  border: 1px solid var(--c-line-strong); border-radius: 6px;
  background: var(--c-surface); color: var(--c-ink);
  font-variant-numeric: tabular-nums; font-size: 12px;
}
.pl-rate:hover { background: var(--accent-bg); border-color: var(--accent-line); }
/* An icon, not the word "download": it sits at the end of a row of controls
   and the only text on that row should be the two clocks. Still an <a
   download> — nothing else gets the browser to save the file under the name
   the server puts on it. */
.pl-dl { flex: none; display: flex; align-items: center; justify-content: center;
         width: 28px; height: 28px; border-radius: var(--r-sm);
         border: 1px solid var(--c-line-strong); background: var(--c-surface);
         color: var(--c-ink-3); }
.pl-dl svg { width: 14px; height: 14px; }
.pl-dl:hover { color: var(--c-ink); border-color: var(--c-ink-4); }

/* The lyrics pane. Bounded height so the page does not grow by the length of
   the call, and scrollable on its own.

   position: relative is load-bearing, not decoration: player.js scrolls this
   pane to `node.offsetTop`, and offsetTop is measured against the nearest
   POSITIONED ancestor. Without this the line offsets would be measured from
   .player and every scroll would land one transport-bar too low. */
.pl-lyrics { position: relative; max-height: 320px; overflow-y: auto;
             padding: 6px 0; outline: none; }
.pl-lyrics:focus-visible { box-shadow: inset 0 0 0 2px var(--accent-line); }
.pl-note { padding: 14px 16px; color: var(--c-ink-soft, #6b6862); font-size: 13px; }
.pl-line {
  display: grid; grid-template-columns: 58px 1fr auto; gap: 10px;
  align-items: baseline; padding: 7px 14px; cursor: pointer;
  border-left: 3px solid transparent;
  /* Unspoken lines sit back so the current one reads as the live position
     without the rest becoming unreadable. */
  opacity: .58; transition: opacity .18s var(--ease-out), background .18s var(--ease-out);
}
.pl-line:hover { background: var(--c-sunk); opacity: .85; }
.pl-line.on { opacity: 1; background: var(--accent-bg); border-left-color: var(--accent); }
.pl-who { font-size: 11px; text-transform: uppercase; letter-spacing: .04em;
          color: var(--c-ink-soft, #6b6862); }
.pl-line.bot .pl-who { color: var(--accent); }
.pl-said { line-height: 1.5; }
.pl-at { font-size: 11px; font-variant-numeric: tabular-nums;
         color: var(--c-ink-soft, #6b6862); }

/* The call's files, inside the player they belong to. This was a button in the
 * history row that fetched, renamed itself "preparing… (3/10)" and then turned
 * into a row of links — three different controls in the same eight pixels. As
 * a line under the transport it is just a fact about the call, and it only
 * appears once there is something in it. */
.pl-artifacts { display: flex; align-items: baseline; gap: 10px;
                flex-wrap: wrap; padding: 8px 12px;
                border-bottom: 1px solid var(--c-line);
                background: var(--c-surface); font-size: 11.5px; }
.pl-artifacts-k { color: var(--c-ink-3); font-size: 10.5px; font-weight: 600;
                  letter-spacing: 0.06em; text-transform: uppercase; }
.pl-artifacts a { color: var(--c-ink-2); font-weight: 500;
                  text-decoration: underline; text-decoration-style: dotted;
                  text-underline-offset: 3px; }
.pl-artifacts a:hover { color: var(--c-ink); }

/* "Jump to latest", the chat idiom, for a transcript. It appears only while
   you are reading somewhere other than where the audio is, which is the only
   moment it means anything. A rectangle at --r-sm, not a capsule: see the note
   over .pill. */
.pl-follow { position: absolute; left: 50%; bottom: 12px; z-index: 2;
             transform: translateX(-50%);
             padding: 5px 10px; font-size: 11px; font-weight: 500;
             line-height: 1; border-radius: var(--r-sm);
             border: 1px solid var(--c-line-strong);
             background: var(--c-surface); color: var(--c-ink-2); }
.pl-follow:hover { color: var(--c-ink); border-color: var(--c-ink-4); }
/* The global press feedback is a bare scale(), which would throw away the
   centring transform and make the pill jump left on every press. */
.pl-follow:active { transform: translateX(-50%) scale(0.97); }
.pl-follow[hidden] { display: none; }

@media (prefers-reduced-motion: reduce) {
  .pl-line { transition: none; }
  .pl-follow:active { transform: translateX(-50%); }
}
@media (max-width: 640px) {
  .pl-line { grid-template-columns: 1fr auto; }
  .pl-who { grid-column: 1 / -1; }
  .pl-lyrics { max-height: 260px; }
}

/* ── the call inspector ────────────────────────────────────────────────────
 * One call, opened from Call history: the recording and its transcript on one
 * tab, the worker's own log on the other. It TAKES THE RAIL'S FRAME — same
 * sticky top, same height, same column — and paints over it, because on the
 * history page the rail is a test console for an agent nobody is testing.
 *
 * Spatial consistency (Apple's rule, and Emil's): it comes in from the right
 * and it leaves to the right, always, because that is where it lives. The
 * entrance is 220ms and the exit is 160ms — leaving should feel like the
 * decision has already been made. Nothing here loops or pulses (rule 3), and
 * nothing moves at all under a keyboard action: walking the list with ↑/↓
 * swaps the contents instantly, because a panel that eases on every keypress
 * is a panel you cannot read while you hold the key down.
 *
 * NO NEW COMPONENTS. The tab strip and the log filter are .howrow/.howtab —
 * the same switch as call-method. The two text actions are
 * .linkbtn. The circle in the transport is .pl-play, which is .hist-play,
 * which is .sc-play. */
.insp {
  /* The rail's frame, banner correction included — see .rail above. */
  position: sticky; top: calc(16px + var(--banner-h, 0px)); z-index: 21;
  height: calc(100vh - 32px - var(--banner-h, 0px));
  height: calc(100dvh - 32px - var(--banner-h, 0px));
  display: flex; flex-direction: column; min-width: 0;
  background: var(--c-surface); border: 1px solid var(--c-line);
  border-radius: var(--r-md); overflow: hidden;
  opacity: 0; transform: translateX(16px);
  transition: opacity 160ms var(--ease-out), transform 160ms var(--ease-out);
}
/* The resting rule carries the EXIT timing and the .on rule overrides it with
 * the entrance — one path, two durations, no second selector to keep in sync. */
.insp.on { opacity: 1; transform: none; transition-duration: 220ms; }
.insp:not(.on) { pointer-events: none; }
.insp:focus-visible { outline: 2px solid var(--c-ink); outline-offset: -2px; }

/* ── header ─────────────────────────────────────────────── */
.insp-head { flex: 0 0 auto; display: flex; align-items: flex-start; gap: 10px;
             padding: 11px 12px 10px; border-bottom: 1px solid var(--c-line);
             background: var(--c-sunk); }
.insp-title { flex: 1 1 auto; min-width: 0; }
.insp-line { display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap; }
.insp-kind { font-size: 13px; font-weight: 600; letter-spacing: -0.01em;
             color: var(--c-ink); }
.insp-agent { font-size: 11px; color: var(--c-ink-3); min-width: 0;
              overflow-wrap: anywhere; }
.insp-sub { font-size: 11px; line-height: 1.45; color: var(--c-ink-3);
            margin-top: 3px; overflow-wrap: anywhere;
            font-variant-numeric: tabular-nums; }
.insp-nav { flex: 0 0 auto; display: flex; align-items: center; gap: 3px; }
.insp-step, .insp-x { width: 26px; height: 26px; padding: 0; flex: none;
                      display: flex; align-items: center; justify-content: center;
                      border: 1px solid var(--c-line-strong);
                      border-radius: var(--r-sm); background: var(--c-surface);
                      color: var(--c-ink-2); }
.insp-step svg { width: 15px; height: 15px; }
.insp-x svg { width: 14px; height: 14px; }
.insp-step:hover:not(:disabled), .insp-x:hover { color: var(--c-ink);
                                                 border-color: var(--c-ink-4); }
.insp-pos { font-size: 11px; color: var(--c-ink-3); padding: 0 5px;
            min-width: 46px; text-align: center; }
.insp-back { display: none; }        /* phone only — see the block below */

/* ── tabs and panels ────────────────────────────────────── */
/* Doubled class for the reason .howrow.devrow is doubled: the base
 * .howrow's own margin is declared earlier and this has to outrank it
 * rather than merely follow it. */
.howrow.insp-tabs { flex: 0 0 auto; margin: 10px 12px 0; }
.insp-panel { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column;
              margin: 0 12px; border: 1px solid var(--c-line); border-top: 0;
              border-radius: 0 0 var(--r-sm) var(--r-sm);
              background: var(--c-surface); overflow: hidden; }
/* display:flex above beats the [hidden] default, so it has to be said. */
.insp-panel[hidden] { display: none; }

/* The player fills the tab instead of being a card inside it: the panel is
 * already the card. THE LYRICS PANE IS THE THING THAT SCROLLS — no max-height,
 * and min-height:0 so a long transcript cannot push the transport off the top
 * of a flex column. */
.insp .player { flex: 1 1 auto; min-height: 0; display: flex;
                flex-direction: column; border: 0; border-radius: 0;
                background: none; }
.insp .pl-bar { flex: 0 0 auto; border-radius: 0; }
.insp .pl-artifacts { flex: 0 0 auto; }
.insp .pl-lyrics { flex: 1 1 auto; min-height: 0; max-height: none; }

/* ── the log ────────────────────────────────────────────── */
.insp-logbar { flex: 0 0 auto; display: flex; align-items: center; gap: 10px;
               justify-content: space-between; flex-wrap: wrap;
               padding: 8px 10px; background: var(--c-sunk);
               border-bottom: 1px solid var(--c-line); }
/* .insp-logf .howrow, not .insp-logf alone, for the reason .howrow.devrow
 * exists: the base two-column .howrow is declared later in this file. */
.howrow.insp-logf { margin: 0; flex: 1 1 210px;
                    grid-template-columns: repeat(4, 1fr); }
.howrow.insp-logf .howtab { min-height: 26px; padding: 5px 4px; font-size: 11px; }
.insp-logacts { flex: 0 0 auto; display: flex; align-items: center; gap: 12px; }
.insp-logacts .linkbtn { padding: 0; font-size: 11px; }
.insp-log { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 3px 0; }
.insp-lognote { padding: 14px 12px; color: var(--c-ink-3); font-size: 12px; }

/* One line of the log. Time, kind, text — and the KIND IS A WORD, so the list
 * is readable with no colour at all (rule 2). Colour only ever repeats what
 * the label already said. */
.ilog { display: grid; grid-template-columns: 48px 46px minmax(0, 1fr);
        gap: 0 8px; align-items: baseline; padding: 5px 12px 5px 10px;
        font-size: 11.5px; border-left: 2px solid transparent; }
.ilog-at { color: var(--c-ink-3); font-size: 10.5px; }
.ilog-k { font-size: 10px; text-transform: uppercase; letter-spacing: 0.04em;
          color: var(--c-ink-3); overflow: hidden; text-overflow: ellipsis; }
.ilog-t { color: var(--c-ink-2); line-height: 1.5; overflow-wrap: anywhere; }
/* What was actually said reads as prose; everything else is instrumentation. */
.ilog-user .ilog-t, .ilog-bot .ilog-t { color: var(--c-ink); }
.ilog-bot .ilog-k { color: var(--accent); }   /* as .pl-line.bot .pl-who */
/* The metric lines keep the quiet `·` register the worker writes them in. */
.ilog-metric .ilog-k { color: var(--c-ink-4); font-size: 12px;
                       letter-spacing: 0; text-align: center; }
.ilog-metric .ilog-t, .ilog-note .ilog-t { color: var(--c-ink-3); }
.ilog-note .ilog-k { color: var(--c-ink-4); }
/* A barge-in is the one thing in this list somebody scans for, so it gets the
 * unresolved mark's edge as well as its word. */
.ilog-intr { border-left-color: var(--c-open-line); }
.ilog-intr .ilog-k { color: var(--c-open-ink); }
.ilog.seekable { cursor: pointer; }
.ilog.seekable:hover { background: var(--c-sunk); }
.ilog.seekable:focus-visible { outline: 2px solid var(--c-ink); outline-offset: -2px; }

/* ── improving a node-based agent's flow from the call ─────────────────────
 * Under the player, above the post-call results. It scrolls on its own once
 * a report is in it, so the transport and the transcript keep their place. */
.insp-improve { flex: 0 1 auto; min-height: 0; max-height: 58%; overflow-y: auto;
  overscroll-behavior: contain; padding: 12px 14px 14px; border-top: 1px solid var(--c-line); }
.insp-improve:focus { outline: none; }
.insp-improve h4 { margin: 0; font: 550 13px/1.45 var(--sans); letter-spacing: -.01em; color: var(--c-ink); }
.insp-improve-hint, .insp-improve-lede, .insp-improve-status { margin: 6px 0 0; color: var(--c-ink-3);
  font-size: 11.5px; line-height: 1.6; }
.insp-improve-status { color: var(--c-ink-2); }
.insp-improve-error { color: var(--c-alarm-ink); }
.insp-improve-comments label { display: block; margin: 0 0 6px; color: var(--c-ink-2);
  font-size: 12px; line-height: 1.5; letter-spacing: normal; text-transform: none; }
.insp-improve-comments textarea { display: block; width: 100%; min-height: 76px; font-size: 12px; line-height: 1.6; }
.insp-improve-comments textarea[readonly] { background: var(--c-sunk); color: var(--c-ink-2); }
.insp-improve-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 12px; }
.insp-improve-actions:first-child { margin-top: 0; }
.insp-improve-decide { position: sticky; bottom: -14px; margin: 14px -14px -14px; padding: 10px 14px 14px;
  border-top: 1px solid var(--c-line); background: var(--c-surface); }
.insp-improve-actions button { margin: 0; }
.insp-improve .flow-refine-section { margin-top: 14px; }
.insp-improve .flow-refine-line { grid-template-columns: 8.5em minmax(0, 1fr); }
.insp-improve .flow-refine-line[data-role="transition"],
.insp-improve .flow-refine-line[data-role="tool"] { padding-left: calc(8.5em + 12px); }
/* The panel is too narrow for two columns: the call, then its replay. */
.insp-improve .flow-refine-compare { grid-template-columns: minmax(0, 1fr); }
.insp-improve .flow-refine-compare .flow-refine-line { padding-left: 0; }

/* ── the keys, stated once ──────────────────────────────── */
/* Discoverability without a tour: the four things this panel does with the
 * keyboard, in the footer, at the size of a caption. */
.insp-keys { flex: 0 0 auto; padding: 9px 12px 11px; font-size: 10.5px;
             color: var(--c-ink-3); font-variant-numeric: tabular-nums; }

/* ── 761–1080px: there is no third column here ─────────────────────────────
 * The 1080px rule folds .app to a single column and lays the nav out as a
 * horizontal strip, so the rail is a block UNDER the page rather than beside
 * it and there is no column for the inspector to take. It becomes an overlay
 * pinned to the right edge instead — the same panel, the same entrance, over
 * the page rather than beside it. */
@media (min-width: 761px) and (max-width: 1080px) {
  /* Below the banner rather than over it: this panel outranks it in the
   * stacking order, and covering the one sentence that says the fleet is down
   * is not a thing a call review should do. */
  .insp { position: fixed; top: var(--banner-h, 0px); right: 0; bottom: 0;
          left: auto; width: min(520px, 92vw); height: auto; z-index: 70;
          border-radius: 0; border-top: 0; border-right: 0; border-bottom: 0; }
}

/* ── phone ─────────────────────────────────────────────────────────────────
 * The same treatment as the call pane: a surface between the title bar and the
 * dock, over the view rather than beside it, and #content goes `inert` under
 * it (inspector.js, the way setCallPane does). The × becomes "‹ Calls",
 * because on a phone this is a place you went rather than a thing you opened,
 * and the way out of a place is back. */
@media (max-width: 760px) {
  .insp { position: absolute; z-index: 25; left: 0; right: 0;
          top: var(--mtop-h); bottom: calc(var(--dock-h) + var(--tabbar-h));
          height: auto; border: 0; border-top: 1px solid var(--c-line);
          border-radius: 0; background: var(--c-paper);
          transform: translateX(24px);
          transition: opacity 170ms var(--ease-out),
                      transform 170ms var(--ease-out); }
  .insp.on { transition-duration: 170ms; }
  .insp-head { padding: 6px 4px 8px 8px; background: none;
               align-items: center; }
  .insp-back { display: flex; align-items: center; gap: 2px; flex: 0 0 auto;
               min-height: 44px; margin-left: -8px; padding: 0 8px 0 4px;
               background: none; border: 0; color: var(--c-ink-3);
               font-size: 12px; font-weight: 500; }
  .insp-back svg { width: 18px; height: 18px; }
  .insp-back:active { color: var(--c-ink); transform: none; }
  .insp-x { display: none; }             /* "‹ Calls" is the way out here */
  .insp-step { width: 34px; height: 34px; }
  .howrow.insp-tabs { margin: 0 10px; }
  .insp-panel { margin: 0 10px 10px; border-radius: 0 0 var(--r-sm) var(--r-sm); }
  /* a transcript is prose being read on a phone, the same as #chat's .msg */
  .insp .pl-said { font-size: 13.5px; }
  /* Two lines per entry, the same fold .pl-line does at 640px: when and what
   * kind on the first, what happened on the second across the full width.
   * The metric mark comes back to the left edge of its column — centred in a
   * narrow desktop column it reads as a bullet, centred in a phone-wide one it
   * is a dot stranded in the middle of the row. */
  .ilog { grid-template-columns: 44px minmax(0, 1fr); row-gap: 1px; }
  .ilog-at { grid-column: 1; grid-row: 1; }
  .ilog-k { grid-column: 2; grid-row: 1; }
  .ilog-metric .ilog-k { text-align: left; }
  .ilog-t { grid-column: 1 / -1; grid-row: 2; }
  /* no keyboard, nothing to say about one */
  .insp-keys { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  /* Opacity only, and quickly. The column still resizes — that is layout, not
   * motion — but it stops being animated. */
  .app { transition: none; }
  .insp { transform: none;
          transition: opacity 120ms var(--ease-out); }
  .insp.on { transform: none; transition-duration: 120ms; }
}

/* ── campaigns view ────────────────────────────────────────────────────────
 * A list, one campaign, and the do-not-call list — three panes in one view,
 * because they are one navigation and the router has no URL for a sub-page.
 * The list is Agent Studio's directory (see "the campaign list" beside
 * .agents-page); what follows is the campaign itself.
 *
 * The contacts rows are grids of spans, not a <table>: the phone folds them
 * into two lines and a table cannot be folded. The one place a table's manners
 * are kept is the header strip, which shares the row's grid so the columns
 * actually line up.
 *
 * NO NEW COLOURS. The segmented progress bar is the only thing here that
 * needed a palette, and it is built out of the tokens already in this file —
 * accent for the calls that landed, the two semantic colours for the ones that
 * did not, and the greys for everything still in flight. Every segment is
 * named in the legend underneath (rule 2: a hue never carries a fact alone). */

.cmp-off h3 { font-size: 14px; }
.cmp-off h3.mark-open::before { vertical-align: -1px; }
.cmp-empty { color: var(--c-ink-3); font-size: 12.5px; padding: 16px 4px; }

/* ── rows (the contacts table) ──────────────────────── */
.cmp-rowhead, .cmp-row {
  display: grid; grid-template-columns: minmax(0, 1fr) 110px 110px 108px;
  align-items: center; gap: 0 12px;
}
.cmp-rowhead { padding: 0 4px 8px; font-size: 10px; letter-spacing: 0.08em;
               text-transform: uppercase; color: var(--c-ink-3);
               font-weight: 600; border-bottom: 1px solid var(--c-line); }
.cmp-row { padding: 11px 4px; font-size: 12px; cursor: pointer;
           border-bottom: 1px solid var(--c-line);
           transition: background-color 90ms ease, box-shadow 90ms ease; }
.cmp-row:last-child { border-bottom: none; }
/* A row you can open says so with a fill AND a line, which is how depth is
 * drawn everywhere else in this build (rule 1) — never with a shadow and never
 * with a shift, because a row that moves under the cursor moves the thing you
 * were about to click. The contacts rows cancel both: they are not clickable
 * and an affordance that lies is worse than none. */
@media (hover: hover) and (pointer: fine) {
  .cmp-row:hover { background: var(--c-sunk);
                   box-shadow: inset 2px 0 0 var(--c-line-strong); }
}
.cmp-row:focus-visible { outline: 2px solid var(--c-ink); outline-offset: -2px; }
.cmp-pager { display: flex; justify-content: center; padding: 10px 0 0; }

/* ── one campaign ───────────────────────────────────── */
.cmp-acts { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 14px; }
/* Stopping is one-way, so the control that does it looks like the alarm it
 * is — but only on hover, because a red button sitting there permanently is
 * one people stop seeing. */
.cmp-stop:hover { color: var(--c-alarm-ink); border-color: var(--c-alarm-line);
                  background: var(--c-alarm-bg); }

/* The wallet covers part of the list. `open` amber and hatched: unresolved,
 * not broken — the campaign is running, there is simply a decision to make. */
.cmp-fund, .cmp-why {
  margin: 0 0 14px; padding: 11px 12px; border-radius: var(--r-sm);
  background: var(--c-open-bg); border: 1px solid var(--c-open-line);
}
.cmp-fundhead { font-size: 12px; color: var(--c-open-ink); }
.cmp-fund .dev-help { color: var(--c-ink-2); }
/* Why a campaign is paused. Same `open` amber as the funding notice and for
 * the same reason: unresolved, not broken. Paused is a state somebody chose or
 * the money caused, and neither is a fault. */
.cmp-whytext { font-size: 12px; color: var(--c-open-ink); }
/* Archived is a shelf, not a fault: the quiet grey of the archived badge. */
.cmp-why.cmp-archived { background: var(--ui-fill-2); border-color: var(--c-line); }
.cmp-archived .cmp-whytext { color: var(--c-ink-2); }
/* The duplicate form's contacts, when they are being copied. */
.cmp-copy { padding: 12px 14px; border-radius: var(--ui-r-control);
            background: var(--ui-fill-2); box-shadow: inset 0 0 0 1px var(--ui-sep); }
.cmp-copy-say { margin: 0 0 4px; font-size: 13px; font-weight: 600; color: var(--c-ink); }
.cmp-copy .modal-help { margin: 0 0 10px; }
.cmp-copy button.small { margin: 0; min-height: 30px; padding: 6px 11px;
                         border-radius: var(--ui-r-control); }
.cmp-copy-back { margin: 0 0 10px; }
.cmp-copy-back .linkbtn { padding: 0; font-size: inherit; text-decoration: underline; }


/* ── the results card ───────────────────────────────────────────────────────
 * What the campaign DID, between the header and the progress bar. It is the
 * only card on this page a customer opens the page for, and it used to be a
 * ghost button called "Insights" sitting sixth in a row of seven.
 *
 * No new colours and no new shapes: the tiles are the .dev-fact pair — a figure
 * and the word for it — laid on a grid instead of a column, and the strip under
 * them is a list row. Nothing here goes green when the answer is good; a tile
 * that changed colour on a number would be this page deciding what a good
 * answer rate is, which is the customer's judgement and not ours (rule 2). */
/* An empty grid still draws its own border, and a 2px hairline box above the
 * "results appear here after the first calls" line is a card that looks broken
 * rather than early. */
.cmp-tiles:empty { display: none; }
.cmp-tiles { display: grid; gap: 1px; background: var(--c-line);
             border: 1px solid var(--c-line); border-radius: var(--r-xs);
             grid-template-columns: repeat(auto-fit, minmax(126px, 1fr)); }
/* The 1px grid `gap` over a line-coloured background IS the hairline between
 * tiles — one rule, drawn once, so tiles that wrap onto a second row cannot end
 * up with a doubled edge the way per-tile borders would. */
.cmp-tile { background: var(--c-surface); padding: 10px 12px; min-width: 0;
            display: flex; flex-direction: column; gap: 2px; }
.cmp-tile b { font-size: 17px; font-weight: 600; letter-spacing: -0.02em;
              color: var(--c-ink); line-height: 1.15; min-width: 0;
              overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cmp-tile span { font-size: 10.5px; letter-spacing: 0.04em; color: var(--c-ink-3);
                 font-weight: 500; overflow-wrap: anywhere; }
/* The card while the report is being built. It loops, which rule 3 forbids for
 * decoration and allows for a thing that is genuinely happening — the licence
 * #speaking takes. It exists only while a request is in the air. */
.cmp-skel { display: block; border-radius: var(--r-xs); background: var(--c-sunk);
            animation: pulse 1.4s ease-in-out infinite; }
.cmp-skel-n { height: 17px; width: 58%; margin-bottom: 3px; }
.cmp-skel-w { height: 8px; width: 82%; }

.cmp-res-quiet { margin: 0; }

/* ── the progress bar ───────────────────────────────── */
.cmp-bar-track { display: flex; width: 100%; height: 10px; overflow: hidden;
                 border-radius: var(--r-xs); background: var(--c-line);
                 margin: 4px 0 12px; }
.cmp-seg { display: block; height: 100%; min-width: 2px; }
/* The same track, thin, under a list row's two numbers. It has to be declared
 * AFTER .cmp-bar-track and not beside the list rules that use it: both are one
 * class deep, so whichever is written last wins, and a 10px bar with 12px of
 * margin inside a table row is a table row twice as tall as its neighbours. */
.cmp-minitrack { height: 3px; margin: 0; border-radius: 1px; }
/* The 2px floor above exists so a segment worth 0.3% of a campaign is still
 * visible in the detail bar. Here there is one segment and it is allowed to be
 * nothing: a 2px nub of accent on a campaign that has finished no calls says
 * something has, which is the one thing this bar must never say. */
.cmp-minitrack .cmp-seg { min-width: 0; }
.cmp-swatch { display: inline-block; width: 9px; height: 9px; flex: none;
              border-radius: 1px; border: 1px solid var(--c-line-strong); }
/* five tones, all of them already in the palette */
.t-done { background: var(--accent); border-color: var(--accent); }
.t-soft { background: var(--c-ink-4); border-color: var(--c-ink-4); }
.t-bad  { background: var(--c-alarm); border-color: var(--c-alarm); }
.t-mute { background: var(--c-line-strong); border-color: var(--c-line-strong); }
.t-live { background: var(--c-ink-2); border-color: var(--c-ink-2); }
.t-wait { background: var(--c-sunk); border-color: var(--c-line-strong); }
.cmp-legend { display: flex; flex-wrap: wrap; gap: 6px 16px; }
.cmp-leg { display: inline-flex; align-items: center; gap: 6px; font-size: 11.5px;
           color: var(--c-ink-3); }
.cmp-leg b { color: var(--c-ink); font-weight: 600; }

/* ── the contacts table ─────────────────────────────── */
/* The phone column has a floor as well as a share, and the time column has only
 * a share. That ordering is the whole responsive rule for this table: when the
 * card narrows, the thing that gives is the date — which degrades to "22 Aug"
 * and still reads — and never the number, which degrades to a truncated phone
 * number, which is a wrong phone number. */
.cmp-crowhead, .cmp-crow {
  /* contact · number · status · tries · last call · length · result · play */
  grid-template-columns: minmax(130px, 1.3fr) 132px 118px 44px 132px 64px minmax(110px, 1.2fr) 60px;
}
.cmp-crow { cursor: default; }
.cmp-crow:hover { background: transparent; box-shadow: none; }
/* A contact WITH a call on record opens the same inspector the Calls page does,
 * so it is a row you press and says so; one without is still inert, and an
 * affordance that lies is worse than none (the note over .cmp-row:hover). The
 * fill alone is hover — the ink rule below is reserved for the open call. */
.cmp-crow[data-h] { cursor: pointer; }
@media (hover: hover) and (pointer: fine) {
  .cmp-crow[data-h]:hover { background: var(--c-sunk); }
}
/* The row whose call is open in the panel — same two marks as .hist-row.sel,
 * for the same reason (a fill alone is what hover means). Drawn as an inset
 * shadow rather than a border, the way .cmp-row:hover already draws its rule:
 * this row is a grid, and two pixels of border would step every column in it
 * sideways as the arrow keys walk the table. */
.cmp-crow.sel { background: var(--c-sunk);
                box-shadow: inset 2px 0 0 var(--c-ink); }
.cmp-c-phone { color: var(--c-ink); font-weight: 500; min-width: 0;
               overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cmp-c-att { color: var(--c-ink-2); }
/* The outcome, with its tone as a mark beside it. The word is still what says
 * what happened (rule 2) — the swatch only lets a column of twenty rows sort
 * itself out at a glance, and it is the same swatch, the same size and the same
 * five tones as the progress legend two cards up. */
.cmp-c-cstatus { color: var(--c-ink-2); min-width: 0; display: flex;
                 align-items: center; gap: 7px; }
.cmp-c-cword { min-width: 0; overflow: hidden; text-overflow: ellipsis;
               white-space: nowrap; }
/* The last cell is a control and a time: press the circle to hear the call,
 * read the line beside it to know when it happened and how long it lasted.
 * .hist-play is reused rather than re-styled — it is the same act as the Calls
 * page, and a second circle that looked slightly different would read as a
 * different thing. Smaller here only because a table row is not a list row.
 *
 * It is no longer mono. It printed a 26-character call id, which is the one
 * kind of string that needs a monospace face; a date does not, and setting one
 * in mono is how a human-readable fact ends up looking like a machine's. */
.cmp-c-call { font-size: 11.5px; color: var(--c-ink-3);
              min-width: 0; display: flex; align-items: center; gap: 8px; }
.cmp-lastcall { min-width: 0; display: flex; align-items: baseline; gap: 5px;
                overflow: hidden; }
.cmp-when { color: var(--c-ink-2); white-space: nowrap; overflow: hidden;
            text-overflow: ellipsis; font-variant-numeric: tabular-nums; }
.cmp-dur:empty { display: none; }
.cmp-dur { color: var(--c-ink-3); white-space: nowrap; flex: none;
           font-variant-numeric: tabular-nums; }
.cmp-play { width: 26px; height: 26px; flex: none; }
.cmp-play svg { width: 11px; height: 11px; }

/* ── do-not-call ────────────────────────────────────── */
.cmp-dncrow { display: grid; grid-template-columns: minmax(0, 1fr) auto auto;
              align-items: center; gap: 10px; padding: 9px 0; font-size: 12px;
              border-top: 1px solid var(--c-line); }
.cmp-dncrow:first-child { border-top: none; }
.cmp-dncwhen { color: var(--c-ink-3); font-size: 11px; }

/* ── the create dialog ──────────────────────────────────────────────────────
 * A real <dialog> with showModal(), so the focus trap, Escape, the scrim and
 * the inert page all come from the platform. It borrows the gate card's
 * entrance (MAGI's `.dialog`: 200ms, scale from 0.97) rather than inventing a
 * second one, and the ::backdrop is the same scrim the phone sheet uses — the
 * only two dimming surfaces in the build. */
dialog.cmp-dlg { position: fixed; inset: 0; width: 100%; max-width: none;
                 height: 100%; max-height: none; margin: 0; padding: 0;
                 border: 0; background: transparent; overflow: hidden;
                 z-index: 60; }
dialog.cmp-dlg[open] { display: flex; align-items: center;
                       justify-content: center; }
dialog.cmp-dlg::backdrop { background: var(--c-scrim); }
.cmp-dlgpanel { width: min(720px, calc(100% - 32px)); max-height: 92vh;
                display: flex; flex-direction: column; overflow: hidden;
                background: var(--c-surface); border: 1px solid var(--c-line);
                border-radius: var(--r-md);
                box-shadow: 0 8px 28px rgba(27, 26, 22, 0.07);
                animation: dialog-in 200ms var(--ease-out) both; }
.cmp-dlgpanel:focus, .cmp-dlgpanel:focus-visible { outline: none; }
.cmp-dlghead { display: flex; align-items: center; justify-content: space-between;
               gap: 10px; padding: 12px 18px;
               border-bottom: 1px solid var(--c-line); }
.cmp-dlghead h2 { margin: 0; font-size: 15px; font-weight: 600;
                  letter-spacing: -0.015em; color: var(--c-ink); }
.cmp-dlghead .sheet-x { display: flex; width: 34px; height: 34px; padding: 0;
                        align-items: center; justify-content: center;
                        background: none; border: 0; color: var(--c-ink-3); }
.cmp-dlghead .sheet-x svg { width: 17px; height: 17px; }
.cmp-dlgbody { padding: 16px 18px 18px; overflow-y: auto;
               overscroll-behavior: contain; }
.cmp-dlgacts { display: flex; justify-content: flex-end; gap: 8px;
               margin-top: 18px; padding-top: 14px;
               border-top: 1px solid var(--c-line); }

/* ═══════════════════════════════════════════════════════════════════════════
 * THE MODAL — one shell for every dialog in the console.
 *
 * Before this, eleven dialogs had eleven looks: three scrims, five radii, three
 * close buttons (×, an SVG, a boxed "×"), labels in mono capitals in one and
 * sentence case in the next, and date/number fields that fell through the form
 * primitives entirely. A customer moving from Campaigns to Phone numbers to
 * Billing met a different product each time.
 *
 * Structure (every dialog, no exceptions):
 *
 *   <dialog class="modal modal-md" aria-labelledby="…">
 *     <div class="modal-panel" tabindex="-1" autofocus>   ← or <form class="modal-panel">
 *       <header class="modal-head">
 *         <div class="modal-titles"><p class="modal-eyebrow">…</p><h2>…</h2><p class="modal-sub">…</p></div>
 *         <button class="modal-x" data-modal-close aria-label="Close …">…</button>
 *       </header>
 *       <div class="modal-body">…</div>
 *       <footer class="modal-foot"><p class="modal-status"></p><button class="ghost">…</button><button>…</button></footer>
 *     </div>
 *   </dialog>
 *
 * A real <dialog> with showModal(): focus trap, Escape, inert page and the top
 * layer are the platform's. The <dialog> fills the viewport and is transparent,
 * so a press on the dimmed area lands on the dialog itself — core.js closes it
 * (unless `data-modal-static`, for forms you would lose by mis-tapping).
 *
 * Geometry follows the Apple-grade layer: a panel at --ui-r-panel lifted with
 * --ui-float, controls at --ui-r-control with the --ui-edge hairline and the
 * --ui-ring focus. Motion: in on the iOS sheet curve from 97% scale, out
 * faster than in; on a phone the panel is a bottom sheet that rises from the
 * edge it leaves by. Reduced motion keeps the fade and drops the movement. */
/* Layout lives on the base rule, not on [open]: while a dialog is leaving,
 * [open] is already gone but the element is still drawn for the exit
 * transition, and a centring that vanished with [open] dropped the panel into
 * the top-left corner mid-fade. Only `display` belongs to [open]. */
dialog.modal { position: fixed; inset: 0; width: 100%; height: 100%;
               max-width: none; max-height: none; margin: 0; padding: 24px;
               align-items: center; justify-content: center;
               border: 0; background: transparent; color: var(--c-ink);
               overflow: hidden; z-index: 60;
               opacity: 0;
               transition: opacity 160ms var(--ease-out),
                           overlay 160ms allow-discrete,
                           display 160ms allow-discrete; }
dialog.modal[open] { display: flex; opacity: 1; transition-duration: 220ms; }
@starting-style { dialog.modal[open] { opacity: 0; } }
/* The scrim fades with the panel instead of blinking off at the end. */
dialog.modal::backdrop { background: var(--c-scrim); opacity: 0;
                         transition: opacity 160ms var(--ease-out),
                                     overlay 160ms allow-discrete,
                                     display 160ms allow-discrete; }
dialog.modal[open]::backdrop { opacity: 1; transition-duration: 220ms; }
@starting-style { dialog.modal[open]::backdrop { opacity: 0; } }
html:has(dialog.modal[open]) { overflow: hidden; }

.modal-panel { position: relative; display: flex; flex-direction: column;
               width: min(var(--modal-w, 560px), 100%);
               max-height: min(var(--modal-h, 880px), calc(100dvh - 48px));
               margin: 0; overflow: hidden;
               background: var(--c-surface); color: var(--c-ink);
               border: 0; border-radius: var(--ui-r-panel);
               box-shadow: var(--ui-float);
               transform: none;
               transition: transform 150ms var(--ease-out);
               timeline-scope: --modal-scroll; }
dialog.modal[open] .modal-panel { transition: transform 320ms var(--ui-spring); }
/* Out the way it came in: a touch smaller, in place, quicker than it arrived. */
dialog.modal:not([open]) .modal-panel { transform: scale(0.97); }
@starting-style { dialog.modal[open] .modal-panel { transform: scale(0.97); } }
/* "Not now": a press outside a dialog that cannot close yet (work in
 * progress) gets a small give, so the press is clearly heard and refused. */
.modal-panel.modal-nudge { animation: modal-nudge 260ms var(--ease-out); }
@keyframes modal-nudge { 0% { transform: scale(1); } 40% { transform: scale(1.012); }
                         100% { transform: scale(1); } }
.modal-panel:focus, .modal-panel:focus-visible { outline: none; }

/* Widths, by what the dialog holds: a question, a form, a long form, a gallery. */
.modal-sm { --modal-w: 420px; }
.modal-md { --modal-w: 540px; }
.modal-lg { --modal-w: 680px; }
.modal-xl { --modal-w: 1040px; --modal-h: 780px; }
.modal-xl .modal-panel { height: min(var(--modal-h), calc(100dvh - 48px)); }

.modal-head { display: flex; align-items: flex-start; gap: 16px;
              padding: 20px 18px 14px 24px; flex: none;
              /* the scroll-edge line: absent at rest, drawn once the body has
               * scrolled under the title (where scroll timelines exist) */
              animation: modal-edge linear both;
              animation-timeline: --modal-scroll;
              animation-range: 0 16px; }
@keyframes modal-edge { from { box-shadow: 0 1px 0 transparent; }
                        to   { box-shadow: 0 1px 0 var(--ui-sep); } }
.modal-titles { flex: 1; min-width: 0; }
.modal-eyebrow { margin: 0 0 3px; font: 500 12px/1.4 var(--sans);
                 letter-spacing: 0; text-transform: none; color: var(--c-ink-3); }
.modal-head h2 { margin: 0; font: 650 18px/1.3 var(--sans); letter-spacing: -0.02em;
                 color: var(--c-ink); overflow-wrap: anywhere; }
.modal-head h2:focus { outline: none; }
.modal-sub { margin: 5px 0 0; font-size: 13px; line-height: 1.5;
             color: var(--c-ink-3); max-width: 62ch; }
/* Close: a quiet filled circle in the corner, the same on every dialog. */
.modal-x { flex: 0 0 28px; display: grid; place-items: center;
           width: 28px; height: 28px; min-height: 0; margin: -2px 0 0; padding: 0;
           border: 0; border-radius: 999px;
           background: var(--ui-fill-2); color: var(--c-ink-3); }
.modal-x:hover { background: var(--ui-fill); color: var(--c-ink); }
.modal-x:active { background: var(--ui-fill-3); transform: scale(0.94); }
.modal-x svg { width: 13px; height: 13px; }

.modal-body { flex: 1 1 auto; min-height: 0; overflow-y: auto;
              overscroll-behavior: contain;
              padding: 6px 24px 24px;
              scroll-timeline: --modal-scroll block; }
.modal-body > :first-child { margin-top: 0; }
.modal-body > :last-child { margin-bottom: 0; }
.modal-body p { line-height: 1.6; }
.modal-copy { margin: 0 0 14px; font-size: 13px; line-height: 1.6; color: var(--c-ink-2); }

/* The footer stays put while the body scrolls: the next step is always in
 * reach. A status line, when the dialog has one, takes the left. */
.modal-foot { display: flex; align-items: center; justify-content: flex-end;
              flex-wrap: wrap; gap: 8px; flex: none;
              padding: 14px 24px; border-top: 1px solid var(--ui-sep);
              background: var(--c-surface); }
.modal-foot[hidden] { display: none; }
.modal-status { flex: 1 1 200px; min-width: 0; margin: 0; font-size: 12.5px;
                line-height: 1.5; color: var(--c-ink-3); }
.modal-status:empty { visibility: hidden; }
/* Inline actions at the end of a body, for a dialog whose buttons must live
 * inside a form that is itself the body. Same row, same buttons. */
.modal-acts { display: flex; align-items: center; justify-content: flex-end;
              flex-wrap: wrap; gap: 8px; margin-top: 22px; }
:is(.modal-foot, .modal-acts) button { margin: 0; min-height: 34px;
                                       padding: 8px 15px; font-size: 13px;
                                       border-radius: var(--ui-r-control); }
.modal.danger :is(.modal-foot, .modal-acts) button:not(.ghost) {
  background: var(--c-alarm-bg); color: var(--c-alarm-ink);
  border-color: var(--c-alarm); font-weight: 600; }
.modal.danger :is(.modal-foot, .modal-acts) button:not(.ghost):hover {
  background: var(--c-alarm-bg-hover); }

/* ── fields inside a modal ────────────────────────────────────────────────
 * The page-wide `label` is MAGI's 10px eyebrow; inside a dialog a label names
 * the field beneath it, so it reads as a sentence. And every input type gets
 * the same box — date, time and number used to fall through to the browser. */
.modal label, .modal .modal-label { font: 500 12.5px/1.4 var(--sans); letter-spacing: -0.003em;
               text-transform: none; color: var(--c-ink-2); }
.modal :is(input:not([type=checkbox], [type=radio], [type=file], [type=range], [type=color]),
           select, textarea) {
  width: 100%; min-height: 36px; margin: 0; padding: 8px 11px;
  font: 400 13.5px/1.35 var(--sans); letter-spacing: 0; color: var(--c-ink);
  background-color: var(--c-surface); border: 1px solid var(--ui-edge);
  border-radius: var(--ui-r-control); box-shadow: none;
  transition: border-color 120ms ease, box-shadow 120ms ease; }
.modal textarea { min-height: 92px; line-height: 1.55; resize: vertical; }
.modal select:not([multiple]) {
  appearance: none; -webkit-appearance: none; padding-right: 32px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' fill='none' stroke='%236f6c65' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3.5 4.75 6 2.5l2.5 2.25M3.5 7.25 6 9.5l2.5-2.25'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 10px center;
  background-size: 12px 12px; }
.modal :is(input, select, textarea):hover { border-color: rgba(27, 26, 22, 0.26); }
.modal :is(input:not([type=checkbox], [type=radio], [type=file]), select, textarea):focus {
  outline: none; border-color: rgba(27, 26, 22, 0.34); box-shadow: var(--ui-ring); }
.modal :is(input, select, textarea):disabled { background-color: var(--c-sunk);
                                               color: var(--c-ink-3); }
.modal input::-webkit-calendar-picker-indicator { opacity: 0.55; cursor: pointer; }
.modal input[type=checkbox], .modal input[type=radio] {
  width: 16px; height: 16px; margin: 2px 0 0; flex: none; accent-color: var(--c-ink); }
.modal input[type=file] { width: 100%; font: 400 12.5px/1.4 var(--sans); color: var(--c-ink-2); }
.modal input[type=file]::file-selector-button {
  margin-right: 12px; padding: 7px 12px; font: 500 12.5px/1 var(--sans);
  color: var(--c-ink); background: var(--ui-fill-2); border: 0;
  border-radius: var(--ui-r-control); cursor: pointer; }
.modal input[type=file]::file-selector-button:hover { background: var(--ui-fill); }

/* A field: label, control, help — one rhythm everywhere. */
.modal-field { display: flex; flex-direction: column; gap: 6px; margin: 0 0 16px; min-width: 0; }
.modal-field > label { display: block; }
.modal-help, .modal .dev-help { margin: 0; font-size: 12px; line-height: 1.5; color: var(--c-ink-3); }
.modal-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 14px; }
.modal-grid.three { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.modal-check { display: flex; align-items: flex-start; gap: 10px; margin: 4px 0 16px;
               font: 400 13px/1.5 var(--sans); color: var(--c-ink); cursor: pointer; }
.modal-check small { display: block; margin-top: 2px; font-size: 12px; line-height: 1.5;
                     color: var(--c-ink-3); }
/* A group of fields under its own small heading, separated by a hairline. */
.modal-section { margin: 20px 0 0; padding-top: 18px; border-top: 1px solid var(--ui-sep); }
.modal-section > h3 { margin: 0 0 12px; font: 600 13.5px/1.4 var(--sans);
                      letter-spacing: -0.01em; color: var(--c-ink); }
/* A quiet inset card for a fact the form depends on (a price, a state). */
.modal-note { margin: 0 0 16px; padding: 14px 16px; border-radius: var(--ui-r-card);
              background: var(--c-sunk); font-size: 13px; line-height: 1.55;
              color: var(--c-ink-2); }
.modal-note strong { color: var(--c-ink); }
.modal-status.err { color: var(--c-alarm-ink); }

@media (max-width: 640px) {
  /* A sheet from the bottom edge: the thumb is already there. */
  dialog.modal { align-items: flex-end; padding: 0; }
  .modal-panel, .modal-xl .modal-panel {
    width: 100%; height: auto; border-radius: var(--ui-r-panel) var(--ui-r-panel) 0 0;
    max-height: calc(100dvh - 12px - env(safe-area-inset-top));
    padding-bottom: env(safe-area-inset-bottom); }
  .modal-xl .modal-panel { height: calc(100dvh - 12px - env(safe-area-inset-top)); }
  dialog.modal:not([open]) .modal-panel { transform: translateY(100%); }
  dialog.modal:not([open]) .modal-panel { transition: transform 200ms var(--ease-out); }
  @starting-style { dialog.modal[open] .modal-panel { transform: translateY(100%); } }
  .modal-head { padding: 18px 16px 12px 20px; }
  /* a thumb-sized circle, still a circle */
  .modal-x { flex-basis: 34px; width: 34px; height: 34px; min-height: 34px; margin-top: -4px; }
  .modal-body { padding: 4px 20px 20px; }
  .modal-foot { padding: 12px 20px; }
  .modal-foot button, .modal-acts button { flex: 1 1 auto; min-height: 42px; }
  .modal-status { flex-basis: 100%; }
  .modal-grid, .modal-grid.three { grid-template-columns: minmax(0, 1fr); }
  /* 16px stops iOS zooming the page when a field takes focus */
  .modal :is(input:not([type=checkbox], [type=radio], [type=file]), select, textarea) {
    font-size: 16px; min-height: 42px; }
}
@media (prefers-reduced-motion: reduce) {
  dialog.modal .modal-panel, dialog.modal[open] .modal-panel,
  dialog.modal:not([open]) .modal-panel { transition: none; }
  .modal-panel.modal-nudge { animation: none; }
  dialog.modal:not([open]) .modal-panel { transform: none; }
  @starting-style { dialog.modal[open] .modal-panel { transform: none; } }
}

.cmp-field { margin-bottom: 14px; min-width: 0; }
.cmp-field > label { display: block; margin-bottom: 5px; }
.cmp-field input[type=text], .cmp-field input[type=tel],
.cmp-field input[type=date], .cmp-field input[type=time],
.cmp-field input[type=number], .cmp-field select { width: 100%; }
.cmp-field .dev-help { margin-top: 5px; }
.cmp-two { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
           gap: 0 12px; }
.cmp-three { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
             gap: 0 12px; }

.cmp-slot { display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr) 28px;
            align-items: center; gap: 10px; margin-bottom: 8px; }
.cmp-slot-dash { color: var(--c-ink-3); font-size: 12.5px; }
.cmp-slot-x { display: grid; place-items: center; width: 28px; height: 28px; min-height: 0; padding: 0;
              border: 0; border-radius: 999px; background: none; color: var(--c-ink-3); }
.cmp-slot-x svg { width: 12px; height: 12px; }
.cmp-slot-x:hover { color: var(--c-alarm-ink); background: var(--c-alarm-bg); }
.cmp-slot-x:disabled { opacity: 0.35; }
.cmp-slotfoot { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.cmp-slotfoot button.small { margin: 0; min-height: 30px; padding: 6px 11px;
                             font-size: 12.5px; border-radius: var(--ui-r-control); }
.cmp-slotfoot .modal-help { flex: 1 1 220px; }

.cmp-contacts-help { margin: -4px 0 12px; }
.cmp-filerow { display: flex; align-items: center; gap: 8px; margin-bottom: 10px;
               flex-wrap: wrap; }
.cmp-filerow input[type=file] { flex: 1; min-width: 0; }
.cmp-filerow button.small { margin: 0; min-height: 30px; padding: 6px 11px;
                            font-size: 12.5px; border-radius: var(--ui-r-control); }
.cmp-contacts textarea { min-height: 120px; font-family: var(--mono);
                         font-size: 12.5px; }
.cmp-modal .modal-help code { font-family: var(--mono); font-size: 11.5px; padding: 1px 4px;
                              border-radius: 4px; background: var(--ui-fill-2); color: var(--c-ink-2); }
.cmp-modal .cmp-preview { border: 0; border-radius: var(--ui-r-card); padding: 12px 14px; }
.cmp-modal .cmp-bad { border-radius: var(--ui-r-control); }
.cmp-modal .cmp-upload { margin-top: 16px; }

/* What the browser made of the file, before a campaign exists. The point of
 * showing it is the LAST block — the rows that cannot be dialled and why. */
.cmp-preview { margin-top: 10px; border: 1px solid var(--c-line);
               border-radius: var(--r-sm); background: var(--c-sunk);
               padding: 10px 12px; }
.cmp-prevsum { font-size: 12px; font-weight: 600; color: var(--c-ink);
               margin-bottom: 8px; }
.cmp-prevrow { display: grid; grid-template-columns: 150px minmax(0, 1fr);
               gap: 10px; font-size: 11.5px; color: var(--c-ink-2);
               padding: 2px 0; font-variant-numeric: tabular-nums; }
.cmp-prevvars { color: var(--c-ink-3); min-width: 0; overflow: hidden;
                text-overflow: ellipsis; white-space: nowrap; }
.cmp-prevmore { font-size: 11.5px; color: var(--c-ink-3); padding-top: 4px; }
.cmp-bad { margin-top: 10px; padding: 9px 10px; border-radius: var(--r-xs);
           background: var(--c-open-bg); border: 1px solid var(--c-open-line); }
.cmp-badhead { font-size: 11.5px; font-weight: 600; color: var(--c-open-ink);
               margin-bottom: 4px; }
.cmp-badrow { font-size: 11.5px; color: var(--c-ink-2); line-height: 1.5;
              overflow-wrap: anywhere; }

.cmp-upload { margin-top: 12px; }
.cmp-upload .cmp-bar-track { margin: 0 0 6px; }
.cmp-upload .cmp-bar-track i { display: block; height: 100%; width: 0%;
                               background: var(--c-ink-2);
                               transition: width 180ms var(--ease-out); }

/* ── the pane swap, and the card arriving ───────────────────────────────────
 * Four panes in one view and no URL between them, so nothing but motion says a
 * navigation happened. 180ms and 4px is the smallest amount that reads as one —
 * enough to see, not enough to wait for. It fires on the swap only: a `hidden`
 * section that becomes visible restarts its animation, and a repaint of a pane
 * already on screen does not touch it, so the poll cannot make the page blink
 * every four seconds.
 *
 * The Results card gets its own, because it arrives later than the pane it is
 * in — the report is a separate request — and a card that appears with no
 * motion at all reads as a page that jumped. */
#camplistpane, #campdetail, #campdnc {
  animation: cmp-pane-in 180ms var(--ease-out) both;
}
.cmp-res { animation: cmp-pane-in 220ms var(--ease-out) both; }
@keyframes cmp-pane-in { from { opacity: 0; transform: translateY(4px); }
                         to   { opacity: 1; transform: none; } }

@media (max-width: 760px) {
  .cmp-two, .cmp-three { grid-template-columns: minmax(0, 1fr); }
  .cmp-facts { grid-template-columns: minmax(0, 1fr); }
  .cmp-dlgpanel { width: 100%; max-width: none; height: 100%; max-height: none;
                  border: 0; border-radius: 0; }
  .cmp-acts button { flex: 1 1 auto; }
  /* Two tiles a row rather than the auto-fit's one: six numbers stacked into a
   * column is a scroll, and these six are meant to be one glance. */
  .cmp-tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .cmp-tile b { font-size: 15px; }
  /* The summary is the first thing to go when there is no room for three
   * columns — the name and what they want are the row; the sentence is in the
   * report. */
}
@media (prefers-reduced-motion: reduce) {
  .cmp-dlgpanel { animation: none; }
  .cmp-upload .cmp-bar-track i { transition: none; }
  /* The swap keeps its opacity — that is what says a navigation happened — and
   * loses the movement. The skeleton stops pulsing and is simply held, which
   * still reads as "not this, yet". */
  #camplistpane, #campdetail, #campdnc,
  .cmp-res { animation: fade-in 160ms var(--ease-out) both; }
  .cmp-skel { animation: none; opacity: 0.6; }
  .cmp-row { transition: none; }
}

/* ═══════════════════════════════════════════════════════════════════════════
 * THE FEEDBACK PASS — five additions that all serve one rule: an answer to a
 * press lands near the press, in this console's own language.
 *
 *   1. #confirmdlg  the house confirm, replacing the last native confirm()s
 *   2. .agent-status feedback chip inside the agent card header
 *   3. #mutebtn     the live call's second control (browser calls only)
 *   5. lat ↔ chat   latency rows open their turn in the transcript
 * ═══════════════════════════════════════════════════════════════════════════ */

/* ── 1 · the house confirm ──────────────────────────────────────────────────
 * The shared modal at question size, drawn as an alert: title and consequence,
 * then the two answers — no close button, no footer rule. The destructive
 * answer is `.modal.danger` (tinted alarm, never a solid red slab) and focus
 * starts on Cancel from core.js, so Enter-through is not the default on a
 * delete. */
.modal-alert .modal-head { padding: 22px 24px 4px; animation: none; }
.modal-alert .modal-sub { font-size: 13px; color: var(--c-ink-2); overflow-wrap: anywhere; }
.modal-alert .modal-sub[hidden] { display: none; }
.modal-alert .modal-body { padding: 10px 24px 0; }
.modal-alert .modal-foot { border-top: 0; padding: 18px 24px 22px; }

/* ── 2 · feedback where the hand was ────────────────────────
 * Rename/create/delete/template/draft all report HERE now, beside the buttons
 * that triggered them, instead of in the rail's status line a metre away.
 * Fades out on itself (agentSay()); reduced motion just flips it. */
.agent-status { font-size: 11px; font-weight: 500; color: var(--c-ink-2);
                opacity: 0; white-space: nowrap; overflow: hidden;
                text-overflow: ellipsis;
                transition: opacity 140ms var(--ease-out); }
.agent-status.on { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  .agent-status { transition: none; }
}

/* ── 3 · mute ───────────────────────────────────────────────
 * Revealed by startCall(), hidden by stopCall(); sits between End-call and
 * the mic meter, because it answers "what exactly am I sending?". Resting =
 * ghost (a control offered); muted = solid ink (a state asserted) — weight
 * and ink carry the state per rule 2, and the label says it twice over. */
.mutebtn { display: flex; align-items: center; justify-content: center;
           gap: 7px; width: 100%; padding: 8px 10px; font-size: 12px;
           font-weight: 500; line-height: 1;
           background: var(--c-surface); color: var(--c-ink-2);
           border: 1px solid var(--c-line-strong); border-radius: var(--r-sm);
           cursor: pointer;
           transition: border-color 90ms ease, background-color 90ms ease; }
.mutebtn svg { width: 14px; height: 14px; flex: 0 0 auto; }
.mutebtn:hover { color: var(--c-ink); border-color: var(--c-ink-4); }
.mutebtn.muted { background: var(--c-ink); border-color: var(--c-ink);
                 color: var(--c-paper); }
/* call.js toggles the hidden attribute; display:flex would beat the UA rule. */
.mutebtn[hidden] { display: none; }

/* ── 5 · the chart opens the transcript ─────────────────────
 * Latency rows gained data-turn + tabindex; cursor and focus say they are
 * controls. The flash is one-shot on a real event (the click), never ambient:
 * the bubble lifts once under a hairline ring and settles by itself. */
.lat-row.lat-link { cursor: pointer; }
.msg.hit { animation: msg-hit 1500ms var(--ease-out) both; }
@keyframes msg-hit {
  0%   { box-shadow: inset 0 0 0 2px var(--c-ink); background: var(--c-sunk); }
  70%  { box-shadow: inset 0 0 0 2px var(--c-ink); }
  100% { box-shadow: inset 0 0 0 0 rgba(27, 26, 22, 0); }
}
@media (prefers-reduced-motion: reduce) {
  .msg.hit { animation: none; box-shadow: inset 0 0 0 2px var(--c-ink); }
}

/* ── the support cat's house ──────────────────────────────────────────────
 * A fixed-size scene: the cat sits up inside it without moving the wallet.
 * Posture belongs to p-* wrappers; small gestures live on c-* children. */
/* A charcoal silhouette with quiet eyes and a few static star flecks. */
#support, .cat-talk {
  --cat-fur: var(--c-dark-line);
  --cat-cream: var(--cat-fur);
  --cat-stripe: var(--cat-fur);
  --cat-cut: var(--c-on-dark-3);
  --cat-ear: var(--cat-fur);
  --cat-nose: var(--c-on-dark-3);
  --cat-collar: var(--c-dark-line);
  --cat-edge: var(--c-ink-2);
  --cat-tag: var(--c-on-dark-2);
}
#support { position: relative; display: flex; flex-direction: column;
           gap: 4px; padding: 0 4px 8px; }
#supbubble { position: relative; display: flex; align-items: center; gap: 7px;
             min-height: 42px; box-sizing: border-box;
             background: var(--c-dark-sunk); border: 1px solid var(--c-dark-line);
             border-radius: 10px; padding: 8px 10px; transform-origin: 50% 100%;
             transition: opacity 200ms var(--ease-out), transform 200ms var(--ease-out); }
#supbubble[hidden] { display: flex; visibility: hidden; opacity: 0;
                     transform: scale(.96) translateY(3px); pointer-events: none; }
#supbubble::after { content: ''; position: absolute; top: 100%; left: 50%;
                    margin-left: -5px; border: 5px solid transparent;
                    border-top-color: var(--c-dark-line); border-bottom: 0; }
#supsays { margin: 0; flex: 1; min-width: 0; font-size: 12px; line-height: 1.4;
           color: var(--c-on-dark-2); transition: opacity 150ms ease; }
#supsays.swap { opacity: 0; }
#supend { flex: none; font-size: 10px; min-height: 28px; padding: 4px 7px;
          color: var(--c-on-dark); border-color: var(--c-dark-line); background: none; }
#supbubble:not(.live) #supend { display: none; }
#supcat { position: relative; display: flex; flex-direction: column; align-items: center;
          width: 100%; padding: 0 0 5px; border: 0; border-radius: 8px;
          background: none; color: var(--c-on-dark-2); cursor: pointer;
          font-family: inherit; line-height: 1.4; }
#supcat:focus-visible { outline: 2px solid var(--cat-tag); outline-offset: 3px; }
#supcat .sup-scene { position: relative; display: block; width: 100%;
                     height: 92px; max-width: 136px; }
.sup-house { position: absolute; width: 100%; height: 100%; inset: 0; }
.sup-house-wall { fill: var(--c-dark-sunk); stroke: var(--c-dark-line); stroke-width: 1.5; }
.sup-house-roof { stroke: var(--c-on-dark-3); stroke-opacity: .3; stroke-width: 5; stroke-linejoin: round;
                  stroke-linecap: round; transition: stroke 220ms ease; }
.sup-house-seam { stroke: var(--c-dark-line); stroke-width: 1.2; stroke-linecap: round; }
.sup-house-door { fill: var(--c-dark); stroke: var(--c-dark-line); stroke-width: 1.5;
                  transition: fill 350ms ease, stroke 350ms ease; }
.sup-house-paw { color: var(--c-on-dark-3); opacity: .4; }
.sup-cushion { fill: var(--c-dark-sunk); stroke: var(--c-dark-line); stroke-width: 1.5; }
#supcat .cat-talk { position: absolute; left: 50%; bottom: 6px;
                    width: 72px; height: 84px; overflow: visible; color: var(--cat-fur);
                    transform: translateX(-50%); transform-origin: 50% 100%;
                    transition: transform 450ms cubic-bezier(.2,.8,.2,1); }
.sup-name { display: flex; align-items: center; gap: 6px; font-size: 12.5px;
            font-weight: 600; letter-spacing: -0.006em; color: var(--c-on-dark); }
#supaction { color: var(--cat-tag); font-size: 11px; margin-top: 3px; }
.sup-voice-mark { display: inline-flex; align-items: center; gap: 2px; height: 12px;
                  width: 10px; margin-right: -16px; opacity: 0; transition: opacity 180ms ease; }
.sup-voice-mark i { width: 2px; height: 5px; border-radius: 2px; background: var(--cat-tag); }
.sup-voice-mark i:nth-child(2) { height: 11px; }
.sup-voice-mark i:nth-child(3) { height: 7px; }
#support.big .sup-voice-mark { opacity: 1; }
#support.big .sup-house-door { fill: var(--c-dark); stroke: var(--cat-edge); }
#support.big .sup-house-roof { stroke: var(--c-on-dark-3); }
#supcat:active { transform: none; }
#supcat:active .cat-talk { transform: translateX(-50%) scale(.98); transition-duration: 100ms; }
@media (hover: hover) and (pointer: fine) {
  #supcat:hover .sup-house-roof { stroke: var(--cat-tag); }
  #supcat:hover #supaction { text-decoration: underline; text-underline-offset: 3px; }
  #supcat:hover .pose-rest .p-head { transform: translate(-3px, 26px) rotate(-4deg); }
  #supcat:hover .pose-rest .c-awake-eyes { opacity: 1; transform: scaleY(.75); }
  #supcat:hover .pose-rest .c-sleep-eyes { opacity: 0; }
}
#supcat:focus-visible .pose-rest .p-head { transform: translate(-3px, 26px) rotate(-4deg); }
#supcat:focus-visible .pose-rest .c-awake-eyes { opacity: 1; }
#supcat:focus-visible .pose-rest .c-sleep-eyes { opacity: 0; }

/* A tucked loaf becomes a seated cat: head first, shoulders next, paws last.
 * Transitions reverse from the current pose even if a call is cancelled midway. */
.cat-talk { color: var(--cat-fur); }
.cat-talk .c-body > path:first-child,
.cat-talk .c-head > path:first-of-type,
.cat-talk .c-ear > path:first-child,
.cat-talk .c-paw-l > ellipse, .cat-talk .c-paw-r > ellipse {
  stroke: var(--cat-edge); stroke-width: 1.2; stroke-linejoin: round;
}
.cat-talk .c-tail > path:first-child { stroke: var(--cat-edge); }
.cat-talk .c-eyes > ellipse { fill: var(--c-on-dark-2); }
.cat-talk .c-whisk { stroke: var(--c-on-dark-3); opacity: .35; }
.cat-talk .c-stars { fill: var(--c-on-dark-3); opacity: .5; }

.cat-talk .p-tail, .cat-talk .p-body, .cat-talk .p-head,
.cat-talk .p-pawl, .cat-talk .p-pawr, .cat-talk .p-yarn {
  transition: transform 620ms cubic-bezier(.22,.8,.25,1);
}
.cat-talk .p-tail { transform-origin: 97px 121px; }
.cat-talk .p-body { transform-origin: 60px 126px; }
.cat-talk .p-head { transform-origin: 60px 74px; }
.cat-talk .p-pawl { transform-origin: 45px 121px; }
.cat-talk .p-pawr { transform-origin: 75px 121px; }
.cat-talk .p-yarn { transform-origin: 16px 126px; }
.cat-talk.pose-rest .p-body { transform: translateY(5px) scale(1.12, .65); }
.cat-talk.pose-rest .p-head { transform: translate(-5px, 32px) rotate(-8deg); }
.cat-talk.pose-rest .p-pawl { transform: translate(-4px, 1px) rotate(-8deg); }
.cat-talk.pose-rest .p-pawr { transform: translate(3px, 1px) rotate(6deg); }
.cat-talk.pose-rest .p-tail { transform: translate(-2px, 3px) rotate(9deg); }
.cat-talk.pose-talk .p-head { transform: none; }
.cat-talk.pose-talk .p-body { transform: none; transition-delay: 60ms; }
.cat-talk.pose-talk .p-pawl, .cat-talk.pose-talk .p-pawr { transform: none; transition-delay: 100ms; }
.cat-talk.pose-talk .p-tail { transform: rotate(-8deg); transition-delay: 140ms; }
.cat-talk.pose-talk .p-yarn { transform: translate(-4px, 2px) rotate(-25deg); transition-delay: 100ms; }
.cat-talk .c-ear-r { transform-origin: 78px 30px; }
.cat-talk .c-awake-eyes, .cat-talk .c-sleep-eyes {
  transform-origin: 60px 46px; transition: opacity 160ms ease, transform 240ms ease;
}
.cat-talk .c-sleep-eyes { opacity: 0; }
.cat-talk.pose-rest .c-awake-eyes { opacity: 0; transform: scaleY(.3); }
.cat-talk.pose-rest .c-sleep-eyes { opacity: 1; }
.cat-talk.pose-rest.f-perk .c-awake-eyes { opacity: 1; transform: scaleY(.8); }
.cat-talk.pose-rest.f-perk .c-sleep-eyes { opacity: 0; }

/* Slow breathing and a small tail curl keep the resting cat calm. */
@keyframes c-breathe { 0%, 100% { transform: scaleY(1); }
                       50% { transform: scaleY(1.025) scaleX(1.008); } }
@keyframes c-bob { 0%, 100% { transform: translateY(0); }
                   50% { transform: translateY(-1px); } }
@keyframes c-swish { 0%, 70%, 100% { transform: rotate(0deg); }
                     82% { transform: rotate(-5deg); } 92% { transform: rotate(2deg); } }
@keyframes c-blink { 0%, 94%, 100% { transform: scaleY(1); }
                     97% { transform: scaleY(.06); } }
@keyframes c-earflick { 0%, 100% { transform: rotate(0deg); }
                        30% { transform: rotate(-12deg); } 65% { transform: rotate(4deg); } }
@keyframes c-tilt { 0%, 100% { transform: rotate(0deg); }
                    40%, 60% { transform: rotate(-4deg); } }
@keyframes c-perk { 0%, 100% { transform: translateY(0); }
                    40%, 60% { transform: translateY(-3px); } }
.cat-talk .c-body { transform-origin: 60px 126px; animation: c-breathe 4.8s ease-in-out infinite; }
.cat-talk .c-head { transform-origin: 60px 74px; animation: c-bob 4.8s ease-in-out infinite; }
.cat-talk .c-tail { transform-origin: 97px 121px; animation: c-swish 8.2s ease-in-out infinite; }
.cat-talk .c-eyes { transform-origin: 60px 46px; animation: c-blink 6.7s ease-in-out infinite; }
.cat-talk.f-ear .c-ear-r { animation: c-earflick 620ms ease-in-out; }
.cat-talk.f-tilt .c-head { animation: c-tilt 1400ms ease-in-out; }
.cat-talk.f-perk .c-head { animation: c-perk 1200ms ease-in-out; }
/* Mouth shapes are driven by actual playback, with no extra CSS delay. */
.cat-talk .mouth { display: none; }
.cat-talk.m0 .m0, .cat-talk.m1 .m1, .cat-talk.m2 .m2 { display: block; }

/* The house compacts with the rail; call and rest always use the same height. */
@media (max-height: 860px) {
  #supcat .sup-scene { height: 80px; max-width: 118px; }
  #supcat .cat-talk { width: 62px; height: 72px; bottom: 5px; }
  #support { padding-bottom: 3px; }
}
@media (max-height: 740px) {
  /* The occasional invitation can float above its house in a short rail. */
  #supbubble { position: absolute; bottom: calc(100% + 3px); left: 0; right: 0; z-index: 3; }
  #supcat .sup-scene { height: 76px; max-width: 110px; }
  #supcat .cat-talk { width: 58px; height: 68px; bottom: 3px; }
}
@media (min-width: 1081px) {
  /* Extra admin links and browser zoom must not put the wallet below reach. */
  .nav { overflow-y: auto; scrollbar-width: thin;
         scrollbar-color: var(--c-dark-line) var(--c-dark); }
}
@media (max-width: 1080px) { #support { display: none; } }
@media (prefers-reduced-motion: reduce) {
  #support *, #support *::before, #support *::after {
    animation: none !important; transition: none !important;
  }
  .cat-talk .c-body, .cat-talk .c-head, .cat-talk .c-tail, .cat-talk .c-eyes,
  .cat-talk .c-ear-r { animation: none; }
  .cat-talk .p-tail, .cat-talk .p-body, .cat-talk .p-head,
  .cat-talk .p-pawl, .cat-talk .p-pawr, .cat-talk .p-yarn,
  .cat-talk .c-awake-eyes, .cat-talk .c-sleep-eyes { transition: none; }
  #supcat:active .cat-talk { transform: translateX(-50%); }
  #supbubble[hidden] { transform: none; }
}

/* ── billing statement ─────────────────────────────────────────────────────
 * The four summary figures reuse .hist-stat so a number means the same thing
 * here as it does on Call history; only the column count changes. Credits take
 * the accent because a credit is the one movement that is good news, and the
 * accent is already this console's green. */
.stm-sum { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.stm-credit { color: var(--accent); }
.stm-bal { color: var(--c-ink); font-weight: 600; }
.stm-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
               margin-top: 14px; }
.stm-hint { font-size: 11.5px; color: var(--c-ink-3); }
/* Tables, not grids: a statement is read across a row, and a row that reflows
 * into stacked pairs stops being a statement. It scrolls in its own box so the
 * page never moves sideways. */
.stm-scroll { overflow-x: auto; }
.stm-table { border-collapse: collapse; width: 100%; font-size: 12.5px;
             font-variant-numeric: tabular-nums; }
.stm-table th { font-size: 10px; font-weight: 600; letter-spacing: 0.08em;
                text-transform: uppercase; color: var(--c-ink-3);
                text-align: left; padding: 0 12px 7px 0; white-space: nowrap;
                border-bottom: 1px solid var(--c-line-strong); }
.stm-table td { padding: 9px 12px 9px 0; color: var(--c-ink-2);
                border-bottom: 1px solid var(--c-line); white-space: nowrap; }
.stm-table tr:last-child td { border-bottom: none; }
.stm-table th.r, .stm-table td.r { text-align: right; padding-right: 0; }
/* The wallet in the rail. The balance is the largest thing in it because it is
 * the only figure that decides anything; the link is the only thing that takes
 * a press, which is why nothing else in the panel reacts to the cursor. */
.wallet { gap: 4px; margin: 0 2px; padding: 10px 10px 9px;
          background: var(--c-dark-sunk); border: 1px solid var(--c-dark-line);
          border-radius: 10px; }
.wallet-bal { display: flex; align-items: baseline; gap: 5px; }
.wallet-bal b { color: var(--c-on-dark); font-size: 20px; font-weight: 650;
                letter-spacing: -0.022em; line-height: 1.15;
                font-variant-numeric: tabular-nums; }
.wallet-bal span { font-size: 11px; color: var(--c-on-dark-3); }
.wallet-link { background: none; border: 0; padding: 0; cursor: pointer;
               font: inherit; font-size: 11.5px; font-weight: 500;
               color: var(--accent-on-dark); line-height: 1.4;
               border-bottom: 1px solid transparent; }
.wallet-link:hover { border-bottom-color: var(--accent-on-dark);
                     transform: none; background: none; }
.wallet-link:focus-visible { outline: 2px solid var(--accent-on-dark);
                             outline-offset: 2px; border-radius: 2px; }
@media (max-width: 700px) {
  .stm-sum { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* A checkbox with its consequence spelled out under it. The label is the click
 * target, so the whole sentence toggles it. */
.cmp-check { display: flex; align-items: flex-start; gap: 10px; margin: 14px 0 4px;
             font-size: 12.5px; color: var(--c-ink); cursor: pointer; }
.cmp-check input { margin-top: 3px; accent-color: var(--accent); }
.cmp-check small { display: block; font-size: 11.5px; color: var(--c-ink-3);
                   line-height: 1.5; margin-top: 2px; max-width: 58ch; }

/* T5: a readable schema, using the existing Agent Studio controls. */
.flow-actions { display:flex; align-items:center; gap:12px; flex-wrap:wrap; margin:16px 0; }
.flow-actions span, .flow-hint { font-size:12px; color:var(--muted); line-height:1.6; }
.flow-preview { display:grid; grid-template-columns:repeat(auto-fit,minmax(220px,1fr)); gap:12px; margin:20px 0; }
.flow-card { border:1px solid var(--hairline); border-radius:10px; padding:16px; min-width:0; }
.flow-card strong { font-size:14px; }
.flow-card p { font-size:12px; line-height:1.65; white-space:pre-wrap; max-height:160px; overflow:auto; }
.flow-card small { display:block; font-size:11px; line-height:1.6; color:var(--muted); margin-top:8px; }
#flow-json { font-family:var(--mono,monospace); font-size:12px; min-height:220px; }
#flow-editor[hidden], #prompt-editor[hidden] { display:none; }
.flow-preview { display:block; }
.flow-map-toolbar { display:flex; align-items:center; gap:8px; margin-bottom:8px; }
.flow-map-toolbar span { flex:1; font-size:11px; color:var(--muted); }
.flow-map-toolbar button { margin:0; min-width:32px; }
.flow-map-viewport { overflow:auto; border:1px solid var(--hairline); border-radius:12px; background:radial-gradient(circle,#d7d8d1 1px,transparent 1px) 0 0 / 18px 18px; }
.flow-canvas { position:relative; transform-origin:top left; }
.flow-canvas svg { position:absolute; inset:0; overflow:visible; }
.flow-map-node { position:absolute; width:210px; height:116px; margin:0; display:flex; flex-direction:column; align-items:flex-start; justify-content:center; gap:9px; padding:16px; text-align:left; white-space:normal; background:var(--bg,#faf9f6); color:var(--c-ink); border:1px solid #c2ccc4; border-radius:12px; box-shadow:0 3px 10px #233d2510; }
.flow-map-node:hover, .flow-map-node[aria-pressed=true] { background:#eef5ef; border-color:#63856e; }
.flow-map-node:focus-visible { outline:2px solid #41634c; outline-offset:3px; }
.flow-map-node small { font-size:9px; letter-spacing:1.1px; color:#597762; }
.flow-map-node strong { font-size:14px; line-height:1.2; }
.flow-map-node span { font-size:11px; color:var(--muted); }
.flow-edge-label { font:11px sans-serif; fill:#54705b; paint-order:stroke; stroke:#faf9f6; stroke-width:5px; stroke-linejoin:round; }
.flow-node-detail { margin-top:12px; padding:16px; border:1px solid var(--hairline); border-radius:10px; }
.flow-node-detail strong { font-size:14px; }
.flow-node-detail p { font-size:12px; line-height:1.7; white-space:pre-wrap; }
.flow-node-detail h3 { margin: 0 0 18px; font-size: 15px; font-weight: 600; }
.flow-node-detail fieldset { min-width: 0; margin: 0; padding: 0; border: 0; }
.flow-node-detail h4 { margin: 24px 0 14px; font-size: 13px; font-weight: 600; }
.flow-node-field { margin-bottom: 16px; }
.flow-node-field label { display: block; margin-bottom: 8px; font-size: 11px; }
.flow-node-field input[type="text"], .flow-node-field textarea { width: 100%; box-sizing: border-box;
  border: 1px solid var(--c-line-strong); border-radius: var(--r-sm); background: var(--c-surface);
  color: var(--c-ink); padding: 10px 12px; font: 400 13px/1.65 var(--sans); }
.flow-node-field textarea { resize: vertical; min-height: 80px; }
.flow-node-detail fieldset:disabled { opacity: .65; }
.flow-node-detail #flow-node-status { color: var(--c-ink-3); margin: 12px 0 0; }
.flow-node-detail #flow-node-status.error { color: var(--c-alarm-ink); }
.flow-transition-detail { color:#54705b; }

/* Speech requests share the call inspector frame, navigation and mobile back. */
#tts-inspector[hidden] { display: none; }
.tts-reqclick { cursor: pointer; }
.tts-reqclick:hover { background: var(--c-sunk); }
.tts-reqclick.sel { background: var(--c-sunk); box-shadow: inset 2px 0 var(--accent); }
.tts-reqid .linkbtn { font: inherit; text-align: left; }
.tts-insp-body { overflow-y: auto; padding: 14px; gap: 12px; }
.tts-insp-body [hidden] { display: none; }
.tts-insp-section + .tts-insp-section { margin-top: 22px; }
.tts-insp-section h3 { margin: 0 0 8px; font-size: 12px; font-weight: 600; }
.tts-insp-text { margin: 12px 0 0; white-space: pre-wrap; overflow-wrap: anywhere;
  font: 13px/1.65 var(--sans); color: var(--c-ink); }
.tts-insp-stats { display: grid; grid-template-columns: 100px minmax(0, 1fr);
  gap: 10px 14px; margin: 12px 0 0; font-size: 12px; line-height: 1.5; }
.tts-insp-stats dt { color: var(--c-ink-3); }
.tts-insp-stats dd { margin: 0; color: var(--c-ink); overflow-wrap: anywhere;
  font-variant-numeric: tabular-nums; }
#tts-insp-generate, #tts-insp-cancel { margin-top: 14px; min-height: 36px; }
#tts-insp-audio { width: 100%; margin-top: 12px; }

/* ── the agent directory ───────────────────────────────────────────────────
 * A Mac list, not a spreadsheet: a large title that matches the sidebar item
 * that brought you here, search and scope on one line, and the agents as one
 * inset grouped list. The whole row is the target. Its two verbs surface
 * under the pointer (and are always there on touch), so a list of twenty
 * agents reads as twenty names rather than forty buttons. */
.agents-page { max-width: 1040px; margin: 0 auto; padding-top: 6px;
               container-type: inline-size; }
.studio-eyebrow { margin: 0 0 10px; color: var(--c-ink-3); font: 500 10px/1.4 var(--mono); letter-spacing: .1em; text-transform: uppercase; }
.agents-heading { display: flex; align-items: flex-end; justify-content: space-between;
                  gap: 24px; margin: 0 0 22px; }
.agents-heading h1 { display: flex; align-items: center; gap: 10px; margin: 0 0 6px;
                     font: 700 30px/1.15 var(--sans); letter-spacing: -0.028em;
                     color: var(--c-ink); }
.agents-heading > div > p:last-child { margin: 0; color: var(--c-ink-3);
                                       font-size: 14px; line-height: 1.45; }
.agents-heading > button { display: inline-flex; align-items: center; gap: 6px;
                           flex-shrink: 0; min-height: 34px; margin: 0 0 4px;
                           padding: 8px 14px 8px 11px; font-size: 13px; font-weight: 600;
                           border-radius: var(--ui-r-control); }
.agents-heading > button svg { width: 15px; height: 15px; }
.agents-total { font: 600 13px/1 var(--sans); font-variant-numeric: tabular-nums;
                letter-spacing: 0; color: var(--c-ink-3); background: var(--ui-fill);
                border-radius: 999px; padding: 5px 9px; }
.agents-total:empty { display: none; }
.agents-toolbar { display: flex; gap: 12px; align-items: center;
                  justify-content: space-between; margin-bottom: 14px; }
.agents-search { position: relative; flex: 0 1 320px; }
.agents-search svg { position: absolute; left: 10px; top: 50%; width: 15px; height: 15px;
                     transform: translateY(-50%); color: var(--c-ink-3); pointer-events: none; }
/* A filled field that turns into a white one while you type in it — the
 * Finder's search field, not a boxed form input. */
.agents-search input { width: 100%; min-height: 34px; padding: 7px 12px 7px 32px;
                       background: var(--ui-fill); color: var(--c-ink); border: 0;
                       border-radius: 9px; font: 400 13.5px/1.3 var(--sans);
                       transition: background-color 150ms ease, box-shadow 150ms ease; }
.agents-search input:hover { border: 0; }
.agents-search input:focus { outline: none; background: var(--c-surface);
                             box-shadow: 0 0 0 1px var(--ui-edge), var(--ui-ring); }
.agents-filters { display: flex; gap: 0; padding: 2px; border-radius: 9px;
                  background: var(--ui-fill); }
.agents-filters[hidden] { display: none; }
#agents-filter-archived-count { font-variant-numeric: tabular-nums; color: var(--c-ink-3); }
.agents-filter { margin: 0; border: 0; background: none; color: var(--c-ink-2);
                 font-size: 12.5px; font-weight: 500; padding: 5px 14px;
                 min-height: 28px; border-radius: 7px; }
.agents-filter:hover { background: none; color: var(--c-ink); }
.agents-filter:active { transform: none; }
.agents-filter[aria-pressed="true"] { background: var(--c-surface); color: var(--c-ink);
                                      box-shadow: var(--ui-raise); }
.agents-table-wrap { background: var(--c-surface); border-radius: var(--ui-r-card);
                     box-shadow: var(--ui-card); overflow: hidden; }
.agents-table { width: 100%; border-collapse: collapse; margin: 0; }
/* The column names stay for a screen reader; a list of named things with a
 * date needs no visible header row. */
.agents-table thead { position: absolute; width: 1px; height: 1px; overflow: hidden;
                      clip-path: inset(50%); white-space: nowrap; }
.agents-table td { padding: 0 16px 0 0; border: 0; vertical-align: middle;
                   font-size: 13px; }
/* Separators are inset to the text, as in every grouped list on a Mac: the
 * line starts where the name does, not under the avatar. */
.agent-list-row + .agent-list-row > td {
  background-image: linear-gradient(var(--ui-sep), var(--ui-sep));
  background-size: 100% 1px; background-repeat: no-repeat; }
.agent-list-row + .agent-list-row > td:first-child {
  background-position: 64px 0; background-size: calc(100% - 64px) 1px; }
.agent-list-row { cursor: pointer; transition: background-color 120ms ease; }
@media (hover: hover) and (pointer: fine) {
  .agent-list-row:hover { background-color: var(--ui-fill-2); }
}
.agents-table .agent-list-identity { width: 100%; max-width: 0; padding: 0; }
.agent-list-link { display: flex; gap: 12px; align-items: center; min-height: 62px;
                   padding: 11px 16px; color: var(--c-ink); text-decoration: none; }
.agent-list-link:hover { text-decoration: none; color: var(--c-ink); }
.agent-list-link:focus-visible { outline-offset: -3px; border-radius: 10px; }
/* A texture where a contact would have a photo (tiles.js) — still in the
 * console's own warm greys, so twenty of them read as a list and never as
 * confetti. The kind of row picks the four tokens (--t0 deep … --t3 light),
 * its id where the light falls (--tx, --ty). This gradient is the whole tile until a
 * shader fades in over it, and for good on a browser without WebGPU. Once the
 * shader's picture is kept (data-cached) the picture is the tile, on every
 * later visit too, and no canvas is left. */
.agent-list-icon { position: relative; flex: 0 0 36px; height: 36px; border-radius: 9px;
                   overflow: hidden; isolation: isolate;
                   background:
                     radial-gradient(circle at var(--tx, 30%) var(--ty, 30%),
                                     var(--t3, var(--c-surface)) 0%, transparent 60%),
                     radial-gradient(circle at calc(100% - var(--tx, 30%)) calc(100% - var(--ty, 30%)),
                                     var(--t0, var(--c-line-strong)) 0%, transparent 65%),
                     linear-gradient(135deg, var(--t1, var(--c-line)), var(--t2, var(--c-sunk))); }
/* The hairline sits over the canvas, not under it. */
.agent-list-icon::after { content: ""; position: absolute; inset: 0; border-radius: inherit;
                          box-shadow: inset 0 0 0 1px var(--ui-sep); pointer-events: none; }
.agent-list-icon canvas { position: absolute; inset: 0; width: 100%; height: 100%;
                          opacity: 0; transition: opacity 280ms var(--ease-out); }
.agent-list-icon[data-gpu] canvas { opacity: 1; }
.agent-list-icon[data-cached] { background: var(--tile-img, none) center / cover; }
/* A dialling campaign's kept loop: one strip of frames, slid a frame at a time
 * by the compositor, forwards then back. tiles.js sets its width, its timing
 * and how far it slides (--reel-end). */
.tile-reel { position: absolute; top: 0; left: 0; height: 100%; will-change: transform;
             background: 0 0 / 100% 100% no-repeat;
             animation: tile-reel 1.2s steps(24, jump-none) infinite alternate; }
@keyframes tile-reel { to { transform: translateX(var(--reel-end, 0)); } }
/* A shelved row keeps its face and loses its weight. */
.agent-list-icon[data-muted] { opacity: 0.45; }
/* Dialling, on a browser the shader cannot reach: the light still drifts. */
.agent-list-icon[data-live]:not([data-gpu]):not([data-cached]) { background-size: 220% 220%, 220% 220%, 100% 100%;
                                              animation: tile-drift 1.4s ease-in-out infinite alternate; }
@keyframes tile-drift { from { background-position: 0% 0%, 100% 100%, 0 0; }
                        to { background-position: 100% 100%, 0% 0%, 0 0; } }
@media (prefers-reduced-motion: reduce) {
  .agent-list-icon canvas { transition: none; }
  .agent-list-icon[data-live]:not([data-gpu]):not([data-cached]), .tile-reel { animation: none; }
}
.agent-list-name { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.agent-list-title { display: flex; align-items: center; gap: 8px; min-width: 0; }
.agent-list-name strong { font-weight: 600; font-size: 14px; line-height: 1.3;
                          letter-spacing: -0.01em; overflow: hidden;
                          text-overflow: ellipsis; white-space: nowrap; }
.agent-list-meta { font-size: 12.5px; line-height: 1.35; color: var(--c-ink-3);
                   overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.agent-list-state { flex: none; font-size: 11px; font-weight: 600; line-height: 1;
                    padding: 3px 7px; border-radius: 999px;
                    color: var(--c-open-ink); background: var(--c-open-bg); }
.agent-list-state[data-state="archived"] { color: var(--c-ink-3); background: var(--ui-fill); }
/* A shelved row says so where the name is, not in a column nobody scans. */
.agent-list-shelved { color: var(--c-ink-3); }
.agent-list-updated { width: 1%; white-space: nowrap; text-align: right;
                      color: var(--c-ink-3); font-size: 12.5px;
                      font-variant-numeric: tabular-nums; }
.agents-table td.agent-list-actions { width: 1%; white-space: nowrap; text-align: right;
                                      padding-right: 12px; }
.agent-list-actions button { display: inline-flex; align-items: center; margin: 0 0 0 4px;
                             min-height: 28px; padding: 5px 10px; font-size: 12px;
                             font-weight: 500; color: var(--c-ink-2);
                             background: var(--ui-fill-2); border: 0;
                             border-radius: 7px; vertical-align: middle; }
.agent-list-actions button:hover { background: var(--ui-fill-3); color: var(--c-ink); }
.agent-list-chevron { display: inline-block; width: 8px; height: 13px; margin-left: 10px;
                      vertical-align: middle; color: var(--c-ink-4); }
@media (hover: hover) and (pointer: fine) {
  .agent-list-actions button { opacity: 0; transition: opacity 120ms ease; }
  .agent-list-row:hover .agent-list-actions button,
  .agent-list-row:focus-within .agent-list-actions button { opacity: 1; }
}
.agents-result { margin: 12px 0 0; color: var(--c-ink-3); font-size: 12px;
                 text-align: center; }
.agents-empty { padding: 56px 24px 60px; text-align: center; }
.agents-empty h2 { font-size: 17px; font-weight: 600; letter-spacing: -0.012em;
                   margin: 0 0 6px; color: var(--c-ink); }
.agents-empty p { color: var(--c-ink-3); font-size: 13.5px; margin: 0 0 16px; }
.agents-empty[hidden] { display: none; }

/* ── the campaign list ─────────────────────────────────────────────────────
 * Agent Studio's directory, for campaigns (campaigns.html): the same title,
 * search and scope bar, and the same inset grouped list, newest first. What is
 * its own: the open target is a button, because a campaign has no URL of its
 * own; a badge for each state worth noticing; and a progress column that folds
 * away whenever no row on screen carries counters. */
.camp-heading-actions { display: flex; gap: 8px; flex-shrink: 0; margin: 0 0 4px; }
.camp-heading-actions button { display: inline-flex; align-items: center; gap: 6px; margin: 0;
                               min-height: 34px; padding: 8px 14px; font-size: 13px;
                               font-weight: 600; border-radius: var(--ui-r-control); }
.camp-heading-actions button.ghost { font-weight: 500; color: var(--c-ink);
                                     border-color: var(--ui-edge); }
.camp-heading-actions svg { width: 15px; height: 15px; }
#campnew { padding-left: 11px; }
.camp-toolbar-end { display: flex; align-items: center; gap: 8px; min-width: 0; }
.camp-filter-count { font-variant-numeric: tabular-nums; color: var(--c-ink-3); }
#campscope { min-height: 32px; max-width: 220px; border-radius: var(--ui-r-control); }
.camp-quiet { margin: 0; min-height: 28px; padding: 5px 11px; font-size: 12.5px;
              color: var(--c-ink-2); background: var(--ui-fill-2);
              border-color: transparent; border-radius: var(--ui-r-control); }
.camp-quiet:hover { background: var(--ui-fill); color: var(--c-ink); }
.camp-list-row { cursor: pointer; transition: background-color 120ms ease; }
@media (hover: hover) and (pointer: fine) {
  .camp-list-row:hover { background-color: var(--ui-fill-2); }
}
.camp-list-row + .camp-list-row > td {
  background-image: linear-gradient(var(--ui-sep), var(--ui-sep));
  background-size: 100% 1px; background-repeat: no-repeat; }
.camp-list-row + .camp-list-row > td:first-child {
  background-position: 64px 0; background-size: calc(100% - 64px) 1px; }
/* The agent row's link, as a button: every default a button brings is put
 * back to the row's, and it neither squashes nor fills on press — the row
 * already answers the pointer. */
.camp-list-row .camp-list-open { display: flex; gap: 12px; align-items: center; width: 100%;
  min-height: 62px; margin: 0; padding: 11px 16px; border: 0; border-radius: 0;
  background: none; color: var(--c-ink); font: inherit; font-size: 13px;
  line-height: normal; text-align: left; }
.camp-list-row .camp-list-open:active { transform: none; }
.camp-list-row a.camp-list-open { box-sizing: border-box; text-decoration: none; }
.camp-list-row a.camp-list-open:hover { text-decoration: none; color: var(--c-ink); }
.camp-list-row .camp-list-open:focus-visible { outline-offset: -3px; border-radius: 10px; }
/* Amber (the agent row's Draft) is a campaign waiting on somebody: a draft or
 * a pause. Dialing is the accent with a dot — the one row that is costing
 * money right now. */
.camp-list-state[data-state="live"] { color: var(--accent); background: var(--accent-bg); }
.camp-list-state[data-state="live"]::before { content: ''; display: inline-block; width: 6px;
  height: 6px; margin-right: 5px; border-radius: 50%; background: currentColor;
  vertical-align: 1px; }
.camp-list-state[data-state="quiet"] { color: var(--c-ink-3); background: var(--ui-fill); }
.camp-list-state[data-state="bad"] { color: var(--c-alarm-ink); background: var(--c-alarm-bg); }
.camp-list-why { color: var(--c-open-ink); }
/* A campaign that is somebody else's wave is demoted, never hidden: it ran, it
 * has its own dial log, and a customer looking for "Servall Recovery" has to
 * find it. Its numbers are in the primary's report, so the row is quieter by
 * a notch and says whose wave it is. */
.camp-list-row.cmp-wave strong { font-weight: 500; color: var(--c-ink-2); }
.camp-list-wave { font-style: italic; }
.agents-table td.camp-list-progress { width: 1%; white-space: nowrap; padding-right: 24px; }
.camp-list-progn { display: block; color: var(--c-ink-3); font-size: 12.5px;
                   font-variant-numeric: tabular-nums; }
.camp-list-progress .cmp-minitrack { width: 112px; margin-top: 5px; }
.camp-table[data-no-progress] .camp-list-progress { display: none; }
/* The row's verbs are Agent Studio's (.agent-list-actions): under the pointer
 * on a Mac, always there as icons on touch. */
@media (hover: hover) and (pointer: fine) {
  .camp-list-row:hover .agent-list-actions button,
  .camp-list-row:focus-within .agent-list-actions button { opacity: 1; }
}
.agents-empty #camp-empty-new { min-height: 34px; padding: 8px 14px; font-size: 13px;
                                font-weight: 600; border-radius: var(--ui-r-control); }
.agents-empty button[hidden] { display: none; }
@container (max-width: 640px) {
  .camp-table .camp-list-progress { display: none; }
}

/* ── the list's pages ── the result line on the left, the numbered pages on
 * the right: ‹ 1 … 4 5 6 … 12 ›. Quiet fills, the current page raised the way
 * the selected segment of a scope bar is. */
.camp-pager { display: flex; align-items: center; justify-content: space-between;
              gap: 12px; flex-wrap: wrap; margin: 12px 2px 0; }
.camp-pager[hidden] { display: none; }
.camp-pager-say { color: var(--c-ink-3); font-size: 12px; font-variant-numeric: tabular-nums; }
.camp-pager-btns { display: flex; align-items: center; gap: 2px; }
.camp-pager-btns:empty { display: none; }
.camp-page { margin: 0; min-width: 30px; min-height: 30px; padding: 0 9px; border: 0;
             border-radius: var(--ui-r-control); background: none; color: var(--c-ink-2);
             font: 500 12.5px/1 var(--sans); font-variant-numeric: tabular-nums; }
.camp-page:hover { background: var(--ui-fill-2); color: var(--c-ink); }
.camp-page:active { transform: none; background: var(--ui-fill); }
.camp-page[aria-current="page"] { opacity: 1; background: var(--c-surface); color: var(--c-ink);
                                  box-shadow: var(--ui-raise); }
.camp-page:disabled:not([aria-current]) { opacity: 0.35; }
.camp-page-gap { min-width: 18px; text-align: center; color: var(--c-ink-4); font-size: 12px; }

/* ══ ONE CAMPAIGN ══════════════════════════════════════════════════════════
 * Agent Studio's page, for one campaign: a back link, a large title with the
 * list row's badge and monogram, the verbs at its right with the one that
 * sets the dialer moving filled. Then the notices, the run at a glance (four
 * numbers and the bar, one grouped card) and three tabs. */
.camp-detail .camp-back { display: inline-flex; align-items: center; gap: 6px; margin: 0 0 14px;
                          padding: 4px 0; font-size: 13px; color: var(--c-ink-2);
                          text-decoration: none; }
.camp-detail .camp-back svg { width: 8px; height: 13px; }
.camp-detail .camp-back:hover { color: var(--c-ink); }
.camp-dhead { display: flex; align-items: center; gap: 14px; margin: 0 0 20px; }
.camp-dicon { flex: 0 0 48px; height: 48px; display: block; }
.camp-dicon .agent-list-icon { display: block; width: 48px; height: 48px; border-radius: 12px; }
.camp-dtitles { flex: 1 1 auto; min-width: 0; }
.camp-dtitles h1 { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 0 0 4px;
                   font: 700 26px/1.2 var(--sans); letter-spacing: -0.025em; color: var(--c-ink);
                   overflow-wrap: anywhere; }
.camp-dtitles h1 .camp-list-state { font-size: 11.5px; padding: 4px 8px; letter-spacing: 0; }
.camp-dtitles h1 .camp-list-state[hidden] { display: none; }
.camp-dtitles p { margin: 0; color: var(--c-ink-3); font-size: 13.5px; line-height: 1.45; }
.camp-dacts { display: flex; gap: 8px; flex-shrink: 0; flex-wrap: wrap; justify-content: flex-end; }
.camp-dacts button { display: inline-flex; align-items: center; margin: 0; min-height: 34px;
                     padding: 8px 14px; font-size: 13px; font-weight: 600;
                     border-radius: var(--ui-r-control); }
.camp-dacts button.ghost { font-weight: 500; color: var(--c-ink); border-color: var(--ui-edge); }
.camp-dacts button[hidden] { display: none; }
.camp-detail .cmp-why, .camp-detail .cmp-fund { border-radius: var(--ui-r-card); padding: 12px 14px;
                                                margin: 0 0 14px; }
.camp-detail .cmp-wavenote { margin: -8px 0 14px; }

/* the run at a glance */
.camp-overview { background: var(--c-surface); border-radius: var(--ui-r-card);
                 box-shadow: var(--ui-card); padding: 4px 18px 16px; }
.camp-stats { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); margin: 0 -18px 14px;
              border-bottom: 1px solid var(--ui-sep); }
.camp-stat { display: flex; flex-direction: column; gap: 3px; padding: 14px 18px; min-width: 0; }
.camp-stat + .camp-stat { box-shadow: inset 1px 0 0 var(--ui-sep); }
.camp-stat b { font: 600 22px/1.15 var(--sans); letter-spacing: -0.02em; color: var(--c-ink);
               overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.camp-stat span { font-size: 12px; color: var(--c-ink-3); }
.camp-overview .cmp-bar-track { height: 8px; margin: 0 0 12px; border-radius: 4px; background: var(--ui-fill); }
.camp-overview .cmp-legend { gap: 6px 18px; }
.camp-count { margin: 10px 0 0; font-size: 12px; color: var(--c-ink-3); }
.camp-count:empty { display: none; }

/* the tabs: the scope bar Agent Studio filters with, grown into the page's
 * own switch — one bar from edge to edge, a third each, at the size of a
 * control you press rather than a filter you glance at (owner, 2026-10-08) */
.camp-tabs { display: flex; margin: 22px 0 16px; padding: 3px; border-radius: 12px; }
.camp-tabs .agents-filter { flex: 1 1 0; min-width: 0; min-height: 42px; padding: 10px 18px;
                            font-size: 14px; font-weight: 600; letter-spacing: -0.006em;
                            border-radius: 9px; }
.camp-tabs .agents-filter[aria-selected="true"] { background: var(--c-surface); color: var(--c-ink);
                                                  box-shadow: var(--ui-raise); }
.camp-panel[hidden] { display: none; }

/* contacts: a toolbar, then the table as one inset grouped card */
.camp-ptools { display: flex; align-items: center; gap: 10px; margin: 0 0 12px; }
.camp-ptools-end { margin-left: auto; display: flex; gap: 8px; }
.camp-select { min-height: 32px; padding: 6px 30px 6px 11px; border: 0; border-radius: var(--ui-r-control);
               background-color: var(--ui-fill); font-size: 12.5px; color: var(--c-ink); }
.camp-select:hover { border: 0; }
.camp-cwrap .cmp-rowhead { padding: 10px 16px 8px; border-bottom: 1px solid var(--ui-sep);
                           font-size: 10.5px; letter-spacing: 0.06em; }
.camp-cwrap .cmp-row { padding: 10px 16px; border-bottom: 0; font-size: 12.5px; }
.camp-cwrap .cmp-row + .cmp-row { box-shadow: inset 0 1px 0 var(--ui-sep); }
.camp-cwrap .cmp-crow.sel { box-shadow: inset 2px 0 0 var(--c-ink); }
@media (hover: hover) and (pointer: fine) {
  .camp-cwrap .cmp-crow[data-h]:hover { background: var(--ui-fill-2); }
}
.camp-cwrap .cmp-empty { padding: 28px 16px; text-align: center; }
.cmp-c-person { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.cmp-c-cname { font-weight: 600; font-size: 13px; color: var(--c-ink); overflow: hidden;
               text-overflow: ellipsis; white-space: nowrap; }
.cmp-c-vars, .cmp-c-ph2 { font-size: 11.5px; color: var(--c-ink-3); overflow: hidden;
                         text-overflow: ellipsis; white-space: nowrap; }
.cmp-c-vars:empty { display: none; }
.cmp-c-ph2 { display: none; }
.camp-cwrap .cmp-c-phone { font-weight: 400; color: var(--c-ink-2); font-size: 12.5px; }
.cmp-c-cstatus { flex-direction: column; align-items: flex-start; gap: 2px; }
.cmp-c-state { display: flex; align-items: center; gap: 7px; min-width: 0; }
.cmp-c-cstatus small { font-size: 11px; color: var(--c-ink-3); white-space: nowrap; overflow: hidden;
                       text-overflow: ellipsis; max-width: 100%; }
.cmp-c-when, .cmp-c-dur { color: var(--c-ink-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cmp-c-result { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.cmp-c-result b { font-weight: 500; color: var(--c-ink-2); white-space: nowrap; overflow: hidden;
                  text-overflow: ellipsis; }
.cmp-c-result b.cmp-c-outcome { font-weight: 600; color: var(--c-ink); }
.cmp-c-summary { font-size: 11.5px; color: var(--c-ink-3); white-space: nowrap; overflow: hidden;
                 text-overflow: ellipsis; }
.camp-cwrap .cmp-c-call { justify-content: flex-end; gap: 4px; }
/* "More": a quiet circle beside the play circle, on every row */
.cmp-more { display: grid; place-items: center; flex: none; width: 26px; height: 26px; min-height: 0; margin: 0;
            padding: 0 0 6px; border: 0; border-radius: 50%; background: none; color: var(--c-ink-3);
            font-size: 16px; line-height: 1; letter-spacing: 0.5px; }
.cmp-more:hover, .cmp-more[aria-expanded="true"] { background: var(--ui-fill); color: var(--c-ink); }
.cmp-more:active { transform: none; }
/* the menu it opens */
.camp-menu { position: fixed; z-index: 150; min-width: 232px; max-width: 300px; padding: 5px;
             background: var(--c-surface); border-radius: 12px; box-shadow: var(--ui-float);
             animation: fade-in 120ms var(--ease-out) both; }
.camp-menu-item { display: flex; flex-direction: column; align-items: flex-start; gap: 2px; width: 100%; margin: 0;
                  padding: 8px 10px; border: 0; border-radius: 8px; background: none; color: var(--c-ink);
                  font: 500 13px/1.3 var(--sans); text-align: left; }
.camp-menu-item small { font-size: 11.5px; font-weight: 400; line-height: 1.4; color: var(--c-ink-3); white-space: normal; }
.camp-menu-item:hover:not(:disabled), .camp-menu-item:focus-visible { background: var(--ui-fill-2); outline: none; }
.camp-menu-item:active { transform: none; background: var(--ui-fill); }
.camp-menu-item:disabled { opacity: 1; color: var(--c-ink-3); cursor: default; }
.camp-menu-item.is-danger { color: var(--c-alarm-ink); }
.camp-menu-item.is-danger:hover { background: var(--c-alarm-bg); }
.cmp-x-tries, .cmp-x-dur { display: none; }
/* The table gives way by the page's own width (so the call panel and the test
 * panel squeeze it the same as a narrow window), and nothing is lost when it
 * does: Tries moves under the status and Length onto the Last call line, so
 * the row stays one line of facts with the number in its own column. Only on
 * a phone does the number move under the name. */
@container (max-width: 1000px) {
  .cmp-crowhead, .cmp-crow {
    grid-template-columns: minmax(120px, 1.2fr) 124px 112px 160px minmax(100px, 1.1fr) 60px; }
  .cmp-h-att, .cmp-crow .cmp-c-att, .cmp-h-dur, .cmp-crow .cmp-c-dur { display: none; }
  .cmp-x-tries { display: block; }
  .cmp-x-dur { display: inline; color: var(--c-ink-3); }
}
@container (max-width: 680px) {
  .cmp-crowhead, .cmp-crow { grid-template-columns: minmax(0, 1fr) 112px 150px 60px; }
  .cmp-h-phone, .cmp-crow .cmp-c-phone, .cmp-h-result, .cmp-crow .cmp-c-result { display: none; }
  .cmp-c-ph2 { display: block; }
}
@container (max-width: 480px) {
  .cmp-crowhead, .cmp-crow { grid-template-columns: minmax(0, 1fr) 104px 60px; }
  .cmp-crowhead > span:nth-child(5), .cmp-crow .cmp-c-when { display: none; }
}


/* contacts: search, status, Filters, then sort and CSV at the right */
.camp-ctools { flex-wrap: wrap; }
.camp-csearch { flex: 1 1 220px; max-width: 320px; }
.camp-csearch input { min-height: 32px; }
.camp-filterbtn { display: inline-flex; align-items: center; gap: 6px; margin: 0; min-height: 32px;
                  padding: 6px 12px 6px 10px; font-size: 12.5px; font-weight: 500; color: var(--c-ink);
                  background: var(--ui-fill); border: 0; border-radius: var(--ui-r-control); }
.camp-filterbtn svg { width: 15px; height: 15px; }
.camp-filterbtn:hover { background: var(--ui-fill-3); }
.camp-filterbtn.is-on { background: var(--c-ink); color: var(--c-paper); }
.camp-fcount:empty { display: none; }
.camp-fcount { min-width: 18px; padding: 2px 5px; border-radius: 999px; font-size: 11px; line-height: 1.2;
               text-align: center; background: var(--c-paper); color: var(--c-ink); font-variant-numeric: tabular-nums; }
.camp-chips { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin: -4px 0 12px; }
.camp-chips[hidden] { display: none; }
.camp-chip { display: inline-flex; align-items: center; gap: 6px; margin: 0; min-height: 28px;
             padding: 4px 8px 4px 11px; border: 0; border-radius: 999px; background: var(--c-surface);
             box-shadow: inset 0 0 0 1px var(--ui-edge); color: var(--c-ink); font-size: 12.5px; font-weight: 500; }
.camp-chip::after { content: '×'; font-size: 14px; line-height: 1; color: var(--c-ink-3); }
.camp-chip:hover { background: var(--ui-fill-2); }
.camp-chip:active { transform: scale(0.97); }
.camp-chip-clear { padding: 4px 6px; font-size: 12.5px; }
.camp-cnote-row { display: flex; align-items: center; justify-content: space-between; gap: 12px;
                  margin: -4px 2px 10px; min-height: 0; }
.camp-cnote { margin: 0; font-size: 12px; color: var(--c-ink-3); }
.camp-cnote[hidden] { display: none; }
.camp-cnote-row:has(.camp-cnote[hidden]):has(#cdfollow[hidden]) { display: none; }
/* the filters dialog */
.camp-presets { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.camp-presets button { margin: 0; min-height: 26px; padding: 3px 10px; border: 0; border-radius: 999px;
                       background: var(--ui-fill-2); color: var(--c-ink-2); font-size: 12px; font-weight: 500; }
.camp-presets button:hover { background: var(--ui-fill); color: var(--c-ink); }
.camp-flrows { display: flex; flex-direction: column; gap: 8px; margin: 10px 0; }
.camp-flrows:empty { display: none; }
.camp-flrow { display: grid; grid-template-columns: minmax(0, 1.2fr) 120px minmax(0, 1fr) 28px; gap: 8px;
              align-items: center; }
.camp-flrow input[hidden] { display: block; visibility: hidden; }
.camp-flx { display: grid; place-items: center; width: 28px; height: 28px; min-height: 0; margin: 0; padding: 0;
            border: 0; border-radius: var(--ui-r-control); background: none; color: var(--c-ink-3); font-size: 16px; }
.camp-flx:hover { background: var(--c-alarm-bg); color: var(--c-alarm-ink); }
.camp-filterdlg [hidden] { display: none; }
.camp-filterdlg .camp-flrow input[hidden] { display: block; visibility: hidden; }
@container (max-width: 680px) {
  .camp-csearch { max-width: none; flex-basis: 100%; }
}
/* settings: what can change, then Edit */
.camp-stools { align-items: center; }
.camp-snote { margin: 0; font-size: 12.5px; color: var(--c-ink-3); }
.camp-edit { margin: 0; min-height: 30px; padding: 6px 13px; font-size: 12.5px; font-weight: 500;
             color: var(--c-ink); background: var(--c-surface); border-color: var(--ui-edge);
             border-radius: var(--ui-r-control); }
.camp-edit:hover { border-color: var(--c-ink-4); }
.camp-edit[hidden] { display: none; }
/* the create form, editing: no list, and the note on what can change now */
#campdlg[data-edit] .cmp-contacts, #campdlg[data-edit] #cfupload { display: none; }
.cmp-editnote { margin: 0 0 16px; padding: 10px 12px; border-radius: var(--ui-r-control);
                font-size: 12.5px; line-height: 1.5; color: var(--c-open-ink);
                background: var(--c-open-bg); box-shadow: inset 0 0 0 1px var(--c-open-line); }
.cmp-editnote[hidden] { display: none; }
.modal input:disabled, .modal select:disabled, .modal textarea:disabled { opacity: 0.55; cursor: not-allowed; }
/* settings: label and value, as in Settings on a Mac */
.camp-facts { margin: 0; background: var(--c-surface); border-radius: var(--ui-r-card);
              box-shadow: var(--ui-card); overflow: hidden; }
.camp-facts > div { display: flex; align-items: baseline; justify-content: space-between; gap: 16px;
                    padding: 12px 16px; min-height: 22px; }
.camp-facts > div + div { box-shadow: inset 0 1px 0 var(--ui-sep); }
.camp-facts dt { margin: 0; font-size: 13px; color: var(--c-ink-2); flex: none; }
.camp-facts dd { margin: 0; font-size: 13px; color: var(--c-ink); text-align: right; min-width: 0;
                 overflow-wrap: anywhere; }

/* results: the card without its old heading, since the tab names it */
.camp-detail .cmp-res { margin: 0; border: 0; border-radius: var(--ui-r-card); box-shadow: var(--ui-card);
                        padding: 16px 18px; }
.cmp-rate { margin: 0; font-size: 13px; color: var(--c-ink); }

/* ── the report, in the page ───────────────────────────────────────────────
 * A line saying when it was built, with the things you do with it at its right
 * (Share filled). The report under it is the share deck's own pages, styled by
 * /ui/insights.css (deck.app_css) — nothing for them lives in this file. */
.camp-rhead { display: flex; align-items: center; justify-content: space-between; gap: 12px;
              flex-wrap: wrap; margin: 0 0 12px;
              /* one layer over the report's page strip, whose paper band reaches
                 up behind this row (deck.py, "THE PAGE STRIP") */
              position: relative; z-index: 4; }
.camp-rmeta { margin: 0; font-size: 12px; color: var(--c-ink-3); }
.camp-rtools { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-left: auto; }
.camp-rtools [hidden] { display: none; }
a.camp-quiet { display: inline-flex; align-items: center; text-decoration: none; font-weight: 500; }
.camp-share { display: inline-flex; align-items: center; gap: 6px; margin: 0; min-height: 30px;
              padding: 6px 13px 6px 11px; font-size: 12.5px; font-weight: 600;
              border-radius: var(--ui-r-control); }
.camp-share svg { width: 14px; height: 14px; }
.camp-detail .cmp-res { margin: 0; padding: 0; border: 0; box-shadow: none; background: none; }
.camp-detail .cmp-res-quiet { padding: 28px 18px; text-align: center; background: var(--c-surface);
                              border-radius: var(--ui-r-card); box-shadow: var(--ui-card); }
/* the share dialog */
.cmp-sharelink { display: flex; gap: 8px; margin: 0 0 14px; }
.cmp-sharelink input { flex: 1 1 auto; min-width: 0; font-family: var(--mono); font-size: 12px; }
.cmp-sharelink button, .cmp-sharelink a { flex: none; margin: 0; }
.cmp-sharelink a.camp-quiet { min-height: 34px; padding: 0 12px; }
.camp-sharedlg .modal-foot [hidden] { display: none; }

@container (max-width: 720px) {
  .camp-dhead { flex-wrap: wrap; align-items: flex-start; }
  .camp-dtitles { flex: 1 1 0; }
  .camp-dacts { flex: 1 1 100%; justify-content: flex-start; }
  .camp-stats { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .camp-stat:nth-child(4) { box-shadow: none; }
  .camp-stat:nth-child(n+4) { border-top: 1px solid var(--ui-sep); }
}
@container (max-width: 720px) {
}
@container (max-width: 480px) {
  .camp-dicon { flex-basis: 40px; height: 40px; }
  .camp-dicon .agent-list-icon { width: 40px; height: 40px; }
  .camp-dtitles h1 { font-size: 22px; }
  .camp-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .camp-stat:nth-child(odd) { box-shadow: none; }
  .camp-stat:nth-child(even) { box-shadow: inset 1px 0 0 var(--ui-sep); }
  .camp-stat:nth-child(n+3) { border-top: 1px solid var(--ui-sep); }
  .camp-tabs .agents-filter { padding: 10px 8px; }
  .camp-dacts button { flex: 1 1 auto; justify-content: center; }
}
@container (max-width: 520px) {
  .camp-toolbar-end { flex-wrap: wrap; }
  .camp-toolbar-end .agents-filters { flex: 1 1 auto; }
}
@container (max-width: 480px) {
  .camp-list-row .camp-list-open { gap: 10px; padding: 10px 12px; }
  .camp-list-row + .camp-list-row > td:first-child { background-position: 58px 0;
    background-size: calc(100% - 58px) 1px; }
}
@media (max-width: 760px) {
  .campaigns-page .agents-heading { flex-wrap: wrap; }
  .campaigns-page .agents-heading > div:first-child { flex: 1 1 100%; }
  .camp-heading-actions { flex: 1 1 100%; margin: 0; }
  .camp-heading-actions button { flex: 1 1 0; justify-content: center; min-height: 40px; }
  .camp-list-row .camp-list-open { min-height: 64px; }
  .camp-quiet { min-height: 32px; }
}

/* Starting points belong to creation; selecting a card never edits an agent. */
/* The shared modal (THE MODAL) at gallery size: a white title bar over a
 * paper canvas, so the white starting-point cards have something to sit on. */
.agent-create-panel { background: var(--c-paper); }
.agent-create-header { background: var(--c-surface); border-bottom: 1px solid var(--ui-sep);
  padding-bottom: 16px; animation: none; }
.agent-create-layout { display: grid; grid-template-columns: 210px minmax(0, 1fr); flex: 1; min-height: 0; }
.agent-create-nav { display: flex; flex-direction: column; gap: 5px; padding: 24px 14px;
  border-right: 1px solid var(--c-line); background: var(--c-surface); overflow-y: auto; }
.agent-create-nav button { min-height: 40px; margin: 0; padding: 10px 12px; text-align: left;
  background: transparent; border-color: transparent; color: var(--c-ink-3); font-size: 12px; line-height: 1.4; }
.agent-create-nav button:hover { background: var(--c-paper); color: var(--c-ink); }
.agent-create-nav button[aria-pressed="true"] { background: var(--c-sunk); color: var(--c-ink); border-color: var(--c-line); }
.agent-create-nav p { margin: 22px 12px 7px; font: 500 10px/1.5 var(--mono);
  text-transform: uppercase; letter-spacing: .06em; color: var(--c-ink-3); }
.agent-create-main { min-width: 0; padding: 26px; overflow-y: auto; overscroll-behavior: contain; scrollbar-gutter: stable; }
.agent-create-main h3 { margin: 0; font: 550 16px/1.4 var(--sans); letter-spacing: -.015em; }
#agent-gallery-title:focus { outline: none; }
#agent-gallery-description, #agent-choice-description { margin: 6px 0 22px; color: var(--c-ink-3); font-size: 12px; line-height: 1.6; }
.agent-template-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; }
.agent-template-grid[hidden], .agent-create-main [hidden] { display: none; }
.agent-create-main #agent-templates-title { margin: 28px 0 16px; font-size: 13px; color: var(--c-ink-3); }
.agent-template-card { position: relative; display: flex; flex-direction: column; align-items: flex-start;
  gap: 10px; margin: 0; padding: 22px; text-align: left; background: var(--c-surface);
  border: 1px solid var(--c-line-strong); color: var(--c-ink); border-radius: var(--r-md); }
.agent-template-card:hover { background: var(--c-surface); border-color: var(--c-ink-3); }
.agent-template-card:active { transform: scale(.99); }
.agent-template-icon { display: grid; place-items: center; width: 36px; height: 36px;
  margin-bottom: 8px; background: var(--c-sunk); border-radius: var(--r-sm); }
.agent-template-icon svg { width: 20px; height: 20px; }
.agent-template-card strong { font-size: 14px; font-weight: 550; line-height: 1.5; }
.agent-template-description { font-size: 12px; font-weight: 400; line-height: 1.6; color: var(--c-ink-3); }
.agent-template-card:disabled { opacity: 1; background: var(--c-sunk); border-color: var(--c-line); color: var(--c-ink-3); }
.agent-template-card:disabled .agent-template-icon { background: var(--c-line); }
.agent-template-soon { position: absolute; top: 22px; right: 18px; padding: 5px 7px;
  border: 1px solid var(--c-line-strong); border-radius: var(--r-sm); font: 500 10px/1.2 var(--sans); }
.agent-create-back { margin: 0 0 20px; padding: 0; min-height: 28px;
  background: transparent; border: 0; color: var(--c-ink-3); }
.agent-create-back:hover { color: var(--c-ink); }
#agent-create-form label { display: block; margin: 20px 0 8px; color: var(--c-ink-2); font-size: 12px; }
#agent-create-form label span { margin-left: 8px; color: var(--c-ink-3); font-size: 11px; font-weight: 400; }
#agent-create-form input { width: 100%; min-height: 42px; font-size: 13px; }
#agent-create-form textarea { font-size: 13px; }
.agent-create-footer { display: flex; align-items: center; justify-content: flex-end; gap: 20px;
  margin-top: 24px; padding-top: 18px; border-top: 1px solid var(--c-line); }
#agent-create-status { flex: 1; margin: 0; color: var(--c-ink-3); font-size: 12px; line-height: 1.6; }
#agent-create-status:empty { display: none; }
.agent-create-footer button { flex-shrink: 0; min-height: 40px; margin: 0; }
/* A node flow from a description, recordings, or both. The recordings are a
 * list of rows that fill in as each one is transcribed; nothing here moves
 * once it is on screen. */
#agent-create-flow textarea { width: 100%; }
#agent-create-flow .flow-hint { margin: 8px 0 0; }
#agent-create-flow .flow-hint:empty { display: none; }
.agent-flow-helper { display: flex; align-items: center; justify-content: space-between; gap: 14px; margin-top: 10px; }
.agent-flow-helper p { margin: 0; color: var(--c-ink-3); font-size: 12px; line-height: 1.6; }
.agent-flow-helper button { flex-shrink: 0; margin: 0; }
.agent-flow-prompt { margin-top: 8px; }
.agent-flow-prompt summary { cursor: pointer; width: max-content; color: var(--c-ink-3); font-size: 12px; }
.agent-flow-prompt summary:hover { color: var(--c-ink); }
#agent-create-form .agent-flow-prompt textarea { margin-top: 8px; font: 12px/1.6 var(--mono); background: var(--c-sunk); }
#agent-create-form .agent-flow-drop { display: flex; align-items: center; justify-content: center; gap: 6px;
  margin: 10px 0 0; min-height: 64px; padding: 14px; cursor: pointer; text-align: center;
  border: 1px dashed var(--c-line-strong); border-radius: var(--r-md); background: var(--c-surface);
  color: var(--c-ink-3); transition: border-color 90ms ease, background-color 90ms ease;
  /* A label only so a click opens the picker: it reads as a sentence. */
  font-size: 12px; font-weight: 400; letter-spacing: normal; text-transform: none; line-height: 1.5; }
#agent-create-form .agent-flow-drop strong { color: var(--c-ink); font-weight: 550; }
#agent-create-form .agent-flow-drop:hover, #agent-create-form .agent-flow-drop[data-over] { border-color: var(--c-ink-3); background: var(--c-sunk); }
.agent-flow-drop:focus-within { outline: 2px solid var(--c-ink); outline-offset: 2px; }
#agent-create-form .agent-flow-drop input { width: 1px; min-height: 0; }
.agent-flow-recordings { margin: 10px 0 0; padding: 0; list-style: none; }
.agent-flow-recordings:empty { display: none; }
.agent-flow-recording { padding: 10px 0; border-top: 1px solid var(--c-line); }
.agent-flow-recording:last-child { border-bottom: 1px solid var(--c-line); }
.agent-flow-recording-row { display: flex; align-items: baseline; gap: 12px; font-size: 12px; }
.agent-flow-recording-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--c-ink); }
.agent-flow-recording-size { color: var(--c-ink-3); font-variant-numeric: tabular-nums; white-space: nowrap; }
.agent-flow-recording-status { color: var(--c-ink-3); font-variant-numeric: tabular-nums; }
.agent-flow-recording[data-state="failed"] .agent-flow-recording-status { color: var(--c-alarm-ink); }
.agent-flow-recording-row button { flex-shrink: 0; margin: 0 0 0 auto; }
.agent-flow-recording details { margin-top: 6px; }
.agent-flow-recording summary { cursor: pointer; width: max-content; color: var(--c-ink-3); font-size: 11px; }
.agent-flow-transcript { max-height: 180px; overflow-y: auto; margin: 6px 0 0; padding: 10px 12px;
  background: var(--c-sunk); border-radius: var(--r-sm); color: var(--c-ink-2);
  font-size: 12px; line-height: 1.6; white-space: pre-wrap; overflow-wrap: anywhere; }
/* What testing the drafted flow found, in place of the form, before the agent
 * exists. A headline, one sentence, lists and disclosures: the same hairline
 * rows as the recordings above, nothing that moves. */
.agent-flow-report { margin-top: 28px; }
.agent-flow-report h4 { margin: 4px 0 0; font: 550 15px/1.45 var(--sans); letter-spacing: -.012em; color: var(--c-ink); }
.agent-flow-report h4:focus { outline: none; }
#agent-flow-report-lede { margin: 6px 0 0; color: var(--c-ink-3); font-size: 12px; line-height: 1.6; }
.agent-create-footer #agent-flow-skip { min-height: 0; padding: 0; }
.flow-refine-section { margin-top: 20px; }
.flow-refine-section h5 { margin: 0 0 8px; font: 500 10px/1.5 var(--mono); text-transform: uppercase;
  letter-spacing: .06em; color: var(--c-ink-3); }
.flow-refine-note { margin: 0 0 8px; color: var(--c-ink-3); font-size: 12px; line-height: 1.6; }
.flow-refine-issues { margin: 0; padding: 0; list-style: none; }
.flow-refine-issue { display: flex; flex-direction: column; gap: 2px; padding: 9px 0; border-top: 1px solid var(--c-line); }
.flow-refine-issue:last-child { border-bottom: 1px solid var(--c-line); }
.flow-refine-issue-title { color: var(--c-ink); font-size: 12.5px; line-height: 1.5; }
.flow-refine-issue-detail { color: var(--c-ink-3); font-size: 12px; line-height: 1.6; overflow-wrap: anywhere; }
/* Fixed is said with a tick and the heading, never with a colour (rule 2). */
.flow-refine-section[data-kind="fixed"] .flow-refine-issue-title::before { content: "✓"; margin-right: 8px; color: var(--c-ink-3); }
.flow-refine-sev { margin-right: 8px; font: 500 10px/1 var(--mono); text-transform: uppercase;
  letter-spacing: .06em; color: var(--c-ink-3); }
.flow-refine-prompt summary { cursor: pointer; width: max-content; margin-top: 4px; color: var(--c-ink-3); font-size: 11.5px; }
.flow-refine-prompt p { margin: 6px 0 0; padding: 10px 12px; background: var(--c-sunk); border-radius: var(--r-sm);
  color: var(--c-ink-2); font-size: 12px; line-height: 1.6; white-space: pre-wrap; overflow-wrap: anywhere; }
.flow-refine-call { border-top: 1px solid var(--c-line); }
.flow-refine-call:last-child { border-bottom: 1px solid var(--c-line); }
.flow-refine-call summary { display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 10px; padding: 9px 0;
  cursor: pointer; color: var(--c-ink); font-size: 12.5px; line-height: 1.5; }
.flow-refine-call summary small { color: var(--c-ink-3); font-size: 11.5px; }
.flow-refine-call summary:hover small { color: var(--c-ink-2); }
.flow-refine-scenario { margin: 0 0 8px; color: var(--c-ink-3); font-size: 12px; line-height: 1.6; }
.flow-refine-lines { max-height: 340px; overflow-y: auto; margin: 0 0 12px; padding: 10px 12px; list-style: none;
  background: var(--c-sunk); border-radius: var(--r-sm); overscroll-behavior: contain; }
.flow-refine-line { display: grid; grid-template-columns: 10em minmax(0, 1fr); gap: 12px; padding: 3px 0;
  font-size: 12px; line-height: 1.6; }
.flow-refine-who { color: var(--c-ink-2); font-weight: 500; overflow-wrap: anywhere; }
.flow-refine-who small { color: var(--c-ink-3); font-size: 11px; font-weight: 400; }
.flow-refine-line[data-role="caller"] .flow-refine-who { color: var(--c-ink-3); }
.flow-refine-said { color: var(--c-ink-2); overflow-wrap: anywhere; }
/* A move between nodes, or a tool call: quiet, under the text column. */
.flow-refine-line[data-role="transition"], .flow-refine-line[data-role="tool"] { display: block; padding-left: calc(10em + 12px); }
.flow-refine-move { color: var(--c-ink-3); font-size: 11px; }
/* A replay beside the call it replays: the original on the left, the same
 * caller on the new flow on the right, each line reading as one sentence. */
.flow-refine-compare { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; margin: 0 0 12px; }
.flow-refine-side h6 { margin: 0 0 6px; font: 500 10px/1.5 var(--mono); text-transform: uppercase;
  letter-spacing: .06em; color: var(--c-ink-3); }
.flow-refine-compare .flow-refine-lines { margin: 0; max-height: 380px; }
.flow-refine-compare .flow-refine-line,
.flow-refine-compare .flow-refine-line[data-role="transition"],
.flow-refine-compare .flow-refine-line[data-role="tool"] { display: block; padding-left: 0; }
.flow-refine-compare .flow-refine-who { margin-right: 6px; }
@media (max-width: 700px) {
  .flow-refine-line { grid-template-columns: minmax(0, 1fr); gap: 0; }
  .flow-refine-line[data-role="transition"], .flow-refine-line[data-role="tool"] { padding-left: 0; }
  .flow-refine-compare { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 700px) {
  .agent-create-layout { display: flex; flex-direction: column; }
  .agent-create-nav { flex: 0 0 auto; flex-direction: row; padding: 10px 14px;
    border-right: 0; border-bottom: 1px solid var(--c-line); overflow-x: auto; }
  .agent-create-nav button { flex-shrink: 0; min-height: 44px; }
  .agent-create-nav p { display: none; }
  .agent-create-main { padding: 20px 16px; }
  .agent-template-card { padding: 18px; }
  .agent-template-soon { top: 18px; right: 12px; }
  .agent-create-footer { flex-wrap: wrap; gap: 12px; }
  #agent-create-status { flex-basis: 100%; }
  #agent-create-form input, .agent-create-footer button { min-height: 44px; }
  .agent-flow-helper, .agent-flow-recording-row { flex-wrap: wrap; }
  .agent-flow-recording-name { flex-basis: 100%; }
}
@media (max-width: 480px) {
  .agent-template-grid { grid-template-columns: minmax(0, 1fr); }
  #agent-create-form input, #agent-create-form textarea { font-size: 16px; }
}
@media (prefers-reduced-motion: reduce) {
  .agent-template-card:active { transform: none; }
}
/* The Studio's header lives with the Studio (agent-workspace.css). */
.agent-body #flow-editor { padding-top: 16px; }
/* The latest self-test of a node-based agent's flow, above the flow: a quiet
 * line while it runs (no motion — it is a fact, not a spinner), a hairline
 * card when there is something to decide. */
.flow-selftest { margin: 0 0 14px; }
.flow-selftest[hidden] { display: none; }
.flow-selftest-line { margin: 0; color: var(--c-ink-3); font-size: 12px; line-height: 1.6; }
.flow-selftest-line strong { color: var(--c-ink); font-weight: 550; }
.flow-selftest[data-kind="fixed"], .flow-selftest[data-kind="edited"], .flow-selftest[data-kind="unfixed"] {
  display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap;
  padding: 10px 12px; background: var(--c-surface); border: 1px solid var(--c-line); border-radius: var(--r-sm); }
.flow-selftest[data-kind="fixed"] .flow-selftest-line, .flow-selftest[data-kind="edited"] .flow-selftest-line,
.flow-selftest[data-kind="unfixed"] .flow-selftest-line { flex: 1 1 260px; color: var(--c-ink-2); font-size: 12.5px; }
.flow-selftest-actions { display: flex; flex-wrap: wrap; gap: 6px; }
.flow-selftest-actions button { margin: 0; }
/* A draft older than the active flow: unresolved, so the open (amber)
 * surface — and the sentence says so, never the colour alone (rule 2). */
.flow-stale { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap;
  margin: 0 0 14px; padding: 10px 12px; background: var(--c-open-bg); border: 1px solid var(--c-open-line);
  border-radius: var(--r-sm); }
.flow-stale[hidden] { display: none; }
.flow-stale-line { flex: 1 1 320px; margin: 0; color: var(--c-ink-2); font-size: 12.5px; line-height: 1.6; }
.flow-stale-line strong { color: var(--c-ink); font-weight: 550; }
.flow-stale-actions { display: flex; flex-wrap: wrap; gap: 6px; }
.flow-stale-actions button { margin: 0; }
/* The self-test report over the builder: the create dialog's panel, narrower. */
/* The self-test report over the builder: the shared modal, long-form width. */
.flow-review-dialog { --modal-w: 820px; }
.flow-review-body { padding-top: 4px; }
@container (max-width: 520px) {
  .agents-toolbar { flex-direction: column; align-items: stretch; gap: 10px; }
  .agents-search { flex: none; }
  .agents-filter { flex: 1; }
}
@container (max-width: 480px) {
  .agents-table .agent-list-updated { display: none; }
  .agent-list-link { gap: 10px; padding: 10px 12px; }
  .agent-list-row + .agent-list-row > td:first-child { background-position: 58px 0;
    background-size: calc(100% - 58px) 1px; }
  .agents-table td.agent-list-actions { padding-right: 8px; }
}
@media (max-width: 760px) {
  /* iOS's large title: the add button sits at the title's right, round. */
  .agents-heading { flex-wrap: nowrap; align-items: flex-start; gap: 12px; margin: 8px 0 18px; }
  .agents-heading h1 { font-size: 28px; }
  .agents-heading > button { justify-content: center; flex: 0 0 38px; width: 38px; height: 38px;
                             min-height: 0; margin: 2px 0 0; padding: 0; gap: 0;
                             border-radius: 50%; font-size: 0; }
  .agents-heading > button svg { width: 17px; height: 17px; }
  .agents-search input { min-height: 40px; font-size: 16px; }
  .agents-filter { min-height: 32px; }
  .agent-list-link { min-height: 64px; }
  /* Touch has no hover, so the verbs are always present — as icons, the way
     Mail's row actions are, each a full 44px target. The words stay in the
     button for the accessibility tree; aria-label names the agent too. */
  .agents-table td.agent-list-actions { padding-right: 6px; }
  .agent-list-actions button { justify-content: center; width: 40px; min-height: 44px; margin: 0;
                               padding: 0; background: none; font-size: 0; color: var(--c-ink-3); }
  .agent-list-actions button::before { content: ''; width: 19px; height: 19px; background: currentColor;
    -webkit-mask: var(--glyph) center / contain no-repeat; mask: var(--glyph) center / contain no-repeat; }
  .agent-list-actions button[aria-label^="Duplicate"] { --glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.7' stroke-linejoin='round'%3E%3Crect x='8' y='8' width='12' height='12' rx='2.5'/%3E%3Cpath d='M16 8V6.5A2.5 2.5 0 0 0 13.5 4h-7A2.5 2.5 0 0 0 4 6.5v7A2.5 2.5 0 0 0 6.5 16H8'/%3E%3C/svg%3E"); }
  .agent-list-actions button[aria-label^="Archive"] { --glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3.5' y='4.5' width='17' height='4.5' rx='1.2'/%3E%3Cpath d='M5 9v8.5A2.5 2.5 0 0 0 7.5 20h9a2.5 2.5 0 0 0 2.5-2.5V9M10 13h4'/%3E%3C/svg%3E"); }
  .agent-list-actions button[aria-label^="Restore"] { --glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M9 14 4 9l5-5'/%3E%3Cpath d='M4 9h10.5a5.5 5.5 0 0 1 0 11H11'/%3E%3C/svg%3E"); }
  .agent-list-chevron { display: none; }
}

/* ══ GO LIVE (#/live) ═══════════════════════════════════════════════════════
 * PLACEMENT ONLY. Every colour, radius and curve below is a token already
 * defined at the top of this file; the view is built out of .card, .rail-fact,
 * .howrow/.howtab, .chip and the three state marks, which is why it reads as
 * the same product as the page it was opened from.
 *
 * The one piece of motion is the step swap: 140ms of opacity with a 2px blur
 * on the way out, which masks the crossfade and is under the 200ms the console
 * holds itself to. Nothing loops, and the payment screens never spin — see the
 * note over #lv-checking in live.html. */
.lv-card { margin-bottom: 14px; }
.lv-card h3 { margin-bottom: 12px; }
/* The last fact in a card has nothing under it to be separated from. */
.lv-last { border-bottom: 0; padding-bottom: 0; margin-bottom: 0; }
.lv-note { margin: 10px 0 0; font-size: 11.5px; line-height: 1.6;
           color: var(--c-ink-3); }
.lv-lede { margin: 0 0 12px; font-size: 12.5px; line-height: 1.6;
           color: var(--c-ink-2); }
.lv-reason { margin: 10px 0; font-size: 12px; line-height: 1.55;
             color: var(--c-open-ink); }

/* ── card B · the checklist ───────────────────────────── */
.lv-agent { display: flex; align-items: flex-start; gap: 10px;
            padding: 9px 0; border-bottom: 1px solid var(--c-line); }
.lv-agent:last-child { border-bottom: 0; padding-bottom: 0; }
.lv-agent-box { margin: 2px 0 0; flex: 0 0 auto; accent-color: var(--c-ink); }
.lv-agent-body { min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.lv-agent-name { display: block; font-size: 12.5px; font-weight: 600;
                 color: var(--c-ink); letter-spacing: -0.01em; cursor: pointer;
                 text-transform: none; }
.lv-agent-sub { font-size: 11.5px; line-height: 1.5; color: var(--c-ink-3); }
.lv-agent-sub.mark-open { color: var(--c-open-ink); }
.lv-agent-sub.mark-bad { color: var(--c-alarm-ink); }
.lv-agent-open { padding-top: 2px; align-self: flex-start; }
#team-list .lv-agent { flex-wrap: wrap; }
#team-list .lv-agent-body { flex: 1 1 180px; }
.team-actions { display: flex; flex-wrap: wrap; gap: 8px; }

/* ── card C · the strip and the panel ─────────────────── */
.lv-steps { padding: 0; border: 0; background: none; }
.howrow.lv-strip { grid-template-columns: repeat(3, 1fr); margin: 0; }
.lv-panel { border: 1px solid var(--c-line); border-top: 0;
            border-radius: 0 0 var(--r-sm) var(--r-sm);
            background: var(--c-surface); padding: 16px 18px;
            transition: opacity 140ms var(--ease-out),
                        filter 140ms var(--ease-out); }
.lv-panel.swapping { opacity: 0; filter: blur(2px); }
.lv-step[hidden] { display: none; }
.lv-step h3 { margin: 0 0 6px; font-size: 13px; font-weight: 600; }
.lv-step p { margin: 0; font-size: 12.5px; line-height: 1.6;
             color: var(--c-ink-2); max-width: 62ch; }
.lv-acts { display: flex; align-items: center; gap: 12px; margin-top: 14px; }
.lv-contact { font-size: 11.5px; color: var(--c-ink-2); }

/* ── the forms ────────────────────────────────────────── */
.lv-form { display: flex; flex-direction: column; gap: 10px;
           align-items: flex-start; }
.lv-form label, .lv-grid label { display: flex; flex-direction: column; gap: 4px;
                                 width: 100%; }
.lv-form input, .lv-grid input, .lv-grid select { width: 100%; }
.lv-form small, .lv-grid small { font-size: 11px; font-weight: 400;
                                 letter-spacing: 0; text-transform: none;
                                 color: var(--c-ink-3); line-height: 1.5; }
.lv-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 12px;
           width: 100%; margin-bottom: 14px; }
.lv-wide { grid-column: 1 / -1; }
/* The opening credit of a pay-as-you-go plan, above the payment figures. */
.lv-payg { margin-bottom: 14px; }
.lv-payg .lv-note { margin-top: 0; }
.lv-gst { display: flex; align-items: flex-end; gap: 10px; width: 100%; }
.lv-gst label { flex: 1 1 auto; }
.lv-sent { margin: 12px 0 8px; font-size: 12px; line-height: 1.55;
           color: var(--c-ink-2); }

/* The code field is a thing that JUST HAPPENED — the email went out and this
 * is what arrived with it — so it is allowed the one entrance in this view. */
.lv-otp { animation: lv-otp-in 160ms var(--ease-out) both; }
.lv-otp[hidden] { display: none; }
@keyframes lv-otp-in {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: none; }
}

/* Comprehension over decoration: the crossfade stays, the blur and the 4px
 * rise go. */
@media (prefers-reduced-motion: reduce) {
  .lv-panel.swapping { filter: none; }
  .lv-otp { animation: none; }
}

@media (max-width: 760px) {
  .lv-grid { grid-template-columns: 1fr; }
  .lv-gst { flex-direction: column; align-items: stretch; }
}

/* ── one page, two environments ─────────────────────────────────────────────
 * The sandbox and the live workspace ask the same questions with different
 * stakes, so the views are the same views and only the sentences change. Both
 * sets ship in the markup; `data-env` on the document picks one.
 *
 * The default is deliberate: with NO attribute the sandbox copy shows. The
 * live journey is switched on per session, and a customer who does not have it
 * must see today's console rather than an empty card where a sentence was. */
:root:not([data-env="live"]) .env-live { display: none !important; }
:root[data-env="live"] .env-sandbox { display: none !important; }

.home-golive { margin: 14px 0 0; font-size: 12px; color: var(--c-ink-2); }
.home-golive[hidden] { display: none; }
.home-golive .how-act { margin: 0; }

/* The live home's first visit, once per tab and never again: 200ms, 40ms
 * apart, opacity and 6px. Arriving somewhere new one time is the one place
 * this console spends motion on delight. */
@keyframes home-in {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  @keyframes home-in { from { opacity: 0; } to { opacity: 1; } }
}

/* The cost of the next press, under the button that spends it. 11px, ink-3:
 * it is a fact to be read once and remembered, not a warning. */
.rail-cost { margin: 0; font-size: 11px; line-height: 1.5;
             color: var(--c-ink-3); text-align: center; }
.rail-cost:empty { display: none; }

/* A console with no test rail. The billing role has no workspace behind that
 * column, so it comes out of the grid rather than standing there empty —
 * `.app` is explicit placement, so the third track has to go too or the
 * content column keeps a 388px hole beside it. */
.app.norail { grid-template-columns: 216px minmax(0, 1fr); }
.app.norail.inspecting { grid-template-columns: 216px minmax(0, 1fr); }
.nav-sec[hidden], .nav-items[hidden], .rail[hidden] { display: none; }

/* ── the gate's two states ──────────────────────────────────────────────────
 * A code opens a sandbox; an email opens a company. Same card, two states.
 * 160ms of opacity and NOTHING ELSE: the field about to be typed into must not
 * move away from the cursor, which is why there is no transform here and no
 * height animation on the card. */
/* The card was the flex column; it now holds two of them, one at a time, so
 * the column (and its 10px gap) moves down a level. */
.gate-state { display: flex; flex-direction: column; gap: 10px; }
.gate-state[hidden] { display: none; }
#gateotp { display: flex; flex-direction: column; gap: 10px; }
.gate-in { animation: gate-fade 160ms var(--ease-out) both; }
@keyframes gate-fade { from { opacity: 0; } to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .gate-in { animation: none; } }
#gateotp[hidden], #gatemagic[hidden] { display: none; }
/* The link that swaps states sits where an <a> did, so it reads like one. */
.gate-card .linkbtn { padding: 0; font-size: inherit; color: inherit; }

/* ── going live, as a dialog ────────────────────────────────────────────────
 * Placement on top of .cmp-dlg, which already owns the scrim, the panel, the
 * entrance and the phone treatment. The outer frame stays the same size on
 * every step; only its scrolling body changes.
 *
 * The panel does not scroll; its BODY does. A modal whose header scrolls away
 * is a modal whose close button you have to hunt for. */
.golive-dlg .cmp-dlgpanel { width: min(940px, calc(100% - 40px));
  height: min(800px, 88dvh); max-height: 88dvh; align-self: flex-start;
  margin-top: min(6vh, 48px); }
.golive-dlg .cmp-dlgbody { flex: 1; min-height: 0; padding: 28px; }
.lv-dialog-footer { display: flex; align-items: center; justify-content: space-between;
  gap: 20px; flex-shrink: 0; border-top: 1px solid var(--c-line); padding: 16px 28px;
  background: var(--c-surface); }
.lv-dialog-footer[hidden] { display: none; }
.lv-dialog-footer p { margin: 0; font-size: 12px; line-height: 1.5; color: var(--c-ink-3); }
#lv-plan-next { display: inline-flex; align-items: center; justify-content: space-between;
  flex-shrink: 0; gap: 28px; min-width: 120px; min-height: 44px; padding: 12px 18px; font-size: 13px; }
/* The partial ships a view-head for when it was a page; inside a dialog the
 * heading above it is the title, so only the sentence is left. */
.golive-dlg .view-head { margin: 0 0 14px; }
.golive-dlg .lv-card:last-of-type { margin-bottom: 0; }

@media (max-width: 760px) {
  /* Full screen, and full VISIBLE screen: 100dvh rather than 100vh, because
   * vh on a phone is measured against a collapsed URL bar and the last 60px of
   * this panel would sit under it. The home indicator gets its inset back. */
  .golive-dlg .cmp-dlgpanel { width: 100%; height: 100dvh; max-height: 100dvh; margin-top: 0;
                              padding-bottom: env(safe-area-inset-bottom); }
  .golive-dlg .cmp-dlgbody { padding: 14px 14px 20px; }
}

@media (prefers-reduced-motion: reduce) {
  .golive-dlg .cmp-dlgpanel { animation: none; }
}

/* Offering first: the same console paper, hairlines and compact controls. */
.lv-offering[hidden], #lv-onboarding[hidden], .lv-plan-reveal[hidden],
.lv-plan-grid[hidden], .lv-plan-selection[hidden] { display: none; }
.lv-eyebrow { font: 500 10px/1.4 var(--mono); letter-spacing: .07em;
               color: var(--c-ink-3); }
.lv-offering-intro { margin-bottom: 24px; }
.lv-offering-intro h3 { font-size: 28px; font-weight: 550; line-height: 1.2;
                       letter-spacing: -.035em; margin: 0 0 8px; }
.lv-offering-intro p { margin: 0; font-size: 14px; color: var(--c-ink-3); line-height: 1.6; }
.lv-offering-options { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
.lv-offering-option, .lv-plan {
  display: flex; flex-direction: column; min-width: 0; text-align: left;
  white-space: normal; color: var(--c-ink); background: var(--c-surface);
  border: 1px solid var(--c-line-strong); border-radius: var(--r-md);
  box-shadow: none; cursor: pointer;
  transition: border-color 150ms ease, background-color 150ms ease, transform 150ms var(--ease-out);
}
.lv-offering-option { padding: 0; overflow: hidden; }
.lv-option-art { display: flex; align-items: center; justify-content: center;
                  position: relative; width: 100%; padding: 14px 24px 6px; background: var(--c-paper);
                  border-bottom: 1px solid var(--c-line); }
.lv-option-art img { display: block; width: 100%; max-width: 360px; height: 144px; object-fit: contain; }
.lv-option-art .lv-choice-mark { position: absolute; top: 16px; right: 16px; background: var(--c-surface); }
.lv-option-content { display: flex; flex-direction: column; gap: 9px; padding: 23px 24px 21px; flex: 1; }
.lv-option-title { font-size: 21px; font-weight: 600; line-height: 1.25; letter-spacing: -.025em; }
.lv-option-copy { max-width: 30ch; font-size: 13px; font-weight: 400; color: var(--c-ink-2); line-height: 1.55; }
.lv-option-detail { font-size: 11px; font-weight: 400; color: var(--c-ink-3); margin-top: 9px; }
.lv-option-action { display: flex; align-items: center; justify-content: space-between; gap: 12px;
                     margin: 0 24px; padding: 15px 0; border-top: 1px solid var(--c-line);
                     font-size: 12px; font-weight: 500; }
.lv-option-action > span:last-child { font-size: 17px; line-height: 1; }
.lv-choice-mark { width: 16px; height: 16px; flex: 0 0 16px; border: 1px solid var(--c-line-strong); border-radius: 50%; }
[aria-pressed="true"] > .lv-option-art .lv-choice-mark,
[aria-pressed="true"] > .lv-plan-action .lv-choice-mark { border: 5px solid var(--c-ink); }
.lv-offering-option[aria-pressed="true"], .lv-plan[aria-pressed="true"] {
  border-color: var(--c-ink); background: var(--c-paper);
}
.lv-offering-option:active, .lv-plan:active { transform: scale(.99); }
.lv-offering-option:focus-visible, .lv-plan:focus-visible, .lv-enterprise-contact:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 3px;
}
.lv-plan-reveal { margin-top: 25px; }
.lv-plan-heading { display: flex; justify-content: space-between; gap: 12px;
                   align-items: baseline; margin-bottom: 12px; }
.lv-plan-heading h4 { margin: 0; font-size: 13px; font-weight: 600; }
.lv-plan-heading > span { font-size: 11px; color: var(--c-ink-3); }
.lv-plan-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; }
.lv-plan-grid-two { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.lv-plan { padding: 19px 17px 0; }
.lv-plan-top { display: flex; flex-direction: column; gap: 5px; }
.lv-plan-name { font: 500 21px/1.2 var(--mono); letter-spacing: -.035em; }
.lv-plan-name-text { font-family: inherit; font-size: 18px; font-weight: 600; }
.lv-plan-position { font-size: 11px; font-weight: 400; color: var(--c-ink-3); }
.lv-plan-price { display: block; font-size: 27px; line-height: 1.3; font-weight: 550;
                  letter-spacing: -.035em; margin: 23px 0 20px; font-variant-numeric: tabular-nums; }
.lv-plan-price > span { font-size: 11px; letter-spacing: 0; font-weight: 400; color: var(--c-ink-3); }
.lv-plan-price-text { font-size: 23px; line-height: 1.526; letter-spacing: -.025em; }
.lv-model-picker[hidden] { display: none; }
.lv-model-picker { margin-bottom: 20px; }
.lv-plan .lv-llm-price { margin: 16px 0 0; }
.lv-llm-price > span { display: block; margin-top: 4px; font-size: 12px; line-height: 1.5; }
.lv-plan-facts { display: flex; flex-direction: column; gap: 10px;
                  font-size: 11.5px; font-weight: 400; color: var(--c-ink-2); line-height: 1.5; }
.lv-plan-facts strong { font-weight: 600; }
.lv-plan-commitment { display: flex; flex-direction: column; gap: 5px;
                       margin-top: auto; padding: 26px 0 19px; }
.lv-plan-commitment > strong { font-size: 13px; font-weight: 600; line-height: 1.5; }
.lv-plan-commitment > strong > span { font-size: 11px; font-weight: 400; color: var(--c-ink-3); }
.lv-plan-commitment > span { font-size: 11px; font-weight: 400; color: var(--c-ink-3); line-height: 1.5; }
.lv-plan-action { display: flex; align-items: center; justify-content: space-between;
                   gap: 10px; border-top: 1px solid var(--c-line); padding: 14px 0;
                   font-size: 12px; font-weight: 500; }
.lv-plan-terms, .lv-plan-selection { font-size: 11px; line-height: 1.6; color: var(--c-ink-3); margin: 12px 0 0; }
.lv-plan-selection { color: var(--c-ink); font-weight: 500; }
.lv-enterprise { display: flex; justify-content: space-between; align-items: center;
                  gap: 24px; padding: 20px 0 0; margin-top: 24px;
                  border-top: 1px solid var(--c-line); }
.lv-enterprise h4 { font-size: 14px; font-weight: 600; letter-spacing: -.02em; margin: 0 0 5px; }
.lv-enterprise p { max-width: 57ch; font-size: 12px; line-height: 1.6; color: var(--c-ink-3); margin: 0; }
.lv-enterprise-contact { display: inline-flex; align-items: center; justify-content: center;
                          gap: 18px; min-height: 44px; flex-shrink: 0; padding: 0 15px;
                          font-size: 12px; font-weight: 500; color: var(--c-ink);
                          border: 1px solid var(--c-line-strong); border-radius: var(--r-md);
                          background: var(--c-surface); text-decoration: none; }
.lv-pricing-tax { margin: 12px 0 0; font-size: 10.5px; color: var(--c-ink-3); }

/* Tabs first, then one quiet plan bar. Terms expand only when requested. */
.lv-selected-plan { display: flex; align-items: flex-start; gap: 8px; padding: 8px 0;
  border-bottom: 1px solid var(--c-line); }
.lv-selected-plan[hidden], .lv-review[hidden], .lv-form[hidden] { display: none; }
.lv-plan-back { display: inline-flex; align-items: center; justify-content: center;
  flex: 0 0 40px; min-height: 44px; padding: 0; text-decoration: none; border-radius: var(--r-sm); }
.lv-plan-back[hidden] { display: none; }
.lv-plan-back:active { transform: scale(.97); }
.lv-plan-disclosure { flex: 1; min-width: 0; }
.lv-plan-disclosure > summary { display: flex; align-items: center; justify-content: space-between;
  gap: 16px; min-height: 44px; padding: 8px; border-radius: var(--r-sm); cursor: pointer; list-style: none; }
.lv-plan-disclosure > summary::-webkit-details-marker { display: none; }
.lv-plan-disclosure > summary:focus-visible, .lv-plan-back:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px; }
.lv-selected-identity { display: inline-flex; align-items: center; gap: 8px; white-space: nowrap;
  font-size: 13px; line-height: 1.5; color: var(--c-ink); }
.lv-selected-identity strong { font-weight: 550; }
.lv-plan-chevron { flex-shrink: 0; color: var(--c-ink-3); }
.lv-plan-disclosure[open] .lv-plan-chevron { transform: rotate(180deg); }
.lv-selected-calculation { display: flex; align-items: center; gap: 10px; font-size: 12px;
  line-height: 1.5; color: var(--c-ink-2); font-variant-numeric: tabular-nums; }
.lv-selected-calculation > span { white-space: nowrap; }
.lv-plan-separator { color: var(--c-ink-3); }
.lv-selected-details { padding: 16px; margin: 4px 8px 8px; background: var(--c-paper);
  border: 1px solid var(--c-line); border-radius: var(--r-sm); }
.lv-selected-facts { display: flex; flex-wrap: wrap; gap: 8px 20px; }
.lv-selected-facts span, .lv-selected-terms p { font-size: 12px; line-height: 1.6; color: var(--c-ink-2); }
.lv-selected-facts strong { font-weight: 500; }
.lv-selected-terms { border-top: 1px solid var(--c-line); padding-top: 12px; margin-top: 12px; }
.lv-selected-terms p { margin: 0; }
.lv-selected-terms p + p { margin-top: 6px; }
#lv-onboarding .lv-steps { border: 0; padding: 0; margin: 0; box-shadow: none; }
.lv-strip .howtab { min-height: 44px; }
#lv-onboarding .lv-panel { border: 0; background: none; padding: 32px 0 0; }
#lv-email { max-width: 480px; margin-inline: auto; }
#lv-email .lv-email-title { font-size: 25px; line-height: 1.25; letter-spacing: -.03em;
                            font-weight: 550; margin: 0 0 10px; }
#lv-email-description { font-size: 13px; margin-bottom: 24px; max-width: 48ch; }
#lv-email-form { display: flex; flex-direction: column; align-items: stretch; gap: 16px; }
#lv-email-form[hidden] { display: none; }
#lv-email-input { min-height: 46px; font-size: 14px; padding: 12px; }
#lv-email-send, #lv-magic { min-height: 46px; font-size: 13px; }
#lv-email-send { display: flex; align-items: center; justify-content: center; gap: 12px; }
#lv-otp-input { min-height: 46px; font-size: 18px; letter-spacing: .18em; font-variant-numeric: tabular-nums; }
#lv-otp-go { min-height: 46px; padding-inline: 20px; }
.lv-email-actions { display: flex; justify-content: space-between; gap: 16px; margin-top: 8px; }
.lv-email-actions button { min-height: 44px; }
#lv-email #lv-email-err:not(:empty) { margin-top: 14px; color: var(--c-alarm-ink); }
.lv-review { padding: 0 16px; background: var(--c-surface); }
.lv-review summary { min-height: 44px; padding: 14px 0; font-size: 12px; line-height: 1.4;
                       cursor: pointer; color: var(--c-ink-2); }
.lv-review summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.lv-review-body { border-top: 1px solid var(--c-line); padding: 14px 0 16px; }

/* Activation is a milestone, once per workspace. No looping confetti. */
.is-live #lv-strip { display: none; }
#lv-onboarding.is-live .lv-panel { padding-top: 24px; }
.lv-launch { max-width: 580px; margin-inline: auto; text-align: center; }
.lv-launch-mark { width: 120px; height: 120px; margin: 0 auto 20px; color: var(--accent); }
.lv-launch-mark svg { display: block; width: 100%; height: 100%; overflow: visible; }
.lv-launch-ring, .lv-launch-rays { transform-origin: center; }
.lv-launch-rays { opacity: 0; }
#lv-live-title { margin: 0 0 12px; font-size: clamp(32px, 5vw, 44px); line-height: 1.1;
  font-weight: 550; letter-spacing: -.04em; }
#lv-live-title:focus { outline: none; }
#lv-live .lv-launch-lede { font-size: 15px; line-height: 1.6; margin-inline: auto; max-width: 40ch; }
#lv-live-p { margin: 12px auto 0; color: var(--c-ink-3); }
#lv-live-p:empty { display: none; }
.lv-production-endpoint { margin: 28px 0 24px; padding: 16px 18px; text-align: left;
  background: var(--c-paper); border: 1px solid var(--c-line); border-radius: var(--r-md); }
.lv-production-endpoint > span { display: block; margin-bottom: 10px; font-size: 12px; color: var(--c-ink-3); }
.lv-production-endpoint > div { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.lv-production-endpoint code { font-family: var(--mono); font-size: 13px; line-height: 1.5;
  overflow-wrap: anywhere; user-select: all; }
.lv-production-endpoint button { flex-shrink: 0; min-height: 40px; min-width: 68px; }
#lv-live .lv-acts { justify-content: center; flex-wrap: wrap; margin-top: 24px; }
#lv-live-key, #lv-live-home { min-height: 46px; padding: 12px 24px; }
#lv-live-key[hidden] { display: none; }
#lv-live-test { margin: 12px auto 0; font-size: 11.5px; }
#lv-live-test[hidden] { display: none; }
.celebrate .lv-launch-ring { animation: lv-launch-ring 320ms var(--ease-out) both; }
.celebrate .lv-launch-rays { animation: lv-launch-burst 480ms var(--ease-out) 60ms both; }
.celebrate .lv-launch-check { animation: lv-launch-check 240ms var(--ease-out) 80ms both; }
.celebrate #lv-live-title { animation: lv-launch-title 280ms var(--ease-out) 80ms both; }
.celebrate .lv-launch-lede { animation: lv-launch-title 240ms var(--ease-out) 140ms both; }
@keyframes lv-launch-ring { from { opacity: 0; transform: scale(.85); } to { opacity: 1; transform: scale(1); } }
@keyframes lv-launch-burst {
  0% { opacity: 0; transform: scale(.85); }
  30% { opacity: 1; }
  100% { opacity: 0; transform: scale(1.16); }
}
@keyframes lv-launch-check { from { opacity: 0; } to { opacity: 1; } }
@keyframes lv-launch-title { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
@media (hover: hover) and (pointer: fine) {
  .lv-plan-disclosure > summary:hover, .lv-plan-back:hover { background: var(--c-paper); }
  .lv-offering-option:hover, .lv-plan:hover { background: var(--c-paper); border-color: var(--c-ink-3); }
  .lv-enterprise-contact:hover { border-color: var(--c-ink); background: var(--c-paper); }
}
@media (prefers-reduced-motion: reduce) {
  .envrow.is-activating::before, .envrow.is-activating #env-live,
  .celebrate .lv-launch-ring, .celebrate .lv-launch-rays, .celebrate .lv-launch-check,
  .celebrate #lv-live-title, .celebrate .lv-launch-lede { animation: none; }
  .lv-offering-option, .lv-plan { transition: none; }
  .lv-offering-option:active, .lv-plan:active, .lv-plan-back:active { transform: none; }
}
@media (max-width: 760px) {
  .golive-dlg .cmp-dlgbody { padding: 20px 16px; }
  .lv-dialog-footer { padding: 14px 16px; gap: 12px; }
  .lv-plan-grid { grid-template-columns: 1fr; }
  .lv-plan-grid-two { grid-template-columns: 1fr 1fr; }
  .lv-plan-heading { align-items: flex-start; flex-direction: column; gap: 4px; }
  .lv-plan-price { margin: 16px 0; }
  .lv-option-content { padding: 18px; }
  .lv-option-title { font-size: 18px; }
  .lv-option-art { padding: 10px; }
  .lv-option-art img { height: 112px; }
  .lv-option-action { margin: 0 18px; }
  .lv-plan-facts { gap: 7px; }
  .lv-plan-commitment { padding-top: 18px; }
  .lv-enterprise { align-items: flex-start; gap: 14px; }
}
@media (max-width: 480px) {
  .lv-production-endpoint { padding: 14px; }
  .lv-production-endpoint > div { align-items: flex-start; flex-direction: column; }
  .lv-launch-mark { width: 96px; height: 96px; }
  .lv-plan-disclosure > summary { align-items: flex-start; flex-direction: column; gap: 4px; }
  .lv-selected-calculation { flex-wrap: wrap; gap: 4px 8px; font-size: 11px; }
  .lv-selected-details { margin-inline: 0; padding: 12px; }
  .lv-selected-facts { flex-direction: column; gap: 8px; }
  #lv-onboarding .lv-panel { padding-top: 24px; }
  .lv-offering-options, .lv-plan-grid-two { grid-template-columns: 1fr; }
  .lv-option-art img { height: 96px; }
  .lv-enterprise { flex-direction: column; }
  .lv-enterprise-contact { align-self: stretch; }
}

/* Release details share the rail's existing quiet typography and stay useful
   in the mobile drawer. Commit identities are available on hover. */
/* The summary is the platform release ("voice-v0.2.75 · conductor-v0.2.52") in
   the rail's quiet ink; the amber triangle appears only when components differ. */
.release-versions { font-size: 11px; color: var(--c-on-dark-3); min-width: 0; }
/* 10.5px with proportional figures keeps both names on one line in the 216px
   rail; a narrower rail wraps at the dot, never inside a name. */
.release-versions summary { cursor: pointer; list-style-position: inside; padding: 4px 0;
                            font-size: 10.5px; }
#releaseline span { white-space: nowrap; }
@media (hover: hover) and (pointer: fine) {
  .release-versions summary:hover { color: var(--c-on-dark-2); }
}
.release-mixed { display: inline-block; width: 10px; height: 10px; margin-left: 4px;
                 vertical-align: -1px; color: var(--c-open-on-dark); }
.release-mixed[hidden] { display: none; }
.release-mixed svg { display: block; width: 100%; height: 100%; }
.release-versions dl { margin: 8px 0 0; display: grid; gap: 7px; }
.release-versions dl > div { display: flex; justify-content: space-between; gap: 12px; }
.release-versions dt { font-weight: 400; }
.release-versions dd { margin: 0; font-variant-numeric: tabular-nums; text-align: right; }
.release-versions [data-aligned="false"] { color: var(--c-open-on-dark); }
/* The phone setup sheet is paper: the same roles from the paper side. */
.sheet-body .release-versions { color: var(--c-ink-3); }
.sheet-body .release-versions summary:hover { color: var(--c-ink-2); }
.sheet-body .release-mixed, .sheet-body .release-versions [data-aligned="false"] { color: var(--c-open-ink); }

/* ══ going live · the sheet ═════════════════════════════════════════════════
 * One narrow sheet for all four beats (plan, email, business, pay), the way a
 * phone asks for an Apple ID: one question per screen, the answer's field
 * already focused, the primary action full width at the bottom of the beat.
 * The width never changes between beats, so moving forward reads as a push
 * inside one object rather than a new dialog. Everything below is scoped to
 * .gl / .gl-head / .gl-progress; the Company and Team views keep .lv-*.
 * ═══════════════════════════════════════════════════════════════════════════ */
.golive-dlg .cmp-dlgpanel { width: min(468px, calc(100% - 32px)); height: auto;
  max-height: min(760px, 92dvh); margin-top: 0; align-self: center;
  border-radius: 18px; border: 0;
  box-shadow: 0 0 0 0.5px rgba(27, 26, 22, 0.12), 0 24px 64px rgba(27, 26, 22, 0.16); }
.golive-dlg .cmp-dlgbody { padding: 6px 28px 28px; }
/* Checkout's moment: the sheet stays put, non-modal, under Razorpay. */
.golive-dlg.gl-under { background: var(--c-scrim); }

/* The head: back · title · close, the title centred like a sheet's. */
.gl-head { display: grid; grid-template-columns: 34px 1fr 34px; border-bottom: 0;
  padding: 12px 12px 4px; }
.gl-head h2 { text-align: center; font-size: 15px; }
.gl-head .gl-back { grid-column: 1; }
.gl-head h2 { grid-column: 2; }
.gl-head #goliveclose { grid-column: 3; }
.gl-head .gl-back[hidden] { display: flex; visibility: hidden; }

/* Four short strokes under the title: where you are, never a control. */
.gl-progress { display: flex; gap: 4px; justify-content: center; padding: 2px 0 10px; }
.gl-progress[hidden] { display: none; }
.gl-progress span { width: 22px; height: 3px; border-radius: 2px; background: var(--ui-fill);
  transition: background-color 380ms var(--ui-spring); }
.gl-progress span.on { background: var(--c-ink); }

.gl-stage { overflow: clip; overflow-clip-margin: 8px; }
.gl-step[hidden] { display: none; }
.gl-step { display: flex; flex-direction: column; gap: 14px; padding-top: 4px; }

.gl-lede { margin: 0 0 4px; text-align: center; font-size: 14px; line-height: 1.5;
  color: var(--c-ink-3); }
.gl-fine { margin: 0; font-size: 12px; line-height: 1.55; color: var(--c-ink-3); }
.gl-center { text-align: center; }
.gl-note { margin: 0; padding: 10px 12px; font-size: 12.5px; line-height: 1.5;
  color: var(--c-open-ink); background: var(--c-open-bg); border-radius: var(--ui-r-control); }
.gl-note[hidden] { display: none; }
.gl-err { margin: -4px 0 0; min-height: 0; font-size: 12.5px; line-height: 1.45;
  color: var(--c-alarm-ink); }
.gl-err:empty { display: none; }
.gl-hint { margin: -6px 0 0; font-size: 12.5px; line-height: 1.45; color: var(--c-ink-3); }
.gl-hint:empty { display: none; }
.gl-aside { margin: 2px 0 0; text-align: center; font-size: 12.5px; }
.gl-aside a { color: var(--c-ink-3); text-decoration: none; }
.gl-aside a:hover { color: var(--c-ink); text-decoration: underline; }

/* ── buttons: one primary per beat, full width, a press you can feel ── */
.gl-primary, .gl-secondary { width: 100%; min-height: 46px; padding: 12px 18px;
  border-radius: 12px; font-size: 15px; font-weight: 600; letter-spacing: -0.01em; }
.gl-primary[hidden], .gl-secondary[hidden] { display: none; }
.gl-secondary { background: var(--ui-fill-2); color: var(--c-ink); border-color: transparent; }
.gl-secondary:hover { background: var(--ui-fill); }
.gl-link { padding: 0; border: 0; background: none; color: var(--accent);
  font-size: 13px; font-weight: 500; line-height: 1.4; }
.gl-link:hover { text-decoration: underline; }
.gl-link:disabled { color: var(--c-ink-3); opacity: 1; }
.gl-link[hidden] { display: none; }
.gl-links { display: flex; justify-content: center; align-items: center; gap: 8px;
  margin: 0; color: var(--c-ink-4); font-size: 13px; }

/* ── grouped lists: the phone's inset-grouped table ── */
.gl-group { background: var(--c-surface); border-radius: var(--ui-r-card);
  box-shadow: var(--ui-card); overflow: hidden; }
.gl-group[hidden] { display: none; }

/* ── plan ── */
.gl-plan { display: grid; grid-template-columns: 22px 1fr auto; align-items: center; gap: 12px;
  width: 100%; min-height: 64px; padding: 12px 16px; border: 0; border-radius: 0;
  background: none; color: var(--c-ink); text-align: left; font-weight: 400; line-height: 1.3;
  transition: background-color 120ms ease; }
.gl-plan + .gl-plan { box-shadow: inset 0 0.5px 0 var(--ui-sep), 0 -0.5px 0 transparent; }
.gl-plan + .gl-plan::before { content: none; }
.gl-plan:hover { background: var(--ui-fill-2); }
.gl-plan:active { transform: none; background: var(--ui-fill); }
.gl-plan:focus-visible { outline: 2px solid var(--c-ink); outline-offset: -2px; }
.gl-plan-body { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.gl-plan-body b { font-size: 15px; font-weight: 600; letter-spacing: -0.01em; }
.gl-plan-body span { font-size: 12.5px; color: var(--c-ink-3); }
.gl-plan-price { display: flex; align-items: baseline; gap: 1px; font-variant-numeric: tabular-nums; }
.gl-plan-price b { font-size: 17px; font-weight: 600; letter-spacing: -0.02em; }
.gl-plan-price span { font-size: 12px; color: var(--c-ink-3); }
.gl-radio { width: 20px; height: 20px; border-radius: 50%; box-shadow: inset 0 0 0 1.5px var(--c-line-strong);
  transition: box-shadow 200ms var(--ui-spring), background-color 200ms var(--ui-spring); }
.gl-plan[aria-checked=true] .gl-radio { background: var(--c-ink);
  box-shadow: inset 0 0 0 5.5px var(--c-ink), inset 0 0 0 20px var(--c-paper); }
.gl-plan[aria-checked=true] { background: var(--ui-fill-2); }

/* ── fields: a label over a quiet, rounded field ── */
.gl-form { display: flex; flex-direction: column; gap: 14px; }
/* The console's labels are MAGI eyebrows (10px uppercase); a form someone is
 * filling in once reads better in sentence case, the way a phone asks. */
.gl-field, .gl-agent { font-size: 12.5px; font-weight: 500; letter-spacing: 0;
  text-transform: none; line-height: 1.35; color: var(--c-ink-2); }
.gl-field { display: flex; flex-direction: column; gap: 6px; }
.gl-field[hidden] { display: none; }
.gl-field em { font-style: normal; font-weight: 400; color: var(--c-ink-3); }
.gl-field input, .gl-field select { width: 100%; min-height: 46px; padding: 11px 14px;
  font-size: 15px; font-weight: 400; color: var(--c-ink); background: var(--c-surface);
  border: 1px solid var(--ui-edge); border-radius: 12px;
  transition: box-shadow 140ms ease, border-color 140ms ease; }
.gl-field input:focus, .gl-field select:focus { outline: none; border-color: var(--c-ink-3);
  box-shadow: var(--ui-ring); }
#gl-gstin { font-family: var(--mono); font-size: 14.5px; letter-spacing: 0.06em; text-transform: uppercase; }
#gl-gstin::placeholder { font-family: var(--sans); letter-spacing: 0; text-transform: none; }
.gl-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.gl-manual { display: flex; flex-direction: column; gap: 14px; }
.gl-manual[hidden] { display: none; }
.gl-nogst { align-self: flex-start; margin-top: -4px; }

/* The code: six cells over one real input, so paste and SMS/email autofill
 * work as they do anywhere else, and the cells only mirror what it holds. */
.gl-otp { position: relative; }
.gl-otp input { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0;
  font-size: 16px; caret-color: transparent; border: 0; }
.gl-otp-cells { display: grid; grid-template-columns: repeat(6, 1fr); gap: 8px; pointer-events: none; }
.gl-otp-cells span { display: flex; align-items: center; justify-content: center; height: 56px;
  font: 600 24px/1 var(--mono); color: var(--c-ink); background: var(--c-surface);
  border: 1px solid var(--ui-edge); border-radius: 12px;
  transition: border-color 140ms ease, box-shadow 140ms ease; }
.gl-otp-cells span.on { border-color: var(--c-ink-3); box-shadow: var(--ui-ring); }

/* The registry's answer, confirmed at a glance. */
.gl-found { display: grid; grid-template-columns: 24px 1fr auto; gap: 10px; align-items: start;
  padding: 14px 16px; }
.gl-found-mark { width: 22px; height: 22px; margin-top: 1px; border-radius: 50%; color: var(--c-paper);
  background: var(--accent); display: flex; align-items: center; justify-content: center; }
.gl-found-mark svg { width: 15px; height: 15px; }
.gl-found-body { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.gl-found-body b { font-size: 14.5px; font-weight: 600; color: var(--c-ink); }
.gl-found-body span { font-size: 12.5px; line-height: 1.45; color: var(--c-ink-2); }
.gl-found-body small { font-size: 11.5px; color: var(--c-ink-3); }

.gl-invoices { display: flex; align-items: center; justify-content: space-between; gap: 12px;
  font-size: 13px; color: var(--c-ink-3); }
.gl-invoices[hidden] { display: none; }
.gl-invoices b { color: var(--c-ink); font-weight: 500; overflow-wrap: anywhere; }

/* ── review: the rows you can change, then the money ── */
.gl-row { display: grid; grid-template-columns: 78px 1fr 14px; align-items: center; gap: 10px;
  width: 100%; min-height: 50px; padding: 12px 16px; border: 0; border-radius: 0;
  background: none; color: var(--c-ink); text-align: left; font-weight: 400; line-height: 1.35; }
.gl-row[hidden] { display: none; }
.gl-row + .gl-row, .gl-agents { box-shadow: inset 0 0.5px 0 var(--ui-sep); }
button.gl-row:hover { background: var(--ui-fill-2); }
button.gl-row:active { transform: none; background: var(--ui-fill); }
button.gl-row:disabled { opacity: 1; }
.gl-row-k { font-size: 13px; color: var(--c-ink-3); }
.gl-row-v { font-size: 14px; font-weight: 500; overflow-wrap: anywhere; }
.gl-chev { width: 7px; height: 7px; justify-self: end; border-right: 1.5px solid var(--c-ink-4);
  border-top: 1.5px solid var(--c-ink-4); transform: rotate(45deg); }
.gl-chev-down { transform: rotate(135deg); }
.gl-row[aria-expanded=true] .gl-chev-down { transform: rotate(-45deg); }
.gl-locked .gl-chev { visibility: hidden; }
.gl-agents { display: flex; flex-direction: column; padding: 6px 16px 10px 104px; }
.gl-agents[hidden] { display: none; }
.gl-agent { display: grid; grid-template-columns: 18px 1fr; column-gap: 10px; align-items: center;
  padding: 7px 0; font-size: 13.5px; color: var(--c-ink); }
.gl-agent input { margin: 0; accent-color: var(--c-ink); }
.gl-agent small { grid-column: 2; font-size: 11.5px; color: var(--c-ink-3); }

.gl-bill { padding: 6px 16px; }
.gl-bill-row { display: flex; justify-content: space-between; gap: 12px; padding: 8px 0;
  font-size: 14px; color: var(--c-ink-2); font-variant-numeric: tabular-nums; }
.gl-bill-row.gl-total { margin-top: 4px; padding-top: 12px; box-shadow: inset 0 0.5px 0 var(--ui-sep);
  font-size: 17px; font-weight: 600; color: var(--c-ink); letter-spacing: -0.01em; }
.gl-pay { margin-top: 2px; }
.gl-secure { display: flex; align-items: center; justify-content: center; gap: 6px; margin: -4px 0 0;
  font-size: 12px; color: var(--c-ink-3); text-align: center; }
.gl-secure svg { width: 13px; height: 13px; flex: 0 0 auto; }

/* ── the server's steps: a sentence, and nothing moving ── */
.gl-state { text-align: center; padding: 18px 0 4px; }
.gl-state h3 { margin: 0; font-size: 19px; font-weight: 600; letter-spacing: -0.02em; }
.gl-state p { margin: 0; font-size: 14px; line-height: 1.55; color: var(--c-ink-2); }
.gl-state h3.mark-open { display: inline-block; margin-inline: auto; padding: 2px 10px;
  border-radius: 8px; background: repeating-linear-gradient(135deg, var(--c-open-bg) 0 6px,
  rgba(138, 89, 0, 0.06) 6px 8px); }
#lv-live.gl-state { gap: 12px; padding-top: 8px; }
#lv-live .lv-launch-mark { width: 88px; height: 88px; margin: 0 auto 6px; }
#lv-live #lv-live-title { margin: 0; font-size: 28px; letter-spacing: -0.03em; }
#lv-live #lv-live-p { margin: 0 auto 4px; max-width: 34ch; font-size: 14px; }
#lv-live #lv-live-test { margin: 0; }
.gl-endpoint { display: flex; flex-direction: column; gap: 6px; padding: 12px 16px; text-align: left; }
.gl-endpoint > span { font-size: 12px; color: var(--c-ink-3); }
.gl-endpoint > div { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.gl-endpoint code { font: 500 13px/1.4 var(--mono); color: var(--c-ink); overflow-wrap: anywhere; }

@media (max-width: 760px) {
  .golive-dlg .cmp-dlgpanel { width: 100%; height: 100dvh; max-height: 100dvh; border-radius: 0;
    box-shadow: none; padding-bottom: env(safe-area-inset-bottom); }
  .golive-dlg .cmp-dlgbody { padding: 6px 18px 24px; }
  .gl-agents { padding-left: 16px; }
}
@media (prefers-reduced-motion: reduce) {
  .gl-progress span, .gl-radio { transition: none; }
}

/* ══ going live · the overture ══════════════════════════════════════════════
 * The stage the sheet arrives on (golive-stage.js). A dark panel, the rail's
 * own --c-dark, with paper light along its bottom and one sentence on its left
 * whose "!" is the one green in the console. Then the sheet comes in on
 * the right, where the light is, and the stage stays behind it as its backdrop
 * until the sheet closes.
 *
 * Every class here is .gl-stage-*, never .gl-stage or .gl-field: those two are
 * already the sheet's own (its step container and its form fields), and a rule
 * written for the stage would land on every field of the form.
 *
 * The resting state is the finished picture. .is-playing lays the overture's
 * motion over it, .is-settled zeroes whatever motion is left so a skip lands
 * on the finished picture at once, .is-short is the quick fade every later
 * open gets, .is-leaving the exit, faster than any entrance. Nothing loops.
 * The letters' flight, the flash and the kick of the lock are Web Animations
 * started by golive-stage.js; what is here is timed to the same lock, 1.3 s.
 * ═══════════════════════════════════════════════════════════════════════════ */
:root { --gl-stage-w: min(1180px, 100vw - 48px); --gl-stage-h: min(720px, 90dvh); }

dialog.gl-stage-dlg { position: fixed; inset: 0; width: 100%; max-width: none;
  height: 100%; max-height: none; margin: 0; padding: 0; border: 0;
  background: transparent; overflow: hidden; z-index: 59; color: var(--c-on-dark); }
dialog.gl-stage-dlg[open] { display: flex; align-items: center; justify-content: center; }
/* The scrim is the stage's own layer, not its ::backdrop: after the handoff it
 * is a non-modal dialog, which has no backdrop. */
dialog.gl-stage-dlg::backdrop { background: transparent; }
.gl-stage-dlg::before { content: ""; position: absolute; inset: 0; background: var(--c-scrim); }
.gl-stage-panel { position: relative; width: var(--gl-stage-w); height: var(--gl-stage-h);
  overflow: hidden; border-radius: 22px; background: var(--c-dark);
  box-shadow: 0 0 0 0.5px rgba(241, 239, 233, 0.08), var(--ui-float); }
.gl-stage-panel:focus, .gl-stage-panel:focus-visible { outline: none; }
.gl-stage-under, .gl-stage-field, .gl-stage-flock, .gl-stage-vignette, .gl-stage-shade,
.gl-stage-bloom { position: absolute; inset: 0; pointer-events: none; }
/* The light every browser gets, there before the engine and instead of it. */
.gl-stage-under {
  background: radial-gradient(60% 55% at 68% 110%, rgba(251, 250, 248, 0.16), transparent 70%),
              radial-gradient(45% 40% at 30% 120%, rgba(251, 250, 248, 0.07), transparent 70%); }
/* The shader's canvas, then its photograph. Masked toward the left so the
 * light lives under the sheet rather than under the sentence. */
.gl-stage-field { opacity: 0; background: center / cover no-repeat;
  -webkit-mask-image: linear-gradient(100deg, rgb(0 0 0 / 0.25) 15%, black 62%);
          mask-image: linear-gradient(100deg, rgb(0 0 0 / 0.25) 15%, black 62%); }
.gl-stage-field[data-lit] { opacity: 1; }
.gl-stage-field canvas, .gl-stage-flock canvas { position: absolute; inset: 0; display: block; }
/* The flock: the chaos and its turn to order, on its own canvas so it can
 * leave once the sheet is in. Masked like the light, a little harder. */
.gl-stage-flock { opacity: 0;
  -webkit-mask-image: linear-gradient(100deg, rgb(0 0 0 / 0.2) 10%, black 60%);
          mask-image: linear-gradient(100deg, rgb(0 0 0 / 0.2) 10%, black 60%); }
.gl-stage-flock[data-lit] { opacity: 1; }
/* The flash of the lock, paper over the light and under the words. */
.gl-stage-bloom { opacity: 0; background: var(--c-paper); }
.gl-stage-vignette {
  background: radial-gradient(120% 90% at 50% 50%, transparent 55%, rgba(21, 21, 19, 0.55) 100%); }
/* The rays cross the left of the panel; this keeps the sentence in clean dark
 * at every frame of them. */
.gl-stage-shade { background: linear-gradient(90deg, rgba(21, 21, 19, 0.72) 0%,
  rgba(21, 21, 19, 0.45) 38%, transparent 62%); }
.gl-stage-words { position: absolute; left: 64px; top: 50%; transform: translateY(-50%);
  max-width: max(300px, calc(100% - 468px - 32px - 64px - 48px)); }
.gl-stage-words h2 { margin: 0; font-size: clamp(36px, 4.6vw, 56px); line-height: 1.05;
  font-weight: 600; letter-spacing: -0.035em; color: var(--c-on-dark); text-wrap: balance; }
.gl-stage-word, .gl-stage-glyph { display: inline-block; }
/* The "!" is the on-air mark: the one green, the green the switch lights when
 * a workspace is live. Green at rest, so the short form, a skip and the
 * photograph all show it lit; only the overture brings it in the headline's
 * colour and turns it at the lock. */
.gl-stage-mark { color: var(--accent-on-dark); }
.gl-stage-words p { margin: 18px 0 0; max-width: 34ch; font-size: 17px; line-height: 1.45;
  color: var(--c-on-dark-2); }

/* The overture: scrim, a fast panel, the glow rising, then at the lock the
 * "!" turning green with one glow that never comes back, and the line under
 * it after. */
.gl-stage-dlg.is-playing::before { animation: gl-stage-fade 200ms var(--ease-out) both; }
.gl-stage-dlg.is-playing .gl-stage-panel { transform-origin: 50% 50%;
  animation: gl-stage-panel-in 280ms var(--ease-out) both; }
.gl-stage-dlg.is-playing .gl-stage-under { animation: gl-stage-fade 900ms var(--ease-out) both,
  gl-stage-rise 1400ms var(--ease-out) both; }
.gl-stage-dlg.is-playing .gl-stage-mark { animation: gl-stage-mark-lit 240ms var(--ease-out) 1300ms both,
  gl-stage-mark-glow 900ms ease-out 1300ms both; }
.gl-stage-dlg.is-playing .gl-stage-words p { animation: gl-stage-sub-in 480ms var(--ease-out) 1600ms both; }
.gl-stage-dlg.is-settled::before, .gl-stage-dlg.is-settled .gl-stage-panel,
.gl-stage-dlg.is-settled .gl-stage-under, .gl-stage-dlg.is-playing.is-settled .gl-stage-mark,
.gl-stage-dlg.is-settled .gl-stage-words p { animation-duration: 0s; animation-delay: 0s; }
/* A press under reduced motion: no overture, but the mark still lights, as a
 * change of colour and nothing else, while the stage fades in. */
.gl-stage-dlg.is-lit .gl-stage-mark { animation: gl-stage-mark-lit 240ms ease-out 160ms both; }

/* The speaker: shown only while the overture plays, because that is the only
 * time there is anything to hear. Gone at the handoff, when the stage behind
 * the sheet can no longer be pressed. A 44px target around a 28px mark. */
.gl-stage-sound { position: absolute; left: 18px; bottom: 16px; display: none;
  width: 28px; height: 28px; padding: 0; align-items: center; justify-content: center;
  border: 0; border-radius: 50%; background: transparent; color: var(--c-on-dark-3);
  cursor: pointer; transition: color 120ms ease, transform 140ms var(--ease-out),
  opacity 200ms var(--ease-out), visibility 0s linear 200ms; }
.gl-stage-sound::after { content: ""; position: absolute; inset: -8px; }
.gl-stage-sound svg { width: 16px; height: 16px; }
.gl-stage-sound[aria-pressed="true"] .gl-stage-sound-off,
.gl-stage-sound[aria-pressed="false"] .gl-stage-sound-on { display: none; }
.gl-stage-sound:focus-visible { outline: 2px solid var(--c-on-dark-3); outline-offset: 2px; }
.gl-stage-dlg.is-playing .gl-stage-sound { display: flex; }
.gl-stage-dlg.is-playing.is-settled .gl-stage-sound { opacity: 0; visibility: hidden; }
@media (hover: hover) and (pointer: fine) {
  .gl-stage-sound:hover { color: var(--c-on-dark-2); }
}
.gl-stage-dlg.is-short { animation: gl-stage-fade 240ms var(--ease-out) both; }
.gl-stage-dlg.is-leaving::before { animation: gl-stage-fade-out 200ms var(--ease-out) both; }
.gl-stage-dlg.is-leaving .gl-stage-panel { animation: gl-stage-panel-out 200ms var(--ease-out) both; }
@keyframes gl-stage-fade { from { opacity: 0; } }
@keyframes gl-stage-fade-out { to { opacity: 0; } }
@keyframes gl-stage-panel-in { from { opacity: 0; transform: scale(0.92); } }
@keyframes gl-stage-panel-out { to { opacity: 0; transform: scale(0.98); } }
@keyframes gl-stage-rise { from { transform: translateY(40px); } }
@keyframes gl-stage-mark-lit { from { color: var(--c-on-dark); } }
@keyframes gl-stage-mark-glow { 0% { text-shadow: 0 0 0 rgba(79, 192, 138, 0); }
                                35% { text-shadow: 0 0 18px rgba(79, 192, 138, 0.85); }
                                100% { text-shadow: 0 0 0 rgba(79, 192, 138, 0); } }
@keyframes gl-stage-sub-in { from { opacity: 0; transform: translateY(10px); } }

/* The sheet, staged: on the stage's right, 32px in from its edge, with no scrim
 * of its own (the stage is the scrim). Its rules above are untouched; these
 * only apply while the stage is up. Vertical centring is both dialogs' own
 * align-items, so the two line up without measuring anything. */
dialog.golive-dlg.gl-staged[open] { justify-content: flex-end; box-sizing: border-box;
  padding-right: calc((100vw - var(--gl-stage-w)) / 2 + 32px); }
dialog.golive-dlg.gl-staged::backdrop { background: transparent; }
.golive-dlg.gl-staged .cmp-dlgpanel { max-height: calc(var(--gl-stage-h) - 64px);
  box-shadow: 0 0 0 0.5px rgba(241, 239, 233, 0.10), 0 30px 80px rgba(0, 0, 0, 0.55);
  animation: gl-sheet-in 560ms var(--ui-spring) both; }
/* Arrived. Checkout shuts the sheet and reopens it (glUnderlay), which would
 * otherwise play the slide again each time. */
.golive-dlg.gl-staged.gl-landed .cmp-dlgpanel { animation: none; }
@keyframes gl-sheet-in { from { opacity: 0; transform: translateX(40px) scale(0.98); } }

@media (max-width: 760px) {
  .gl-stage-panel { width: 100%; height: 100dvh; border-radius: 0; box-shadow: none; }
  .gl-stage-words { left: 24px; right: 24px; max-width: none; }
  .gl-stage-shade { background: linear-gradient(180deg, transparent 15%,
    rgba(21, 21, 19, 0.55) 40%, rgba(21, 21, 19, 0.55) 60%, transparent 85%); }
  dialog.golive-dlg.gl-staged[open] { padding: 0; justify-content: center; }
  /* The phone sheet is full screen, as it is unstaged. */
  .golive-dlg.gl-staged .cmp-dlgpanel { max-height: 100dvh; box-shadow: none; }
}
/* Never the overture here (golive-stage.js plays only the short form), and
 * every fade is opacity alone. */
@media (prefers-reduced-motion: reduce) {
  .gl-stage-dlg.is-short { animation-duration: 200ms; }
  .gl-stage-dlg.is-leaving::before, .gl-stage-dlg.is-leaving .gl-stage-panel {
    animation: gl-stage-fade-out 160ms ease-out both; }
  .golive-dlg.gl-staged .cmp-dlgpanel { animation: gl-stage-fade 180ms ease-out both; }
}

.marketing-event { border-top: 1px solid var(--c-line); padding: 14px 0; }
.marketing-event summary { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 6px 16px; cursor: pointer; }
.marketing-event-title::before { content: "+"; display: inline-block; width: 18px; }
.marketing-event[open] .marketing-event-title::before { content: "−"; }
.marketing-event-title { font-weight: 600; font-size: 14px; }
.marketing-event-subject { overflow-wrap: anywhere; font-size: 13px; color: var(--c-ink-3); }
.marketing-event time { font-size: 12px; text-align: right; }
.marketing-event-actor { text-align: right; font-size: 12px; color: var(--c-ink-3); }
.marketing-event summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; border-radius: 4px; }
.marketing-changes { width: 100%; table-layout: fixed; border-collapse: collapse; margin-top: 14px; font-size: 12px; }
.marketing-changes th, .marketing-changes td { text-align: left; vertical-align: top; padding: 9px 8px; border-top: 1px solid var(--c-line); overflow-wrap: anywhere; white-space: pre-wrap; }
.marketing-changes th:first-child { width: 24%; }
@media (max-width: 600px) {
  .howrow.admin-tabs { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .marketing-event summary { grid-template-columns: minmax(0, 1fr); }
  .marketing-event time, .marketing-event-actor { text-align: left; }
  .marketing-changes th:first-child { width: 28%; }
}

/* ── the anonymous sandbox ────────────────────────────────────────────────────
 * A visitor with no code yet (console/anon_sandbox.py, static/anon.js): the
 * same console, read-only, and one ask on the first press that would spend
 * engine time or create something. Nothing here adds a colour or a radius;
 * every value is a token the console already uses. */

/* "Cookie settings" at the rail's foot, where the brand pill and "sign out"
 * sit for a session. */
.anon-link { margin: 0; padding: 4px 0; background: none; border: 0; cursor: pointer;
             font: 500 11px/1.3 var(--sans); color: var(--c-on-dark-2); }
.anon-link:hover { color: var(--c-on-dark); background: none; transform: none; }
.sheet-body .anon-link { min-height: 44px; color: var(--c-ink-2); font-size: 13px; }

/* The ask: THE MODAL's shell around the gate's own card. The card's frame is
 * the modal's now, so it drops its own; its fields keep the modal's. */
.intent-dlg .modal-head { padding-bottom: 10px; }
.intent-dlg .gate-card { padding: 0; max-width: none; background: transparent; border: 0;
                         border-radius: 0; box-shadow: none; animation: none; gap: 10px; }
.intent-dlg .gate-card h1 { font-size: 15px; letter-spacing: -0.012em; }
.intent-dlg .gate-card button:not(.linkbtn) { min-height: 40px; border-radius: var(--ui-r-control); }
.modal-sub[hidden] { display: none; }

/* The email sheet (A2) in the same body: two fields, one button, and the two
 * other ways in underneath, small. Its states swap with the gate's own 160ms
 * fade (.gate-in) and nothing moves. */
.claim { position: relative; }
.claim-state { display: flex; flex-direction: column; gap: 10px; }
.claim-state[hidden] { display: none; }
.claim-label { margin: 2px 0 -4px; font-size: 12px; font-weight: 500; color: var(--c-ink-2); }
.claim-optional { margin-left: 4px; font-weight: 400; color: var(--c-ink-3); }
.claim-state input { width: 100%; padding: 11px 12px; font-size: 15px; }
.claim-state button:not(.linkbtn) { min-height: 40px; padding: 11px 14px; font-size: 13px;
                                    font-weight: 600; border-radius: var(--ui-r-control); }
.claim .err { min-height: 16px; font-size: 11.5px; font-weight: 500; line-height: 1.4;
              color: var(--c-alarm-ink); }
.claim .err:not(:empty)::before {
  content: ''; display: inline-block; width: 7px; height: 7px; border-radius: 1px;
  margin-right: 5px; background: var(--c-alarm); }
.claim-fine { margin: 0; font-size: 11.5px; line-height: 1.6; color: var(--c-ink-3); }
.claim-alt { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; margin: 2px 0 0;
             font-size: 12px; color: var(--c-ink-3); }
.claim-alt .linkbtn { padding: 0; font-size: inherit; }
.claim-back { margin: 0; }
.claim-copy { margin: 0; font-size: 13.5px; line-height: 1.6; color: var(--c-ink-2); }
.claim-copy b { font-weight: 600; color: var(--c-ink); overflow-wrap: anywhere; }
.claim-acts { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; }
.claim-status { font-size: 12px; color: var(--c-ink-3); font-variant-numeric: tabular-nums; }
.claim-status:empty { display: none; }
/* For bots: off-screen, out of the tab order and the accessibility tree. */
.claim-trap { position: absolute; left: -10000px; width: 1px; height: 1px; overflow: hidden;
              clip-path: inset(50%); white-space: nowrap; }
@media (max-width: 760px) {
  /* Under 16px iOS zooms the page into the field. */
  .claim-state input { font-size: 16px; }
  .claim-state button:not(.linkbtn) { min-height: 44px; }
  .claim-alt .linkbtn { min-height: 44px; }
}

/* A view with nothing to show yet: one tile, one sentence, one button. */
.anon-tile { display: flex; flex-direction: column; align-items: flex-start; gap: 12px;
             max-width: 560px; padding: 24px; }
.anon-tile-mark { display: grid; place-items: center; width: 36px; height: 36px;
                  border-radius: var(--r-sm); background: var(--c-sunk); color: var(--c-ink-2); }
.anon-tile-mark svg { width: 18px; height: 18px; }
.anon-tile-line { margin: 0; max-width: 52ch; font-size: 13.5px; line-height: 1.6;
                  color: var(--c-ink-2); }
.anon-tile button { margin: 4px 0 0; }

/* Billing, before there is a bill: the offer a visitor would start on. */
.anon-offer { max-width: 560px; padding: 24px; }
.anon-offer-lead { margin: 0; font: 650 28px/1.15 var(--sans); letter-spacing: -0.025em;
                   color: var(--c-ink); font-variant-numeric: tabular-nums; }
.anon-offer-lead[hidden] { display: none; }
.anon-offer-terms { margin: 6px 0 0; font-size: 13.5px; color: var(--c-ink-2);
                    font-variant-numeric: tabular-nums; }
.anon-offer-facts { display: grid; gap: 6px; margin: 18px 0 20px; padding: 14px 0 0;
                    list-style: none; border-top: 1px solid var(--c-line);
                    font-size: 12.5px; line-height: 1.6; color: var(--c-ink-3); }
.anon-offer button { margin: 0; }

/* A model page's Logs and API tabs: their furniture stays in the DOM (the
 * page's own script still writes into it) and one line stands in front. */
.anon-preview > :not(.anon-empty) { display: none !important; }
.anon-empty { grid-column: 1 / -1; padding: 20px 22px; font-size: 13px; line-height: 1.6;
              color: var(--c-ink-3); background: var(--c-surface);
              border: 1px solid var(--c-line); border-radius: var(--r-md); }
.anon-empty p { margin: 0 0 4px; max-width: 58ch; }
.anon-empty .linkbtn { padding: 0; font-size: 12.5px; }

/* Developers: what one key opens, where the key list will be. */
.anon-unlocks { padding: 12px 0 2px; border-top: 1px solid var(--c-line); margin-top: 12px; }
.anon-unlocks-head { margin: 0 0 8px; font-size: 12.5px; font-weight: 600; color: var(--c-ink); }
.anon-unlocks ul { display: grid; gap: 6px; margin: 0; padding: 0; list-style: none;
                   font-size: 12.5px; line-height: 1.55; color: var(--c-ink-3); }
.anon-unlocks b { color: var(--c-ink-2); font-weight: 600; }

/* `.nav-item { display: flex }` outranks the `hidden` attribute, so a rail
 * item core.js hides (Company details, Team) would still be drawn the moment
 * its section is shown. Said once for the rail, and for the phone's tab bar
 * while a guest pass is in use. */
.nav .nav-item[hidden], html[data-session="guest"] .tabbar .nav-item[hidden] { display: none; }

/* ── the guest pass (static/anon.js) ─────────────────────────────────────────
 * The strip is the offer strip (promotion.css) as it is. The rail's credit
 * row is the wallet's block with the pass's numbers; spent, its figure goes
 * quiet. Tokens only; the stage that mints the pass is guest-stage.js's. */
.guest-credit[hidden] { display: none; }
/* On a phone the strip's second line starts a line of its own: no dot. */
@media (max-width: 600px) { .guest-strip-lead { display: none; } }
.guest-credit[data-spent="true"] .wallet-bal b { color: var(--c-on-dark-2); }
/* The meter under End call / End chat: the rail cost line's own type, with
 * figures that do not shift as they tick. */
.guest-meter { margin-top: 8px; font-variant-numeric: tabular-nums; }
.guest-meter[hidden] { display: none; }

/* ── the cookie choice (A9) ──────────────────────────────────────────────────
 * One small card, only for a session the Google tag may run on and only until
 * a choice is made (static/measure.js): a title, one line, one button and a
 * quiet × in the corner. It floats over the page, so it takes the floating
 * shadow; every other value is a token the console already uses. It rises in
 * once, the way a toast does, and goes at once: leaving is the answer to a
 * press. */
.consent { position: fixed; z-index: 150; left: 0; right: 0; bottom: 16px;
           width: min(460px, calc(100vw - 32px)); margin: 0 auto;
           display: flex; flex-direction: column; gap: 12px; padding: 16px;
           background: var(--c-surface); border-radius: var(--ui-r-card);
           box-shadow: var(--ui-float); color: var(--c-ink-2);
           font-size: 12.5px; line-height: 1.55;
           animation: toast-in 200ms var(--ease-out) both; }
.consent[hidden] { display: none; }
.consent-copy { display: flex; flex-direction: column; gap: 3px; }
.consent-title { margin: 0; padding-right: 28px; font-size: 13.5px; font-weight: 600;
                 letter-spacing: -0.006em; color: var(--c-ink); }
.consent-line { margin: 0; }
.consent-line a { color: var(--c-ink); white-space: nowrap; text-decoration: underline;
                  text-decoration-color: var(--c-ink-4); text-underline-offset: 2px; }
.consent-line a:hover { text-decoration-color: currentColor; }
.consent-acts { display: flex; justify-content: flex-end; gap: 8px; }
.consent-acts button { min-height: 34px; padding: 8px 14px; font-size: 12.5px; line-height: 1.25;
                       border-radius: var(--ui-r-control); }
.consent-acts button[hidden] { display: none; }
/* The ×: away for this tab, no choice made. Ink-3 on nothing, a fill only
 * under the pointer, so "Allow All" stays the one thing to press. */
.consent-close { position: absolute; top: 10px; right: 10px; display: grid; place-items: center;
                 width: 28px; height: 28px; min-height: 0; padding: 0; border: 0;
                 border-radius: 999px; background: none; color: var(--c-ink-3); }
.consent-close:hover { background: var(--ui-fill-2); color: var(--c-ink); }
.consent-close:active { background: var(--ui-fill-3); transform: scale(0.94); }
.consent-close svg { width: 12px; height: 12px; }
/* "Cookie settings" at the foot of the rail, under the ways in. */
.cookie-link { align-self: flex-start; padding: 2px 6px; }
.cookie-link[hidden] { display: none; }
@media (max-width: 760px) {
  /* Above the dock and the tab bar, so the Test button stays a thumb away. */
  .consent { left: 10px; right: 10px; width: auto; padding: 14px;
             bottom: calc(var(--dock-h) + var(--tabbar-h) + 10px); }
  .consent-acts button { flex: 1 1 0; min-height: 44px; }
  /* a thumb's target; the glyph stays small */
  .consent-close { top: 4px; right: 4px; width: 40px; height: 40px; }
  .consent-title { padding-right: 32px; }
}
@media (prefers-reduced-motion: reduce) {
  .consent { animation: consent-fade 160ms ease-out both; }
}
@keyframes consent-fade { from { opacity: 0; } to { opacity: 1; } }

/* ═══════════════════════════════════════════════════════════════════════════
 * CALLS COME FROM — one picker, two homes (calls-from.js).
 *
 * The campaign form's "Calls come from" and Agent Studio → Phone → "Calls
 * from this agent". A pop-up button in the modal's own field geometry (edge,
 * radius, ring, the same double chevron), opening a listbox in the console's
 * one menu material: the camp-menu / context-menu surface, 12px corners,
 * --ui-float, rows that wash with --ui-fill-2.
 *
 * Motion: the list grows from the corner of the control it hangs off
 * (transform-origin follows data-side), in on ease-out, out faster than in.
 * It never animates on its own and reduced motion keeps only the fade.
 * ═══════════════════════════════════════════════════════════════════════════ */
.cf-picker { min-width: 0; }
/* trigger and list share this box, so the list hangs from the control and not
 * from the origin line under it */
.cf-anchor { position: relative; }
.cf-trigger { position: relative; display: grid; gap: 1px; width: 100%; min-height: 36px; margin: 0;
  padding: 7px 32px 7px 11px; text-align: left; font: 400 13.5px/1.35 var(--sans); letter-spacing: 0;
  color: var(--c-ink); background-color: var(--c-surface);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' fill='none' stroke='%236f6c65' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3.5 4.75 6 2.5l2.5 2.25M3.5 7.25 6 9.5l2.5-2.25'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 10px center; background-size: 12px 12px;
  border: 1px solid var(--ui-edge); border-radius: var(--ui-r-control); box-shadow: none;
  transition: border-color 120ms ease, box-shadow 120ms ease, transform 140ms var(--ease-out); }
.cf-trigger:hover { border-color: rgba(27, 26, 22, 0.26); }
.cf-trigger:focus-visible, .cf-picker[data-open] .cf-trigger {
  outline: none; border-color: rgba(27, 26, 22, 0.34); box-shadow: var(--ui-ring); }
/* A wide control squashes less than a button does: 1%, still felt. */
.cf-trigger:active { transform: scale(0.99); }
.cf-trigger:disabled { background-color: var(--c-sunk); color: var(--c-ink-3); opacity: 0.55; cursor: not-allowed; }
.cf-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-weight: 500; font-variant-numeric: tabular-nums; }
.cf-busy .cf-name { font-weight: 400; color: var(--c-ink-3); }
.cf-meta { font-size: 12px; line-height: 1.4; color: var(--c-ink-3); overflow-wrap: anywhere; }
.cf-meta:empty { display: none; }
.cf-origin { display: block; margin-top: 6px; font-size: 12px; line-height: 1.4; color: var(--c-ink-3); }
.cf-origin:empty { display: none; }
/* under the open list, the line steps back rather than peeking at its corner */
.cf-origin { transition: opacity 100ms var(--ease-out); }
.cf-picker[data-open] .cf-origin { opacity: 0; }

.cf-pop { position: absolute; left: 0; top: calc(100% + 6px); z-index: 30;
  width: max(100%, 340px); max-width: min(400px, calc(100vw - 32px));
  padding: 5px; background: var(--c-surface); border-radius: 12px; box-shadow: var(--ui-float);
  transform-origin: top left; transform: scale(0.96); opacity: 0; visibility: hidden; pointer-events: none;
  transition: transform 100ms var(--ease-out), opacity 100ms var(--ease-out), visibility 0s linear 100ms; }
.cf-picker[data-side="top"] .cf-pop { top: auto; bottom: calc(100% + 6px); transform-origin: bottom left; }
.cf-picker[data-open] .cf-pop { transform: none; opacity: 1; visibility: visible; pointer-events: auto;
  transition: transform 150ms var(--ease-out), opacity 120ms var(--ease-out), visibility 0s; }
.cf-list { max-height: 280px; overflow: auto; overscroll-behavior: contain; outline: none; }
.cf-group { padding: 8px 8px 4px; font-size: 11px; font-weight: 600; color: var(--c-ink-3); }
.cf-shared { margin-top: 4px; padding-top: 4px; border-top: 1px solid var(--ui-sep); }
.cf-list > .cf-shared:first-child { margin-top: 0; padding-top: 0; border-top: 0; }
/* The number is never cut: it takes its own width and the words beside it
 * wrap instead. */
.cf-opt { display: grid; grid-template-columns: 16px max-content minmax(0, 1fr); align-items: baseline; column-gap: 10px;
  width: 100%; min-height: 34px; margin: 0; padding: 8px 10px 8px 6px; border: 0; border-radius: 8px;
  background: none; color: var(--c-ink); font: 400 13px/1.35 var(--sans); text-align: left; }
.cf-opt:hover:not(:disabled), .cf-opt:focus-visible { background: var(--ui-fill-2); outline: none; }
.cf-opt:active { transform: none; background: var(--ui-fill); }
.cf-opt:disabled { opacity: 1; color: var(--c-ink-3); cursor: default; }
.cf-check { grid-row: 1 / span 2; align-self: center; width: 14px; height: 14px; visibility: hidden; color: var(--c-ink); }
.cf-opt[aria-selected="true"] .cf-check { visibility: visible; }
.cf-opt-name { font-weight: 500; white-space: nowrap; }
.cf-opt-meta { font-size: 12px; line-height: 1.4; color: var(--c-ink-3); text-align: right; }
/* The shared line says what it is in a sentence, so it takes two lines. */
.cf-opt-wide .cf-opt-meta { grid-column: 2 / -1; text-align: left; }
.cf-note { margin: 4px 8px 6px; padding-top: 8px; border-top: 1px solid var(--ui-sep);
  font-size: 12px; line-height: 1.5; color: var(--c-ink-3); }
.cf-note a, .cf-cap-link { color: var(--c-ink); font-weight: 500; text-decoration: none; white-space: nowrap; }
.cf-note a:hover, .cf-cap-link:hover { text-decoration: underline; }

/* The campaign form's who-row: the picker's column is the widest, as the
 * mockup draws it. One column on a phone, like every modal grid. */
@media (min-width: 641px) {
  .modal-grid.cf-who { grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr) minmax(0, 1fr); }
}
/* The capacity line under the pacing grid: arithmetic, never an error. The
 * caution is amber with the hatch beside it (rule 2: a shape as well as a
 * hue); "covers it" gets the hollow ring; a fact about the shared line gets
 * no mark at all. */
.cf-cap { margin: 2px 0 16px; padding: 11px 14px; border-radius: var(--ui-r-card); background: var(--c-sunk);
  font-size: 12.5px; line-height: 1.55; color: var(--c-ink-2); }
.cf-cap[hidden] { display: none; }
.cf-cap b { color: var(--c-ink); font-weight: 600; font-variant-numeric: tabular-nums; }
.cf-cap[data-tone="open"] .cf-cap-note { color: var(--c-open-ink); }

/* Agent Studio → Phone. Two cards: who rings the agent, what it rings from. */
.inbound-card > .inbound-head { margin: 0; padding: 12px 18px 8px; }
.cf-card[hidden] { display: none; }
.cf-row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 14px; }
.cf-row > .cf-picker { flex: 0 1 360px; }
.cf-save { margin: 0; font-size: 12.5px; color: var(--c-ink-3); transition: opacity 200ms var(--ease-out); }
.cf-save:empty { opacity: 0; }
.cf-save.cf-error { color: var(--c-alarm-ink); }

/* The number a call came from, in the Calls list and the inspector header. */
.hist-from { color: var(--c-ink-3); font-size: 11px; font-variant-numeric: tabular-nums; }
.hist-from:empty, .insp-from:empty { display: none; }
.insp-from { font-size: 11px; line-height: 1.45; color: var(--c-ink-3); font-variant-numeric: tabular-nums; }

@media (prefers-reduced-motion: reduce) {
  .cf-pop, .cf-picker[data-open] .cf-pop { transform: none; transition: opacity 120ms ease, visibility 0s linear 120ms; }
  .cf-picker[data-open] .cf-pop { transition: opacity 120ms ease, visibility 0s; }
  .cf-trigger:active { transform: none; }
}


/* guest stage ═══════════════════════════════════════════════════════════════
 * The guest pass, minted in front of the visitor (guest-stage.js; plan
 * docs/sprints/prod/PLAN_GUEST_PASS_2026-10-09.md §3). The overture's room:
 * its panel geometry (--gl-stage-w/h, read here, never redefined), the rail's
 * --c-dark, paper light and the one --accent-on-dark mark, the full stop. Its
 * own selectors throughout, .gs-stage-*: nothing here reaches the overture or
 * the go-live sheet. No hue is added: every colour is a token, or a token
 * mixed with transparent.
 *
 * The resting state is the finished picture. .is-playing lays the chaos over
 * it, .is-snapped is the lock, .is-said brings in the copy and the buttons,
 * .is-short is the still form (reduced motion, a repeat visit, a hidden tab),
 * .is-skipped a chaos cut short, .is-failed the failure with the sign-in card
 * moved in, .is-busy the page booting after a press, .is-leaving and
 * .is-going the two exits. Nothing loops, and nothing counts down: only a
 * press goes forward. The letters' flight, the code's lock, the flash and the
 * kick are Web Animations started by guest-stage.js; what is here is the room
 * and its entrances.
 * ═══════════════════════════════════════════════════════════════════════════ */
dialog.gs-stage-dlg { position: fixed; inset: 0; width: 100%; max-width: none;
  height: 100%; max-height: none; margin: 0; padding: 0; border: 0;
  background: transparent; overflow: hidden; color: var(--c-on-dark); }
dialog.gs-stage-dlg[open] { display: flex; align-items: center; justify-content: center; }
/* Modal for its whole life (it never steps down as the overture does), so
 * the scrim is its own ::backdrop, over the sign-in page. */
dialog.gs-stage-dlg::backdrop { background: var(--c-scrim); }
.gs-stage-panel { position: relative; overflow: hidden;
  width: var(--gl-stage-w, min(1180px, 100vw - 48px)); height: var(--gl-stage-h, min(720px, 90dvh));
  border-radius: 22px; background: var(--c-dark);
  box-shadow: 0 0 0 0.5px color-mix(in srgb, var(--c-on-dark) 8%, transparent), var(--ui-float); }
.gs-stage-panel:focus, .gs-stage-panel:focus-visible { outline: none; }
.gs-stage-under, .gs-stage-field, .gs-stage-flock, .gs-stage-vignette, .gs-stage-shade,
.gs-stage-bloom { position: absolute; inset: 0; pointer-events: none; }
/* The light every browser gets, there before the engine and instead of it. */
.gs-stage-under {
  background: radial-gradient(60% 55% at 72% 110%, color-mix(in srgb, var(--c-paper) 16%, transparent), transparent 70%),
              radial-gradient(45% 40% at 34% 120%, color-mix(in srgb, var(--c-paper) 7%, transparent), transparent 70%); }
/* The shader's canvas, then its photograph (or the one this browser kept).
 * Masked toward the left so the light lives beside the words, not under them. */
.gs-stage-field { opacity: 0; background: center / cover no-repeat;
  -webkit-mask-image: linear-gradient(100deg, rgb(0 0 0 / 0.25) 15%, rgb(0 0 0) 62%);
          mask-image: linear-gradient(100deg, rgb(0 0 0 / 0.25) 15%, rgb(0 0 0) 62%); }
.gs-stage-field[data-lit] { opacity: 1; }
.gs-stage-field canvas, .gs-stage-flock canvas { position: absolute; inset: 0; display: block; }
/* The flock, harder still: the copy arrives while it turns, and nothing may
 * cross the words as they are read. */
.gs-stage-flock { opacity: 0;
  -webkit-mask-image: linear-gradient(100deg, rgb(0 0 0 / 0) 36%, rgb(0 0 0) 68%);
          mask-image: linear-gradient(100deg, rgb(0 0 0 / 0) 36%, rgb(0 0 0) 68%); }
.gs-stage-flock[data-lit] { opacity: 1; }
/* The flash of the lock, paper over the light and under the words. */
.gs-stage-bloom { opacity: 0; background: var(--c-paper); }
.gs-stage-vignette { background: radial-gradient(120% 90% at 50% 50%, transparent 55%,
  color-mix(in srgb, var(--c-dark) 55%, transparent) 100%); }
/* Keeps the words in clean dark at every frame of the rays. */
.gs-stage-shade { background: linear-gradient(90deg, color-mix(in srgb, var(--c-dark) 78%, transparent) 0%,
  color-mix(in srgb, var(--c-dark) 50%, transparent) 42%, transparent 64%); }

/* The words: one column on the left, the light on the right. Every line that
 * arrives later already has its space, so nothing above it moves when it does. */
.gs-stage-words { position: absolute; top: 0; bottom: 0; left: 64px;
  width: min(520px, calc(50% - 80px)); display: flex; flex-direction: column; justify-content: center; }
.gs-stage-words p { margin: 0; }
.gs-stage-words .gs-stage-eyebrow { margin: 0 0 14px; font: 600 11.5px/1.3 var(--sans);
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--c-on-dark-3); }
.gs-stage-words h2 { margin: 0; font-size: clamp(36px, 4.6vw, 56px); line-height: 1.05;
  font-weight: 600; letter-spacing: -0.035em; color: var(--c-on-dark); text-wrap: balance; }
.gs-stage-word, .gs-stage-glyph { display: inline-block; }
/* The full stop is the mark: green at rest (the short form and a skip land on
 * it lit), in the headline's colour until the code locks, then it turns, with
 * one glow that never comes back. */
.gs-stage-mark { color: var(--accent-on-dark); }
.gs-stage-dlg:not(.is-snapped) .gs-stage-mark { color: var(--c-on-dark); }
.gs-stage-dlg.is-snapped .gs-stage-mark { animation: gs-stage-mark-lit 240ms var(--ease-out) both; }
.gs-stage-dlg.is-playing.is-snapped:not(.is-skipped) .gs-stage-mark {
  animation: gs-stage-mark-lit 240ms var(--ease-out) both, gs-stage-mark-glow 900ms ease-out both; }

/* The pass and the failure share one cell, so the column keeps its height. */
.gs-stage-stack { display: grid; margin-top: 28px; }
.gs-stage-stack > * { grid-area: 1 / 1; min-width: 0; }
.gs-stage-pass { display: flex; flex-direction: column; }
/* The slot: ink-4 letters ticking while the code is minted, the code in paper
 * once it locks. Monospace, so the ticking never changes the row's width. */
.gs-stage-slot { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: 8px 12px; padding: 8px 8px 8px 16px; border-radius: 12px;
  background: color-mix(in srgb, var(--c-on-dark) 5%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--c-on-dark) 12%, transparent); }
.gs-stage-code { flex: 1 1 12ch; min-width: 0; min-height: 1.25em;
  font: 500 clamp(19px, 1.2vw + 10px, 26px)/1.25 var(--mono); letter-spacing: 0.01em;
  font-variant-ligatures: none; color: var(--c-paper); overflow-wrap: anywhere; user-select: all; }
.gs-stage-dlg:not(.is-snapped) .gs-stage-code { color: var(--c-ink-4); user-select: none; }
.gs-stage-copy { flex: none; display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  min-width: 7.6em; min-height: 36px; margin: 0; padding: 0 14px; border: 0; border-radius: var(--ui-r-control);
  background: color-mix(in srgb, var(--c-on-dark) 10%, transparent); color: var(--c-on-dark);
  font: 600 12.5px/1 var(--sans); cursor: pointer;
  transition: transform 140ms var(--ease-out), background-color 120ms ease; }
.gs-stage-copy:active { transform: scale(0.97); }
.gs-stage-copy:focus-visible { outline: 2px solid var(--c-on-dark-2); outline-offset: 2px; }
/* Copied: a drawn tick, in the label's own colour (the one green is the mark's). */
.gs-stage-copy.is-copied::before { content: ""; width: 9px; height: 5px; margin-top: -3px;
  border-left: 1.6px solid currentColor; border-bottom: 1.6px solid currentColor; transform: rotate(-45deg); }
.gs-stage-words .gs-stage-note { margin: 10px 0 0; min-height: 1.5em; font-size: 13px; line-height: 1.5;
  color: var(--c-on-dark-3); }
.gs-stage-words .gs-stage-copytext { margin: 26px 0 0; max-width: 46ch; font-size: 16px; line-height: 1.55;
  color: var(--c-on-dark-2); text-wrap: pretty; }
.gs-stage-copytext b { font-weight: 600; color: var(--c-on-dark); }
.gs-stage-acts { display: flex; margin-top: 24px; }
/* The way in, and the allow press. Paper on the dark, the press a 3 % squash. */
.gs-stage-go { display: inline-flex; align-items: center; gap: 8px;
  min-height: 48px; margin: 0; padding: 0 22px; border: 0; border-radius: var(--ui-r-control);
  background: var(--c-paper); color: var(--c-dark); font: 600 15px/1 var(--sans); letter-spacing: -0.01em;
  cursor: pointer; transition: transform 140ms var(--ease-out), background-color 120ms ease; }
.gs-stage-go > span { display: inline-block; transition: transform 200ms var(--ease-out); }
.gs-stage-go:active { transform: scale(0.97); }
.gs-stage-go:focus-visible { outline: 2px solid var(--c-on-dark-3); outline-offset: 3px; }
.gs-stage-dlg.is-busy .gs-stage-go { cursor: progress; background: var(--c-on-dark-2); }
.gs-stage-words .gs-stage-consent { margin: 14px 0 0; max-width: 54ch; font-size: 12.5px; line-height: 1.55;
  color: var(--c-on-dark-3); }
.gs-stage-link { display: inline; margin: 0; padding: 0; border: 0; border-radius: 2px; background: none;
  font: inherit; font-weight: 500; line-height: inherit; color: var(--c-on-dark-2); cursor: pointer;
  text-decoration: underline; text-decoration-style: dotted; text-underline-offset: 3px; transition: color 120ms ease; }
.gs-stage-link:active { transform: none; }
.gs-stage-sep { margin: 0 2px; color: var(--c-on-dark-3); }
.gs-stage-link:focus-visible { outline: 2px solid var(--c-on-dark-2); outline-offset: 2px; }
@media (hover: hover) and (pointer: fine) {
  .gs-stage-copy:hover { background: color-mix(in srgb, var(--c-on-dark) 16%, transparent); }
  .gs-stage-go:hover { background: var(--c-surface); }
  .gs-stage-go:hover > span { transform: translateX(3px); }
  .gs-stage-link:hover { color: var(--c-on-dark); }
}
/* What comes in after the lock holds its space before it, hidden, so it can
 * be neither seen nor pressed early. */
.gs-stage-dlg:not(.is-said) .gs-stage-later { visibility: hidden; opacity: 0; }
.gs-stage-dlg.is-said .gs-stage-later { animation: gs-stage-sub-in 480ms var(--ease-out) both; }
.gs-stage-dlg.is-said .gs-stage-copytext { animation-delay: 40ms; }
.gs-stage-dlg.is-said .gs-stage-acts { animation-delay: 90ms; }
.gs-stage-dlg.is-said .gs-stage-consent { animation-delay: 140ms; }
.gs-stage-dlg.is-short.is-said .gs-stage-later,
.gs-stage-dlg.is-skipped.is-said .gs-stage-later { animation: gs-stage-fade 200ms var(--ease-out) both; }

/* The failure: the pass gives way to one sentence, and the sign-in card is
 * moved onto the stage's right, where the light is. */
.gs-stage-fail { visibility: hidden; align-self: start; }
.gs-stage-dlg.is-failed .gs-stage-pass { visibility: hidden; }
.gs-stage-dlg.is-failed .gs-stage-fail { visibility: visible; animation: gs-stage-sub-in 420ms var(--ease-out) both; }
.gs-stage-dlg.is-failed .gs-stage-mark { color: var(--c-on-dark); animation: none; }
.gs-stage-words .gs-stage-failline { font-size: 17px; line-height: 1.45; font-weight: 600; color: var(--c-on-dark); }
.gs-stage-words .gs-stage-failhint { margin-top: 6px; max-width: 40ch; font-size: 14px; line-height: 1.5;
  color: var(--c-on-dark-2); }
.gs-stage-gate { position: absolute; top: 50%; right: 48px; width: min(420px, calc(50% - 72px));
  transform: translateY(-50%); }
.gs-stage-gate:empty { display: none; }
.gs-stage-gate .gate-card { max-width: none; color: var(--c-ink);
  box-shadow: 0 0 0 0.5px color-mix(in srgb, var(--c-on-dark) 10%, transparent),
              0 30px 80px color-mix(in srgb, var(--c-dark) 60%, transparent);
  animation: gs-stage-card-in 560ms var(--ui-spring) both; }

/* The chaos: scrim, a fast panel, the glow rising, the slot and its line
 * arriving. The lock is timed by guest-stage.js (it waits for the code). */
.gs-stage-dlg.is-playing::backdrop { animation: gs-stage-fade 200ms var(--ease-out) both; }
.gs-stage-dlg.is-playing .gs-stage-panel { transform-origin: 50% 50%;
  animation: gs-stage-panel-in 280ms var(--ease-out) both; }
.gs-stage-dlg.is-playing .gs-stage-under { animation: gs-stage-fade 900ms var(--ease-out) both,
  gs-stage-rise 1400ms var(--ease-out) both; }
.gs-stage-dlg.is-playing .gs-stage-eyebrow { animation: gs-stage-fade 600ms var(--ease-out) 200ms both; }
.gs-stage-dlg.is-playing .gs-stage-slot { animation: gs-stage-sub-in 520ms var(--ease-out) 240ms both; }
.gs-stage-dlg.is-playing .gs-stage-note { animation: gs-stage-fade 520ms var(--ease-out) 360ms both; }
.gs-stage-dlg.is-short, .gs-stage-dlg.is-short::backdrop { animation: gs-stage-fade 240ms var(--ease-out) both; }
/* The exits, faster than any entrance. Walking in, the panel opens a touch
 * toward the visitor as it goes. */
.gs-stage-dlg.is-leaving::backdrop { animation: gs-stage-fade-out 200ms var(--ease-out) both; }
.gs-stage-dlg.is-leaving .gs-stage-panel { animation: gs-stage-panel-out 200ms var(--ease-out) both; }
.gs-stage-dlg.is-going .gs-stage-panel { animation: gs-stage-panel-go 240ms var(--ease-out) both; }
@keyframes gs-stage-fade { from { opacity: 0; } }
@keyframes gs-stage-fade-out { to { opacity: 0; } }
@keyframes gs-stage-panel-in { from { opacity: 0; transform: scale(0.92); } }
@keyframes gs-stage-panel-out { to { opacity: 0; transform: scale(0.98); } }
@keyframes gs-stage-panel-go { to { opacity: 0; transform: scale(1.02); } }
@keyframes gs-stage-rise { from { transform: translateY(40px); } }
@keyframes gs-stage-sub-in { from { opacity: 0; transform: translateY(10px); } }
@keyframes gs-stage-card-in { from { opacity: 0; transform: translateX(40px) scale(0.98); } }
@keyframes gs-stage-mark-lit { from { color: var(--c-on-dark); } }
@keyframes gs-stage-mark-glow { 0% { text-shadow: 0 0 0 transparent; }
  35% { text-shadow: 0 0 18px color-mix(in srgb, var(--accent-on-dark) 85%, transparent); }
  100% { text-shadow: 0 0 0 transparent; } }

/* A short window: the same column, tighter. */
@media (min-width: 761px) and (max-height: 700px) {
  .gs-stage-words h2 { font-size: clamp(32px, 4vw, 44px); }
  .gs-stage-stack { margin-top: 20px; }
  .gs-stage-words .gs-stage-copytext { margin-top: 18px; font-size: 15px; }
  .gs-stage-acts { margin-top: 18px; }
}
/* A phone: the stage is the screen, the column runs top to bottom, and on a
 * failure the sign-in card follows the sentence. */
@media (max-width: 760px) {
  .gs-stage-panel { width: 100%; height: 100dvh; border-radius: 0; box-shadow: none;
    overflow-y: auto; overscroll-behavior: contain; }
  .gs-stage-words { position: relative; inset: auto; width: auto; min-height: 100%;
    padding: max(32px, env(safe-area-inset-top)) 24px max(28px, env(safe-area-inset-bottom)); }
  .gs-stage-shade { background: linear-gradient(180deg, transparent 0%,
    color-mix(in srgb, var(--c-dark) 55%, transparent) 30%, color-mix(in srgb, var(--c-dark) 55%, transparent) 70%,
    transparent 100%); }
  .gs-stage-words h2 { font-size: clamp(34px, 10vw, 44px); }
  .gs-stage-code { font-size: 17px; }
  .gs-stage-copy { min-height: 40px; }
  .gs-stage-words .gs-stage-copytext { font-size: 15px; }
  .gs-stage-go { width: 100%; justify-content: center; min-height: 50px; }
  /* A 44px target around an inline link, without moving the line. */
  .gs-stage-link { padding: 12px 0; margin: -12px 0; }
  .gs-stage-dlg.is-failed .gs-stage-pass { display: none; }
  .gs-stage-dlg.is-failed .gs-stage-words { min-height: 0; padding-bottom: 20px; }
  .gs-stage-gate { position: relative; top: auto; right: auto; width: auto; transform: none;
    margin: 0 16px max(16px, env(safe-area-inset-bottom)); }
  .gs-stage-gate .gate-card { animation: gs-stage-sub-in 420ms var(--ease-out) both; }
}
/* Never the chaos here (guest-stage.js plays only the short form), and every
 * entrance and exit is opacity alone. */
@media (prefers-reduced-motion: reduce) {
  .gs-stage-dlg.is-short, .gs-stage-dlg.is-short::backdrop { animation-duration: 200ms; }
  .gs-stage-dlg.is-said .gs-stage-later, .gs-stage-dlg.is-failed .gs-stage-fail,
  .gs-stage-gate .gate-card { animation: gs-stage-fade 200ms ease-out both; }
  .gs-stage-dlg.is-leaving::backdrop, .gs-stage-dlg.is-leaving .gs-stage-panel,
  .gs-stage-dlg.is-going .gs-stage-panel { animation: gs-stage-fade-out 160ms ease-out both; }
  .gs-stage-go > span { transition: none; }
}
