:root {
    --bg: #050505;
    --node-bg: rgba(15, 15, 18, 0.95);
    --border: rgba(255, 255, 255, 0.15);
    --accent: #00f2ea;
    --accent-sec: #ff0055;
    --text: #eee;
    --socket-size: 14px;
    --touch-target: 32px;

    /* CATEGORY SYSTEM
       Six stages of getting a shot made. Every node belongs to exactly one, and
       its colour appears in three places: the nav button, the node's header rail
       and its output socket — so the canvas reads by colour before you read a
       word of it.
       The hues are picked at matching lightness and chroma, which is what keeps
       six colours looking like one family instead of a rainbow. */
    --cat-source: #6ea8ff;   /* the world:      scene, style, location, render  */
    --cat-subject: #f5a742;  /* what's in it:   character, animals, objects     */
    --cat-light: #e8c547;    /* how it's lit:   light, atmosphere               */
    --cat-camera: #3ad4c8;   /* the apparatus:  camera, shot, movement          */
    --cat-grade: #b98cf0;    /* how it's finished: grade, composition, preview  */
    --cat-output: #5fc98a;   /* what comes out: stack, sequence, negative       */

    --topbar-h: 60px;
}

body { 
    margin: 0; overflow: hidden; background: var(--bg); 
    font-family: 'Inter', sans-serif; color: var(--text); 
    user-select: none; -webkit-user-select: none;
    touch-action: none;
}

/* INFINITE VIEWPORT — sits below the fixed top bar rather than under it.
   The bar reserves the top safe-area (notch), so the viewport starts below it. */
#viewport {
    width: 100vw;
    height: calc(100vh - var(--topbar-h) - env(safe-area-inset-top));
    margin-top: calc(var(--topbar-h) + env(safe-area-inset-top));
    overflow: hidden; position: relative; cursor: grab;
}
#viewport:active { cursor: grabbing; }
#world { position: absolute; left: 0; top: 0; width: 0; height: 0; transform-origin: 0 0; pointer-events: none; }

.grid-layer {
    position: absolute; width: 50000px; height: 50000px; left: -25000px; top: -25000px;
    background-image: linear-gradient(#1a1a1a 1px, transparent 1px), linear-gradient(90deg, #1a1a1a 1px, transparent 1px);
    background-size: 40px 40px; opacity: 0.6; z-index: -1; pointer-events: auto;
}

/* SVG CABLES */
svg { position: absolute; top: 0; left: 0; width: 1px; height: 1px; overflow: visible; pointer-events: none; z-index: 5; }
@keyframes flux { to { stroke-dashoffset: -20; } }
path {
    fill: none; stroke: #fff; stroke-width: 3px; stroke-dasharray: 10, 5; stroke-linecap: round;
    animation: flux 0.4s linear infinite; filter: drop-shadow(0 0 5px var(--accent));
    pointer-events: auto; cursor: pointer; opacity: 0.8; transition: stroke-width 0.1s;
}
path:hover { stroke-width: 6px; opacity: 1; }
path.active-cable { stroke: var(--accent); stroke-width: 4px; filter: drop-shadow(0 0 8px var(--accent)); }

/* Each cable = <g> holding a fat invisible hit path + the visible path.
   The hit path carries all pointer events so touch has a 26px target. */
.cable-group .cable-hit {
    stroke: transparent; stroke-width: 26px; stroke-dasharray: none;
    animation: none; filter: none; opacity: 1;
    pointer-events: stroke; cursor: pointer;
}
.cable-group .cable { pointer-events: none; }
.cable-group:hover .cable { stroke-width: 6px; opacity: 1; }
.cable-group .cable.cable-selected {
    stroke: var(--accent-sec); stroke-width: 5px; opacity: 1;
    filter: drop-shadow(0 0 8px var(--accent-sec));
}

/* Drop-target affordance while dragging a cable */
.socket-wrapper.valid-target .socket {
    border-color: #55ff99; background: #55ff99;
    box-shadow: 0 0 12px #55ff99; scale: 1.4;
}
.socket-wrapper.invalid-target .socket { opacity: 0.15; }

/* NODES */
.node {
    position: absolute; width: 260px; min-height: 100px;
    background: var(--node-bg); backdrop-filter: blur(25px); -webkit-backdrop-filter: blur(25px);
    border: 1px solid var(--border); border-radius: 10px;
    box-shadow: 0 20px 50px rgba(0,0,0,0.8);
    display: flex; flex-direction: column; z-index: 10;
    pointer-events: auto; resize: both; overflow: hidden;
    transition: border-color 0.2s; touch-action: none;
}
.node.selected { border-color: var(--accent); z-index: 100; box-shadow: 0 0 30px rgba(0, 242, 234, 0.15); }

/* The node carries its category colour on the header rail and its output socket.
   Same six tokens as the nav buttons, so a button and the node it creates are
   visibly the same thing. */
.node { --cat: #667; }
.node[data-cat="source"]  { --cat: var(--cat-source); }
.node[data-cat="subject"] { --cat: var(--cat-subject); }
.node[data-cat="light"]   { --cat: var(--cat-light); }
.node[data-cat="camera"]  { --cat: var(--cat-camera); }
.node[data-cat="grade"]   { --cat: var(--cat-grade); }
.node[data-cat="output"]  { --cat: var(--cat-output); }

.node-header {
    padding: 12px; font-size: 0.7rem; font-weight: 800; letter-spacing: 1px;
    background: rgba(255,255,255,0.03); border-bottom: 1px solid var(--border);
    border-left: 3px solid var(--cat);
    display: flex; justify-content: space-between; align-items: center;
    cursor: grab; color: #bbb; flex-shrink: 0;
}
.node-title { display: flex; flex-direction: column; gap: 2px; line-height: 1; flex: 1; }

/* Collapse toggle: folds the node to its header chip. The chevron rotates when
   collapsed; a collapsed node keeps its sockets so it can still be wired. */
.collapse-btn {
    background: none; border: none; color: #7a8380; cursor: pointer;
    font-size: 0.8rem; line-height: 1; padding: 0 8px; min-width: 34px;
    align-self: stretch; transition: transform 0.15s, color 0.15s;
}
.collapse-btn:hover { color: #fff; }
.node.collapsed .collapse-btn { transform: rotate(-90deg); }
.node.collapsed .node-content { display: none; }
.node.collapsed { min-height: 0; height: auto !important; resize: none; }
.node-cat {
    font-family: 'JetBrains Mono', monospace; font-size: 0.48rem; font-weight: 500;
    letter-spacing: 0.14em; color: var(--cat); opacity: 0.9;
}
.node-content { padding: 15px; display: flex; flex-direction: column; gap: 12px; flex: 1; overflow-y: auto; height: 100%; box-sizing: border-box; pointer-events: auto; }

/* INPUTS */
select, input[type="text"], input[type="number"], textarea {
    width: 100%; background: #080808; border: 1px solid #333; box-sizing: border-box;
    color: var(--accent); padding: 10px; font-family: 'JetBrains Mono', monospace;
    font-size: 0.8rem; border-radius: 6px; outline: none; min-height: 36px;
}
/* outline:none above hides the ugly default ring from a mouse click, but a
   keyboard/switch-access user tabbing through a node had NO way to see which
   field they were on. :focus-visible only fires for keyboard focus, so the
   mouse-click case above is untouched. */
select:focus-visible, input:focus-visible, textarea:focus-visible {
    outline: 2px solid var(--accent); outline-offset: 1px;
}
.stack-output {
    flex: 1;
    min-height: 80px;
    resize: none;
}
.obj-input { text-align: center; font-weight: bold; font-size: 0.9rem; color: #fff; border-color: var(--accent-sec); }
.slider-row { display: flex; align-items: center; gap: 10px; font-size: 0.75rem; color: #888; min-height: 30px; }
input[type="range"] { flex: 1; accent-color: var(--accent); cursor: pointer; height: 8px; border-radius: 4px; }

/* 3D VIEWER */
.viewport-3d {
    background: #000; border-radius: 6px; margin-bottom: 5px;
    display: flex; align-items: center; justify-content: center;
    overflow: hidden; position: relative; width: 100%; height: 100%; min-height: 160px;
    touch-action: none;
}
/* The main preview grows to fill the node's content, so the node's own resize
   handle drives the render. It has no handle of its own — one draggable corner,
   not two. */
.preview-fill {
    flex: 1 1 auto; min-height: 140px; width: 100%; margin-bottom: 0;
}
#three-canvas-container {
    width: 100%; height: 100%;
}

/* SOCKETS */
.socket-wrapper {
    position: absolute; width: var(--touch-target); height: var(--touch-target);
    display: flex; align-items: center; justify-content: center; z-index: 30;
    top: 50%; transform: translateY(-50%); cursor: crosshair;
}
.socket-wrapper.in { left: calc(var(--touch-target) / -2); }
.socket-wrapper.out { right: calc(var(--touch-target) / -2); }

.socket {
    width: var(--socket-size); height: var(--socket-size);
    background: #111; border: 2px solid #666; border-radius: 50%;
    pointer-events: none; transition: 0.2s; box-sizing: border-box;
}
.socket-wrapper:hover .socket { background: #fff; scale: 1.2; }
.in .socket { border-color: var(--accent-sec); }
/* Output socket wears the node's category colour: a cable's origin is readable
   from the dot it leaves. */
.out .socket { border-color: var(--cat); }

/* Tap-to-connect: an armed output socket pulses in its category colour until
   you pick an input (or tap away). */
@keyframes sock-pulse { 0%,100% { transform: scale(1); } 50% { transform: scale(1.4); } }
.socket-wrapper.out.armed .socket {
    background: var(--cat); border-color: var(--cat);
    box-shadow: 0 0 12px var(--cat);
    animation: sock-pulse 0.9s ease-in-out infinite;
}

/* BUTTONS */
.btn-gen { background: var(--accent); color: #000; border: none; width: 100%; padding: 12px; font-weight: 800; cursor: pointer; border-radius: 6px; margin-top: auto; text-transform: uppercase; letter-spacing: 1px; min-height: 44px; }
.btn-gen:hover { background: #fff; box-shadow: 0 0 15px var(--accent); }
.stack-tools { display: flex; gap: 5px; margin-top: 5px; }
.btn-tool { flex: 1; background: #222; border: 1px solid #333; color: #aaa; padding: 10px; cursor: pointer; border-radius: 6px; font-size: 0.75rem; font-weight: bold; min-height: 38px; }
.btn-tool:hover { border-color: #fff; color: #fff; }

/* TOP BAR — one flex row: brand | nav | file actions.
   The logo used to be absolutely positioned over the nav's left edge and sat on
   top of the first button. It is part of the layout now, so it cannot collide. */
.top-bar {
    position: fixed; top: 0; left: 0; right: 0;
    height: calc(var(--topbar-h) + env(safe-area-inset-top));
    padding: env(safe-area-inset-top) calc(14px + env(safe-area-inset-right)) 0 calc(14px + env(safe-area-inset-left));
    display: flex; align-items: center; gap: 14px;
    z-index: 1000; background: rgba(6, 7, 9, 0.82);
    backdrop-filter: blur(18px); -webkit-backdrop-filter: blur(18px);
    border-bottom: 1px solid rgba(255,255,255,0.07);
    box-sizing: border-box;
}

/* BRAND */
.brand { display: flex; align-items: center; gap: 10px; flex-shrink: 0; text-decoration: none; }
.brand-mark { height: 22px; filter: invert(1) opacity(0.75); transition: filter 0.2s; }
.brand:hover .brand-mark { filter: invert(1) opacity(1) drop-shadow(0 0 8px rgba(0,242,234,0.5)); }
.brand-rule { width: 1px; height: 26px; background: rgba(255,255,255,0.12); }
.brand-text { display: flex; flex-direction: column; line-height: 1; gap: 3px; }
.brand-name {
    font-size: 0.9rem; font-weight: 800; letter-spacing: -0.02em; color: #fff;
}
.brand-name span { font-weight: 400; color: #7d8a94; }
.brand-tag {
    font-family: 'JetBrains Mono', monospace; font-size: 0.5rem; letter-spacing: 0.16em;
    color: var(--accent); text-transform: uppercase; opacity: 0.85;
}

/* NAV */
.top-nav {
    display: flex; gap: 6px; align-items: center; flex: 1; min-width: 0;
    overflow-x: auto; white-space: nowrap; -webkit-overflow-scrolling: touch;
    padding: 4px 0;
}
.top-nav::-webkit-scrollbar { display: none; }
/* Signals "there's more, scroll me" — the nav now holds enough node types
   (SOURCE alone has 8) that the right edge silently clips buttons with no
   indication a scrollbar exists. JS (initNavOverflowHint, app.js) toggles this
   class off once actually scrolled to the end, so the fade always matches
   real scroll position instead of guessing. */
.top-nav.has-more {
    mask-image: linear-gradient(to right, #000 calc(100% - 36px), transparent 100%);
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 36px), transparent 100%);
}
.nav-sep { width: 1px; height: 22px; background: rgba(255,255,255,0.1); flex-shrink: 0; }

.nav-btn {
    --cat: #667;
    background: rgba(20,20,25,0.9);
    border: 1px solid rgba(255,255,255,0.09);
    border-left: 3px solid var(--cat);
    color: #ccc; padding: 0 13px; border-radius: 6px; cursor: pointer;
    font-weight: 700; font-size: 0.68rem; letter-spacing: 0.04em;
    text-transform: uppercase; transition: 0.15s;
    display: inline-flex; align-items: center; gap: 5px;
    min-height: 34px; flex-shrink: 0;
}
.nav-btn:hover, .nav-btn:focus-visible {
    border-color: var(--cat); color: #fff; background: rgba(35,35,42,0.95);
}
.nav-btn[data-cat="source"]  { --cat: var(--cat-source); }
.nav-btn[data-cat="subject"] { --cat: var(--cat-subject); }
.nav-btn[data-cat="light"]   { --cat: var(--cat-light); }
.nav-btn[data-cat="camera"]  { --cat: var(--cat-camera); }
.nav-btn[data-cat="grade"]   { --cat: var(--cat-grade); }
.nav-btn[data-cat="output"]  { --cat: var(--cat-output); }
/* Tools (undo/redo/add) are not node categories — they read as neutral chrome. */
.nav-btn[data-cat="tool"] { --cat: #4a4f57; font-weight: 600; }
.nav-btn.primary { --cat: var(--accent); color: var(--accent); }

.close-btn { padding: 5px 10px; font-size: 1.2rem; cursor: pointer; }

/* Quick-add palette: same category rails, but grid-friendly. Reusing .nav-btn
   here overflowed the modal — its flex-shrink:0 plus inline-flex meant grid
   tracks could not shrink below the label's min-content width. */
.palette-btn {
    --cat: #667;
    background: rgba(20,20,25,0.9);
    border: 1px solid rgba(255,255,255,0.09);
    border-left: 3px solid var(--cat);
    color: #ccc; border-radius: 6px; cursor: pointer;
    font-weight: 700; font-size: 0.62rem; letter-spacing: 0.04em; text-transform: uppercase;
    min-height: 44px; padding: 0 6px; width: 100%; min-width: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    transition: 0.15s;
}
.palette-btn:hover { border-color: var(--cat); color: #fff; background: rgba(35,35,42,0.95); }
.palette-btn[data-cat="source"]  { --cat: var(--cat-source); }
.palette-btn[data-cat="subject"] { --cat: var(--cat-subject); }
.palette-btn[data-cat="light"]   { --cat: var(--cat-light); }
.palette-btn[data-cat="camera"]  { --cat: var(--cat-camera); }
.palette-btn[data-cat="grade"]   { --cat: var(--cat-grade); }
.palette-btn[data-cat="output"]  { --cat: var(--cat-output); }

.file-actions { display: flex; gap: 6px; align-items: center; flex-shrink: 0; }

/* The "⋯" overflow trigger and its popover — hidden on desktop, the only file
   control on phone. */
.overflow-wrap { position: relative; flex-shrink: 0; }
.overflow-btn { display: none; }

/* Phone thumb-zone bar — the four things you do most, within reach. Hidden on
   desktop, where the top bar covers them. */
.action-bar { display: none; }
.sheet-grab { display: none; }
@keyframes sheet-up { from { transform: translateY(100%); } to { transform: none; } }
@keyframes spin { to { transform: rotate(360deg); } }

@media (max-width: 900px) {
    /* Keep the mark, drop the wordmark — the nav needs the room. */
    .brand-text, .brand-rule { display: none; }
}

/* -------- PHONE ( < 640 ): the top bar becomes brand + tools + overflow.
   Node-creation moves entirely to the + Add palette; file actions fold into a
   tap-to-open popover so nothing is pushed off the right edge. -------- */
@media (max-width: 640px) {
    :root { --touch-target: 44px; --socket-size: 20px; }

    /* Node-category buttons are redundant with the searchable + Add palette. */
    .top-nav .nav-btn:not(.primary):not([data-cat="tool"]),
    .top-nav .nav-sep,
    #subject-nav { display: none; }

    /* Multi-select box is a desktop power move; long-press covers per-node
       actions on touch. */
    #sel-mode-btn { display: none; }

    /* + Add and Random live in the bottom action bar on phone, not the top bar. */
    .phone-hide { display: none; }

    /* Full-width nodes read like a mobile form once Tidy stacks them in a
       column. (Inline widths — e.g. the preview node — still win.) */
    .node { width: min(92vw, 380px); }

    /* Presets have their own manager button; the quick dropdown is desktop-only. */
    #preset_sel { display: none; }

    /* Collapse the file actions behind ⋯. */
    .overflow-btn { display: inline-flex; }
    .file-actions {
        display: none; position: absolute; top: calc(100% + 6px); right: 0;
        flex-direction: column; align-items: stretch; gap: 4px;
        background: #14161b; border: 1px solid #333; border-radius: 8px;
        padding: 6px; min-width: 168px; z-index: 1200;
        box-shadow: 0 10px 30px rgba(0,0,0,0.7);
    }
    .file-actions.open { display: flex; }
    .file-actions .nav-btn { justify-content: flex-start; width: 100%; font-size: 0.72rem; }
    .file-actions .nav-btn .glyph { display: inline-block; width: 1.3em; }

    /* Bigger, iOS-zoom-safe form controls inside nodes. */
    select, input[type="text"], input[type="number"], textarea { min-height: 44px; font-size: 16px; }
    .spatial-grid select { font-size: 16px; min-height: 40px; }

    /* ---- Bottom action bar: the thumb-zone primary actions ---- */
    .action-bar {
        display: flex; position: fixed; left: 0; right: 0; bottom: 0;
        height: calc(56px + env(safe-area-inset-bottom));
        padding: 0 6px env(safe-area-inset-bottom);
        align-items: center; gap: 6px; z-index: 1500;
        background: rgba(8,9,12,0.95);
        backdrop-filter: blur(18px); -webkit-backdrop-filter: blur(18px);
        border-top: 1px solid rgba(255,255,255,0.08);
    }
    .action-bar button {
        flex: 1; min-height: 46px; border-radius: 10px; cursor: pointer;
        background: rgba(22,24,30,0.9); border: 1px solid rgba(255,255,255,0.1);
        color: #cdd6d4; font-family: inherit; font-weight: 700; font-size: 0.58rem;
        letter-spacing: 0.05em; text-transform: uppercase;
        display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
    }
    .action-bar button .ic { font-size: 1.05rem; line-height: 1; }
    .action-bar button.pri { background: var(--accent); color: #00201f; border-color: var(--accent); }

    /* The bar owns the bottom; the minimap is a desktop overview affordance. */
    #minimap { display: none; }

    /* ---- Modals become bottom sheets (override their inline centering) ---- */
    .modal-sheet {
        top: auto !important; bottom: 0 !important; left: 0 !important; right: 0 !important;
        transform: none !important; width: 100% !important; max-width: none !important;
        max-height: 82vh !important; border-radius: 16px 16px 0 0 !important;
        border-bottom: none !important; padding-bottom: env(safe-area-inset-bottom) !important;
        /* Slide up on open. Only here — where transform is none — so it never
           fights the desktop centering transform. */
        animation: sheet-up 0.22s cubic-bezier(0.2,0.7,0.2,1);
    }
    .sheet-grab {
        display: block; width: 36px; height: 4px; border-radius: 3px;
        background: rgba(255,255,255,0.22); margin: 8px auto 2px;
    }
    #quick-add-list { grid-template-columns: repeat(3, 1fr); }
}

@media (prefers-reduced-motion: reduce) {
    .modal-sheet { animation: none !important; }
}

/* One-time tips card — bottom-centred, clearing the phone action bar. */
.onboard-card {
    position: fixed; left: 50%; transform: translateX(-50%);
    bottom: calc(24px + env(safe-area-inset-bottom));
    width: min(360px, 92vw); z-index: 2200;
    background: rgba(18,20,26,0.97); border: 1px solid rgba(255,255,255,0.12);
    border-left: 3px solid var(--accent);
    border-radius: 12px; padding: 14px 16px;
    box-shadow: 0 12px 34px rgba(0,0,0,0.6);
    font-size: 0.85rem; color: #dfe6e4;
}
.onboard-card .ob-title {
    font-family: 'JetBrains Mono', monospace; font-size: 0.6rem; letter-spacing: 0.14em;
    text-transform: uppercase; color: var(--accent); margin-bottom: 8px;
}
.onboard-card ul { margin: 0 0 12px; padding-left: 18px; }
.onboard-card li { margin: 5px 0; }
.onboard-card li::marker { color: var(--accent); }
.onboard-card b { color: #fff; font-weight: 650; }
.onboard-card .ob-ok {
    width: 100%; min-height: 42px; border-radius: 8px; cursor: pointer;
    background: var(--accent); color: #00201f; border: none;
    font-family: inherit; font-weight: 800; font-size: 0.8rem; letter-spacing: 0.04em;
}
@media (max-width: 640px) {
    /* Sit above the 56px action bar. */
    .onboard-card { bottom: calc(70px + env(safe-area-inset-bottom)); }
}

/* SPATIAL CONTEXT */
.spatial-panel { margin-top: 10px; background: rgba(0,0,0,0.3); border-radius: 4px; padding: 5px; }
.spatial-panel summary { font-size: 0.65rem; color: var(--accent); cursor: pointer; font-weight: bold; outline: none; user-select: none; }
.spatial-panel summary:hover { color: #fff; }
.spatial-grid { display: grid; grid-template-columns: 1fr; gap: 5px; margin-top: 5px; }
.spatial-grid select { width: 100%; padding: 4px; background: #111; color: #eee; border: 1px solid #333; border-radius: 4px; font-size: 0.7rem; }

/* TOAST NOTIFICATION */
.toast {
    position: fixed; bottom: calc(20px + env(safe-area-inset-bottom)); left: 50%; transform: translateX(-50%) translateY(100px);
    background: var(--accent); color: #000; padding: 10px 20px; border-radius: 25px;
    font-weight: bold; font-size: 0.85rem; box-shadow: 0 5px 15px rgba(0,0,0,0.5);
    transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); z-index: 2000;
    pointer-events: none; opacity: 0;
}
.toast.show { transform: translateX(-50%) translateY(0); opacity: 1; }

/* MINIMAP */
#minimap {
    position: fixed; right: calc(20px + env(safe-area-inset-right)); bottom: calc(20px + env(safe-area-inset-bottom)); width: 150px; height: 100px;
    background: rgba(10,10,10,0.8); border: 1px solid #444; border-radius: 8px;
    z-index: 1000; overflow: hidden; pointer-events: none;
}
.mini-node { position: absolute; width: 4px; height: 4px; background: var(--accent); border-radius: 50%; transform: translate(-50%, -50%); }
.mini-vp { position: absolute; border: 1px solid rgba(255,255,255,0.3); }
