/* ═══════════════════════════════════════════════════════════════════════════
 * 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); }
.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; }
}

/* ── the tier strip ────────────────────────────────────────────────────────
 * Reuses .howrow/.howtab (the call-method switch) because it is the same kind
 * of control and a customer should only have to learn one: a single hairline
 * box split into equal parts, the selected one carrying weight and ink.
 * Three columns rather than two, and one word in each: the tier's name. */
/* First control in the card, so it gets the card's top edge and a rule under
 * it — everything below is a setting WITHIN the tier, and the hairline says so. */
.tierpick { padding: 0 0 12px; margin-bottom: 4px;
            border-bottom: 1px solid var(--ui-sep); }
/* .howrow.tierrow, not .tierrow: the base .howrow below is a TWO-column switch
 * and it is declared later in this file, so at equal specificity it won it —
 * which is why T5 spent a release wrapped onto a second row. The doubled class
 * is the whole fix; do not "simplify" it back. */
.howrow.tierrow { grid-template-columns: repeat(3, 1fr); margin: 0; }
/* One word per tab, centred, at a size that can carry a tab on its own now
 * that it is not sharing the line with a price. */
.tierrow .howtab { min-height: 32px; padding: 6px 4px; line-height: 1.2;
                   white-space: nowrap; }
.tierrow .howtab b { font-size: 13px; font-weight: 600; letter-spacing: 0.01em; }
/* A tier this deployment cannot serve. It has to READ as unreachable and still
 * take a press — agent.js shows its sentence instead of selecting it — so it
 * keeps the disabled treatment and loses `cursor: not-allowed`, which promises
 * that nothing will happen. */
.tierrow .howtab[aria-disabled="true"] { opacity: 0.5; }
/* The description of whatever is selected. It is the panel the strip is
 * joined to, so it carries the same paper and no top rule — the strip and its
 * sentence are one object, the way the call-method switch and its panel are. */
.tiernote { margin: 0; padding: 8px 2px 0; font-size: 12px; line-height: 1.45;
            color: var(--c-ink-3); background: none; border: 0; }
.tiernote b { color: var(--c-ink-2); font-weight: 600; }

/* ── the intelligence moment ───────────────────────────────────────────────
 * Moving up a tier is the one place in this console where something genuinely
 * changes about what the agent can do, and it happens a handful of times per
 * session — rare enough to afford delight, unlike the call button. So the
 * strip gets three coordinated things, all transform/opacity, all under the
 * 300ms rule for the parts that carry meaning:
 *
 *   1. a sheen that sweeps the strip once   (the state changed)
 *   2. sparks around the tab you chose      (what it changed TO)
 *   3. the sentence below crossfades up     (what that means)
 *
 * Sparks fire only when moving UP. Motion that fires identically in both
 * directions is decoration; motion that fires in one direction is a sentence.
 * Going back down to t1 gets the sweep and the crossfade and nothing else —
 * which is the honest reading, because nothing got smarter.
 *
 * The crossfade carries a 2px blur for the 90ms it overlaps: without it you
 * see two sentences stacked rather than one becoming the other. */
.tierpick { position: relative; }
.tierrow { position: relative; isolation: isolate; }
/* The sheen. A single pass of a soft highlight across the strip, clipped to
 * it, behind the labels — a light moving under glass, not a flash. */
.tierrow::after {
  content: ""; position: absolute; inset: 0; z-index: -1; opacity: 0;
  /* LIGHT, not hue. The one brand colour in this build means "mirai minds",
   * never "primary" and never "good" (see the palette note), so the sweep is
   * a highlight passing over paper — the same move a sheen makes on metal. */
  background: linear-gradient(100deg, transparent 20%,
              rgba(255, 255, 255, 0.72) 48%, transparent 76%);
  transform: translateX(-60%);
}
.tierrow.sweep::after { animation: tier-sweep 760ms var(--ease-out) both; }
@keyframes tier-sweep {
  0%   { opacity: 0;   transform: translateX(-70%) skewX(-8deg); }
  18%  { opacity: 1; }
  100% { opacity: 0;   transform: translateX(70%)  skewX(-8deg); }
}
/* The tab you landed on reacts to being landed on: a fast lift and settle.
 * It is the only element that MOVED in the interaction, so it is the one that
 * should acknowledge the press — and it is transform-only, so the strip's
 * layout never shifts under it. */
.tierrow .howtab.landed { animation: tier-land 420ms var(--ease-out) both; }
@keyframes tier-land {
  0%   { transform: scale(0.97); }
  38%  { transform: scale(1.055); }
  100% { transform: scale(1); }
}
/* …and a ring that expands out of it once and dissolves — the visual echo of
 * something switching on. Drawn on the strip, not on the button, so it can
 * overflow the tab without being clipped by it. */
.tierglow { position: absolute; left: var(--sx, 0px); width: var(--sw, 0px);
            top: 0; height: 100%; pointer-events: none; z-index: 1; opacity: 0;
            border-radius: var(--r-sm);
            box-shadow: 0 0 0 0 rgba(255, 255, 255, 0.9) inset; }
.tierglow.on { animation: tier-glow 640ms var(--ease-out) both; }
@keyframes tier-glow {
  0%   { opacity: 0;    transform: scale(0.94); }
  30%  { opacity: 0.95; transform: scale(1.02);
         box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.95) inset,
                     0 0 14px 2px rgba(255, 255, 255, 0.55); }
  100% { opacity: 0;    transform: scale(1.09);
         box-shadow: 0 0 0 6px rgba(255, 255, 255, 0) inset,
                     0 0 22px 6px rgba(255, 255, 255, 0); }
}
/* The sparks. Positioned over the selected tab by renderTiers(), which sets
 * --sx/--sw from its offsets — four points of light, staggered, each rising a
 * few pixels as it fades. Never from scale(0): they start at 0.4, so they read
 * as something small getting brighter rather than something from nowhere. */
/* The sparks overflow the tab deliberately — they are what the tier change
 * throws off, and a spark that stops at the button edge reads as a texture
 * rather than an event. -8px/16px of headroom on each side. */
.tiersparks { position: absolute; left: calc(var(--sx, 0px) - 8px);
              width: calc(var(--sw, 0px) + 16px);
              top: -8px; height: 56px; pointer-events: none; z-index: 2; }
.tiersparks i { position: absolute; left: var(--x); top: var(--y);
                width: var(--s, 9px); height: var(--s, 9px); opacity: 0;
                /* ink, for the same reason the sweep is light: a coloured
                 * spark would be the palette's one brand hue used as applause */
                background: var(--c-ink-2);
                /* a four-point star, so it reads as a spark and not a dot */
                clip-path: polygon(50% 0, 60% 40%, 100% 50%, 60% 60%,
                                   50% 100%, 40% 60%, 0 50%, 40% 40%); }
.tiersparks.on i { animation: spark 720ms var(--ease-out) var(--d, 0ms) both; }
/* Rise, spin a quarter turn, and go. The rotation is what makes a spark read
 * as light rather than as a small black star sliding upwards. */
@keyframes spark {
  0%   { opacity: 0; transform: scale(0.4) translateY(4px)   rotate(-25deg); }
  30%  { opacity: 1; transform: scale(1.25) translateY(-2px) rotate(10deg); }
  60%  { opacity: 0.85; transform: scale(0.95) translateY(-8px) rotate(45deg); }
  100% { opacity: 0; transform: scale(0.5) translateY(-16px) rotate(70deg); }
}
.tiernote { transition: opacity 140ms var(--ease-out),
                        transform 140ms var(--ease-out),
                        filter 140ms var(--ease-out); }
.tiernote.swapping { opacity: 0; transform: translateY(3px); filter: blur(2px); }

/* Reduced motion keeps the meaning and drops the movement: the sentence still
 * crossfades so you can see it changed, the sweep and the sparks do not run. */
@media (prefers-reduced-motion: reduce) {
  .tierrow.sweep::after { animation: none; }
  .tiersparks.on i { animation: none; }
  .tierglow.on { animation: none; }
  .tierrow .howtab.landed { animation: none; }
  .tiernote.swapping { transform: none; 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 the t3 voice does not need
 * the GPU at all — same reasoning as the tier picker 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 tier strip marks the tier it
     cannot serve 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 above the tiers, 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):not(.tierrow),
.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 { 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 { 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 { 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 { color: var(--c-ink); border-color: var(--c-ink-4); background: none; transform: none; }
.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-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-result:empty { display: none; }
.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-dialog .cmp-dlgpanel { width: min(560px, calc(100vw - 32px)); }
.bill-cancel-dialog .cmp-dlgbody { padding: 24px; }
.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; font-size: 12px; font-weight: 500; }
.bill-cancel-step label span { margin-left: 8px; font-weight: 400; }
.bill-cancel-step select, .bill-cancel-step textarea { width: 100%; padding: 10px 12px; border: 1px solid var(--c-line); border-radius: 4px; background: var(--c-surface); color: var(--c-ink); font: inherit; font-size: 13px; }
.bill-cancel-step textarea { resize: vertical; min-height: 80px; max-height: 180px; }
.bill-cancel-step .cmp-dlgacts { 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 .cmp-dlgacts { width: 100%; }
.bill-cancel-success .cmp-dlgacts 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-dialog .cmp-dlgbody { padding: 18px; }
  .bill-cancel-step select, .bill-cancel-step textarea { font-size: 16px; }
  .bill-cancel-step .cmp-dlgacts { 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 .cmp-dlgpanel { width: min(450px, calc(100% - 32px)); }
.bill-topup-dialog .cmp-dlgbody { padding: 22px; }
.bill-topup-dialog input { width: 100%; font-size: 24px; font-variant-numeric: tabular-nums; padding: 10px 12px; }
.bill-amount-label { display: block; margin: 22px 0 8px; font-size: 12px; font-weight: 500; }
.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 .cmp-dlgacts button { width: 100%; }
@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 ──────────────────────────────
 * Demoted, never hidden: it ran, it has its own dial log, and a customer
 * looking for "Servall Recovery" has to find it. What it does not have is a
 * report of its own — its numbers are in the primary's union — so the row is
 * quieter by a notch and says whose wave it is. One notch, not a grey-out:
 * a row nobody can read is a row somebody will ask us about. */
.cmp-row.cmp-wave .cmp-c-title { font-weight: 500; color: var(--c-ink-2); }
.cmp-row.cmp-wave .cmp-c-who { font-style: italic; }
.cmp-wavenote { margin: -2px 0 10px; }

/* ── the campaign report, framed by the console ───────────────────────────
 * The deck is a whole document — its own stylesheet, its own scroll-snap, its
 * own keyboard — so it is served into an <iframe> rather than merged into this
 * page, and everything here is the CHROME around it. That chrome is pure
 * console: the same hairline, the same 10px uppercase label, the same link
 * weight for a back control that the detail pane already uses. Nothing in this
 * block styles the report itself, and nothing in the report reaches out here.
 *
 * A fixed height rather than a growing one, because the deck inside pages by
 * viewport: a frame that is as tall as its content would leave the reader
 * scrolling this page and the report at the same time. */
#campreport { display: flex; flex-direction: column; min-height: 480px;
              height: calc(100dvh - 32px); }
/* `display: flex` beats the user-agent's [hidden] rule, so without this the
   report pane — its bar and a viewport-tall iframe — rendered UNDER the list
   and detail panes whenever campPane() had hidden it. Same fix .insp-panel and
   .dev-panel already carry, for the same reason. */
#campreport[hidden] { display: none; }
.rpt-bar { display: flex; align-items: center; gap: 12px;
           padding: 0 0 10px; margin-bottom: 10px;
           border-bottom: 1px solid var(--c-line); }
.rpt-bar .linkbtn { padding-left: 0; flex: none; }
.rpt-title { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.rpt-title b { font-size: 13px; font-weight: 600; letter-spacing: -0.01em;
               color: var(--c-ink); white-space: nowrap; overflow: hidden;
               text-overflow: ellipsis; display: block; }
.rpt-title span { font-size: 10px; letter-spacing: 0.08em;
                  text-transform: uppercase; font-weight: 600;
                  color: var(--c-ink-3); line-height: 1; }
/* The spreadsheet, on the page people read the report on. `margin-left: auto`
 * moves to the FIRST of the trailing controls, so the group stays right-aligned
 * as one block rather than each control finding its own edge. */
.rpt-csv { margin-left: auto; }
.rpt-bar button.ghost { flex: none; }
.rpt-full { flex: none; font-size: 11.5px; font-weight: 500;
            color: var(--c-ink-3); text-decoration: none; white-space: nowrap; }
.rpt-full:hover { color: var(--c-ink); }
/* No shadow and no inner padding: the deck draws its own sheets, and a frame
 * with a border AND a page with a border is two boxes for one thing. */
#cdrptframe { flex: 1 1 auto; display: block; width: 100%; min-height: 0;
              border: 1px solid var(--c-line); border-radius: var(--r-md);
              background: var(--c-paper); }

/* ── 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 and which the tier strip
 * already borrows: 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. Same trap .tierrow documents; 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 and .tierrow both document: 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.
 *
 * Two columns. Not .dev-grid — that is a grid of cards with a 300px aside,
 * and this is a FIXED-HEIGHT chat beside a scrolling rail, which behaves
 * differently the moment the window changes size. `align-items: start` so the
 * rail hugs its content instead of stretching to the chat's height and
 * leaving a tall empty card. */
/* ONE COLUMN IS THE DEFAULT, and that is the safe direction: the chat gets the
 * full width and the settings stack under it. The second column is an
 * enhancement that only appears when it has been measured to fit, so a browser
 * that does not do container queries lands on the layout that keeps the
 * conversation biggest rather than on a 300px chat.
 *
 * 820px because that is where the chat still gets ~540px after the rail and
 * the gutter — below it the two-column version is worse than no rail at all. */
.llm-shell { container-type: inline-size; }
.llm-work { display: grid; grid-template-columns: 1fr; gap: 12px; }
@container (min-width: 820px) {
  .llm-work { grid-template-columns: minmax(0, 1fr) 264px; align-items: start; }
}
/* Stacked but wide: the two settings cards sit side by side rather than
 * becoming a tall column the chat has to be scrolled past. */
@container (max-width: 819px) {
  .llm-side { flex-direction: row; align-items: flex-start; }
  .llm-side > * { flex: 1 1 260px; }
}
@container (max-width: 560px) {
  .llm-side { flex-direction: column; }
}
.llm-below { margin-top: 12px; }

/* 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; }

/* Speaker over line, not bubbles on two sides. A two-sided bubble layout is a
 * messaging-app idiom; this is a transcript being read and compared, the same
 * shape the call transcript already uses. The user's turn is inset and sits on
 * the sunk paper so the two sides are still distinguishable at a glance
 * WITHOUT relying on which edge they are near — greyscale, and rule 2. */
.llm-msg { display: flex; flex-direction: column; gap: 3px; }
.llm-msg.user { align-self: flex-end; align-items: flex-end; max-width: 82%;
                padding: 8px 11px; border-radius: var(--r-sm);
                background: var(--c-sunk); border: 1px solid var(--c-line); }
.llm-who { font-size: 10px; letter-spacing: 0.08em; text-transform: uppercase;
           font-weight: 600; line-height: 1; color: var(--c-ink-3); }
.llm-msg.bot .llm-who { color: var(--accent); }
.llm-body { margin: 0; font-size: 13px; line-height: 1.6; color: var(--c-ink);
            white-space: pre-wrap; overflow-wrap: anywhere; }
.llm-msg.user .llm-body { color: var(--c-ink-2); text-align: left; }

/* 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. Both cards are disclosures for the
 * same reason: these are the second thing anybody touches, and a system prompt
 * textarea standing open beside an empty conversation is a form asking to be
 * filled in before the interesting part. */
.llm-side { display: flex; flex-direction: column; gap: 12px; min-width: 0; }
.llm-sidecard { padding: 12px 14px; }
.llm-det summary { display: flex; align-items: baseline; gap: 8px;
                   cursor: pointer; user-select: none; }
.llm-det summary b { font-size: 13px; font-weight: 600; color: var(--c-ink);
                     letter-spacing: -0.01em; }
.llm-det summary i { font-style: normal; font-size: 11px; color: var(--c-ink-3); }
.llm-det summary:hover b { color: var(--accent); }
.llm-sysbox { width: 100%; margin-top: 10px; resize: vertical; min-height: 96px;
              font-family: inherit; font-size: 12.5px; line-height: 1.5; }

/* The tool toggles. Rows rather than chips: each carries a function name AND a
 * sentence about what it demonstrates, and a chip cannot hold the sentence. An
 * armed one gains the paper, the border and the ink — three axes, so "on"
 * survives greyscale (rule 2) instead of being a tint on a checkbox. */
.llm-tools { display: flex; flex-direction: column; gap: 6px; margin-top: 10px; }
.llm-tool { border: 1px solid var(--c-line); border-radius: var(--r-sm);
            background: var(--c-sunk); padding: 8px 10px; }
.llm-tool.on { background: var(--c-surface); border-color: var(--c-line-strong); }
.llm-toolhead { display: flex; align-items: flex-start; gap: 8px;
                cursor: pointer; }
.llm-tool input { margin: 2px 0 0; flex: none; accent-color: var(--c-ink); }
.llm-tool-txt { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.llm-tool-txt code { font-family: var(--mono); font-size: 11.5px;
                     color: var(--c-ink-3); }
.llm-tool.on .llm-tool-txt code { color: var(--c-ink); font-weight: 600; }
.llm-tool-txt i { font-style: normal; font-size: 11px; line-height: 1.45;
                  color: var(--c-ink-3); }
/* The schema, one click down. It is the thing a developer checks second — the
 * arguments they will be parsing — and the thing nobody needs while reading
 * the sentence above it. */
.llm-schema { margin-top: 7px; }
.llm-schema summary { font-size: 10.5px; color: var(--c-ink-3);
                      cursor: pointer; user-select: none; }
.llm-schema summary:hover { color: var(--accent); }

/* ── 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(--c-line-strong);
                 border-left: 2px solid var(--c-ink);
                 border-radius: var(--r-sm); 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); }
/* 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 ─────────────────────────────────────────────── */
  #campreport { height: calc(var(--vvh) - var(--mtop-h) - var(--tabbar-h) - 28px); }
  /* Same arithmetic as #campreport above, plus the view head this one has and
     that one does not. 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; }
  /* 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 and the tier picker. 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.tierrow 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.tierrow
 * 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-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.
 *
 * Rows are grids of spans, not a <table>, for the same reason every other list
 * in this build is: 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-bar { margin-bottom: 12px; }
.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; }

/* ── the list ───────────────────────────────────────── */
.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-c-name { font-weight: 600; color: var(--c-ink); min-width: 0;
              overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The name cell is a stack too, and only on an admin's list: under the campaign
 * name, whose it is. Empty on every customer's own page — the server sends no
 * brand there — and `:empty` collapses it, so their rows are the height they
 * always were. */
.cmp-c-title { display: block; overflow: hidden; text-overflow: ellipsis;
               white-space: nowrap; }
.cmp-c-who:empty { display: none; }
.cmp-c-who { display: block; font-style: normal; font-weight: 500;
             font-size: 10.5px; line-height: 1.3; color: var(--c-ink-3);
             overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The status cell is a stack: the pill, and under it the two-word reason a
 * paused campaign is paused. Nothing else ever fills the second line, so the
 * row height is unchanged for every campaign that is not paused. */
.cmp-c-state { justify-self: start; min-width: 0; display: flex;
               flex-direction: column; align-items: flex-start; gap: 3px; }
/* display:none when empty, not merely unstyled — the flex `gap` above applies
 * between items whether or not the second one has anything in it, and 3px on
 * every unpaused row is 3px of nothing. */
.cmp-c-why:empty { display: none; }
.cmp-c-why { font-size: 10.5px; line-height: 1.2; color: var(--c-open-ink); }
.cmp-c-when { color: var(--c-ink-3); }
/* The progress cell is a stack too: the two numbers, and under them the shape
 * of those two numbers. The text is the fact and the bar is the glance — twenty
 * rows of "8,412 / 20,000" is twenty small divisions to do by eye, and the one
 * campaign that has barely started should be findable without doing any of
 * them. One segment on the same track and in the same accent the detail pane's
 * bar gives a completed call, so the vocabulary is learned once. */
.cmp-c-prog { min-width: 0; display: flex; flex-direction: column;
              align-items: stretch; gap: 4px; }
.cmp-c-progn { color: var(--c-ink-3); font-weight: 500; }
.cmp-pager { display: flex; justify-content: center; padding: 10px 0 0; }

/* ── one campaign ───────────────────────────────────── */
.cmp-back { padding-left: 0; margin-bottom: 6px; }
.cmp-headtop { display: flex; align-items: center; justify-content: space-between;
               gap: 10px; margin-bottom: 12px; }
.cmp-name { margin: 0; font-size: 17px; font-weight: 600;
            letter-spacing: -0.02em; color: var(--c-ink); min-width: 0;
            overflow-wrap: anywhere; }
.cmp-facts { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
             gap: 0 18px; }
.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); }
.cmp-cardhead { display: flex; align-items: center; justify-content: space-between;
                gap: 10px; margin-bottom: 10px; }

/* 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); }

/* The tier, while it can still be changed. Sized to sit inside a .dev-fact row
 * without stretching it — the fact rows either side of it must not move when
 * this one turns into a control. */
.cmp-tiersel { max-width: 66%; min-width: 0; font-size: 11.5px;
               padding: 3px 7px; }

/* ── 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%; }

/* People to act on. Three rows, and they are the reason the full report gets
 * opened: everything else on this page is counters, and this is the part that
 * is work rather than reading. */
.cmp-people:empty { display: none; }
.cmp-people { margin-top: 14px; }
.cmp-people-head { font-size: 10px; letter-spacing: 0.08em;
                   text-transform: uppercase; color: var(--c-ink-3);
                   font-weight: 600; margin-bottom: 2px; }
.cmp-person { display: grid; grid-template-columns: minmax(90px, 0.9fr)
              minmax(0, 2fr) auto; align-items: baseline; gap: 10px;
              padding: 8px 0; font-size: 12px;
              border-bottom: 1px solid var(--c-line); }
.cmp-person b { color: var(--c-ink); font-weight: 600; min-width: 0;
                overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* One line. A model's summary of a phone call is as long as it likes, and a
 * strip whose rows are three lines tall is a strip nobody reads to the end. */
.cmp-person-say { color: var(--c-ink-2); min-width: 0; overflow: hidden;
                  text-overflow: ellipsis; white-space: nowrap; }
.cmp-person:last-child { border-bottom: none; }
.cmp-person-what { color: var(--c-ink-3); font-size: 11px; text-align: right;
                   white-space: nowrap; }
.cmp-people-more { font-size: 11.5px; color: var(--c-ink-3); padding-top: 8px; }
/* The card's own controls, and the only place they are: Insights, Share and the
 * CSV are what a customer does with this answer, so they sit under it. */
.cmp-res-acts { margin-top: 16px; }
.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 {
  grid-template-columns: minmax(126px, 1.2fr) 118px 84px minmax(0, 1fr);
}
.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); }

.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: 1fr auto 1fr auto;
            align-items: center; gap: 8px; margin-bottom: 6px; }
.cmp-slot-dash { color: var(--c-ink-3); font-size: 12px; }
.cmp-slot-x { background: none; border: 0; color: var(--c-ink-3); padding: 4px 6px;
              font-size: 12px; line-height: 1; }
.cmp-slot-x:hover { color: var(--c-alarm-ink); background: none; transform: none; }

.cmp-filerow { display: flex; align-items: center; gap: 8px; margin-bottom: 8px;
               flex-wrap: wrap; }
.cmp-filerow input[type=file] { flex: 1; min-width: 0; font-size: 11.5px;
                                color: var(--c-ink-2); }
.cmp-contacts textarea { min-height: 120px; font-family: var(--mono);
                         font-size: 12px; }

/* 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, #campreport, #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: 900px) {
  .cmp-rowhead, .cmp-row { grid-template-columns: minmax(0, 1fr) 96px 92px; }
  .cmp-rowhead span:last-child, .cmp-row .cmp-c-when { display: none; }
  /* The narrow layout keeps the whole of the last column, because the last
   * column is now the point of this table: the circle that plays the call and
   * the time it happened. What gives instead is the duration — a second fact
   * about a call you can simply play — and the Attempts column, which loses its
   * heading room rather than its number. */
  .cmp-crowhead, .cmp-crow {
    grid-template-columns: minmax(104px, 1fr) 92px 52px minmax(0, 1.05fr);
  }
  .cmp-crow .cmp-dur { display: none; }
}
/* With the inspector open the content column loses up to 520px of the window,
 * so this table is laid out narrow long before the window is — measured: at
 * 1180 the wide grid is 46px over its own row and the play circle hangs out of
 * the last column. It takes the SAME narrow grid as the block above rather
 * than a third layout, and loses the duration for the same reason: it is a
 * second fact about a call you are in the middle of playing. Below 1081 the
 * panel is an overlay (see .insp there) and takes nothing from this column, so
 * this band is the whole of the squeeze. */
@media (max-width: 1280px) {
  .app.inspecting .cmp-crowhead, .app.inspecting .cmp-crow {
    grid-template-columns: minmax(104px, 1fr) 92px 52px minmax(0, 1.05fr);
  }
  .app.inspecting .cmp-crow .cmp-dur { display: none; }
}
/* Below this the narrow grid is over too — measured 18px at 1081, where the
 * squeeze is worst — and Attempts is the column that can go, because the
 * panel's own header says "2 attempts" about the very call the row opened.
 * .cmp-rowhead already drops a heading positionally in the block above; the
 * head cells have no classes to name. */
@media (max-width: 1200px) {
  .app.inspecting .cmp-crowhead, .app.inspecting .cmp-crow {
    grid-template-columns: minmax(104px, 1fr) 92px minmax(0, 1.05fr);
  }
  .app.inspecting .cmp-crowhead span:nth-child(3),
  .app.inspecting .cmp-crow .cmp-c-att { display: 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. */
  .cmp-person { grid-template-columns: minmax(0, 1fr) auto; }
  .cmp-person-say { display: none; }
}
@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, #campreport, #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 campaigns dialog's wrapper pattern, at question size: full-screen
 * transparent <dialog>, centred panel, ::backdrop = --c-scrim (one of the two
 * dimming surfaces in the build), MAGI's dialog-in entrance. The destructive
 * confirm borrows button.hang's recipe — tinted alarm-bg, never a solid red
 * slab ("a solid red button in a page of quiet ones is a dare") — and focus
 * starts on Cancel from core.js, so Enter-through is not the default path on
 * a delete. */
dialog.confirmdlg { 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: 70; }
dialog.confirmdlg[open] { display: flex; align-items: center;
                          justify-content: center; }
dialog.confirmdlg::backdrop { background: var(--c-scrim); }
.cf-panel { width: min(420px, calc(100% - 32px)); background: var(--c-surface);
            border: 1px solid var(--c-line); border-radius: var(--r-md);
            padding: 16px 18px;
            box-shadow: 0 8px 28px rgba(27, 26, 22, 0.07);
            animation: dialog-in 200ms var(--ease-out) both; }
.cf-panel:focus, .cf-panel:focus-visible { outline: none; }
#cf-title { margin: 0 0 7px; font-size: 14px; font-weight: 600;
            letter-spacing: -0.01em; color: var(--c-ink);
            overflow-wrap: anywhere; }
#cf-body { margin: 0 0 15px; font-size: 12.5px; line-height: 1.6;
           color: var(--c-ink-2); overflow-wrap: anywhere; }
#cf-body[hidden] { display: none; }
.cf-acts { display: flex; justify-content: flex-end; gap: 8px; }
.confirmdlg.danger #cf-ok { background: var(--c-alarm-bg);
                            color: var(--c-alarm-ink); border-color: var(--c-alarm);
                            font-weight: 600; }
.confirmdlg.danger #cf-ok:hover { background: var(--c-alarm-bg-hover); }

/* ── 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; }
  .cf-panel { animation: 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 monogram, like a contact without a photo — in the console's own warm
 * greys, so twenty of them read as a list and never as confetti. */
.agent-list-icon { display: grid; place-items: center; flex: 0 0 36px; height: 36px;
                   border-radius: 9px; color: var(--c-ink-2);
                   font: 600 13px/1 var(--sans); letter-spacing: 0.02em;
                   background: var(--c-sunk);
                   box-shadow: inset 0 0 0 1px var(--c-line); }
.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; }

/* Starting points belong to creation; selecting a card never edits an agent. */
dialog.agent-create-dialog { position: fixed; inset: 0; width: 100%; max-width: none;
  height: 100%; max-height: none; margin: 0; padding: 0; border: 0;
  background: transparent; color: var(--c-ink); overflow: hidden; }
dialog.agent-create-dialog[open] { display: flex; align-items: center; justify-content: center; }
dialog.agent-create-dialog::backdrop { background: var(--c-scrim); }
.agent-create-panel { width: min(1040px, calc(100% - 48px)); height: min(780px, 90dvh);
  display: flex; flex-direction: column; overflow: hidden; background: var(--c-paper);
  border: 1px solid var(--c-line); border-radius: var(--r-md);
  box-shadow: 0 8px 28px rgba(27, 26, 22, .07); animation: dialog-in 200ms var(--ease-out) both; }
.agent-create-header { display: flex; align-items: center; justify-content: space-between;
  gap: 16px; padding: 20px 24px; border-bottom: 1px solid var(--c-line); background: var(--c-surface); }
.agent-create-header h2 { margin: 0; font: 600 20px/1.3 var(--sans); letter-spacing: -.025em; }
.agent-create-header p { margin: 5px 0 0; color: var(--c-ink-3); font-size: 12px; line-height: 1.5; }
.agent-create-header #agentcancel { flex: 0 0 36px; height: 36px; margin: 0; padding: 0;
  font-size: 24px; font-weight: 400; border-color: transparent; background: transparent; }
.agent-create-header #agentcancel:hover { background: var(--c-sunk); }
.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-panel { width: calc(100% - 20px); height: 94dvh; }
  .agent-create-header { padding: 16px; }
  .agent-create-header h2 { font-size: 18px; }
  .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-create-panel { animation: none; }
  .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. */
dialog.flow-review-dialog { position: fixed; inset: 0; width: 100%; max-width: none; height: 100%; max-height: none;
  margin: 0; padding: 0; border: 0; background: transparent; color: var(--c-ink); overflow: hidden; }
dialog.flow-review-dialog[open] { display: flex; align-items: center; justify-content: center; }
dialog.flow-review-dialog::backdrop { background: var(--c-scrim); }
.flow-review-panel { width: min(820px, calc(100% - 48px)); max-height: min(860px, 90dvh); display: flex; flex-direction: column;
  overflow: hidden; background: var(--c-paper); border: 1px solid var(--c-line); border-radius: var(--r-md);
  box-shadow: 0 8px 28px rgba(27, 26, 22, .07); animation: dialog-in 200ms var(--ease-out) both; }
.flow-review-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px;
  padding: 18px 22px; border-bottom: 1px solid var(--c-line); background: var(--c-surface); }
.flow-review-header h2 { margin: 0; font: 600 17px/1.35 var(--sans); letter-spacing: -.02em; }
.flow-review-header p { margin: 5px 0 0; color: var(--c-ink-3); font-size: 12px; line-height: 1.6; }
.flow-review-header button { flex: 0 0 34px; height: 34px; margin: 0; padding: 0; font-size: 22px; font-weight: 400;
  border-color: transparent; background: transparent; }
.flow-review-header button:hover { background: var(--c-sunk); }
.flow-review-body { flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain; padding: 4px 22px 20px; }
.flow-review-footer { display: flex; align-items: center; justify-content: flex-end; gap: 12px;
  padding: 14px 22px; border-top: 1px solid var(--c-line); background: var(--c-surface); }
.flow-review-footer p { flex: 1; margin: 0; color: var(--c-ink-3); font-size: 12px; line-height: 1.6; }
.flow-review-footer p:empty { visibility: hidden; }
.flow-review-footer button { margin: 0; min-height: 36px; }
@media (max-width: 700px) {
  .flow-review-panel { width: calc(100% - 20px); max-height: 94dvh; }
  .flow-review-footer { flex-wrap: wrap; }
  .flow-review-footer p { flex-basis: 100%; }
}
@media (prefers-reduced-motion: reduce) { .flow-review-panel { animation: none; } }
@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; }

/* ── 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. */
.release-versions { font-size: 11px; color: var(--muted); min-width: 0; }
.release-versions summary { cursor: pointer; list-style-position: inside; padding: 4px 0; }
.release-versions summary span { font-size: 10px; margin-left: 4px; opacity: .8; }
.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(--amber, #9a6700); }

/* ══ 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; }
}

/* Campaigns share the paper-and-ink shell; their figures remain readable on phones. */
.site-promotion-banner { display: flex; align-items: center; justify-content: center; gap: 24px;
  padding: 12px 24px; background: var(--canvas); border-bottom: 1px solid var(--c-line); flex: 0 0 auto; }
.site-promotion-banner[hidden], .site-promotion-copy[hidden] { display: none; }
body:has(#gate:not(.hidden)) #site-promotion-banner { display: none; }
.site-promotion-copy { display: grid; gap: 4px; max-width: 850px; overflow-wrap: anywhere; }
.site-promotion-copy strong { font-size: 14px; }
.site-promotion-copy small { font-size: 11px; line-height: 1.5; opacity: .8; }
.site-promotion-banner a { flex: 0 0 auto; color: inherit; text-underline-offset: 3px; }
.site-promotion-admin { margin-bottom: 24px; }
.site-promotion-admin summary { cursor: pointer; font-size: 16px; font-weight: 600; }
.site-promotion-admin summary span { font: 12px var(--sans); margin-left: 12px; }
.site-promotion-admin fieldset { border: 0; padding: 0; margin: 16px 0 0; }
.site-promotion-fields { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
.site-promotion-fields label { display: grid; gap: 6px; }
.site-promotion-fields input, .site-promotion-fields select, .site-promotion-fields textarea { width: 100%; min-width: 0; box-sizing: border-box; }
.site-promotion-fields textarea { resize: vertical; font: inherit; }
.site-promotion-enabled { display: flex; align-items: center; gap: 8px; margin: 20px 0; }
.site-promotion-enabled input { width: auto; }
#site-promotion-preview { padding: 16px; border: 1px solid var(--c-line); }
@media(max-width: 760px) {
  .site-promotion-banner { padding: 8px 14px; gap: 12px; font-size: 12px; }
  .site-promotion-banner .site-promotion-copy > span { display: none; }
  .site-promotion-fields { grid-template-columns: minmax(0, 1fr); }
}

/* Marketing uses the console's form and log surfaces. Details remain readable on phones. */
#site-promotion-state { font-size: 12px; color: var(--c-ink-3); }
.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%; }
}
