/* ===== self-hosted fonts (tools/build-fonts.py): no third-party request =====
   Inter for the key labels; JetBrains Mono for the UI's mono text */
@font-face { font-family: 'JetBrains Mono'; src: url(fonts/JetBrainsMono-500.woff2) format('woff2'); font-weight: 500; font-style: normal; font-display: swap; }
@font-face { font-family: 'JetBrains Mono'; src: url(fonts/JetBrainsMono-600.woff2) format('woff2'); font-weight: 600; font-style: normal; font-display: swap; }
@font-face { font-family: 'KSV Inter'; src: url(fonts/Inter-400.woff2) format('woff2'); font-weight: 400; font-style: normal; font-display: block; }
@font-face { font-family: 'KSV Inter'; src: url(fonts/Inter-500.woff2) format('woff2'); font-weight: 500; font-style: normal; font-display: block; }
@font-face { font-family: 'KSV Inter'; src: url(fonts/Inter-600.woff2) format('woff2'); font-weight: 600; font-style: normal; font-display: block; }
@font-face { font-family: 'KSV Inter'; src: url(fonts/Inter-700.woff2) format('woff2'); font-weight: 700; font-style: normal; font-display: block; }

/* ===== page ===== */
html, body { margin: 0; height: 100%; background: #0a0b0d; }
/* desktop-only: below the app's min-width the page scrolls horizontally instead of cramming.
   body is the scroller (html clips), so the wide app never overflows the document itself —
   otherwise mobile browsers widen the layout viewport to fit it, the page height grows with
   it, and the app (100vh) ends up a band at the top of a mostly empty page. */
html { overflow: hidden; }
body { overflow: auto; }
#app { height: 100%; }

/* ===== shared keyboard ===== */
.opt-d .d-footer {
  /* in-flow at the bottom (kb-wrap's auto margins push it down); shares the same
     centered content column as .head / .kb-wrap so its edges line up with the keyboard */
  width: 100%; max-width: var(--content-max); margin: 0 auto;
  display: flex; align-items: center; justify-content: space-between; gap: 18px;
  padding: 16px 0 4px; pointer-events: none;
}
.opt-d .d-footer .cf-meta { display: flex; align-items: center; gap: 9px; min-width: 0; }
/* version tag and its menu of archived versions (renderVersion); hidden until config.js fills it */
.opt-d .d-footer .ver-menu { position: relative; flex: none; pointer-events: auto; }
.opt-d .d-footer .ver-menu[hidden] { display: none; }
.opt-d .d-footer .cf-ver { display: inline-flex; align-items: center; gap: 3px; background: none; cursor: pointer; }
.opt-d .d-footer .cf-ver:disabled { cursor: default; }
.opt-d .d-footer .cf-ver .ic { font-size: 11px; transition: transform .15s ease; }
.opt-d .d-footer .cf-ver[aria-expanded="true"] .ic { transform: rotate(180deg); }
.opt-d .ver-list {
  position: absolute; left: 0; bottom: calc(100% + 8px); z-index: 5; min-width: 210px;
  display: flex; flex-direction: column; padding: 4px; border: 1px solid var(--line); border-radius: 8px;
  background: var(--rail-solid); box-shadow: 0 8px 24px rgba(0,0,0,.25);
}
.opt-d .ver-list[hidden] { display: none; }
.opt-d .ver-item {
  display: flex; align-items: baseline; gap: 10px; padding: 7px 9px; border-radius: 5px;
  font: 600 11.5px/1 var(--mono); color: var(--text); text-decoration: none; letter-spacing: .04em;
}
.opt-d .ver-item em { font-style: normal; font-weight: 500; color: var(--muted-text); }
.opt-d .ver-item em .ic { display: inline-block; font-size: 10px; margin-left: 4px; vertical-align: -1px; }
.opt-d .ver-item.is-current { color: var(--accent-ink); cursor: default; }
.opt-d a.ver-item:hover, .opt-d a.ver-item:focus-visible { background: var(--field-hover); outline: none; }
.opt-d .d-footer .cf-ver { flex: none; font: 600 10.5px/1 var(--mono); letter-spacing: .06em; color: var(--accent-ink); padding: 3px 6px; border: 1px solid color-mix(in oklab, var(--accent) 35%, var(--pill-line)); border-radius: 5px; }
.opt-d .d-footer .cf-text { font: 500 11.5px/1 var(--mono); color: var(--muted-text); letter-spacing: .01em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.opt-d .d-footer .cf-link { display: inline-flex; align-items: center; gap: 4px; font: 500 12px/1 var(--mono); letter-spacing: .04em; color: var(--muted-text); text-decoration: none; flex: none; pointer-events: auto; transition: color .14s ease; }
.opt-d .d-footer .cf-link:hover { color: var(--accent-ink); }
.ksv-kb { display: flex; flex-direction: column; gap: var(--g, 6px); width: 100%; }
.ksv-fnrow { display: grid; grid-template-columns: repeat(var(--cols, 58), 1fr); gap: var(--g, 6px); }
.ksv-row { display: grid; grid-template-columns: repeat(var(--cols, 58), 1fr); gap: var(--g, 6px); }
/* blank Touch ID placeholder at the end of the function row */
.ksv-touchid { pointer-events: none; opacity: .4; }
.ksv-touchid::after { content: ''; width: 38%; aspect-ratio: 1; border-radius: 50%; border: 1px solid var(--keyBorder); opacity: .55; }
.ksv-touchid.no-sensor::after { display: none; }
.ksv-key {
  position: relative; box-sizing: border-box;
  border: 1px solid var(--keyBorder); background: var(--keyBg); color: var(--keyFg);
  /* key geometry derives from --keyH via the KEYSPEC ratios (see ksv.js applySpec);
     fallbacks here mirror KEYSPEC so the stylesheet is still correct on its own */
  border-radius: calc(var(--keyH, 56px) * var(--r-radius, 0.125)); height: var(--keyH, 58px);
  display: flex; align-items: center; justify-content: center;
  font: 400 max(var(--fmin-letter, 19px), calc(var(--keyH, 56px) * var(--r-letter, 0.330)))/1 var(--key); cursor: pointer; user-select: none; padding: 0;
  transition: transform .05s ease;
}
.ksv-fn { font-size: max(var(--fmin-fn, 13px), calc(var(--keyH, 56px) * var(--r-fn, 0.232))); }
.ksv-fn.esc { flex-direction: column; align-items: flex-start; justify-content: flex-end; padding: calc(var(--keyH, 56px) * var(--r-pady, 0.143)) calc(var(--keyH, 56px) * var(--r-padx, 0.161)); }
.ksv-fn.esc .sub { font-size: max(var(--fmin-sub, 11px), calc(var(--keyH, 56px) * var(--r-sub, 0.196))); font-weight: 500; letter-spacing: .01em; color: var(--keyFg); }
.ksv-key.sm { flex-direction: column; align-items: flex-start; justify-content: space-between; padding: calc(var(--keyH, 56px) * var(--r-pady, 0.143)) calc(var(--keyH, 56px) * var(--r-padx, 0.161)); font-size: max(var(--fmin-fn, 13px), calc(var(--keyH, 56px) * var(--r-fn, 0.232))); }
.ksv-key.sm .cap { font-size: max(var(--fmin-glyph, 15px), calc(var(--keyH, 56px) * var(--r-glyph, 0.268))); line-height: 1; color: var(--keyFg); }
.ksv-key.sm.ralign { align-items: flex-end; text-align: right; }
/* fn key (like a real Mac): globe icon bottom-left, "fn" text top-right */
.ksv-key.sm.fnkey { flex-direction: column; justify-content: space-between; }
.ksv-key.sm.fnkey .cap { order: 2; align-self: flex-start; }
.ksv-key.sm.fnkey .sub { order: 1; align-self: flex-end; }
/* a MacBook's modifiers as printed (ksv.js LOOKS): a lone glyph in the bottom corner (low),
   the globe and "fn" at either end of the bottom line (spread), larger words and a narrower
   margin (phys: KEYSPEC.phys) */
.ksv-key.sm .sub:empty { display: none; }
/* a logo alone on its key, large and centred (a PC's super key: KEYSPEC.big) */
.ksv-key.sm.big { justify-content: center; align-items: center; }
.ksv-key.sm.big .cap { font-size: calc(var(--keyH, 56px) * var(--r-big, 0.42)); }
.ksv-key.sm.low { justify-content: flex-end; }
.ksv-key.sm.low:has(> .sub:not(:empty)) { justify-content: space-between; }
.ksv-key.sm.phys { padding-left: calc(var(--keyH, 56px) * var(--r-phys-padx, 0.14)); padding-right: calc(var(--keyH, 56px) * var(--r-phys-padx, 0.14)); }
.ksv-key.sm.phys .sub { font-size: max(var(--fmin-sub, 11px), calc(var(--keyH, 56px) * var(--r-phys-sub, 0.215))); }
.ksv-key.sm.fnkey.spread { flex-direction: row; align-items: flex-end; justify-content: space-between; }
.ksv-key.sm.fnkey.spread .cap, .ksv-key.sm.fnkey.spread .sub { order: 0; align-self: auto; }
/* a lone small character in the top-left corner (², on a French PC) */
.ksv-key.corner { align-items: flex-start; justify-content: flex-start; padding: calc(var(--keyH, 56px) * var(--r-pady, 0.143)) calc(var(--keyH, 56px) * var(--r-padx, 0.161));
  font-size: max(var(--fmin-fn, 13px), calc(var(--keyH, 56px) * var(--r-dual, 0.3))); }
.ic { display: block; }
/* a run of glyphs inline in text (segmented control labels) */
.ic-row { display: inline-flex; align-items: center; gap: 1px; font-size: 13px; vertical-align: middle; }
.ksv-key.modkey:not(.on) .cap, .ksv-key.hyper:not(.on) .cap { color: var(--accent-ink, var(--accent)); }
.ksv-key.sm .sub { font-size: max(var(--fmin-sub, 11px), calc(var(--keyH, 56px) * var(--r-sub, 0.196))); font-weight: 500; letter-spacing: .01em; color: var(--keyFg); }
.ksv-key:hover { border-color: var(--accent); }
.ksv-key.is-hyper:not(.on) { border-color: var(--accent); }
.ksv-key.is-hyper:not(.on) .cap { color: var(--accent-ink, var(--accent)); }
.ksv-key.is-hyper:not(.on) .sub { color: var(--accent-ink, var(--accent)); }
.ksv-key.locked { opacity: .28; cursor: not-allowed; }
.ksv-key.locked:hover { border-color: var(--keyBorder); }
.ksv-key.maxed { opacity: .28; cursor: not-allowed; }
.ksv-key.maxed:hover { border-color: var(--keyBorder); }
.ksv-key:active { transform: translateY(1px); }
.ksv-key.dbl::after {
  content: '2'; position: absolute; top: -6px; right: -6px; z-index: 4;
  width: 18px; height: 18px; border-radius: 50%;
  background: var(--badge, var(--accent)); color: var(--badge-fg, var(--onFg, #0a0b0d));
  font: 700 11px/18px var(--key); text-align: center;
  box-shadow: 0 0 0 2px var(--keyBg, #131519);
}
.ksv-static .ksv-key.dbl::after { width: 11px; height: 11px; font-size: 7px; line-height: 11px; top: -3px; right: -3px; box-shadow: 0 0 0 1.5px var(--keyBg, #131519); }
.ksv-key.on,
.ksv-key.hyper.on,
.ksv-key.modkey.on {
  background: color-mix(in oklab, var(--accent) 32%, transparent);
  border-color: transparent; color: var(--keyFg);
  box-shadow: 0 0 0 1px var(--accent);
}
.ksv-key.on .sub { opacity: .85; }
/* selected-key fill + ring — shared by the live selector and the export preview, and
   mirrored on <canvas> by boardSurface(). All values come from ONSTATE (ksv.js) via
   the --on-* custom properties, so the look stays identical across all three. */
.opt-d .stage .ksv-key.on,
.opt-d .is-board .ksv-key.on {
  border: 1px solid transparent;
  background:
    linear-gradient(color-mix(in oklab, var(--accent) calc(var(--on-fill, 0.26) * 100%), transparent) 0 100%) padding-box,
    linear-gradient(135deg,
      color-mix(in oklab, var(--accent) calc(var(--on-ring-from, 0.72) * 100%), transparent),
      color-mix(in oklab, var(--accent) calc(var(--on-ring-to, 0.16) * 100%), transparent) var(--on-ring-stop, 75%)) border-box;
  /* solid key surface behind the translucent accent layers (matches the idle key);
     each surface supplies its own --keyBg — solid on the preview/export, frosted on the selector */
  background-color: var(--keyBg);
}
/* a key in several shortcuts (ksv.js paint): their colours in a soft 135° gradient (--multi,
   ONSTATE.multi), the wash and glows in their average (--accent, set on the key) */
.opt-d .stage .ksv-key.on.multi,
.opt-d .is-board .ksv-key.on.multi {
  background:
    linear-gradient(color-mix(in oklab, var(--accent) calc(var(--on-fill, 0.26) * 100%), transparent) 0 100%) padding-box,
    var(--multi) border-box;
  background-color: var(--keyBg);
}
/* glow geometry is per-surface (the selector tracks the cursor; the preview is
   static) but its accent opacity is shared via --on-glow */
.opt-d .stage .ksv-key.on {
  box-shadow:
    0 0 calc(var(--keyH) * var(--on-glow-size, 0.1)) 0 color-mix(in oklab, var(--accent) calc(var(--on-glow, 0.55) * 100%), transparent),
    inset 0 0 calc(var(--keyH) * var(--on-inner-size, 0.16)) color-mix(in oklab, var(--accent) calc(var(--on-inner, 0.55) * 100%), transparent);
}
/* two legends, stacked as printed (KEYSPEC.dual*; the export: drawKeyFace): each centred on its
   KEYSPEC.dualY line. Under a symbol, a digit reads as large as a letter */
.ksv-key .dual { position: absolute; inset: 0; }
.ksv-key .dual > span { position: absolute; left: 0; right: 0; text-align: center; line-height: 1; transform: translateY(-50%);
  top: calc(var(--keyH, 56px) * var(--r-dual-y0, 0.28)); font-size: max(var(--fmin-fn, 13px), calc(var(--keyH, 56px) * var(--r-dual, 0.3))); }
.ksv-key .dual > span + span { top: calc(var(--keyH, 56px) * var(--r-dual-y1, 0.7)); }
.ksv-key .dual > span.short { transform: translateY(-50%) scale(.85); } /* ksv.js SHORT */
.ksv-key .dual.num > span:first-child { font-size: max(var(--fmin-sub, 11px), calc(var(--keyH, 56px) * var(--r-dual-sm, 0.23))); }
.ksv-key .dual.num > span + span { font-size: max(var(--fmin-letter, 19px), calc(var(--keyH, 56px) * var(--r-letter, 0.33))); }
/* ISO return (AZERTY): an L over two rows. The button spans its row and hangs over the next
   one (the negative margin keeps its row one key high); its surface is the SVG path from
   isoShape() (ksv.js), painted here like any key, idle or selected. Only the path takes
   clicks, so the corner it leaves free stays the neighbour's. */
.ksv-key.iso-ret {
  height: calc(2 * var(--keyH, 56px) + var(--g, 6px)); margin-bottom: calc(-1 * (var(--keyH, 56px) + var(--g, 6px)));
  padding-bottom: calc(var(--keyH, 56px) * (1 + var(--r-pady, 0.143)) + var(--g, 6px));
  z-index: 1; pointer-events: none;
}
.ksv-key.iso-ret, .ksv-key.iso-ret:hover, .opt-d .stage .ksv-key.iso-ret.on, .opt-d .is-board .ksv-key.iso-ret.on { background: none; border-color: transparent; box-shadow: none; }
.iso-shape { position: absolute; inset: -1px; width: calc(100% + 2px); height: calc(100% + 2px); overflow: visible; z-index: -1; pointer-events: none; }
.iso-shape path { pointer-events: none; }
.iso-shape .iso-face { fill: var(--keyBg); pointer-events: auto; }
.ksv-static .iso-shape .iso-face { pointer-events: none; }
.iso-edge { fill: none; stroke: var(--keyBorder); stroke-width: 1; }
.ksv-key.iso-ret:hover .iso-edge { stroke: var(--accent); }
.iso-tint, .iso-wash, .iso-inner, .iso-ring { display: none; }
.iso-s0 { stop-color: var(--accent); stop-opacity: var(--on-ring-from, 1); }
.iso-s1 { stop-color: var(--accent); stop-opacity: var(--on-ring-to, .1); }
.ksv-key.iso-ret.on .iso-edge { display: none; }
.ksv-key.iso-ret.on .iso-tint, .ksv-key.iso-ret.on .iso-wash, .ksv-key.iso-ret.on .iso-inner, .ksv-key.iso-ret.on .iso-ring { display: inline; }
.iso-wash { fill: var(--accent); fill-opacity: var(--on-fill, .14); }
.iso-inner { fill: none; stroke: var(--accent); stroke-opacity: var(--on-inner, .55); stroke-width: 2; }
.iso-ring { fill: none; stroke-width: 1.5; }
.ksv-key.iso-ret.on .iso-shape { filter: drop-shadow(0 0 calc(var(--keyH, 56px) * var(--on-glow-size, 0.1) / 2) color-mix(in oklab, var(--accent) calc(var(--on-glow, 0.55) * 100%), transparent)); }
/* the arrow cluster's gap is half the key gap — the export draws the same (drawBoard) */
.ksv-arrows { display: grid; grid-template-columns: repeat(3, 1fr); grid-template-rows: 1fr 1fr; gap: calc(var(--g, 6px) / 2); }
.ksv-arrows .ar { height: calc((var(--keyH, 58px) - var(--g, 6px) / 2) / 2); font-size: max(var(--fmin-sub, 11px), calc(var(--keyH, 56px) * var(--r-sub, 0.196))); border-radius: calc(var(--keyH, 56px) * var(--r-radius, 0.125) * 0.6); }
.ar.up { grid-column: 2; grid-row: 1; } .ar.lf { grid-column: 1; grid-row: 2; }
.ar.dn { grid-column: 2; grid-row: 2; } .ar.rt { grid-column: 3; grid-row: 2; }

/* ===== keycaps preview: the export itself, drawn on a canvas (app.js updatePreview) ===== */
.ksv-app .prev-caps { display: block; max-width: 100%; height: auto; }
.ksv-empty { font: 500 15px/1.4 var(--ui); color: var(--muted, #5b6472); }

/* ===== canvas frame resets ===== */
.ksv-app { box-sizing: border-box; width: 100%; height: 100%; font-family: var(--ui); -webkit-font-smoothing: antialiased; }
.ksv-app * { box-sizing: border-box; }

/* ================= OPTION D — Console Inspector (primary) ================= */
.opt-d {
  --ui: -apple-system, 'SF Pro Display', ui-sans-serif, system-ui, sans-serif;
  /* key labels (keyboard, chips, badges) — the same font the exports draw (KSV.UIFONT) */
  --key: 'KSV Inter', var(--ui);
  /* secondary text: the darkest grey that still reads at WCAG AA (4.5:1) on the panels */
  --muted-text: #767d88;
  --mono: 'JetBrains Mono', ui-monospace, 'SF Mono', monospace;
  --accent: #22d3ee; --onFg: #0a0b0d;
  --keyFg: #d7dbe0; --keyBorder: #23262d;
  /* translucent so the live keys read frosted over the panel; the flat export and
     its preview use the solid colour instead (KSV.KEYBG, applied via applySpec) */
  --keyBg: rgba(19,21,25,0.6); /* fallback: applySpec sets it from KSV.SURFACE.translucent */
  --muted: #5a616b;
  /* canvas tokens: set from KSV.THEMES by app.js (applyCanvas); dark values as fallback */
  --canvas-bg: #0a0b0d; --check-base: #0c0d10; --check: rgba(255,255,255,.07);
  --grid-color: rgba(255,255,255,.02); --frame-stroke: #1d2026; --accent-ink: var(--accent);
  /* chrome tokens: the page's own UI, dark here and light under .theme-light */
  --text: #d7dbe0; --title: #f4f6f7; --label: #7b828c;
  --line: #1d2026; --line-soft: #16191d;
  --field: #0a0b0d; --field-text: #8a93a0; --field-hover: #1b1f25; --field-hover-text: #cfd4da;
  --pill-line: #23262d; --pill-hover: #3a3f48;
  --rail-bg: rgba(13,15,18,0.4); --rail-solid: #0b0d0f;
  --track: #1d2026; --thumb-ring: #0a0b0d; --sw-on: #f4f6f4; --sw-inset: rgba(255,255,255,.14);
  background: var(--canvas-bg); color: var(--text);
  position: relative;
  /* desktop-only: never let the layout cram below this — narrower viewports scroll horizontally */
  min-width: 1120px;
  /* rail grows gently on large screens, the stage (1fr) takes the rest */
  display: grid; grid-template-columns: 1fr 300px;
  /* the rail folds away (⇧S: app.js toggleRail), the stage takes its room */
  transition: grid-template-columns .3s ease;
  /* content max-width that the stage column centers around (see .head / .kb-wrap / .d-footer) */
  --content-max: 1180px;
}
.opt-d .frame-grid {
  position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background-image: linear-gradient(var(--grid-color) 1px, transparent 1px), linear-gradient(90deg, var(--grid-color) 1px, transparent 1px);
  background-size: 30px 30px;
  /* anchored on the keyboard case's top-left corner (app.js alignToGrid) */
  background-position: var(--grid-x, 0px) var(--grid-y, 0px);
}
/* left stage */
.opt-d .stage {
  position: relative; z-index: 1; padding: 30px 32px; display: flex; flex-direction: column; min-width: 0;
}
.opt-d .stage > * { position: relative; z-index: 1; }
.opt-d .grid-lit {
  position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background-image:
    linear-gradient(color-mix(in oklab, var(--accent) 28%, transparent) 1px, transparent 1px),
    linear-gradient(90deg, color-mix(in oklab, var(--accent) 28%, transparent) 1px, transparent 1px);
  background-size: 30px 30px;
  background-position: calc(var(--grid-x, 0px) + var(--vx, 0px)) calc(var(--grid-y, 0px) + var(--vy, 0px));
  -webkit-mask-image: radial-gradient(var(--glow-r, 90px) circle at var(--gx, 50%) var(--gy, -40%), #000, rgba(0,0,0,.3) 50%, transparent 72%);
  mask-image: radial-gradient(var(--glow-r, 90px) circle at var(--gx, 50%) var(--gy, -40%), #000, rgba(0,0,0,.3) 50%, transparent 72%);
}
.opt-d .head { width: 100%; max-width: var(--content-max); margin: 0 auto 22px; }
.opt-d .head .eyebrow { display: block; font: 600 11px/1 var(--ui); letter-spacing: .2em; text-transform: uppercase; color: var(--accent-ink); margin-bottom: 11px; }
.opt-d .head .title { margin: 0; font: 700 clamp(36px, 2.4vw, 48px)/1 var(--ui); letter-spacing: -.025em; color: var(--title); }
.opt-d .head .lede { margin: 13px 0 0; font: 500 12.5px/1.5 var(--mono); color: var(--muted-text); }
.opt-d .head .lede b { color: var(--accent-ink); font-weight: 600; white-space: nowrap; }
.opt-d .head .lede b .ic { display: inline-block; vertical-align: -.15em; margin-right: 1px; }
/* the case's fill and hairline come from KSV.THEMES (set inline by applyCanvas) */
.opt-d .panel { position: relative; border: 1px solid var(--frame-stroke); background: rgba(13,15,18,0.4); -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px); border-radius: 12px; padding: 22px; margin: 0; }
.opt-d .kb-wrap { margin: auto; width: 100%; max-width: var(--content-max); }

/* modifier-key controls live in the main frame, by the keyboard */
.opt-d .kb-controls { display: flex; flex-wrap: wrap; align-items: center; gap: 14px 26px; margin: 0 0 16px; }
.opt-d .kbc { display: flex; align-items: center; gap: 10px; }
.opt-d .kbc.accent-kbc { margin-left: auto; }
.opt-d .kbc[hidden] { display: none; }
.opt-d .kb-controls.kb-below { margin: 16px 0 0; }
/* while the pointer is on the keyboard, the controls above and below it step back: they
   fade, shrink a little toward the keyboard and soften, as if further away */
.opt-d .kb-controls { transition: opacity .25s ease, transform .35s ease, filter .35s ease; transform-origin: 50% 100%; }
.opt-d .kb-controls.kb-below { transform-origin: 50% 0; }
.opt-d .kb-wrap:has(> .panel:hover) .kb-controls { opacity: .35; transform: scale(.985); filter: blur(.6px); }
/* keyboard layout: a native select, dressed like the segmented controls */
/* the three dropdowns read as one group: closer than the other controls */
.opt-d .kb-picks { display: flex; flex-wrap: wrap; gap: 8px; }
.opt-d .kb-pick { position: relative; }
.opt-d .pick-btn {
  display: inline-flex; align-items: center; gap: 10px; height: 35px; padding: 0 11px 0 13px;
  background: var(--field); border: 1px solid var(--line); border-radius: 8px;
  font: 600 11.5px/1 var(--mono); color: var(--field-text); cursor: pointer;
}
.opt-d .pick-btn:hover, .opt-d .pick-btn[aria-expanded="true"] { background: var(--field-hover); color: var(--field-hover-text); }
.opt-d .pick-btn > .ic:last-child { width: 14px; height: 14px; transform: rotate(180deg); transition: transform .15s ease; }
.opt-d .pick-btn[aria-expanded="true"] > .ic:last-child { transform: none; }
/* an option's icon (the super key's look), before its label */
.opt-d .pick-btn > .ic:not(:last-child), .opt-d .pick-opt .ic { width: 14px; height: 14px; flex: none; }
.opt-d .pick-btn > .ic:not(:last-child) { margin-right: -2px; }
/* the options: the segmented control's look, stacked (.seg) */
.opt-d .pick-list {
  position: absolute; left: 0; top: calc(100% + 6px); z-index: 6; min-width: 100%; box-sizing: border-box;
  display: flex; flex-direction: column; gap: 3px; padding: 3px; border: 1px solid var(--line); border-radius: 8px;
  background: var(--field); box-shadow: 0 8px 24px rgba(0,0,0,.25);
}
.opt-d .pick-list[hidden] { display: none; }
.opt-d .pick-opt {
  display: flex; align-items: center; gap: 8px; height: 29px; padding: 0 10px; border: none; background: none; border-radius: 5px; text-align: left; white-space: nowrap;
  font: 600 11.5px/1 var(--mono); color: var(--field-text); cursor: pointer;
}
.opt-d .pick-opt:hover, .opt-d .pick-opt:focus-visible { background: var(--field-hover); color: var(--field-hover-text); outline: none; }
.opt-d .pick-opt.on { background: var(--accent); color: var(--onFg, #0a0b0d); }
/* the multi shortcut switch, then its swatches (one per shortcut) */
.opt-d .kbc.accent-kbc .swatches { gap: 6px; margin-left: 6px; }
.opt-d .kbc.accent-kbc .sw-chip { width: 22px; height: 22px; border-radius: 6px; }
.opt-d .kbc-l { font: 500 12.5px/1 var(--mono); color: var(--label); }

/* right rail — docked to the window's right edge, over the grid: the keyboard case's fill
   and blur, one border on the stage side */
.opt-d .rail {
  position: relative; z-index: 1;
  border-left: 1px solid var(--line);
  background: var(--rail-bg);
  -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px);
  display: flex; flex-direction: column; min-height: 0; min-width: 0; overflow: hidden;
  /* hidden once folded (its controls leave the tab order: app.js), shown as it starts to open */
  transition: visibility 0s .3s;
}
/* the content keeps its width while the column narrows: it slides out of view */
.opt-d .rail > * { min-width: 299px; }
.opt-d.rail-off { grid-template-columns: 1fr 0px; }
/* the rail's toggle: tucked into the stage's top-right corner, against the rail (or the
   window's edge once it's folded) and riding along with it; filled like the keyboard case,
   rounded only on the corner that faces the page */
.opt-d .rail-btn {
  position: absolute; top: 0; right: 300px; z-index: 3;
  display: grid; place-items: center; width: 40px; height: 40px; padding: 0;
  background: var(--case-bg); -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px);
  border: none; border-radius: 0 0 0 10px;
  color: var(--field-text); cursor: pointer; transition: right .3s ease;
}
.opt-d .rail-btn .ic { width: 16px; height: 16px; }
.opt-d .rail-btn:hover { background: var(--field-hover); color: var(--field-hover-text); }
.opt-d.rail-off .rail-btn { right: 0; }
.opt-d.rail-off .rail { visibility: hidden; border-left-color: transparent; }
.opt-d:not(.rail-off) .rail { transition: none; }
.opt-d .rlbl { font: 500 11px/1 var(--mono); letter-spacing: .04em; color: var(--muted-text); }
.opt-d .prev-card {
  margin-top: 12px; min-height: 80px; border: 1px solid var(--line); background: var(--canvas-bg);
  border-radius: 10px; padding: 16px 14px; display: flex; align-items: center; justify-content: center; overflow: hidden;
}

.opt-d .inspector { flex: 1; overflow-y: auto; overflow-x: hidden; padding: 20px; display: flex; flex-direction: column; gap: 32px; }
.opt-d .grp { display: flex; flex-direction: column; gap: 13px; }
.opt-d .grp-h { font: 600 10.5px/1 var(--mono); letter-spacing: .16em; text-transform: uppercase; color: var(--muted-text); margin-bottom: 5px; }
.opt-d .fl-row { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
/* accent readout (the output resolution). Not `.cap`: that's a key's glyph, and the two
   collided in the preview, where selected keys took the accent */
.opt-d .readout { font: 500 11.5px/1 var(--mono); color: var(--accent-ink); letter-spacing: .01em; }
.opt-d .seg { display: inline-flex; gap: 3px; background: var(--field); border: 1px solid var(--line); border-radius: 8px; padding: 3px; }
.opt-d .seg.wide { width: 100%; }
.opt-d .seg.wide button { flex: 1; }
.opt-d .seg button { height: 27px; padding: 0 11px; border: none; background: none; border-radius: 5px; font: 600 11.5px/1 var(--mono); color: var(--field-text); cursor: pointer; text-transform: lowercase; }
.opt-d .seg button.on { background: var(--accent); color: var(--onFg, #0a0b0d); }
/* glow: four choices in the row's width */
.opt-d #glowSeg button { padding: 0 9px; }
.opt-d .seg button:not(.on):not(.is-off):hover { background: var(--field-hover); color: var(--field-hover-text); }
.opt-d .seg button.is-off { opacity: .3; cursor: not-allowed; }
.opt-d .seg.disabled { opacity: .42; }
.opt-d .seg.disabled button { cursor: not-allowed; }

/* independent on/off toggles (several can be on): separate pills, outlined when off,
   accent-tinted when on — unlike .seg, whose solid fill marks the one choice */
.opt-d .pills { display: inline-flex; gap: 6px; }
.opt-d .pills button {
  height: 27px; padding: 0 12px; border-radius: 999px; border: 1px solid var(--pill-line); background: none;
  font: 600 11.5px/1 var(--mono); color: var(--field-text); cursor: pointer; text-transform: lowercase;
  transition: color .14s ease, border-color .14s ease, background-color .14s ease;
}
.opt-d .pills button:not(.on):not(:disabled):hover { border-color: var(--pill-hover); color: var(--field-hover-text); }
.opt-d .pills button.on {
  color: var(--accent-ink); border-color: color-mix(in oklab, var(--accent) 60%, transparent);
  background: color-mix(in oklab, var(--accent) 14%, transparent);
}
.opt-d .pills.disabled { opacity: .42; }
.opt-d .pills.disabled button { cursor: not-allowed; }

/* export scale slider — same track/fill language as the segmented controls */
/* one labelled control per line in the output group (unavailable keys) */
.opt-d .opt-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding-left: 2px; white-space: nowrap; }
.opt-d .opt-row[hidden] { display: none; }
.opt-d .scale-row { display: flex; align-items: center; gap: 12px; height: 35px; padding: 0 2px; }
.opt-d .scale-row .readout { min-width: 30px; text-align: right; white-space: nowrap; }
.opt-d .scale-row.disabled { opacity: .42; }
.opt-d .scale-row input[type="range"] {
  flex: 1; min-width: 0; height: 4px; margin: 0; border-radius: 4px; cursor: pointer; -webkit-appearance: none; appearance: none;
  background: linear-gradient(var(--accent), var(--accent)) 0 / var(--fill, 33%) 100% no-repeat, var(--track);
}
.opt-d .scale-row input[type="range"]:disabled { cursor: not-allowed; }
.opt-d .scale-row input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none; width: 14px; height: 14px; border-radius: 50%;
  background: var(--accent); border: 3px solid var(--thumb-ring); box-shadow: 0 0 0 1px var(--accent);
}
.opt-d .scale-row input[type="range"]::-moz-range-thumb {
  width: 8px; height: 8px; border-radius: 50%; background: var(--accent); border: 3px solid var(--thumb-ring); box-shadow: 0 0 0 1px var(--accent);
}

.opt-d .rail-foot { padding: 16px 20px 20px; border-top: 1px solid var(--line-soft); display: flex; gap: 10px; }
.opt-d .rail-foot .go { flex: 1; white-space: nowrap; display: flex; align-items: center; justify-content: center; gap: 6px; height: 40px; border-radius: 8px; border: none; background: var(--accent); color: var(--onFg, #0a0b0d); font: 700 13px/1 var(--mono); cursor: pointer; text-transform: lowercase; }
.opt-d .rail-foot .go:hover { filter: brightness(1.06); }
/* the button's hotkey on its right edge, the label on the left, in a small box tinted by the
   label's colour */
.opt-d .rail-foot .go:has(kbd) { justify-content: flex-start; padding: 0 8px 0 14px; }
.opt-d .rail-foot .go kbd {
  display: inline-flex; align-items: center; gap: 1px; margin-left: auto; padding: 3px 5px; border-radius: 5px;
  font: 600 10.5px/1 var(--mono); text-transform: none; background: color-mix(in oklab, currentColor 14%, transparent);
}
.opt-d .rail-foot .go kbd .ic { width: 11px; height: 11px; }
/* secondary action next to download: outlined, fills once copied */
.opt-d .rail-foot .go.alt { flex: 1; background: none; color: var(--accent-ink); box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--accent) 45%, transparent); }
.opt-d .rail-foot .go.alt:hover { filter: none; background: color-mix(in oklab, var(--accent) 12%, transparent); }
.opt-d .rail-foot .go.alt:disabled { opacity: .42; cursor: not-allowed; background: none; }
.opt-d .rail-foot .go.alt.is-done { background: color-mix(in oklab, var(--accent) 18%, transparent); }

/* preview pinned at the bottom of the rail */
.opt-d .rail-prev { padding: 18px 20px; border-top: 1px solid var(--line-soft); display: flex; flex-direction: column; gap: 13px; }
.opt-d .rail-prev .prev-card { margin-top: 0; }

/* ===== shared accent swatches ===== */
.swatches { display: flex; gap: 7px; }
.sw-chip { width: 26px; height: 26px; border-radius: 7px; border: 2px solid transparent; cursor: pointer; padding: 0; background: var(--sw); box-shadow: inset 0 0 0 1px var(--sw-inset, rgba(255,255,255,.14)); transition: transform .08s ease; }
/* a shortcut with no keys yet: its colour as an outline only */
.sw-chip.empty { background: transparent; box-shadow: inset 0 0 0 2px var(--sw); }
.sw-chip:hover { transform: translateY(-1px); }
.sw-chip.on { border-color: var(--sw-on, #f4f6f4); box-shadow: inset 0 0 0 1px rgba(0,0,0,.35); }
.sw-chip.on.empty { box-shadow: inset 0 0 0 2px var(--sw); }
/* above a swatch, on hover or focus: edit its colour, clear its shortcut (while it has keys) */
.sw { position: relative; display: flex; }
.sw-tools {
  position: absolute; bottom: calc(100% + 4px); left: 50%; transform: translate(-50%, 3px);
  display: flex; gap: 3px; opacity: 0; pointer-events: none; transition: opacity .12s ease, transform .12s ease;
}
.sw-tools button {
  display: grid; place-items: center; width: 20px; height: 20px; padding: 0; border: 0; border-radius: 6px;
  background: var(--field); color: var(--field-text); box-shadow: inset 0 0 0 1px var(--line); cursor: pointer;
}
.sw-tools .ic { width: 12px; height: 12px; }
.sw-tools button:hover { color: var(--field-hover-text); background: var(--field-hover); }
.sw-tools button[hidden] { display: none; }
/* the gap between the tools and the swatch keeps the hover */
.sw-tools::after { content: ''; position: absolute; left: 0; right: 0; top: 100%; height: 6px; }
.sw:hover .sw-tools, .sw:focus-within .sw-tools { opacity: 1; pointer-events: auto; transform: translate(-50%, 0); }

/* the colour picker (app.js openPicker): just above its swatch, placed by script */
.picker {
  position: fixed; z-index: 25; width: 236px; box-sizing: border-box;
  display: flex; flex-direction: column; gap: 8px; padding: 8px;
  background: var(--field); border: 1px solid var(--line); border-radius: 10px; box-shadow: 0 10px 30px rgba(0,0,0,.35);
}
.pk-sv {
  position: relative; height: 80px; border-radius: 6px; cursor: crosshair; touch-action: none; outline: none;
  background: linear-gradient(to top, #000, transparent), linear-gradient(to right, #fff, hsl(var(--h, 0) 100% 50%));
}
.pk-sv:focus-visible { box-shadow: 0 0 0 2px var(--accent); }
.pk-dot {
  position: absolute; left: calc(var(--s, 1) * 100%); top: calc((1 - var(--v, 1)) * 100%);
  width: 12px; height: 12px; border-radius: 50%; transform: translate(-50%, -50%); pointer-events: none;
  border: 2px solid #fff; box-shadow: 0 0 0 1px rgba(0,0,0,.45);
}
.pk-hue {
  -webkit-appearance: none; appearance: none; width: 100%; height: 10px; margin: 0; border-radius: 5px; cursor: pointer;
  background: linear-gradient(to right, #f00, #ff0, #0f0, #0ff, #00f, #f0f, #f00);
}
.pk-hue::-webkit-slider-thumb { -webkit-appearance: none; width: 14px; height: 14px; border-radius: 50%; background: hsl(var(--h, 0) 100% 50%); border: 2px solid #fff; box-shadow: 0 0 0 1px rgba(0,0,0,.45); }
.pk-hue::-moz-range-thumb { width: 10px; height: 10px; border-radius: 50%; background: hsl(var(--h, 0) 100% 50%); border: 2px solid #fff; }
.pk-row { display: flex; gap: 6px; }
.pk-hex {
  flex: 1; min-width: 0; height: 28px; padding: 0 8px; box-sizing: border-box; border-radius: 6px;
  background: var(--canvas-bg); border: 1px solid var(--line); color: var(--text); font: 600 11.5px/1 var(--mono);
}
.pk-hex:focus { outline: none; border-color: var(--accent); }
.pk-reset {
  display: inline-flex; align-items: center; gap: 5px; height: 28px; padding: 0 8px; border: 0; border-radius: 6px;
  background: none; color: var(--field-text); box-shadow: inset 0 0 0 1px var(--line); font: 600 11px/1 var(--mono); cursor: pointer;
}
.pk-reset:hover { color: var(--field-hover-text); background: var(--field-hover); }
.pk-reset .ic { width: 12px; height: 12px; }
.pk-reset[hidden] { display: none; }

/* ===== transparent background: a checkerboard in the theme's tones ===== */
.ksv-app .prev-card.is-trans, .opt-d.cv-trans, .opt-d .shot.is-trans, .opt-d .shot-fig.is-trans {
  background-color: var(--check-base);
  background-image: conic-gradient(var(--check) 90deg, transparent 90deg 180deg, var(--check) 180deg 270deg, transparent 270deg);
  background-size: 18px 18px;
}

/* ===== board preview — the same keyboard as the selector, scaled down =====
   The preview is a real (static) selector keyboard: it inherits every KEYSPEC
   ratio, so it shares the selector's exact proportions. Only the px floors are
   zeroed (via the static flag in applySpec) so labels shrink with the keys. */
.ksv-app .is-board .ksv-kb { width: 100%; position: relative; }
/* the board preview is the export scaled down (sizes set in app.js): no card padding */
.opt-d .prev-card.is-board { padding: 0; }
.ksv-app .prev-board { position: relative; flex: none; box-sizing: border-box; }
.ksv-app .prev-board.with-grid {
  background-image:
    linear-gradient(var(--grid-color) var(--grid-line), transparent var(--grid-line)),
    linear-gradient(90deg, var(--grid-color) var(--grid-line), transparent var(--grid-line));
  background-size: var(--grid-step) var(--grid-step);
}
.ksv-app .prev-frame { position: absolute; border: 1px solid; box-sizing: border-box; }
/* solid key surface (--keyBg) is set on the preview keyboard by applySpec from the
   shared KSV.KEYBG, so it always matches the export */
.ksv-app .is-board .ksv-key { min-height: 0; }
.ksv-app .is-board .ksv-key.sm .sub { display: block; }
.ksv-app .is-board .ksv-fnrow { gap: var(--g); }
.ksv-app .is-board .ksv-key:hover { border-color: var(--keyBorder); }
/* board preview shares the fill + ring rule above; only its static glow differs */
.opt-d .is-board .ksv-key.on {
  box-shadow:
    0 0 calc(var(--keyH) * var(--on-glow-size, 0.12)) 0 color-mix(in oklab, var(--accent) calc(var(--on-glow, 0.8) * 100%), transparent),
    inset 0 0 calc(var(--keyH) * var(--on-inner-size, 0.16)) color-mix(in oklab, var(--accent) calc(var(--on-inner, 0.55) * 100%), transparent);
}
/* static export-preview keyboard is display-only, not interactive */
.ksv-static .ksv-key { pointer-events: none; cursor: default; }

/* keyboard focus: a visible accent ring on everything interactive */
.opt-d button:focus-visible, .opt-d input:focus-visible, .opt-d a:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px;
}
.opt-d .seg button:focus-visible { outline-offset: 0; }

/* ===== the canvas shows the export settings (app.js applyCanvas) =====
   theme, background, grid, frame and surface: what the export draws, the page draws too.
   Canvas colours come from KSV.THEMES as variables; the chrome follows .theme-light. */
.opt-d.cv-no-grid .frame-grid, .opt-d.cv-no-grid .grid-lit { display: none; }
/* the rail is UI, not canvas: opaque over the checkerboard */
.opt-d.cv-trans .rail { background: var(--rail-solid); }

/* ===== light theme: the page's chrome (AA contrast on the light canvas and rail) ===== */
.opt-d.theme-light {
  --text: #1d2025; --title: #0f1114; --label: #4f5560; --muted-text: #555c67;
  --line: #d5d9e0; --line-soft: #e1e4e9;
  --field: #ffffff; --field-text: #555c67; --field-hover: #eef0f3; --field-hover-text: #1d2025;
  --pill-line: #cfd3da; --pill-hover: #a9afb9;
  --rail-bg: rgba(255,255,255,0.55); --rail-solid: #f7f8fa;
  --track: #d5d9e0; --thumb-ring: #ffffff; --sw-on: #14161a; --sw-inset: rgba(0,0,0,.12);
}

/* ===== cursor light on the hairlines: near the cursor, the case's and the keys' borders
   light up, with a soft glow around the lit part. The rings live in .spot-layer, an empty
   copy of the keyboard laid over it inside the case (app.js buildSpotLayer): each ring is
   one radial gradient in the case's coordinates (the cursor glow's position, set on .panel;
   each copy key's --kx / --ky, its place in the case: app.js syncSpotLayer), so it holds as
   the case leans,
   masked to a 1 px border, so every border samples the same light; the layer's
   drop-shadow then glows only what's lit, fading with it. Page only: no cursor in exports. */
.opt-d .spot-layer {
  position: absolute; inset: -1px; border-radius: inherit; pointer-events: none; z-index: 2;
  opacity: var(--spot-o, 0); transition: opacity .3s ease;
  filter: drop-shadow(0 0 2px color-mix(in oklab, var(--accent) 90%, transparent))
          drop-shadow(0 0 7px color-mix(in oklab, var(--accent) 55%, transparent));
}
.opt-d .spot-layer .ksv-kb { position: absolute; }
/* (stronger than the selected-key rule, which would otherwise paint the copies too) */
.opt-d .stage .spot-layer .ksv-key, .opt-d .stage .spot-layer .ksv-key.on { background: none; border-color: transparent; box-shadow: none; color: transparent; }
.opt-d .spot-layer .ksv-key > *, .opt-d .spot-layer .ksv-key::after { visibility: hidden; }
.opt-d .spot-ring, .opt-d .spot-layer .ksv-key::before {
  content: ''; position: absolute; inset: -1px; border-radius: inherit; padding: 1px;
  background: radial-gradient(var(--spot-r, 110px) circle at calc(var(--spot-x, -999px) - var(--kx, 0px)) calc(var(--spot-y, -999px) - var(--ky, 0px)),
    var(--accent), color-mix(in oklab, var(--accent) 45%, transparent) 40%, transparent 75%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor; mask-composite: exclude;
}
.opt-d .spot-ring { inset: 0; }
.opt-d .spot-layer .ksv-key.locked::before, .opt-d .spot-layer .ksv-key.maxed::before, .opt-d .spot-layer .ksv-touchid::before { display: none; }
/* the ISO return's ring would be its box, not its L: no border light on it */
.opt-d .spot-layer .ksv-key.iso-ret::before { display: none; }

/* export / copy with nothing to export (keycaps, no key selected) */
.opt-d .rail-foot .go:disabled { opacity: .42; cursor: not-allowed; filter: none; }

/* ===== small screens: the app keeps its desktop width and scrolls sideways; say so once ===== */
.small-screen { display: none; }
@media (max-width: 767px) {
  .small-screen {
    display: flex; align-items: center; gap: 12px; position: fixed; top: 0; left: 0; right: 0; z-index: 10;
    padding: 12px 16px; background: #14161a; color: #e6eaef; border-bottom: 1px solid #23262d;
    font: 500 13px/1.4 'JetBrains Mono', ui-monospace, monospace;
  }
  .small-screen span { flex: 1; }
  .small-screen button { flex: none; display: grid; place-items: center; width: 32px; height: 32px; border: 0; border-radius: 8px; background: #23262d; color: inherit; font-size: 16px; cursor: pointer; }
}
/* narrower than the app (.opt-d min-width), the page scrolls sideways: the rail stays open
   and can't be folded (app.js NARROW) */
@media (max-width: 1119px) {
  .opt-d .rail-btn, .opt-d .hint-rail { display: none; }
}

/* ===== what went out, with the rail folded (app.js showShot): a thumbnail bottom right,
   then the image large on a click ===== */
.opt-d .shot {
  position: fixed; right: 32px; bottom: 30px; z-index: 20; padding: 0; cursor: zoom-in;
  display: block; border: 1px solid var(--line); border-radius: 10px; overflow: hidden;
  background: var(--canvas-bg); box-shadow: 0 10px 30px rgba(0,0,0,.35);
  /* slides in from the right edge, and back out past it when it goes */
  opacity: 0; transform: translateX(calc(100% + 40px)); transition: opacity .3s ease, transform .35s cubic-bezier(.2, .8, .2, 1);
}
.opt-d .shot.in { opacity: 1; transform: none; }
.opt-d .shot:hover { border-color: var(--accent); }
.opt-d .shot img { display: block; max-width: 220px; max-height: 120px; }
.opt-d .shot-cap {
  position: absolute; left: 6px; bottom: 6px; padding: 3px 6px; border-radius: 5px;
  font: 600 10.5px/1 var(--mono); color: var(--onFg, #0a0b0d); background: var(--accent);
}
.opt-d .shot-view {
  position: fixed; inset: 0; z-index: 30; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 14px;
  padding: 40px; background: rgba(0,0,0,.6); -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
  cursor: zoom-out; outline: none; opacity: 0; transition: opacity .2s ease;
}
.opt-d .shot-view.in { opacity: 1; }
.opt-d .shot-fig { border: 1px solid var(--line); border-radius: 12px; overflow: hidden; background: var(--canvas-bg); max-width: 100%; }
.opt-d .shot-fig img { display: block; max-width: min(90vw, 1400px); max-height: 78vh; }
.opt-d .shot-name { font: 500 11.5px/1 var(--mono); color: #cfd4da; }
