/* Playground Demo — app.css
   Sections copied verbatim from Playground's templates/index.html so the demo
   matches the app; line ranges noted per section. Demo-only rules live in demo.css. */

/* ── design tokens (index.html:31-157) ── */
  :root {
    /* ── Neutrals ─────────────────────────────── */
    --canvas:        #F5F6F8;   /* app background      */
    --surface:       #FFFFFF;   /* cards, panels       */
    --surface-2:     #F7F8FA;   /* inset, inputs       */
    --surface-3:     #EEF0F4;   /* hover, wells        */
    --surface-4:     #E4E7EC;   /* image placeholder   */
    --border:        #E4E7EC;
    --border-strong: #D0D5DD;

    --ink:    #111318;   /* primary text — near black  */
    --ink-2:  #454C5E;   /* secondary                  */
    --ink-3:  #6B7385;   /* muted / captions           */
    --ink-on-color: #FFFFFF;

    /* ── Brand ────────────────────────────────── */
    --brand:        #7C3AED;
    --brand-ink:    #6425C7;
    --brand-hover:  #6D28D9;
    --brand-tint:   #EDE3FC;
    --brand-tint-2: #F6F1FE;

    /* ── Semantic ─────────────────────────────── */
    --green:      #0ACF83;  --green-ink:  #047952;  --green-tint:  #DDF8EE;
    --red:        #F24E1E;  --red-ink:    #C13209;  --red-tint:    #FDE6DF;
    --danger:     var(--red-ink);   /* referenced by inline error styles */
    --amber:      #F5A524;  --amber-ink:  #8A5A05;  --amber-tint:  #FEF2E0;
    --blue:       #1A8CFF;  --blue-ink:   #0A5FC2;  --blue-tint:   #DFEFFF;

    /* ── Entity colours ───────────────────────────
       Locations: one fixed blue for every project.
       Objects:   one fixed neutral for every project.
       Characters get everything else (see --char-* below). */
    --loc:       #1A8CFF;  --loc-ink:  #0A5FC2;  --loc-tint:  #DFEFFF;
    --obj:       #98A0B3;  --obj-ink:  #555C6E;  --obj-tint:  #EDEFF3;

    /* ── Character rotation (8) ───────────────────
       Hash-assigned per name so a character keeps their colour
       across every stage. Excludes location blue, object grey
       and brand violet by design. */
    --char-1:  #0ACF83;  --char-1-ink: #047952;   /* emerald */
    --char-2:  #E11FD4;  --char-2-ink: #9C1A93;   /* fuchsia */
    --char-3:  #FF7262;  --char-3-ink: #C03A22;   /* orange  */
    --char-4:  #12C0C0;  --char-4-ink: #0A6E70;   /* teal    */
    --char-5:  #FF3D8B;  --char-5-ink: #B5155A;   /* rose    */
    --char-6:  #F5A524;  --char-6-ink: #8A5A05;   /* amber   */
    --char-7:  #84CC16;  --char-7-ink: #4A6E0A;   /* lime    */
    --char-8:  #4F62F5;  --char-8-ink: #2F3FC4;   /* indigo  */

    /* Fallbacks — _charVars() overrides these inline, per element. */
    --char-color: var(--ink-3);
    --char-ink:   var(--ink-2);
    --char-tint:  var(--surface-3);

    /* ── Radii ────────────────────────────────── */
    --r-xs: 4px;
    --r-sm: 6px;
    --r:    8px;
    --r-lg: 12px;
    --r-xl: 16px;
    --r-pill: 999px;
    --radius:    var(--r);      /* legacy aliases */
    --radius-lg: var(--r-lg);

    /* ── Elevation — light mode leans on hairlines,
       shadows stay soft and low-spread. ─────────── */
    --sh-1: 0 1px 2px rgba(16,24,40,0.05);
    --sh-2: 0 2px 4px rgba(16,24,40,0.06), 0 1px 2px rgba(16,24,40,0.04);
    --sh-3: 0 8px 20px rgba(16,24,40,0.10), 0 2px 6px rgba(16,24,40,0.05);
    --sh-4: 0 20px 48px rgba(16,24,40,0.16), 0 4px 12px rgba(16,24,40,0.08);

    /* ── Type ─────────────────────────────────── */
    --font: 'DM Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
    --font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, monospace;
    --font-serif: Georgia, 'Iowan Old Style', 'Times New Roman', serif;

    /* ── Motion ───────────────────────────────── */
    --t-fast: 120ms cubic-bezier(0.4, 0, 0.2, 1);
    --t:      160ms cubic-bezier(0.4, 0, 0.2, 1);
    --t-slow: 240ms cubic-bezier(0.4, 0, 0.2, 1);
    /* Landing composer dock/expand transitions only — 25% slower than
       --t so the chatbox's shape/position shifts read as deliberate. */
    --t-lc:   200ms cubic-bezier(0.4, 0, 0.2, 1);

    /* ── Layout ───────────────────────────────── */
    --sidebar-w: 260px;

    /* ── Script-stage aliases ─────────────────────
       The script stage shipped its own token set. They now point
       at the global scale so both naming systems stay in lockstep. */
    --sp-panel-bg:   var(--surface);
    --sp-line:       var(--border);
    --sp-inset:      var(--surface-2);
    --sp-title:      var(--ink);
    --sp-body:       var(--ink-2);
    --sp-muted:      var(--ink-3);
    --sp-amber:      var(--brand);
    --sp-amber-tint: var(--brand-tint);
    --sp-danger-text: var(--red-ink);
    --sp-danger-bg:   var(--red-tint);
    --sp-serif:      var(--font-serif);

    /* ── Legacy aliases (kept so inline styles in JS resolve) ── */
    --bg:          var(--canvas);
    --bg-raised:   var(--surface);
    --bg-card:     var(--surface);
    --bg-input:    var(--surface-2);
    --border-light: var(--border-strong);
    --text:        var(--ink);
    --text-dim:    var(--ink-2);
    --text-muted:  var(--ink-3);
    --accent:      var(--brand);
    --accent-hover: var(--brand-hover);
    --accent-dim:  var(--brand-ink);
    --purple:      var(--brand);
  }

  /* ══════════════════════════════════════════════
     CURSOR
     Chunky Figma-style arrow. Hotspot sits on the tip (6,2).
     Dark fill by default, brand violet on anything clickable.
     ══════════════════════════════════════════════ */
  :root {
    --cur-default: auto;
    --cur-pointer: pointer;
  }


/* ── base (index.html:157-) ── */

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

  /* overscroll-behavior-x: a two-finger swipe to the left is also the
     Mac's "back" gesture, and a pan that wanders sideways over the
     sidebar would leave the app. Chrome and Firefox honour this on the
     root; Safari does not, and shell.js cancels the wheel event there. */
  html { cursor: var(--cur-default); overscroll-behavior-x: none; }

  body {
    font-family: var(--font);
    background: var(--canvas);
    color: var(--ink);
    min-height: 100vh;
    overflow-x: hidden;
    overscroll-behavior-x: none;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    cursor: var(--cur-default);
  }

  /* Clickable things get the violet arrow — same silhouette, brand fill. */
  a, button, select, summary, label.icon-btn,
  .btn, .sp-btn, .step, .entry-card, .icon-btn, .image-card img,
  .panel-cell, .panel-header, .version-badge, .switch, .checkbox-row,
  .toast-dismiss-all, .regen-opt, .upscale-regen-thumb,
  .upscale-regen-thumb-add, .ref-img-thumb-wrap img, .ref-img-upload-label,
  .panel-comment-btn, .video-player-wrap, .lightbox, .vp-progress,
  .vfs-progress, .panel-row-handle, [onclick] {
    cursor: var(--cur-pointer);
  }

  input, textarea, [contenteditable] { cursor: text; }
  .btn:disabled, .sp-btn:disabled, .icon-btn:disabled,
  button:disabled, .step.disabled { cursor: not-allowed; }
  .panel-row { cursor: grab; }
  .panel-row:active { cursor: grabbing; }

  ::selection { background: var(--brand-tint); color: var(--brand-ink); }

  /* Scrollbars — light, unobtrusive */
  * { scrollbar-width: thin; scrollbar-color: var(--border-strong) transparent; }
  ::-webkit-scrollbar { width: 10px; height: 10px; }
  ::-webkit-scrollbar-track { background: transparent; }
  ::-webkit-scrollbar-thumb {
    background: var(--border-strong);
    border-radius: var(--r-pill);
    border: 3px solid var(--canvas);
  }
  ::-webkit-scrollbar-thumb:hover { background: #B9C0CD; }

  /* ══════════════════════════════════════════════
     LAYOUT
     ══════════════════════════════════════════════ */
  /* Exactly the viewport, never taller. The sidebar's project list scrolls
     inside .pg-nav-list and .main scrolls inside itself, so the page never
     gains a scrollbar of its own. Without a definite height here, .app
     stretched to the full project list once enough projects existed: .main
     stayed capped at 100vh and the body's --canvas showed through as a grey
     band below it, and the whole page scrolled under the open canvas. */
  .app { display: flex; height: 100vh; height: 100dvh; min-height: 100vh; min-height: 100dvh; overflow: hidden; }

  .main {
    flex: 1;
    padding: 32px 40px 64px;
    overflow-y: auto;
    max-height: 100vh;
    background: var(--surface);
  }


/* ── icon buttons + tooltips (index.html:343-) ── */
  .icon-btn {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    border-radius: var(--r);
    border: 1px solid var(--border-strong);
    background: var(--surface);
    box-shadow: var(--sh-1);
    transition: all var(--t-fast);
    flex-shrink: 0;
    padding: 0;
    box-sizing: border-box;
    color: var(--ink-2);
  }
  .icon-btn svg { stroke: currentColor; transition: stroke var(--t-fast); }
  .icon-btn:hover {
    background: var(--brand-tint);
    border-color: var(--brand);
    color: var(--brand-ink);
    box-shadow: var(--sh-2);
  }
  .icon-btn:active { transform: translateY(1px); box-shadow: none; }
  .icon-btn:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
  .icon-btn:disabled { opacity: 0.45; pointer-events: none; box-shadow: none; }
  .icon-btn input[type="file"] { display: none; }
  label.icon-btn { vertical-align: middle; margin: 0; }

  /* Tooltip — solid colour bubble, white text, small tail. Also used by the
     canvas HUD buttons (.pg-hud-btn), which aren't .icon-btns themselves —
     see .pg-hud-btn's own position:relative, needed for the same reason. */
  .icon-btn::after, .pg-hud-btn::after {
    content: attr(data-tooltip);
    position: absolute;
    bottom: calc(100% + 8px);
    left: 50%;
    transform: translateX(-50%) translateY(3px);
    background: var(--brand-ink);
    color: var(--ink-on-color);
    font-family: var(--font);
    font-size: 11.5px;
    font-weight: 600;
    letter-spacing: 0.1px;
    padding: 5px 9px;
    border-radius: var(--r-sm);
    white-space: nowrap;
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--t), transform var(--t);
    z-index: 60;
    box-shadow: var(--sh-3);
  }
  .icon-btn::before, .pg-hud-btn::before {
    content: "";
    position: absolute;
    bottom: calc(100% + 3px);
    left: 50%;
    transform: translateX(-50%) translateY(3px);
    border: 5px solid transparent;
    border-top-color: var(--brand-ink);
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--t), transform var(--t);
    z-index: 60;
  }
  .icon-btn:hover::after, .icon-btn:hover::before,
  .pg-hud-btn:hover::after, .pg-hud-btn:hover::before {
    opacity: 1;
    transform: translateX(-50%) translateY(0);
  }

  /* Nav-top icon buttons (search, collapse) sit inside .pg-nav, which
     clips overflow to stay within the rail's own width — 60px when
     collapsed, too narrow for any CSS-anchored tooltip bubble to fit
     without being cut off. These get a JS-positioned tooltip instead
     (see PLAYGROUND — PROJECT NAVIGATION SIDEBAR), so the default
     pseudo-element bubble is suppressed here. */
  .pg-nav-top .icon-btn::after,
  .pg-nav-top .icon-btn::before { content: none; }

  .pg-nav-fixed-tooltip {
    position: fixed;
    background: var(--brand-ink);
    color: var(--ink-on-color);
    font-family: var(--font);
    font-size: 11.5px;
    font-weight: 600;
    letter-spacing: 0.1px;
    padding: 5px 9px;
    border-radius: var(--r-sm);
    white-space: nowrap;
    z-index: 2100;
    box-shadow: var(--sh-3);
    pointer-events: none;
  }


/* ── project navigation sidebar (index.html:2727-3054) ── */
  /* ══════════════════════════════════════════════
     PLAYGROUND — PROJECT NAVIGATION SIDEBAR

     Always-visible left rail in the main app shell (see .app). Reuses
     .icon-btn and .divider recipes — no new colours, radii or shadows.

     --pg-nav-w on :root is the single source of truth for the rail's
     width; the canvas shell's floating title/HUD pills read the same
     variable so they never sit under the rail once the canvas returns.
     ══════════════════════════════════════════════ */
  :root { --pg-nav-w: 272px; }

  .pg-shell .pg-title,
  .pg-shell .pg-hud { left: calc(var(--pg-nav-w) + 22px); }
  .pg-shell.pg-nav-collapsed .pg-title,
  .pg-shell.pg-nav-collapsed .pg-hud { left: 82px; }

  /* Always-visible rail in the main app shell (see .app), not the
     canvas overlay — .pg-shell is dealt with separately later. */
  .pg-nav {
    position: relative;
    z-index: 11;
    width: var(--pg-nav-w);
    flex: none;
    display: flex;
    flex-direction: column;
    background: var(--surface);
    border-right: 1px solid var(--border);
    box-shadow: var(--sh-2);
    overflow: hidden;
    transition: width var(--t-slow);
  }
  .pg-nav.is-resizing { transition: none; }
  .pg-nav.is-collapsed { width: 60px; }

  .pg-nav-top {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 16px 8.75px 12px 14px;
    flex-shrink: 0;
  }
  .pg-nav.is-collapsed .pg-nav-top {
    flex-direction: column;
    align-items: center;
    padding: 16px 8px 12px;
  }

  .pg-nav-brand {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
    overflow: hidden;
  }
  .pg-nav-brand-star { width: 15px; height: 15px; color: var(--brand); flex: none; }
  .pg-nav-brand-star svg { width: 100%; height: 100%; display: block; }
  .pg-nav-brand-label {
    font-size: 16.5px;
    font-weight: 400;
    letter-spacing: -0.2px;
    color: var(--ink);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .pg-nav.is-collapsed .pg-nav-brand-label { display: none; }
  .pg-nav.is-collapsed .pg-nav-brand-star { width: 20px; height: 20px; }

  .pg-nav-top-actions { display: flex; align-items: center; gap: 3.5px; flex: none; }
  .pg-nav-top-actions .icon-btn { width: 28px; height: 28px; }
  .pg-nav.is-collapsed .pg-nav-top-actions { flex-direction: column; }
  .pg-nav.is-collapsed .pg-nav-search-btn { display: none; }

  /* Nav-top icon buttons (search, collapse) — no box, just an icon
     colour swap on hover/active. */
  .pg-nav-top-actions .icon-btn {
    border-color: transparent;
    background: transparent;
    box-shadow: none;
  }
  .pg-nav-top-actions .icon-btn:hover {
    background: transparent;
    border-color: transparent;
    color: var(--brand-ink);
    box-shadow: none;
  }
  .pg-nav-search-btn.is-active {
    background: transparent;
    border-color: transparent;
    color: var(--brand-ink);
  }

  .pg-nav-search-row { display: none; padding: 0 14px 10px; flex-shrink: 0; }
  .pg-nav-search-row.is-open { display: block; }
  .pg-nav.is-collapsed .pg-nav-search-row { display: none; }
  .pg-nav-search-input {
    width: 100%;
    box-sizing: border-box;
    padding: 8px 10px;
    font: inherit;
    font-size: 13px;
    color: var(--ink);
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--r);
    transition: border-color var(--t-fast), background var(--t-fast);
  }
  .pg-nav-search-input:focus {
    outline: none;
    border-color: var(--brand);
    background: var(--surface);
  }

  .pg-nav-newproj {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 0 12px 10px;
    padding: 9px 10px;
    border-radius: var(--r);
    border: 1px solid transparent;
    background: transparent;
    color: var(--ink-2);
    font-family: var(--font);
    font-size: 13px;
    font-weight: 600;
    text-align: left;
    flex-shrink: 0;
    transition: color var(--t-fast);
  }
  .pg-nav-newproj:hover { color: var(--brand-ink); }
  .pg-nav-newproj-icon { width: 14px; height: 14px; flex: none; display: flex; }
  .pg-nav-newproj-icon svg { width: 100%; height: 100%; display: block; }
  .pg-nav.is-collapsed .pg-nav-newproj {
    justify-content: center;
    margin: 0 8px 10px;
    padding: 9px;
  }
  .pg-nav.is-collapsed .pg-nav-newproj-label { display: none; }

  .pg-nav-divider { border: none; border-top: 1px solid var(--border); margin: 0 14px 8px; flex-shrink: 0; }
  .pg-nav.is-collapsed .pg-nav-divider { display: none; }

  .pg-nav-list {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    padding: 0 8px 12px;
  }
  .pg-nav.is-collapsed .pg-nav-list { display: none; }

  .pg-nav-section-label {
    padding: 10px 8px 6px;
    font-size: 10px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 1.2px;
    color: var(--ink-3);
  }
  .pg-nav-section-label:first-child { padding-top: 2px; }

  /* Fixed rail for bookmarked projects — sits between "+ New" and the
     divider above the scrollable list, so it never scrolls out of view. */
  .pg-nav-pinned { padding: 0 8px; flex-shrink: 0; }
  .pg-nav-pinned:empty { display: none; }
  .pg-nav.is-collapsed .pg-nav-pinned,
  .pg-nav.is-collapsed .pg-nav-pinned-divider { display: none; }

  .pg-nav-row {
    display: flex;
    align-items: center;
    padding: 7px 8px;
    border-radius: var(--r);
    font-size: 13px;
    font-weight: 400;
    color: var(--ink-2);
    margin-bottom: 1px;
    text-align: left;
    transition: background var(--t-fast), color var(--t-fast);
  }
  .pg-nav-row:hover { background: var(--surface-3); color: var(--ink); }
  .pg-nav-row.is-pinned { font-weight: 500; }
  .pg-nav-row.is-current {
    background: var(--brand-tint);
    color: var(--brand-ink);
    font-weight: 700;
    cursor: default;
  }
  .pg-nav-row.is-current:hover { background: var(--brand-tint); color: var(--brand-ink); }
  /* The row's clickable surface. A real <button> — not the row div itself
     — because the row also holds the ⋮ menu button, and a button can't
     nest inside another button. Reset to invisible chrome so it reads as
     the same row it always did. */
  .pg-nav-row-btn {
    flex: 1;
    min-width: 0;
    display: block;
    width: 100%;
    font: inherit;
    color: inherit;
    text-align: left;
    background: none;
    border: none;
    padding: 0;
    cursor: pointer;
  }
  .pg-nav-row.is-current .pg-nav-row-btn { cursor: default; }
  .pg-nav-row-btn:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }
  .pg-nav-row-label {
    display: block;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    text-align: left;
  }
  /* Kept in flow (so no space is reserved) but collapsed to zero width
     until the row is hovered or its menu is open — expanding it shrinks
     the label via flex, which is what truncates the text on hover. */
  .pg-nav-menu-btn {
    width: 0;
    height: 22px;
    margin-left: 0;
    flex: none;
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
    border: none;
    background: transparent;
    border-radius: var(--r-sm);
    color: var(--ink-3);
    opacity: 0;
    transition: width var(--t-fast), margin-left var(--t-fast), opacity var(--t-fast),
                background var(--t-fast), color var(--t-fast);
  }
  .pg-nav-menu-btn svg { width: 13px; height: 13px; display: block; flex-shrink: 0; }
  .pg-nav-row:hover .pg-nav-menu-btn,
  .pg-nav-row.is-menu-open .pg-nav-menu-btn {
    width: 22px;
    margin-left: 4px;
    opacity: 1;
  }
  .pg-nav-menu-btn:hover { background: var(--surface-4); color: var(--ink); }
  .pg-nav-row.is-menu-open .pg-nav-menu-btn { background: var(--surface-4); color: var(--ink); }

  /* Floating dropdown for a row's ⋮ menu / right-click context menu.
     Appended to <body> (see .pg-nav-fixed-tooltip above) so it isn't
     clipped by .pg-nav-list's overflow:auto. */
  .pg-nav-row-menu { z-index: 2100; }
  .pg-nav-row-menu .pg-menu-item-danger { color: var(--red-ink); }
  .pg-nav-row-menu .pg-menu-item-danger:hover { background: var(--red-tint); }

  .pg-nav-empty {
    padding: 10px 8px;
    font-size: 12.5px;
    color: var(--ink-3);
  }

  /* Archived rows render inline at the bottom of the same list, dimmed so
     the eye still lands on the active projects above them first. */
  .pg-nav-row.is-archived { opacity: 0.55; }
  .pg-nav-row.is-archived:hover { opacity: 0.85; }

  /* Divider-with-circle toggle between the active rows and the archived
     ones (rendered inline by archivedDividerHtml() — only present in the
     list's HTML when there's at least one archived project). The line
     matches .pg-nav-divider's weight and colour exactly; the circle just
     breaks the line to hold the +/− affordance at its midpoint. */
  .pg-nav-archived-divider {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 21px;
    margin: 2px 0 3px;
    padding: 0;
    border: none;
    background: none;
    cursor: pointer;
  }
  .pg-nav-archived-divider::before {
    content: "";
    position: absolute;
    left: 6px; right: 6px; top: 50%;
    border-top: 1px solid var(--border);
    transform: translateY(-50%);
  }
  .pg-nav-archived-divider-icon {
    position: relative;
    z-index: 1;
    width: 15px;
    height: 15px;
    flex: none;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--border);
    border-radius: 50%;
    background: var(--surface);
    color: var(--border);
    transition: color var(--t-fast), border-color var(--t-fast);
  }
  .pg-nav-archived-divider-icon svg { width: 8px; height: 8px; display: block; }
  .pg-nav-archived-divider:hover .pg-nav-archived-divider-icon { color: var(--ink-3); border-color: var(--ink-3); }

  .pg-nav-resize {
    position: absolute;
    top: 0; right: -4px;
    width: 8px; height: 100%;
    cursor: ew-resize;
    z-index: 5;
    touch-action: none;
  }
  .pg-nav-resize::after {
    content: "";
    position: absolute;
    top: 0; bottom: 0; left: 3px;
    width: 2px;
    background: transparent;
    transition: background var(--t-fast);
  }
  .pg-nav-resize:hover::after,
  .pg-nav.is-resizing .pg-nav-resize::after { background: var(--brand); }
  .pg-nav.is-collapsed .pg-nav-resize { display: none; }


/* ── landing composer / chatbox (index.html:831-1099) ── */
  /* Landing composer (Setup stage) */
  .lc-stage { position: relative; min-height: 60vh; }
  .lc-center-wrap {
    position: fixed;
    left: 50%;
    top: 44%;
    transform: translate(-50%, -50%);
    width: min(640px, 90vw);
    text-align: center;
    transition: top var(--t-lc), transform var(--t-lc), opacity var(--t-lc);
    z-index: 20;
  }
  .lc-center-wrap h1 { font-size: 30px; font-weight: 400; letter-spacing: -0.025em; margin-bottom: 26px; line-height: 1.25; color: var(--ink); }
  .lc-center-wrap h1 .accent { color: var(--brand); }

  .lc-composer {
    background: var(--surface);
    border: 1px solid var(--border-strong);
    border-radius: var(--r-xl);
    box-shadow: var(--sh-2);
    padding: 14px 16px 12px;
    text-align: left;
    transition: box-shadow var(--t-lc), border-color var(--t-lc), padding var(--t-lc), border-radius var(--t-lc), height var(--t-lc);
  }
  .lc-pills { display: flex; gap: 6px; margin-bottom: 11px; }
  .lc-pillbtn {
    font-family: var(--font);
    font-size: 12.5px;
    font-weight: 600;
    padding: 6px 14px;
    border-radius: var(--r-pill);
    border: 1.5px solid var(--border);
    background: var(--surface-2);
    color: var(--ink-3);
    cursor: pointer;
    transition: var(--t);
  }
  /* Each mode has its own colour: tinted at rest, solid when selected. */
  .lc-pillbtn[data-c="brief"]  { --pill: var(--brand); --pill-ink: var(--brand-ink); --pill-tint: var(--brand-tint); }
  .lc-pillbtn[data-c="script"] { --pill: var(--blue);  --pill-ink: var(--blue-ink);  --pill-tint: var(--blue-tint); }
  .lc-pillbtn[data-c="ideas"]  { --pill: var(--green); --pill-ink: var(--green-ink); --pill-tint: var(--green-tint); }
  .lc-pillbtn[data-c] { background: var(--pill-tint); border-color: transparent; color: var(--pill-ink); }
  .lc-pillbtn.on { background: var(--pill, var(--brand)); border-color: var(--pill, var(--brand)); color: #fff; }
  .lc-pillbtn:hover:not(.on) { border-color: var(--pill, var(--border-strong)); }

  .lc-input-row { display: flex; align-items: flex-end; gap: 10px; }
  .lc-clip-btn { border: 0; background: transparent; padding: 4px; cursor: pointer; color: var(--ink); flex-shrink: 0; display: flex; align-items: center; justify-content: center; opacity: .75; transition: var(--t); align-self: flex-start; height: 20.3px; margin-top: 6px; }
  .lc-clip-btn:hover { opacity: 1; }
  .lc-clip-icon { transform: rotate(-45deg); }
  .lc-ph {
    flex: 1;
    min-height: 26px;
    max-height: 14em;
    line-height: 1.4;
    overflow-y: auto;
    color: var(--ink-3);
    font-size: 14.5px;
    font-family: var(--font);
    outline: none;
    white-space: pre-wrap;
    word-break: break-word;
    padding: 6px 0;
  }
  .lc-ph[contenteditable]:empty:before { content: attr(data-ph); color: var(--ink-3); }
  .lc-ph:focus { color: var(--ink); }
  .lc-send { width: 30px; height: 30px; border-radius: 50%; background: var(--brand); color: #fff; border: 0; display: flex; align-items: center; justify-content: center; cursor: pointer; flex-shrink: 0; transition: var(--t); }
  .lc-send:hover { background: var(--brand-hover); }
  .lc-send:disabled { background: var(--border-strong); cursor: not-allowed; opacity: .6; }
  .lc-send:disabled:hover { background: var(--border-strong); }

  .lc-send-wrap { position: relative; display: inline-flex; flex-shrink: 0; }
  .lc-send-wrap[data-tip] .lc-send:disabled { cursor: default; }
  .lc-send-wrap[data-tip]::after {
    content: attr(data-tip);
    position: absolute;
    bottom: calc(100% + 8px);
    right: 0;
    background: var(--brand-ink);
    color: var(--ink-on-color);
    font-family: var(--font);
    font-size: 11.5px;
    font-weight: 600;
    letter-spacing: 0.1px;
    padding: 5px 9px;
    border-radius: var(--r-sm);
    white-space: nowrap;
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--t), transform var(--t);
    transform: translateY(3px);
    z-index: 60;
    box-shadow: var(--sh-3);
  }
  .lc-send-wrap[data-tip]::before {
    content: "";
    position: absolute;
    bottom: calc(100% + 3px);
    right: 10px;
    border: 5px solid transparent;
    border-top-color: var(--brand-ink);
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--t), transform var(--t);
    transform: translateY(3px);
    z-index: 60;
  }
  .lc-send-wrap[data-tip]:hover::after,
  .lc-send-wrap[data-tip]:hover::before {
    opacity: 1;
    transform: translateY(0);
  }

  .lc-files-row { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
  .lc-file-pill {
    display: inline-flex; align-items: center; gap: 6px;
    max-width: 220px;
    background: var(--surface-2); border: 1px solid var(--border);
    border-radius: var(--r-pill); padding: 4px 6px 4px 12px;
    font-size: 12px; color: var(--ink-2);
  }
  .lc-file-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .lc-file-remove {
    border: 0; background: transparent; color: var(--ink-3); cursor: pointer;
    width: 16px; height: 16px; border-radius: 50%; flex-shrink: 0; padding: 0;
    display: flex; align-items: center; justify-content: center; font-size: 14px; line-height: 1;
  }
  .lc-file-remove:hover { background: var(--border); color: var(--ink); }

  .lc-stage.docked .lc-files-row { display: none; }
  /* Rest state (the collapsed 1-row bar) sits over the live canvas, so it's
     centred on the canvas's own visible width — viewport minus the nav
     rail, via --pg-nav-w (kept accurate in both states by setCollapsed())
     — rather than the full window, and sized down to roughly half its
     landing-page width so it reads as a compact handle, not a full bar. */
  .lc-stage.docked .lc-center-wrap {
    top: calc(100vh - 54px);
    left: calc(var(--pg-nav-w) + (100vw - var(--pg-nav-w)) / 2);
    width: min(380px, 46vw);
  }
  .lc-stage.docked .lc-center-wrap h1 { display: none; }
  .lc-stage.docked .lc-pills { display: none; }
  .lc-stage.docked .lc-composer { border-radius: var(--r-pill); padding: 8px 8px 8px 16px; box-shadow: 0 2px 8px rgba(16,24,40,.08); }
  .lc-stage.docked .lc-ph { font-size: 13px; }
  .lc-stage.docked .lc-send { width: 26px; height: 26px; }
  /* Sending isn't wired up for canvas mode yet — the docked row's only
     job right now is expanding into the chat history (see .expanded
     below), so its input controls stay inert and dimmed rather than
     fully disabled. */
  .lc-stage.docked .lc-ph,
  .lc-stage.docked .lc-clip-btn,
  .lc-stage.docked .lc-send-wrap { pointer-events: none; opacity: .6; }
  /* Attaching a document only ever seeds a NEW project, so the clip belongs
     to the centred landing composer alone. Docked means a project is open —
     nothing to attach to — so the button leaves the row entirely.
     _lcApplyLock() also sets its disabled attribute there (and clears it when
     the canvas closes and the composer floats back to the centre), so it is
     out of the tab order and inert even while this rule hides it. */
  .lc-stage.docked .lc-clip-btn:disabled { display: none; }
  .lc-stage.docked .lc-input-row { cursor: pointer; }
  /* Neither the collapsed 1-row bar nor its expanded chat panel take new
     input yet (see above) — an empty placeholder ("Attach or write your
     video idea" etc.) in either reads as a stray, half-dimmed leftover. */
  .lc-stage.docked .lc-ph[contenteditable]:empty:before { content: ""; }

  /* Clicking the docked row expands the SAME composer upward into a tall
     chat panel — no separate overlay, just this element growing in place.
     .lc-history unhides above the input row, which stays pinned at the
     bottom of the panel (and stays the click target that collapses it). */
  .lc-stage.docked.expanded .lc-center-wrap {
    top: 5vh;
    width: min(760px, 92vw);
    transform: translateX(-50%);
  }
  .lc-stage.docked.expanded .lc-composer {
    display: flex;
    flex-direction: column;
    height: 88vh;
    padding: 0;
    border-radius: var(--r-xl);
    box-shadow: var(--sh-4);
  }
  .lc-stage.docked.expanded .lc-input-row { padding: 10px 14px; border-top: 1px solid var(--border); flex-shrink: 0; }

  .lc-history { display: none; }
  .lc-stage.docked.expanded .lc-history {
    display: flex;
    flex-direction: column;
    gap: 10px;
    flex: 1;
    overflow-y: auto;
    padding: 20px;
  }

  .lc-msg-row {
    display: flex;
    align-items: flex-end;
    gap: 6px;
    max-width: 65%;
  }
  .lc-msg-row.user { align-self: flex-end; flex-direction: row-reverse; }
  .lc-msg-row.system { align-self: flex-start; }
  .lc-msg-time {
    flex: 0 0 auto;
    font-size: 10px;
    color: var(--ink-3);
    white-space: nowrap;
  }
  .lc-msg {
    min-width: 0;
    padding: 10px 14px;
    border-radius: var(--r-lg);
    font-size: 14px;
    line-height: 1.5;
    white-space: pre-wrap;
    word-break: break-word;
  }
  .lc-msg.user {
    background: var(--brand);
    color: var(--ink-on-color);
    border-bottom-right-radius: var(--r-sm);
  }
  .lc-msg.system {
    background: var(--surface-2);
    border: 1px solid var(--border);
    color: var(--ink);
    border-bottom-left-radius: var(--r-sm);
  }
  .lc-history-empty { color: var(--ink-3); font-size: 13px; text-align: center; margin-top: 40px; }

  /* A system bubble still waiting on an API response breathes, so it is
     obvious work is happening off-screen. */
  .lc-msg.pending { animation: lc-pulse 1.6s ease-in-out infinite; }
  @keyframes lc-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .5; } }
  @media (prefers-reduced-motion: reduce) { .lc-msg.pending { animation: none; } }

  .lc-msg.error {
    background: var(--red-tint);
    border-color: var(--red);
    color: var(--red-ink);
  }

  /* Bubbles backed by a prompt/response log on disk open it on click. */
  .lc-msg.has-detail { cursor: var(--cur-pointer); }
  .lc-msg-eye {
    display: inline-flex;
    vertical-align: -2px;
    margin-left: 7px;
    opacity: .45;
    transition: opacity var(--t-fast);
  }
  .lc-msg.has-detail:hover .lc-msg-eye { opacity: 1; }

  .lc-detail-pre {
    margin: 0;
    max-height: 58vh;
    overflow: auto;
    white-space: pre-wrap;
    word-break: break-word;
    font-family: var(--font-mono);
    font-size: 11px;
    line-height: 1.7;
    color: var(--ink-2);
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--r-sm);
    padding: 12px;
  }
  .lc-detail-path {
    margin-top: 8px;
    font-family: var(--font-mono);
    font-size: 10px;
    color: var(--ink-3);
    word-break: break-all;
  }

/* ── canvas shell + node cards (index.html:2211-2448) ── */
  .pg-shell {
    position: fixed;
    inset: 0;
    z-index: 10;
    display: none;
    overflow: hidden;
    isolation: isolate;
    background: var(--canvas);
  }
  .pg-shell.is-open { display: block; }
  .pg-shell:focus { outline: none; }
  /* The canvas always sits behind the sidebar, log and chatbox — populated
     or not — so they stay visible and clickable while the canvas is open. */
  .pg-shell.pg-canvas-empty { z-index: 10; }
  /* A modal that lives inside the shell (New project, close-time rename —
     see .pg-modal below) needs to sit above EVERYTHING, sidebar included,
     while it's showing — restores the 2000 the .pg-modal-overlay comment
     already documents, which .pg-shell itself had dropped to 10 for the
     normal open-canvas case above. Reactive to .show so it only applies
     while a modal is actually up. */
  .pg-shell:has(.pg-modal.show) { z-index: 2000; }

  /* ── Viewport (input surface) ────────────────── */
  .pg-viewport {
    position: absolute;
    inset: 0;
    z-index: 0;
    overflow: hidden;
    touch-action: none;
    cursor: grab;
  }
  .pg-viewport.is-panning,
  .pg-viewport.is-space { cursor: grabbing; }

  /* ── Grid — two scales, both derived from --ink ─ */
  .pg-grid {
    --pg-minor: rgba(17, 19, 24, 0.12);
    --pg-major: rgba(17, 19, 24, 0.19);
    position: absolute;
    inset: -2px;
    z-index: 0;
    background-repeat: repeat, repeat;
    background-image:
      radial-gradient(circle at 1px 1px, var(--pg-major) 1.7px, transparent 1.8px),
      radial-gradient(circle at 1px 1px, var(--pg-minor) 1px, transparent 1.1px);
  }
  .pg-grid.is-coarse { --pg-minor: transparent; }

  /* ── World (the thing that pans and zooms) ───── */
  /* will-change is applied by JS (see "crisp zoom" in the Playground block),
     not set here. It pins the layer's raster scale, so while it is on every
     card is a bitmap stretched by scale() — crisp only at the scale it was
     rastered at. It goes on for the duration of a zoom gesture, where that
     pin is what keeps the gesture at 60fps, and comes off once the zoom
     settles so text and edges re-raster sharp at the resting scale.
     The translate3d() in the transform keeps the layer composited either
     way, so panning never repaints regardless. */
  .pg-world {
    position: absolute;
    top: 0; left: 0;
    width: 0; height: 0;
    z-index: 1;
    transform-origin: 0 0;
  }
  .pg-world.is-zooming { will-change: transform; }
  .pg-edges {
    position: absolute;
    top: 0; left: 0;
    width: 1px; height: 1px;
    overflow: visible;
    pointer-events: none;
    z-index: 1;
  }
  .pg-nodes {
    position: absolute;
    top: 0; left: 0;
    width: 0; height: 0;
    z-index: 2;
  }

  /* ══════════════════════════════════════════════
     NODE CARDS — STYLE ONLY

     Nothing renders these yet and no JS references them. They exist so the
     tool nodes added later are already visually correct. The recipe is a
     copy of .panel / .entry-card / .review-card, resized for a canvas.

     Canonical markup lives in the pg-node-template element in the body.
     Set the hue with data-accent="brand|blue|green|amber|rose|teal".
     ══════════════════════════════════════════════ */
  .pg-node {
    --card-accent: var(--brand);
    position: absolute;
    width: 264px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--r-lg);
    box-shadow: var(--sh-2);
    font-family: var(--font);
    overflow: hidden;
    transition: border-color var(--t), box-shadow var(--t);
    /* .pg-viewport sets cursor:grab for canvas panning; without this the
       whole card inherits that hand cursor even where it isn't draggable. */
    cursor: var(--cur-default);
  }
  .pg-node-header::before {
    content: "";
    position: absolute;
    top: 0; left: 0; right: 0;
    height: 3px;
    background: var(--card-accent);
    z-index: 1;
  }
  .pg-node:hover { border-color: var(--border-strong); box-shadow: var(--sh-3); }
  .pg-node.is-selected {
    border-color: var(--brand);
    box-shadow: 0 0 0 3px var(--brand-tint), var(--sh-3);
  }
  .pg-node.is-dragging { box-shadow: var(--sh-4); }
  .pg-node.is-running  { border-color: var(--brand); }
  .pg-node.is-failed   { border-color: var(--red); }

  .pg-node[data-accent="brand"] { --card-accent: var(--brand); }
  .pg-node[data-accent="blue"]  { --card-accent: var(--loc); }
  .pg-node[data-accent="green"] { --card-accent: var(--green); }
  .pg-node[data-accent="amber"] { --card-accent: var(--char-6); }
  .pg-node[data-accent="rose"]  { --card-accent: var(--char-5); }
  .pg-node[data-accent="teal"]  { --card-accent: var(--char-4); }

  .pg-node-header {
    position: relative;
    overflow: hidden;
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 13px 14px;
    min-height: 46px;
    cursor: grab;
    transition: background var(--t-fast);
  }
  .pg-node-header:hover { background: var(--surface-2); }
  .pg-node-header:active { cursor: grabbing; }

  /* Saturated dot — same idea as .character-pill::before */
  .pg-node-dot {
    flex: none;
    width: 7px; height: 7px;
    border-radius: 50%;
    background: var(--card-accent);
  }
  .pg-node-title {
    flex: 1 1 auto;
    min-width: 0;
    font-size: 13.5px;
    font-weight: 600;
    line-height: 1.4;
    letter-spacing: -0.1px;
    color: var(--ink);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .pg-node-badge {
    flex: none;
    display: inline-flex;
    align-items: center;
    padding: 3px 8px;
    border-radius: var(--r-pill);
    background: var(--surface-3);
    color: var(--ink-2);
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    white-space: nowrap;
  }

  .pg-node-body {
    padding: 13px 14px;
    border-top: 1px solid var(--border);
    font-size: 12.5px;
    line-height: 1.55;
    color: var(--ink-2);
  }
  .pg-node-thumb {
    width: 100%;
    aspect-ratio: 16/9;
    border-radius: var(--r);
    background: var(--surface-4);
    object-fit: cover;
    display: block;
  }
  .pg-node-meta {
    font-family: var(--font-mono);
    font-size: 11px;
    color: var(--ink-3);
  }
  .pg-node-footer {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 14px;
    border-top: 1px solid var(--border);
    background: var(--surface-2);
  }

  /* Ports — style only, nothing wires them yet. */
  .pg-port {
    position: absolute;
    top: 50%;
    width: 11px; height: 11px;
    margin-top: -5.5px;
    border-radius: 50%;
    background: var(--surface);
    border: 2px solid var(--border-strong);
    transition: border-color var(--t-fast), transform var(--t-fast), background var(--t-fast);
  }
  .pg-port-in  { left: -6px; }
  .pg-port-out { right: -6px; }
  .pg-port:hover,
  .pg-port.is-active { border-color: var(--brand); background: var(--brand-tint); transform: scale(1.25); }
  .pg-port.is-connected { border-color: var(--card-accent); background: var(--card-accent); }

  /* Connections — style only. */
  .pg-edge {
    fill: none;
    stroke: var(--border-strong);
    stroke-width: 2;
    stroke-linecap: round;
  }
  .pg-edge.is-selected { stroke: var(--brand); }
  .pg-edge.is-pending  { stroke: var(--ink-3); stroke-dasharray: 5 5; }

  /* ── Origin ──────────────────────────────────────
     Sits at world 0,0. A static target mark — not a control — that
     shows only while the canvas has no nodes on it (see
     .pg-shell.pg-canvas-empty below). New projects start from the
     sidebar only, so this never handles input. ── */

/* ── chrome: origin, modal, vignette, close, HUD (index.html:2449-2726) ── */
  .pg-origin {
    display: none;
    position: absolute;
    top: 0; left: 0;
    width: 24px; height: 24px;
    transform: translate(-50%, -50%);
    pointer-events: none;
    user-select: none;
  }
  .pg-shell.pg-canvas-empty .pg-origin { display: block; }
  .pg-origin svg { width: 100%; height: 100%; display: block; }


  /* ── New-project modal ───────────────────────────
     Lives inside .pg-shell: the shell traps focus, and its z-index
     (2000) is above the app's generic modal (300). ── */
  .pg-modal-overlay {
    position: absolute;
    inset: 0;
    z-index: 20;
    display: none;
    background: rgba(17, 19, 24, 0.34);
    backdrop-filter: blur(2px);
  }
  .pg-modal-overlay.show { display: block; }
  .pg-modal {
    position: absolute;
    top: 50%; left: 50%;
    z-index: 21;
    transform: translate(-50%, -50%) scale(0.96);
    width: 400px;
    max-width: calc(100vw - 32px);
    box-sizing: border-box;
    padding: 24px 26px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-top: 3px solid var(--brand);
    border-radius: var(--r-lg);
    box-shadow: var(--sh-4);
    opacity: 0;
    pointer-events: none;
    transition: all var(--t);
  }
  .pg-modal.show { transform: translate(-50%, -50%) scale(1); opacity: 1; pointer-events: auto; }
  /* Opt-in wide variant (showModal opts.wide) — the block-prompt modal with
     its verify panel outgrew the default width and clipped off-screen.
     The generic modal (#generic-modal) is a .regen-modal; .pg-modal is
     covered too in case a shell modal ever opts in. */
  .regen-modal.pg-modal-wide,
  .pg-modal.pg-modal-wide {
    width: min(1120px, calc(100vw - 48px));
    max-width: calc(100vw - 48px);
    max-height: calc(100vh - 40px);
    overflow-y: auto;
  }
  .pg-modal h3 { margin: 0 0 6px; font-size: 16px; }
  .pg-modal-sub {
    margin: 0 0 18px;
    font-size: 12px;
    line-height: 1.5;
    color: var(--ink-3);
  }
  .pg-field { display: block; margin-bottom: 14px; }
  .pg-field > span {
    display: block;
    margin-bottom: 6px;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.3px;
    text-transform: uppercase;
    color: var(--ink-3);
  }
  .pg-field > input {
    width: 100%;
    box-sizing: border-box;
    padding: 9px 11px;
    font: inherit;
    font-size: 13px;
    color: var(--ink);
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--r);
    transition: border-color var(--t-fast), background var(--t-fast);
  }
  .pg-field > input:focus {
    outline: none;
    border-color: var(--brand);
    background: var(--surface);
  }
  .pg-modal-folder {
    font-family: var(--font-mono);
    font-size: 11px;
    line-height: 1.5;
    color: var(--ink-3);
    word-break: break-all;
    margin-bottom: 4px;
    min-height: 17px;
  }
  .pg-modal-error {
    font-size: 12px;
    color: var(--danger, #c0392b);
    min-height: 16px;
    margin-bottom: 10px;
  }
  .pg-modal-actions {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
  }
  .pg-modal-btn {
    padding: 9px 16px;
    font: inherit;
    font-size: 13px;
    font-weight: 600;
    color: var(--ink-2);
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--r);
    cursor: pointer;
    transition: all var(--t-fast);
  }
  .pg-modal-btn:hover { border-color: var(--border-strong); color: var(--ink); }
  .pg-modal-btn.is-primary {
    color: #fff;
    background: var(--brand);
    border-color: var(--brand);
  }
  .pg-modal-btn.is-primary:hover { filter: brightness(1.06); }
  .pg-modal-btn.is-danger { color: var(--red-ink); border-color: #F3B7A4; }
  .pg-modal-btn.is-danger:hover {
    background: var(--red-tint);
    border-color: var(--red);
    color: var(--red-ink);
  }
  /* Pushed to the far side of the actions row: destructive, and not the
     button anyone should reach for on the way past. Three actions only clear
     the 400px modal if none of them breaks its label over two lines. */
  #pg-closerename-discard { margin-right: auto; }
  #pg-closerename .pg-modal-btn { white-space: nowrap; }
  .pg-modal-sub.is-warning { color: var(--red-ink); }
  .pg-modal-btn[disabled] { opacity: 0.55; cursor: default; filter: none; }

  /* ── Vignette ────────────────────────────────── */
  .pg-vignette {
    position: absolute;
    inset: 0;
    z-index: 3;
    pointer-events: none;
    background: radial-gradient(125% 95% at 50% 45%, transparent 54%, rgba(17, 19, 24, 0.05) 100%);
  }

  /* ── Chrome ──────────────────────────────────── */
  .pg-chrome {
    position: absolute;
    inset: 0;
    z-index: 10;
    pointer-events: none;
  }
  /* Present as soon as the shell opens — no fade/slide-in. */
  .pg-chrome > * {
    pointer-events: auto;
    transition: color var(--t-fast), background var(--t-fast), border-color var(--t-fast);
  }

  .pg-title {
    position: absolute;
    top: 18px; left: 22px;
    display: flex;
    align-items: center;
    gap: 9px;
    height: 36px;
    padding: 0 15px 0 12px;
    border-radius: var(--r-pill);
    border: 1px solid var(--border);
    background: var(--surface);
    box-shadow: var(--sh-1);
    font-size: 13px;
    font-weight: 600;
    letter-spacing: -0.1px;
    color: var(--ink);
    user-select: none;
  }
  .pg-title-star { width: 14px; height: 14px; color: var(--brand); flex: none; }
  .pg-title-star svg { width: 100%; height: 100%; display: block; }
  .pg-title-sub {
    font-family: var(--font-mono);
    font-size: 11px;
    font-weight: 400;
    color: var(--ink-3);
    padding-left: 9px;
    margin-left: 1px;
    border-left: 1px solid var(--border);
  }

  /* Mirrors .icon-btn, two pixels larger for a floating context. */
  .pg-close {
    position: absolute;
    top: 18px; right: 22px;
    width: 36px; height: 36px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--r);
    border: 1px solid var(--border-strong);
    background: var(--surface);
    color: var(--ink-2);
    box-shadow: var(--sh-1);
  }
  .pg-close:hover {
    color: var(--red-ink);
    border-color: #F3B7A4;
    background: var(--red-tint);
  }
  .pg-close:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
  .pg-close svg { width: 15px; height: 15px; display: block; }

  .pg-hud {
    position: absolute;
    /* bottom puts this pill's own centre (height/2 above it) on the same
       horizontal line as the docked chat composer's collapsed row, whose
       centre sits at a fixed 54px above the viewport bottom (see
       .lc-stage.docked .lc-center-wrap) — 54 - 36/2 = 36. */
    left: 22px; bottom: 36px;
    display: flex;
    align-items: center;
    gap: 1px;
    height: 36px;
    padding: 0 5px;
    border-radius: var(--r-pill);
    border: 1px solid var(--border);
    background: var(--surface);
    box-shadow: var(--sh-1);
  }
  .pg-hud-btn {
    position: relative;
    width: 28px; height: 28px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 0;
    background: transparent;
    border-radius: var(--r-sm);
    color: var(--ink-2);
    font-family: var(--font);
    font-size: 15px;
    font-weight: 600;
    line-height: 1;
    transition: background var(--t-fast), color var(--t-fast);
  }
  .pg-hud-btn:hover { background: var(--surface-3); color: var(--ink); }
  .pg-hud-btn:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }
  .pg-hud-zoom {
    min-width: 48px;
    text-align: center;
    font-family: var(--font-mono);
    font-size: 11.5px;
    font-weight: 500;
    color: var(--ink-2);
    user-select: none;
  }
  .pg-hud-sep { width: 1px; height: 17px; background: var(--border); margin: 0 5px; }

  @media (max-width: 768px) {
    .pg-title-sub { display: none; }
    .pg-title { top: 14px; left: 14px; }
    .pg-close { top: 14px; right: 14px; }
    .pg-hud   { left: 14px; bottom: 36px; }
  }

  @media (pointer: coarse) {
    .pg-viewport, .pg-viewport.is-panning, .pg-viewport.is-space,
    .pg-node-header { cursor: auto; }
  }

  @media print {
    .pg-shell { display: none !important; }
  }


/* ── ports, edges, add menu, note, LOD, buttons, fields (index.html:3055-3495) ── */
  /* ══════════════════════════════════════════════
     PLAYGROUND — GRAPH LAYER (phase 1)

     Adds ports, edges, selection and the add-node menu on top of the
     canvas foundation. Still no pipeline data: phase 2 brings that in.

     Everything reuses the tokens already defined above. Nothing new.
     ══════════════════════════════════════════════ */

  /* Ports overhang the card edge, so the card can no longer clip.
     The header and footer take the corner radii instead, which keeps
     their hover fills inside the rounded silhouette. */
  .pg-node { overflow: visible; }
  .pg-node-header { border-radius: var(--r-lg) var(--r-lg) 0 0; }
  .pg-node-footer { border-radius: 0 0 var(--r-lg) var(--r-lg); }
  /* :not(.is-unfolded) — a card opened up inside the overview (see the
     unfolded-card block in the state-bridge styles) shows its body even
     if the user had folded it by hand; the fold is still there when the
     overview comes down. */
  .pg-node.is-collapsed:not(.is-unfolded) .pg-node-body,
  .pg-node.is-collapsed:not(.is-unfolded) .pg-node-ports { display: none; }
  .pg-node.is-collapsed:not(.is-unfolded) .pg-node-header { border-radius: var(--r-lg); }

  .pg-node-ports {
    padding: 7px 0;
    border-top: 1px solid var(--border);
  }
  .pg-port-row {
    position: relative;
    display: flex;
    align-items: center;
    height: 23px;
    padding: 0 14px;
    font-family: var(--font-mono);
    font-size: 11px;
    color: var(--ink-3);
    white-space: nowrap;
  }
  .pg-port-row.is-out { justify-content: flex-end; text-align: right; }
  .pg-port-row.is-in  .pg-port { left: -6px; }
  .pg-port-row.is-out .pg-port { right: -6px; }
  .pg-port-row:hover { color: var(--ink-2); }

  /* A port bound to an arbitrary element inside the body (phase 2 binds
     one to each panel thumbnail). The anchor must be position:relative. */
  .pg-port-anchor { position: relative; }

  /* Hit target — invisible, thick, catches clicks on thin curves. */
  .pg-edge-hit {
    fill: none;
    stroke: transparent;
    stroke-width: 14;
    pointer-events: stroke;
    cursor: var(--cur-pointer);
  }
  .pg-edges { pointer-events: none; }
  .pg-edges .pg-edge-group { pointer-events: auto; }
  .pg-edge-group:hover .pg-edge { stroke: var(--ink-3); stroke-width: 2.5; }
  .pg-edge-group.is-selected .pg-edge { stroke: var(--brand); stroke-width: 2.5; }
  .pg-edge-pending { pointer-events: none; }

  /* Ports light up while a compatible connection is being dragged. */
  .pg-shell.is-linking .pg-port { transition: none; }
  .pg-port.is-candidate {
    border-color: var(--brand);
    background: var(--brand-tint);
    transform: scale(1.35);
  }
  .pg-port.is-blocked { opacity: 0.35; }

  /* ── Add-node menu (right-click on empty canvas) ── */
  .pg-menu {
    position: fixed;
    z-index: 20;
    min-width: 184px;
    padding: 5px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--r-lg);
    box-shadow: var(--sh-4);
    font-family: var(--font);
  }
  .pg-menu-label {
    padding: 7px 10px 5px;
    font-size: 10.5px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    color: var(--ink-3);
  }
  .pg-menu-item {
    display: flex;
    align-items: center;
    gap: 9px;
    width: 100%;
    padding: 8px 10px;
    border: 0;
    background: transparent;
    border-radius: var(--r-sm);
    font-family: var(--font);
    font-size: 13px;
    font-weight: 500;
    color: var(--ink);
    text-align: left;
    cursor: var(--cur-pointer);
    transition: background var(--t-fast);
  }
  .pg-menu-item:hover { background: var(--surface-3); }
  /* Add-node menu only: differentiates the older node set from the newer
     card systems (Brand Context / Script / Video Brief, ...) sorted above
     them — see openMenu() in the graph layer. */
  .pg-menu-item-legacy { font-style: italic; }
  .pg-menu-dot { width: 7px; height: 7px; border-radius: 50%; flex: none; }

  /* Add-node menu only (.pg-menu-add), never the nav row menu. It lists
     every registered type — 23 and counting, ~800px — which overflows a
     13" laptop with no way to reach the entries past the fold: openMenu can
     nudge the menu upward but it cannot shrink it. Cap it and scroll. */
  .pg-menu-add {
    max-height: 62vh;
    overflow-y: auto;
    overscroll-behavior: contain;
  }
  .pg-menu-add .pg-menu-label {
    position: sticky;
    top: -5px;                     /* cancels the menu's own 5px padding */
    background: var(--surface);
    z-index: 1;
  }
  .pg-menu-group {
    padding: 9px 10px 4px;
    margin-top: 5px;
    border-top: 1px solid var(--border);
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    color: var(--ink-3);
  }
  .pg-menu-label + .pg-menu-group { margin-top: 0; border-top: 0; }

  /* ── Note node body ── */
  .pg-note-text {
    width: 100%;
    min-height: 76px;
    resize: vertical;
    border: 1px solid var(--border);
    border-radius: var(--r);
    background: var(--surface-2);
    padding: 9px 10px;
    font-family: var(--font);
    font-size: 12.5px;
    line-height: 1.55;
    color: var(--ink);
  }
  .pg-note-text:focus { outline: none; border-color: var(--brand); background: var(--surface); }

  /* ══════════════════════════════════════════════
     PLAYGROUND — STATE BRIDGE (phase 2)
     Scene grid cards, sink nodes, zoom level-of-detail.
     Read-only: nothing here writes to the pipeline.
     ══════════════════════════════════════════════ */

  .pg-scene-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 5px;
  }
  .pg-scene-cell {
    position: relative;
    aspect-ratio: 16/9;
    border-radius: var(--r-sm);
    background: var(--surface-4);
    overflow: visible;
    box-shadow: inset 0 0 0 1px var(--border);
    transition: box-shadow var(--t-fast);
  }
  .pg-scene-cell img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    border-radius: var(--r-sm);
    display: block;
  }
  .pg-scene-cell:hover { box-shadow: inset 0 0 0 1px var(--border-strong); }
  .pg-scene-cell.is-sb  { box-shadow: inset 0 0 0 2px var(--green); }
  .pg-scene-cell.is-vid { box-shadow: inset 0 0 0 2px var(--char-4); }
  .pg-scene-cell.is-sb.is-vid {
    box-shadow: inset 0 0 0 2px var(--green), 0 0 0 2px var(--char-4);
  }
  .pg-scene-num {
    position: absolute;
    left: 3px; bottom: 3px;
    padding: 1px 4px;
    border-radius: 3px;
    background: rgba(17, 19, 24, 0.62);
    color: #fff;
    font-family: var(--font-mono);
    font-size: 9px;
    line-height: 1.5;
    pointer-events: none;
  }

  /* Per-panel ports stay out of the way until they matter. */
  .pg-scene-cell .pg-port { opacity: 0; }
  .pg-scene-cell:hover .pg-port,
  .pg-scene-cell .pg-port.is-connected,
  .pg-shell.is-linking .pg-scene-cell .pg-port { opacity: 1; }

  /* Level of detail — thumbnails are noise when zoomed way out. Every
     far-zoom rule here and in the overview block below is scoped to
     .pg-node:not(.is-unfolded), so a card the user opens up inside the
     overview (see the unfolded-card block further down) gets its normal
     styling back without any of it being restated. */
  .pg-shell.pg-lod-far .pg-node:not(.is-unfolded) .pg-scene-cell img { visibility: hidden; }
  .pg-shell.pg-lod-far .pg-node:not(.is-unfolded) .pg-scene-num { display: none; }
  .pg-shell.pg-lod-far .pg-node:not(.is-unfolded) .pg-node-ports { display: none; }
  /* Same effect as a manually collapsed card (.pg-node.is-collapsed below) —
     below the floor a card's body is unreadable anyway, so skip rendering
     it rather than paying for a hundred illegible ones. Reverts on its own
     the moment the shell zooms back past LOD_ZOOM; a card the user
     collapsed on purpose stays collapsed regardless, since that's a
     separate class this rule never touches. */
  .pg-shell.pg-lod-far .pg-node:not(.is-unfolded) .pg-node-body { display: none; }
  .pg-shell.pg-lod-far .pg-node:not(.is-unfolded) .pg-node-header { border-radius: var(--r-lg); }

  /* ── Overview (far zoom, or called in by hand with PP) ─────────
     Zoomed out a card stops being a form and becomes a label on a map,
     so it keeps one thing: its title. Body, footer, badge and port rails
     all go, and what is left grows — a wider card with a much larger
     title, which is what makes the board readable at 25% instead of a
     field of grey slivers. The matching vertical squeeze on card
     POSITIONS lives in the graph layer (OV_SQUEEZE); CSS can size a card
     but it cannot move one. */
  .pg-shell.pg-lod-far .pg-node:not(.is-unfolded) {
    /* addNode() writes the width inline from the node type's def, so the
       override has to out-rank an inline style. */
    width: 330px !important;
    border-width: 2px;
    border-radius: 14px;
  }
  .pg-shell.pg-lod-far .pg-node:not(.is-unfolded) .pg-node-footer,
  .pg-shell.pg-lod-far .pg-node:not(.is-unfolded) .pg-node-badge { display: none; }
  .pg-shell.pg-lod-far .pg-node:not(.is-unfolded) .pg-node-header {
    gap: 14px;
    padding: 20px 24px;
    min-height: 88px;
    border-radius: 12px;
  }
  .pg-shell.pg-lod-far .pg-node:not(.is-unfolded) .pg-node-header::before { height: 7px; }
  .pg-shell.pg-lod-far .pg-node:not(.is-unfolded) .pg-node-dot { width: 15px; height: 15px; }
  .pg-shell.pg-lod-far .pg-node:not(.is-unfolded) .pg-node-title {
    font-size: 27px;
    line-height: 1.2;
    letter-spacing: -0.4px;
    /* The title is the only thing left on the card, so it wraps rather than
       ellipsising — "Character Generator" does not fit one line at this size
       and "Character Genera…" is not a label anybody can navigate by. Two
       lines is the cap: it covers every generator and scene name, and it
       bounds the card's height, which is what keeps the squeezed columns
       from running into each other where a route title is a whole sentence. */
    white-space: normal;
    overflow-wrap: anywhere;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    overflow: hidden;
  }
  /* Thicker, so the wiring still reads as wiring once it is drawn a
     quarter size. */
  .pg-shell.pg-lod-far .pg-edge { stroke-width: 4; }
  .pg-shell.pg-lod-far .pg-edge-group:hover .pg-edge,
  .pg-shell.pg-lod-far .pg-edge-group.is-selected .pg-edge { stroke-width: 5; }

  /* ── Unfolded card (overview only) ─────────────
     Double-click a label, or PP with it selected, and the card opens back
     up in place: body, ports and footer at its own width, scaled so that
     on screen it is the size it would be at 90% zoom (--pg-unfold-scale,
     written on the nodes layer by the graph layer on every viewchange and
     absent — so 1 — outside the overview). That is what makes it readable
     and its buttons usable from 25%. It grows from the top centre, so the
     header stays where its label was, and sits above its neighbours, since
     at that scale it covers several of them. Every far-zoom rule above
     carries :not(.is-unfolded), so the card's normal styling simply comes
     back rather than being restated here. The scale animates on the way
     in; while a zoom gesture is rewriting the variable every frame the
     transition comes off, or the card would trail the zoom by a beat. */
  .pg-shell.pg-lod-far .pg-node.is-unfolded {
    z-index: 20;
    transform: scale(var(--pg-unfold-scale, 1));
    transform-origin: 50% 0;
    transition: transform 220ms cubic-bezier(0.2, 0.8, 0.2, 1),
                border-color var(--t), box-shadow var(--t);
  }
  .pg-shell.pg-lod-far .pg-world.is-zooming .pg-node.is-unfolded {
    transition: border-color var(--t), box-shadow var(--t);
  }

  .pg-sink-line {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 10px;
    padding: 3px 0;
    font-size: 12px;
    color: var(--ink-2);
  }
  .pg-sink-line + .pg-sink-line { border-top: 1px solid var(--border); }
  .pg-sink-val { font-family: var(--font-mono); font-size: 11px; color: var(--ink-3); }

  /* ══════════════════════════════════════════════
     PLAYGROUND — UPSCALE NODE (phase 4)
     ══════════════════════════════════════════════ */

  .pg-node-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    height: 28px;
    padding: 0 12px;
    border-radius: var(--r-sm);
    border: 1px solid var(--border-strong);
    background: var(--surface);
    color: var(--ink);
    font-family: var(--font);
    font-size: 12px;
    font-weight: 600;
    letter-spacing: -0.1px;
    cursor: var(--cur-pointer);
    transition: background var(--t-fast), border-color var(--t-fast), color var(--t-fast);
  }
  .pg-node-btn:hover {
    border-color: var(--brand);
    background: var(--brand-tint-2);
    color: var(--brand-ink);
  }
  .pg-node-btn:disabled { opacity: 0.5; cursor: default; }
  .pg-node-btn:disabled:hover {
    border-color: var(--border-strong);
    background: var(--surface);
    color: var(--ink);
  }

  .pg-up-grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 6px;
  }
  .pg-up-cell {
    position: relative;
    aspect-ratio: 16/9;
    border-radius: var(--r-sm);
    background: var(--surface-4);
    box-shadow: inset 0 0 0 1px var(--border);
  }
  .pg-up-cell img {
    width: 100%; height: 100%;
    object-fit: cover;
    border-radius: var(--r-sm);
    display: block;
  }
  .pg-up-key {
    position: absolute;
    left: 3px; bottom: 3px;
    padding: 1px 4px;
    border-radius: 3px;
    background: rgba(17, 19, 24, 0.62);
    color: #fff;
    font-family: var(--font-mono);
    font-size: 9px;
    line-height: 1.5;
    pointer-events: none;
  }
  .pg-up-cell .pg-port { opacity: 0; }
  .pg-up-cell:hover .pg-port,
  .pg-up-cell .pg-port.is-connected,
  .pg-shell.is-linking .pg-up-cell .pg-port { opacity: 1; }
  .pg-shell.pg-lod-far .pg-node:not(.is-unfolded) .pg-up-cell img,
  .pg-shell.pg-lod-far .pg-node:not(.is-unfolded) .pg-up-key { visibility: hidden; }

  /* ══════════════════════════════════════════════
     PLAYGROUND — PROMPT + MODEL NODES (phase 5)
     ══════════════════════════════════════════════ */

  .pg-field { margin-bottom: 9px; }
  .pg-field:last-child { margin-bottom: 0; }
  .pg-field-label {
    display: block;
    margin-bottom: 4px;
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.4px;
    color: var(--ink-3);
  }
  .pg-select, .pg-input {
    width: 100%;
    height: 30px;
    padding: 0 8px;
    border: 1px solid var(--border);
    border-radius: var(--r-sm);
    background: var(--surface-2);
    color: var(--ink);
    font-family: var(--font);
    font-size: 12px;
  }
  .pg-select:focus, .pg-input:focus {
    outline: none;
    border-color: var(--brand);
    background: var(--surface);
  }
  .pg-row { display: flex; gap: 7px; }
  .pg-row > .pg-field { flex: 1 1 0; min-width: 0; }

  .pg-check {
    display: flex;
    align-items: center;
    gap: 7px;
    font-size: 12px;
    color: var(--ink-2);
  }
  .pg-check input { width: 14px; height: 14px; accent-color: var(--brand); }

  .pg-prompt-text {
    width: 100%;
    min-height: 96px;
    resize: vertical;
    border: 1px solid var(--border);
    border-radius: var(--r);
    background: var(--surface-2);
    padding: 9px 10px;
    font-family: var(--font);
    font-size: 12.5px;
    line-height: 1.55;
    color: var(--ink);
  }
  .pg-prompt-text:focus { outline: none; border-color: var(--brand); background: var(--surface); }


/* ── status line (index.html:3496-3504) ── */
  .pg-status {
    font-family: var(--font-mono);
    font-size: 10.5px;
    line-height: 1.5;
    color: var(--ink-3);
    word-break: break-all;
  }
  .pg-status.is-err { color: var(--red-ink); }
  .pg-status.is-ok  { color: var(--green); }

/* ── minimap (index.html:3541-3573) ── */
  .pg-minimap {
    position: absolute;
    right: 22px; bottom: 22px;
    width: 188px; height: 122px;
    border-radius: var(--r-lg);
    border: 1px solid var(--border);
    background: var(--surface);
    box-shadow: var(--sh-2);
    overflow: hidden;
    cursor: var(--cur-pointer);
  }
  .pg-minimap-inner { position: absolute; inset: 0; }
  .pg-mm-node {
    position: absolute;
    border-radius: 2px;
    background: var(--border-strong);
  }
  .pg-mm-node.is-user { background: var(--brand); opacity: 0.55; }
  .pg-mm-view {
    position: absolute;
    border: 1.5px solid var(--brand);
    border-radius: 3px;
    background: rgba(124, 58, 237, 0.07);
    pointer-events: none;
  }
  .pg-minimap-empty {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 100%;
    font-family: var(--font-mono);
    font-size: 10px;
    color: var(--ink-3);
  }
  @media (max-width: 768px) { .pg-minimap { display: none; } }

/* ── lightbox (index.html:1556-1564) ── */
  .lightbox {
    position: fixed; inset: 0;
    background: rgba(10,12,16,0.92);
    display: none;
    align-items: center; justify-content: center;
    z-index: 150;
  }
  .lightbox.open { display: flex; }
  .lightbox img { max-width: 92vw; max-height: 90vh; border-radius: var(--r-lg); box-shadow: var(--sh-4); }
