/* ============================================================================
   Cairn — theme tokens + components (extracted from the design handoff).
   Self-hosted IBM Plex fonts so the app works fully offline.
   ============================================================================ */
@font-face{font-family:'IBM Plex Sans';font-style:normal;font-weight:400;font-display:swap;src:url('fonts/ibm-plex-sans-latin-400-normal.woff2') format('woff2');}
@font-face{font-family:'IBM Plex Sans';font-style:normal;font-weight:500;font-display:swap;src:url('fonts/ibm-plex-sans-latin-500-normal.woff2') format('woff2');}
@font-face{font-family:'IBM Plex Sans';font-style:normal;font-weight:600;font-display:swap;src:url('fonts/ibm-plex-sans-latin-600-normal.woff2') format('woff2');}
@font-face{font-family:'IBM Plex Sans';font-style:normal;font-weight:700;font-display:swap;src:url('fonts/ibm-plex-sans-latin-700-normal.woff2') format('woff2');}
@font-face{font-family:'IBM Plex Mono';font-style:normal;font-weight:400;font-display:swap;src:url('fonts/ibm-plex-mono-latin-400-normal.woff2') format('woff2');}
@font-face{font-family:'IBM Plex Mono';font-style:normal;font-weight:500;font-display:swap;src:url('fonts/ibm-plex-mono-latin-500-normal.woff2') format('woff2');}
@font-face{font-family:'IBM Plex Mono';font-style:normal;font-weight:600;font-display:swap;src:url('fonts/ibm-plex-mono-latin-600-normal.woff2') format('woff2');}

/* ============================================================================
   CAIRN — design tokens. Re-theme the whole app from this one block.
   ============================================================================ */
/* Refined dark professional (2 Jul 2026): neutral charcoal replaces the warm
   walnut, the accent is a desaturated sage (olive identity, no lime), shadows
   are lighter. The goal: reads as survey software, not a game engine. */
:root, [data-theme="dark"]{
  --bg-app:#1A1B1D; --bg-chrome:#212225; --bg-panel:#26272A; --bg-elev:#2E2F33; --bg-elev2:#37383D;
  --bg-hover:rgba(232,233,234,0.05); --bg-active:rgba(154,168,112,0.12);
  --border:#383A3E; --border-strong:#45474C; --border-soft:#2F3134;
  /* text-3 lifted from #75787D -> #8C8F94 (6 Jul 2026 accessibility pass): the old
     value sat at 3.89:1 on bg-app / 3.37:1 on bg-panel, below WCAG AA (4.5:1) for
     the small (10-11.5px) metadata/status-bar text it's used for. This clears
     5.31:1 / 4.60:1 / 4.90:1 (bg-app/bg-panel/bg-chrome) — verified by script. */
  --text-1:#E8E9EA; --text-2:#A6A9AD; --text-3:#8C8F94;
  --accent:#9AA870; --accent-hover:#A9B77F; --accent-press:#8A9861; --accent-fg:#1B1C16; --accent-soft:rgba(154,168,112,0.16);
  /* danger nudged #C26451 -> #C56B59 (was 4.30:1 on bg-app, AA-large only; now 4.61:1) */
  --success:#6FA268; --warning:#C99C4C; --danger:#C56B59; --info:#7BA3BD;
  --success-soft:rgba(111,162,104,0.14); --danger-soft:rgba(197,107,89,0.14);
  --shadow-1:0 1px 2px rgba(0,0,0,.3); --shadow-2:0 4px 14px rgba(0,0,0,.32); --shadow-pop:0 8px 26px rgba(0,0,0,.38);
  --r1:2px; --r2:3px; --r3:5px; --r4:8px;
  --vp-base:#17181A;
  --font-sans:'IBM Plex Sans',system-ui,sans-serif; --font-mono:'IBM Plex Mono',ui-monospace,monospace;
  /* Type scale (29 Jul 2026, brief 2 item 1). Values match theme-v2.css so the
     two agree; they live HERE, not only there, because the v1 pages load only
     theme.css. Renderers used to write inline font-size, which beats any
     stylesheet at any specificity and was the confirmed reason v2 typography
     did not carry through. The .fs-* utilities below replace those inline
     declarations, so theme-v2.css can now retune the scale by overriding these
     variables alone. */
  --fs-micro:10.5px; --fs-tiny:11px; --fs-small:11.5px; --fs-cap:12px;
  --fs-body:13px; --fs-md:15px; --fs-lg:19px; --fs-xl:26px;
  --track-label:.07em;
  --icon-lg:16.2px; --icon-md:13.5px; --icon-sm:11.7px;
}
[data-theme="light"]{
  /* 6 Jul 2026 accessibility pass: the light theme was left on its original
     walnut-era tokens when the dark theme got its "refined professional" rework
     (see NEXT-STEPS.md), and never got the same contrast rigor. text-3/success/
     warning were failing WCAG AA outright (2.93/2.85/2.51:1 on bg-app); accent/
     danger/info cleared only the 3:1 large-text threshold, not 4.5:1. All seven
     tokens below are recomputed to clear 4.5:1 on bg-app (and bg-panel, where
     relevant) while keeping the same hue — verified by script, not eyeballed.
     accent-hover/press are shifted by the same amount so the hover/press ladder
     keeps its spacing. */
  --bg-app:#E4DDCD; --bg-chrome:#F1EBDD; --bg-panel:#F7F2E7; --bg-elev:#FBF8F0; --bg-elev2:#FFFFFF;
  --bg-hover:rgba(42,36,32,0.05); --bg-active:rgba(93,103,39,0.12);
  --border:#D6CCB9; --border-strong:#C4B9A2; --border-soft:#E2D9C7;
  --text-1:#2A2420; --text-2:#5E5446; --text-3:#695F51;
  --accent:#5D6727; --accent-hover:#4B541F; --accent-press:#3D4618; --accent-fg:#FBF8F0; --accent-soft:rgba(93,103,39,0.14);
  --success:#466937; --warning:#7C5915; --danger:#A24536; --info:#376486;
  --success-soft:rgba(70,105,55,0.14); --danger-soft:rgba(162,69,54,0.14);
  --shadow-1:0 1px 2px rgba(60,50,40,.12); --shadow-2:0 6px 18px rgba(60,50,40,.16); --shadow-pop:0 10px 30px rgba(60,50,40,.22);
  --vp-base:#cfc8b7;
}

/* base reset */
*{box-sizing:border-box;}
html,body{margin:0;padding:0;height:100%;}
body{background:var(--bg-app);color:var(--text-1);font-family:var(--font-sans);font-size:13px;line-height:1.45;-webkit-font-smoothing:antialiased;overflow:hidden;}
button,input{font-family:inherit;}
::-webkit-scrollbar{width:9px;height:9px;}
::-webkit-scrollbar-thumb{background:var(--border-strong);border-radius:6px;border:2px solid transparent;background-clip:content-box;}
::-webkit-scrollbar-track{background:transparent;}
.mono{font-family:var(--font-mono);}
[hidden]{display:none !important;}

/* keyframes */
@keyframes spin{to{transform:rotate(360deg);}}
@keyframes popIn{from{opacity:0;transform:translateY(-4px) scale(.985);}to{opacity:1;transform:none;}}
@keyframes modalIn{from{opacity:0;transform:translateY(8px) scale(.99);}to{opacity:1;transform:none;}}
@keyframes toastIn{from{opacity:0;transform:translate(-50%,10px) scale(.98);}to{opacity:1;transform:translate(-50%,0) scale(1);}}

/* ---- reusable parts ------------------------------------------------------- */
/* Icon sizes, 10% down from 18/15/13 (John, 29 Jul). Expressed as variables
   so the whole set moves together — every icon in the app uses one of these
   three classes, so changing the ladder here is the only edit needed. */
.icon{width:var(--icon-lg);height:var(--icon-lg);flex:none;}
.icon-sm{width:var(--icon-md);height:var(--icon-md);flex:none;}
.icon-xs{width:var(--icon-sm);height:var(--icon-sm);flex:none;}

.ibtn{width:34px;height:34px;border:none;border-radius:var(--r2);background:transparent;color:var(--text-2);cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background .12s,color .12s;}
.ibtn:hover{background:var(--bg-hover);color:var(--text-1);}
.ibtn:focus-visible{outline:2px solid var(--accent);outline-offset:1px;}

.btn{height:34px;display:inline-flex;align-items:center;gap:7px;padding:0 14px;border:none;border-radius:var(--r2);font-size:12.5px;font-weight:600;cursor:pointer;transition:background .12s,border-color .12s,color .12s;}
.btn-primary{background:var(--accent);color:var(--accent-fg);box-shadow:var(--shadow-1);}
.btn-primary:hover{background:var(--accent-hover);}
.btn-primary:active{background:var(--accent-press);}
.btn-ghost{background:transparent;border:1px solid var(--border);color:var(--text-2);font-weight:500;}
.btn-ghost:hover{color:var(--text-1);border-color:var(--border-strong);}
.btn:disabled{opacity:.45;cursor:not-allowed;}
.btn:focus-visible,.tbtn:focus-visible{outline:2px solid var(--accent);outline-offset:1px;}

.tbtn{height:32px;display:inline-flex;align-items:center;gap:7px;padding:0 11px;border:none;border-radius:var(--r2);background:transparent;cursor:pointer;color:var(--text-2);font-size:12.5px;font-weight:500;transition:background .12s,color .12s;}
.tbtn:hover{background:var(--bg-hover);color:var(--text-1);}
.tbtn .chev{color:var(--text-3);}

.divider{width:1px;height:22px;background:var(--border);margin:0 5px;}

/* segmented control */
.seg{display:flex;gap:2px;background:var(--bg-panel);border:1px solid var(--border);border-radius:var(--r2);padding:2px;}
.seg button{height:26px;padding:0 11px;border:none;border-radius:var(--r1);background:transparent;color:var(--text-2);font-size:12px;font-weight:500;cursor:pointer;}
.seg button[aria-pressed="true"]{background:var(--bg-elev);color:var(--text-1);box-shadow:inset 0 0 0 1px var(--border);}

/* dropdown menu */
.menu-wrap{position:relative;}
.menu{position:absolute;top:38px;left:0;background:var(--bg-panel);border:1px solid var(--border-strong);border-radius:var(--r3);box-shadow:var(--shadow-pop);padding:5px;z-index:60;animation:popIn .12s ease;}
.menu-item{display:flex;align-items:center;gap:10px;padding:8px 9px;border-radius:var(--r2);cursor:pointer;font-size:12.5px;color:var(--text-1);}
.menu-item:hover{background:var(--bg-hover);}
.menu-item .k{margin-left:auto;font-family:var(--font-mono);font-size:11px;color:var(--text-3);}
.menu-label{padding:6px 9px 4px;font-size:10px;letter-spacing:.06em;text-transform:uppercase;color:var(--text-3);font-weight:600;}
.menu-sep{height:1px;background:var(--border);margin:3px 4px;}

/* toggle switch */
.sw{width:34px;height:19px;border-radius:999px;position:relative;cursor:pointer;background:var(--bg-elev2);flex:none;transition:background .15s;}
.sw .knob{position:absolute;top:2px;left:2px;width:15px;height:15px;border-radius:50%;background:#fff;transition:left .15s;box-shadow:0 1px 2px rgba(0,0,0,.4);}
.sw[aria-checked="true"]{background:var(--accent);}
.sw[aria-checked="true"] .knob{left:17px;}

input[type=range]{accent-color:var(--accent);height:4px;width:100%;}

/* status badge */
.badge{display:inline-flex;align-items:center;gap:6px;height:22px;padding:0 9px;border-radius:999px;font-size:11px;font-weight:600;}
.badge .d{width:6px;height:6px;border-radius:50%;}
.badge-ready{background:var(--success-soft);color:var(--success);}
.badge-error{background:var(--danger-soft);color:var(--danger);}

.tip{position:relative;}

/* ============================ SCREEN LAYOUTS ============================== */
.screen{height:100vh;}

/* dashboard */
.dash{display:flex;height:100vh;}
.rail{width:56px;flex:none;background:var(--bg-chrome);border-right:1px solid var(--border);display:flex;flex-direction:column;align-items:center;padding:12px 0;gap:4px;}
.rail .logo{width:34px;height:34px;border-radius:var(--r3);display:flex;align-items:center;justify-content:center;margin-bottom:10px;background:var(--bg-elev);box-shadow:inset 0 0 0 1px var(--border);}
.rail-btn{width:40px;height:40px;border:none;border-radius:var(--r3);background:transparent;color:var(--text-3);cursor:pointer;display:flex;align-items:center;justify-content:center;}
.rail-btn:hover{background:var(--bg-hover);color:var(--text-1);}
.rail-btn.active{background:var(--bg-active);color:var(--accent);box-shadow:inset 0 0 0 1px var(--accent-soft);}
.avatar{width:34px;height:34px;border:none;border-radius:50%;background:var(--accent);color:var(--accent-fg);cursor:pointer;font-weight:600;font-size:12px;}

.main{flex:1;display:flex;flex-direction:column;min-width:0;}
.dash-head{height:60px;flex:none;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:16px;padding:0 24px;background:var(--bg-app);}
.search{position:relative;width:264px;}
.search svg{position:absolute;left:10px;top:50%;transform:translateY(-50%);color:var(--text-3);}
.search input{width:100%;height:34px;border:1px solid var(--border);background:var(--bg-panel);color:var(--text-1);border-radius:var(--r2);padding:0 10px 0 32px;font-size:12.5px;outline:none;}
.search input:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft);}

.dash-body{flex:1;overflow-y:auto;padding:24px;}
.wrap{max-width:1240px;margin:0 auto;}

.dropzone{display:flex;align-items:center;gap:18px;border:1.5px dashed var(--border-strong);border-radius:var(--r4);background:var(--bg-panel);padding:20px 24px;cursor:pointer;transition:border-color .15s,background .15s;}
.dropzone:hover{border-color:var(--accent);background:var(--bg-elev);}
.dropzone .ic{width:50px;height:50px;flex:none;border-radius:var(--r3);background:var(--bg-elev);display:flex;align-items:center;justify-content:center;color:var(--accent);box-shadow:inset 0 0 0 1px var(--border);}
.chip{font-size:11px;color:var(--text-3);font-family:var(--font-mono);border:1px solid var(--border);border-radius:var(--r1);padding:3px 7px;}

.toolbar-row{display:flex;align-items:center;gap:10px;margin:22px 0 14px;}

.tbl{border:1px solid var(--border);border-radius:var(--r4);overflow:hidden;background:var(--bg-panel);}
.tr{display:grid;grid-template-columns:300px 1fr 130px 120px 96px 40px;align-items:center;gap:16px;padding:12px 18px;border-bottom:1px solid var(--border-soft);cursor:pointer;transition:background .12s;}
.tr:last-child{border-bottom:none;}
.tr:hover{background:var(--bg-hover);}
.th{padding:9px 18px;border-bottom:1px solid var(--border);background:var(--bg-chrome);font-size:10.5px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--text-3);cursor:default;}
.th:hover{background:var(--bg-chrome);}
.pname{display:flex;align-items:center;gap:12px;min-width:0;}
.thumb{width:64px;height:44px;flex:none;border-radius:var(--r2);box-shadow:inset 0 0 0 1px var(--border);position:relative;overflow:hidden;}
.thumb::after{content:"";position:absolute;inset:0;background-image:radial-gradient(rgba(255,255,255,0.14) 0.6px,transparent 0.7px);background-size:5px 5px;}
.ell{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.prog{height:5px;border-radius:999px;background:var(--bg-elev);overflow:hidden;max-width:200px;}
.prog>span{display:block;height:100%;background:var(--warning);border-radius:999px;}

.empty{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:80px 20px;border:1px solid var(--border-soft);border-radius:var(--r4);background:var(--bg-panel);}
.empty .ic{width:72px;height:72px;border-radius:var(--r4);background:var(--bg-elev);display:flex;align-items:center;justify-content:center;color:var(--accent);margin-bottom:18px;box-shadow:inset 0 0 0 1px var(--border);}

/* viewer */
.viewer{display:flex;flex-direction:column;height:100vh;}
.topbar{height:48px;flex:none;background:var(--bg-chrome);border-bottom:1px solid var(--border);display:flex;align-items:center;padding:0 8px;gap:4px;position:relative;z-index:30;}
.proj-btn{height:34px;display:flex;align-items:center;gap:9px;padding:0 10px;border:none;border-radius:var(--r2);background:transparent;cursor:pointer;color:var(--text-1);font-size:13px;font-weight:600;}
.proj-btn:hover{background:var(--bg-hover);}

.vbody{flex:1;display:flex;min-height:0;position:relative;}
.dock{flex:none;background:var(--bg-panel);display:flex;flex-direction:column;transition:width .16s ease;overflow:hidden;z-index:20;}
.dock-left{width:288px;border-right:1px solid var(--border);}
.dock-right{width:300px;border-left:1px solid var(--border);}
.dock.collapsed{width:0;}
.dock-inner{display:flex;flex-direction:column;height:100%;}
.dock-left .dock-inner{width:288px;}
.dock-right .dock-inner{width:300px;}

.tabs{display:flex;height:40px;flex:none;border-bottom:1px solid var(--border);padding:0 4px;align-items:center;gap:1px;}
.tab{flex:1;height:30px;border:none;border-radius:var(--r2);background:transparent;color:var(--text-2);cursor:pointer;display:flex;align-items:center;justify-content:center;}
.tab:hover{background:var(--bg-hover);color:var(--text-1);}
.tab.active{background:var(--bg-active);color:var(--accent);box-shadow:inset 0 0 0 1px var(--accent-soft);}
.tab:disabled{opacity:.35;cursor:not-allowed;}
.dock-scroll{flex:1;overflow-y:auto;}
.sec-label{padding:2px 6px 8px;font-size:10px;letter-spacing:.06em;text-transform:uppercase;color:var(--text-3);font-weight:600;}
.row{display:flex;align-items:center;gap:8px;padding:6px;border-radius:var(--r2);cursor:pointer;}
.row:hover{background:var(--bg-hover);}
.eye{width:22px;height:22px;flex:none;border:none;border-radius:var(--r1);background:transparent;cursor:pointer;display:flex;align-items:center;justify-content:center;color:var(--accent);}
.eye.off{color:var(--text-3);}
.dim{color:var(--text-3) !important;}

.panel-head{height:40px;flex:none;display:flex;align-items:center;gap:8px;padding:0 14px;border-bottom:1px solid var(--border);}
.block{padding:13px 14px;border-bottom:1px solid var(--border-soft);}
.field{display:flex;align-items:center;justify-content:space-between;margin-bottom:9px;}
.field:last-child{margin-bottom:0;}
.ms{display:grid;grid-template-columns:7px minmax(48px,1fr) minmax(82px,auto) 24px 24px;align-items:center;gap:8px;padding:8px;margin-bottom:4px;border:1px solid var(--border-soft);border-radius:var(--r2);background:var(--bg-app);}
.ms-value{min-width:0;justify-self:end;text-align:right;white-space:nowrap;}
.ms:hover{border-color:var(--border);}
.ms .del,.row .del{width:24px;height:24px;flex:none;border:none;border-radius:var(--r1);background:transparent;color:var(--text-3);cursor:pointer;display:flex;align-items:center;justify-content:center;}
.ms .del:hover,.row .del:hover{background:var(--danger-soft);color:var(--danger);}
/* Panorama Stations list — step through a walk from the panel. Collapsible,
   matching the .pgroup chevron convention above it. */
.pano-list-head{gap:6px;}
.pano-list-head:hover{background:var(--bg-elev);}
.pano-list-head .chev{flex:none;transition:transform .15s ease;color:var(--text-3);}
.pano-list-head .chev.collapsed{transform:rotate(-90deg);}
.pano-station-row:hover{background:var(--bg-elev);}
.pano-station-row.current{background:color-mix(in srgb,var(--accent) 16%,transparent);}
.pano-station-row.current .ell{color:var(--text-1);font-weight:500;}

/* Station cues animate the INNER <svg>, never the marker div: controls.ts
   rewrites the div's transform every frame to reposition it, so anything
   animating transform there is overwritten before it can be seen. */
.station-marker-nearest>svg{animation:cairn-station-nearest 1.45s ease-in-out infinite;filter:drop-shadow(0 0 5px rgba(154,168,112,.72));}
/* The two closest stations bounce — "you can step here". Placed AFTER the
   nearest rule, and at equal specificity, so the nearest marker (always one of
   the two) bounces rather than doing the older pulse. */
.station-marker-near>svg{animation:cairn-station-bounce 1.15s ease-in-out infinite;}
/* Next and previous along the walk: the same bounce plus a colour saying which
   way — green ahead, orange behind. Two animations on purpose, and deliberately
   on DIFFERENT properties (bounce owns transform, blink owns filter) so neither
   cancels the other out. */
.station-marker-next>svg{animation:cairn-station-bounce 1.15s ease-in-out infinite,cairn-station-ahead 1.6s ease-in-out infinite;}
.station-marker-prev>svg{animation:cairn-station-bounce 1.15s ease-in-out infinite,cairn-station-behind 1.6s ease-in-out infinite;}
@keyframes cairn-station-nearest{
  0%,100%{transform:translateY(0) scale(1);}
  50%{transform:translateY(-4px) scale(1.08);}
}
@keyframes cairn-station-bounce{
  0%,100%{transform:translateY(0) scale(1);}
  50%{transform:translateY(-5px) scale(1.06);}
}
/* Two stacked drop-shadows rather than one: a tight core plus a wide halo
   reads as a real blink at marker size, where a single shadow just looked
   slightly smudged. brightness() at the peak lifts the icon itself, so the
   colour carries even against a bright panorama. */
@keyframes cairn-station-ahead{
  0%,100%{filter:drop-shadow(0 0 3px rgba(58,222,110,.8)) drop-shadow(0 0 7px rgba(58,222,110,.4));}
  50%{filter:drop-shadow(0 0 5px rgba(58,222,110,1)) drop-shadow(0 0 18px rgba(58,222,110,.9)) brightness(1.3);}
}
@keyframes cairn-station-behind{
  0%,100%{filter:drop-shadow(0 0 3px rgba(255,150,40,.8)) drop-shadow(0 0 7px rgba(255,150,40,.4));}
  50%{filter:drop-shadow(0 0 5px rgba(255,150,40,1)) drop-shadow(0 0 18px rgba(255,150,40,.9)) brightness(1.3);}
}
@media (prefers-reduced-motion:reduce){
  .station-marker-nearest>svg{animation:none;filter:drop-shadow(0 0 7px rgba(154,168,112,.9));}
  /* Motion removed, meaning kept: the direction colours stay as a steady glow
     so the ahead/behind cue does not vanish for anyone who needs this. */
  .station-marker-near>svg{animation:none;}
  .station-marker-next>svg{animation:none;filter:drop-shadow(0 0 4px rgba(58,222,110,1)) drop-shadow(0 0 14px rgba(58,222,110,.85));}
  .station-marker-prev>svg{animation:none;filter:drop-shadow(0 0 4px rgba(255,150,40,1)) drop-shadow(0 0 14px rgba(255,150,40,.85));}
}

/* viewport */
.vp{flex:1;position:relative;min-width:0;background:var(--vp-base);overflow:hidden;}
.cloud{position:absolute;inset:0;opacity:.96;background-color:var(--vp-base);background-size:6px 6px,cover;background-position:center;background-blend-mode:overlay,normal;}
.cloud.cb-rgb{background-image:radial-gradient(rgba(255,255,255,0.05) .8px,transparent .9px),radial-gradient(70% 55% at 32% 34%,rgba(168,154,128,.55),transparent 60%),radial-gradient(65% 60% at 72% 64%,rgba(120,128,92,.45),transparent 62%),linear-gradient(160deg,#403930,#221d18);}
.cloud.cb-elevation{background-image:radial-gradient(rgba(255,255,255,0.05) .8px,transparent .9px),linear-gradient(178deg,#2a3a72 0%,#3f6fb0 16%,#4fae8e 36%,#b7c24f 56%,#d39a2e 74%,#b8472f 100%);}
.cloud.cb-intensity{background-image:radial-gradient(rgba(255,255,255,0.05) .8px,transparent .9px),linear-gradient(176deg,#0e0c0a,#3a352e 42%,#9a9182 76%,#e8e2d4);}
.cloud.cb-classification{background-image:radial-gradient(rgba(255,255,255,0.05) .8px,transparent .9px),conic-gradient(from 200deg at 50% 55%,#b3c158,#7ca6c4,#cd6552,#d8a445,#7fb069,#b3c158);}
.vp.bg-walnut{--vp-base:#17181A;} .vp.bg-black{--vp-base:#000;} .vp.bg-grey{--vp-base:#2E2F33;} .vp.bg-light{--vp-base:#D3D4D2;}
.vignette{position:absolute;inset:0;pointer-events:none;background:radial-gradient(120% 100% at 50% 42%,transparent 55%,rgba(0,0,0,0.45) 100%);}
.meas-chip{position:absolute;background:var(--bg-chrome);border:1px solid var(--accent);color:var(--text-1);font-family:var(--font-mono);font-size:11px;font-weight:600;padding:3px 8px;border-radius:var(--r1);box-shadow:var(--shadow-1);pointer-events:none;}

.quick{position:absolute;left:12px;top:50%;transform:translateY(-50%);display:flex;flex-direction:column;gap:3px;background:var(--bg-chrome);border:1px solid var(--border);border-radius:var(--r3);padding:4px;box-shadow:var(--shadow-2);z-index:5;}
.qbtn{width:36px;height:36px;border:none;border-radius:var(--r2);background:transparent;color:var(--text-2);cursor:pointer;display:flex;align-items:center;justify-content:center;}
.qbtn:hover{background:var(--bg-hover);color:var(--text-1);}
.qbtn.active{background:var(--bg-active);color:var(--accent);box-shadow:inset 0 0 0 1px var(--accent-soft);}
.q-sep{height:1px;background:var(--border);margin:2px 3px;}

/* top:52px (not 14px) so this never collides with the view-quickbar / tool tray
   that also live at the top of the viewport — confirmed colliding at narrower
   window widths on 6 Jul 2026 review (the two are on separate rows now). */
.hint{position:absolute;left:50%;top:52px;transform:translateX(-50%);background:var(--bg-chrome);border:1px solid var(--border);border-radius:999px;padding:5px 13px;font-size:11.5px;color:var(--text-2);box-shadow:var(--shadow-1);display:flex;align-items:center;gap:7px;z-index:5;}
.hint .d{width:6px;height:6px;border-radius:50%;background:var(--accent);}

.gizmo{position:absolute;right:16px;bottom:54px;display:flex;flex-direction:column;align-items:center;gap:10px;z-index:5;}
.navbar{display:flex;gap:3px;background:var(--bg-chrome);border:1px solid var(--border);border-radius:999px;padding:3px;box-shadow:var(--shadow-2);}
.navbar button{width:30px;height:30px;border:none;border-radius:50%;background:transparent;color:var(--text-2);cursor:pointer;display:flex;align-items:center;justify-content:center;}
.navbar button:hover{background:var(--bg-hover);color:var(--text-1);}
.handle{position:absolute;top:10px;width:28px;height:28px;border:1px solid var(--border);border-radius:var(--r2);background:var(--bg-chrome);color:var(--text-2);cursor:pointer;display:flex;align-items:center;justify-content:center;box-shadow:var(--shadow-1);z-index:6;}
.handle:hover{color:var(--text-1);}
.handle.l{left:8px;} .handle.r{right:8px;}

.statusbar{height:28px;flex:none;background:var(--bg-chrome);border-top:1px solid var(--border);display:flex;align-items:center;padding:0 12px;gap:16px;font-size:11px;z-index:25;}
.statusbar .grp{display:flex;align-items:center;gap:6px;color:var(--text-3);}
.statusbar .grp .v{color:var(--text-2);font-family:var(--font-mono);}
.statusbar .sep{width:1px;height:14px;background:var(--border);}

/* overlays */
.scrim{position:fixed;inset:0;background:rgba(10,11,12,0.55);backdrop-filter:blur(2px);display:flex;align-items:center;justify-content:center;z-index:300;}
.modal{background:var(--bg-panel);border:1px solid var(--border-strong);border-radius:var(--r4);box-shadow:var(--shadow-pop);animation:modalIn .16s ease;overflow:hidden;}
.modal-head{display:flex;align-items:center;gap:10px;padding:16px 20px;border-bottom:1px solid var(--border);}
.modal-head .t{font-size:15px;font-weight:600;flex:1;}
.modal-body{padding:18px 20px;display:flex;flex-direction:column;gap:17px;}
.modal-foot{display:flex;justify-content:flex-end;gap:9px;padding:14px 20px;border-top:1px solid var(--border);}
.cb-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:5px;}
.cbtn{height:32px;border:1px solid var(--border);border-radius:var(--r2);background:var(--bg-elev);color:var(--text-2);font-size:12px;cursor:pointer;}
.cbtn.active{background:var(--accent);color:var(--accent-fg);border-color:var(--accent);}
.sw-row{display:flex;gap:8px;}
.swatch{width:36px;height:36px;border-radius:var(--r2);cursor:pointer;border:none;box-shadow:inset 0 0 0 1px var(--border);}
.swatch.active{box-shadow:0 0 0 2px var(--accent),0 0 0 4px var(--bg-panel);}
.lbl{font-size:12px;color:var(--text-2);font-weight:500;}
.access{display:flex;flex-direction:column;gap:1px;border:1px solid var(--border);border-radius:var(--r2);overflow:hidden;}
.access label{display:flex;align-items:center;gap:11px;padding:11px 12px;background:var(--bg-app);cursor:pointer;}
.access label+label{border-top:1px solid var(--border-soft);}
.radio{width:16px;height:16px;border-radius:50%;border:2px solid var(--border-strong);flex:none;}
.radio.on{border:5px solid var(--accent);}
.linkfield{display:flex;gap:8px;}
.linkfield input{flex:1;height:34px;border:1px solid var(--border);background:var(--bg-app);color:var(--text-2);border-radius:var(--r2);padding:0 11px;font-family:var(--font-mono);font-size:11.5px;outline:none;}

.ctx{position:fixed;width:206px;background:var(--bg-panel);border:1px solid var(--border-strong);border-radius:var(--r3);box-shadow:var(--shadow-pop);padding:5px;z-index:200;animation:popIn .1s ease;}
.toast{position:fixed;bottom:42px;left:50%;transform:translateX(-50%);background:var(--bg-elev2);border:1px solid var(--border-strong);border-radius:var(--r3);box-shadow:var(--shadow-pop);padding:11px 16px;display:flex;align-items:center;gap:10px;z-index:400;animation:toastIn .18s ease;}
.toast .ok{width:20px;height:20px;border-radius:50%;background:var(--accent);color:var(--accent-fg);display:flex;align-items:center;justify-content:center;flex:none;}

/* ============================================================================
   Type-scale utilities (29 Jul 2026, brief 2 item 1).

   These exist so no renderer has to write an inline font-size. Inline styles
   beat every stylesheet regardless of specificity, so while the generated
   markup carried its own sizes, theme-v2.css could not restyle any of it —
   that was the confirmed root cause of "typography does not carry through".

   Sizes resolve from the --fs-* variables in the token block above, which
   theme-v2.css overrides for the v2 pages. Adding a size here rather than
   inline is what lets a page retune the whole app from one place.
   ============================================================================ */
.fs-micro{font-size:var(--fs-micro);}
.fs-tiny{font-size:var(--fs-tiny);}
.fs-small{font-size:var(--fs-small);}
.fs-cap{font-size:var(--fs-cap);}
.fs-body{font-size:var(--fs-body);}
.fs-md{font-size:var(--fs-md);}
.fs-lg{font-size:var(--fs-lg);}
/* text-colour utilities, paired with the scale for the same reason */
.tx-1{color:var(--text-1);}
.tx-2{color:var(--text-2);}
.tx-3{color:var(--text-3);}
.tx-accent{color:var(--accent);}
.tx-warning{color:var(--warning);}
.tx-danger{color:var(--danger);}
.tx-success{color:var(--success);}
.tx-info{color:var(--info);}
.wt-600{font-weight:600;}
