
  .tab-l-short { display: none; }
  :root {
    /* Brand-green palette (rebrand 2026-05-06) — variable names preserved
       so every existing rule referencing --violet-* inherits the new
       values with zero per-rule edits. The "violet" labels are now legacy
       names for what is the lime/green ramp matching the landing. */
    --violet-950: #1a2e05;  /* deep forest — was deep purple */
    --violet-900: #365314;  /* lime-900 */
    --violet-800: #4d7508;  /* darker brand-green */
    --violet-700: #6da00f;  /* brand-700 — the canonical landing accent */
    --violet-600: #84cc16;  /* lime-500 */
    --violet-500: #a3dc2d;  /* brand-2 — same as the gradient's mid stop */
    --violet-300: #d9f99d;  /* lime-200 — light tint */
    --violet-100: #ecfccb;  /* lime-100 — hover background */
    --violet-50:  #f7fee7;  /* lime-50 — subtle wash */
    --amber-400: #fbbf24; --emerald-500: #10b981; --rose-500: #f43f5e; --rose-600: #e11d48;
    --white: #ffffff;
    --ink-900: #0f172a; --ink-700: #334155; --ink-500: #64748b;
    --ink-300: #cbd5e1; --ink-200: #e2e8f0; --ink-100: #f1f5f9; --ink-50: #f8fafc;
    /* Brand-aligned aliases — opt-in for any new rules. */
    --brand-1: #c5f44f; --brand-2: #a3dc2d; --brand-700: #6da00f; --brand-800: #4d7508;
    --green-text: #14532d;
  }
  * { box-sizing: border-box; }
  html, body { margin: 0; padding: 0;
    /* Calm light-mint surface instead of the dark purple ribbon — matches
       the landing's reading-friendly background so /view feels like part
       of the same product rather than a separate purple application. */
    background: linear-gradient(180deg, #f7fee7 0%, #ffffff 240px) #ffffff;
    background-attachment: fixed; min-height: 100vh;
    color: var(--ink-900);
    font: 15px/1.6 "Inter", -apple-system, BlinkMacSystemFont, "SF Pro", system-ui, sans-serif;
    -webkit-font-smoothing: antialiased; }

  /* nav: all styles live in shared-nav.js */

  /* layout — main padding-top is 0 so the side rails + center-sticky
     header start IMMEDIATELY below the global nav. With sticky-top
     also pinned just below the nav, there's zero scroll-before-pin
     gap (operator feedback: "they should be sticky with no change in
     position due to scroll"). The 16px breathing room moves to a
     small spacer above the first content card instead. */
  main { max-width: 1320px; margin: 0 auto; padding: 0 24px 120px;
    display: grid; grid-template-columns: 260px 1fr 280px; gap: 20px;
    align-items: start; }
  /* Right rail empty (see _pruneRightRail): drop its track rather than
     leave a 280px hole. Declared HERE, with the base grid, not further
     down the sheet — main:has() and main have the same specificity under
     a media query, so a late base rule would beat every breakpoint below
     on source order. It did: at 384px the transcript column collapsed to
     102px because this rule outranked the single-column phone layout. */
  main:has(aside.right.is-empty) { grid-template-columns: 260px 1fr; }
  /* STUDY MODE — chat docked beside the transcript instead of replacing it.
     Operator chose this over a split centre or a bottom drawer: the transcript
     keeps the full reading column and the shell already exists, since the
     right rail is usually pruned away on this page anyway (measured: the grid
     is 260px 992px at 1440).

     Declared here WITH the base grid for the reason in the comment above —
     main.is-study and main:has(...) have equal specificity, so a rule placed
     later in the sheet would beat every breakpoint below on source order.
     The .is-study rules must therefore be repeated in each media query. */
  main.is-study { grid-template-columns: 260px 1fr 420px; }
  main.is-study:has(aside.right.is-empty) { grid-template-columns: 260px 1fr 420px; }
  @media (max-width: 1180px) {
    main { grid-template-columns: 240px 1fr 260px; gap: 16px; }
    main:has(aside.right.is-empty) { grid-template-columns: 240px 1fr; }
    /* Left rail goes before the transcript does — reading width is the
       thing being protected. */
    main.is-study,
    main.is-study:has(aside.right.is-empty) { grid-template-columns: 1fr 380px; }
    main.is-study aside.left { display: none; }
  }
  @media (max-width: 1024px) {
    main { grid-template-columns: 1fr 260px; }
    main:has(aside.right.is-empty) { grid-template-columns: 1fr; }
    aside.left { display: none; }
    main.is-study,
    main.is-study:has(aside.right.is-empty) { grid-template-columns: 1fr 340px; }
  }
  @media (max-width: 820px) {
    main { grid-template-columns: 1fr; padding: 0 8px 120px; gap: 14px; }
    main:has(aside.right.is-empty) { grid-template-columns: 1fr; }
    aside.right { position: static; max-height: none; overflow: visible; }
    /* Below 900px there is no honest side-by-side: two 380px columns would
       leave a transcript nobody can read. JS un-docks before this ever
       applies, but the rule is here so a resize mid-session cannot strand
       the layout in a broken state. */
    main.is-study,
    main.is-study:has(aside.right.is-empty) { grid-template-columns: 1fr; }
  }
  /* Operator 2026-05-17 ticket c766vh: more horizontal real-estate on
     phones — main padding 12→4px and insights card inner padding tighter. */
  @media (max-width: 560px) {
    main { padding: 0 4px 130px; }
    .insights .ins-head { padding: 12px 14px; }
    .insights .ins-body { padding: 0 12px 14px; }
    .insights .ins-section { padding-top: 14px; margin-top: 14px; }
  }

  /* common card */
  .card { background: var(--white); color: var(--ink-900);
    border-radius: 16px; padding: 20px;
    box-shadow: 0 30px 60px -20px rgba(0,0,0,0.35), 0 0 0 1px rgba(255,255,255,0.08); }

  /* Left sidebar transparent — operator 2026-05-17 ticket 1l99d8. */
  aside.left .card {
    background: transparent !important;
    box-shadow: none !important;
    padding: 12px 14px !important;
  }

  /* left sidebar — sticky pinned to the bottom of the global nav.
     top:55 matches the nav's height + main has padding-top:0 above,
     so the rail is in its sticky position from the very first paint:
     scroll-before-pin gap is zero. max-height + overflow-y means a
     long recent-transcripts list scrolls inside the rail without
     pushing other surfaces. */
  aside.left {
    position: sticky;
    top: 55px;
    max-height: calc(100vh - 60px);
    overflow-y: auto;
  }
  aside.left .card h4 { margin: 0 0 12px; font-size: 12px; font-weight: 700;
    text-transform: uppercase; letter-spacing: 0.06em; color: var(--ink-500); }
  .recent-item { display: block; padding: 8px 10px; border-radius: 8px;
    font-size: 13px; color: var(--ink-700); text-decoration: none;
    margin-bottom: 2px; cursor: pointer;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    transition: background 0.15s; }
  .recent-item:hover { background: var(--violet-50); color: var(--violet-700); }
  .recent-item.current { background: var(--violet-100); color: var(--violet-700); font-weight: 600; }
  .recent-empty { color: var(--ink-500); font-size: 12px; padding: 8px 10px; }
  .recent-new { display: block; padding: 10px 12px; border-radius: 8px;
    background: linear-gradient(135deg, var(--violet-600), var(--violet-700));
    color: var(--white); text-decoration: none; font-size: 13px; font-weight: 700;
    text-align: center; margin: 0 0 10px; }

  /* center */
  /* Sticky wrapper: pins the title block + tab strip below the global nav
     so the transcript / insights body scrolls underneath while the user
     keeps the title, status, and tab switch in reach. Global nav is
     sticky at top:0 with height ~55px — top:55px clears it cleanly.
     Solid bg + soft shadow so transcript content scrolling behind doesn't
     bleed through the sticky header. */
  .center .center-sticky {
    position: sticky;
    top: 55px;
    z-index: 20;
    background: linear-gradient(180deg, #f7fee7 0%, #ffffff 100%);
    padding: 0 0 6px;
    margin: 0 0 8px;
    box-shadow: 0 6px 10px -8px rgba(15, 23, 42, 0.18);
    border-radius: 0 0 12px 12px;
  }
  /* The sticky header sits ABOVE the transcript in the same scroll
     context, so a segment scrolled to the top of the list slides under it
     and reads as chopped in half — which is exactly what the first line
     did on a phone. scroll-margin lifts any segment we scroll to (and any
     :target / click-to-seek jump) clear of the header; scroll-padding does
     the same for the scroll container itself. */
  #transcript { scroll-padding-top: 8px; }
  /* Derived at runtime from the real sticky header (see _whipSyncStickyH).
     The fallback is only for the first paint; a hardcoded guess here is
     what left the spoken line 56px behind the header. */
  #transcript .segment { scroll-margin-top: calc(var(--whip-sticky-h, 206px) + 14px); }
  @media (max-width: 700px) {
    /* Phone: nav 55px + tabs + the title/duration row is ~150px of
       chrome. Give the list that much breathing room at the top so the
       first segment starts BELOW the header instead of behind it, and
       keep the header's own padding tighter so it costs less height. */
    .center .center-sticky { padding-bottom: 4px; margin-bottom: 4px; }
    #transcript { padding-top: 10px; }
    #transcript .segment:first-child { margin-top: 2px; }
    /* The jump-to-time input was running past the right edge next to the
       icon row; let the toolbar wrap instead of overflowing. */
    .center .center-sticky .head-actions,
    .center .center-sticky .head-row { flex-wrap: wrap; row-gap: 6px; }
  }
  /* Tabs come first — most prominent action surface. Compact head sits
     below as a single thin line: title · duration · language · status. */
  .center .center-sticky .view-tabs { margin: 0 0 4px; background: transparent; }
  .center .center-sticky .head { margin-bottom: 4px; }
  /* Sticky pinned IMMEDIATELY under the global nav — zero gap (operator
     2026-05-15: "white gap between menu and Transcripts/Intelligence/Notes
     menu, remove this gap"). The negative top compensates for default
     browser/main spacing that was pushing the sticky 19px below the nav
     on mobile (and ~6px on desktop). */
  .center .center-sticky { top: 55px !important; padding-top: 0; margin-top: 0; }
  /* Pull .center's first child flush against the top of main — kills the
     ~19px gap visible on mobile between the nav and the sticky bar. */
  main > .center > *:first-child { margin-top: 0 !important; }
  main { padding-top: 0 !important; }
  @media (max-width: 768px) {
    /* On mobile the global nav is shorter (~36px tall); pin the sticky to
       match so there is no white gap, and tighten the sticky's own internal
       padding to zero. */
    .center .center-sticky { top: 36px !important; padding: 0 0 4px !important; margin: 0 !important; }
  }
  .center .head {
    background: transparent;
    border-radius: 0;
    padding: 8px 4px;
    box-shadow: none;
    display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
    color: var(--ink-700);
  }
  .center .head h1 {
    margin: 0;
    font-size: 14px; font-weight: 700;
    letter-spacing: -0.005em;
    color: var(--ink-500);   /* match #meta-duration muted slate */
    word-break: break-word;
    max-width: 60ch;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  /* min-height reserves the row BEFORE its fields un-hide. Measured
     2026-08-04 on prod: the meta line went 0 -> 19px at ~1200ms as date,
     duration and language were revealed, growing .head and .center-sticky
     by 12px and shoving the transcript down mid-read. One of three shifts
     that put this page at CLS 0.30 (poor). Reserving costs nothing when
     the fields are absent — the row is empty either way. */
  .center .head .meta-line { margin: 0; color: var(--ink-500); font-size: 12.5px;
    display: inline-flex; gap: 8px; flex-wrap: wrap; align-items: baseline;
    min-height: 19px; }
  .center .head .meta-line .dot { opacity: 0.4; }
  .center .head .meta-line .status { display: inline-flex; align-items: center; gap: 4px;
    color: var(--ink-500); font-weight: 500; font-size: 12px; }
  /* Status colour variants — operator 2026-05-17 wsh8eo: keep both
     done + running at the same muted slate as the lang chip so the
     status reads as metadata, not a chrome accent. */
  .center .head .meta-line .status.is-done    { color: var(--ink-500); }
  .center .head .meta-line .status.is-running { color: var(--ink-500); }
  /* Status dot — smaller + very light grey, hollow appearance via low
     opacity (operator 2026-05-17 x1zwvq). */
  .center .head .meta-line .status .status-dot {
    display: inline-block;
    font-size: 8px; line-height: 1;
    color: var(--ink-300, #cbd5e1);
    vertical-align: 1.5px;
    margin-right: 1px;
  }
  /* Sample-attribution line removed per operator 2026-05-15 — keep the
     header thin: only title · duration · language · status. */
  .center .head #sample-attribution { display: none !important; }
  .center .head .sample-attr a { color: var(--ink-500); text-decoration: underline; text-underline-offset: 2px; }
  .center .head .sample-attr a:hover { color: var(--ink-900); }

  .transcript { background: var(--white); color: var(--ink-900); border-radius: 16px;
    padding: 14px 24px; line-height: 1.8; font-size: 16px;
    margin-top: 0;
    box-shadow: 0 20px 40px -15px rgba(0,0,0,0.25), 0 0 0 1px rgba(255,255,255,0.06); }
  /* ───────────────────────────────────────────────────────────────────
     Viewport-locked shell (2026-08-03). Operator: "scroll bar should be next
     to the transcript."

     Three earlier attempts capped .transcript with max-height: calc(...) and
     all failed for the same reason: the PAGE still scrolled, so there were two
     bars and the window one -- taller, at the far edge -- stayed the one the
     eye finds. .whip-share-footer alone puts the document 367px past the fold
     at 1440x900, which no transcript height can absorb.

     So lock the shell instead. body becomes a flex column of exactly viewport
     height with overflow hidden; the transcript is the only thing that can
     scroll, and its height comes from the layout rather than from arithmetic
     on constants that drift.

     min-height: 0 on every ancestor is load-bearing, not decoration: a flex or
     grid item defaults to min-height:auto and refuses to shrink below its
     content, so without it the transcript stays 68,000px tall and the shell
     overflows anyway.

     Scoped to >=1024 wide and >=640 tall. Measured gap from the transcript's
     right edge to the window edge is 84px at 1440 but 4px at 390 -- on a phone
     the bar is already beside the text, and locking the shell there would pin
     the chrome and cost real reading area. Narrow and short viewports keep the
     page scroll they have always had. */
  @media (min-width: 1024px) and (min-height: 640px) {
    html { height: 100%; }
    body {
      height: 100%;
      min-height: 0;
      overflow: hidden;
      display: flex;
      flex-direction: column;
      /* The audio bar is position:fixed, so reserve its strip here and the
         flex column simply ends above it. --dock-bottom is kept in sync
         with the bar's real height by JS.

         max() is the floor, and it is load-bearing. The bar starts short
         and settles at 81px, so --dock-bottom RISES as it measures. In
         this shell body is a fixed-height flex column and main is
         flex:1 1 auto, so a growing reserve comes straight out of main:
         measured on prod, main went 748 -> 678px at ~2.1s, the single
         largest layout shift on the page. Flooring at the settled value
         means the reserve can only shrink, which costs nothing. */
      padding-bottom: var(--dock-bottom, 93px);
    }
    main {
      flex: 1 1 auto;
      min-height: 0;
      padding-bottom: 0;   /* the 120px existed to clear the fixed bar */
      /* width:100% is the whole layout-shift fix, after five wrong guesses.
         body here is display:flex + column, which makes main a flex ITEM whose
         cross axis is horizontal. `margin: 0 auto` on main CANCELS the default
         align-items:stretch, so main became shrink-to-fit — sized by its own
         content. Measured on prod as the transcript streamed in:

             934ms  w= 935  marginLeft=252.66px
             956ms  w=1010
            1198ms  w=1320  marginLeft=60px

         a 385px widening and a 193px slide left, which is why the shift's own
         source read `main 215,59 1010x748 -> 60,59 1341x678` and why every
         fix aimed at a CHILD (nav strip, dock offset, share footer, audio
         reserve) missed: nothing inside main was moving it. width:100% gives
         it a definite cross size, max-width still caps it at 1320 and the auto
         margins still centre it, so the box is identical from first paint and
         no longer depends on what has loaded. */
      width: 100%;
    }
    /* align-self, not align-items: the grid keeps `align-items: start` so the
       left rail and right rail stay top-aligned; only the centre column is
       stretched to full height. */
    main > .center {
      align-self: stretch;
      min-height: 0;
      display: flex;
      flex-direction: column;
    }
    .center > .view-panel {
      flex: 1 1 auto;
      min-height: 0;
      display: flex;
      flex-direction: column;
    }
    .transcript {
      flex: 1 1 auto;
      min-height: 0;
      overflow-y: auto;
      overscroll-behavior: contain;
      scrollbar-gutter: stable;
      scrollbar-width: thin;
      scrollbar-color: rgba(132, 204, 22, 0.75) rgba(15, 23, 42, 0.06);
    }

    .whip-share-footer { flex: 0 0 auto; }

    /* WebKit needs the explicit track/thumb. Green matches the lime already
       used for the mobile page scrollbar; 12px is a mouse target. */
    .transcript::-webkit-scrollbar { width: 12px; }
    .transcript::-webkit-scrollbar-track {
      background: rgba(15, 23, 42, 0.06); border-radius: 8px; margin: 6px 0; }
    .transcript::-webkit-scrollbar-thumb {
      background: rgba(132, 204, 22, 0.75); border-radius: 8px;
      border: 3px solid transparent; background-clip: padding-box; }
    .transcript::-webkit-scrollbar-thumb:hover {
      background: rgba(132, 204, 22, 0.95); }
  }

  /* A touch user on a wide screen still needs a thumb-sized target. */
  @media (pointer: coarse) and (min-width: 1024px) and (min-height: 640px) {
    .transcript::-webkit-scrollbar { width: 16px; }
  }

  /* Two-phase paint fast-preview: plain-text block shown for the brief
     window between JSON-parse and rich-segment-DOM construction on long
     transcripts. Subtle fade-in so the upgrade to the timestamp DOM in
     the next frame doesn't feel jarring. */
  .transcript .transcript-fastpaint {
    white-space: pre-wrap;
    color: var(--ink-700);
    animation: tx-fastpaint-fade 0.18s ease-out both;
  }
  @keyframes tx-fastpaint-fade {
    from { opacity: 0; } to { opacity: 1; }
  }
  .transcript .segment {
    margin-bottom: 14px;
    /* Auto-scroll lands the active segment with block:'start'; this
       margin lifts it below the sticky stack (~145px) AND leaves ~2
       lines of preceding context visible above it, so the playing line
       sits as the 3rd visible row (operator 2026-05-20: "current playing
       should be 2 lines below the top visible line"). Desktop line-
       height ~27px × 2 + sticky-buffer → 240px. Mobile ~20px × 2 → 150px. */
    scroll-margin-top: 240px;
  }
  @media (max-width: 768px) {
    .transcript .segment { scroll-margin-top: 150px; }
  }
  .transcript .segment p {
    margin: 0;
    /* Operator 2026-05-17 u9n80y: justified text for prettier reading.
       hyphens auto keeps words from leaving big rivers on narrow lines. */
    text-align: justify;
    text-justify: inter-word;
    hyphens: auto;
    -webkit-hyphens: auto;
    /* Force wrapping of unbreakable runs (Whisper hallucinated 3000+
       repeats of Malayalam syllable ത്ത on transcript ab218bd1 with no
       spaces — to CSS that was one word, .segment grew to 4630px wide
       and broke the whole layout. overflow-wrap:anywhere caps any glyph
       run at the column width. min-width:0 lets the flex/grid parent
       contract the cell. Operator 2026-05-29 fix.)
       overflow-wrap:anywhere ALONE doesn't break Indic virama-joined
       sequences in Chrome — pair with line-break:anywhere + word-break:
       break-all to force breaks regardless of script's joining rules. */
    overflow-wrap: anywhere;
    word-break: break-all;
    line-break: anywhere;
    min-width: 0;
  }
  .transcript .segment { min-width: 0; max-width: 100%; }
  /* CSS grid quirk: a 1fr column has `min-width: auto`, so a cell with
     unbreakable-ish content can force the column to grow past its track.
     `min-width: 0` on the grid item lets the column actually be 1fr.
     Same trick on .transcript so its own intrinsic width can't push the
     parent. (operator 2026-05-29 — same runaway segment also expanded
     the whole .center column to 1580px in a 1501px viewport.) */
  main > .center { min-width: 0; }
  .transcript { min-width: 0; max-width: 100%; }
  /* Operator 2026-05-17 ticket ggcs1q: text selection must always work
     inside transcript even though paragraph clicks also seek. Pointer
     for legibility, but text remains selectable via mouse drag. */
  .transcript .segment,
  .transcript .segment p,
  .transcript .w { user-select: text !important; -webkit-user-select: text !important; cursor: text; }
  /* Timestamp — bold dark-green chip with a fixed min-width so swapping
     to .ts-pause on the active segment never shifts the body text
     (operator 2026-05-17 ticket ev4mks). */
  .transcript .ts { color: #15803d; font-weight: 800; font-size: 12.5px;
    font-family: ui-monospace, Menlo, monospace; cursor: pointer;
    margin-right: 6px; user-select: none; padding: 2px 5px;
    border-radius: 5px; transition: all 0.15s; vertical-align: 1px;
    background: rgba(132, 204, 22, 0.10);
    display: inline-block; min-width: 50px; text-align: center; }
  .transcript .ts:hover { background: #dcfce7; color: #14532d; }
  .transcript .ts.active { background: #16a34a; color: #fff; }

  /* Per-segment play button (ticket 5svfdb → 5mbq5q → p7mlct → vtwony
     → myqliv): ACTIVE segment hides timestamp + shows play button.
     The has-played gate was dropped so the FIRST segment (always
     .active on load) shows the play button immediately instead of
     a blank gap. */
  .transcript .ts-pause { display: none; }
  .transcript .segment.active .ts { display: none; }
  .transcript .segment.active .ts-pause { display: inline-flex; }
  /* Operator 2026-05-17 ticket 2v1h8g + ev4mks: transparent + futuristic
     pill that's the SAME width as the timestamp chip — swapping between
     timestamp (inactive) and play (active) keeps body text at the
     exact same x-position. */
  .transcript .ts-pause {
    align-items: center; justify-content: center;
    appearance: none; cursor: pointer;
    margin-right: 6px; padding: 2px 5px;
    min-width: 50px; height: 22px;
    border: 1px solid rgba(132, 204, 22, 0.45);
    background: transparent;
    color: rgba(21, 128, 61, 0.78);
    border-radius: 11px;
    font: 700 11px/1 ui-monospace, Menlo, monospace;
    vertical-align: 1px;
    transition: transform 0.12s, background 0.15s, color 0.15s, border-color 0.15s;
  }
  .transcript .ts-pause:hover {
    background: rgba(132, 204, 22, 0.14);
    color: #15803d;
    border-color: rgba(132, 204, 22, 0.7);
    transform: scale(1.06);
  }
  /* Default: every play button shows ▶. The HTML ships with ❚❚ text
     content (active+playing default), so use ::before override to keep
     a consistent visual without DOM mutation. */
  .transcript .ts-pause { font-size: 0; }
  .transcript .ts-pause::before { content: "▶"; font-size: 10px; }
  /* On the currently-active+playing segment swap to pause. */
  body:not(.audio-paused) .transcript .segment.active .ts-pause::before {
    content: "❚❚"; font-size: 9px;
  }

  /* Spoken-word highlight — current 3 words get a light-green
     underline (no background fill). Operator 2026-05-17. Clears on
     pause/stop. */
  .transcript .w { transition: text-decoration-color 0.18s; }
  /* Spoken-word marker removed entirely per operator 2026-05-17 —
     no underline, no fill. The active-segment left-edge accent is
     the only "currently-playing" indicator. */
  .transcript .w.w-now { text-decoration: none; }

  /* Nav-jump flash — when TOP/NOW/END pills land the user on a segment,
     briefly tint it lime so the eye catches WHERE the jump went.
     Auto-removes after 1.3s via JS. */
  @keyframes navFlashFade {
    0%   { background: rgba(132, 204, 22, 0.32); }
    60%  { background: rgba(132, 204, 22, 0.18); }
    100% { background: transparent; }
  }
  .transcript .segment.nav-flash {
    animation: navFlashFade 1.2s ease-out forwards;
    border-radius: 6px;
  }

  /* Quick-nav dock for long transcripts — fixed right edge, mid-screen.
     Three vertical pills: Top · Now (current playing position) · End.
     Visible only on the Transcript tab (not Intelligence/Notes) via a
     body class flipped by the tab switcher. */
  /* Never paint the un-hoisted (sticky right rail) variant. Operator
     2026-05-17 ticket hqthht: even when the JS reparent fails or races
     a slow .head paint, the visitor must NEVER see the floating dock
     in the page center-right. Show only when .is-hoisted has been
     applied by the reparenter below. */
  .transcript-nav-dock:not(.is-hoisted) { display: none !important; }

  /* Hoisted nav-dock — floating fixed above the audio bar on the
     right edge on every viewport (operator 2026-05-17 ticket znsgvb:
     'didn't appear when i scrolled past actively playing section').
     Was inline in .head on desktop; switched to fixed so it stays
     visible regardless of scroll. Bottom anchored by --dock-bottom
     CSS var that JS keeps in sync with audio-bar height. */
  .transcript-nav-dock.is-hoisted {
    /* Operator 2026-05-17 ticket 7naotb: visible on EVERY tab + always
       while scrolling — explicit display:flex overrides the
       body.tab-transcript guard so leaving Transcript for Summary
       doesn't hide the pills. */
    display: flex !important;
    position: fixed !important;
    right: 12px !important;
    bottom: var(--dock-bottom, 93px) !important;
    flex-direction: row !important;
    gap: 4px !important;
    margin: 0 !important;
    padding: 4px 6px !important;
    background: rgba(255, 255, 255, 0.96) !important;
    border: 1px solid var(--ink-200, #e2e8f0) !important;
    border-radius: 999px !important;
    box-shadow: 0 10px 24px -10px rgba(15, 23, 42, 0.3) !important;
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
    top: auto !important; left: auto !important; transform: none !important;
    z-index: 45;  /* above audio-bar (40) — operator 2026-05-17 2fofu0 */
    width: fit-content;
  }
  .transcript-nav-dock.is-hoisted .nav-pill {
    width: 32px; height: 22px;
    gap: 0; padding: 0;
    border-radius: 999px;
    position: relative;   /* anchors the hit-box overlay below */
  }
  /* Touch target without a visual change. The pill stays 32x22; a centred
     overlay carries the tap area. Measured on prod at 430px: 32x22 with
     centres 36px apart.
     Height goes to the full 44px. Width is capped at 36px — the centre-to-
     centre distance — because two 44px-wide boxes cannot sit 36px apart
     without overlapping, and an overlap here means tapping "Top" and getting
     "Now". A hit box that steals its neighbour's taps is worse than a small
     one. Closing the remaining 8px needs a wider gap, which is a visual
     change the operator has not asked for. */
  .transcript-nav-dock.is-hoisted .nav-pill::after {
    content: ""; position: absolute; left: 50%; top: 50%;
    transform: translate(-50%, -50%);
    width: 36px; height: 44px;
  }
  .transcript-nav-dock.is-hoisted .nav-pill .nav-icon {
    display: inline-flex; align-items: center; justify-content: center;
    width: 100%; height: 100%;
  }
  .transcript-nav-dock.is-hoisted .nav-pill .nav-label { display: none; }
  /* Hoisted Now should match the other pills (no green fill), matching mobile */
  .transcript-nav-dock.is-hoisted .nav-pill.is-current {
    background: transparent !important;
    border-color: rgba(15, 23, 42, 0.12) !important;
    color: inherit !important;
  }
  .transcript-nav-dock.is-hoisted .nav-pill.is-current .nav-icon svg circle { fill: currentColor; }
  /* Cancel the old docked-rail ::before icon overrides that don't apply once hoisted. */
  .transcript-nav-dock.is-hoisted .nav-pill[data-nav] .nav-icon::before { content: none !important; }
  .transcript-nav-dock.is-hoisted .nav-pill .nav-icon svg { width: 12px; height: 12px; display: block; }
  .transcript-nav-dock {
    position: fixed;
    /* Land in the gap between the transcript column and the right rail
       (Ask AI panel) so the pills sit at the transcript's right edge,
       not the browser's. Math: half the viewport-vs-1320-overflow,
       plus 24 px outer padding, plus 280 px right sidebar, minus a
       30 px nudge so the 36 px-wide dock sits inside the gap with a
       small overlap into the sidebar's left edge instead of
       cantilevered into the browser margin. */
    right: max(16px, calc((100vw - 1320px) / 2 + 294px));
    top: 50%;
    transform: translateY(-50%);
    z-index: 25;
    display: none; flex-direction: column; gap: 8px;
    pointer-events: none;
  }
  body.tab-transcript .transcript-nav-dock { display: flex; }
  .transcript-nav-dock .nav-pill {
    pointer-events: auto;
    appearance: none; cursor: pointer;
    width: 36px; height: 36px;
    border: 1px solid rgba(15,23,42,0.08);
    background: rgba(255,255,255,0.96);
    color: var(--ink-700);
    border-radius: 999px;
    box-shadow:
      0 1px 2px rgba(15,23,42,0.04),
      0 6px 18px -6px rgba(15,23,42,0.18);
    font: 700 12px/1.1 -apple-system, "Inter", system-ui, sans-serif;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 1px;
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
    transition: background 0.15s, color 0.15s, transform 0.12s, border-color 0.15s;
  }
  .transcript-nav-dock .nav-pill .nav-icon { font-size: 14px; line-height: 1; }
  .transcript-nav-dock .nav-pill .nav-label {
    font-size: 9px; letter-spacing: 0.04em; text-transform: uppercase;
    color: var(--ink-500); font-weight: 700;
  }
  .transcript-nav-dock .nav-pill:hover {
    background: var(--brand-1); border-color: var(--brand-2); color: #1a2e05;
    transform: translateX(-2px);
  }
  .transcript-nav-dock .nav-pill:hover .nav-label { color: #1a2e05; }
  .transcript-nav-dock .nav-pill.is-current {
    background: var(--violet-700); color: var(--white); border-color: var(--violet-700);
  }
  .transcript-nav-dock .nav-pill.is-current .nav-label { color: rgba(255,255,255,0.85); }
  @media (max-width: 820px) {
    .transcript-nav-dock { right: 10px; }
    .transcript-nav-dock .nav-pill { width: 40px; height: 40px; }
  }
  /* No-speech (VAD-rejected) state — calm explainer panel where the
     transcript would normally render. */
  .no-speech {
    text-align: center;
    padding: 56px 24px 64px;
    color: var(--ink-700);
  }
  .no-speech-headline {
    font-size: 22px; font-weight: 700;
    color: var(--ink-900);
    margin-bottom: 10px;
    letter-spacing: -0.01em;
  }
  .no-speech-body {
    font-size: 15px; line-height: 1.55;
    max-width: 520px; margin: 0 auto 14px;
    color: var(--ink-700);
  }
  .no-speech-ratio {
    font-family: ui-monospace, Menlo, monospace;
    font-size: 12px; color: var(--ink-500);
  }
  /* Sidebar utility — used by renderTranscriptData when hiding export
     panels for VAD-rejected jobs. */
  aside.right .is-hidden { display: none !important; }
  /* Segment-level highlight while the audio plays this segment's range */
  .transcript .segment {
    padding: 6px 10px;
    border-radius: 8px;
    transition: background-color 0.25s ease, box-shadow 0.25s ease;
  }
  /* Active segment — soft lime wash matching the brand green used on the
     jump-to-current / focus-ring affordances (operator 2026-05-20:
     "the highlight tinge is the greenish tinge we use on going to the
     current row"). Same rgba(132,204,22,~0.10) used by .recent-search
     focus + the existing left-edge accent. */
  .transcript .segment.active {
    background: rgba(132, 204, 22, 0.10);
    box-shadow: inset 3px 0 0 var(--violet-600);
    border-radius: 6px;
    transition: background 0.18s ease-out;
  }
  body.hide-ts .transcript .ts { display: none; }
  /* Removed-artifact rows: off by default, on via View → Show removed artifacts (the detailed view). */
  .seg-gap { display: none; gap: 10px; align-items: baseline; margin: 6px 0 6px 0; padding: 4px 8px; border-left: 3px dashed var(--line, #d9d9d9); color: var(--muted, #6b6b6b); font-size: 13px; font-style: italic; }
  body.wt-show-gaps .seg-gap { display: flex; }
  .seg-gap small { font-style: normal; font-size: 11px; opacity: .8; }
  .transcript .loading, .transcript .errored {
    text-align: center; color: var(--ink-500); padding: 40px 0; font-size: 14px; }
  .transcript .errored { color: var(--rose-600); }

  /* Tab strip above transcript / intelligence panels.
     Operator 2026-05-17 2ht11r → 29pzt2: more padding top + bigger
     tab height for breathing room. */
  .view-tabs {
    display: flex; align-items: stretch; gap: 4px;
    border-bottom: 1px solid #e5e7eb;
    margin: 36px 0 22px;
    padding-top: 8px;
  }
  .view-tabs .view-tab {
    min-height: 44px;
    padding: 0 18px !important;
    font-size: 14.5px !important;
    line-height: 1 !important;
  }
  /* Explicit color (not via font:inherit) so the contrast is clear
     against the white/cream background — earlier the inactive tab
     could collapse to the page text color and disappear. */
  .view-tabs .view-tab {
    background: transparent; border: 0;
    padding: 9px 16px;
    margin-bottom: -1px;
    font-family: inherit; font-size: 14px; font-weight: 600;
    color: #1e1b4b !important;       /* dark indigo — readable on white */
    cursor: pointer;
    border-bottom: 2px solid transparent;
    display: inline-flex; align-items: center; justify-content: center; gap: 8px;
    transition: color 0.15s, border-color 0.15s, background 0.15s;
    border-radius: 8px 8px 0 0;
  }
  .view-tabs .view-tab:hover:not(:disabled) {
    color: #4d7508 !important;
    background: #faf7ff;
  }
  .view-tabs .view-tab.is-active {
    color: #4d7508 !important;
    background: transparent !important;   /* operator 2026-05-17 vop3sb */
    border-bottom-color: #84cc16;
    font-weight: 700;
  }
  .view-tabs .view-tab:disabled {
    color: #94a3b8 !important; cursor: not-allowed; opacity: 0.85;
  }
  /* Tiny ✓ pill on the Notes tab when there are saved notes for this
     transcript — gives the user a glanceable signal that they have
     content stashed under that tab without having to switch to it. */
  .view-tabs .view-tab .notes-saved-pill {
    font: 700 9.5px/1.2 var(--sans, "Inter", system-ui, sans-serif);
    background: var(--violet-700, #4d7508);
    color: #ffffff;
    width: 14px; height: 14px;
    display: inline-grid; place-items: center;
    border-radius: 50%;
    margin-left: 2px;
  }
  .view-tabs .view-tab .notes-saved-pill[hidden] { display: none; }

  /* Notes panel — clean cream card with one big textarea. */
  .notes-card {
    background: #ffffff;
    border-radius: 14px;
    padding: 18px 20px 16px;
    box-shadow: 0 14px 32px -16px rgba(15, 23, 42, 0.18), 0 0 0 1px rgba(15, 23, 42, 0.05);
  }
  .notes-head {
    display: flex; align-items: center; justify-content: space-between;
    margin-bottom: 12px;
  }
  .notes-head h3 {
    margin: 0; font: 700 14px/1.2 var(--sans, "Inter", system-ui, sans-serif);
    letter-spacing: -0.005em; color: var(--ink-900, #0f172a);
  }
  .notes-status {
    font: 600 11.5px/1.2 var(--sans, "Inter", system-ui, sans-serif);
    color: var(--ink-500, #64748b);
    background: var(--ink-50, #f8fafc);
    border: 1px solid rgba(15, 23, 42, 0.06);
    padding: 3px 9px; border-radius: 999px;
  }
  .notes-status.is-saving { color: var(--violet-700, #4d7508); }
  .notes-status.is-saved  { color: #14532d; background: var(--violet-100, #ecfccb); border-color: rgba(132, 204, 22, 0.4); }
  #notes-textarea {
    width: 100%; min-height: 360px;
    padding: 14px 16px;
    font: 400 15px/1.7 var(--sans, "Inter", system-ui, sans-serif);
    color: var(--ink-900, #0f172a);
    background: var(--ink-50, #fafbfc);
    border: 1px solid rgba(15, 23, 42, 0.08);
    border-radius: 10px;
    resize: vertical;
    outline: none;
    transition: border-color 0.15s, box-shadow 0.15s;
  }
  #notes-textarea:focus {
    border-color: var(--violet-500, #84cc16);
    box-shadow: 0 0 0 3px rgba(132, 204, 22, 0.15);
    background: #ffffff;
  }
  /* Operator ticket e26dxj: Edit/Preview toggle for the notes panel.
     The buttons sit inline with the head; the preview pane mirrors
     the markdown styles used in chat bubbles so notes feel consistent. */
  .notes-mode {
    display: inline-flex; gap: 0;
    border: 1px solid rgba(15, 23, 42, 0.08);
    border-radius: 8px; overflow: hidden;
    margin-right: 8px;
  }
  .notes-mode button {
    background: transparent; border: none;
    padding: 5px 12px;
    font: 600 12px/1.2 var(--sans, "Inter", system-ui, sans-serif);
    color: var(--ink-500, #64748b);
    cursor: pointer;
    transition: background 0.15s, color 0.15s;
  }
  .notes-mode button:hover { color: var(--ink-900, #0f172a); }
  .notes-mode button.is-active {
    background: var(--violet-100, #ecfccb);
    color: #14532d;
  }
  .notes-preview {
    min-height: 360px;
    padding: 14px 16px;
    font: 400 15px/1.7 var(--sans, "Inter", system-ui, sans-serif);
    color: var(--ink-900, #0f172a);
    background: #ffffff;
    border: 1px solid rgba(15, 23, 42, 0.08);
    border-radius: 10px;
  }
  .notes-preview > *:first-child { margin-top: 0 !important; }
  .notes-preview > *:last-child { margin-bottom: 0 !important; }
  .notes-preview p { margin: 0 0 10px; }
  .notes-preview h2 { font-size: 18px; font-weight: 700; margin: 14px 0 8px; }
  .notes-preview h3 { font-size: 16px; font-weight: 700; margin: 12px 0 6px; }
  .notes-preview h4 { font-size: 14.5px; font-weight: 700; margin: 10px 0 5px; }
  .notes-preview ul, .notes-preview ol { margin: 8px 0 12px; padding-left: 24px; }
  .notes-preview li { margin: 3px 0; }
  .notes-preview code {
    background: #f1f5f9; padding: 1.5px 6px; border-radius: 4px;
    font-family: var(--mono, ui-monospace, Menlo, monospace);
    font-size: 13px; color: #334155;
  }
  .notes-preview pre {
    background: #0f172a; color: #e2e8f0;
    padding: 12px 14px; border-radius: 8px; overflow-x: auto;
    margin: 10px 0; font-size: 13px;
  }
  .notes-preview pre code { background: transparent; padding: 0; color: inherit; }
  .notes-preview a { color: var(--violet-700, #4d7508); text-decoration: underline; }
  .notes-preview blockquote {
    border-left: 3px solid #cbd5e1;
    margin: 8px 0; padding: 2px 14px; color: #475569;
  }
  .notes-preview-empty { color: var(--ink-500, #64748b); font-style: italic; }

  /* Quote-context pill above the chat form (operator ticket pekd60). */
  .chat-quote-ctx {
    margin: 8px 14px 0;
    padding: 8px 12px;
    background: linear-gradient(90deg, #f7fee7 0%, #ecfccb 100%);
    border: 1px solid rgba(132, 204, 22, 0.4);
    border-radius: 8px;
    font-size: 13px; color: #14532d;
    display: flex; align-items: flex-start; gap: 8px;
  }
  .chat-quote-ctx .ctx-body { flex: 1; min-width: 0; }
  .chat-quote-ctx .ctx-label {
    font-weight: 700; font-size: 11.5px; letter-spacing: 0.04em;
    text-transform: uppercase; color: #4d7508;
    margin-bottom: 2px;
  }
  .chat-quote-ctx .ctx-quote { font-style: italic; color: #1f2937; }
  .chat-quote-ctx button {
    flex: none;
    background: transparent; border: none;
    color: #4d7508; font-size: 18px; line-height: 1;
    padding: 0 2px; cursor: pointer;
  }
  .chat-quote-ctx button:hover { color: #14532d; }
  .notes-meta {
    margin: 8px 4px 0;
    font: 500 11.5px/1.4 var(--sans, "Inter", system-ui, sans-serif);
    color: var(--ink-500, #64748b);
    font-variant-numeric: tabular-nums;
  }

  .view-tabs .view-tab .tab-beta {
    font-size: 9px; font-weight: 700; letter-spacing: 0.08em;
    text-transform: uppercase;
    background: #ecfccb; color: #6da00f;
    padding: 2px 5px; border-radius: 4px;
    line-height: 1.2;
  }
  .view-tabs .view-tab:disabled .tab-beta { background: #f1f5f9; color: #94a3b8; }
  .view-panel[hidden] { display: none; }

  /* Recent transcripts — search input + paginator */
  .recent-search {
    width: 100%;
    background: #f8fafc;
    border: 1px solid #e5e7eb;
    border-radius: 8px;
    padding: 7px 10px; font: inherit; font-size: 13px;
    color: #0f172a; outline: none;
    margin-bottom: 8px;
    transition: border-color 0.15s;
  }
  .recent-search:focus { border-color: #bef264; box-shadow: 0 0 0 3px rgba(132, 204, 22,0.10); }
  .recent-search::placeholder { color: #94a3b8; }
  .recent-pager {
    display: flex; align-items: center; justify-content: space-between;
    margin-top: 8px; padding-top: 8px;
    border-top: 1px solid #f1f5f9;
    font-size: 11.5px; color: #64748b;
    font-variant-numeric: tabular-nums;
  }
  .recent-pager button {
    background: transparent; border: 1px solid #e5e7eb;
    color: #475569;
    width: 24px; height: 24px;
    border-radius: 6px; cursor: pointer;
    display: inline-grid; place-items: center;
    font: inherit; font-size: 12px; line-height: 1;
    transition: border-color 0.15s, color 0.15s, background 0.15s;
  }
  .recent-pager button:hover:not(:disabled) {
    border-color: #bef264; color: #4d7508; background: #ecfccb;
  }
  .recent-pager button:disabled { opacity: 0.4; cursor: not-allowed; }

  /* Insight panel — post-transcript intelligence layer (summary, pull
     quotes, topics, per-speaker contributions). Mounts above the
     transcript on /view once the transcript is ready. Kept visually
     distinct from the transcript card: same rounded white surface, but a
     violet-tinted header and chip rail so it reads as "derived analysis"
     rather than "raw data". */
  .insights {
    background: var(--white); color: var(--ink-900); border-radius: 16px;
    padding: 0; margin-bottom: 16px; overflow: hidden;
    box-shadow: 0 20px 40px -15px rgba(0,0,0,0.25), 0 0 0 1px rgba(255,255,255,0.06);
  }
  .insights[hidden] { display: none; }

  /* Panel scrolling for the fixed-height shell. Declared HERE, after
     `.insights { overflow: hidden }` above, and NOT up with the other shell
     rules — the two selectors have equal specificity and a media query adds
     none, so source order decides. Placed earlier it lost silently: measured
     on staging, overflow-y still computed to `hidden` and only JS could reach
     the clipped 458px. The same trap is documented on main:has() in this file.

     body is a fixed-height flex column with overflow:hidden above 1024x640, so
     any panel that outgrows the viewport is clipped and unreachable unless it
     scrolls itself. .transcript was the only one that did. */
  @media (min-width: 1024px) and (min-height: 640px) {
    .insights,
    #panel-notes .notes-card,
    #chat-inline-mount {
      flex: 1 1 auto;
      min-height: 0;
      overflow-y: auto !important;
      overscroll-behavior: contain;
      scrollbar-gutter: stable;
      scrollbar-width: thin;
      scrollbar-color: rgba(132, 204, 22, 0.75) rgba(15, 23, 42, 0.06);
    }
    .insights::-webkit-scrollbar { width: 12px; }
    .insights::-webkit-scrollbar-track {
      background: rgba(15, 23, 42, 0.06); border-radius: 8px; margin: 6px 0; }
    .insights::-webkit-scrollbar-thumb {
      background: rgba(132, 204, 22, 0.75); border-radius: 8px;
      border: 3px solid transparent; background-clip: padding-box; }
  }
  .insights .ins-head {
    display: flex; align-items: center; justify-content: space-between;
    padding: 14px 22px; gap: 12px;
    background: linear-gradient(135deg, var(--violet-50), rgba(196,181,253,0.25));
    border-bottom: 1px solid var(--violet-100);
  }
  .insights .ins-head-left { display: flex; align-items: center; gap: 10px; }
  .insights .ins-head h2 {
    margin: 0; font-size: 13px; font-weight: 700;
    text-transform: uppercase; letter-spacing: 0.08em;
    color: var(--violet-700);
  }
  .insights .ins-beta {
    display: inline-block; padding: 2px 8px;
    font-size: 10.5px; font-weight: 700; letter-spacing: 0.06em;
    color: var(--violet-700); background: var(--violet-100);
    border-radius: 999px; text-transform: uppercase;
  }
  .insights .ins-beta-note {
    font-size: 11.5px; color: var(--ink-500);
    font-weight: 500; text-transform: none; letter-spacing: 0;
  }
  .insights .ins-body { padding: 20px 24px 26px; }
  .insights .ins-section { margin-top: 22px; padding-top: 18px;
    border-top: 1px solid var(--ink-100); }
  .insights .ins-section:first-child { margin-top: 0; padding-top: 0; border-top: 0; }
  .insights .ins-section-head {
    display: flex; align-items: center; justify-content: space-between;
    margin-bottom: 12px;
    font-size: 11.5px; font-weight: 700;
    text-transform: uppercase; letter-spacing: 0.08em;
    color: var(--ink-500);
  }
  .insights .ins-toggle {
    appearance: none; cursor: pointer; font: inherit;
    background: var(--ink-50);
    border: 1px solid var(--ink-200);
    color: var(--ink-700);
    font-size: 12px; font-weight: 600;
    padding: 5px 12px; border-radius: 999px;
    text-transform: none; letter-spacing: 0;
    transition: background 0.15s, border-color 0.15s, color 0.15s;
    display: inline-flex; align-items: center; gap: 6px;
  }
  .insights .ins-toggle::after {
    content: "▾"; font-size: 10px; line-height: 1;
    transition: transform 0.18s;
  }
  .insights .ins-section[data-collapsed="true"] .ins-toggle::after {
    transform: rotate(-90deg);
  }
  .insights .ins-toggle:hover {
    background: var(--brand-1); border-color: var(--brand-2); color: #1a2e05;
  }
  .insights .ins-section[data-collapsed="true"] .ins-section-body { display: none; }
  .insights .ins-section[data-collapsed="true"] .ins-section-head { margin-bottom: 0; }

  /* Summary — reads like a paragraph. */
  .insights .ins-summary {
    font-size: 15.5px; line-height: 1.65; color: var(--ink-900);
  }

  /* Quotes — left rule + speaker + clickable timestamp. */
  .insights .ins-quote {
    position: relative;
    padding: 10px 14px 10px 18px;
    margin-bottom: 10px;
    border-left: 3px solid var(--violet-500);
    background: var(--ink-50);
    border-radius: 0 10px 10px 0;
    font-size: 14.5px; line-height: 1.55; color: var(--ink-900);
  }
  .insights .ins-quote:last-child { margin-bottom: 0; }
  .insights .ins-quote-meta {
    margin-top: 6px;
    display: flex; gap: 10px; align-items: center;
    font-size: 12px; color: var(--ink-500);
  }
  .insights .ins-quote-speaker {
    font-weight: 600; color: var(--ink-700);
  }

  /* Chapter navigation in the Summary tab. Rows rather than pills: a label is
     a phrase, and pills wrap into an unreadable mosaic once there are eight. */
  .insights .ins-chapters { display: grid; gap: 4px; }
  .insights .ins-chapter {
    display: grid; grid-template-columns: 62px 1fr; gap: 10px; align-items: baseline;
    width: 100%; text-align: left; appearance: none; border: 0; cursor: pointer;
    background: transparent; padding: 8px 10px; border-radius: 8px; font: inherit;
  }
  .insights .ins-chapter:hover { background: var(--violet-50); }
  .insights .ins-chapter-t {
    font: 600 12px/1.5 ui-monospace, Menlo, monospace; color: var(--violet-600);
    font-variant-numeric: tabular-nums; white-space: nowrap;
  }
  .insights .ins-chapter-l { font-size: 14.5px; line-height: 1.5; color: var(--ink-900); }
  @media (max-width: 560px) {
    .insights .ins-chapter { grid-template-columns: 54px 1fr; gap: 8px; padding: 7px 8px; }
    .insights .ins-chapter-l { font-size: 14px; }
  }
  .insights .ins-quote-seek {
    appearance: none; background: transparent; border: 0;
    color: var(--violet-600); cursor: pointer;
    font: inherit; font-size: 12px; font-weight: 600;
    font-family: ui-monospace, Menlo, monospace;
    padding: 1px 6px; border-radius: 4px;
  }

  /* ── Transcript search ───────────────────────────────────────────────
     Sits in .center-sticky, transcript tab only. See the markup comment
     for why it exists (session dfd20b57). */
  .tsearch { display: none; margin: 0 0 10px; }
  body.tab-transcript .tsearch { display: block; }
  .tsearch-open { display: none; }
  .tsearch-field {
    display: flex; align-items: center; gap: 6px;
    background: #fff; border: 1px solid var(--ink-200);
    border-radius: 10px; padding: 0 8px 0 10px;
    transition: border-color .15s, box-shadow .15s;
  }
  .tsearch-field:focus-within {
    border-color: var(--violet-500);
    box-shadow: 0 0 0 3px var(--violet-50);
  }
  .tsearch-ico { color: var(--ink-500); flex: 0 0 auto; }
  .tsearch input {
    flex: 1 1 auto; min-width: 0; border: 0; outline: 0; background: transparent;
    font: inherit; font-size: 14.5px; color: var(--ink-900);
    padding: 9px 0; -webkit-appearance: none;
  }
  .tsearch input::-webkit-search-cancel-button { display: none; }
  .tsearch-count {
    flex: 0 0 auto; font: 600 12px/1 ui-monospace, Menlo, monospace;
    color: var(--ink-500); font-variant-numeric: tabular-nums;
    white-space: nowrap; padding: 0 2px;
  }
  .tsearch-count.is-none { color: #b91c1c; }
  .tsearch-nav {
    appearance: none; border: 0; background: transparent; cursor: pointer;
    color: var(--ink-500); font-size: 14px; line-height: 1;
    padding: 6px 5px; border-radius: 6px; flex: 0 0 auto;
  }
  .tsearch-nav:hover { background: var(--ink-100); color: var(--ink-900); }
  .tsearch-clear { font-size: 18px; }

  /* Results: timestamp + the line, so relevance is judged without jumping. */
  .tsearch-results {
    margin: 0 0 12px; max-height: 34vh; overflow-y: auto;
    border: 1px solid var(--ink-200); border-radius: 10px;
    background: #fff; box-shadow: 0 10px 24px -18px rgba(15,23,42,.35);
  }
  .tsr {
    display: grid; grid-template-columns: 74px 1fr; gap: 10px;
    align-items: baseline; width: 100%; text-align: left;
    appearance: none; border: 0; background: transparent; cursor: pointer;
    padding: 9px 12px; border-bottom: 1px solid var(--ink-100);
    font: inherit; font-size: 13.5px; line-height: 1.5; color: var(--ink-700);
  }
  .tsr:last-child { border-bottom: 0; }
  .tsr:hover, .tsr:focus-visible { background: var(--violet-50); outline: none; }
  .tsr.is-current { background: var(--violet-50); box-shadow: inset 3px 0 0 var(--violet-500); }
  .tsr-t {
    font: 600 12px/1.5 ui-monospace, Menlo, monospace;
    color: var(--violet-600); font-variant-numeric: tabular-nums; white-space: nowrap;
  }
  .tsr-x { min-width: 0; }
  .tsr mark, .transcript mark {
    background: #fde68a; color: inherit; border-radius: 2px; padding: 0 1px;
  }
  .transcript mark.is-current { background: #fbbf24; box-shadow: 0 0 0 2px #fbbf24; }
  .tsearch-empty {
    padding: 12px 14px; font-size: 13.5px; color: var(--ink-500);
  }
  .tsearch-empty b { color: var(--ink-900); }

  /* Clickable citation inside a chat answer — deliberately the same violet
     mono treatment as .ins-quote-seek so "a time you can click" reads the
     same everywhere on the page. */
  .chat-msg.assistant .cts {
    appearance: none; border: 0; cursor: pointer;
    background: var(--violet-50); color: var(--violet-700);
    font: 600 12px/1.35 ui-monospace, Menlo, monospace;
    padding: 2px 7px; border-radius: 6px;
    display: inline-flex; align-items: center; gap: 5px;
    vertical-align: baseline; white-space: nowrap;
  }
  .chat-msg.assistant .cts:hover { background: var(--violet-100); color: var(--violet-700); }
  .chat-msg.assistant .cts-ico { font-size: 9px; opacity: .8; }
  .chat-msg.assistant .cts-spk { opacity: .75; font-weight: 500; }

  /* The segment a citation or a result points at. */
  .transcript .segment.seg-flash {
    animation: segFlash 1.6s ease-out 1;
    border-radius: 8px;
  }
  @keyframes segFlash {
    0%, 30% { background: #fef3c7; }
    100%    { background: transparent; }
  }
  @media (prefers-reduced-motion: reduce) {
    .transcript .segment.seg-flash { animation: none; background: #fef3c7; }
  }

  /* Phone: the header is scarce, so collapse to an icon until it is used.
     The mm.ss control was removed from this same row for taking 44px of a
     169px header on a 699px screen — this must not repeat that. */
  @media (max-width: 760px) {
    body.tab-transcript .tsearch { display: flex; justify-content: flex-end; }
    .tsearch-open {
      display: inline-flex; align-items: center; justify-content: center;
      width: 34px; height: 34px; border-radius: 8px;
      border: 1px solid var(--ink-200); background: #fff;
      color: var(--ink-700); cursor: pointer;
    }
    .tsearch .tsearch-field { display: none; }
    .tsearch.is-open { display: block; }
    .tsearch.is-open .tsearch-open { display: none; }
    .tsearch.is-open .tsearch-field { display: flex; }
    .tsearch-results { max-height: 44vh; }
    .tsr { grid-template-columns: 62px 1fr; gap: 8px; font-size: 13px; }
  }
  .insights .ins-quote-seek:hover { background: var(--violet-100); color: var(--violet-700); }
  /* Per-quote chat button (operator 2026-05-17 ticket faerlh) — opens
     the chat modal preloaded with this quote as context + quick prompts. */
  .insights .ins-quote-chat {
    appearance: none; background: transparent; border: 0;
    color: var(--ink-500); cursor: pointer;
    padding: 2px 4px; border-radius: 4px; margin-left: 4px;
    display: inline-flex; align-items: center;
    transition: background 0.12s, color 0.12s;
  }
  .insights .ins-quote-chat:hover { background: rgba(132, 204, 22, 0.16); color: #15803d; }

  /* Topics — plain selectable text per operator 2026-05-17 ticket 0w6okw.
     No button chrome, no hover color change, no filter click. Selecting
     text triggers an automatic copy-to-clipboard via JS below. */
  .insights .ins-topics {
    display: block;
    line-height: 1.7;
    color: var(--ink-700);
    font-size: 13.5px;
  }
  .insights .ins-topic {
    appearance: none; border: 0; background: transparent;
    color: inherit;
    padding: 0 4px 0 0;
    font: inherit;
    cursor: text;
    user-select: text;
  }
  .insights .ins-topic::after { content: ' · '; color: var(--ink-300); }
  .insights .ins-topic:last-child::after { content: ''; }
  .insights .ins-topic:hover,
  .insights .ins-topic.active { background: transparent; color: inherit; border: 0; }

  /* Per-speaker contributions. */
  .insights .ins-speaker {
    padding: 10px 0; border-bottom: 1px solid var(--ink-100);
  }
  .insights .ins-speaker:last-child { border-bottom: 0; padding-bottom: 0; }
  .insights .ins-speaker-label {
    font-size: 12px; font-weight: 700; color: var(--violet-700);
    text-transform: uppercase; letter-spacing: 0.05em;
    margin-bottom: 4px;
  }
  .insights .ins-speaker-summary { font-size: 14.5px; line-height: 1.55; color: var(--ink-900); }

  /* Skeleton loading bars — match the section layout so the user's eye
     doesn't jump when real content swaps in. */
  .insights .ins-elapsed {
    font: 600 11.5px/1.2 var(--sans, "Inter");
    color: var(--violet-700, #4d7508);
    background: var(--violet-100, #ecfccb);
    border: 1px solid var(--violet-300, #d9f99d);
    padding: 3px 9px; border-radius: 999px;
    font-variant-numeric: tabular-nums;
  }
  .insights .ins-prep-bar {
    height: 4px;
    background: var(--ink-100, #f1f5f9);
    border-radius: 999px;
    overflow: hidden;
    margin: 10px 0 14px;
  }
  .insights .ins-prep-bar-fill {
    height: 100%;
    width: 0%;
    background: linear-gradient(90deg, var(--violet-500, #a3dc2d), var(--violet-700, #4d7508));
    transition: width 0.6s ease-out;
  }
  .insights .ins-skel {
    height: 12px; background: var(--ink-100); border-radius: 6px;
    margin-bottom: 8px;
    background: linear-gradient(90deg, var(--ink-100) 25%, var(--ink-200) 50%, var(--ink-100) 75%);
    background-size: 200% 100%;
    animation: ins-shimmer 1.3s ease-in-out infinite;
  }
  .insights .ins-skel.w-80 { width: 80%; }
  .insights .ins-skel.w-60 { width: 60%; }
  .insights .ins-skel.w-40 { width: 40%; }
  @keyframes ins-shimmer {
    0% { background-position: 200% 0; }
    100% { background-position: -200% 0; }
  }
  .insights .ins-fallback {
    padding: 24px 22px 28px; font-size: 13.5px; color: var(--ink-500);
    text-align: center;
  }
  .insights .ins-retry-btn {
    appearance: none; border: 0;
    background: linear-gradient(135deg, var(--violet-500, #a3dc2d), var(--violet-700, #4d7508));
    color: #ffffff;
    font: 700 13px/1.2 var(--sans, "Inter", system-ui, sans-serif);
    padding: 9px 20px;
    border-radius: 8px;
    cursor: pointer;
    transition: transform 0.1s, box-shadow 0.15s;
    box-shadow: 0 6px 16px -8px rgba(77, 117, 8, 0.4);
  }
  .insights .ins-retry-btn:hover {
    transform: translateY(-1px);
    box-shadow: 0 10px 22px -8px rgba(77, 117, 8, 0.5);
  }

  /* First paint shows the chosen tab's panel before the script switches (no Transcript flash). */
  html[data-whip-tab="intelligence"] #panel-transcript, html[data-whip-tab="ai-chat"] #panel-transcript { display: none; }
  html[data-whip-tab="intelligence"] #panel-intelligence { display: block; }
  html[data-whip-tab="ai-chat"] #panel-ai-chat { display: block; }
  /* Overview tab layout (operator 2026-09-25): band, two lanes, create cards, FAQ. */
  .ov-cost { margin-top: 8px; font-size: 12.5px; color: #6b7280; }
  .ov-cost b { color: #374151; }
  .ov-band { background: linear-gradient(135deg, var(--violet-50), #f8faf3); border: 1px solid var(--violet-100); border-radius: 14px; padding: 16px 20px; margin-bottom: 14px; }
  .ov-band-top { display: flex; justify-content: space-between; gap: 12px; align-items: center; flex-wrap: wrap; }
  .ov-eyebrow { font-size: 11px; font-weight: 800; letter-spacing: .08em; text-transform: uppercase; color: var(--violet-700); }
  .ov-ai { font-size: 12px; color: #6b7280; background: #eef2e6; padding: 3px 9px; border-radius: 999px; }
  .ov-title { margin: 6px 0 4px; font-size: 24px; line-height: 1.2; letter-spacing: -0.01em; }
  .ov-meta { font-size: 13px; color: #6b7280; }
  .ov-grid { display: grid; grid-template-columns: minmax(0, 1.35fr) minmax(280px, 1fr); gap: 14px; align-items: start; }
  @media (max-width: 900px) { .ov-grid { grid-template-columns: 1fr; } }
  .ov-lane { min-width: 0; }
  .ov-card { background: #fff; border: 1px solid var(--violet-100); border-radius: 14px; padding: 16px 18px; margin-bottom: 14px; }
  .ov-card-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; margin-bottom: 8px; }
  .ov-card-head h3 { margin: 0; font-size: 16px; font-weight: 800; letter-spacing: -0.01em; }
  .ov-copy, .ov-plan { font: inherit; font-size: 13px; color: var(--violet-700); background: transparent; border: 0; padding: 4px 6px; cursor: pointer; border-radius: 6px; }
  .ov-copy:hover, .ov-plan:hover { background: var(--violet-50); }
  .ov-plan { border: 1px solid var(--violet-700); padding: 8px 14px; font-weight: 600; }
  .ov-thesis { margin: 0 0 10px; font-size: 17px; font-weight: 700; line-height: 1.35; }
  .ov-takeaways { margin: 0; padding-left: 0; list-style: none; counter-reset: tk; }
  .ov-takeaways li { counter-increment: tk; display: grid; grid-template-columns: 26px 1fr; gap: 10px; padding: 8px 0; border-top: 1px solid #eef2e6; }
  .ov-takeaways li::before { content: counter(tk); width: 24px; height: 24px; border-radius: 50%; background: var(--violet-700); color: #fff; font-size: 12px; font-weight: 700; display: grid; place-items: center; }
  .ov-takeaways strong { display: block; font-size: 14px; } .ov-takeaways span { font-size: 13px; color: #6b7280; }
  .ov-sub { font-size: 12px; color: #6b7280; margin: 0 0 8px; }
  .ov-chapters { margin: 0; padding: 0; list-style: none; max-height: 372px; overflow: auto; scrollbar-width: thin; }
  .ov-chapter { width: 100%; display: grid; grid-template-columns: 52px 12px 1fr; gap: 8px; align-items: start; text-align: left; background: transparent; border: 0; padding: 8px 4px; font: inherit; cursor: pointer; border-radius: 8px; }
  .ov-chapter:hover { background: var(--violet-50); }
  .ov-chapter .t { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; color: var(--violet-700); padding-top: 2px; }
  .ov-chapter .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--violet-700); margin-top: 6px; box-shadow: 0 0 0 3px #fff, 0 0 0 4px var(--violet-100); }
  .ov-chapter strong { display: block; font-size: 14px; } .ov-chapter span { display: block; font-size: 12.5px; color: #6b7280; }
  .ov-chapter.is-now { background: var(--violet-50); } .ov-chapter.is-now strong { color: var(--violet-700); }
  .ov-chips { display: flex; flex-wrap: wrap; gap: 6px; }
  .ov-chip { font: inherit; font-size: 12.5px; padding: 4px 10px; border-radius: 999px; border: 1px solid #e5e7eb; background: #f8faf3; cursor: pointer; }
  .ov-chip.num { background: #fdf3e4; border-color: #f3e2c0; } .ov-chip.person { background: var(--violet-50); border-color: var(--violet-100); }
  .ov-chip:hover { border-color: var(--violet-700); }
  .ov-lock { margin-top: 10px; padding: 12px; border-radius: 10px; background: #fdf3e4; border: 1px solid #f3e2c0; }
  .ov-lock p { margin: 0 0 8px; font-size: 13px; }
  .ov-sample { opacity: .55; pointer-events: none; }
  .ov-actions { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 10px; }
  @media (max-width: 900px) { .ov-actions { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
  @media (max-width: 520px) { .ov-actions { grid-template-columns: 1fr; } }
  .ov-action { text-align: left; font: inherit; background: #fff; border: 1px solid #e5e7eb; border-radius: 12px; padding: 12px 14px; cursor: pointer; display: grid; gap: 3px; }
  .ov-action:hover { border-color: var(--violet-700); background: var(--violet-50); }
  .ov-action strong { font-size: 14px; } .ov-action span { font-size: 12.5px; color: #6b7280; }
  .ov-action[disabled] { opacity: .6; cursor: default; }
  .ov-q { border-top: 1px solid #eef2e6; padding: 6px 0; }
  .ov-q summary { cursor: pointer; font-weight: 600; font-size: 14px; padding: 6px 0; list-style: none; display: flex; gap: 8px; }
  .ov-q summary::before { content: '▸'; color: var(--violet-700); } .ov-q[open] summary::before { content: '▾'; }
  .ov-q p { margin: 2px 0 8px 18px; font-size: 14px; color: #374151; }
  .ov-play { font: inherit; font-size: 12.5px; color: var(--violet-700); background: var(--violet-50); border: 0; border-radius: 999px; padding: 4px 10px; cursor: pointer; margin-left: 18px; }
  .ov-play:hover { background: var(--violet-100); }
  .ov-play-inline { margin-left: 6px; padding: 1px 8px; font-size: 12px; vertical-align: 1px; white-space: nowrap; }
  .seg-flash { animation: ov-flash 1.6s ease-out; }
  @keyframes ov-flash { 0% { background: #fdf3e4; } 100% { background: transparent; } }
  /* Intelligence-Beta layout (matches landing /Q3-earnings mock):
     Executive Summary card with "AI" pill + green-dot bullets;
     Topics & mentions chip rail;
     KEY QUOTE block — large italic line + speaker attribution. */
  .insights .ins-exec-card {
    background: linear-gradient(135deg, var(--violet-50), #ffffff 60%);
    border: 1px solid var(--violet-100);
    border-radius: 14px;
    padding: 18px 20px;
    margin-bottom: 14px;
  }
  .insights .ins-exec-head {
    display: flex; align-items: center; gap: 8px;
    margin-bottom: 12px;
  }
  .insights .ins-exec-head .ins-exec-title {
    font-size: 13px; font-weight: 800;
    color: var(--ink-900);
    letter-spacing: -0.01em;
  }
  .insights .ins-exec-head .ins-ai-pill {
    font-size: 9.5px; font-weight: 800; letter-spacing: 0.08em;
    text-transform: uppercase;
    background: var(--violet-700); color: #ffffff;
    padding: 2px 6px; border-radius: 4px;
    line-height: 1.2;
  }
  .insights .ins-exec-bullets {
    list-style: none; padding: 0; margin: 0;
    display: flex; flex-direction: column; gap: 10px;
  }
  .insights .ins-exec-bullets li {
    position: relative;
    padding-left: 18px;
    font-size: 14.5px; line-height: 1.55;
    color: var(--ink-900);
  }
  .insights .ins-exec-bullets li::before {
    content: ""; position: absolute; left: 0; top: 8px;
    width: 8px; height: 8px; border-radius: 50%;
    background: var(--violet-600);
  }
  .insights .ins-key-quote {
    background: #ffffff;
    border: 1px solid var(--violet-100);
    border-left: 4px solid var(--violet-600);
    border-radius: 10px;
    padding: 18px 22px;
    margin-top: 14px;
  }
  .insights .ins-key-quote-label {
    font-size: 10.5px; font-weight: 800; letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--violet-700);
    margin-bottom: 8px;
  }
  .insights .ins-key-quote-text {
    font-size: 17px; line-height: 1.5; font-style: italic;
    color: var(--ink-900);
    font-weight: 500;
  }
  .insights .ins-key-quote-attr {
    margin-top: 10px;
    font-size: 12.5px; color: var(--ink-500);
    display: flex; gap: 8px; align-items: center;
    flex-wrap: wrap;
  }
  .insights .ins-key-quote-attr .ins-quote-seek { font-size: 12px; }
  /* Per-quote AI Chat button right-aligned (operator 2026-05-17 wk3g99). */
  .insights .ins-key-quote-attr .ins-quote-chat,
  .insights .ins-quote-meta .ins-quote-chat { margin-left: auto; }

  /* Filter-mode — when a topic chip is active, non-matching segments dim. */
  .transcript.filter-active .segment { opacity: 0.28; transition: opacity 0.2s; }
  .transcript.filter-active .segment.filter-match { opacity: 1; }
  .transcript.filter-active .segment.filter-match .ts {
    background: var(--violet-100); color: var(--violet-700);
  }

  /* Share footer — small, muted, below the transcript. Light surface
     to match the cream-wash page background; ink text + lime accent
     keeps it on-brand with landing/pricing. Copy button is a bordered
     pill so it's obviously clickable but doesn't dominate. */
  /* Operator 2026-05-17 lm3zhh: share footer always visible + clears
     the audio bar overlap. Embed/LP variants still hide via earlier
     overrides. */
  /* (Removed 2026-08-05: a leftover from the footer-reservation experiment
     that survived its own revert. It forced the footer to render while
     [hidden], costing ~70px of reading area to buy ~0.01 CLS. The real
     cause of the shift was main being shrink-to-fit, now fixed.) */
  #view-share-footer { margin-bottom: 130px; }
  /* Post-done top-up nudge (2026-05-29) — soft lime accent matching
     the focus ring + active-segment tint. Stays inline, not modal,
     so it doesn't break the read flow. */
  .whip-topup-nudge {
    display: block;
    margin: 28px 0 24px;
    padding: 18px 20px;
    background: rgba(132, 204, 22, 0.06);
    border: 1px solid rgba(132, 204, 22, 0.25);
    border-radius: 12px;
  }
  .whip-topup-nudge .wtn-row { display: flex; flex-direction: column; gap: 2px; margin-bottom: 12px; }
  .whip-topup-nudge .wtn-h { font: 600 16px / 1.3 var(--sans); color: #1e3a0f; }
  .whip-topup-nudge .wtn-sub { font: 500 13px / 1.4 var(--sans); color: var(--ink-500); }
  .whip-topup-nudge .wtn-actions { display: flex; gap: 10px; flex-wrap: wrap; }
  .whip-topup-nudge .wtn-btn-primary {
    background: var(--violet-600, #84cc16); color: #fff;
    padding: 10px 16px; border-radius: 8px;
    font: 600 14px / 1 var(--sans); text-decoration: none;
    transition: filter 0.15s;
  }
  .whip-topup-nudge .wtn-btn-primary:hover { filter: brightness(1.08); }
  .whip-topup-nudge .wtn-btn-secondary {
    color: var(--ink-700); padding: 10px 14px;
    font: 500 14px / 1 var(--sans); text-decoration: none;
    border: 1px solid var(--rule, #e2e8f0); border-radius: 8px;
  }
  .whip-topup-nudge .wtn-btn-secondary:hover { background: var(--paper, #f8fafc); }
  .whip-topup-nudge .wtn-note { margin: 10px 0 0; font: 500 12px / 1.4 var(--sans); color: var(--ink-500); }
  /* Floating placement (2026-08-02): the inline card renders after the
     ENTIRE transcript — measured y=387,309 on a 387,719px page for a
     12-hour job, ~430 viewports below the fold — so a reader who never
     scrolls to the absolute end is never asked. When revealed, the card
     floats above the audio bar instead. Same copy, same 2-show session
     cap, plus an explicit dismiss. Anchored bottom-LEFT because the
     nav-dock owns bottom-right (right:12px, z-index 45). */
  .whip-topup-nudge.is-floating {
    position: fixed;
    left: 12px;
    bottom: calc(var(--dock-bottom, 93px) + 10px);
    z-index: 44; /* above audio-bar (40), below nav-dock (45) */
    max-width: 380px;
    margin: 0;
    background: #f8fcf1; /* opaque equivalent of the inline lime tint —
                            this now paints over transcript text */
    box-shadow: 0 18px 36px -16px rgba(15, 23, 42, 0.3);
  }
  .whip-topup-nudge .wtn-dismiss {
    display: none; /* inline placement scrolls away; no dismiss needed */
    position: absolute; top: 8px; right: 8px;
    width: 28px; height: 28px;
    border: 0; border-radius: 8px; background: transparent;
    color: var(--ink-500); font: 400 18px / 1 var(--sans);
    cursor: pointer;
  }
  .whip-topup-nudge .wtn-dismiss:hover { background: rgba(15, 23, 42, 0.06); color: var(--ink-700); }
  .whip-topup-nudge.is-floating .wtn-dismiss { display: block; }
  @media (max-width: 640px) {
    .whip-topup-nudge.is-floating { left: 10px; right: 10px; max-width: none; }
  }
  .whip-share-footer {
    display: flex; align-items: center; justify-content: flex-start;
    gap: 12px; flex-wrap: wrap;
    margin-top: 20px; padding: 14px 16px;
    background: var(--white);
    border: 1px solid rgba(15,23,42,0.08);
    border-radius: 12px;
    color: var(--ink-700);
    font-size: 13px;
    box-shadow:
      0 1px 2px rgba(15,23,42,0.04),
      0 4px 14px -2px rgba(15,23,42,0.05);
  }
  .whip-share-footer .wsf-link {
    color: var(--brand-800); font-weight: 600; text-decoration: underline;
    text-decoration-color: rgba(109,160,15,0.45); text-underline-offset: 3px;
  }
  .whip-share-footer .wsf-link:hover { text-decoration-color: var(--brand-700); }
  .whip-share-footer .wsf-copy {
    appearance: none; background: var(--ink-50); color: var(--ink-900);
    border: 1px solid rgba(15,23,42,0.14); border-radius: 8px;
    padding: 6px 12px; font: inherit; font-size: 12.5px; font-weight: 600;
    cursor: pointer; transition: background 0.15s, border-color 0.15s, color 0.15s;
  }
  .whip-share-footer .wsf-copy:hover {
    background: var(--brand-1); border-color: var(--brand-2); color: #1a2e05;
  }
  .whip-share-footer .wsf-copy.copied {
    background: var(--brand-1); border-color: var(--brand-2); color: #1a2e05;
  }

  /* Anonymous email-capture — sits directly under the share footer,
     only renders for visitors who haven't given us an email yet.
     Same light card surface as the share footer so it reads as one
     cohesive footer block tuned to the cream-wash background. */
  .whip-email-capture { margin-top: 12px; padding: 18px 20px;
    background: var(--white);
    border: 1px solid rgba(15,23,42,0.08);
    border-radius: 14px;
    color: var(--ink-900);
    box-shadow:
      0 1px 2px rgba(15,23,42,0.04),
      0 8px 24px -10px rgba(15,23,42,0.08);
    transition: box-shadow 0.3s, border-color 0.3s, background 0.3s; }
  /* Value-realization pulse (2026-05-29). Triggered after the user
     successfully downloads or copies — soft lime accent to draw the eye
     without being aggressive. */
  .whip-email-capture.wec-pulse {
    border-color: rgba(132, 204, 22, 0.55);
    background: rgba(132, 204, 22, 0.05);
    box-shadow:
      0 0 0 4px rgba(132, 204, 22, 0.18),
      0 8px 24px -10px rgba(132, 204, 22, 0.4);
  }
  .whip-email-capture .wec-h { font-weight: 700; font-size: 15px;
    color: var(--ink-900); margin: 0 0 4px; letter-spacing: -0.005em; }
  .whip-email-capture .wec-sub { font-size: 13px;
    color: var(--ink-500); margin: 0 0 12px; line-height: 1.55; }
  .whip-email-capture form { display: flex; gap: 10px; flex-wrap: wrap; }
  .whip-email-capture input[type="email"] {
    flex: 1 1 240px; min-width: 0;
    padding: 10px 14px; font: inherit; font-size: 14px;
    color: var(--ink-900); background: #fff;
    border: 1px solid var(--ink-200);
    border-radius: 10px;
    transition: border-color 0.15s, box-shadow 0.15s; }
  .whip-email-capture input[type="email"]::placeholder { color: var(--ink-300); }
  .whip-email-capture input[type="email"]:focus { outline: none;
    border-color: var(--brand-2);
    box-shadow: 0 0 0 3px rgba(197,244,79,0.35); }
  .whip-email-capture button { appearance: none;
    padding: 10px 18px; font: inherit; font-size: 14px; font-weight: 700;
    color: #1a2e05; background: var(--brand-1, #c5f44f);
    border: 1px solid var(--brand-2, #a3dc2d);
    border-radius: 10px; cursor: pointer;
    transition: transform 0.12s, box-shadow 0.15s, background 0.15s; }
  .whip-email-capture button:hover:not(:disabled) { transform: translateY(-1px);
    background: var(--brand-2);
    box-shadow: 0 8px 18px -8px rgba(109,160,15,0.45); }
  .whip-email-capture button:disabled { opacity: 0.55; cursor: not-allowed; }
  .whip-email-capture .wec-consent { font-size: 11.5px;
    color: var(--ink-500); margin: 10px 0 0; line-height: 1.5; }
  .whip-email-capture .wec-consent a { color: var(--ink-700); text-decoration: underline; text-underline-offset: 2px; }
  .whip-email-capture .wec-consent a:hover { color: var(--brand-800); }
  .whip-email-capture .wec-success { color: var(--brand-800); font-size: 13.5px;
    font-weight: 600; margin: 0; }

  /* right sidebar — taste pass: tiled SVG icons, primary download
     button, tighter panels, real brand marks for AI destinations.
     Pinned just below the global nav (top: 55px) with no scroll-
     before-pin gap. max-height + overflow-y means the rail stays
     reachable even when the visitor zooms in. */
  aside.right { position: sticky; top: 55px;
    max-height: calc(100vh - 60px);
    overflow-y: auto;
    display: flex; flex-direction: column; gap: 12px; }
  .panel {
    background: var(--white); color: var(--ink-900);
    border-radius: 14px;
    padding: 14px 14px;
    box-shadow: 0 18px 40px -20px rgba(0,0,0,0.25);
  }
  .panel h2.panel-heading {
    margin: 2px 4px 12px; font-size: 11px; font-weight: 700;
    text-transform: uppercase; letter-spacing: 0.08em; color: var(--ink-500);
  }

  /* Primary Download button — visually heavier than any row item. */
  .panel .btn-primary {
    display: inline-flex; align-items: center; justify-content: center; gap: 8px;
    width: 100%;
    padding: 11px 16px;
    border: 0; border-radius: 10px;
    background: linear-gradient(135deg, var(--violet-700), var(--violet-800));
    color: var(--white);
    font: inherit; font-size: 14px; font-weight: 600;
    cursor: pointer;
    box-shadow: 0 8px 22px -8px rgba(109, 40, 217, 0.5);
    transition: transform 0.1s, box-shadow 0.15s;
    margin-top: 10px;
  }
  .panel .btn-primary svg { width: 16px; height: 16px; flex-shrink: 0; }
  .panel .btn-primary:hover { transform: translateY(-1px);
    box-shadow: 0 12px 28px -8px rgba(109, 40, 217, 0.6); }
  .panel .btn-primary:active { transform: translateY(0); }
  .panel .btn-primary:disabled { opacity: 0.85; cursor: wait; transform: none; }
  /* Spinner shown inside the Download button while a download fetch is
     in flight. Same stroke as the static download arrow so it doesn't
     visually shift when swapping in. */
  .panel .btn-primary .dl-spin {
    width: 16px; height: 16px; flex-shrink: 0;
    animation: dl-spin 0.8s linear infinite;
  }
  @keyframes dl-spin { to { transform: rotate(360deg); } }

  /* Format select — matches the primary button's rhythm. */
  .panel .fmt-select {
    width: 100%;
    padding: 10px 12px;
    font: inherit; font-size: 13.5px;
    color: var(--ink-900);
    background: var(--ink-50);
    border: 1px solid var(--ink-200);
    border-radius: 10px;
    cursor: pointer;
    appearance: none;
    background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%2364748b' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M6 9l6 6 6-6'/></svg>");
    background-repeat: no-repeat;
    background-position: right 12px center;
    padding-right: 36px;
  }
  .panel .fmt-select:focus { outline: none; border-color: var(--violet-500);
    box-shadow: 0 0 0 3px rgba(163, 220, 45, 0.15); }

  /* Row-style items (secondary actions + AI destinations). */
  .panel .item {
    display: flex; align-items: center; gap: 10px;
    padding: 8px 8px;
    border-radius: 8px; cursor: pointer;
    font-size: 13.5px; color: var(--ink-900);
    text-decoration: none;
    border: 0; background: transparent;
    width: 100%; text-align: left; font-family: inherit;
    transition: background 0.12s, color 0.12s;
  }
  .panel .item:hover { background: var(--violet-50); color: var(--violet-800); }
  .panel .item:focus-visible { outline: none;
    box-shadow: inset 0 0 0 2px var(--violet-300); background: var(--violet-50); }
  .panel .item + .item { margin-top: 2px; }

  /* Icon tile — consistent geometry across every row + brand-agnostic
     so feather-style glyphs, inline brand SVGs, and coloured brand marks
     all occupy the same optical weight. */
  .panel .tool-ico {
    width: 28px; height: 28px;
    display: flex; align-items: center; justify-content: center;
    background: var(--violet-50);
    color: var(--violet-700);
    border-radius: 7px;
    flex-shrink: 0;
  }
  .panel .tool-ico svg { width: 16px; height: 16px; }
  /* Brand-coloured tiles — white background + brand glyph so Chrome,
     Claude, etc. read as themselves and not as violet chrome. */
  .panel .tool-ico.brand-openai { background: #fff; color: #10a37f; border: 1px solid var(--ink-200); }
  .panel .tool-ico.brand-claude { background: #fff; color: #cc7a5c; border: 1px solid var(--ink-200); }
  .panel .tool-ico.brand-perplexity { background: #fff; color: #20808d; border: 1px solid var(--ink-200); }
  .panel .tool-ico.brand-gemini { background: #fff; border: 1px solid var(--ink-200); padding: 4px; }
  .panel .tool-ico.brand-gemini svg { width: 18px; height: 18px; }
  .panel .tool-ico.brand-obsidian { background: #fff; border: 1px solid var(--ink-200); padding: 4px; }
  .panel .tool-ico.brand-obsidian svg { width: 18px; height: 18px; }

  .panel .item.danger { color: var(--rose-600); }
  .panel .item.danger .tool-ico { background: #fef2f2; color: var(--rose-600); }
  .panel .item.danger:hover { background: #fef2f2; color: var(--rose-600); }

  /* Toggle — same row geometry, switch on the right. */
  .panel .item.toggle { justify-content: space-between; }
  .panel .item.toggle .item-label { display: flex; align-items: center; gap: 10px; }
  .panel .item.toggle .switch {
    width: 34px; height: 20px; background: var(--ink-200); border-radius: 100px;
    position: relative; transition: background 0.15s; flex-shrink: 0;
  }
  .panel .item.toggle.on .switch { background: var(--violet-600); }
  .panel .item.toggle .switch::before {
    content: ""; position: absolute; top: 2px; left: 2px;
    width: 16px; height: 16px; border-radius: 50%;
    background: var(--white); transition: transform 0.15s;
    box-shadow: 0 1px 3px rgba(0,0,0,0.15);
  }
  .panel .item.toggle.on .switch::before { transform: translateX(14px); }
  /* Collapsible panel variant — details/summary without default disclosure UI. */
  .panel-details { padding: 0; }
  .panel-details > summary {
    list-style: none;
    padding: 14px 14px 12px;
    font-size: 11px; font-weight: 700;
    text-transform: uppercase; letter-spacing: 0.08em;
    color: var(--ink-500);
    cursor: pointer;
    display: flex; align-items: center; justify-content: space-between;
    border-radius: 14px;
    user-select: none;
  }
  .panel-details > summary::-webkit-details-marker { display: none; }
  .panel-details > summary::marker { content: ''; }
  .panel-details > summary:hover { color: var(--ink-900); }
  .panel-details > summary .h4-inline { text-transform: inherit; letter-spacing: inherit; }
  .panel-details > summary .summary-chevron {
    font-size: 14px; color: var(--ink-500);
    transition: transform 0.15s;
    line-height: 1;
  }
  .panel-details[open] > summary .summary-chevron { transform: rotate(90deg); }
  .panel-details > summary .summary-chevron svg {
    width: 14px; height: 14px; display: block;
  }
  .panel-details .panel-details-body { padding: 0 14px 14px; }
  .panel-details .panel-details-body .fmt-select,
  .panel-details .panel-details-body .ai-template-select { margin-bottom: 8px; }
  .panel-details .panel-details-body .ai-note {
    margin-top: 10px; padding: 10px 12px;
    background: var(--ink-50);
    border-radius: 8px;
    font-size: 11.5px; color: var(--ink-500);
    line-height: 1.45;
  }
  .panel .item .item-label { display: flex; align-items: center; gap: 10px; }

  /* Operator ticket sqw9yx: hide the audio bar on Notes + AI Chat
     tabs — those surfaces have nothing to do with playback and the
     bar was just covering the input. Audio element keeps playing
     (display:none on the bar doesn't pause the underlying <audio>),
     so a user who was listening can switch tabs without interruption. */
  body.tab-notes .audio-bar,
  body.tab-ai-chat .audio-bar { display: none !important; }

  /* Sticky-bottom audio player — sits above the fold but fixed to the viewport.
     Initial opacity 0 → JS sync() applies the correct desktop width then
     adds .is-synced to fade in. Avoids the "renders full-width then snaps"
     flash on first paint (operator ticket fb_q6ix53). */
  .audio-bar {
    position: fixed; left: 0; right: 0; bottom: 0;
    z-index: 40;
    opacity: 0;
    transition: opacity 0.18s ease-out;
    background: rgba(255, 255, 255, 0.96);
    color: var(--ink-900);
    /* Operator 2026-05-17 98hrn9: use the full center column width
       on desktop — inner padding is small (16px right, 12px left)
       since the JS sync already constrains the bar to .center bounds. */
    padding: 14px 16px 14px 12px;
    border-top: 1px solid rgba(76, 29, 149, 0.08);
    box-shadow: 0 -20px 40px -20px rgba(76, 29, 149, 0.18);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    display: grid;
    grid-template-columns: auto 1fr auto auto;
    grid-template-areas: "play scrub time vol" "play sub sub sub";
    align-items: center;
    column-gap: 20px;
    row-gap: 6px;
    overflow: hidden;
  }
  .audio-bar.is-synced { opacity: 1; }
  .audio-bar::before {
    /* soft violet glow so the bar feels lit rather than flat */
    content: "";
    position: absolute; inset: 0;
    background: radial-gradient(120% 160% at 50% 0%, rgba(190, 242, 100, 0.10), transparent 55%);
    pointer-events: none;
  }
  /* Page padding so the sticky bar never covers transcript content */
  body { padding-bottom: 110px; }

  /* Shell overrides, placed HERE on purpose. The shell block earlier in this
     sheet sets body padding-bottom too, but `body{padding-bottom:110px}` above
     has identical specificity and later source order, so it won. Measured at
     1198x640: padding computed to 110 instead of 93, and the share footer was
     contributing margin-top 20 + margin-bottom 130 -- 167px of dead space
     inside a locked viewport, which squeezed the transcript to 114px.

     Those margins and that padding exist to clear the fixed audio bar while
     the PAGE scrolls. In the locked shell the column already ends above the
     bar, so they buy nothing and cost reading area. */
  @media (min-width: 1024px) and (min-height: 640px) {
    body { padding-bottom: var(--dock-bottom, 93px); }
    /* #view-share-footer (an ID) sets margin-bottom:130px to clear the fixed
       audio bar during page scroll. An ID beats a class regardless of source
       order, so the class override below was silently losing -- measured 130
       still applied. Match the ID to win, and only inside the locked shell
       where the column already ends above the bar. */
    #view-share-footer { margin-bottom: 0; }
    .whip-share-footer { margin-top: 6px; }
  }
  /* Play-block — wraps the −15 seek, big play, +15 seek into one
     grid cell so the existing grid-template-areas (play / scrub /
     time / vol) still works. */
  .audio-bar .play-block {
    grid-area: play;
    display: inline-flex; align-items: center; gap: 10px;
    flex-shrink: 0;
  }
  /* Operator 2026-05-17 ticket eq905u: half-arc curved arrow + the
     "5" sitting INSIDE the open curve with small left/right padding.
     No background ring on hover, just the icon + glyph getting more
     lime. The SVG paths in the HTML were already partial arcs; this
     positions the label cleanly in the curve. */
  /* Seek buttons — light grey neutral (operator 2026-05-17 ticket 8gtswc),
     hover stays a slightly darker grey. No green tint. */
  .audio-bar .audio-seek {
    position: relative;
    width: 36px; height: 36px;
    display: inline-grid; place-items: center;
    background: transparent;
    color: var(--ink-300, #cbd5e1);
    border: 0; cursor: pointer; border-radius: 999px;
    transition: color 0.15s, transform 0.12s, background 0.15s;
  }
  .audio-bar .audio-seek:hover {
    color: var(--ink-500, #64748b);
    background: rgba(15, 23, 42, 0.05);
  }
  .audio-bar .audio-seek:active { transform: scale(0.92); }
  .audio-bar .audio-seek:disabled { opacity: 0.4; cursor: default; }
  .audio-bar .audio-seek svg { width: 32px; height: 32px; display: block; }
  .audio-bar .audio-seek .audio-seek-label {
    position: absolute; top: 50%; left: 50%;
    transform: translate(-50%, calc(-50% + 1px));
    font: 700 10px/1 ui-monospace, Menlo, monospace;
    letter-spacing: -0.02em;
    pointer-events: none;
    color: inherit;
  }
  /* Playback-rate pill — cycles 1× → 1.5× → 2× → 2.5× → 3× → 1×. */
  .audio-bar .audio-rate {
    position: relative;     /* anchor the popup menu directly above this pill */
    display: inline-grid; place-items: center;
    min-width: 38px; height: 28px;
    padding: 0 8px; margin-left: 2px;
    background: transparent; color: var(--ink-700, #334155);
    border: 1px solid var(--ink-200, #e2e8f0); border-radius: 999px;
    cursor: pointer; font: 700 12px/1 ui-monospace, Menlo, monospace;
    letter-spacing: -0.01em;
    transition: background 0.15s, color 0.15s, border-color 0.15s, transform 0.12s;
  }
  .audio-bar .audio-rate:hover { background: rgba(132, 204, 22, 0.16); color: #15803d;
    border-color: rgba(132, 204, 22, 0.42); }
  .audio-bar .audio-rate:active { transform: scale(0.94); }
  .audio-bar .audio-rate.is-fast {
    background: linear-gradient(135deg, #bef264, #84cc16 60%); color: #fff;
    border-color: #6da00f;
  }
  /* Speed-picker popup (operator 2026-05-17 9q8ilm) */
  .audio-bar .audio-rate-menu {
    position: absolute;
    bottom: calc(100% + 10px); left: 50%; transform: translateX(-50%);
    background: #fff;
    border: 1px solid var(--ink-200, #e2e8f0); border-radius: 14px;
    box-shadow: 0 16px 36px -10px rgba(15, 23, 42, 0.28);
    padding: 6px;
    display: none;
    min-width: 84px;
    z-index: 50;
  }
  .audio-bar .audio-rate-menu.is-open { display: flex; flex-direction: column; gap: 2px; }
  .audio-bar .audio-rate-menu button {
    appearance: none; cursor: pointer;
    padding: 7px 12px; min-height: 28px;
    background: transparent; border: 0; border-radius: 8px;
    color: var(--ink-700, #334155);
    font: 700 12px/1 ui-monospace, Menlo, monospace;
    text-align: center;
    transition: background 0.12s, color 0.12s;
  }
  .audio-bar .audio-rate-menu button:hover {
    background: rgba(132, 204, 22, 0.16); color: #15803d;
  }
  .audio-bar .audio-rate-menu button.is-active {
    background: linear-gradient(135deg, #bef264, #84cc16 60%); color: #fff;
  }
  /* Ensure the popup escapes the audio-bar overflow:hidden clipping */
  .audio-bar { overflow: visible !important; }
  .audio-bar .play-block { position: relative; }
  .audio-bar .play {
    width: 52px; height: 52px; border-radius: 50%;
    background: linear-gradient(135deg, #bef264, #84cc16 60%, #6da00f);
    color: #fff;
    display: grid; place-items: center;
    font-size: 18px;
    border: 0; cursor: pointer; font-weight: 700; flex-shrink: 0;
    box-shadow: 0 10px 24px -6px rgba(132, 204, 22, 0.45);
    transition: transform 0.12s, box-shadow 0.15s;
    position: relative;
  }
  .audio-bar .play:hover:not(:disabled) {
    transform: translateY(-1px) scale(1.03);
    box-shadow: 0 14px 32px -6px rgba(132, 204, 22, 0.55);
  }
  .audio-bar .play:active:not(:disabled) { transform: translateY(0) scale(0.98); }
  .audio-bar .play:disabled {
    background: #e9e5f4; color: var(--ink-500);
    cursor: not-allowed; box-shadow: none;
  }
  /* Scrub: visually 10px, but we give it a 28px hit-area via vertical
     padding so it's easy to click/tap precisely (the wrapper holds the
     hitbox; the inner .scrub-track is what you see). */
  .audio-bar .scrub {
    grid-area: scrub;
    position: relative;
    min-width: 80px;
    padding: 12px 0;   /* expands hit area vertically */
    cursor: pointer;
    touch-action: none;   /* prevent page scroll while dragging the scrub */
    user-select: none;
  }
  .audio-bar .scrub.unavailable { opacity: 0.4; pointer-events: none; }
  .audio-bar .scrub .scrub-track {
    height: 10px;
    background:
      repeating-linear-gradient(90deg,
        rgba(132, 204, 22, 0.13) 0 1px,
        transparent 1px 3px),
      linear-gradient(0deg, #eee7ff, #eee7ff);
    border-radius: 100px;
    position: relative;
    overflow: visible;   /* thumb should poke outside */
    transition: height 0.15s ease;
  }
  .audio-bar .scrub:hover .scrub-track,
  .audio-bar .scrub.dragging .scrub-track { height: 12px; }
  .audio-bar .scrub .fill {
    height: 100%; width: 0%;
    background: linear-gradient(90deg, var(--violet-500), var(--violet-700) 60%, var(--amber-400));
    border-radius: 100px;
    pointer-events: none;
    transition: width 0.08s linear;
    box-shadow: 0 0 10px rgba(132, 204, 22, 0.45);
  }
  .audio-bar .scrub .thumb {
    position: absolute;
    left: 0;   /* positioned via translateX in JS */
    top: 50%;
    transform: translate(-50%, -50%);
    width: 16px; height: 16px; border-radius: 50%;
    background: #fff;
    border: 2px solid var(--violet-700);
    box-shadow: 0 2px 6px rgba(76, 29, 149, 0.4);
    pointer-events: none;
    transition: transform 0.12s ease, width 0.12s ease, height 0.12s ease;
  }
  .audio-bar .scrub:hover .thumb,
  .audio-bar .scrub.dragging .thumb {
    width: 20px; height: 20px;
  }
  .audio-bar .time {
    grid-area: time;
    font-family: ui-monospace, Menlo, monospace;
    font-size: 13px; font-weight: 600;
    color: var(--ink-900);
    white-space: nowrap;
    letter-spacing: -0.01em;
    font-variant-numeric: tabular-nums;
  }
  /* Volume: speaker icon. The slider is now a vertical popup that opens
     on click (operator 2026-05-17 ticket 38wre1) instead of an inline
     horizontal slider — gives the bar more breathing room. Extra right
     padding on .vol so the speaker isn't pinned to the bar edge. */
  .audio-bar .vol {
    grid-area: vol;
    position: relative;
    display: flex; align-items: center; gap: 8px;
    padding-right: 12px;
  }
  .audio-bar .vol-btn {
    width: 36px; height: 36px; border-radius: 50%;
    border: 0; background: transparent;
    font-size: 16px; cursor: pointer;
    display: grid; place-items: center;
    color: var(--ink-700);
    transition: background 0.12s ease, transform 0.12s ease;
  }
  .audio-bar .vol-btn:hover { background: var(--violet-100); }
  .audio-bar .vol-btn:active { transform: scale(0.94); }
  .audio-bar .vol-btn.muted { color: var(--rose-500); }
  /* Vertical popup volume slider — hidden by default; .is-open toggled
     on vol-btn click. Anchored above the speaker icon. */
  .audio-bar .vol-slider {
    position: absolute;
    bottom: calc(100% + 8px); left: 50%; transform: translateX(-50%);
    width: 36px; height: 120px;
    padding: 12px 0;
    background: #fff;
    border: 1px solid var(--ink-200, #e2e8f0); border-radius: 18px;
    box-shadow: 0 16px 36px -10px rgba(15, 23, 42, 0.25);
    cursor: pointer; touch-action: none; user-select: none;
    opacity: 0; pointer-events: none;
    transition: opacity 0.15s ease, transform 0.15s ease;
  }
  .audio-bar .vol-slider.is-open {
    opacity: 1; pointer-events: auto;
    transform: translateX(-50%) translateY(0);
  }
  /* Vertical track inside the popup — 4px wide column, full height */
  .audio-bar .vol-slider .vol-track {
    width: 4px; height: 100%; border-radius: 100px;
    background: linear-gradient(0deg, #eee7ff, #eee7ff);
    position: relative; margin: 0 auto;
  }
  /* Fill grows from bottom upward */
  .audio-bar .vol-slider .vol-fill {
    width: 100%; height: 100%;
    background: linear-gradient(0deg, var(--violet-500), var(--violet-700));
    border-radius: 100px;
    position: absolute; bottom: 0; left: 0;
    transition: height 0.08s linear;
  }
  .audio-bar .vol-slider .vol-thumb {
    position: absolute;
    left: 50%; top: 0;
    transform: translate(-50%, -50%);
    width: 14px; height: 14px; border-radius: 50%;
    background: #fff; border: 2px solid var(--violet-700);
    pointer-events: none;
    transition: transform 0.12s ease;
  }
  .audio-bar .vol-slider:hover .vol-thumb,
  .audio-bar .vol-slider.dragging .vol-thumb { transform: translate(-50%, -50%) scale(1.3); }
  .audio-bar .vol.muted .vol-fill { opacity: 0.2; }
  .audio-bar .track-info { grid-area: sub; display: flex; align-items: center; gap: 8px; min-width: 0; flex-wrap: wrap; }
  .audio-bar .track-title {
    font-size: 12px; font-weight: 600;
    color: var(--ink-700);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    max-width: 280px;
  }
  .audio-bar .track-note {
    font-size: 11px;
    font-family: ui-monospace, Menlo, monospace;
    color: var(--ink-500);
    letter-spacing: 0.02em;
  }
  .audio-bar .track-note::before { content: "· "; color: var(--ink-300); }
  .audio-bar.playing .play { animation: audioPulse 2s ease-in-out infinite; }
  @keyframes audioPulse {
    0%, 100% { box-shadow: 0 10px 24px -6px rgba(132, 204, 22, 0.45), 0 0 0 0 rgba(132, 204, 22, 0.45); }
    50%      { box-shadow: 0 10px 24px -6px rgba(132, 204, 22, 0.55), 0 0 0 6px rgba(132, 204, 22, 0); }
  }
  @media (max-width: 640px) {
    /* Mobile audio-bar — scrub spans top with time tucked right;
     play-block CENTERED + vol RIGHT on row 2 (operator 2026-05-17
     hsgze9 → ka7f65 → 9jg1zd → o9vhzn). 3-col grid puts an empty
     spacer in col 1 to push play to the middle. */
    .audio-bar {
      padding: 8px 12px 8px 18px;
      column-gap: 8px; row-gap: 4px;
      border-radius: 12px;
      grid-template-columns: 1fr auto 1fr;
      grid-template-areas:
        "scrub scrub time"
        ".     play  vol"
        "sub   sub   sub";
    }
    .audio-bar .play-block { justify-self: center; }
    .audio-bar .vol { justify-self: end; }
    .audio-bar .play { width: 40px; height: 40px; font-size: 14px; }
    .audio-bar .scrub  { grid-area: scrub; }
    .audio-bar .time   { grid-area: time; text-align: right;
                         font-size: 11px !important;
                         color: var(--ink-500); white-space: nowrap; }
    .audio-bar .play-block { grid-area: play; gap: 6px !important; }
    .audio-bar .vol    { grid-area: vol; padding-right: 0 !important; }
    .audio-bar .audio-seek { width: 30px; height: 30px; }
    .audio-bar .audio-seek svg { width: 18px; height: 18px; }
    .audio-bar .audio-rate { min-width: 32px; height: 24px;
                             font-size: 10.5px; }
    /* Touch targets, visuals untouched. Measured on prod at 430px: seek
       30x30 with 82px between centres (the play button sits between them),
       so a full 44x44 fits with room to spare. The rate chip is 32x24 with
       only 8px of clear space to seek-fwd, so its box widens to 40 — half
       the gap each side — rather than overlapping the control next to it. */
    .audio-bar .audio-seek, .audio-bar .audio-rate { position: relative; }
    .audio-bar .audio-seek::after, .audio-bar .audio-rate::after {
      content: ""; position: absolute; left: 50%; top: 50%;
      transform: translate(-50%, -50%);
      height: 44px;
    }
    .audio-bar .audio-seek::after { width: 44px; }
    .audio-bar .audio-rate::after { width: 40px; }
    .audio-bar .track-title { max-width: 140px; }

    /* Operator 2026-05-17 suxnkr: drop the 'WhipScribe' wordmark on
       mobile, keep only the small 'W' logo. */
    .whip-nav .whip-brand span:not(.whip-logo):not(.whip-beta) { display: none; }
    .whip-nav .whip-brand .whip-logo {
      width: 22px !important; height: 22px !important;
      font-size: 12px !important;
    }
    /* Operator 2026-05-17 110qym: hide Notes tab on mobile. */
    #tab-notes { display: none !important; }
  }

  /* Mobile polish — give the transcript card real breathing room without
     losing the reading line. Sticky title block is also tightened so it
     doesn't dominate the viewport on phones. */
  @media (max-width: 820px) {
    .transcript { padding: 14px 16px; font-size: 15.5px; line-height: 1.75; }
    .center .center-sticky { top: 33px !important; padding: 0 0 4px !important; margin: 0 !important; }
    .center .head h1 { font-size: 13.5px; max-width: 100%; }
    .panel { padding: 12px 14px; }
    .whip-share-footer { padding: 12px 14px; font-size: 12.5px; }
    .whip-email-capture { padding: 16px; }
    .whip-email-capture form { flex-direction: column; }
    .whip-email-capture input[type="email"],
    .whip-email-capture button { flex: 1 1 auto; width: 100%; }
    aside.right { gap: 10px; }
  }
  @media (max-width: 480px) {
    .transcript { padding: 16px 14px; font-size: 15px; }
    .transcript .ts { font-size: 11px; margin-right: 6px; }
    body { padding-bottom: 130px; }
  }

  /* Mobile polish (≤420px) — operator REDO 2026-05-16.
     Targets: thicker scrollbar, smaller title/meta/body, 32px nav pills
     (icon-only), JTC docked above audio bar, hide JTC on non-transcript
     tabs, transcript bottom padding clears the audio bar, chat-support
     in-menu (top-nav CTA hidden). All scoped to ≤420px so desktop
     ≥421px stays pixel-identical. */
  @media (max-width: 420px) {
    /* G2 — thicker native scrollbar (no new sidebar/FAB). WebKit+Firefox. */
    .transcript {
      scrollbar-width: auto;
      scrollbar-color: rgba(132, 204, 22, 0.55) rgba(15, 23, 42, 0.05);
    }
    html { scrollbar-width: auto; scrollbar-color: rgba(132, 204, 22, 0.55) rgba(15, 23, 42, 0.05); }
    /* Operator 2026-05-17 ticket n5eb48: 3× thicker scrollbar on mobile. */
    html::-webkit-scrollbar { width: 30px; height: 14px; }
    html::-webkit-scrollbar-track { background: rgba(15, 23, 42, 0.05); }
    html::-webkit-scrollbar-thumb {
      background: rgba(132, 204, 22, 0.55);
      border-radius: 8px;
      border: 2px solid transparent;
      background-clip: padding-box;
    }
    html::-webkit-scrollbar-thumb:hover { background: rgba(132, 204, 22, 0.78); }

    /* Mobile title — smaller + not bold (operator 2026-05-17 ox7yve). */
    .center .head h1 {
      font-size: clamp(11px, 2.8vw, 13px);
      font-weight: 500;
      line-height: 1.22;
      max-width: 100%;
      letter-spacing: -0.005em;
      margin-bottom: 2px;
    }
    .center .head .meta-line { font-size: 9.5px; gap: 5px; }
    .center .head .meta-line .status { font-size: 9.5px; }
    /* Audio length: lighter green + bigger so the user clocks the size
       quickly. Operator 2026-05-16. */
    .center .head .meta-line #meta-duration {
      font-size: 12.5px;
      font-weight: 600;
      color: #65a30d;     /* lime-700 — lighter green, distinct from body ink */
      letter-spacing: 0.01em;
    }

    /* ygnlsk — move nav-dock INTO the .head row on mobile, smaller pills,
       prettier glyphs (↥ ● ↧). DOM relocation is in the IIFE near the
       bottom of <body>. CSS handles the inline layout when the dock is
       a child of .head. */
    .center .head { position: relative; }
    /* Operator 2026-05-17 1h1p6k: bottom value is set DYNAMICALLY by JS
       (sync-dock-to-bar) to the live audio-bar height + a small gap, so
       any layout change — 2-row / 3-row / mute popup / speed picker —
       keeps the pills clinging just above the bar instead of being
       eaten by it. Fallback value covers first paint before JS runs. */
    .center .head .transcript-nav-dock,
    .transcript-nav-dock.is-hoisted {
      position: fixed !important;
      right: 12px !important;
      bottom: var(--dock-bottom, 150px) !important;
      flex-direction: row !important;
      gap: 4px !important;
      margin: 0 !important;
      padding: 4px 6px !important;
      background: rgba(255, 255, 255, 0.96) !important;
      border: 1px solid var(--ink-200, #e2e8f0) !important;
      border-radius: 999px !important;
      box-shadow: 0 10px 24px -10px rgba(15, 23, 42, 0.3) !important;
      backdrop-filter: blur(8px);
      -webkit-backdrop-filter: blur(8px);
      top: auto !important; left: auto !important; transform: none !important;
      z-index: 45;
    }
    /* Tighten the title section bottom padding so there's less dead space
       below the nav-pills. Operator 2026-05-16. */
    .center .head { padding-bottom: 4px !important; }
    .center { padding-bottom: 6px !important; }
    .center .head .transcript-nav-dock .nav-pill {
      width: 38px; height: 28px;   /* bigger per operator 2026-05-17 rn40xu */
      gap: 0;
      border-radius: 999px;
      padding: 0;
    }
    .center .head .transcript-nav-dock .nav-pill .nav-icon svg {
      width: 14px !important; height: 14px !important;
    }
    .center .head .transcript-nav-dock .nav-pill .nav-icon {
      display: inline-flex; align-items: center; justify-content: center;
      width: 100%; height: 100%;
    }
    .center .head .transcript-nav-dock .nav-pill .nav-label { display: none; }
    /* In the inline-head layout the Now pill should not be green — match
       the other two for visual consistency. */
    .center .head .transcript-nav-dock .nav-pill.is-current {
      background: transparent;
      border-color: rgba(15, 23, 42, 0.12);
      color: inherit;
    }
    .center .head .transcript-nav-dock .nav-pill.is-current .nav-icon svg circle { fill: currentColor; }
    /* Wipe the desktop ::before overrides so the inline SVG icons render
       (the icon-only ⤒/◉/⤓ ::before rule above is for the OLD docked-right
       layout that doesn't apply once the dock moved into .head). */
    .center .head .transcript-nav-dock .nav-pill[data-nav] .nav-icon::before { content: none !important; }
    .center .head .transcript-nav-dock .nav-pill .nav-icon svg { width: 12px; height: 12px; display: block; }

    /* G6 — visibly smaller transcript body. */
    .transcript { font-size: 13px; line-height: 1.55; padding: 14px 12px; }
    .transcript .segment { padding: 4px 8px; margin-bottom: 12px; }
    .transcript .segment p { font-size: 13px; line-height: 1.55; }
    .transcript .ts { font-size: 10.5px; margin-right: 5px; }

    /* G4 — clear the fixed audio bar at the bottom. Audio bar at ≤640px
       is ~76px (12px+44px play+12px+row-gap+sub-line); add a 92px buffer
       so the last segment is reachable with ≥16px breathing room. */
    body { padding-bottom: 96px; }
    main { padding-bottom: 96px !important; }

    /* G7 — icon-only 32×32 nav pills. Hide labels; swap glyphs to
       arrow-with-bar (⤒ ⤓) + filled center dot via ::before content. */
    .transcript-nav-dock { right: 8px; gap: 6px; }
    .transcript-nav-dock .nav-pill {
      width: 32px; height: 32px;
      gap: 0;
    }
    .transcript-nav-dock .nav-pill .nav-label { display: none; }
    .transcript-nav-dock .nav-pill .nav-icon { font-size: 0; line-height: 1; }
    .transcript-nav-dock .nav-pill[data-nav="top"] .nav-icon::before {
      content: "\2912"; /* ⤒ up arrow to bar */
      font-size: 18px;
      line-height: 1;
    }
    .transcript-nav-dock .nav-pill[data-nav="now"] .nav-icon::before {
      content: "\25C9"; /* ◉ */
      font-size: 14px;
      line-height: 1;
    }
    .transcript-nav-dock .nav-pill[data-nav="end"] .nav-icon::before {
      content: "\2913"; /* ⤓ down arrow to bar */
      font-size: 18px;
      line-height: 1;
    }

    /* jo0y8v — JTC docks at right edge ABOVE the audio bar (not centered).
       Arrow ↑/↓ is set dynamically by JS based on active-segment position. */
    /* #id selector to outrank the later .jump-to-current base rule. */
    #jump-to-current.jump-to-current {
      left: auto;
      right: 12px;
      bottom: 86px;          /* flush against audio-bar top (audio-bar height = 86px) */
      transform: translateY(8px);
      padding: 5px 11px;     /* slimmer pill */
      font-size: 11.5px;
      min-height: 0;
      line-height: 1.25;
      border-radius: 999px;
    }
    #jump-to-current.jump-to-current.show { transform: translateY(0); }

    /* smh17a — hide JTC on Intelligence + Notes tabs. */
    body:not(.tab-transcript) .jump-to-current { display: none !important; }

    /* xxkjn0 — chat-support moves INTO the menu drawer. Hide top-nav
       CTA pill on mobile only; the JS-injected drawer link handles
       open-support-chat via shared-support.js delegate. */
    .whip-nav .whip-nav-cta { display: none !important; }
  }

  /* Legacy Download button — kept only so existing JS handlers fire on
     click(); operator 2026-05-17 yi0904: never paint it. */
  #dl-go { display: none !important; }
  /* Sample attribution row — operator 2026-05-17 1ps2w8: remove. */
  #sample-attribution { display: none !important; }
  /* Press-space hint (operator 2026-05-17 w0cqc6 → 5qki3r) — lives
     INSIDE the audio bar at left-bottom, not over the transcript. */
  .press-space-hint {
    position: absolute; left: 8px; bottom: 4px;
    z-index: 1;
    padding: 3px 8px;
    background: rgba(15, 23, 42, 0.40); color: #fff;
    font: 600 10px/1 ui-monospace, Menlo, monospace;
    letter-spacing: 0.04em;
    border-radius: 999px;
    pointer-events: none;
    opacity: 0.7;
    transition: opacity 0.6s ease;
  }
  .press-space-hint.is-fading { opacity: 0; }
  .press-space-hint[hidden] { display: none; }
  /* Operator 2026-05-17 1ze8ol: drop the EN lang chip + status dot —
     reduce header chrome. */
  #meta-language,
  #meta-language + .dot,
  #meta-status { display: none !important; }
  /* Operator 2026-05-17 g4ytue + 0u6z5f + 7sutx2 + xyq2vx: split .head
     into a 60% title block (title + duration) and a 40% action block
     (head-icons). Title wraps to multiple lines if long. */
  .center .head {
    display: grid !important;
    grid-template-columns: 60% 40%;     /* operator 2026-05-17 cveusj (reverted qs1wjz) */
    align-items: center;
    gap: 8px;
    flex-wrap: initial;
  }
  .center .head h1 {
    grid-column: 1;
    width: 100% !important;
    max-width: 100% !important;
    white-space: normal !important;       /* allow wrap */
    overflow: visible !important;
    text-overflow: clip !important;
    padding-left: 16px;                    /* operator 2026-05-17 wzccge */
  }
  .center .head .meta-line { grid-column: 1; padding-left: 16px; }
  .center .head .head-actions {
    grid-column: 2; grid-row: 1 / span 2;
    margin-left: auto !important;     /* align right per ticket 5fyk33 */
    justify-self: end;
    align-self: center;
    display: inline-flex; gap: 8px;
  }
  .center .head #meta-duration {
    color: #84cc16 !important;
    font-weight: 400 !important;
    font-size: 12px !important;
  }
  /* Operator 2026-05-17 ivv5wd + l1315k: hide sidebar Ask-AI panel,
     Copy + Show-timestamps legacy buttons — all proxied by .head icons. */
  aside.right .panel:has(#chat-open) { display: none !important; }
  #copy-all,
  #toggle-ts { display: none !important; }

  /* ── Empty right rail ────────────────────────────────────────────────
     Those rules, plus the owner-only Rename/Delete, can leave a .panel
     holding nothing but its own heading. On prod that is exactly what an
     anonymous visitor got: a 280px column containing the single word
     TRANSCRIPT and 60px of white (operator 2026-08-03, "the ui looks
     shitty", session 84b0485e).

     Marked by _pruneRightRail rather than :has(), because these controls
     are hidden by computed style — which a selector cannot test — not by
     the [hidden] attribute. The grid tracks are fixed widths, so hiding
     the aside alone would leave the 280px gap behind; the column has to
     be dropped from grid-template-columns too, at every breakpoint that
     declares one. */
  aside.right .panel.is-empty { display: none !important; }
  aside.right.is-empty { display: none !important; }
  /* The pager sets [hidden] when there is nothing to page, but .recent-pager
     declares display:flex, which outranks the attribute's UA rule — so two
     dead arrows rendered under an empty list on every anonymous visit. */
  .recent-pager[hidden] { display: none !important; }
  /* The hidden ATTRIBUTE loses to any class rule that sets display, and
     .view-tab sets display:flex — so `<button class="view-tab" hidden>` kept
     rendering. Notes was hidden on 2026-08-04 and was still on production
     because of exactly this. It is a documented trap in this codebase and I
     walked into it anyway; these guards make the attribute authoritative. */
  /* The hidden ATTRIBUTE loses to any class rule that sets display, and this
     page is full of components whose class sets display:flex/block. That has
     now caused three separate bugs:
       - .wts-row-link  — a "View transcript" link live 48s early with href="#"
       - .view-tab      — Notes stayed on production after being hidden
       - .whip-topup-nudge — 156px of card rendered for 345ms then collapsed,
                             which was the entire 0.83 mobile layout shift
     One global guard instead of chasing the fourth. `hidden` means hidden. */
  [hidden] { display: none !important; }

  /* Hide the modal's 'Chat with transcript ×' header when reparented
     into the inline panel — operator 2026-05-17 fy1c11. */
  #chat-inline-mount .chat-head { display: none !important; }
  /* Operator 2026-05-17 fo61am: drop the audio-bar 'Audio playback' /
     'Paused/Now playing' sub-line — track-title + track-note clutter.
     That removal stands: every decorative state #track-note carries
     ('Loading audio…', 'Ready to play', 'Now playing', 'Paused', 'Ended',
     'Buffering…') stays hidden.
     What it also hid, unintentionally, was the ONLY surface any audio
     failure writes to — 'Audio expired', 'File not found', 'Invalid file
     id'. Those went into a 0x0 box, so a visitor whose audio had expired
     saw a fully live-looking player with nothing behind it.
     markAudioUnavailable() flags the note .is-error, and only that state is
     revealed. Keyed on the marker rather than on :not(:empty) because the
     note is never empty — matching on emptiness would put the decorative
     sub-line straight back. */
  #track-title { display: none !important; }
  #track-note, .audio-bar .track-info { display: none !important; }
  .audio-bar:has(#track-note.is-error) .track-info { display: flex !important; }
  .audio-bar:has(#track-note.is-error) #track-note { display: block !important; }

  /* Single Export icon in .head + dropdown menu (operator 2026-05-17
     jzfpqd) — replaces the sidebar Export panel. */
  .head-actions { position: relative; margin-left: auto; display: inline-flex; align-items: center; gap: 4px; }
  /* Icon-only transparent action button (operator 2026-05-17 8etpzq →
     xyq2vx → qs1wjz → da5coi: bigger again + shifted left in the 40%
     column so they don't feel pinned to the page edge). */
  .head-icon-btn {
    display: inline-flex; align-items: center; justify-content: center;
    width: 38px; height: 38px;
    padding: 0;
    background: transparent;
    border: 0; border-radius: 999px;
    cursor: pointer; color: var(--ink-500, #64748b);
    transition: background 0.12s, color 0.12s, transform 0.1s;
  }
  .head-icon-btn:hover { background: rgba(132, 204, 22, 0.16); color: #15803d; }
  .head-icon-btn:active { transform: scale(0.92); }
  .head-icon-btn svg { width: 20px; height: 20px; display: block; }
  .head-action-menu {
    position: absolute;
    top: calc(100% + 8px); right: 0;
    background: #fff;
    border: 1px solid var(--ink-200, #e2e8f0); border-radius: 14px;
    box-shadow: 0 20px 44px -12px rgba(15, 23, 42, 0.28);
    padding: 18px 20px 16px;
    display: grid; grid-template-columns: repeat(2, 1fr); gap: 6px;
    min-width: 320px;
    z-index: 30;
  }
  .head-export-menu { min-width: 380px; }
  .head-copy-menu {
    min-width: 300px;
    grid-template-columns: 1fr;
    padding: 12px;
  }
  .head-menu-item {
    appearance: none;
    display: flex; align-items: center; justify-content: space-between; gap: 16px;
    width: 100%;
    padding: 10px 12px;
    border: 0; border-radius: 10px;
    background: transparent;
    color: var(--ink-900, #0f172a);
    font: 600 13px/1.25 -apple-system, BlinkMacSystemFont, "Inter", system-ui, sans-serif;
    text-align: left;
    cursor: pointer;
  }
  .head-menu-item:hover,
  .head-menu-item:focus-visible {
    outline: none;
    background: rgba(132, 204, 22, 0.14);
    color: #15803d;
  }
  .head-menu-item .label { white-space: nowrap; }
  .head-menu-item .muted { color: var(--ink-500, #64748b); font-weight: 500; text-align: right; }
  .head-icon-btn .action-caret {
    width: 12px; height: 12px;
    margin-left: -3px;
    color: currentColor;
    opacity: 0.75;
  }
  .head-ts-toggle {
    width: 38px;
    min-width: 38px;
    height: 38px;
    padding: 0;
    border: 1px solid rgba(132, 204, 22, 0.35);
    border-radius: 999px;
    color: #15803d;
    background: rgba(132, 204, 22, 0.12);
  }
  .head-ts-toggle[aria-pressed="false"] {
    color: var(--ink-500, #64748b);
    background: transparent;
    border-color: var(--ink-200, #cbd5e1);
  }
  .head-ts-toggle svg { width: 20px; height: 20px; }
  /* Top-right close × (operator 2026-05-17 ticket 43m47x). */
  .head-export-close {
    position: absolute; top: 8px; right: 10px;
    appearance: none; background: transparent; border: 0;
    cursor: pointer; color: var(--ink-500, #64748b);
    width: 32px; height: 32px; border-radius: 999px;
    font: 700 22px/1 -apple-system, BlinkMacSystemFont, "Inter", system-ui, sans-serif;
    display: inline-grid; place-items: center;
    transition: background 0.12s, color 0.12s;
  }
  .head-export-close:hover { background: var(--ink-100, #f1f5f9); color: var(--ink-900, #0f172a); }
  .head-action-menu[hidden] { display: none; }
  /* When .dl-grid mounts INSIDE the popup, drop its own grid styling so
     the parent .head-export-menu grid drives the layout. */
  .head-export-menu .dl-grid { display: contents; }

  /* Export-format icon grid — operator 2026-05-17 gd3mhq + tfks3r + sd592v:
     three sections (Transcript / Summary / All), wider/breathable popup,
     greenish accent on the section titles. */
  .dl-grid { display: grid; grid-template-columns: repeat(4, 1fr);
    gap: 6px 8px; margin: 0; }
  .dl-grid .dl-section-title {
    grid-column: 1 / -1;
    font-size: 11px; font-weight: 800; letter-spacing: 0.08em;
    text-transform: uppercase;
    color: #15803d;        /* lime/green-700 accent */
    margin: 14px 2px 4px;
    padding-bottom: 4px;
    border-bottom: 1px solid rgba(132, 204, 22, 0.25);
  }
  .dl-fmt .dl-lbl { white-space: normal; line-height: 1.15; }
  .dl-grid .dl-section-title:first-child { margin-top: 0; }
  .dl-fmt { display: inline-flex; align-items: center; gap: 8px;
    padding: 8px 10px; background: #fff;
    border: 1px solid var(--ink-200, #cbd5e1); border-radius: 8px;
    cursor: pointer; color: var(--ink-700, #334155);
    font: 600 12.5px/1 -apple-system, BlinkMacSystemFont, "Inter", system-ui, sans-serif;
    text-align: left;
    min-height: 34px;
    transition: background 0.12s, border-color 0.12s, transform 0.1s; }
  .dl-fmt:hover { background: var(--ink-50, #f1f5f9);
    border-color: var(--ink-300, #94a3b8); color: var(--ink-900, #0f172a); }
  .dl-fmt:active { transform: scale(0.97); }
  .dl-fmt .dl-ico { display: inline-grid; place-items: center; flex-shrink: 0; }
  .dl-fmt .dl-lbl { font-family: ui-monospace, Menlo, monospace; font-size: 11.5px; letter-spacing: -0.01em; }
  .dl-fmt.is-busy { opacity: 0.6; cursor: progress; }

  /* AI send-to panel bits */
  .ai-template-select { width: 100%; padding: 8px 10px; border: 1px solid var(--ink-300);
    border-radius: 8px; font: inherit; font-size: 13px; background: var(--white);
    color: var(--ink-900); cursor: pointer; margin-bottom: 6px; }
  .ai-template-select:focus { outline: none; border-color: var(--violet-500); }
  .export-row { display: flex; gap: 8px; align-items: stretch; }
  .export-row select { flex: 1; margin-bottom: 0; }
  .export-row .item { margin: 0; white-space: nowrap; }
  .ai-note { margin-top: 8px; padding: 8px 10px; background: var(--violet-50);
    border-radius: 6px; font-size: 11px; color: var(--ink-500); line-height: 1.5; }

  /* "Jump to current" pill — docks at the right edge of the main content
     column, just above the audio bar (operator 2026-05-17 cn3iaa). */
  .jump-to-current { position: fixed;
    left: auto;
    right: max(24px, calc((100vw - 1280px) / 2 + 24px));
    bottom: 96px;          /* audio-bar height + small gap */
    transform: translateY(8px);
    padding: 6px 12px; border-radius: 999px;
    border: 0; cursor: pointer;
    background: var(--violet-700); color: var(--white);
    font: 600 11.5px/1.25 -apple-system, BlinkMacSystemFont, "SF Pro", "Inter", system-ui, sans-serif;
    box-shadow: 0 8px 22px -8px rgba(0,0,0,0.35);
    opacity: 0; pointer-events: none;
    transition: opacity 0.18s ease, transform 0.18s ease;
    z-index: 30; }
  .jump-to-current.show { opacity: 1; transform: translateY(0); pointer-events: auto; }
  .jump-to-current:hover { background: var(--violet-600); }
  /* Hide when audio is paused or stopped — operator 2026-05-17 ticket
     td4uti: "should only show when the audio playing and the user is
     out of the view". JS already adds .show on out-of-view; this
     suppresses it whenever playback isn't actively going. */
  body.audio-paused .jump-to-current.show {
    opacity: 0 !important;
    pointer-events: none !important;
  }

  /* toast */
  .toast { position: fixed; left: 50%; top: 80px; transform: translateX(-50%) translateY(-20px);
    background: var(--ink-900); color: var(--white); padding: 10px 18px;
    border-radius: 10px; font-size: 13px; font-weight: 500;
    opacity: 0; pointer-events: none; transition: all 0.2s; z-index: 30;
    box-shadow: 0 10px 30px rgba(0,0,0,0.4); }
  .toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }

  /* Embed mode (?embed=1) — render the transcript + right-side actions
     only, no Whipscribe nav, no Recent-transcripts left sidebar. Used
     by the Knowledge panel to show transcripts inside its slide-in
     side modal. Activated via the `embed` class added on <html> by an
     inline script before paint. */
  /* Reserve the nav strip before shared-nav.js fills it. Measured on prod
     2026-08-04: #whip-nav is empty at first paint and mounts at ~800ms as a
     59px (desktop) / 51px (phone) sticky bar, shoving main down by exactly
     that — two of the layout shifts on this page. It is display:none under
     html.embed / html.embed-lp, and min-height on a display:none element
     reserves nothing, so those modes are unaffected. */
  #whip-nav { min-height: 59px; }
  @media (max-width: 760px) { #whip-nav { min-height: 51px; } }

  html.embed #whip-nav { display: none !important; }
  html.embed body { padding-top: 0 !important; }
  html.embed main {
    grid-template-columns: 1fr 280px !important;
    max-width: 100% !important;
    padding: 16px 20px 60px !important;
    gap: 18px !important;
  }
  html.embed aside.left { display: none !important; }
  html.embed .whip-share-footer { display: none !important; }
  @media (max-width: 760px) {
    html.embed main {
      grid-template-columns: 1fr !important;
      padding: 14px 16px 40px !important;
    }
    html.embed aside.right { position: static !important; }
  }

  /* Embed-LP mode (?embed=lp) — campaign landing-page iframe.
     Aggressively strip everything that could let the visitor navigate
     away from the iframe (per operator: "user may navigate and get
     confused"). Force mobile-style single column at every viewport so
     the right-side actions stack inside the transcript flow. Hide
     rename + delete (don't apply to a public demo). Audio player
     sticks to the top so the seek bar is always visible while the
     transcript scrolls inside the iframe. */
  /* Operator 2026-05-18: hide the global #whip-nav inside embed-lp
     iframes — the LP container already has its own brand context, and
     the nav was leaving a 55px gap that the .center-sticky gradient
     filled with bright lime above the tabs. Also collapse the
     sticky's `top: 55px` since there's no nav to clear. */
  html.embed-lp #whip-nav { display: none !important; }
  html.embed-lp body { padding-top: 0 !important; margin-top: 0 !important; }
  html.embed-lp main {
    grid-template-columns: 1fr !important;
    padding: 0 14px 24px !important;
    gap: 14px !important;
    max-width: 100% !important;
  }
  html.embed-lp .center .center-sticky,
  html.embed-lp .center .center-sticky[class] {
    top: 0 !important;
    background: #ffffff !important;
    box-shadow: 0 4px 6px -6px rgba(15, 23, 42, 0.12) !important;
    border-radius: 0 0 12px 12px !important;
    padding: 6px 0 4px !important;
  }
  /* Stop the .head from overlapping the first transcript line on
     initial render — explicit margin pushes the transcript content
     clear of the sticky head, and scroll-margin-top makes #shorts/
     anchor jumps land below the sticky. */
  html.embed-lp .transcript { padding-top: 10px !important; }
  html.embed-lp .transcript > * { scroll-margin-top: 140px !important; }
  html.embed-lp aside.right { position: static !important; }
  html.embed-lp #rename,
  html.embed-lp #delete,
  html.embed-lp .whip-share-footer,
  html.embed-lp .recent-panel,
  html.embed-lp aside.left { display: none !important; }
  /* Sticky audio at the top of the iframe scroll context so seek bar
     stays visible while user scrolls the transcript below. */
  html.embed-lp .audio-wrap,
  html.embed-lp .player-wrap,
  html.embed-lp audio,
  html.embed-lp #player {
    position: sticky !important;
    top: 0 !important;
    z-index: 5;
    background: #fff;
  }
  /* Neutralize any anchors that would navigate the iframe away from
     the current transcript surface. Lets visitors interact with the
     transcript but never escape into the parent site by accident. */
  html.embed-lp a:not([href^="#"]):not([data-keep-embed-link]) {
    pointer-events: none;
    color: inherit;
    text-decoration: none;
    cursor: default;
  }


  /* Chat with transcript — sign-in-gated modal. Sits above everything,
     does not persist server-side; conversation lives in the tab. */
  .chat-backdrop {
    position: fixed; inset: 0;
    background: rgba(15, 23, 42, 0.45);
    backdrop-filter: blur(2px);
    z-index: 60;
    display: flex; align-items: stretch; justify-content: flex-end;
  }
  .chat-backdrop[hidden] { display: none; }
  .chat-modal {
    width: min(560px, 100%);
    background: #ffffff;
    display: flex; flex-direction: column;
    box-shadow: -20px 0 60px -20px rgba(0,0,0,0.35);
    animation: chat-slide 0.18s ease-out;
  }
  @keyframes chat-slide { from { transform: translateX(20px); opacity: 0; } }
  .chat-head {
    display: flex; align-items: center; justify-content: space-between;
    padding: 14px 18px;
    border-bottom: 1px solid var(--ink-100);
    background: linear-gradient(135deg, var(--violet-50), #ffffff);
  }
  .chat-head h3 {
    margin: 0; font-size: 14px; font-weight: 800;
    display: flex; align-items: center; gap: 8px;
    color: var(--ink-900);
  }
  .chat-head .chat-beta {
    font-size: 9.5px; font-weight: 800; letter-spacing: 0.08em;
    text-transform: uppercase;
    background: var(--violet-700); color: #ffffff;
    padding: 2px 6px; border-radius: 4px;
  }
  .chat-close {
    appearance: none; background: transparent; border: 0;
    color: var(--ink-500); cursor: pointer;
    width: 32px; height: 32px; border-radius: 8px;
    font: inherit; font-size: 18px; line-height: 1;
  }
  .chat-close:hover { background: var(--ink-100); color: var(--ink-900); }
  .chat-expand {
    appearance: none; background: transparent; border: 0;
    color: var(--ink-500); cursor: pointer;
    width: 32px; height: 32px; border-radius: 8px;
    display: inline-grid; place-items: center;
    margin-right: 4px;
  }
  .chat-expand:hover { background: var(--ink-100); color: var(--ink-900); }
  .chat-expand svg { width: 16px; height: 16px; }
  .chat-head-actions { display: inline-flex; align-items: center; gap: 4px; }

  /* Expand mode — modal goes full-width with a notes pane on the LEFT
     and the chat pane on the RIGHT. Notes mirror the same content as
     the Notes tab (same localStorage key). */
  .chat-backdrop.is-expanded { align-items: stretch; justify-content: stretch; }
  .chat-backdrop.is-expanded .chat-modal {
    width: 100%; max-width: none;
    flex-direction: row;
  }
  .chat-backdrop.is-expanded .chat-modal-side {
    flex: 0 0 38%;
    min-width: 280px;
    /* Heavy green left rail so the notes pane reads as a distinct
       surface from the chat pane on its right (operator: "the notes
       should be visible with some colored boundary"). */
    border-right: 4px solid var(--violet-700, #4d7508);
    display: flex; flex-direction: column;
    background: linear-gradient(180deg, var(--violet-50, #f7fee7), var(--brand-100, #ecfccb));
    box-shadow: inset -1px 0 0 rgba(132, 204, 22, 0.45);
  }
  .chat-backdrop.is-expanded .chat-modal-main {
    flex: 1;
    display: flex; flex-direction: column;
    min-width: 0;
  }
  .chat-modal-side { display: none; }   /* hidden in default narrow mode */
  .chat-modal-main { display: flex; flex-direction: column; flex: 1; min-height: 0; }

  .chat-side-head {
    display: flex; align-items: center; justify-content: space-between;
    padding: 14px 18px;
    border-bottom: 1px solid var(--ink-100);
    background: #ffffff;
  }
  .chat-side-head h3 {
    margin: 0; font-size: 14px; font-weight: 800;
    color: var(--ink-900);
  }
  .chat-side-status {
    font: 600 11px/1.2 var(--sans, "Inter", system-ui, sans-serif);
    color: var(--ink-500);
    background: var(--ink-100);
    padding: 3px 9px; border-radius: 999px;
  }
  .chat-side-status.is-saved { color: #14532d; background: var(--violet-100, #ecfccb); }
  .chat-side-textarea {
    flex: 1;
    width: 100%;
    margin: 0;
    padding: 16px 18px;
    border: 0;
    background: transparent;
    font: 400 15px/1.7 var(--sans, "Inter", system-ui, sans-serif);
    color: var(--ink-900);
    resize: none;
    outline: none;
  }
  .chat-side-textarea:focus { background: #ffffff; }

  /* Hide ONLY the floating bubble while a visitor is reading a
     transcript — it double-stacks with the in-page "Chat with
     transcript" panel. The support PANEL itself stays reachable so
     the nav "💬 Chat Support" CTA can still open it on demand. */
  .ws-support-fab { display: none !important; }

  /* Suppress the global cookie strip on transcript views — the page
     is dense (audio bar, sticky nav, sidebar, in-page chat) and the
     bottom strip stacks on top of the audio controls. Transcript
     readers landed here from a share link; they aren't the right
     surface for first-touch consent. */
  #whip-cookie-strip { display: none !important; }
  @media (max-width: 720px) {
    .chat-backdrop.is-expanded .chat-modal { flex-direction: column; }
    .chat-backdrop.is-expanded .chat-modal-side { flex: 0 0 auto; max-height: 40vh; border-right: 0; border-bottom: 1px solid var(--ink-100); }
  }
  /* Privacy banner inside the chat modal — says plainly where the
     conversation goes and when it is deleted. Sits between the head and
     the message stream so users see it before the empty-state prompt. */
  .chat-privacy {
    display: flex; gap: 10px; align-items: flex-start;
    padding: 10px 14px;
    background: linear-gradient(90deg, #f7fee7 0%, #ecfccb 100%);
    border-bottom: 1px solid var(--violet-100);
    color: #14532d;
    font-size: 12.5px; line-height: 1.5;
    /* Operator ticket n954cf: auto-dismiss the privacy note after a
       few seconds so it doesn't squat above the chat permanently.
       Class .is-hidden is toggled by JS on a 3s timer (see view.html
       chat-mount block). */
    transition: opacity 400ms ease, max-height 400ms ease, padding 400ms ease, border-color 400ms ease;
    overflow: hidden; max-height: 120px;
  }
  .chat-privacy.is-hidden {
    opacity: 0; max-height: 0; padding-top: 0; padding-bottom: 0; border-color: transparent;
    pointer-events: none;
  }
  .chat-privacy svg {
    width: 16px; height: 16px; flex-shrink: 0;
    color: var(--violet-700);
    margin-top: 1px;
  }
  .chat-privacy strong { font-weight: 700; }
  .chat-body {
    flex: 1; overflow-y: auto;
    padding: 18px; display: flex; flex-direction: column; gap: 12px;
    background: #fafafa;
  }
  .chat-body .chat-empty {
    text-align: center; color: var(--ink-500); font-size: 13.5px;
    padding: 28px 12px; line-height: 1.55;
  }
  .chat-body .chat-empty strong { color: var(--ink-900); display: block; margin-bottom: 6px; }
  .chat-msg {
    max-width: 85%;
    padding: 10px 14px;
    border-radius: 12px;
    font-size: 14px; line-height: 1.55;
    white-space: pre-wrap; word-wrap: break-word;
  }
  .chat-msg.user {
    align-self: flex-end;
    background: var(--violet-700); color: #ffffff;
    border-bottom-right-radius: 4px;
  }
  .chat-msg.assistant {
    align-self: flex-start;
    background: #ffffff; color: var(--ink-900);
    border: 1px solid var(--ink-100);
    border-bottom-left-radius: 4px;
  }
  .chat-msg.assistant.error { border-color: #fecaca; color: #b91c1c; background: #fef2f2; }
  /* Markdown rendering for assistant messages (operator ticket sgacd9).
     Tight in-bubble spacing — long answers stay readable without
     bursting the bubble out of the chat lane. */
  .chat-msg.assistant.md { white-space: normal; }
  .chat-msg.assistant.md > *:first-child { margin-top: 0 !important; }
  .chat-msg.assistant.md > *:last-child { margin-bottom: 0 !important; }
  .chat-msg.assistant.md p { margin: 0 0 8px; }
  .chat-msg.assistant.md h2 { font-size: 16px; font-weight: 700; margin: 12px 0 6px; }
  .chat-msg.assistant.md h3 { font-size: 14.5px; font-weight: 700; margin: 10px 0 5px; }
  .chat-msg.assistant.md h4 { font-size: 13.5px; font-weight: 700; margin: 8px 0 4px; }
  .chat-msg.assistant.md ul,
  .chat-msg.assistant.md ol { margin: 6px 0 10px; padding-left: 22px; }
  .chat-msg.assistant.md li { margin: 2px 0; }
  .chat-msg.assistant.md code {
    background: #f1f5f9; padding: 1.5px 6px; border-radius: 4px;
    font-family: var(--mono, ui-monospace, Menlo, monospace);
    font-size: 12.5px; color: #334155;
  }
  .chat-msg.assistant.md pre {
    background: #0f172a; color: #e2e8f0;
    padding: 10px 12px; border-radius: 8px; overflow-x: auto;
    margin: 8px 0; font-size: 12.5px;
  }
  .chat-msg.assistant.md pre code { background: transparent; padding: 0; color: inherit; }
  .chat-msg.assistant.md a { color: var(--violet-700); text-decoration: underline; }
  .chat-msg.assistant.md img,
  .chat-msg.assistant.md svg {
    max-width: 100%; height: auto;
    border-radius: 6px; margin: 6px 0; display: block;
  }
  .chat-msg.assistant.md blockquote {
    border-left: 3px solid #cbd5e1;
    margin: 8px 0; padding: 2px 12px; color: #475569;
  }
  .chat-typing {
    align-self: flex-start;
    color: var(--ink-500); font-size: 13px;
    padding: 8px 14px;
    display: flex; align-items: center; gap: 6px;
  }
  .chat-typing .dot {
    width: 6px; height: 6px; border-radius: 50%;
    background: var(--ink-300);
    animation: chat-bounce 1.2s infinite ease-in-out both;
  }
  .chat-typing .dot:nth-child(2) { animation-delay: 0.15s; }
  .chat-typing .dot:nth-child(3) { animation-delay: 0.3s; }
  @keyframes chat-bounce {
    0%, 80%, 100% { opacity: 0.3; transform: translateY(0); }
    40% { opacity: 1; transform: translateY(-3px); }
  }
  .chat-form {
    border-top: 1px solid var(--ink-100);
    padding: 12px 14px;
    background: #ffffff;
    display: flex; gap: 10px; align-items: flex-end;
  }
  .chat-form textarea {
    flex: 1; min-height: 42px; max-height: 160px;
    padding: 10px 12px;
    font: inherit; font-size: 14px;
    border: 1px solid var(--ink-200);
    border-radius: 10px;
    resize: none;
    outline: none;
    transition: border-color 0.15s;
  }
  .chat-form textarea:focus { border-color: var(--violet-500); box-shadow: 0 0 0 3px rgba(163,220,45,0.15); }
  .chat-form button {
    appearance: none; border: 0;
    padding: 11px 18px;
    background: linear-gradient(135deg, var(--violet-700), var(--violet-800));
    color: #ffffff;
    font: inherit; font-size: 14px; font-weight: 700;
    border-radius: 10px; cursor: pointer;
  }
  .chat-form button:disabled { opacity: 0.5; cursor: not-allowed; }
  @media (max-width: 600px) {
    .chat-modal { width: 100%; }
  }

  /* ── Phone: give the screen back to the transcript ──────────────────
     Measured on prod against a real 55-minute job at 384x699 (the
     viewport PostHog recorded in session f7e8c0c3, Android Chrome):
     sticky nav 51px + sticky header 169px + fixed audio bar 99px = 319
     of 699, so 46% of the phone was permanent furniture and the reader
     saw about seven segments. Field metrics from the same session agree
     — CLS 0.204, LCP 3136ms on /view.

     The 169px header was mostly the action row wrapping onto two lines:
     .head-actions flex-wraps on a narrow screen, so the jump-to-time
     form dropped below the icons and the block went 44px → 90px. That
     form was first hidden here under 768px; on 2026-08-03 the operator
     removed it from every width, so what remains below is the rest of
     the phone treatment and the nowrap that stops a long file title
     re-creating the same two-line wrap.

     Everything kept here also gets to the 44px minimum tap target the
     icons were missing at 38x38. Net: chrome ~319px → ~250px. */
  @media (max-width: 768px) {
    /* No wrap left to do once the form is gone — keep the icons on one
       row so a long file title can never push them onto a second. */
    .center .center-sticky .head-actions { flex-wrap: nowrap; row-gap: 0; }
    .center .head .head-icon-btn { width: 44px; height: 44px; }
    /* The head is a fixed 60%/40% split. Three 44px targets plus gaps need
       148px, which 40% stops covering below ~370px — at 360px the icon row
       ran 10px past the right edge and the whole page scrolled sideways.
       (Self-inflicted: they were 38px until the tap-target fix earlier
       today.) Give the actions exactly what they need and let the title
       take the remainder, rather than shrinking a target back under 44px. */
    .center .head {
      grid-template-columns: 1fr auto !important;
      gap: 6px;
    }
    /* Tabs at 14px padding rendered 47px tall. Padding alone is the wrong
       lever — 11px measured 41px once the line box was accounted for, i.e.
       under the tap minimum. Pin the height and centre the label instead,
       so it stays exactly 44px whatever the font metrics do. */
    .view-tabs .view-tab {
      padding: 0 16px !important;
      min-height: 44px;
      display: inline-flex; align-items: center;
    }
    /* A file title long enough to wrap costs a whole line of reading
       area on every scroll. One line, ellipsed — the full name is still
       in the tooltip and the export filename. */
    .center .head h1 {
      white-space: nowrap !important;
      overflow: hidden !important;
      text-overflow: ellipsis !important;
    }
  }

  /* ── Line editing ────────────────────────────────────────────── */
  .seg-acts { display: inline-flex; gap: 4px; margin-left: 8px; vertical-align: baseline;
    opacity: 0; transition: opacity .15s; }
  .segment:hover .seg-acts, .segment:focus-within .seg-acts { opacity: 1; }
  @media (hover: none) { .seg-acts { opacity: .45; } }
  .seg-act { appearance: none; border: 1px solid var(--ink-200, #e2e8f0); background: #fff;
    color: var(--ink-500, #64748b); border-radius: 6px; font-size: 11px; line-height: 1;
    padding: 3px 6px; cursor: pointer; }
  .seg-act:hover { color: var(--ink-900, #0f172a); border-color: var(--ink-300, #cbd5e1); }
  .seg-act-del:hover { color: #b91c1c; border-color: #f3d4d4; background: #fdf2f2; }
  .segment.seg-deleted .seg-text { text-decoration: line-through; opacity: .4; }
  .segment.seg-deleted .ts { opacity: .4; }
  .seg-editor { display: block; width: 100%; box-sizing: border-box; margin-top: 6px;
    padding: 10px 12px; border: 1px solid #a3dc2d; border-radius: 10px;
    font: inherit; line-height: 1.6; color: var(--ink-900, #0f172a); resize: vertical;
    background: #fff; outline: none; }
  .seg-editor-row { display: flex; gap: 8px; justify-content: flex-end; margin-top: 6px; }
  .seg-editor-row button { appearance: none; cursor: pointer; font-size: 13px; font-weight: 600;
    padding: 6px 12px; border-radius: 8px; border: 1px solid var(--ink-200, #e2e8f0);
    background: #fff; color: var(--ink-700, #334155); }
  .seg-editor-row .seg-ed-ok { background: #14532d; border-color: #14532d; color: #fff; }
  #seg-save-bar { position: fixed; left: 50%; transform: translateX(-50%);
    bottom: calc(var(--dock-bottom, 4px) + 66px); z-index: 9000;
    display: flex; align-items: center; gap: 12px; padding: 10px 16px;
    background: #0f172a; color: #e2e8f0; border-radius: 999px;
    box-shadow: 0 10px 30px -10px rgba(15,23,42,.5); font-size: 13.5px; }
  #seg-save-bar .n { font-weight: 600; }
  #seg-save-bar button { appearance: none; cursor: pointer; font-size: 13px; font-weight: 700;
    padding: 7px 14px; border-radius: 999px; border: 1px solid #334155;
    background: transparent; color: #cbd5e1; }
  #seg-save-bar button.primary { background: linear-gradient(135deg,#c5f44f,#a3dc2d);
    color: #14532d; border-color: transparent; }
  #seg-save-bar button:disabled { opacity: .4; cursor: default; }
  #seg-save-bar #seg-undo, #seg-save-bar #seg-redo { border-color: transparent; padding: 7px 10px; }

  /* Chrome-extension promo line in the right rail */
  #v-ext-promo { display: flex; align-items: center; gap: 6px; padding: 10px 12px; }
  #v-ext-promo .v-ext-a { flex: 1; font-size: 12.5px; color: var(--ink-500, #64748b);
    text-decoration: none; line-height: 1.45; }
  #v-ext-promo .v-ext-a b { color: var(--green-700, #4d7c0f); font-weight: 700; }
  #v-ext-promo .v-ext-a:hover b { text-decoration: underline; }
  #v-ext-promo button { appearance: none; border: 0; background: none; cursor: pointer;
    color: #94a3b8; font-size: 13px; line-height: 1; padding: 2px 4px; border-radius: 5px; }
  #v-ext-promo button:hover { color: #0f172a; background: #f1f5f9; }
