    /* ──────────────────────────────────────────────────────────────────
       PRESENTATION INTELLIGENCE — staging layer

       The chamber doesn't render modules as a uniform vertical list.
       Loryn decides composition + weight per response, the renderer
       honors it. The grammar:

         mode → composition → primary/supporting/background → weight

       Weight classes (size + density per surface):
         .surface-compact   — small tile; multiple per row
         .surface-standard  — current default; single-column module
         .surface-expanded  — focal surface; larger, richer scale

       Composition wrappers (how surfaces relate to each other):
         .composition-stack             — vertical, centered (default)
         .composition-focus-with-support — primary above a row of compacts
         .composition-grid              — peer surfaces, auto grid

       Wide compositions break OUT of the message wrapper's 640px column
       via `left: 50%; transform: translateX(-50%)` and own their own
       width caps. The prose text stays in its narrow column; only the
       composition expands to use more of the chamber canvas.
       ────────────────────────────────────────────────────────────────── */

    /* Composition wrapper — the staging container. Stack composition
       inherits the message column's reading width so default rendering
       is visually unchanged. Wider compositions break out below. */
    .atrium-composition {
      margin: 0 auto;
      width: 100%;
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: 16px;
    }
    /* Composition manages spacing via gap; reset the card's own
       baseline margin so spacing doesn't double up. */
    .atrium-composition .atrium-inline-card { margin: 0; }
    .atrium-composition.composition-stack {
      max-width: 540px;
    }
    /* Wide canvas break-out. The composition centers itself relative
       to the chamber regardless of the message wrapper's 640px cap,
       using the margin-left:50% + translateX(-50%) breakout pattern.
       The prose .atrium-msg-text remains constrained inside the
       wrapper, so the reading column stays tight while the
       composition uses more chamber.

       Widened from 1000px → 1200px so focus-with-support and
       three-panel actually use the operational canvas instead of
       feeling like a narrow column floating in a wide room. */
    .atrium-composition.composition-focus-with-support,
    .atrium-composition.composition-grid,
    .atrium-composition.composition-split-compare,
    .atrium-composition.composition-three-panel {
      position: relative;
      left: 50%;
      transform: translateX(-50%);
      width: min(94vw, 1200px);
      max-width: 1200px;
    }
    .atrium-composition.composition-focus-with-support {
      gap: 20px;
    }
    /* Grid lays out peer surfaces. Tiles size by weight; the grid
       just decides how many fit per row. */
    .atrium-composition.composition-grid {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
      gap: 14px;
      align-items: start;
      width: min(92vw, 820px);
    }

    /* Split-compare — two primary-weight surfaces side by side. Used
       for direct comparisons: today vs yesterday, Asia vs London,
       account A vs account B. Both surfaces get equal canvas; no
       hierarchy. Items come from primary + supporting flattened. */
    .atrium-composition.composition-split-compare {
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 20px;
      align-items: stretch;
      width: min(94vw, 1100px);
    }
    .atrium-composition.composition-split-compare > .atrium-inline-card {
      max-width: none;
      width: 100%;
    }

    /* Three-panel — three surfaces in a row. Used when the user asks
       for N=3 analytics or three parallel views. Each panel gets ~1/3
       of the canvas. Below 720px the row collapses to a vertical
       stack via mobile rules below. */
    .atrium-composition.composition-three-panel {
      display: grid;
      grid-template-columns: 1fr 1fr 1fr;
      gap: 18px;
      align-items: stretch;
    }
    .atrium-composition.composition-three-panel > .atrium-inline-card {
      max-width: none;
      width: 100%;
    }

    /* Role wrappers — used inside focus-with-support to stage primary
       above a row of supporting tiles, with background sitting deeper. */
    .atrium-primary {
      width: 100%;
      display: flex;
      justify-content: center;
    }
    .atrium-supporting {
      width: 100%;
      display: flex;
      flex-wrap: wrap;
      justify-content: center;
      gap: 14px;
    }
    .atrium-background {
      width: 100%;
      display: flex;
      flex-wrap: wrap;
      justify-content: center;
      gap: 12px;
      opacity: 0.62;
    }

    /* ── Weight classes ─────────────────────────────────────────────
       Modifiers on .atrium-inline-card. The base card already provides
       chrome (background, border, backdrop-filter, entrance animation).
       Weight just adjusts size, padding, margin, and inner type scale. */

    /* Compact — small tile, low chrome, sized to live in a row. */
    .atrium-inline-card.surface-compact {
      max-width: 220px;
      padding: 12px 14px;
      margin: 0;
      flex: 0 1 auto;
    }
    .surface-compact .loryn-mod-stat-label { font-size: 9.5px; letter-spacing: 0.22em; }
    .surface-compact .loryn-mod-stat-value { font-size: 20px; letter-spacing: -0.01em; }
    .surface-compact .loryn-mod-stat-sub { font-size: 10.5px; }
    .surface-compact .loryn-mod-day-date { font-size: 9.5px; letter-spacing: 0.22em; }
    .surface-compact .loryn-mod-day-headline { font-size: 16px; }
    .surface-compact .loryn-mod-day-fact { font-size: 11px; }
    .surface-compact .loryn-mod-callout { gap: 8px; }
    .surface-compact .loryn-mod-callout-icon { width: 14px; height: 14px; }
    .surface-compact .loryn-mod-callout-text { font-size: 11.5px; }
    .surface-compact .loryn-mod-compare-title { font-size: 10px; letter-spacing: 0.2em; }
    .surface-compact .loryn-mod-compare-item-value { font-size: 18px; }

    /* Compact accounts grid — chamber-sized vault view. Designed to
       sit inside one Command surface without overflow. Each tile is
       intentionally small (90×64) so 16 accounts fit comfortably in
       a 4-wide grid that's ~430px wide. Color tone comes from a
       per-tile dot + a subtle left border tint. */
    .loryn-mod-accounts-grid {
      display: flex;
      flex-direction: column;
      gap: 10px;
      min-height: 100%;
      max-height: 100%;
      overflow: hidden;
    }
    .loryn-mod-accounts-grid.empty {
      color: var(--muted);
      font-size: 13px;
      text-align: center;
      padding: 20px 12px;
    }
    .loryn-mod-accounts-grid-title {
      font-size: 10px;
      letter-spacing: 0.18em;
      text-transform: uppercase;
      color: var(--muted);
      font-weight: 700;
    }
    .loryn-mod-accounts-grid .ag-grid {
      display: grid;
      /* Force a real grid even in narrow chamber columns. Tiles size
         to fit but the layout is NEVER a single-column vertical
         stack — which is the bug Jake screenshotted: 16 identical
         cards stacking down the screen.
         minmax(140px, 1fr) gives roughly 4 tiles per ~600px column,
         3 tiles per ~450px, 2 per ~290px. Auto-fit collapses unused
         tracks so a single tile doesn't stretch grotesquely. */
      grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
      gap: 8px;
      width: 100%;
    }
    /* Cluster summary tiles (N accounts at $X each) are visually
       distinct from single-account tiles — a slightly stronger left
       rule + bolder count, so the user instantly sees "this is a
       group, not one account." */
    .loryn-mod-accounts-grid .ag-tile.ag-cluster {
      border-left-width: 3px;
      background: rgba(255,255,255,0.038);
    }
    .loryn-mod-accounts-grid .ag-tile.ag-cluster .ag-label {
      color: rgba(232, 240, 255, 0.62);
      font-weight: 700;
    }
    .loryn-mod-accounts-grid .ag-tile {
      position: relative;
      padding: var(--vv-space-snug) var(--vv-space-comfort) var(--vv-space-snug) calc(var(--vv-space-comfort) + 4px);
      border-radius: 10px;
      background: linear-gradient(180deg,
        rgba(255,255,255,0.022) 0%,
        rgba(255,255,255,0.010) 100%);
      border-left: 2px solid #2a3140;
      box-shadow: var(--vv-shadow-soft), var(--vv-edge-glass);
      display: flex;
      flex-direction: column;
      gap: var(--vv-space-hair);
      transition: background var(--vv-dur-quick) var(--vv-ease-cinematic),
                  box-shadow var(--vv-dur-quick) var(--vv-ease-cinematic),
                  transform var(--vv-dur-quick) var(--vv-ease-cinematic);
      animation: vvCinematicEnter var(--vv-dur-considered) var(--vv-ease-emerge) both;
    }
    .loryn-mod-accounts-grid .ag-tile:hover {
      background: linear-gradient(180deg,
        rgba(255,255,255,0.036) 0%,
        rgba(255,255,255,0.018) 100%);
      box-shadow: var(--vv-shadow-soft), var(--vv-glow-soft), var(--vv-edge-glass);
      transform: translateY(-1px);
    }
    .loryn-mod-accounts-grid .ag-dot {
      position: absolute;
      top: 8px;
      right: 8px;
      width: 6px;
      height: 6px;
      border-radius: 50%;
      background: #5e5e5e;
    }
    .loryn-mod-accounts-grid .ag-label {
      font-size: 9px;
      letter-spacing: 0.08em;
      color: var(--muted);
      font-weight: 600;
      text-transform: uppercase;
    }
    .loryn-mod-accounts-grid .ag-bal {
      font-family: var(--vv-font-numerals);
      font-size: 15px;
      color: var(--vv-text-primary);
      font-variant-numeric: tabular-nums;
      font-weight: 350;
      letter-spacing: -0.005em;
      line-height: 1.1;
    }
    .loryn-mod-accounts-grid .ag-pnl {
      font-family: var(--vv-font-label);
      font-size: 10px;
      color: var(--vv-text-muted);
      font-variant-numeric: tabular-nums;
      letter-spacing: 0.04em;
      line-height: 1.15;
    }
    /* Tone variants — subtle so they read at a glance without shouting. */
    .ag-tone-good  { border-left-color: #7ab8f5; }
    .ag-tone-good  .ag-dot { background: #7ab8f5; }
    .ag-tone-good  .ag-pnl { color: #7ab8f5; }
    .ag-tone-flat  { border-left-color: #555a66; }
    .ag-tone-flat  .ag-dot { background: #6e7585; }
    .ag-tone-warn  { border-left-color: #8f8f8f; }
    .ag-tone-warn  .ag-dot { background: #b08f4a; }
    .ag-tone-warn  .ag-pnl { color: #b08f4a; }
    .ag-tone-bad   { border-left-color: #5e5e5e; }
    .ag-tone-bad   .ag-dot { background: #b4bcc8; }
    .ag-tone-bad   .ag-pnl { color: #b4bcc8; }
    .ag-tone-blown { opacity: 0.45; border-left-color: #3a3a3a; }
    .ag-tone-blown .ag-dot { background: #5e5e5e; }
    .ag-tone-blown .ag-pnl { color: #5e5e5e; }
    .loryn-mod-accounts-grid-foot {
      font-size: 10px;
      color: var(--muted);
      text-align: right;
      letter-spacing: 0.05em;
    }
    /* ── Hero Verdict tableau — the curated composition for
       "where do I stand" when the book is uniform.
       Structure: LABEL ("20 / 20 GREEN") + HERO NUMBER ("+$12,150") +
       VERDICT PROSE (the story under the number) + STATS MICRO ROW.
       Matches the Daily Timeline tableau's level of polish so the
       library's hero compositions share a visual DNA. */
    .loryn-mod-accounts-hero {
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      gap: var(--vv-space-snug);
      padding: var(--vv-space-generous) var(--vv-space-roomy);
      text-align: center;
      background: linear-gradient(180deg,
        rgba(255,255,255,0.022) 0%,
        rgba(255,255,255,0.012) 50%,
        rgba(255,255,255,0.006) 100%);
      box-shadow: var(--vv-shadow-deep), var(--vv-glow-medium), var(--vv-edge-glass);
      border-radius: 18px;
      animation: vvCinematicEnter var(--vv-dur-cinematic) var(--vv-ease-emerge) both;
    }
    /* Hero Verdict needs more chamber width than the default anchor
       primary cell allows (900px). Override when it's hosted. */
    .strategy-anchor .strategy-primary:has(.tableau-hero-verdict) {
      max-width: 1100px !important;
    }
    .loryn-mod-accounts-hero .hero-label {
      font-family: var(--vv-font-label);
      font-size: 11px;
      font-weight: 700;
      letter-spacing: 0.28em;
      color: var(--vv-text-muted);
      text-transform: uppercase;
    }
    .loryn-mod-accounts-hero .hero-value {
      font-family: var(--vv-font-numerals);
      font-size: 76px;
      font-weight: 200;
      letter-spacing: -0.022em;
      line-height: 1.0;
      font-variant-numeric: tabular-nums;
      color: var(--vv-text-primary);
      margin-top: var(--vv-space-snug);
    }
    /* Multi-layer soft bloom instead of a single tight 36px halo — the
       original wrapped a 76px tabular numeral in a 36px shadow (47% of
       text height), which traced the line and read as a rounded
       rectangle around the number. Three layers spread the falloff so
       no single boundary is perceptible. Same recipe as the splash
       greeting fix; per-tone color preserved. */
    .loryn-mod-accounts-hero.ag-tone-good  .hero-value { color: var(--vv-cyan-accent); text-shadow: 0 0 22px rgba(122,184,245,0.13), 0 0 52px rgba(122,184,245,0.09), 0 0 96px rgba(122,184,245,0.05); }
    .loryn-mod-accounts-hero.ag-tone-warn  .hero-value { color: var(--vv-warn-amber); text-shadow: 0 0 22px rgba(176,143,74,0.11),  0 0 52px rgba(176,143,74,0.075), 0 0 96px rgba(176,143,74,0.04); }
    .loryn-mod-accounts-hero.ag-tone-bad   .hero-value { color: var(--vv-alarm-red);  text-shadow: 0 0 22px rgba(180,188,200,0.11), 0 0 52px rgba(180,188,200,0.075), 0 0 96px rgba(180,188,200,0.04); }
    .loryn-mod-accounts-hero.ag-tone-blown .hero-value { color: rgba(138,100,100,0.7); }
    .loryn-mod-accounts-hero .hero-verdict {
      font-family: var(--vv-font-display);
      font-size: 16px;
      font-weight: 300;
      letter-spacing: 0.005em;
      line-height: 1.5;
      color: var(--vv-text-primary);
      margin-top: var(--vv-space-comfort);
      max-width: 680px;
      text-align: center;
    }
    .loryn-mod-accounts-hero .hero-sub {
      font-family: var(--vv-font-label);
      font-size: 12px;
      font-weight: 500;
      letter-spacing: 0.1em;
      text-transform: uppercase;
      color: var(--vv-text-secondary);
      font-variant-numeric: tabular-nums;
      margin-top: var(--vv-space-snug);
    }
    /* Mini bar context — last 5 days. Adds visual density to the
       Hero Verdict without competing with the hero number. Each bar
       is tiny, the today bar is emphasized. */
    .loryn-mod-accounts-hero .hero-mini-bars {
      display: flex;
      align-items: flex-end;
      gap: var(--vv-space-comfort);
      margin-top: var(--vv-space-comfort);
      padding: var(--vv-space-snug) var(--vv-space-roomy);
      border-top: 1px solid rgba(255,255,255,0.04);
    }
    .loryn-mod-accounts-hero .hero-bar-cell {
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: var(--vv-space-tight);
      min-width: 48px;
    }
    .loryn-mod-accounts-hero .hero-bar-stack {
      display: flex;
      flex-direction: column;
      align-items: center;
      height: 80px;
      justify-content: center;
      gap: 1px;
    }
    .loryn-mod-accounts-hero .hero-bar-base {
      width: 28px;
      height: 1px;
      background: rgba(255,255,255,0.12);
    }
    .loryn-mod-accounts-hero .hero-bar-fill {
      width: 24px;
      border-radius: 2px;
      transition: height var(--vv-dur-considered) var(--vv-ease-cinematic);
    }
    .loryn-mod-accounts-hero .hero-bar-fill.tone-good {
      background: linear-gradient(180deg, rgba(122,184,245,0.95), rgba(122,184,245,0.55));
      box-shadow: 0 0 6px rgba(122,184,245,0.4);
    }
    .loryn-mod-accounts-hero .hero-bar-fill.tone-bad {
      background: linear-gradient(180deg, rgba(180,188,200,0.55), rgba(180,188,200,0.95));
      box-shadow: 0 0 6px rgba(180,188,200,0.3);
    }
    .loryn-mod-accounts-hero .hero-bar-cell.today .hero-bar-fill {
      width: 28px;
      filter: brightness(1.1);
    }
    .loryn-mod-accounts-hero .hero-bar-label {
      font-family: var(--vv-font-label);
      font-size: 9.5px;
      font-weight: 600;
      letter-spacing: 0.18em;
      text-transform: uppercase;
      color: var(--vv-text-muted);
    }
    .loryn-mod-accounts-hero .hero-bar-cell.today .hero-bar-label {
      color: var(--vv-text-primary);
    }
    /* ── Decision Card Stack — vertical stack of trade cards for
       "walk me through Thursday" / "show me the press" / "what killed me".
       Story-trades carry visual weight. Each card has setup tag + prices
       + mini sparkline + P&L + per-trade Loryn observation. */
    .loryn-mod-stack {
      display: flex;
      flex-direction: column;
      gap: var(--vv-space-comfort);
      width: 100%;
      padding: var(--vv-space-generous) var(--vv-space-roomy) var(--vv-space-roomy);
      background: linear-gradient(180deg,
        rgba(255,255,255,0.020) 0%,
        rgba(255,255,255,0.010) 100%);
      border-radius: 18px;
      box-shadow: var(--vv-shadow-deep), var(--vv-glow-medium), var(--vv-edge-glass);
      animation: vvCinematicEnter var(--vv-dur-cinematic) var(--vv-ease-emerge) both;
    }
    .loryn-mod-stack.empty {
      padding: var(--vv-space-vast) var(--vv-space-roomy);
      text-align: center;
      color: var(--vv-text-muted);
    }
    .strategy-anchor .strategy-primary:has(.tableau-decision-stack) {
      max-width: 1100px !important;
    }
    .stack-header {
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: var(--vv-space-tight);
      padding-bottom: var(--vv-space-comfort);
      border-bottom: 1px solid rgba(255,255,255,0.04);
      text-align: center;
    }
    .stack-hero-label {
      font-family: var(--vv-font-label);
      font-size: 10.5px;
      font-weight: 700;
      letter-spacing: 0.28em;
      text-transform: uppercase;
      color: var(--vv-text-muted);
    }
    .stack-hero-value {
      font-family: var(--vv-font-numerals);
      font-size: 56px;
      font-weight: 200;
      letter-spacing: -0.018em;
      line-height: 1.0;
      font-variant-numeric: tabular-nums;
      color: var(--vv-text-primary);
    }
    /* Multi-layer soft bloom — see comment on .loryn-mod-accounts-hero
       .hero-value above. Tighter outer radius (76px) since this hero
       lives in a stacked card and shouldn't bleed beyond its bounds. */
    .stack-hero-value.vv-tone-good { color: var(--vv-cyan-accent); text-shadow: 0 0 18px rgba(122,184,245,0.10), 0 0 42px rgba(122,184,245,0.07), 0 0 76px rgba(122,184,245,0.035); }
    .stack-hero-value.vv-tone-bad  { color: var(--vv-alarm-red);   text-shadow: 0 0 18px rgba(180,188,200,0.09), 0 0 42px rgba(180,188,200,0.06), 0 0 76px rgba(180,188,200,0.03); }
    .stack-hero-verdict {
      font-family: var(--vv-font-display);
      font-size: 15px;
      font-weight: 300;
      letter-spacing: 0.005em;
      color: var(--vv-text-primary);
      margin-top: var(--vv-space-tight);
      max-width: 680px;
    }
    .stack-hero-sub {
      font-family: var(--vv-font-label);
      font-size: 11px;
      font-weight: 500;
      letter-spacing: 0.1em;
      text-transform: uppercase;
      color: var(--vv-text-secondary);
      margin-top: var(--vv-space-tight);
    }
    .stack-cards {
      display: flex;
      flex-direction: column;
      gap: var(--vv-space-snug);
    }

    /* Individual trade cards */
    .stack-card {
      display: flex;
      flex-direction: column;
      gap: var(--vv-space-snug);
      padding: var(--vv-space-comfort) var(--vv-space-roomy);
      background: linear-gradient(180deg,
        rgba(255,255,255,0.016) 0%,
        rgba(255,255,255,0.008) 100%);
      border-radius: 12px;
      border-left: 3px solid rgba(255,255,255,0.10);
      box-shadow: var(--vv-shadow-soft), var(--vv-edge-glass);
      transition: background var(--vv-dur-quick) var(--vv-ease-cinematic),
                  transform var(--vv-dur-quick) var(--vv-ease-cinematic);
      animation: vvCinematicEnter var(--vv-dur-considered) var(--vv-ease-emerge) both;
    }
    .stack-card:hover {
      background: linear-gradient(180deg, rgba(255,255,255,0.028) 0%, rgba(255,255,255,0.014) 100%);
      transform: translateY(-1px);
    }
    .stack-card-good { border-left-color: var(--vv-cyan-accent); }
    .stack-card-bad  { border-left-color: var(--vv-alarm-red); }
    /* Story cards — the worst loss, the biggest win, the rule break —
       carry extra visual weight via deeper shadow + brighter border. */
    .stack-card-story {
      box-shadow: var(--vv-shadow-deep), var(--vv-glow-soft), var(--vv-edge-glass);
      border-left-width: 4px;
    }
    .stack-card-story.stack-card-bad { box-shadow: var(--vv-shadow-deep), 0 0 32px rgba(180,188,200,0.10), var(--vv-edge-glass); }
    .stack-card-story.stack-card-good { box-shadow: var(--vv-shadow-deep), 0 0 32px rgba(122,184,245,0.12), var(--vv-edge-glass); }

    .stack-card-head {
      display: flex;
      justify-content: space-between;
      align-items: center;
    }
    .stack-card-tag {
      display: flex;
      align-items: center;
      gap: var(--vv-space-snug);
    }
    .stack-card-side {
      font-family: var(--vv-font-label);
      font-size: 9.5px;
      font-weight: 700;
      letter-spacing: 0.22em;
      text-transform: uppercase;
      padding: 3px var(--vv-space-tight);
      border-radius: 3px;
    }
    .stack-card-side.side-good {
      color: var(--vv-cyan-accent);
      background: rgba(122,184,245,0.10);
    }
    .stack-card-side.side-bad {
      color: var(--vv-alarm-red);
      background: rgba(180,188,200,0.10);
    }
    .stack-card-inst {
      font-family: var(--vv-font-label);
      font-size: 11px;
      font-weight: 700;
      letter-spacing: 0.15em;
      color: var(--vv-text-primary);
    }
    .stack-card-setup {
      font-family: var(--vv-font-label);
      font-size: 9.5px;
      font-weight: 500;
      letter-spacing: 0.14em;
      text-transform: uppercase;
      color: var(--vv-text-muted);
      padding-left: var(--vv-space-snug);
      border-left: 1px solid rgba(255,255,255,0.08);
    }
    .stack-card-time {
      font-family: var(--vv-font-label);
      font-size: 10px;
      font-weight: 500;
      letter-spacing: 0.10em;
      text-transform: uppercase;
      color: var(--vv-text-muted);
      font-variant-numeric: tabular-nums;
    }

    .stack-card-body {
      display: grid;
      grid-template-columns: minmax(260px, 1fr) minmax(180px, 240px) minmax(120px, 160px);
      gap: var(--vv-space-roomy);
      align-items: center;
    }
    .stack-card-prices {
      display: flex;
      gap: var(--vv-space-comfort);
    }
    .price-leg {
      display: flex;
      flex-direction: column;
      gap: 2px;
    }
    .price-leg span {
      font-family: var(--vv-font-label);
      font-size: 9px;
      font-weight: 600;
      letter-spacing: 0.18em;
      text-transform: uppercase;
      color: var(--vv-text-quiet);
    }
    .price-leg b {
      font-family: var(--vv-font-numerals);
      font-size: 16px;
      font-weight: 400;
      color: var(--vv-text-primary);
      font-variant-numeric: tabular-nums;
      letter-spacing: -0.005em;
    }
    .stack-card-mini {
      width: 100%;
      height: 36px;
      display: block;
    }
    .stack-card-pnl {
      display: flex;
      flex-direction: column;
      align-items: flex-end;
      gap: var(--vv-space-hair);
    }
    .pnl-value {
      font-family: var(--vv-font-numerals);
      font-size: 22px;
      font-weight: 350;
      letter-spacing: -0.012em;
      font-variant-numeric: tabular-nums;
    }
    .pnl-value.vv-tone-good { color: var(--vv-cyan-accent); }
    .pnl-value.vv-tone-bad  { color: var(--vv-alarm-red); }
    .pnl-pct {
      font-family: var(--vv-font-label);
      font-size: 10px;
      font-weight: 500;
      letter-spacing: 0.10em;
      color: var(--vv-text-muted);
    }
    .stack-card-obs {
      font-family: var(--vv-font-display);
      font-size: 13.5px;
      font-weight: 300;
      letter-spacing: 0.005em;
      line-height: 1.5;
      color: var(--vv-text-secondary);
      padding-top: var(--vv-space-snug);
      border-top: 1px solid rgba(255,255,255,0.04);
      font-style: italic;
    }

    /* ── Account Roster — Bloomberg-grade dense composition for
       "where do I stand" when the book has variation across accounts.
       Tier 1 visual surface. Instrument panel feel. */
    .loryn-mod-roster {
      display: flex;
      flex-direction: column;
      gap: var(--vv-space-comfort);
      width: 100%;
      padding: var(--vv-space-generous) var(--vv-space-roomy) var(--vv-space-roomy);
      background: linear-gradient(180deg,
        rgba(255,255,255,0.020) 0%,
        rgba(255,255,255,0.010) 100%);
      border-radius: 18px;
      box-shadow: var(--vv-shadow-deep), var(--vv-glow-medium), var(--vv-edge-glass);
      animation: vvCinematicEnter var(--vv-dur-cinematic) var(--vv-ease-emerge) both;
    }
    .loryn-mod-roster.empty {
      padding: var(--vv-space-vast) var(--vv-space-roomy);
      text-align: center;
      color: var(--vv-text-muted);
    }
    /* Roster needs full chamber width — Bloomberg density doesn't
       collapse to a column. */
    .strategy-anchor .strategy-primary:has(.tableau-account-roster) {
      max-width: 1240px !important;
    }

    /* Header strip: hero P&L + verdict prose + tone-distribution band */
    .roster-header {
      display: flex;
      flex-direction: column;
      gap: var(--vv-space-comfort);
      padding-bottom: var(--vv-space-comfort);
      border-bottom: 1px solid rgba(255,255,255,0.04);
    }
    .roster-hero {
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: var(--vv-space-tight);
      text-align: center;
    }
    .roster-hero-label {
      font-family: var(--vv-font-label);
      font-size: 10.5px;
      font-weight: 700;
      letter-spacing: 0.28em;
      text-transform: uppercase;
      color: var(--vv-text-muted);
    }
    .roster-hero-value {
      font-family: var(--vv-font-numerals);
      font-size: 60px;
      font-weight: 200;
      letter-spacing: -0.020em;
      line-height: 1.0;
      font-variant-numeric: tabular-nums;
      color: var(--vv-text-primary);
    }
    /* Multi-layer soft bloom — see comment above. */
    .roster-hero-value.vv-tone-good { color: var(--vv-cyan-accent); text-shadow: 0 0 18px rgba(122,184,245,0.10), 0 0 42px rgba(122,184,245,0.07), 0 0 76px rgba(122,184,245,0.035); }
    .roster-hero-value.vv-tone-bad  { color: var(--vv-alarm-red);   text-shadow: 0 0 18px rgba(180,188,200,0.09), 0 0 42px rgba(180,188,200,0.06), 0 0 76px rgba(180,188,200,0.03); }
    .roster-hero-verdict {
      font-family: var(--vv-font-display);
      font-size: 15px;
      font-weight: 300;
      letter-spacing: 0.005em;
      color: var(--vv-text-primary);
      max-width: 680px;
    }

    /* Tone distribution band — visual summary of who's where */
    .roster-tone-band {
      display: flex;
      width: 100%;
      height: 4px;
      gap: 2px;
      border-radius: 2px;
      overflow: hidden;
    }
    .band-seg {
      display: block;
      height: 100%;
      border-radius: 1px;
      transition: filter var(--vv-dur-quick) var(--vv-ease-cinematic);
    }
    .band-seg.seg-blown { background: rgba(138,100,100,0.5); }
    .band-seg.seg-bad   { background: rgba(180,188,200,0.85); box-shadow: 0 0 6px rgba(180,188,200,0.3); }
    .band-seg.seg-flat  { background: rgba(255,255,255,0.18); }
    .band-seg.seg-good  { background: rgba(122,184,245,0.92); box-shadow: 0 0 6px rgba(122,184,245,0.4); }

    /* Column headers */
    .roster-column-head {
      display: grid;
      grid-template-columns: 12px 60px 70px 110px 1fr 110px 70px;
      gap: var(--vv-space-comfort);
      align-items: center;
      padding: var(--vv-space-tight) var(--vv-space-snug);
      font-family: var(--vv-font-label);
      font-size: 9.5px;
      font-weight: 600;
      letter-spacing: 0.18em;
      text-transform: uppercase;
      color: var(--vv-text-quiet);
    }
    .roster-h-id   { grid-column: 1 / span 2; }
    .roster-h-pnl  { text-align: right; }
    .roster-h-pct  { text-align: right; }

    /* Roster rows */
    .roster-grid {
      display: flex;
      flex-direction: column;
      gap: 1px;
    }
    .roster-row {
      display: grid;
      grid-template-columns: 12px 60px 70px 110px 1fr 110px 70px;
      gap: var(--vv-space-comfort);
      align-items: center;
      padding: var(--vv-space-snug) var(--vv-space-snug);
      background: rgba(255,255,255,0.015);
      border-radius: 6px;
      transition: background var(--vv-dur-quick) var(--vv-ease-cinematic);
    }
    .roster-row:hover {
      background: rgba(255,255,255,0.030);
    }
    .roster-row.roster-tone-blown { opacity: 0.45; }
    .roster-dot {
      width: 6px;
      height: 6px;
      border-radius: 50%;
      background: rgba(255,255,255,0.20);
      justify-self: center;
    }
    .roster-row.roster-tone-good  .roster-dot { background: var(--vv-cyan-accent); box-shadow: 0 0 6px rgba(122,184,245,0.6); }
    .roster-row.roster-tone-warn  .roster-dot { background: var(--vv-warn-amber); }
    .roster-row.roster-tone-bad   .roster-dot { background: var(--vv-alarm-red); box-shadow: 0 0 6px rgba(180,188,200,0.4); }
    .roster-row.roster-tone-blown .roster-dot { background: rgba(138,100,100,0.6); }
    .roster-row.roster-tone-flat  .roster-dot { background: rgba(255,255,255,0.30); }

    .roster-id, .roster-size, .roster-bal, .roster-pnl, .roster-pct {
      font-family: var(--vv-font-numerals);
      font-size: 13px;
      font-variant-numeric: tabular-nums;
    }
    .roster-id {
      color: var(--vv-text-secondary);
      letter-spacing: 0.04em;
    }
    .roster-size {
      color: var(--vv-text-muted);
      font-weight: 400;
    }
    .roster-bal {
      color: var(--vv-text-primary);
      font-weight: 400;
    }
    .roster-pnl {
      text-align: right;
      font-weight: 500;
      color: var(--vv-text-primary);
    }
    .roster-row.roster-tone-good  .roster-pnl { color: var(--vv-cyan-accent); }
    .roster-row.roster-tone-warn  .roster-pnl { color: var(--vv-warn-amber); }
    .roster-row.roster-tone-bad   .roster-pnl { color: var(--vv-alarm-red); }
    .roster-row.roster-tone-blown .roster-pnl { color: rgba(138,100,100,0.7); }
    .roster-pct {
      text-align: right;
      color: var(--vv-text-muted);
      font-size: 11.5px;
      font-weight: 400;
    }

    /* Per-row mini bar showing P&L shape */
    .roster-bar {
      display: flex;
      align-items: center;
      height: 14px;
      position: relative;
    }
    .roster-bar-track {
      width: 100%;
      height: 4px;
      background: rgba(255,255,255,0.04);
      border-radius: 2px;
      position: relative;
      display: flex;
      align-items: center;
    }
    .roster-bar-fill {
      position: absolute;
      height: 100%;
      border-radius: 2px;
      top: 0;
    }
    .roster-bar-fill.roster-bar-pos { left: 50%; }
    .roster-bar-fill.roster-bar-neg { right: 50%; }
    .roster-bar-fill.tone-good  { background: linear-gradient(90deg, rgba(122,184,245,0.65), rgba(122,184,245,0.95)); box-shadow: 0 0 5px rgba(122,184,245,0.4); }
    .roster-bar-fill.tone-warn  { background: linear-gradient(90deg, rgba(176,143,74,0.55), rgba(176,143,74,0.92)); }
    .roster-bar-fill.tone-bad   { background: linear-gradient(90deg, rgba(180,188,200,0.55), rgba(180,188,200,0.92)); box-shadow: 0 0 5px rgba(180,188,200,0.3); }
    .roster-bar-fill.tone-flat  { background: rgba(255,255,255,0.18); }
    .roster-bar-fill.tone-blown { background: rgba(138,100,100,0.45); }
    /* Center hairline marker at the 50% point (zero line) */
    .roster-bar-track::before {
      content: '';
      position: absolute;
      left: 50%;
      top: -4px;
      bottom: -4px;
      width: 1px;
      background: rgba(255,255,255,0.18);
    }

    /* Footer aggregate stats */
    .roster-footer {
      display: flex;
      justify-content: space-around;
      gap: var(--vv-space-roomy);
      padding-top: var(--vv-space-comfort);
      border-top: 1px solid rgba(255,255,255,0.04);
      margin-top: var(--vv-space-tight);
    }
    .roster-foot-stat {
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: var(--vv-space-hair);
    }
    .roster-foot-stat span {
      font-family: var(--vv-font-label);
      font-size: 9.5px;
      font-weight: 600;
      letter-spacing: 0.22em;
      text-transform: uppercase;
      color: var(--vv-text-muted);
    }
    .roster-foot-stat b {
      font-family: var(--vv-font-numerals);
      font-size: 18px;
      font-weight: 300;
      letter-spacing: -0.005em;
      font-variant-numeric: tabular-nums;
      color: var(--vv-text-primary);
    }
    .roster-foot-stat b.vv-tone-good { color: var(--vv-cyan-accent); }
    .roster-foot-stat b.vv-tone-bad  { color: var(--vv-alarm-red); }

    /* ── Daily Timeline — the cinematic answer to "how was today" ──
       Hero block (weighted P&L), sub line, then a full-width SVG
       canvas with session bands, P&L journey curve, trade glyphs,
       and Loryn's annotations. Tier 1 visual surface. */
    .loryn-mod-timeline {
      display: flex;
      flex-direction: column;
      gap: var(--vv-space-generous);
      padding: var(--vv-space-generous) var(--vv-space-roomy) var(--vv-space-roomy);
      width: 100%;
      background: linear-gradient(180deg,
        rgba(255,255,255,0.018) 0%,
        rgba(255,255,255,0.008) 100%);
      border-radius: 18px;
      box-shadow: var(--vv-shadow-deep), var(--vv-glow-medium), var(--vv-edge-glass);
      animation: vvCinematicEnter var(--vv-dur-cinematic) var(--vv-ease-emerge) both;
    }
    /* Daily timeline needs more chamber width than the default anchor
       primary cell allows (900px). Override the strategy-anchor cap
       when it carries a daily_timeline. */
    .strategy-anchor .strategy-primary:has(.loryn-mod-timeline) {
      max-width: 1240px !important;
    }
    .loryn-mod-timeline.empty {
      padding: var(--vv-space-vast) var(--vv-space-roomy);
      text-align: center;
      color: var(--vv-text-muted);
    }
    .vv-tl-hero {
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: var(--vv-space-tight);
      text-align: center;
    }
    .vv-tl-hero-label {
      font-family: var(--vv-font-label);
      font-size: 11px;
      font-weight: 700;
      letter-spacing: 0.28em;
      text-transform: uppercase;
      color: var(--vv-text-muted);
    }
    .vv-tl-hero-value {
      font-family: var(--vv-font-numerals);
      font-size: 72px;
      font-weight: 200;
      letter-spacing: -0.022em;
      line-height: 1.0;
      font-variant-numeric: tabular-nums;
      color: var(--vv-text-primary);
      margin-top: var(--vv-space-snug);
    }
    /* Multi-layer soft bloom — see comment above. */
    .vv-tl-hero-value.vv-tone-good { color: var(--vv-cyan-accent); text-shadow: 0 0 18px rgba(122,184,245,0.11), 0 0 42px rgba(122,184,245,0.075), 0 0 76px rgba(122,184,245,0.04); }
    .vv-tl-hero-value.vv-tone-bad  { color: var(--vv-alarm-red);   text-shadow: 0 0 18px rgba(180,188,200,0.10), 0 0 42px rgba(180,188,200,0.07),  0 0 76px rgba(180,188,200,0.035); }
    .vv-tl-hero-value.vv-tone-warn { color: var(--vv-warn-amber);  text-shadow: 0 0 18px rgba(176,143,74,0.10),  0 0 42px rgba(176,143,74,0.07),  0 0 76px rgba(176,143,74,0.035); }
    .vv-tl-hero-verdict {
      font-family: var(--vv-font-display);
      font-size: 16px;
      font-weight: 300;
      letter-spacing: 0.005em;
      line-height: 1.5;
      color: var(--vv-text-primary);
      margin-top: var(--vv-space-comfort);
      max-width: 720px;
      text-align: center;
    }
    .vv-tl-hero-sub {
      font-family: var(--vv-font-label);
      font-size: 12px;
      font-weight: 500;
      letter-spacing: 0.1em;
      text-transform: uppercase;
      color: var(--vv-text-secondary);
      margin-top: var(--vv-space-snug);
    }
    .vv-tl-canvas {
      width: 100%;
      height: 360px;
      position: relative;
    }
    .vv-tl-svg {
      width: 100%;
      height: 100%;
      display: block;
      overflow: visible;
    }
    .vv-tl-session-label {
      font-family: var(--vv-font-label);
      font-size: 11px;
      font-weight: 700;
      fill: rgba(232,240,255,0.40);
      letter-spacing: 0.22em;
      text-transform: uppercase;
    }
    .vv-tl-tick {
      font-family: var(--vv-font-label);
      font-size: 11px;
      fill: var(--vv-text-muted);
      letter-spacing: 0.06em;
      font-variant-numeric: tabular-nums;
    }
    .vv-tl-anno-text {
      font-family: var(--vv-font-display);
      font-size: 12px;
      font-weight: 500;
      letter-spacing: 0.01em;
    }
    .vv-tl-trade-label {
      font-family: var(--vv-font-numerals);
      font-size: 12.5px;
      font-weight: 500;
      letter-spacing: -0.01em;
      font-variant-numeric: tabular-nums;
    }
    .vv-tl-trade { cursor: default; }
    .vv-tl-trade circle { transition: opacity var(--vv-dur-quick) var(--vv-ease-cinematic); }

    /* ── Width fix for the anchor strategy primary cell ─────────────
       .strategy sets `justify-items: center`, which collapses
       .strategy-primary to its content width. For the accounts_grid
       (grid-template-columns: repeat(auto-fit, minmax(140px, 1fr))),
       this collapses the grid track to a single tile width and stacks
       tiles vertically — the exact bug Jake screenshotted. Force the
       primary cell to stretch to its grid track so auto-fit can spread
       tiles horizontally. */
    .strategy-anchor .strategy-primary {
      width: 100%;
      justify-self: stretch;
      max-width: 900px;
      margin: 0 auto;
    }

    /* Embed — Loryn standing the live journal section beside her words.
       The cloned node already carries its own styles via its original id;
       we only chrome the outer container and constrain size so a full
       equity chart fits inside one Command surface. */
    .loryn-mod-embed {
      display: flex;
      flex-direction: column;
      gap: 8px;
      min-height: 100%;
      max-height: 100%;
      overflow: hidden;
    }
    .loryn-mod-embed-title {
      font-size: 10px;
      letter-spacing: 0.2em;
      text-transform: uppercase;
      color: rgba(176, 192, 215, 0.55);
    }
    .loryn-mod-embed-body {
      flex: 1;
      overflow: hidden;
      display: flex;
      align-items: stretch;
      justify-content: stretch;
      min-height: 0;
    }
    .loryn-mod-embed-body > * {
      width: 100%;
      max-width: 100%;
      height: 100%;
      max-height: 100%;
      margin: 0;
    }
    /* The embed clones a node by id — collapse any sticky/fixed positioning
       its source may have used and let it shrink to fit. */
    .loryn-mod-embed-body [id="equityWrap"],
    .loryn-mod-embed-body [id="healthPanel"],
    .loryn-mod-embed-body [id="calendarWrap"],
    .loryn-mod-embed-body [id="newsStrip"],
    .loryn-mod-embed-body [id="streak"],
    .loryn-mod-embed-body [id="weatherCard"] {
      position: static;
      transform: none;
      width: 100%;
      height: 100%;
      max-height: 100%;
      box-sizing: border-box;
    }
    .loryn-mod-embed-missing {
      color: rgba(176, 192, 215, 0.4);
      font-size: 12px;
      text-align: center;
      padding: 20px;
      font-style: italic;
    }

    /* ── Sparkline ────────────────────────────────────────────────── */
    /* Sparkline — composed from vocab. Label + hero value + svg curve
       + micro sub. */
    .loryn-mod-sparkline {
      display: flex;
      flex-direction: column;
      gap: var(--vv-space-snug);
      width: 100%;
      animation: vvCinematicEnter var(--vv-dur-cinematic) var(--vv-ease-emerge) both;
    }
    .loryn-mod-spark-label {
      font-family: var(--vv-font-label);
      font-size: var(--vv-label-size);
      font-weight: var(--vv-label-weight);
      letter-spacing: var(--vv-label-tracking);
      text-transform: uppercase;
      color: var(--vv-text-muted);
    }
    .loryn-mod-spark-value {
      font-family: var(--vv-font-numerals);
      font-size: 42px;
      line-height: 1.05;
      letter-spacing: -0.018em;
      font-weight: 250;
      color: var(--vv-text-primary);
      font-variant-numeric: tabular-nums;
    }
    .loryn-mod-spark-value.tone-good {
      color: var(--vv-cyan-accent);
      text-shadow: 0 0 24px rgba(122,184,245,0.18);
    }
    .loryn-mod-spark-value.tone-bad  {
      color: var(--vv-alarm-red);
      text-shadow: 0 0 24px rgba(180,188,200,0.16);
    }
    .loryn-mod-spark-svg {
      width: 100%;
      height: 56px;
      display: block;
    }
    .loryn-mod-spark-sub {
      font-family: var(--vv-font-label);
      font-size: var(--vv-micro-size);
      font-weight: var(--vv-micro-weight);
      letter-spacing: var(--vv-micro-tracking);
      color: var(--vv-text-muted);
    }
    .chamber-surface.surface-expanded .loryn-mod-spark-value {
      font-size: var(--vv-hero-size);
      font-weight: var(--vv-hero-weight);
      letter-spacing: var(--vv-hero-tracking);
    }
    .chamber-surface.surface-expanded .loryn-mod-spark-svg   { height: 96px; }

    /* ── Bar chart — composed from vocab ──────────────────────────── */
    .loryn-mod-bars {
      display: flex;
      flex-direction: column;
      gap: var(--vv-space-snug);
      width: 100%;
      animation: vvCinematicEnter var(--vv-dur-cinematic) var(--vv-ease-emerge) both;
    }
    .loryn-mod-bars-title {
      font-family: var(--vv-font-label);
      font-size: var(--vv-label-size);
      font-weight: var(--vv-label-weight);
      letter-spacing: var(--vv-label-tracking);
      text-transform: uppercase;
      color: var(--vv-text-muted);
      margin-bottom: var(--vv-space-tight);
    }
    .loryn-mod-bars-rows {
      display: flex;
      flex-direction: column;
      gap: var(--vv-space-tight);
    }
    .loryn-mod-bars-row {
      display: grid;
      grid-template-columns: minmax(70px, 110px) 1fr minmax(70px, 100px);
      align-items: center;
      gap: var(--vv-space-snug);
    }
    .loryn-mod-bars-label {
      font-family: var(--vv-font-label);
      font-size: 10.5px;
      font-weight: 600;
      color: var(--vv-text-secondary);
      letter-spacing: 0.1em;
      text-transform: uppercase;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
    }
    .loryn-mod-bars-track {
      /* No rectangular track — the bar itself is the data. Without a
         dark slot underneath, each row reads as "a magnitude" rather
         than "a row in a chart." Boxless. */
      background: transparent;
      border-radius: 0;
      height: 8px;
      position: relative;
      overflow: visible;
    }
    .loryn-mod-bars-fill {
      border-radius: 1px;
    }
    .loryn-mod-bars-fill {
      height: 100%;
      border-radius: 2px;
      transition: width 600ms cubic-bezier(0.22, 0.61, 0.36, 1);
    }
    .loryn-mod-bars-fill.tone-good    {
      background: linear-gradient(90deg, rgba(122,184,245,0.55), rgba(122,184,245,0.95));
      box-shadow: 0 0 8px rgba(122,184,245,0.32);
    }
    .loryn-mod-bars-fill.tone-bad     {
      background: linear-gradient(90deg, rgba(180,188,200,0.45), rgba(180,188,200,0.85));
      box-shadow: 0 0 6px rgba(180,188,200,0.25);
    }
    .loryn-mod-bars-fill.tone-neutral { background: linear-gradient(90deg, rgba(176,192,215,0.3), rgba(176,192,215,0.6)); }
    .loryn-mod-bars-value {
      font-family: var(--vv-font-numerals);
      font-size: 14px;
      font-weight: 400;
      letter-spacing: -0.005em;
      font-variant-numeric: tabular-nums;
      text-align: right;
      color: var(--vv-text-primary);
    }
    .loryn-mod-bars-value.tone-good { color: var(--vv-cyan-accent); }
    .loryn-mod-bars-value.tone-bad  { color: var(--vv-alarm-red); }
    .chamber-surface.surface-expanded .loryn-mod-bars-track { height: 24px; }
    .chamber-surface.surface-expanded .loryn-mod-bars-value { font-size: 16px; }
    .chamber-surface.surface-expanded .loryn-mod-bars-label { font-size: 12px; }
    /* Standard-weight bar charts compact further so 3 rows + title fit
       in the supports row height without clipping. */
    .chamber-workstation.chamber-free-canvas .chamber-surface.surface-standard .loryn-mod-bars-rows { gap: 4px; }
    .chamber-workstation.chamber-free-canvas .chamber-surface.surface-standard .loryn-mod-bars-track { height: 12px; }
    .chamber-workstation.chamber-free-canvas .chamber-surface.surface-standard .loryn-mod-bars-row {
      grid-template-columns: minmax(50px, 70px) 1fr minmax(60px, 80px);
      gap: 10px;
    }
    .chamber-workstation.chamber-free-canvas .chamber-surface.surface-standard .loryn-mod-bars-value {
      font-size: 11.5px;
    }
    .chamber-workstation.chamber-free-canvas .chamber-surface.surface-standard .loryn-mod-bars-label {
      font-size: 9.5px;
    }
    .chamber-workstation.chamber-free-canvas .chamber-surface.surface-standard .loryn-mod-bars-title {
      font-size: 9.5px;
      margin-bottom: 8px;
    }
    /* Reduce surface padding for standard supports so we get more room
       for content (was 22px / 24px). */
    .chamber-workstation.chamber-free-canvas .chamber-zone .chamber-surface.surface-standard {
      padding: 12px 18px !important;
    }

    /* ── Concentration ───────────────────────────────────────────── */
    .loryn-mod-concentration {
      display: flex;
      flex-direction: column;
      gap: 14px;
      width: 100%;
    }
    .loryn-mod-conc-title {
      font-size: 10.5px;
      letter-spacing: 0.3em;
      text-transform: uppercase;
      color: rgba(176, 192, 215, 0.55);
    }
    .loryn-mod-conc-headline {
      font-size: 17px;
      line-height: 1.35;
      color: #dde2e9;
      font-weight: 300;
    }
    .chamber-surface.surface-expanded .loryn-mod-conc-headline {
      font-size: 24px;
      line-height: 1.32;
    }
    .loryn-mod-conc-bar {
      display: flex;
      width: 100%;
      height: 14px;
      border-radius: 3px;
      overflow: hidden;
      background: rgba(176, 192, 215, 0.06);
      gap: 2px;
    }
    .chamber-surface.surface-expanded .loryn-mod-conc-bar { height: 22px; }
    .loryn-mod-conc-seg {
      height: 100%;
      transition: width 600ms cubic-bezier(0.22, 0.61, 0.36, 1);
    }
    .loryn-mod-conc-seg.tone-good    { background: linear-gradient(180deg, rgba(122,184,245,0.95), rgba(122,184,245,0.75)); }
    .loryn-mod-conc-seg.tone-bad     { background: linear-gradient(180deg, rgba(150,160,178,0.85), rgba(150,160,178,0.55)); }
    .loryn-mod-conc-seg.tone-neutral { background: linear-gradient(180deg, rgba(176,192,215,0.65), rgba(176,192,215,0.4)); }
    .loryn-mod-conc-legend {
      display: flex;
      flex-direction: column;
      gap: 4px;
    }
    .loryn-mod-conc-legend-row {
      display: grid;
      grid-template-columns: 10px 1fr auto;
      align-items: center;
      gap: 10px;
      font-size: 11.5px;
      color: rgba(176, 192, 215, 0.7);
    }
    .loryn-mod-conc-legend-swatch {
      width: 10px; height: 10px; border-radius: 2px;
    }
    .loryn-mod-conc-legend-swatch.tone-good    { background: rgba(122,184,245,0.9); }
    .loryn-mod-conc-legend-swatch.tone-bad     { background: rgba(150,160,178,0.7); }
    .loryn-mod-conc-legend-swatch.tone-neutral { background: rgba(176,192,215,0.55); }
    .loryn-mod-conc-legend-label { letter-spacing: 0.05em; }
    .loryn-mod-conc-legend-pct {
      font-variant-numeric: tabular-nums;
      color: rgba(220,232,248,0.85);
    }

    /* Standard — explicit but matches the .atrium-inline-card defaults.
       Stated so the contract is visible at the class level. */
    .atrium-inline-card.surface-standard {
      max-width: 540px;
      padding: 18px 22px;
    }

    /* Expanded — focal surface. Wider, more padding, richer type scale.
       Used for the primary thought in focus-with-support compositions
       or as a solo surface for an analysis.

       Tuned tighter than the original draft: 28→22 vertical padding,
       36→32 horizontal, max-width 780→680. The earlier values felt
       imposing for short stat values like "5 of 16" — Jake noted the
       card was visually overwhelming. The numerals stay at 44px which
       lands well for both short values and longer ones like
       "-$11,650"; horizontal padding gives them room without
       inflating overall card height. */
    .atrium-inline-card.surface-expanded {
      max-width: 680px;
      padding: 22px 32px;
      margin: 0;
    }
    .surface-expanded .loryn-mod-stat-label { font-size: 11.5px; letter-spacing: 0.3em; margin-bottom: 4px; }
    .surface-expanded .loryn-mod-stat-value { font-size: 42px; letter-spacing: -0.015em; line-height: 1.05; }
    .surface-expanded .loryn-mod-stat-sub { font-size: 12.5px; margin-top: 8px; color: #6e747f; }
    .surface-expanded .loryn-mod-day-date { font-size: 12px; letter-spacing: 0.3em; }
    .surface-expanded .loryn-mod-day-headline { font-size: 24px; }
    .surface-expanded .loryn-mod-day-fact { font-size: 13.5px; }
    .surface-expanded .loryn-mod-callout-text { font-size: 15px; }
    .surface-expanded .loryn-mod-compare-item-value { font-size: 28px; }

    /* ── Collection surface — peer-group compression ──────────────────
       The collection surface holds N compact tiles laid out per variant
       (grid / ranked_list / strip / timeline / cluster). It's emitted
       by the orchestration compressor when N≥collapseAt peer items of
       the same type appear together. ONE collection surface counts as
       ONE supporting/background slot, so role caps don't fragment a
       coherent peer set.

       The outer .atrium-collection has NO card chrome (no background,
       no border, no backdrop-filter) — the tiles inside have their
       own .surface-compact chrome and the collection is just a layout
       grouping with optional summary + overflow indicator. */
    .atrium-collection {
      width: 100%;
      display: flex;
      flex-direction: column;
      align-items: stretch;
      gap: 10px;
    }
    .atrium-collection-summary {
      font-size: 10px;
      letter-spacing: 0.3em;
      text-transform: uppercase;
      color: #8a909a;
      text-align: center;
      font-weight: 400;
    }
    /* Section labels (collection summary + compare title) in free-canvas
       supports become the visual anchor that ties items into a group.
       Slightly bigger, accent-tinted, with a thin underline that runs
       beneath. Same treatment for both so the chamber reads consistently. */
    .chamber-workstation.chamber-free-canvas .chamber-surface.surface-standard .atrium-collection-summary,
    .chamber-workstation.chamber-free-canvas .chamber-surface.surface-standard .loryn-mod-compare-title,
    .chamber-workstation.chamber-free-canvas .chamber-surface.surface-standard .loryn-mod-bars-title {
      font-size: 10.5px;
      letter-spacing: 0.32em;
      text-transform: uppercase;
      color: rgba(176, 192, 215, 0.62);
      text-align: left;
      font-weight: 500;
      padding-bottom: 10px;
      margin-bottom: 14px;
      border-bottom: 1px solid rgba(122, 184, 245, 0.12);
    }
    .atrium-collection-items {
      width: 100%;
      display: flex;
      flex-wrap: wrap;
      gap: 10px;
      justify-content: center;
    }
    .atrium-collection-overflow {
      font-size: 9.5px;
      letter-spacing: 0.28em;
      text-transform: uppercase;
      color: #6e747f;
      padding: 6px 4px 0;
      text-align: center;
      cursor: default;
    }

    /* Grid — auto-fit columns of peer tiles. The default for collections
       of equivalent items (accounts, generic). */
    .atrium-collection-items.collection-grid-items {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
      gap: 10px;
    }
    /* When the grid lives INSIDE a workstation zone (which is narrower
       than the full atrium-convo center column), tighten the minmax so
       2-3 small tiles can wrap into a column instead of stacking. */
    .chamber-surface .atrium-collection-items.collection-grid-items,
    .chamber-zone .atrium-collection-items.collection-grid-items {
      grid-template-columns: repeat(auto-fit, minmax(108px, 1fr));
      gap: 8px;
    }

    /* Ranked list — vertical column with implied rank order. Used for
       trades, news, tasks, account risk lists. Each item gets full row
       width but extra-tight chrome AND a horizontal label/value layout
       so each row stays ~30-36px tall — multiple rows fit in viewport
       without clipping below the chamber.

       Special inner layout: stat surfaces inside ranked_list collapse
       from their default vertical stack (label / value / sub) to a
       horizontal row (label · sub on the left, value on the right).
       Saves ~40px per row vs stacked layout. */
    .atrium-collection-items.collection-ranked_list-items {
      display: flex;
      flex-direction: column;
      gap: 6px;
    }
    .atrium-collection-items.collection-ranked_list-items > .atrium-inline-card {
      max-width: none;
      width: 100%;
      padding: 8px 16px;
    }
    .atrium-collection-items.collection-ranked_list-items .loryn-mod-stat {
      display: grid;
      grid-template-columns: 1fr auto;
      align-items: baseline;
      gap: 12px;
      text-align: left;
    }
    .atrium-collection-items.collection-ranked_list-items .loryn-mod-stat-label {
      font-size: 9.5px;
      letter-spacing: 0.24em;
      text-align: left;
      grid-column: 1;
    }
    .atrium-collection-items.collection-ranked_list-items .loryn-mod-stat-value {
      font-size: 15px;
      letter-spacing: -0.005em;
      grid-column: 2;
      text-align: right;
      font-weight: 400;
    }
    .atrium-collection-items.collection-ranked_list-items .loryn-mod-stat-sub {
      font-size: 10px;
      grid-column: 1 / -1;
      text-align: left;
      margin-top: 2px;
      color: #6e747f;
    }

    /* Strip — horizontal row, scroll if overflow. Used for temporal
       sequences (days, sessions). Scrollbar suppressed; the chamber
       reads as a continuous horizon. */
    .atrium-collection-items.collection-strip-items {
      display: flex;
      flex-direction: row;
      flex-wrap: nowrap;
      gap: 10px;
      overflow-x: auto;
      scrollbar-width: none;
      -ms-overflow-style: none;
      padding: 2px 4px;
      justify-content: flex-start;
    }
    .atrium-collection-items.collection-strip-items::-webkit-scrollbar {
      display: none;
    }
    .atrium-collection-items.collection-strip-items > .atrium-inline-card {
      flex: 0 0 auto;
      min-width: 170px;
      max-width: 200px;
    }
    /* S10 fix: when a strip lives inside a workstation zone (narrow
       column), the horizontal scroll causes items past the zone width
       to clip mid-text. Inside zones, flip strip to a vertical wrap so
       items stack cleanly instead of getting chopped.
       EXCEPTION: in free-canvas mode the planner gives wide slots, so
       strips stay horizontal — vertical would waste the width. */
    .chamber-zone .atrium-collection-items.collection-strip-items {
      flex-direction: column;
      flex-wrap: nowrap;
      overflow-x: visible;
      overflow-y: visible;
      gap: 10px;
      align-items: stretch;
    }
    .chamber-zone .atrium-collection-items.collection-strip-items > .atrium-inline-card {
      min-width: 0;
      max-width: 100%;
      width: 100%;
    }
    /* Free-canvas override — strips stay horizontal because the planner
       allocates wide-and-short slots. Items distribute evenly across
       the available width; if there are too many to fit, they clip
       cleanly rather than scroll. */
    .chamber-workstation.chamber-free-canvas .chamber-zone .atrium-collection-items.collection-strip-items {
      flex-direction: row;
      align-items: stretch;
      justify-content: space-between;
      gap: 10px;
      overflow: hidden;
    }
    .chamber-workstation.chamber-free-canvas .chamber-zone .atrium-collection-items.collection-strip-items > .atrium-inline-card {
      flex: 1 1 0;
      min-width: 0;
      max-width: none;
    }
    /* Ranked lists in free-canvas: cap visible items and dim overflow.
       Keeps a 4-trade list from blowing past its 158px slot. */
    .chamber-workstation.chamber-free-canvas .atrium-collection-items.collection-ranked_list-items {
      max-height: 100%;
      overflow: hidden;
    }
    /* Tighter padding on inline cards inside compact slots so contents
       have room. */
    .chamber-workstation.chamber-free-canvas .chamber-zone .atrium-collection .atrium-inline-card {
      padding: 8px 10px;
    }

    /* Timeline — strip variant with date-axis markers. v1 renders the
       same as strip; date markers come in a later motion pass. */
    .atrium-collection-items.collection-timeline-items {
      display: flex;
      flex-direction: row;
      flex-wrap: nowrap;
      gap: 16px;
      overflow-x: auto;
      scrollbar-width: none;
      padding: 8px 4px;
    }
    .atrium-collection-items.collection-timeline-items::-webkit-scrollbar {
      display: none;
    }
    .atrium-collection-items.collection-timeline-items > .atrium-inline-card {
      flex: 0 0 auto;
      min-width: 170px;
    }

    /* Cluster — flex-wrap chips, no implied order. Used for memory
       fragments and similar recall surfaces. Tighter spacing so they
       read as a related group. */
    .atrium-collection-items.collection-cluster-items {
      display: flex;
      flex-wrap: wrap;
      gap: 8px;
      justify-content: center;
    }
    .atrium-collection-items.collection-cluster-items > .atrium-inline-card {
      max-width: 200px;
    }

    /* Mobile — wide compositions collapse to vertical stack; compact
       tiles wrap to two-up; expanded surfaces shrink their padding
       so they still feel composed but don't burst the chamber. */
    @media (max-width: 720px) {
      .atrium-composition.composition-focus-with-support,
      .atrium-composition.composition-grid,
      .atrium-composition.composition-split-compare,
      .atrium-composition.composition-three-panel {
        width: calc(100vw - 32px);
        max-width: calc(100vw - 32px);
      }
      .atrium-composition.composition-grid {
        grid-template-columns: repeat(2, 1fr);
      }
      /* Split-compare + three-panel collapse to single column on phone.
         At iPhone width 3 panels in a row would each be ~110px which
         is too cramped — vertical stack reads better. */
      .atrium-composition.composition-split-compare,
      .atrium-composition.composition-three-panel {
        grid-template-columns: 1fr;
        gap: 14px;
      }
      .atrium-inline-card.surface-compact {
        max-width: none;
        flex: 1 1 calc(50% - 7px);
        min-width: 0;
      }
      .atrium-inline-card.surface-expanded {
        max-width: 100%;
        padding: 22px 24px;
      }
      .surface-expanded .loryn-mod-stat-value { font-size: 34px; }
      .surface-expanded .loryn-mod-day-headline { font-size: 22px; }
    }

    /* Horizon-line input. The line is a separate pseudo-element on the
       PARENT container, positioned right beneath the text. The input
       itself has no border, no background, no chrome at all — typing
       just floats above the line. When focused, ONLY the line glows
       (driven by :focus-within on the parent), never the box around
       the input. The glow is a thin offset shadow rather than a box
       outline, so it reads as "the line is luminous", not "the input
       has a border". */
    .atrium-line {
      padding: 18px 40px max(24px, calc(env(safe-area-inset-bottom) + 20px));
      display: flex;
      justify-content: center;
      /* Bottom-align so the resting line sits on the horizon and a
         growing paragraph extends UPWARD off the line, never down. */
      align-items: flex-end;
      /* PINNED to the bottom of the chamber. The input bar must NEVER
         move — long responses, short responses, surfaces, no surfaces
         — same place every time. Was position:relative which left it
         in the flex flow; long prose above pushed it around. Absolute
         removes it from flow; the convo above gets padding-bottom to
         reserve the space so content doesn't render UNDER it. */
      position: absolute;
      bottom: 0;
      left: 0;
      right: 0;
      z-index: 5;
    }
    /* Horizon line — PERMANENTLY accent blue and gently breathing,
       like she's listening even when you're not typing. The breath
       cycles opacity + glow on a slow 4s pulse. On focus, the breath
       stops and the line locks at peak brightness so it reads as "she
       has your attention now". */
    /* Horizon line uses TWO stacked layers because CSS animations and
       transitions can't share the same property cleanly — when an
       animation was driving opacity, `animation: none` on focus snapped
       the opacity instantly rather than transitioning.

       ::before = the dim breathing line. Always present, always
       cycling 0.42 → 0.62 opacity on a slow 4.6s breath. Never touched
       by focus state — it just keeps breathing underneath.

       ::after = a bright focus overlay sitting on top. Hidden at
       opacity 0 by default, transitions to opacity 1 over 1100ms when
       the input is focused. Because nothing else is animating its
       opacity, the transition runs cleanly. The slow blue swell. */
    .atrium-line::before,
    .atrium-line::after {
      content: '';
      position: absolute;
      left: 40px;
      right: 40px;
      bottom: max(20px, calc(env(safe-area-inset-bottom) + 16px));
      height: 1px;
      pointer-events: none;
    }
    .atrium-line::before {
      background: linear-gradient(to right,
        transparent 0%,
        rgba(122,184,245,0.5) 22%,
        rgba(180,220,255,0.95) 50%,
        rgba(122,184,245,0.5) 78%,
        transparent 100%);
      box-shadow: 0 0 8px rgba(122,184,245,0.18);
      animation: atriumLineBreath 4.6s ease-in-out infinite;
    }
    @keyframes atriumLineBreath {
      0%, 100% { opacity: 0.42; }
      50%      { opacity: 0.62; }
    }
    .atrium-line::after {
      background: linear-gradient(to right,
        transparent 0%,
        rgba(122,184,245,0.65) 22%,
        rgba(210,235,255,1)    50%,
        rgba(122,184,245,0.65) 78%,
        transparent 100%);
      box-shadow: 0 0 24px rgba(122,184,245,0.6),
                  0 0 48px rgba(122,184,245,0.25);
      opacity: 0;
      transition: opacity 1100ms ease;
    }
    .atrium-line:focus-within::after {
      opacity: 1;
    }
    .atrium-input {
      width: 100%;
      max-width: none;
      background: transparent;
      border: none;
      border-radius: 0;
      color: rgba(232, 240, 255, 0.92);
      font-size: 14px;
      font-weight: 300;
      font-family: inherit;
      padding: 12px 4px;
      outline: none;
      letter-spacing: 0.038em;
      caret-color: rgba(122, 184, 245, 0.55);
      text-align: center;
      transition: text-shadow 600ms cubic-bezier(0.32, 0.7, 0.32, 1),
                  transform 320ms cubic-bezier(0.22, 1, 0.36, 1);
      transform-origin: center bottom;
      /* textarea behaviour — resting state is a single line identical to
         the old input; JS auto-grows height to fit, capped here at ~4
         lines, after which it scrolls. resize handle removed. */
      display: block;
      resize: none;
      line-height: 1.45;
      max-height: calc(1.45em * 4 + 24px);
      overflow-y: auto;
      scrollbar-width: thin;
      scrollbar-color: rgba(122,184,245,0.3) transparent;
    }
    /* Subtle hover magnify on the typing bar — a quiet "I'm here", same spirit
       as the orb's hover swell (Jake). Grows gently from the horizon line. */
    .atrium-line:hover .atrium-input { transform: scale(1.045); }
    .atrium-input::-webkit-scrollbar { width: 6px; }
    .atrium-input::-webkit-scrollbar-track { background: transparent; }
    .atrium-input::-webkit-scrollbar-thumb {
      background: rgba(122,184,245,0.28); border-radius: 3px;
    }
    /* Greyed prompt in the chat bar (Jake: like ChatGPT's "Ask ChatGPT", but
       warmer — "Ask me anything, sir"). Shows only while empty; vanishes on type. */
    /* Smaller, quieter, more minimal than the typed text — a faint invitation,
       not a label (Jake). */
    .atrium-input::placeholder { color: rgba(232, 240, 255, 0.20); font-size: 11.5px; font-weight: 300; letter-spacing: 0.07em; }
    .atrium-input:focus::placeholder { color: rgba(232, 240, 255, 0.12); }
    /* When the input has focus and content, Jake's words gain a faint
       accent halo — his voice carries the same kind of presence-trace
       Loryn's prose does. Subtle, never theatrical. */
    .atrium-line:focus-within .atrium-input:not(:placeholder-shown) {
      text-shadow: 0 0 8px rgba(122, 184, 245, 0.22);
    }
    /* Horizon glow scales with chamber-tension. The room itself listens
       harder when she's processing. */
    body.in-atrium .atrium-line::before {
      box-shadow: 0 0 calc(8px + var(--chamber-tension, 0.2) * 18px)
                  rgba(122,184,245, calc(0.18 + var(--chamber-tension, 0.2) * 0.18));
      transition: box-shadow 1600ms cubic-bezier(0.32, 0.7, 0.32, 1);
    }

    /* ── Atrium entry — MATERIALIZATION sequence. No flying orb. The
       journal dissolves into pure black, then the chamber's layers each
       fade up in parallel from their own delays. Every animation is
       transform + opacity only, so the whole sequence stays on the GPU
       compositor — no main-thread layout, no jitter.

       Sequence (parallel timelines, all from t=0 of in-atrium):
         T+0     orb materializes from a center point (2200ms)
         T+0,200,400  three ambient wisps emerge at staggered delays
         T+800   conversation column rises
         T+1100  horizon line rises
         T+1400  back arrow fades in
       ──────────────────────────────────────────────────────────────── */
    /* Pure opacity fade — slow melt to black. The journal recedes for
       1100ms before the in-atrium swap, which sets .app back to opacity
       1 INSTANTLY but on a canvas where everything inside is either
       display:none (journal chrome) or at opacity 0 (Atrium layers
       waiting to materialize). The visual result is a clean dissolve to
       black, then the chamber starts assembling. */
    body.atrium-entering .app {
      animation: atriumEnterFade 1100ms ease-out forwards;
    }
    @keyframes atriumEnterFade {
      0%   { opacity: 1; }
      100% { opacity: 0; }
    }
    /* When in-atrium kicks in, snap .app back to opacity 1 instantly so
       the Atrium content (which has its own materialization animations)
       can become visible. Without this override the .app would stay
       stuck at opacity 0 from the entering animation's forwards fill. */
    body.in-atrium .app {
      opacity: 1 !important;
      animation: none !important;
    }
    body.atrium-exiting .app {
      animation: atriumExitFade 700ms ease-out forwards;
    }
    @keyframes atriumExitFade {
      0%   { opacity: 0; }
      100% { opacity: 1; }
    }

    /* The .atrium-orb container itself stays static — its inner SVG
       layers handle the materialization sequentially (see the layered
       orb assembly rules below). transform-origin centered so any
       internal scale animations pivot from the orb's heart. */
    body.in-atrium .atrium-orb {
      transform-origin: center;
    }

    /* Ambient wisps fade in at staggered offsets and STAY visible (forwards
       fill — backwards was leaving them at opacity 0 after the emerge
       animation ended, which read as a glitch where the glow appeared and
       sharply vanished). Three independent timelines drifting on long
       cycles give the chamber its "multiple moving parts" feel. */
    body.in-atrium .atrium-wisp-1 {
      animation: atriumWisp1 32s ease-in-out infinite, atriumWispEmerge 2600ms 0ms cubic-bezier(0.22, 1, 0.36, 1) forwards;
    }
    body.in-atrium .atrium-wisp-2 {
      animation: atriumWisp2 40s ease-in-out infinite reverse, atriumWispEmerge 2600ms 250ms cubic-bezier(0.22, 1, 0.36, 1) forwards;
    }
    body.in-atrium .atrium-wisp-3 {
      animation: atriumWisp3 48s ease-in-out infinite, atriumWispEmerge 2600ms 500ms cubic-bezier(0.22, 1, 0.36, 1) forwards;
    }
    @keyframes atriumWispEmerge {
      from { opacity: 0; }
      to   { opacity: 1; }
    }

    /* ── Orb LAYERED ASSEMBLY — Loryn materializes piece-by-piece.
       Each layer of the orb's own SVG (outer aura → halo → mist body
       → secondary ring → main ring) fades in on its own delayed
       timeline. After each emerge animation completes, the existing
       infinite breathing / rotating animations take over.
       Sequence:
         T+ 200ms  outer aura
         T+ 500ms  halo
         T+ 800ms  mist body (the orb's "substance")
         T+1200ms  secondary ring
         T+1500ms  main ring
       This is the orb literally assembling itself layer by layer. ───── */
    /* Emerge staggers (200/500/800ms) keep the layer-by-layer assembly,
       but every breath animation starts at the SAME wall-clock moment
       (2400ms) so the orb's layers breathe in unison instead of drifting
       in and out of phase with each other. */
    body.in-atrium .atrium-orb .orb-outer {
      animation:
        orbEmergeOpacity 1800ms cubic-bezier(0.4, 0, 0.6, 1) 200ms backwards,
        orbOuterBreathe 11s ease-in-out infinite 2400ms;
    }
    body.in-atrium .atrium-orb .orb-halo {
      animation:
        orbEmergeOpacity 1800ms cubic-bezier(0.4, 0, 0.6, 1) 500ms backwards,
        orbHaloBreathe 8.5s ease-in-out infinite 2400ms;
    }
    body.in-atrium .atrium-orb .orb-mist {
      animation:
        orbEmergeMist 1800ms cubic-bezier(0.4, 0, 0.6, 1) 800ms backwards,
        orbMistBreathe 9s ease-in-out infinite 2400ms;
    }
    /* Rotation starts at the SAME moment the trace begins, so the ring is
       being drawn onto an already-orbiting element. The trace's leading
       edge (the drawing point) traces the ring while the ring itself
       rotates — reads as a moving point laying down its own orbital path.
       No pause is possible because rotation has been happening throughout
       the draw, not chained after it. */
    body.in-atrium .atrium-orb .orb-ring-sub {
      animation: orbRingSubSpin 38s linear infinite reverse 1700ms;
    }
    body.in-atrium .atrium-orb .orb-ring-main {
      animation: orbRingMainSpin 22s linear infinite 1400ms;
    }

    /* ─── Orbital trace ───────────────────────────────────────────
       Each ring is drawn into existence by an energy point that
       travels along its orbital path. Implementation: stroke-dasharray
       set to a value larger than the ring's perimeter (so one long
       continuous dash), stroke-dashoffset animated from the dash
       length down to 0. The leading edge of the dash IS the moving
       spark; the stroke behind it is the trail it leaves. As the
       offset reaches 0 the orbit is complete.

       Custom property --orb-trace-len holds the dash length; the
       elements have it inline so we can override per-ring if needed.
       The cubic-bezier curve (0.5, 0, 0.2, 1) gives a celestial
       feel: quick acceleration into the orbit, smooth coast, gentle
       lock-in as the path closes. */
    /* Ring ellipses are dashed-invisible by DEFAULT inside .atrium-orb,
       not only when body has in-atrium. Was conditional — caused a
       one-frame full-stroke flash at the moment the chamber became
       visible (the in-atrium styles hadn't applied yet when the
       browser composited the first frame). Now the rings are always
       ready to be traced in via orbTrace — no flash, no apparent
       "freeze frame" before the trace animation begins. */
    .atrium-orb .orb-ring-main ellipse,
    .atrium-orb .orb-ring-sub  ellipse {
      stroke-dasharray: 200;
      stroke-dashoffset: 200;
      /* Ease ring opacity on EVERY state change (sweep P1: it only transitioned
         into listening, so leaving any state snapped the ring). */
      transition: stroke-opacity 0.45s cubic-bezier(0.32, 0.7, 0.32, 1);
    }
    body.in-atrium .atrium-orb .orb-ring-main ellipse {
      animation: orbTrace 2200ms cubic-bezier(0.5, 0, 0.2, 1) 1400ms forwards;
    }
    body.in-atrium .atrium-orb .orb-ring-sub  ellipse {
      animation: orbTrace 2400ms cubic-bezier(0.5, 0, 0.2, 1) 1700ms forwards;
    }
    @keyframes orbTrace {
      from { stroke-dashoffset: 200; }
      to   { stroke-dashoffset:   0; }
    }
    @keyframes orbEmergeOpacity {
      from { opacity: 0; }
      to   { opacity: 1; }
    }
    @keyframes orbEmergeMist {
      from { opacity: 0; transform: scale(0.25); }
      to   { opacity: 1; transform: scale(1); }
    }

    /* Conversation column rises from below, slightly delayed so the orb
       lands first. */
    body.in-atrium .atrium-convo {
      animation: atriumRise 1500ms cubic-bezier(0.22, 1, 0.36, 1) 800ms backwards;
    }
    /* Horizon line + back arrow fade in coordinated with the greeting settling
       (~4.4s), so the entrance reads as one sequence — NOT jumping ahead of
       Loryn's arrival. The keyframe no longer holds pointer-events:none during
       the delay, so the bar is interactive the instant it mounts (no dead-zone;
       the old 5000ms+gated version felt dead). Voice is orb-driven and immediate
       regardless of this. */
    /* Text bar + exit arrow reveal on the SAME instant as the hero / offers /
       tableau — the single `chamber-revealed` body class set in one mountChrome
       callback. The old animation counted from body.in-atrium (a DIFFERENT,
       earlier clock), so the bar drifted ahead by ~1-2s. One trigger = true
       unison. Jake: "the four quadrants need to come in at the same time." */
    /* The text bar fades in SMOOTHLY, in unison with the rest of the chamber on
       chamber-revealed — it must NOT pop in immediately (Jake). Interactivity is
       DECOUPLED from the visual: pointer-events stay live even while it's still
       at opacity 0, so you can already click/type during the orb's formation now
       that the splash no longer sits on top of it. */
    body.in-atrium .atrium-line {
      opacity: 0; pointer-events: auto;
      transition: opacity 1000ms cubic-bezier(0.32, 0.7, 0.32, 1);
    }
    body.in-atrium.chamber-revealed .atrium-line { opacity: 1; }
    body.in-atrium .atrium-exit {
      opacity: 0; pointer-events: none;
      transition: opacity 1000ms cubic-bezier(0.32, 0.7, 0.32, 1);
    }
    body.in-atrium.chamber-revealed .atrium-exit { opacity: 1; pointer-events: auto; }
    @keyframes atriumLineFadeIn {
      from { opacity: 0; }
      to   { opacity: 1; }
    }
    @keyframes atriumExitFadeIn {
      from { opacity: 0; pointer-events: none; }
      to   { opacity: 1; pointer-events: auto; }
    }
    @keyframes atriumRise {
      from { opacity: 0; transform: translateY(18px); }
      to   { opacity: 1; transform: translateY(0); }
    }
    @keyframes atriumLineRise {
      from { opacity: 0; transform: translateY(22px); }
      to   { opacity: 1; transform: translateY(0); }
    }
    @keyframes atriumExitArrowIn {
      from { opacity: 0; transform: translateX(-6px); }
      to   { opacity: 0.55; transform: translateX(0); }
    }

    @media (max-width: 720px) {
      .atrium-orb-zone {
        /* Always clear the Dynamic Island / notch on iPhone. The
           env(safe-area-inset-top) value resolves to ~47px on iPhones
           with a notch (zero on older models and desktop). */
        padding: max(70px, calc(env(safe-area-inset-top) + 28px)) 0 22px;
        transition: padding 280ms cubic-bezier(0.22, 1, 0.36, 1);
      }
      .atrium-orb {
        width: 150px;
        height: 150px;
        transition: width 280ms cubic-bezier(0.22, 1, 0.36, 1),
                    height 280ms cubic-bezier(0.22, 1, 0.36, 1);
      }
      .atrium-convo { padding: 0 18px 22px; gap: 18px; }
      .atrium-line { padding: 14px 18px max(20px, calc(env(safe-area-inset-bottom) + 14px)); }
      /* Mobile: tighter type, but no left padding — the dot avatar is
         gone in the new chamber model so there's nothing to clear. */
      .atrium-msg-loryn { font-size: 13.5px; padding-left: 0; }
      .atrium-msg-user { font-size: 9.5px; letter-spacing: 0.22em; }
      .atrium-input { font-size: 13px; }

      /* When the conversation has begun on mobile, shrink further.
         56/200 = 0.28. Use the variable so animations compose. */
      body.atrium-active.in-atrium .atrium-orb {
        --orb-base-scale: 0.28;
      }
      body.atrium-active.in-atrium .atrium-orb-zone {
        padding: max(18px, calc(env(safe-area-inset-top) + 10px)) 0 8px;
      }

      /* When the iOS keyboard is open: compact the orb so the
         conversation column has room. Standard chat-app UX. Top
         padding still clears the Dynamic Island. */
      body.keyboard-open.in-atrium .atrium-orb-zone {
        padding: max(10px, calc(env(safe-area-inset-top) + 6px)) 0 6px;
      }
      body.keyboard-open.in-atrium .atrium-orb { --orb-base-scale: 0.26; }
      /* Hide the back arrow when the keyboard is open — it's not
         relevant during typing and freed corner space avoids touch
         targets near the keyboard's nav bar. */
      body.keyboard-open.in-atrium .atrium-exit { opacity: 0; pointer-events: none; }

      /* If keyboard is open AND conversation is active, the orb
         compresses even further so EVERY pixel between Dynamic Island
         and keyboard top is available for messages + modules. This is
         the dense-chat mobile state Jake asked for. */
      body.atrium-active.keyboard-open.in-atrium .atrium-orb {
        --orb-base-scale: 0.18;
      }
      body.atrium-active.keyboard-open.in-atrium .atrium-orb-zone {
        padding: max(8px, calc(env(safe-area-inset-top) + 4px)) 0 4px;
      }
    }

    /* ── Loryn tab view ──────────────────────────────────────────────────── */
    .loryn-tab-header {
      display: flex;
      align-items: center;
      gap: 18px;
      padding-bottom: 22px;
      border-bottom: 1px solid var(--border);
      margin-bottom: 22px;
    }
    .loryn-tab-orb { width: 56px; height: 56px; flex: 0 0 auto; }
    .loryn-tab-headline { flex: 1; }
    .loryn-tab-title {
      font-size: 26px;
      font-weight: 700;
      letter-spacing: 0.06em;
      color: var(--accent);
      text-shadow: 0 0 18px rgba(122,184,245,0.32);
    }
    .loryn-tab-sub {
      font-size: 12px;
      color: var(--muted);
      letter-spacing: 0.02em;
      margin-top: 4px;
    }
    .loryn-tab-open-chat {
      background: transparent;
      border: 1px solid #2a2a2a;
      color: var(--accent);
      font-size: 11px;
      letter-spacing: 0.16em;
      text-transform: uppercase;
      padding: 9px 16px;
      border-radius: 22px;
      cursor: pointer;
      font-family: inherit;
      transition: border-color 0.18s ease, background 0.18s ease;
    }
    .loryn-tab-open-chat:hover { border-color: var(--accent); background: rgba(122,184,245,0.06); }
    .loryn-tab-grid {
      display: grid;
      grid-template-columns: repeat(2, minmax(0, 1fr));
      gap: 18px;
    }
    .loryn-card {
      background: var(--surface);
      border: 1px solid var(--border);
      border-radius: 14px;
      padding: 20px 22px;
    }
    .loryn-card-title {
      font-size: 11px;
      letter-spacing: 0.18em;
      text-transform: uppercase;
      color: var(--muted);
      margin-bottom: 4px;
    }
    .loryn-card-sub {
      font-size: 11px;
      color: #555;
      margin-bottom: 16px;
      line-height: 1.5;
    }
    .loryn-memory { list-style: none; }
    .loryn-memory li {
      display: grid;
      grid-template-columns: 84px 1fr;
      gap: 10px;
      padding: 8px 0;
      border-bottom: 1px solid #181818;
      font-size: 12px;
    }
    .loryn-memory li:last-child { border-bottom: none; }
    .loryn-mem-k {
      color: var(--accent);
      text-transform: uppercase;
      letter-spacing: 0.14em;
      font-size: 10px;
      padding-top: 1px;
    }
    .loryn-mem-v { color: var(--text); line-height: 1.5; }
    .loryn-watch { list-style: none; margin-bottom: 12px; }
    .loryn-watch li {
      display: flex;
      align-items: center;
      gap: 10px;
      padding: 9px 0;
      border-bottom: 1px solid #181818;
      cursor: pointer;
      font-size: 12px;
      line-height: 1.45;
      color: #c0c4ca;
      transition: color 0.18s ease;
    }
    .loryn-watch li:last-child { border-bottom: none; }
    .loryn-watch li:hover { color: var(--text); }
    .loryn-watch-dot {
      width: 8px;
      height: 8px;
      border-radius: 50%;
      background: #2a2a2a;
      flex: 0 0 auto;
      transition: background 0.2s ease, box-shadow 0.2s ease;
    }
    .loryn-watch-dot.on {
      background: var(--accent);
      box-shadow: 0 0 6px rgba(122,184,245,0.7);
    }
    .loryn-watch-add {
      background: transparent;
      border: 1px dashed #2a2a2a;
      color: var(--muted);
      font-size: 11px;
      padding: 8px 14px;
      border-radius: 8px;
      cursor: pointer;
      font-family: inherit;
      letter-spacing: 0.04em;
      transition: border-color 0.18s ease, color 0.18s ease;
    }
    .loryn-watch-add:hover { border-color: var(--accent); color: var(--accent); }
    .loryn-briefing p {
      font-size: 13px;
      color: var(--text);
      line-height: 1.6;
      margin-bottom: 8px;
    }
    .loryn-briefing p:first-child {
      color: var(--accent);
      font-weight: 500;
      text-shadow: 0 0 10px rgba(122,184,245,0.25);
    }

    /* ── Loryn module primitives — the JSON-spec → DOM library ────────────
       Modules are rendered into `.atrium-inline-card` wrappers so they
       inherit the glass background, border, blur and entrance animation
       — every primitive feels like family no matter which Claude picks.
       Tone is intentionally constrained to accent-blue / muted-grey;
       NO red/green coding (see --neg / --red set to grey at :root). */

    /* Tone tokens shared by all primitives. */
    .tone-good    { color: var(--accent); }
    .tone-bad     { color: var(--neg); }
    .tone-neutral { color: var(--text); }

    /* stat — single label + value tile. The smallest primitive; Loryn
       uses this when the whole answer is one number ("yesterday: +$312"). */
    /* Composed from visual-vocab typography ladder (label + stat + micro). */
    .loryn-mod-stat {
      display: flex;
      flex-direction: column;
      gap: var(--vv-space-tight);
    }
    .loryn-mod-stat-label {
      font-family: var(--vv-font-label);
      font-size: var(--vv-label-size);
      font-weight: var(--vv-label-weight);
      letter-spacing: var(--vv-label-tracking);
      text-transform: uppercase;
      color: var(--vv-text-muted);
    }
    .loryn-mod-stat-value {
      font-family: var(--vv-font-numerals);
      font-size: var(--vv-stat-size);
      font-weight: var(--vv-stat-weight);
      letter-spacing: var(--vv-stat-tracking);
      line-height: var(--vv-stat-leading);
      font-variant-numeric: tabular-nums;
      color: var(--vv-text-primary);
    }
    .loryn-mod-stat-sub {
      font-family: var(--vv-font-label);
      font-size: var(--vv-micro-size);
      font-weight: var(--vv-micro-weight);
      letter-spacing: var(--vv-micro-tracking);
      color: var(--vv-text-muted);
      margin-top: var(--vv-space-hair);
    }

    /* compare — title + grid of stat-shaped tiles side by side. The
       canonical "X vs Y" surface (Tue vs Thu, long vs short, etc.). */
    .loryn-mod-compare-title {
      font-family: var(--vv-font-label);
      font-size: var(--vv-label-size);
      font-weight: var(--vv-label-weight);
      letter-spacing: var(--vv-label-tracking);
      text-transform: uppercase;
      color: var(--vv-text-muted);
      margin-bottom: var(--vv-space-comfort);
    }
    .loryn-mod-compare-grid {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
      gap: var(--vv-space-comfort);
      animation: vvCinematicEnter var(--vv-dur-cinematic) var(--vv-ease-emerge) both;
    }
    .loryn-mod-compare-item {
      display: flex;
      flex-direction: column;
      gap: var(--vv-space-hair);
    }
    .loryn-mod-compare-item-label {
      font-family: var(--vv-font-label);
      font-size: var(--vv-label-size);
      font-weight: var(--vv-label-weight);
      letter-spacing: var(--vv-label-tracking);
      text-transform: uppercase;
      color: var(--vv-text-muted);
    }
    .loryn-mod-compare-item-value {
      font-family: var(--vv-font-numerals);
      font-size: 22px;
      font-weight: 300;
      line-height: 1.12;
      letter-spacing: -0.012em;
      font-variant-numeric: tabular-nums;
      color: var(--vv-text-primary);
    }
    .loryn-mod-compare-item-sub {
      font-family: var(--vv-font-label);
      font-size: var(--vv-micro-size);
      font-weight: var(--vv-micro-weight);
      letter-spacing: var(--vv-micro-tracking);
      color: var(--vv-text-muted);
      line-height: 1.4;
    }

    /* callout — icon glyph + short text. Loryn's POV surface; this is
       where she says the thing she wants you to notice. No header, no
       chrome, just a luminous mark and a sentence. */
    .loryn-mod-callout {
      display: flex;
      align-items: flex-start;
      gap: 14px;
    }
    .loryn-mod-callout-icon {
      width: 18px;
      height: 18px;
      flex: 0 0 18px;
      margin-top: 3px;
      color: var(--accent);
      filter: drop-shadow(0 0 6px rgba(122,184,245,0.45));
    }
    .loryn-mod-callout-icon svg {
      width: 100%;
      height: 100%;
      display: block;
    }
    .loryn-mod-callout-text {
      font-size: 13px;
      line-height: 1.55;
      color: var(--text);
    }

    /* day_card — date + headline + key_fact. The "remember this day"
       surface; Loryn surfaces these when discussing a session's character. */
    .loryn-mod-day {
      display: flex;
      flex-direction: column;
      gap: 8px;
    }
    .loryn-mod-day-date {
      font-size: 10px;
      letter-spacing: 0.16em;
      text-transform: uppercase;
      color: var(--muted);
    }
    .loryn-mod-day-headline {
      font-size: 22px;
      font-weight: 600;
      letter-spacing: -0.01em;
    }
    .loryn-mod-day-fact {
      font-size: 12px;
      color: var(--text);
      line-height: 1.5;
      opacity: 0.88;
    }

    .loryn-setting-row {
      display: flex;
      align-items: center;
      gap: 12px;
      padding: 8px 0;
      border-bottom: 1px solid #181818;
    }
    .loryn-setting-row:last-child { border-bottom: none; }
    .loryn-setting-row label {
      flex: 0 0 160px;
      font-size: 11px;
      color: var(--muted);
      letter-spacing: 0.04em;
    }
    .loryn-setting-sel {
      flex: 1;
      background: #0d0d0d;
      border: 1px solid #1f1f1f;
      color: var(--text);
      font-size: 12px;
      padding: 6px 10px;
      border-radius: 8px;
      font-family: inherit;
      outline: none;
      cursor: pointer;
    }
    .loryn-setting-sel:focus { border-color: var(--accent); }

    /* ── Lock screen — minimal & mystical.
       Just the orb, an unlabelled input bar, and a small Face ID glyph
       beside it. No status text, no placeholder. If you don't know what
       to do here, the screen is not for you. */
    .loryn-lock {
      position: fixed;
      inset: 0;
      z-index: 250;
      background: #000;
      display: none;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      gap: 32px;
      padding: 40px;
      text-align: center;
    }
    .loryn-lock.show { display: flex; }
    /* Lock screen exit. Pure opacity fade — no scale, no blur. Those
       parent transforms/filters were visibly interfering with the orb's
       continuously running internal SVG animations (rings rotating,
       mist breathing), producing the slight stutter on Face ID unlock.
       Clean fade is smooth. */
    .loryn-lock.unlocking {
      animation: lockUnlock 0.62s cubic-bezier(0.22, 1, 0.36, 1) forwards;
    }
    @keyframes lockUnlock {
      0%   { opacity: 1; }
      100% { opacity: 0; }
    }
    /* The orb is the clickable target on the lock screen. The button has
       no visible chrome — it's transparent — so the orb itself is what you
       interact with. Slight scale-on-hover invites the tap. */
    .loryn-lock-orb-btn {
      background: transparent;
      border: none;
      padding: 0;
      width: 160px;
      height: 160px;
      cursor: pointer;
      transition: transform 0.32s cubic-bezier(0.22, 1, 0.36, 1),
                  filter 0.32s ease;
    }
    .loryn-lock-orb-btn:hover { transform: scale(1.05); filter: brightness(1.1); }
    .loryn-lock-orb-btn:active { transform: scale(0.97); }
    .loryn-lock-orb-btn.shake-orb { animation: lockShake 0.42s ease-in-out; }
    /* Make the inner SVG and wrapper non-interactive so taps go straight
       to the button host — eliminates a class of "click registered on
       child element instead of button" bugs that prevent the Face ID
       prompt from firing on iOS. */
    .loryn-lock-orb-btn * { pointer-events: none; }

    /* Greeting under the orb — identical aesthetic to the splash greeting
       (gradient-clipped text, slow brightness emergence). Hidden by
       default; shown after a successful unlock for the welcome beat. */
    .loryn-lock-greeting {
      font-size: clamp(20px, 2.5vw, 28px);
      font-weight: 500;
      letter-spacing: 0.015em;
      white-space: nowrap;
      text-align: center;
      line-height: 1.1;
      background: radial-gradient(ellipse 60% 200% at 50% 50%,
        #aaccef 0%, #7ba8d4 52%, #52749a 100%);
      -webkit-background-clip: text;
      background-clip: text;
      -webkit-text-fill-color: transparent;
      color: transparent;
      text-shadow: 0 0 26px rgba(122,184,245,0.24);
      filter: brightness(0);
      opacity: 0;
      transform: translateY(-4px);
      transition: opacity 0.6s ease, transform 0.6s ease;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
    }
    .loryn-lock-greeting.show { opacity: 1; transform: translateY(0); }

    /* On web origin: the splash is redundant when the lock screen is the
       front door. Suppress it entirely so there's no double-greeting and
       no Enter button after Face ID. The lock screen handles auth +
       welcome + handoff to the journal as one flow. */
    body.web-locked .splash { display: none !important; }
    .loryn-lock-orb { width: 100%; height: 100%; }
    /* Password input is hidden by default. On a device with no passkey
       (or after a Face ID cancel) it slides into view from below the orb. */
    #lorynLockPassword {
      background: var(--vv-void-far);
      border: 1px solid #1c1c1c;
      color: var(--text);
      font-size: 14px;
      padding: 12px 18px;
      border-radius: 22px;
      width: 240px;
      text-align: center;
      letter-spacing: 0.34em;
      outline: none;
      font-family: inherit;
      caret-color: var(--accent);
      display: none;
      opacity: 0;
      transform: translateY(-8px);
      transition: border-color 0.22s ease, background 0.22s ease,
                  box-shadow 0.22s ease, opacity 0.32s ease,
                  transform 0.32s cubic-bezier(0.22, 1, 0.36, 1);
    }
    #lorynLockPassword.show {
      display: block;
      opacity: 1;
      transform: translateY(0);
    }
    #lorynLockPassword:focus {
      border-color: var(--accent);
      background: #0e0e0e;
      box-shadow: 0 0 22px rgba(122,184,245,0.18);
    }
    #lorynLockPassword.shake {
      animation: lockShake 0.42s ease-in-out;
      border-color: #aa3a3a;
    }
    @keyframes lockShake {
      0%, 100% { transform: translateX(0); }
      20%      { transform: translateX(-6px); }
      40%      { transform: translateX(6px); }
      60%      { transform: translateX(-4px); }
      80%      { transform: translateX(4px); }
    }

    .loryn-lock-pair-wrap {
      display: none;
      gap: 12px;
    }
    .loryn-lock-pair-wrap.show { display: inline-flex; }
    .loryn-lock-pair-yes, .loryn-lock-pair-no {
      background: transparent;
      border: 1px solid #2a2a2a;
      color: var(--text);
      font-size: 11px;
      letter-spacing: 0.18em;
      text-transform: uppercase;
      padding: 10px 22px;
      border-radius: 22px;
      cursor: pointer;
      font-family: inherit;
      transition: border-color 0.18s ease, color 0.18s ease, background 0.18s ease;
    }
    .loryn-lock-pair-yes { border-color: var(--accent); color: var(--accent); }
    .loryn-lock-pair-yes:hover { background: rgba(122,184,245,0.08); box-shadow: 0 0 18px rgba(122,184,245,0.18); }
    .loryn-lock-pair-no:hover  { color: var(--accent); border-color: var(--accent); }

    /* ── Mobile pass for Loryn elements ──────────────────────────────── */
    @media (max-width: 600px) {
      /* Orb sits inside the titlebar flex container — no separate
         positioning override needed. Just nudge size down on tight phones. */
      .loryn-orb-btn { width: 30px; height: 30px; }
      .hero-titlebar { gap: 10px; }
      /* Tab cards: stack to single column on phones. */
      .loryn-tab-grid { grid-template-columns: 1fr; gap: 14px; }
      .loryn-tab-header { gap: 12px; padding-bottom: 16px; margin-bottom: 16px; }
      .loryn-tab-orb { width: 42px; height: 42px; }
      .loryn-tab-title { font-size: 22px; }
      .loryn-tab-open-chat { padding: 7px 12px; font-size: 10px; }
      .loryn-card { padding: 16px 16px; }
      .loryn-setting-row { flex-direction: column; align-items: stretch; gap: 6px; }
      .loryn-setting-row label { flex: none; }
      /* Drawer full-bleed on phones with safe-area-inset support for the
         iOS status bar (so the header isn't tucked under the notch). */
      .loryn-drawer-panel { width: 100%; max-width: 100%; }
      .loryn-drawer-header {
        padding-top: max(18px, calc(env(safe-area-inset-top) + 10px));
        padding-left:  max(18px, env(safe-area-inset-left));
        padding-right: max(18px, env(safe-area-inset-right));
      }
      .loryn-drawer-body { padding: 14px 16px; }
      .loryn-drawer-input {
        padding-left:   max(14px, env(safe-area-inset-left));
        padding-right:  max(14px, env(safe-area-inset-right));
      }
      /* Lock screen — give the password input + Face ID button room to
         breathe on a narrow column. */
      #lorynLockPassword { width: 100%; max-width: 240px; }
      .loryn-lock-pair-wrap.show { flex-direction: column; gap: 8px; }

      /* "Show all trades" fix — mobile renders rows as cards with
         display:grid, which has higher specificity than the desktop
         display:none on .tr-extra. Re-assert the hidden state with
         the same specificity so collapsed mode actually hides the
         overflow trades. */
      .trades-col table.trades-collapsed tr.tr-extra { display: none; }
    }

    /* ──────────────────────────────────────────────────────────────
       PHONE COMMAND MODE — voice-first chamber on ≤720px viewports

       On phone, the chamber's wide composition + multi-surface staging
       doesn't fit. Command becomes voice-only: chamber chrome
       (conversation thread, horizon-line input) is hidden; the orb is
       centered as the sole interface; tapping it surfaces a
       bottom-sheet CTA (built by command-layout.js) that confirms the
       user wants to talk to Loryn before voice mode opens.

       Drawer chat still handles typed conversation — Command on phone
       is voice and only voice.
       ────────────────────────────────────────────────────────────── */

    @media (max-width: 720px) {
      /* All of the phone Command rules are double-gated: the @media
         query scopes them to phone viewports, AND `.phone-command-active`
         on body must be present. The body class is toggled by
         command-layout.js based on the PHONE_COMMAND_ENABLED kill
         switch + viewport. Flip the kill switch off and these rules
         stop matching — the chamber returns to its desktop shape on
         phone too. */

      /* Hide chamber chrome. The conversation thread (manifestation
         field + module deck) and the horizon-line input both go away
         on phone. The orb is left as the only chamber element. */
      body.phone-command-active.in-atrium .atrium-convo,
      body.phone-command-active.in-atrium .atrium-line {
        display: none !important;
      }

      /* Center the orb vertically inside the chamber. Override the
         existing top-padding-positioned orb zone since on phone the
         orb is the entire interface. */
      body.phone-command-active.in-atrium .atrium-orb-zone {
        position: absolute;
        inset: 0;
        padding: 0;
        display: flex;
        align-items: center;
        justify-content: center;
        pointer-events: none;
      }
      body.phone-command-active.in-atrium .atrium-orb {
        width: 160px;
        height: 160px;
        pointer-events: auto;
      }
      /* The earlier atrium-active rule shrinks the orb to 56px once
         a conversation begins. On phone we never enter the typed
         conversation flow inside the chamber, so revert that — the
         orb stays large and central regardless of atrium-active. */
      body.phone-command-active.atrium-active.in-atrium .atrium-orb {
        --orb-base-scale: 0.8;
      }
      body.phone-command-active.atrium-active.in-atrium .atrium-orb-zone {
        padding: 0;
      }

      /* Bottom-sheet voice CTA — DOM injected lazily by
         command-layout.js on first orb tap. Hidden by default;
         .open slides it up. The backdrop dims the chamber further so
         the CTA reads as a focused decision moment. */
      .atrium-voice-cta {
        position: fixed;
        inset: 0;
        z-index: 1000;
        pointer-events: none;
        display: flex;
        flex-direction: column;
        justify-content: flex-end;
      }
      .atrium-voice-cta-backdrop {
        position: absolute;
        inset: 0;
        background: rgba(0, 0, 0, 0);
        backdrop-filter: blur(0);
        -webkit-backdrop-filter: blur(0);
        transition: background 320ms ease, backdrop-filter 320ms ease,
                    -webkit-backdrop-filter 320ms ease;
      }
      .atrium-voice-cta-panel {
        position: relative;
        padding: 36px 24px max(36px, calc(env(safe-area-inset-bottom) + 28px));
        display: flex;
        flex-direction: column;
        align-items: center;
        gap: 18px;
        transform: translateY(100%);
        transition: transform 380ms cubic-bezier(0.32, 0.7, 0.32, 1);
      }
      .atrium-voice-cta.open {
        pointer-events: auto;
      }
      .atrium-voice-cta.open .atrium-voice-cta-backdrop {
        background: rgba(0, 0, 0, 0.55);
        backdrop-filter: blur(12px);
        -webkit-backdrop-filter: blur(12px);
      }
      .atrium-voice-cta.open .atrium-voice-cta-panel {
        transform: translateY(0);
      }
      .atrium-voice-cta-label {
        font-size: 10px;
        letter-spacing: 0.32em;
        text-transform: uppercase;
        color: #8a909a;
        font-weight: 400;
      }
      .atrium-voice-cta-button {
        border: 1px solid rgba(122,184,245,0.55);
        background: rgba(122,184,245,0.08);
        color: #dde2e9;
        padding: 14px 28px;
        border-radius: 999px;
        font-size: 13.5px;
        font-weight: 400;
        letter-spacing: 0.04em;
        font-family: inherit;
        cursor: pointer;
        display: flex;
        align-items: center;
        gap: 10px;
        box-shadow: 0 0 28px rgba(122,184,245,0.22),
                    0 0 6px rgba(122,184,245,0.18) inset;
        -webkit-tap-highlight-color: transparent;
      }
      .atrium-voice-cta-button:active {
        background: rgba(122,184,245,0.14);
      }
      .atrium-voice-cta-mic {
        width: 14px;
        height: 14px;
        color: var(--accent);
      }
      .atrium-voice-cta-dismiss {
        border: none;
        background: transparent;
        color: #6e747f;
        padding: 6px 14px;
        font-size: 10px;
        letter-spacing: 0.28em;
        text-transform: uppercase;
        font-family: inherit;
        cursor: pointer;
        -webkit-tap-highlight-color: transparent;
      }
    }

