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

    :root {
      /* Chip geometry — single source of truth (mirrors modules/geometry.js,
         bound by tests/geometry-css.test.mjs). Everything derives from the two
         base knobs --row-height and --chip-border-w; see features/chip-geometry.md. */
      --row-height: 36px;
      --chip-border-w: 2px;
      --date-part-w: 38px;
      --notch-outward-reclaim: 8px;
      --notch-inward-reclaim: 12px;
      --sliver-min-w: 30px;
      /* A chip is the row height minus a fixed 10px vertical inset. */
      --chip-height: calc(var(--row-height) - 10px);
      /* A chain connector is a full-height circle, so its radius is half the height. */
      --connector-r: calc(var(--chip-height) / 2);
      /* The chip-end circle: author identicon diameter, emojeval wheel base
         size, and the full size of an emojeval-only chip. */
      --circle-size: var(--chip-height);
      /* Extend-chain ghost body width — a NARROW phantom "next chained chip":
         half a minimal chip's date-part, just enough to hold the centred +, much
         less wide than the full date-part it used to be. Its dashed half-circle cap
         adds --connector-r on top (features/chip-edge-decoration.md, "The
         extend-chain ghost"). */
      --extend-ghost-w: calc(var(--date-part-w) / 2);
      /* The selection ring replaces the border, so it matches its weight. */
      --sel-ring-w: var(--chip-border-w);
      /* Name cell — the membership circle's diameter is the cell's box height
         (the row height minus the cell's top+bottom margin), so it is slightly
         larger than a chip's author circle since the cell is taller. The cell's
         left border caps it as a semicircle (radius = half the diameter), exactly
         like a chip's capsule border wraps its author circle. */
      --name-cell-vmargin: 3px;
      --name-cell-circle: calc(var(--row-height) - 2 * var(--name-cell-vmargin));
    }

    /* Dark mode (default) */
    :root, [data-theme="dark"] {
      --bg: #1c1e26;
      --bg-surface: #22252e;
      --bg-elevated: #2a2d38;
      --text: #cdd3de;
      --text-dim: #6e7a91;
      --text-muted: #3d4455;
      --accent: #7aa2f7;
      --accent-green: #73c991;
      --accent-green-muted: #2d4a37;
      --accent-yellow: #e0af68;
      --accent-orange: #d19a66;
      --accent-red: #e06c75;
      --accent-purple: #a78bdb;
      /* Multi-select accent: a teal distinct from both the blue single-select
         accent and the purple narration hue (features/selection.md). */
      --accent-multi: #4fd6be;
      --border: #2e3240;
      --selection: #2a3a5c;
      /* Opaque, not rgba(): a hovered truncated/date-only chip's revealed
         .chip-text flap uses `background: inherit`, escaping the chip's own
         box to paint over neighboring chips (see chip-rendering.md "Chip
         hover hit-zone"). An alpha-blended background can't fully occlude
         whatever is underneath it, so it must be pre-composited to an opaque
         color here. color-mix() against --bg-elevated (srgb, same formula as
         alpha compositing over that surface) keeps the same visual tint the
         previous rgba() values produced. */
      --chip-overdue-bg: color-mix(in srgb, var(--accent-red) 20%, var(--bg-elevated));
      --chip-due-soon-bg: color-mix(in srgb, var(--accent-yellow) 15%, var(--bg-elevated));
      --narration-bg: rgba(167, 139, 219, 0.15);
      --narration-border: rgba(167, 139, 219, 0.4);
    }

    /* Light mode */
    [data-theme="light"] {
      --bg: #ffffff;
      --bg-surface: #f5f5f7;
      --bg-elevated: #ecedf0;
      --text: #1d1d1f;
      --text-dim: #6e6e73;
      --text-muted: #c7c7cc;
      --accent: #3478f6;
      --accent-green: #34a853;
      --accent-green-muted: #dff5e3;
      --accent-yellow: #c08b30;
      --accent-orange: #b07630;
      --accent-red: #d93025;
      --accent-purple: #7b61c4;
      --accent-multi: #0d9488;
      --border: #d2d2d7;
      --selection: #d6e4ff;
      --chip-overdue-bg: color-mix(in srgb, var(--accent-red) 10%, var(--bg-elevated));
      --chip-due-soon-bg: color-mix(in srgb, var(--accent-yellow) 10%, var(--bg-elevated));
      --narration-bg: rgba(123, 97, 196, 0.1);
      --narration-border: rgba(123, 97, 196, 0.3);
    }

    /* Respect system preference */
    @media (prefers-color-scheme: light) {
      :root:not([data-theme="dark"]) {
        --bg: #ffffff;
        --bg-surface: #f5f5f7;
        --bg-elevated: #ecedf0;
        --text: #1d1d1f;
        --text-dim: #6e6e73;
        --text-muted: #c7c7cc;
        --accent: #3478f6;
        --accent-green: #34a853;
        --accent-green-muted: #dff5e3;
        --accent-yellow: #c08b30;
        --accent-orange: #b07630;
        --accent-red: #d93025;
        --accent-purple: #7b61c4;
        --accent-multi: #0d9488;
        --border: #d2d2d7;
        --selection: #d6e4ff;
        --chip-overdue-bg: color-mix(in srgb, var(--accent-red) 10%, var(--bg-elevated));
        --chip-due-soon-bg: color-mix(in srgb, var(--accent-yellow) 10%, var(--bg-elevated));
        --narration-bg: rgba(123, 97, 196, 0.1);
        --narration-border: rgba(123, 97, 196, 0.3);
      }
    }

    body {
      font-family: -apple-system, BlinkMacSystemFont, 'Inter', 'Segoe UI', sans-serif;
      background: var(--bg);
      color: var(--text);
      height: 100vh;
      overflow: hidden;
    }

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

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

    /* --- Toolbar --- */
    .toolbar-header {
      padding: 6px 16px;
      background: var(--bg-surface);
      border-bottom: 1px solid var(--border);
      display: flex;
      align-items: center;
      gap: 12px;
      flex-shrink: 0;
    }
    .toolbar-header h1 { font-size: 16px; font-weight: 600; color: var(--accent); }
    .toolbar-actions, .toolbar-timeline { display: flex; gap: 4px; }
    .toolbar-timeline { margin-left: auto; align-items: center; }
    .toolbar-header button {
      background: var(--bg-elevated);
      color: var(--text);
      border: 1px solid var(--border);
      padding: 4px 10px;
      border-radius: 4px;
      cursor: pointer;
      font-size: 12px;
    }
    .toolbar-header button:hover { background: var(--selection); }
    .zoom-label { font-size: 11px; color: var(--text-dim); min-width: 30px; text-align: center; }

    /* --- Grid container --- */
    .grid-container {
      flex: 1;
      display: flex;
      flex-direction: column;
      overflow: hidden;
      position: relative;
    }

    /* --- Column shadow overlays (full-height, independent of rows) --- */
    .column-shadow-overlay {
      position: absolute;
      top: 0;
      bottom: 0;
      pointer-events: none;
      z-index: 4;
    }
    .column-shadow-overlay.present-shadow {
      box-shadow: -3px 0 6px -2px rgba(0,0,0,0.3), 3px 0 6px -2px rgba(0,0,0,0.3);
    }
    .column-shadow-overlay.name-shadow {
      box-shadow: 3px 0 6px -2px rgba(0,0,0,0.3);
    }
    @media (prefers-color-scheme: light) {
      :root:not([data-theme="dark"]) .column-shadow-overlay.present-shadow {
        box-shadow: -3px 0 6px -2px rgba(0,0,0,0.15), 3px 0 6px -2px rgba(0,0,0,0.15);
      }
      :root:not([data-theme="dark"]) .column-shadow-overlay.name-shadow {
        box-shadow: 3px 0 6px -2px rgba(0,0,0,0.15);
      }
    }
    [data-theme="light"] .column-shadow-overlay.present-shadow {
      box-shadow: -3px 0 6px -2px rgba(0,0,0,0.15), 3px 0 6px -2px rgba(0,0,0,0.15);
    }
    [data-theme="light"] .column-shadow-overlay.name-shadow {
      box-shadow: 3px 0 6px -2px rgba(0,0,0,0.15);
    }

    /* --- Timeline header --- */
    .timeline-header {
      height: 24px;
      display: flex;
      background: var(--bg-surface);
      border-bottom: 1px solid var(--border);
      flex-shrink: 0;
    }
    .col-header {
      font-size: 10px;
      text-transform: lowercase;
      letter-spacing: 0.3px;
      color: var(--text-dim);
      padding: 0 10px;
      display: flex;
      align-items: center;
      flex-shrink: 0;
      border-right: 1px solid var(--border);
    }
    .col-header.present-header {
      background: var(--bg-surface);
      z-index: 3;
      padding: 0;
      border-right: none;
    }
    .tick {
      position: absolute;
      top: 0;
      height: 100%;
      display: flex;
      align-items: center;
      font-size: 10px;
      color: var(--text-dim);
      border-left: 1px solid var(--text-muted);
      padding-left: 3px;
      white-space: nowrap;
    }
    .today-marker {
      position: absolute;
      top: 0;
      width: 2px;
      height: 100%;
      background: var(--accent-orange);
      z-index: 2;
    }

    /* --- Grid body --- */
    .grid-body {
      flex: 1;
      overflow-y: auto;
      overflow-x: hidden;
      min-height: 0;
    }

    .grid-body-inner {
      position: relative;
    }

    .empty-state {
      padding: 60px 40px;
      text-align: center;
      color: var(--text-dim);
    }
    .empty-title { font-size: 18px; margin-bottom: 8px; }
    .empty-state kbd {
      background: var(--bg-elevated);
      padding: 2px 6px;
      border-radius: 3px;
      border: 1px solid var(--border);
    }

    /* --- Row --- */
    .grid-row {
      display: flex;
      height: var(--row-height);
      border-bottom: 1px solid var(--border);
      position: relative;
    }
    .grid-row { overflow: visible; }
    .grid-row:hover { background: color-mix(in srgb, var(--accent) 4%, transparent); }
    .grid-row.selected { background: color-mix(in srgb, var(--accent) 8%, transparent); }
    .grid-row.done { opacity: 0.55; }
    .grid-row.done:hover { opacity: 0.8; }
    .grid-row.drop-target {
      outline: 2px solid var(--accent);
      outline-offset: -2px;
      background: rgba(139, 233, 253, 0.08);
    }
    .drop-action-label {
      position: absolute;
      right: 12px;
      top: 50%;
      transform: translateY(-50%);
      font-size: 12px;
      font-weight: 600;
      color: var(--accent);
      background: var(--bg-elevated);
      padding: 2px 10px;
      border-radius: 4px;
      border: 1px solid var(--accent);
      z-index: 20;
      pointer-events: none;
    }

    /* --- Name cell (chip style) --- */
    .name-cell {
      position: relative;
      min-width: 0;
      padding: 0 6px;
      /* Nesting indent flows through --name-cell-indent (set inline per depth) so
         a member row can add the circle reclaim on top of it (rule below). */
      padding-left: calc(6px + var(--name-cell-indent, 0px));
      margin: var(--name-cell-vmargin) 4px;
      display: flex;
      align-items: center;
      gap: 6px;
      font-size: 13px;
      cursor: pointer;
      overflow: hidden;
      white-space: nowrap;
      user-select: none;
      background: transparent;
      border: 1px solid color-mix(in srgb, var(--text-dim) 30%, var(--border));
      border-radius: 4px;
    }
    .name-cell.sel {
      outline: 2px solid white;
      z-index: 10;
      cursor: text;
    }
    .name-cell.inactive { color: var(--text-dim); }
    .name-cell .dot { font-size: 10px; flex-shrink: 0; }
    .name-cell .name-text {
      overflow: hidden;
      text-overflow: ellipsis;
      flex: 1;
      outline: none;
      border-radius: 2px;
      padding: 1px 3px;
      margin: -1px -3px;
      align-self: stretch;
      display: flex;
      align-items: center;
    }
    .name-cell .name-text:focus {
      outline: none;
    }
    .name-cell .score-badge {
      font-size: 11px;
      color: var(--accent-yellow);
      font-weight: 600;
      flex-shrink: 0;
    }
    .name-cell { cursor: pointer; }
    .name-cell[draggable="true"] { cursor: pointer; }
    .name-cell.dragging { opacity: 0.5; outline: 2px dashed var(--accent); }

    .grid-row.add-row { border-bottom: none; cursor: pointer; }
    .grid-row.add-row:hover { background: rgba(255,255,255,0.03); }

    /* --- Nesting fold-out (see features/nesting-foldout.md) ---
       The disclosure control is an unmistakable chevron at the name cell's RIGHT
       edge (name-text's flex:1 pushes it there), with a comfortable 24px hit
       target. Collapsed points right; expanded rotates a quarter turn down. */
    .name-cell .fold-chevron {
      flex-shrink: 0;
      width: 24px;
      height: 24px;
      margin: 0 -4px 0 0;
      padding: 0;
      display: inline-flex;
      align-items: center;
      justify-content: center;
      border: none;
      background: transparent;
      border-radius: 6px;
      color: var(--text-muted);
      cursor: pointer;
      user-select: none;
      -webkit-appearance: none;
      appearance: none;
    }
    .name-cell .fold-chevron .fold-chevron-glyph {
      font-size: 18px;
      line-height: 1;
      font-weight: 700;
      display: block;
      transition: transform 0.15s ease;
      transform: rotate(0deg);
    }
    .name-cell .fold-chevron:hover {
      color: var(--accent);
      background: color-mix(in srgb, var(--accent) 14%, transparent);
    }
    .name-cell .fold-chevron.expanded { color: var(--text); }
    .name-cell .fold-chevron.expanded .fold-chevron-glyph { transform: rotate(90deg); }
    /* Nested sub-rows read as secondary to their parent's top-level row. */
    .name-cell.nested { border-style: dashed; opacity: 0.92; }

    /* See-also link row (cycle truncation): name only, no timeline. */
    .grid-row.see-also-row { pointer-events: auto; }
    .name-cell.see-also-cell {
      cursor: pointer;
      border-style: dotted;
      color: var(--text-muted);
      gap: 6px;
    }
    .name-cell.see-also-cell:hover { color: var(--accent); border-color: var(--accent); }
    .name-cell .see-also-arrow { font-size: 11px; flex-shrink: 0; }
    .name-cell .see-also-text {
      overflow: hidden;
      text-overflow: ellipsis;
      font-style: italic;
    }

    /* --- Membership circle (features/admission.md, "Invites and requests on the
       row"). ONE always-visible affordance at the name cell's identity LEFT edge:
       a FULL circular roster identicon (diameter = --name-cell-circle, the cell
       height), sitting flush at the left edge with the cell's LEFT BORDER curving
       around it as a semicircular cap — exactly how a timeline chip's capsule
       border wraps its author circle. Segmented by roster size. Clicking opens the
       one members/requests/invite popover. --- */
    /* The cell's left edge becomes a semicircular cap around the circle, and it
       reserves one circle of left padding (on top of any nesting indent) so the
       name text clears it — the chip author-circle reclaim, mirrored to the left. */
    .name-cell:has(.name-members) {
      border-top-left-radius: calc(var(--name-cell-circle) / 2);
      border-bottom-left-radius: calc(var(--name-cell-circle) / 2);
      padding-left: calc(var(--name-cell-circle) + var(--name-cell-indent, 0px));
    }
    .name-members {
      position: absolute;
      left: 0;
      top: 50%;
      transform: translateY(-50%);
      width: var(--name-cell-circle);
      height: var(--name-cell-circle);
      display: inline-flex;
      align-items: center;
      justify-content: center;
      z-index: 1;
    }
    .name-semicircle {
      display: block;
      width: var(--name-cell-circle);
      height: var(--name-cell-circle);
      padding: 0;
      border: 0;
      background: transparent;
      cursor: pointer;
      line-height: 0;
      color: var(--text-dim); /* currentColor for the +N marker / request ring */
    }
    .name-semicircle svg { display: block; width: 100%; height: 100%; border-radius: 50%; }
    /* Pending-request marker: the disc-outline ring (drawn in the SVG as
       currentColor) turns accent, plus a small count badge. */
    .name-semicircle.has-requests { color: var(--accent); }
    .name-semicircle-count {
      position: absolute;
      top: -3px;
      right: -5px;
      min-width: 14px;
      height: 14px;
      padding: 0 3px;
      border-radius: 7px;
      background: var(--accent);
      color: #fff;
      font-size: 9px;
      font-weight: 700;
      line-height: 14px;
      text-align: center;
      box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
    }

    /* Portaled popovers (members + requests + invite). */
    .name-pop-backdrop { position: fixed; inset: 0; z-index: 9990; }
    .name-pop {
      position: fixed;
      display: flex;
      flex-direction: column;
      gap: 6px;
      width: 260px;
      max-width: 90vw;
      padding: 10px;
      border: 1px solid var(--border);
      border-radius: 8px;
      background: var(--bg-elevated);
      color: var(--text);
      box-shadow: 0 8px 28px rgba(0, 0, 0, 0.4);
      font-size: 12px;
    }
    .name-pop-title { font-weight: 600; }
    .name-pop-subtitle { font-weight: 600; color: var(--text-dim); font-size: 11px; }
    .name-pop-sep { height: 0; border-top: 1px solid var(--border); margin: 2px 0; }
    .name-pop-status { color: var(--text-dim); font-size: 11px; }
    .name-pop textarea {
      width: 100%;
      font: 11px monospace;
      resize: vertical;
      border: 1px solid var(--border);
      border-radius: 4px;
      background: var(--bg-surface);
      color: var(--text);
      padding: 4px;
      box-sizing: border-box;
    }
    .name-pop-btn {
      align-self: flex-start;
      padding: 4px 10px;
      border: 1px solid var(--border);
      border-radius: 6px;
      background: var(--bg-surface);
      color: var(--text);
      cursor: pointer;
      font: inherit;
    }
    .name-pop-btn:hover { border-color: var(--accent); color: var(--accent); }
    .name-pop .join-request,
    .name-pop .dv-member {
      display: flex;
      align-items: center;
      gap: 6px;
    }
    .name-pop .dv-member-face {
      width: 18px;
      height: 18px;
      border-radius: 50%;
      overflow: hidden;
      flex: 0 0 auto;
    }
    .name-pop .dv-member-face svg { display: block; width: 100%; height: 100%; }
    .name-pop .dv-member-did {
      flex: 1;
      overflow: hidden;
      text-overflow: ellipsis;
      font: 11px monospace;
    }
    .name-pop .join-request .name-pop-btn,
    .name-pop .dv-member .name-pop-btn { align-self: auto; padding: 2px 8px; }

    /* --- The assignment picker (features/assignment.md): the chip circle's
       participant popover, in the members-popover family — identicon rows
       over the admission roster plus Unassign. --- */
    .assign-pop { width: 220px; gap: 2px; }
    .assign-row {
      display: flex;
      align-items: center;
      gap: 6px;
      padding: 4px 6px;
      border: none;
      border-radius: 6px;
      background: transparent;
      color: var(--text);
      cursor: pointer;
      font: inherit;
      text-align: left;
    }
    .assign-row:hover { background: var(--bg-surface); }
    .assign-row.current { background: var(--bg-surface); }
    .assign-row .assign-current { color: var(--accent); flex: 0 0 auto; }
    .assign-row.assign-unassign { color: var(--text-dim); }

    /* --- Empty state: zero visible narratives (features/admission.md,
       "Accepting a link is global"). A create CTA + a join-link paste field. --- */
    .empty-state {
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: 10px;
      padding: 32px 16px;
      color: var(--text-dim);
    }
    .empty-state-title { font-size: 15px; font-weight: 600; color: var(--text); }
    .empty-state-create,
    .empty-state-join,
    .empty-state-import {
      padding: 6px 14px;
      border: 1px solid var(--accent);
      border-radius: 6px;
      background: var(--bg-surface);
      color: var(--text);
      cursor: pointer;
      font: inherit;
    }
    .empty-state-import { display: inline-block; }
    .empty-state-create:hover,
    .empty-state-join:hover,
    .empty-state-import:hover { border-color: var(--accent); color: var(--accent); }
    .empty-state-or { font-size: 11px; }
    /* File-picker inputs ride inside a styled <label>; the raw input is hidden. */
    .empty-state-import-input, .name-import-input { display: none; }
    /* The popover import affordance is a <label> styled as a name-pop-btn. */
    .name-pop-btn.import-backup-btn { cursor: pointer; }
    .empty-state-paste {
      width: 280px;
      max-width: 90vw;
      font: 11px monospace;
      resize: vertical;
      border: 1px solid var(--border);
      border-radius: 4px;
      background: var(--bg-surface);
      color: var(--text);
      padding: 4px;
    }
    .empty-state-msg { font-size: 11px; }

    /* --- Global join confirm dialog (a document paste that parsed as a link). --- */
    .join-confirm-backdrop {
      position: fixed;
      inset: 0;
      z-index: 9995;
      display: grid;
      place-items: center;
      background: rgba(0, 0, 0, 0.4);
    }
    .join-confirm {
      display: flex;
      flex-direction: column;
      gap: 10px;
      width: 320px;
      max-width: 90vw;
      padding: 16px;
      border: 1px solid var(--border);
      border-radius: 10px;
      background: var(--bg-elevated);
      color: var(--text);
      box-shadow: 0 12px 36px rgba(0, 0, 0, 0.5);
    }
    .join-confirm-title { font-weight: 600; font-size: 14px; }
    .join-confirm-body { font-size: 12px; color: var(--text-dim); }
    .join-confirm-actions { display: flex; justify-content: flex-end; gap: 8px; }
    .join-confirm-go { border-color: var(--accent); color: var(--accent); }

    /* --- Narrate-and-present summary prompt (the cross-row drop, D2). --- */
    .narrate-prompt-backdrop {
      position: fixed;
      inset: 0;
      z-index: 9995;
      display: grid;
      place-items: center;
      background: rgba(0, 0, 0, 0.4);
    }
    .narrate-prompt {
      display: flex;
      flex-direction: column;
      gap: 10px;
      width: 320px;
      max-width: 90vw;
      padding: 16px;
      border: 1px solid var(--border);
      border-radius: 10px;
      background: var(--bg-elevated);
      color: var(--text);
      box-shadow: 0 12px 36px rgba(0, 0, 0, 0.5);
    }
    .narrate-prompt-title { font-weight: 600; font-size: 14px; }
    .narrate-prompt-body { font-size: 12px; color: var(--text-dim); }
    .narrate-prompt-input {
      font-size: 13px;
      padding: 6px 8px;
      border: 1px solid var(--border);
      border-radius: 6px;
      background: var(--bg-surface);
      color: var(--text);
    }
    .narrate-prompt-input:focus { outline: none; border-color: var(--accent); }
    .narrate-prompt-actions { display: flex; justify-content: flex-end; gap: 8px; }
    .narrate-prompt-go { border-color: var(--accent); color: var(--accent); }

    /* --- Next cell (combined [text | date] chip) --- */
    .present-cell {
      display: flex;
      align-items: center;
      margin: 3px 4px;
      overflow: visible;
      flex-shrink: 0;
      font-size: 12px;
    }
    .present-cell.empty {
      border: 1px solid transparent;
      border-radius: 4px;
    }
    .present-cell.empty:hover,
    .present-cell.empty.sel {
      border-color: color-mix(in srgb, var(--text-dim) 30%, var(--border));
    }
    .present-cell.sel .entry-chip {
      outline: 2px solid var(--chip-border, var(--border));
      z-index: 10;
    }
    .present-cell.sel .entry-chip.active { outline-color: var(--text); }
    /* Next chip: entry-chip inside next-cell needs static positioning */
    .present-cell .entry-chip {
      position: relative;
      width: 100%;
      height: var(--chip-height);
      background: var(--bg);
      --chip-border: color-mix(in srgb, var(--text-dim) 30%, var(--border));
      border: 2px solid var(--chip-border);
    }
    /* Override the type-specific top:4px offsets (which apply to absolute chips in timeline areas).
       .present-cell .entry-chip.TYPE has specificity 0,3,0 > .entry-chip.TYPE at 0,2,0, so top:0
       here wins and the chip aligns vertically with timeline chips in the same row. */
    .present-cell .entry-chip.intent {
      background: var(--bg);
      --chip-border: color-mix(in srgb, var(--text-dim) 30%, var(--border));
      border: 2px solid var(--chip-border);
      top: 0;
    }
    .present-cell .entry-chip.active { border-color: var(--text); }
    .present-cell .entry-chip.overdue { background: var(--chip-overdue-bg); border-color: var(--accent-red); }
    .present-cell .entry-chip.due-soon { background: var(--chip-due-soon-bg); border-color: var(--accent-yellow); }
    .chip-text {
      flex: 1;
      padding: 0 4px;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
      color: var(--text);
      outline: none;
      align-self: stretch;
      display: flex;
      align-items: center;
      cursor: pointer;
      position: relative;
    }
    /* Reclaim unused space in sequential chip layout.
       has-connector (middle/final): parent's convex covers the concave cutout
         regardless of truncation — widen text 12px left by shrinking chip's padding-left.
         The :not(.truncated) scope was removed: layout uses an unconditional
         inward reclaim (assumes padding-left: 1px), so the CSS must match.
       The former has-notch outward text-widening (margin-right: -8px over the
       convex bump) is RETIRED: the author circle permanently occupies that
       space (features/chip-rendering.md, "The author circle chrome"). */
    .entry-chip.has-connector:not(.mirrored) { padding-left: 1px; }
    .chip-when {
      padding: 0 3px;
      font-size: 11px;
      color: var(--text-dim);
      border-right: 1px solid color-mix(in srgb, var(--chip-border, var(--border)) 30%, transparent);
      white-space: nowrap;
      flex-shrink: 0;
      cursor: pointer;
      align-self: stretch;
      display: flex;
      align-items: center;
      min-width: 38px;
      justify-content: center;
    }
    .entry-chip.mirrored .chip-when {
      border-right: none;
      border-left: 1px solid color-mix(in srgb, var(--chip-border, var(--border)) 30%, transparent);
    }
    .chip-when.empty { color: var(--text-muted); }
    .present-cell:hover .chip-when.empty,
    .present-cell.sel .chip-when.empty { opacity: 1; }
    .present-cell.sel .chip-text { cursor: text; }
    .present-cell.sel .chip-when { cursor: text; }
    .present-cell .entry-chip.active .chip-when { color: var(--text); }
    .present-cell .entry-chip.overdue .chip-when { color: var(--accent-red); }
    .present-cell .entry-chip.due-soon .chip-when { color: var(--accent-yellow); }
    .create-intent-hint {
      font-size: 11px;
      color: var(--text-muted);
      opacity: 0;
      cursor: pointer;
      outline: none;
    }
    .present-cell:hover .create-intent-hint,
    .present-cell.sel .create-intent-hint { opacity: 1; }
    .create-intent-hint[contenteditable="true"] { opacity: 1; color: var(--text); }
    .when-input {
      font-size: inherit;
      font-family: inherit;
      background: none;
      color: var(--text-dim);
      border: 0;
      padding: 0;
      margin: 0;
      width: 32px;
      height: 100%;
      box-sizing: border-box;
      outline: 0;
      text-align: center;
      -webkit-appearance: none;
      appearance: none;
    }

    /* --- Column dividers (draggable) --- */
    .col-divider {
      position: absolute;
      right: -3px;
      top: 0;
      width: 6px;
      height: 100%;
      cursor: col-resize;
      z-index: 10;
    }
    .present-divider-left {
      left: -3px;
      right: auto;
    }
    .present-divider-right {
      right: -3px;
    }
    .col-divider:hover {
      background: var(--accent);
      opacity: 0.3;
    }

    /* --- Entries area --- */
    .future-area {
      position: relative;
      overflow-x: clip;
      overflow-y: visible;
      flex-shrink: 0;
    }
    .future-area .entry-chip {
      background: var(--bg);
      --chip-border: color-mix(in srgb, var(--text-dim) 30%, var(--border));
      border: 2px solid var(--chip-border);
    }
    .future-area .entry-chip.intent {
      background: var(--bg-elevated);
      --chip-border: color-mix(in srgb, var(--text-dim) 30%, var(--border));
      border: 2px solid var(--chip-border);
    }
    .past-area {
      position: relative;
      overflow-x: clip;
      overflow-y: visible;
      flex-shrink: 0;
    }

    /* Edge indicators for off-screen chips */
    .edge-indicator {
      position: absolute;
      top: 4px;
      height: var(--chip-height);
      cursor: pointer;
      z-index: 25;
      transition: opacity 0.15s;
    }
    .edge-indicator:hover { opacity: 0.9; }
    .edge-indicator.left { left: 0; }
    .edge-indicator.right { right: 0; }
    .edge-sliver {
      position: absolute;
      top: 0;
      bottom: 0;
      width: 8px;
      border: 1px solid;
      background: var(--bg-elevated);
      box-sizing: border-box;
    }
    /* Hide the outer border so the sliver looks like it continues off-screen */
    .edge-indicator.left .edge-sliver { border-left: none; border-radius: 0 3px 3px 0; }
    .edge-indicator.right .edge-sliver { border-right: none; border-radius: 3px 0 0 2px; }
    .edge-sliver.completed, .edge-sliver.event {
      border-color: var(--accent-green);
      background: var(--bg);
    }
    .edge-sliver.intent {
      border-color: color-mix(in srgb, var(--text-dim) 30%, var(--border));
      background: var(--bg);
    }
    .edge-sliver.narration {
      border-color: var(--narration-border);
      background: var(--narration-bg);
    }
    .sliver-count {
      position: absolute;
      top: 0;
      bottom: 0;
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 10px;
      font-weight: 600;
      color: var(--text-dim);
      line-height: 1;
    }
    .edge-indicator.left .sliver-count { left: 0; }
    .edge-indicator.right .sliver-count { right: 0; }
    .edge-indicator.left .edge-sliver.count-bg { border-right: none; }
    .edge-indicator.right .edge-sliver.count-bg { border-left: none; }

    /* --- Entry chip --- */
    /* The capsule is the whole edge language (features/chip-edge-decoration.md,
       "The capsule is the whole language"): every free chip end is a full-height
       CONNECTOR_R cap, so the base box is a stadium (both ends capped). The ONLY
       corners that flatten are the two at a chain junction, where the convex bump
       or concave cut — the same CONNECTOR_R circle — supplies the round instead
       (the has-notch / has-connector overrides below). There are no 4px flat ends. */
    .entry-chip {
      position: absolute;
      top: 3px;
      display: flex;
      flex-direction: row;
      align-items: center;
      font-size: 12px;
      border-radius: var(--connector-r);
      cursor: pointer;
      user-select: none;
      z-index: 2;
      overflow: hidden;
    }
    /* Hover: chip stays at constrained width, text overflows visually */
    /* Extend hover zone to cover the inter-chip gap */
    .entry-chip::after {
      content: '';
      position: absolute;
      top: 0; bottom: 0;
      left: 100%;
      width: 5px;
    }
    .entry-chip.mirrored::after {
      left: auto;
      right: 100%;
    }
    /* Mouse hover: chip stays constrained, text overflows visually */
    .entry-chip:hover {
      z-index: 15;
      overflow: visible;
    }
    /* Chain z-index boosting on hover is handled entirely by the reactive
       hoveredChainInfo signal in TimelineArea.jsx — no CSS sibling rule needed.
       The ~ combinator selected chips in the wrong direction for future area DOM order. */
    /* Hide the convex bump (::before) on hover for TRUNCATED has-notch chips.
       When a truncated chip expands on hover, the bump would sit at the wrong position
       (it's anchored to left: -13px of the chip box, which hasn't moved).
       The concave cut (has-connector, ::after border arc) stays — it's mask-based and
       always correct regardless of chip width. Middle chips (has-notch + has-connector)
       only hide ::before (the bump); their ::after (concave border arc) is kept.
       The .truncated scoping excludes present cell chips (fixed-width, never truncated). */
    .future-area .entry-chip.has-notch.truncated:not(.sel):hover::before,
    .past-area .entry-chip.has-notch.truncated:not(.sel):hover::before {
      display: none;
    }
    /* Non-mirrored truncated reveal: a clean IN-FLOW reveal — the chip box
       itself grows on hover (width:auto, the hover-expansion family), so the
       text needs only to escape its flex clamp. The legacy "flap" chrome
       (border: inherit + squared corners + background patch) is retired: it
       closed the text with a straight border just left of the circle
       (features/assignment.md, "Circle edge junctions"). The mirrored
       equivalent was converted with the author circle (#43); this is its
       non-mirrored twin. */
    .entry-chip.truncated:not(.mirrored):not(.sel):hover .chip-text {
      flex: none;
      width: max-content;
      overflow: visible;
      pointer-events: none;
    }
    .entry-chip.mirrored.truncated:hover .chip-when,
    .entry-chip.date-only.mirrored:hover .chip-when {
      margin-left: auto;
    }
    .entry-chip.truncated:hover .chip-text[contenteditable="true"] {
      pointer-events: auto;
    }
    /* Date-only reveal: same clean in-flow reveal as the truncated rule above
       (no flap chrome at the circle junction). */
    .entry-chip.date-only:not(.mirrored):not(.sel):hover .chip-text {
      display: flex;
      flex: none;
      width: max-content;
      overflow: visible;
      pointer-events: none;
    }
    .entry-chip.date-only:hover .chip-text[contenteditable="true"] {
      pointer-events: auto;
    }
    .entry-chip.date-only:not(.mirrored):hover .chip-when { border-right: 1px solid color-mix(in srgb, var(--text-dim) 20%, var(--border)); }
    .entry-chip.date-only.mirrored:hover .chip-when { border-left: 1px solid color-mix(in srgb, var(--text-dim) 20%, var(--border)); }
    .entry-chip.hide-date:not(.has-connector):hover .chip-when { display: flex; }
    /* Keyboard selection: chip expands fully (no flicker concern) */
    .entry-chip.sel {
      z-index: 15;
      outline: 2px solid var(--chip-border, var(--border));
      border-color: transparent !important;
      overflow: visible;
    }
    /* has-connector chips: outline draws a rectangular line on the left side
       (where the concave cutout is). Replace with box-shadow on top/bottom only. */
    .entry-chip.has-connector:not(.mirrored).sel {
      outline: none;
      box-shadow: 0 calc(-1 * var(--sel-ring-w)) 0 0 var(--chip-border, var(--border)),
                  0 var(--sel-ring-w) 0 0 var(--chip-border, var(--border));
    }
    /* .has-circle no longer touches border-radius: every chip end is already a
       CONNECTOR_R cap from the base rule (features/chip-edge-decoration.md, "The
       capsule is the whole language"), so the author circle simply nests inside
       the cap the box already has. .has-circle now carries ONLY the circle's
       padding reservation (see "The author circle" block below). The former
       selected-anchor left-cap special-case is gone too — the has-notch override
       below caps the anchor's free (left) edge in every state, selected or not. */
    .past-area .entry-chip.sel {
      width: auto !important;
    }
    .future-area .entry-chip.sel {
      width: auto !important;
    }
    .past-area .entry-chip.sel.has-circle {
      max-width: fit-content;
    }
    .past-area .entry-chip.sel .chip-text { overflow: hidden; white-space: nowrap; }
    .future-area .entry-chip.sel .chip-text { overflow: visible; white-space: nowrap; }
    .entry-chip.sel .chip-text { cursor: text; }
    .entry-chip.sel .chip-text.placeholder::after {
      content: '...';
      color: var(--text-dim);
      pointer-events: none;
    }
    .entry-chip.sel .chip-when { cursor: text; }
    .entry-chip.date-only.sel .chip-text { display: flex; }
    .entry-chip.date-only.sel .chip-when { border-right: 1px solid color-mix(in srgb, var(--text-dim) 20%, var(--border)); }
    .entry-chip.hide-date:not(.has-connector).sel .chip-when { display: flex; }
    .entry-chip.dragging {
      background: transparent !important;
      border: 1px dotted var(--border) !important;
      color: transparent !important;
      outline: none !important;
    }
    .entry-chip.dragging.drag-snapped {
      background: inherit !important;
      border: 2px solid var(--chip-border, var(--border)) !important;
      color: inherit !important;
      outline: none !important;
    }
    .entry-chip.dragging .chip-when {
      color: transparent !important;
      border-right-color: transparent !important;
    }
    .entry-chip.dragging.drag-snapped .chip-when {
      color: inherit !important;
      border-right-color: inherit !important;
    }
    .entry-chip.dragging .chip-text { color: transparent !important; }
    .entry-chip.dragging.drag-snapped .chip-text { color: inherit !important; }
    .entry-chip.dragging .chip-badge { display: none; }
    .entry-chip.dragging.drag-snapped .chip-badge { display: inherit; }
    .entry-chip.dragging.drag-present {
      opacity: 0.5;
      background: inherit !important;
      border: 2px solid var(--chip-border, var(--border)) !important;
      color: inherit !important;
      outline: 2px dashed var(--accent);
    }
    .entry-chip.dragging.drag-present .chip-when { color: var(--text-dim); border-right-color: color-mix(in srgb, var(--chip-border, var(--border)) 30%, transparent); }
    .entry-chip.dragging.drag-present .chip-text { color: var(--text); }
    .entry-chip { cursor: pointer; }

    /* chip-text and chip-when shared between next-chip and entry-chip */
    .entry-chip .chip-text {
      flex: 1;
      min-width: 0;
      padding: 0 4px;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
      outline: none;
    }
    /* Hide text when chip is too narrow — date-only mode */
    .entry-chip.date-only .chip-text { display: none; }
    .entry-chip.date-only .chip-when { border-right: none; }
    /* Hide date when chip is in its natural day column (day view) */
    .entry-chip.hide-date .chip-when { display: none; }
    .entry-chip.hide-date .chip-when + .chip-badge { right: -2px; }

    /* Chain connector — circular (semicircle) connectors linking sequential chain chips.
       Chip height is 26px; connector radius R = 13px (full chip height).
       ARROW_PROTRUSION = GAP magnitude = 13px so circles interlock flush (net gap = 0).

       Convex (has-notch): a semicircular bump on the outward edge (RIGHT — away
         from present; chains live in the future area, the non-mirrored side).
         Implemented as ::before pseudo-element: 26×26px circle, positioned so its centre
         sits at the chip's outward edge, protruding 13px beyond it.
       Concave (has-connector): a semicircular indentation on the inward edge (LEFT —
         facing present). Implemented via mask-image: cuts a 13px semicircle from the
         inward edge. A ::before pseudo-element behind the chip provides the border arc.

       For future-area chips (non-mirrored): outward = RIGHT, inward = LEFT.
       For present-cell chips: outward = RIGHT (toward future area). */

    /* --- Shared overflow rule: convex bump must paint outside the chip box --- */
    .entry-chip.has-notch:not(.mirrored),
    .present-cell .entry-chip.has-notch {
      overflow: visible;
    }

    /* --- Convex bump (has-notch): full-height circle on the outward (RIGHT) edge ---
       ::before = the visible bump (border + background fill).
       Centre of the 26×26px circle sits at the chip's right edge (right: -13px),
       so it protrudes 13px to the right beyond the chip box.
       The chip's own right border is hidden (border-right: none) to prevent a double
       border where the chip edge and the circle border overlap. */
    .entry-chip.has-notch:not(.mirrored),
    .present-cell .entry-chip.has-notch {
      border-right-width: 0;
    }
    .entry-chip.has-notch:not(.mirrored)::before,
    .present-cell .entry-chip.has-notch::before {
      content: '';
      position: absolute;
      right: calc(-1 * var(--connector-r));
      top: -1px;
      width: var(--connector-r);
      height: calc(100% + 2px);
      border-radius: 0 var(--connector-r) var(--connector-r) 0;
      /* Inherit only the solid bg color, not the radial-gradient
         that paints the row color in the concave cutout. */
      background-color: inherit;
      background-image: none;
      border: var(--chip-border-w) solid var(--chip-border, var(--border));
      border-left: none;
      border-color: inherit;
      border-left-color: transparent;
      pointer-events: none;
      box-sizing: border-box;
    }
    /* Present cell: z-index 26 so the convex bump renders above sliver indicators (z-index 25) */
    /* Present-cell anchor bump containment (features/chip-edge-decoration.md,
       "present-cell chips all cap both ends, and the anchor's bump aligns inside
       the column" — absorbs task #15). Narrow the chip by one CONNECTOR_R so its
       outward bump (::before at right: -connector-r) tips exactly at the column's
       inner right edge — the same pixel a plain present chip's right cap sits at.
       A bump and a cap are the same CONNECTOR_R semicircle, so the anchor's right
       end is now pixel-identical to every other present chip's, and never
       protrudes past the present divider. */
    .present-cell .entry-chip.has-notch {
      width: calc(100% - var(--connector-r));
      z-index: 26;
    }
    .present-cell .entry-chip.has-notch.active::before { border-color: var(--text); }

    /* --- Concave cut (has-connector): semicircle indent on the inward (LEFT) edge ---
       mask-image cuts a 13px radius semicircle from the left edge of the chip.
       The mask centre is at (0%, 50%) — exactly at the left edge, mid-height.
       transparent inside the circle, black (opaque) outside.
       The chip's own left border is hidden (border-left: none) to prevent a double
       border where the chip edge and the ::after border arc overlap.
       The 12.5px soft stop reduces aliasing on non-integer DPR screens. */
    .entry-chip.has-connector:not(.mirrored) {
      border-left-color: transparent;
      /* Concave visual: the convex semicircle from the neighbor chip overlaps the
         chip overlaps this area, providing the visual interlocking. The ::after
         border arc marks the concave boundary. */
    }
    /* Base has-connector ::after: no border needed — the neighbor's convex
       semicircle covers this area entirely. Selected state below provides
       the visible thick concave border arc. */
    .entry-chip.has-connector:not(.mirrored)::after {
      content: '';
      position: absolute;
      left: -12px;
      top: -1px;
      width: 12px;
      height: calc(100% + 2px);
      border-radius: 12px 0 0 12px;
      background: transparent;
      pointer-events: none;
      box-sizing: border-box;
    }

    /* Selected state — the selection ring.
       Selection must NOT change the chip's box or its connector geometry (see
       features/chip-geometry.md and tests/browser/selection-no-layout-shift). The
       convex bump (::before) and concave arc (::after) above are NEVER touched by
       selection — no rule in this section targets them. The ring is a separate,
       real DOM element (.notch-ring-clip / .connector-ring-clip, rendered by
       Chip.jsx only while selected) layered on top, so the existing pseudo-elements
       stay pixel-identical whether selected or not.

       Each connector is a half-disc: a public curved edge (rounded, where the ring
       must show) and one flat edge that is purely internal, flush with the chip's
       own box (border-radius 0 on that side). A box-shadow ring on that half-disc
       shape bleeds --sel-ring-w past the flat edge too — INTO the chip, on top of
       the text. clip-path can flush-cut that edge, but at the fractional chip
       x-positions this app's panning produces, clip-path's mask is rasterized in a
       separate compositing pass from the box-shadow paint and the two don't agree
       to the sub-pixel: a ~1px seam of the ring color survives, visible as a line
       through the text (verified empirically — see
       tests/browser/chain-notch-selection-ring-bleed.browser.mjs history).
       overflow: hidden clips in the SAME paint pass as the box-shadow it's
       clipping and leaves no such seam — verified the same way. That's the only
       reason this needs a real element: pseudo-elements can't be clipped by their
       own overflow (an element's overflow never clips its own box-shadow, only a
       DESCENDANT's), so the ring-drawing shape must be a child of a real clipping
       ancestor, and the connectors already use their one pseudo-element slot for
       the base bump/arc fill. */
    .notch-ring-clip,
    .connector-ring-clip {
      position: absolute;
      pointer-events: none;
      overflow: hidden;
    }
    /* Convex bump ring: outward (public) edge is RIGHT, flush (internal) edge is
       LEFT — the wrapper gets --sel-ring-w of extra room on the right/top/bottom
       for the ring to bleed into, and zero on the left so it clips flush. */
    .entry-chip.has-notch:not(.mirrored) .notch-ring-clip,
    .present-cell .entry-chip.has-notch .notch-ring-clip {
      right: calc(-1 * (var(--connector-r) + var(--sel-ring-w)));
      top: calc(-1px - var(--sel-ring-w));
      width: calc(var(--connector-r) + var(--sel-ring-w));
      height: calc(100% + 2px + 2 * var(--sel-ring-w));
    }
    /* The ring-drawing pseudo recreates the base ::before's exact geometry
       (right/top/width/height/border-radius) relative to the wrapper, so its
       visible edge lands exactly where the base bump's own edge is; box-shadow
       adds the ring outside that, which the wrapper then clips flush on the left. */
    .entry-chip.has-notch:not(.mirrored) .notch-ring-clip::before,
    .present-cell .entry-chip.has-notch .notch-ring-clip::before {
      content: '';
      position: absolute;
      right: var(--sel-ring-w);
      top: var(--sel-ring-w);
      width: var(--connector-r);
      height: calc(100% - 2 * var(--sel-ring-w));
      border-radius: 0 var(--connector-r) var(--connector-r) 0;
      box-shadow: 0 0 0 var(--sel-ring-w) var(--chip-border, var(--border));
    }
    /* Concave arc ring: outward (public) edge is LEFT, flush (internal) edge is
       RIGHT — mirror of the above. The arc's own unselected geometry (12px) is not
       tied to --connector-r (13px) — a pre-existing un-tokenized literal in the
       base ::after rule above — so the ring matches that same 12px, not the
       notch's 13px, to stay concentric with the arc it rings. */
    .entry-chip.has-connector:not(.mirrored) .connector-ring-clip {
      left: calc(-1 * (12px + var(--sel-ring-w)));
      top: calc(-1px - var(--sel-ring-w));
      width: calc(12px + var(--sel-ring-w));
      height: calc(100% + 2px + 2 * var(--sel-ring-w));
    }
    .entry-chip.has-connector:not(.mirrored) .connector-ring-clip::before {
      content: '';
      position: absolute;
      left: var(--sel-ring-w);
      top: var(--sel-ring-w);
      width: 12px;
      height: calc(100% - 2 * var(--sel-ring-w));
      border-radius: 12px 0 0 12px;
      box-shadow: 0 0 0 var(--sel-ring-w) var(--chip-border, var(--border));
    }

    /* Junction edges: the ONE place a cap is replaced by the bump/cut, so those
       two corners drop to 0 while the free edge keeps its CONNECTOR_R cap from the
       base rule (features/chip-edge-decoration.md, "one edge, one terminator").
       These hold in every state — the free-edge cap does not depend on selection,
       so there are no separate .sel radius rules for the anchor any more. */
    /* has-notch anchor: RIGHT edge is the convex bump (flat box corners there);
       LEFT (free) edge keeps its cap. */
    .entry-chip.has-notch:not(.mirrored) { border-radius: var(--connector-r) 0 0 var(--connector-r); }
    .present-cell .entry-chip.has-notch { border-radius: var(--connector-r) 0 0 var(--connector-r); }
    /* has-connector tail: LEFT edge is the concave cut (flat box corners there);
       RIGHT (free) edge keeps its cap. */
    .entry-chip.has-connector:not(.mirrored) { border-radius: 0 var(--connector-r) var(--connector-r) 0; padding-left: var(--connector-r); }
    .entry-chip.has-connector:not(.mirrored).sel { border-radius: 0 var(--connector-r) var(--connector-r) 0; }
    /* has-notch + has-connector (mid-chain): both edges are junctions, so both
       ends' box corners are flat — the bump and the cut supply every round. */
    .entry-chip.has-notch.has-connector:not(.mirrored) { border-radius: 0; padding-left: var(--connector-r); }
    .entry-chip.has-notch.has-connector:not(.mirrored).sel { border-radius: 0; }

    /* Extend-chain ghost — the promise of the next chained chip, drawn as the
       next chip: a dashed phantom chip (top+bottom dashed edges), a centred +
       in its body, closed by a dashed CONNECTOR_R half-circle cap:
           [chip◯) ┈ + ┈ ◗
       It renders in the selected chip's RIGHT overflow (left:100%), so it never
       enters the chip's idealWidth/nx — the chip keeps its ordinary has-circle
       capsule cap, with no extend-specific corner treatment. One orientation:
       extend only ever protrudes RIGHT (away from present), on future-area
       intents and the present cell. The dashes reuse the .next-ghost dash
       language (1.5px dashed, muted); the + is token-coloured in the subordinate
       accent. The ghost is dashed-by-design chrome and, like every dotted
       affordance, never gains a box-shadow selection ring (a ring cannot render
       dashed — it would paint a second, solid outline alongside the dotted one;
       features/chip-edge-decoration.md, "the selection ring wraps solid chrome
       only"), so there is deliberately no .sel rule below. */
    .extend-chain-connector {
      position: absolute;
      left: 100%;
      top: 0;
      height: 100%;
      width: calc(var(--extend-ghost-w) + var(--connector-r));
      cursor: pointer;
      z-index: 4;
    }
    /* ::before = the phantom chip's two horizontal dashed edges (top + bottom),
       spanning the body only; the cap closes the right end. */
    .extend-chain-connector::before {
      content: '';
      position: absolute;
      left: 0;
      top: 0;
      width: var(--extend-ghost-w);
      height: 100%;
      border-top: 1.5px dashed var(--text-muted);
      border-bottom: 1.5px dashed var(--text-muted);
      box-sizing: border-box;
      pointer-events: none;
    }
    /* ::after = the dashed half-circle cap closing the phantom chip. The same
       CONNECTOR_R semicircle a real chain link's convex bump draws, dashed. */
    .extend-chain-connector::after {
      content: '';
      position: absolute;
      left: var(--extend-ghost-w);
      top: 0;
      width: var(--connector-r);
      height: 100%;
      border: 1.5px dashed var(--text-muted);
      border-left: none;
      border-radius: 0 var(--connector-r) var(--connector-r) 0;
      box-sizing: border-box;
      pointer-events: none;
    }
    /* The + symbol, centred in the phantom body. At rest it takes the ghost's own
       OUTLINE colour (--text-muted, same as the dashes) — subordinate, not the
       accent (features/chip-edge-decoration.md, "The extend-chain ghost"). */
    .extend-ghost-plus {
      position: absolute;
      left: 0;
      top: 0;
      width: var(--extend-ghost-w);
      height: 100%;
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 15px;
      line-height: 1;
      color: var(--text-muted);
      pointer-events: none;
    }
    /* Hover: the whole ghost brightens to accent (dashes AND the + symbol). */
    .extend-chain-connector:hover::before,
    .extend-chain-connector:hover::after {
      border-color: var(--accent);
    }
    .extend-chain-connector:hover .extend-ghost-plus {
      color: var(--accent);
    }
    /* Present cell: the ghost stays INSIDE the column — it does not protrude past
       the present divider the way the future-area variant overflows into the empty
       future area. Reserve the ghost's full width at the chip's right so the ghost
       fills that freed slice, its cap flush with the column's inner right edge
       (features/chip-edge-decoration.md, "Present-cell containment"). */
    .present-cell .entry-chip.sel.has-extend {
      width: calc(100% - var(--extend-ghost-w) - var(--connector-r));
    }
    .entry-chip .chip-when {
      padding: 0 3px;
      font-size: 11px;
      color: var(--text-dim);
      white-space: nowrap;
      flex-shrink: 0;
    }

    /* Type colors */
    .entry-chip.intent {
      background: var(--bg-elevated);
      color: var(--accent);
      --chip-border: var(--border);
      border: 2px solid var(--chip-border);
      height: var(--chip-height);
      top: 4px;
    }
    .entry-chip.completed,
    .entry-chip.event {
      background: var(--bg);
      color: var(--accent-green);
      --chip-border: var(--accent-green);
      border: 2px solid var(--chip-border);
      height: var(--chip-height);
      top: 4px;
    }
    .entry-chip.narration {
      background: var(--narration-bg);
      color: var(--accent-purple);
      --chip-border: var(--narration-border);
      border: 2px solid var(--chip-border);
      height: var(--chip-height);
      top: 4px;
      text-align: left;
      padding-left: 6px;
      padding-right: 6px;
    }
    .entry-chip.narration:hover,
    .entry-chip.narration.sel {
      background: color-mix(in srgb, var(--accent-purple) 25%, var(--bg));
    }
    .entry-chip.narration:hover .chip-text,
    .entry-chip.narration.sel .chip-text {
      background: color-mix(in srgb, var(--accent-purple) 25%, var(--bg));
      border-color: var(--narration-border);
    }
    .entry-chip:hover .chip-badge { display: none; }
    /* The badge protrudes past the chip's own border (top:-6px, right:-6px —
       see .chip-badge below). It's shown exactly in the resting state (hidden on
       hover just above; .sel already gets overflow:visible from the selection
       rules). Without this, the base .entry-chip{overflow:hidden} clips the
       protruding rim to a sliver instead of a full circle. */
    .entry-chip.has-badge:not(.sel) {
      overflow: visible;
    }
    /* Same reason for the behind-the-source badge: let its protruding rim paint
       outside the chip box (features/presentations.md, "Presented-chip staleness"). */
    .entry-chip.has-source-behind:not(.sel) {
      overflow: visible;
    }
    .entry-chip.narration > .chip-date {
      display: block;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
    }
    .chip-badge {
      position: absolute;
      top: -6px;
      right: -6px;
      background: var(--bg);
      color: var(--accent-purple);
      border: 1.5px solid var(--accent-purple);
      border-radius: 50%;
      font-size: 9px;
      font-weight: bold;
      width: 16px;
      height: 16px;
      display: flex;
      align-items: center;
      justify-content: center;
      line-height: 1;
    }
    .past-area.has-ghost {
      overflow-x: visible;
    }
    .entry-chip.redate-ghost {
      z-index: 20;
      pointer-events: none;
      height: var(--chip-height);
      top: 4px;
    }
    .entry-chip.drag-ghost {
      background: var(--bg);
      border: 1px dotted var(--border);
      color: var(--text);
    }
    /* Ghost outline showing where the next-action is planned */
    .next-ghost {
      position: absolute;
      top: 4px;
      height: var(--chip-height);
      background: transparent;
      border: 1.5px dashed var(--text-muted);
      border-radius: 4px;
      cursor: grab;
      z-index: 1;
    }
    .next-ghost.active {
      border-color: var(--text);
    }
    .next-ghost.overdue {
      border-color: var(--accent-red);
    }
    .next-ghost.due-soon {
      border-color: var(--accent-yellow);
    }
    .ghost-connector {
      position: absolute;
      top: calc(var(--row-height) / 2);
      height: 0;
      border-top: 1px dashed var(--border);
      pointer-events: none;
      z-index: 0;
    }
    .ghost-connector.active {
      border-top-color: var(--text);
    }
    .ghost-connector.overdue {
      border-top-color: var(--accent-red);
    }
    .ghost-connector.due-soon {
      border-top-color: var(--accent-yellow);
    }
    .entry-chip.redate-source {
      background: transparent !important;
      color: transparent !important;
      border: 2px dashed var(--text-muted) !important;
    }
    .entry-chip.redate-source .chip-date,
    .entry-chip.redate-source .chip-badge {
      visibility: hidden;
    }
    .entry-chip.presented {
      border-style: dotted;
      opacity: 0.8;
    }
    /* Sealed content this device holds no key for (features/narrative-encryption.md):
       dotted outline, muted, a lock glyph where the text would be. Edit
       affordances are gated in Chip.jsx; the cursor mirrors that. */
    .entry-chip.locked {
      border-style: dotted;
      border-color: color-mix(in srgb, var(--text-dim) 45%, var(--border));
      opacity: 0.85;
    }
    .chip-lock {
      flex: 1;
      display: flex;
      align-items: center;
      justify-content: center;
      align-self: stretch;
      padding: 0 4px;
      font-size: 10px;
      color: var(--text-dim);
      cursor: default;
    }
    .name-cell.locked .name-text { color: var(--text-dim); }
    .name-lock {
      font-size: 10px;
      color: var(--text-dim);
      margin-right: 2px;
    }
    /* Read-only narrative (features/admission.md): this device is not a
       co-author on the chain — a subtle eye badge on the name cell; the
       chips themselves gate editing behaviourally (no visual change). */
    .name-cell.readonly .name-text { color: var(--text-dim); font-style: italic; }
    .name-readonly {
      font-size: 10px;
      color: var(--text-dim);
      margin-right: 2px;
      cursor: default;
    }
    /* Multi-selected chips are unmistakable and distinct from the white single
       active-selection outline: a ring + fill tint in the multi-select accent
       (features/selection.md). Token-driven, no !important. */
    .entry-chip.multi-sel {
      outline: 2px solid var(--accent-multi);
      outline-offset: 1px;
      background: color-mix(in srgb, var(--accent-multi) 22%, var(--bg-elevated));
      border-color: var(--accent-multi);
      box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent-multi) 45%, transparent);
      z-index: 8;
    }
    /* Narrate hint pill (features/selection.md, "The narrate hint"): a small
       floating cue shown only while >=2 chips are multi-selected. */
    .narrate-hint {
      position: absolute;
      transform: translate(-50%, 0);
      z-index: 40;
      pointer-events: none;
      display: inline-flex;
      align-items: center;
      gap: 5px;
      padding: 3px 10px;
      font-size: 11px;
      white-space: nowrap;
      color: var(--text);
      background: color-mix(in srgb, var(--accent-multi) 20%, var(--bg-elevated));
      border: 1px solid var(--accent-multi);
      border-radius: 11px;
      box-shadow: 0 2px 6px rgba(0,0,0,0.25);
    }
    .narrate-hint .narrate-hint-key {
      font-weight: 700;
      color: var(--accent-multi);
    }
    .narrate-hint .narrate-hint-sep { opacity: 0.5; }

    /* Time-range band (features/selection.md): the visual for a shift+lasso
       selection — clearly visible but subordinate to the chips painted over it.
       Draggable to present the span (Window lens). Token-driven. */
    .time-range-band {
      position: absolute;
      top: 3px;
      bottom: 3px;
      z-index: 0;
      background: color-mix(in srgb, var(--accent-multi) 14%, transparent);
      border-left: 2px solid var(--accent-multi);
      border-right: 2px solid var(--accent-multi);
      border-radius: 3px;
      cursor: grab;
    }
    .time-range-band:active { cursor: grabbing; }

    /* Narration staleness nudge (features/narrations.md): a small dot in the
       narration (purple) accent, top-right of the chip. Subtle by design. */
    .entry-chip .narration-stale-dot {
      position: absolute;
      top: -3px;
      right: -3px;
      width: 7px;
      height: 7px;
      border-radius: 50%;
      background: var(--accent-purple);
      border: 1px solid var(--bg-elevated);
      z-index: 16;
      pointer-events: none;
    }
    /* Behind-the-source badge (features/presentations.md, "Presented-chip
       staleness"; features/chip-rendering.md): the numeric count on a presented
       chip whose source advanced past the citation. Absolute overlay — never a
       layout input (nx/width/truncation unchanged). Top-LEFT so it clears the
       top-right window dot and covered-count badge; warning/yellow accent, quiet. */
    .entry-chip .source-behind-badge {
      position: absolute;
      top: -6px;
      left: -6px;
      min-width: 15px;
      height: 15px;
      padding: 0 3px;
      display: flex;
      align-items: center;
      justify-content: center;
      border-radius: 8px;
      background: var(--bg);
      border: 1.5px solid var(--accent-yellow);
      color: var(--accent-yellow);
      font-size: 9px;
      font-weight: bold;
      line-height: 1;
      z-index: 16;
      pointer-events: none;
    }
    .entry-chip.overdue {
      background: var(--chip-overdue-bg);
      color: var(--accent-red);
      border-color: var(--accent-red);
    }
    .entry-chip.due-soon {
      background: var(--chip-due-soon-bg);
      color: var(--accent-yellow);
      border-color: var(--accent-yellow);
    }
    /* Intent-specific overdue/due-soon: higher specificity (0,3,0) to beat
       .future-area .entry-chip.intent (0,2,1) which resets border/background. */
    .entry-chip.intent.overdue {
      background: var(--chip-overdue-bg);
      border-color: var(--accent-red);
    }
    .entry-chip.intent.due-soon {
      background: var(--chip-due-soon-bg);
      border-color: var(--accent-yellow);
    }
    .entry-chip.intent.active {
      border-color: var(--text);
      --chip-border: var(--text);
    }
    /* Timing date-label colors for intent chips in ALL areas (not just .present-cell). */
    .entry-chip.intent.overdue .chip-when { color: var(--accent-red); }
    .entry-chip.intent.due-soon .chip-when { color: var(--accent-yellow); }
    .entry-chip.intent.active .chip-when { color: var(--text); }

    /* --- The author circle (+ emojeval wheel during a drag) on chips ---
       Every real chip carries .has-circle (except a resting emojeval-only chip,
       which IS a circle): the cap end reserves one circle of padding for the
       always-visible author identicon, and the emojeval wheel canvas mounts in the
       same .chip-circle anchor during a drag. The cap SHAPE is already the base
       chip's own end (features/chip-edge-decoration.md, "The author-circle capsule
       is not a separate variant"), so .has-circle sets ONLY the padding that makes
       room for the circle — it never restates a border-radius.
       See features/chip-rendering.md ("The chip circle") and emojeval.md. */
    .entry-chip.has-circle {
      padding-right: var(--circle-size); /* room for the circle inside the cap end */
    }
    .entry-chip.mirrored:not(.has-connector).has-circle {
      padding-right: 0;
      padding-left: var(--circle-size);
    }
    .entry-chip:not(.mirrored).has-connector.has-circle {
      padding-right: var(--circle-size);
    }
    /* has-notch anchor + circle: the identicon sits on the RIGHT over the bump
       (right: -13px — see .chip-circle rule below), so text only needs to clear
       the connector radius, not a whole circle. The box's right corners are already
       flat from the has-notch rule (the bump is the cap); nothing to restate here. */
    .entry-chip.has-notch:not(.mirrored):not(.has-connector).has-circle {
      padding-right: var(--connector-r);
    }
    /* Mid-chain (has-notch + has-connector) + circle: same right-over-bump padding;
       both ends already flat-cornered from the mid-chain rule. */
    .entry-chip.has-notch.has-connector:not(.mirrored).has-circle {
      padding-right: var(--connector-r);
    }
    .entry-chip.has-notch:not(.mirrored).has-circle .chip-circle {
      right: calc(-1 * var(--connector-r));
      left: auto;
    }
    /* Hovered chips with emojeval need width:auto to let padding expand them.
       Emojeval-only chips are excluded — they stay as a 26px circle on hover. */
    .entry-chip:not(.sel):not(.emojeval-only):hover.has-circle {
      width: auto !important;
    }
    /* Mirrored truncated/date-only chips reveal their full text in flow on hover
       so the .has-circle `width: auto` expansion (above) sizes the chip to fit.
       Every real chip carries .has-circle, so this is the only reveal path; the
       old absolute-positioned "flap" variant it used to override was retired with
       the author circle (#43) and has been removed. */
    .entry-chip.mirrored.truncated:not(.sel):hover.has-circle .chip-text,
    .entry-chip.date-only.mirrored:not(.sel):hover.has-circle .chip-text {
      position: static;
      flex: none;
      width: max-content;
      max-width: none;
      min-width: 0;
      overflow: visible;
      top: auto;
      bottom: auto;
      right: auto;
      border: none;
      border-radius: 0;
      margin: 0;
      padding: 0 4px;
      background: transparent;
    }
    .chip-circle {
      position: absolute;
      right: 0;
      top: 50%;
      transform: translateY(-50%);
      pointer-events: auto;
      touch-action: none; /* a press on the circle may become an emotion drag */
      line-height: 0;
    }
    .entry-chip.mirrored .chip-circle {
      right: auto;
      left: 0;
      transform: translateY(-50%);
    }
    .chip-circle canvas {
      display: block;
    }
    /* The resting identicon: the circle identity's face (assignee ?? author)
       clipped to a circle (features/chip-rendering.md, "The chip circle"). */
    .chip-identicon {
      display: block;
      width: var(--circle-size);
      height: var(--circle-size);
      border-radius: 50%;
      overflow: hidden;
      line-height: 0;
    }
    /* The circle visibility matrix (features/assignment.md): has-circle now
       means "circle chrome present NOW" — Chip.jsx emits it on foreign /
       hover / selection (and while the wheel is up). An OWN chip at rest
       carries neither the class nor the capsule padding: full-width text,
       and its circle anchor must not paint over that text. FOREIGN chips
       keep the class (and so the reserved chrome + permanent identicon);
       the own-chip hover/selection reveal is the outward-overflow growth
       (width:auto + the capsule padding appearing together). Emojeval-only
       circles paint without the class by design. */
    .entry-chip:not(.has-circle):not(.emojeval-only) .chip-circle {
      display: none;
    }
    .chip-identicon svg {
      display: block;
      width: 100%;
      height: 100%;
    }
    /* While the wheel (canvas) is up, the identicon steps aside. */
    .entry-chip.emojeval-expanded .chip-identicon {
      display: none;
    }
    /* Inside the emojeval-only circle chip the identicon sits within the 2px
       emotion-tinted border, so it shrinks by the border on each side. */
    .entry-chip.emojeval-only:not(.sel) .chip-identicon {
      width: calc(var(--circle-size) - 2 * var(--chip-border-w));
      height: calc(var(--circle-size) - 2 * var(--chip-border-w));
    }
    .entry-chip.emojeval-expanded {
      z-index: 100 !important;
      overflow: visible !important;
    }
    .entry-chip.emojeval-expanded .chip-circle {
      z-index: 100;
    }
    /* Row z-index for emojeval is set via inline style in Grid.jsx
       because rows use inline z-index that overrides CSS rules */

    /* --- Emojeval-only chips ---
       A chip with no text and emotion set renders as a circle the height of a
       chip. No chip background, no date label — the author's identicon inside
       a 2px border carrying the opaque emotion color (the border tint is the
       resting emotion channel — features/emojeval.md, "Emotion-only chips").

       The circle shape applies in all non-selected states — INCLUDING hover.
       Only .sel (selected/editing) breaks out into capsule form. */
    /* Emojeval-only is a plain variant. A neutral/hover emojeval-only chip is a
       CIRCLE, not a capsule, so it deliberately does NOT carry the .has-circle
       class (see Chip.jsx) — the capsule/anchor rules that key on .has-circle
       simply don't match it, so it needs no !important to override them. Most
       declarations then win by ordinary specificity (0,3,0, authored after the
       base .entry-chip rules). Two exceptions remain, each because a PLAIN chain
       geometry rule (not a .has-circle rule) at 0,4,0 also matches an
       emojeval-only chip that happens to be a chain member; and one because chip
       width is applied inline. */
    .entry-chip.emojeval-only:not(.sel) {
      background: transparent;   /* beats .future-area .entry-chip.intent bg (0,3,0, earlier) */
      background-image: none;    /* no competing background-image rule */
      border-width: 2px;         /* beats has-notch border-left-width:0 (0,3,0, earlier) */
      border-style: solid;
      border-radius: 50% !important;          /* beats plain chain radius e.g. .has-notch.has-connector:not(.mirrored) (0,4,0) */
      width: var(--circle-size) !important; /* beats the inline width (layout px / auto) */
      height: var(--circle-size);           /* same value as base; no higher-specificity competitor */
      padding: 0 !important;                  /* beats plain chain padding e.g. .has-connector:not(.mirrored) padding-left (0,4,0) */
      overflow: visible;
    }
    /* Empty text/date: must beat the date-only hover rules (up to 0,6,0). */
    .entry-chip.emojeval-only:not(.sel) .chip-when,
    .entry-chip.emojeval-only:not(.sel) .chip-text {
      display: none !important;
    }
    /* Suppress chain connectors (::before notch, ::after connector) on emojeval-only.
       Equal specificity to the connector ::before/::after (0,3,1) and authored
       later, so ordinary source order wins — no !important needed. */
    .entry-chip.emojeval-only:not(.sel)::before,
    .entry-chip.emojeval-only:not(.sel)::after {
      display: none;
    }
    /* Center the emojeval anchor within the circle (neutral/hover only).
       The former higher-specificity competitor — the chain anchor rule
       .entry-chip.has-notch:not(.mirrored).has-circle .chip-circle — no longer
       matches, because a circle emojeval-only chip carries no .has-circle. So
       centering now wins by ordinary specificity (0,4,0), no !important.
       When selected (.sel) this rule drops out and the anchor falls to its
       default edge position for edit mode. */
    .entry-chip.emojeval-only:not(.sel) .chip-circle {
      left: 50%;
      right: auto;
      top: 50%;
      transform: translate(-50%, -50%);
    }

    /* Today line through grid rows */
    .today-line {
      position: absolute;
      top: 0;
      width: 2px;
      height: 100%;
      background: var(--accent-orange);
      opacity: 0.5;
      z-index: 1;
      pointer-events: none;
    }

    /* --- Status bar --- */
    .status-bar {
      height: 24px;
      padding: 0 16px;
      background: var(--bg-surface);
      border-top: 1px solid var(--border);
      font-size: 11px;
      color: var(--text-dim);
      display: flex;
      align-items: center;
      flex-shrink: 0;
    }

    /* --- Modal --- */
    .modal-overlay {
      position: fixed;
      inset: 0;
      background: rgba(0,0,0,0.6);
      display: flex;
      align-items: center;
      justify-content: center;
      z-index: 200;
    }
    .modal {
      background: var(--bg-surface);
      border: 1px solid var(--border);
      border-radius: 8px;
      padding: 20px;
      min-width: 320px;
      max-width: 480px;
      width: 90%;
    }
    .modal-title {
      font-size: 16px;
      font-weight: 600;
      margin-bottom: 12px;
      color: var(--accent);
    }
    .form-field { margin-bottom: 10px; }
    .form-field label {
      display: block;
      font-size: 12px;
      color: var(--text-dim);
      margin-bottom: 4px;
    }
    .form-field input, .form-field textarea, .form-field select {
      width: 100%;
      padding: 8px;
      background: var(--bg);
      color: var(--text);
      border: 1px solid var(--border);
      border-radius: 4px;
      font-size: 14px;
      font-family: inherit;
    }
    .form-field input:focus, .form-field textarea:focus, .form-field select:focus {
      outline: none;
      border-color: var(--accent);
    }
    .checklist { max-height: 200px; overflow-y: auto; }
    .checklist-item {
      display: flex; align-items: center; gap: 6px;
      padding: 3px 0; font-size: 12px; cursor: pointer;
    }
    .checklist-item input[type="checkbox"] { margin: 0; }
    .range-display {
      display: inline-block;
      margin-left: 8px;
      font-size: 20px;
      font-weight: bold;
    }
    .modal-actions {
      display: flex;
      justify-content: flex-end;
      gap: 8px;
      margin-top: 16px;
    }
    .btn {
      padding: 6px 16px;
      border-radius: 4px;
      border: 1px solid var(--border);
      background: var(--bg);
      color: var(--text);
      cursor: pointer;
      font-size: 13px;
    }
    .btn:hover { background: var(--bg-elevated); }
    .btn.primary { background: var(--accent); color: var(--bg); border-color: var(--accent); }
    .btn.primary:hover { opacity: 0.9; }
    .key-hint { font-size: 10px; opacity: 0.5; margin-left: 4px; }

    /* --- Responsive --- */
    @media (max-width: 768px) {
      :root { --name-width: 150px; --row-height: 36px; }
      .toolbar-actions button span { display: none; }
    }

    /* ================================================================
       Persona switcher (features/personas.md) — fixed bottom-right app
       chrome: the active persona's identicon circle, fanning out the other
       personas + a (+) on hover/keyboard-focus. NOT a timeline chip (no
       gallery cell). z-index sits with the other bottom-right chrome, below
       the PRF unlock overlay.
       ================================================================ */
    .persona-switcher {
      position: fixed;
      right: 16px;
      bottom: 16px;
      z-index: 9998;
      display: flex;
      flex-direction: column;
      align-items: flex-end;
      gap: 8px;
      font: 12px/1.4 system-ui, sans-serif;
    }
    .persona-circle {
      display: grid;
      place-items: center;
      padding: 0;
      border-radius: 50%;
      overflow: hidden;
      border: 2px solid var(--border);
      background: var(--bg-elevated);
      color: var(--text-dim);
      cursor: pointer;
      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
      transition: transform 0.12s ease, box-shadow 0.12s ease;
    }
    .persona-circle svg { display: block; width: 100%; height: 100%; }
    .persona-circle:disabled { cursor: default; opacity: 0.6; }
    .persona-circle:hover:not(:disabled) { transform: scale(1.06); }
    .persona-circle:focus-visible {
      outline: 2px solid var(--accent);
      outline-offset: 2px;
    }
    /* Every circle is the SAME diameter — the active anchor and each fan circle
       (features/personas.md: an equal set). */
    .persona-active,
    .persona-other,
    .persona-logout,
    .persona-add { width: 40px; height: 40px; }
    .persona-add,
    .persona-logout {
      background: var(--bg-surface);
      color: var(--text-dim);
      font-size: 22px;
      line-height: 1;
    }
    .persona-logout { font-size: 18px; }
    /* The active identity carries a persistent selection ring. */
    .persona-active {
      border-color: var(--accent);
      box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 55%, transparent),
                  0 2px 8px rgba(0, 0, 0, 0.25);
    }

    /* A fan row: the name label sits to the LEFT of its circle, the whole row
       right-aligned so every circle lines up under the active circle. */
    .persona-entry {
      display: flex;
      align-items: center;
      gap: 6px;
    }
    .persona-label {
      max-width: 140px;
      padding: 2px 6px;
      border-radius: 6px;
      background: var(--bg-elevated);
      color: var(--text);
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
      box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
    }
    /* The active anchor's own name label. Absolutely positioned to the LEFT of
       the circle so it never reserves layout space — the anchor circle stays
       flush to the bottom-right corner when the fan is closed — and fades in on
       hover/focus like the fan (features/personas.md: "named on hover too"). */
    .persona-active-entry { position: relative; }
    .persona-active-label {
      position: absolute;
      right: calc(100% + 6px);
      top: 50%;
      transform: translateY(-50%);
      visibility: hidden;
      opacity: 0;
      transition: opacity 0.15s ease, visibility 0.15s;
    }
    .persona-switcher:hover .persona-active-label,
    .persona-switcher:focus-within .persona-active-label {
      visibility: visible;
      opacity: 1;
    }
    .persona-name-input {
      width: 150px;
      padding: 4px 8px;
      border: 2px solid var(--accent);
      border-radius: 14px;
      background: var(--bg-elevated);
      color: var(--text);
      font: inherit;
    }

    /* The fan is hidden until the switcher is hovered or holds keyboard focus;
       visibility (not display:none) so focus-within can reveal it, then Tab
       reaches the now-visible circles. */
    .persona-fan {
      display: flex;
      flex-direction: column;
      align-items: flex-end;
      gap: 6px;
      visibility: hidden;
      opacity: 0;
      transform: translateY(6px);
      pointer-events: none;
      transition: opacity 0.15s ease, transform 0.15s ease, visibility 0.15s;
    }
    .persona-switcher:hover .persona-fan,
    .persona-switcher:focus-within .persona-fan {
      visibility: visible;
      opacity: 1;
      transform: none;
      pointer-events: auto;
    }

    .persona-msg {
      max-width: 200px;
      padding: 6px 8px;
      border: 1px solid var(--border);
      border-radius: 6px;
      background: var(--bg-elevated);
      color: var(--text);
      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
    }

    /* Brief ring-pulse confirmation after a switch/add. */
    .persona-switcher.pulsing .persona-active { animation: persona-pulse 0.5s ease-out; }
    @keyframes persona-pulse {
      0% { box-shadow: 0 0 0 0 var(--accent); }
      100% { box-shadow: 0 0 0 10px rgba(122, 162, 247, 0); }
    }
