/* Roboto replaces IBM Plex Sans as of this pass - loaded at 500/700 only (Roboto has
   no 600 weight; a requested 600 resolves to 700 by the CSS weight-matching spec,
   and the app never used a true 400/regular anywhere either, so this keeps every
   existing font-weight declaration below reading exactly as bold/semibold as before). */
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@500;700&family=JetBrains+Mono:wght@400;500;700&family=Caveat:wght@500;700&family=Patrick+Hand&display=swap');

:root{
  --bg:#0b0f14;
  --panel:#0e151c;
  --panel2:#111a22;
  --comp-fill:#12181e;
  --knob-off:#556370;
  --line:#1d2b38;
  --line-strong:#2a3d4d;
  --comp-outline:#95a9bb;
  --text:#c9d6e0;
  --text-dim:#7c93a5;
  --text-dimmer:#546575;
  --amber:#ffb454;
  --amber-dim:#5c4a1d;
  --green:#39ff8c;
  --green-dim:#1d5c3c;
  --red:#ff5566;
  --red-dim:#5c1d24;
  --sky:#5ec8ff;
  --sky-dim:#1d4a5c;
  /* "Pressed/active" background for toolbar toggle buttons (tool-btn, icon-only-btn,
     settings-mode-btn, etc.) and danger-hover backgrounds - distinct from --amber-dim/
     --red-dim (those are punchier chip tints) because this needs to read as a subtle
     glow, not a badge. Originally hardcoded as literal hex in a dozen+ selectors with
     no light-theme equivalent at all, which is exactly why Settings and the toolbar
     kept showing a dark near-black wash even after switching to light mode - see
     html.theme-light below for the actual bug fix. */
  --amber-active-bg:#201a0c;
  --red-active-bg:#3a1015;
  --off:#33465a;
  /* Canvas background only - a hair bluer than --bg itself (used everywhere else:
     panels, body, etc.), so just the circuit area reads as very subtly cooler than the
     rest of the app rather than perfectly neutral. Deliberately a tiny nudge, not a
     visible colour shift. */
  --canvas-bg:#0a0f16;
  /* The "lit" colour for wires, pins, switches, LEDs, and every other circuit visual
     that reads as green when carrying a 1 - kept as its own variable rather than reusing
     --green directly, since --green also has to work as small text/borders/badges
     (needing enough contrast against the panel behind it), while a wire or LED's own
     fill is a large glowing shape that wants to be as vivid as possible - the two pull
     in different directions once Light mode redefines --green to something duller for
     that text-contrast reason (see html.theme-light below). Same value as --green here
     in dark mode, so this changes nothing there. */
  --circuit-hi-color:#39ff8c;
  /* Heading/emphasis text and "brighten on hover" text used to be hardcoded #eaf2f8/#fff
     throughout this file - pulled out into their own variables (rather than reusing
     --text, which reads fine on a dark panel but isn't quite the same near-white/near-
     black "make this pop" role) so Light mode (see html.theme-light below) can flip
     them too, instead of leaving near-white headings sitting unreadable on a white page. */
  --heading:#eaf2f8;
  --text-hover:#ffffff;
  --accent-line:#8fa3b0;
  /* A fully-solved problem category's own background/border (see .problem-category.
     all-solved below) - kept as its own variable rather than a hardcoded hex, since
     Light mode needs a genuinely different (light, not just less-dark) tint here, not
     just a lighter version of the same near-black colour. */
  --cat-solved-bg:#0f2117;
  --cat-solved-border:#1f4a30;
  /* The streak counter's own "currently on a streak" background (see .game-stat.
     streak-active below) - same reasoning as --cat-solved-bg above. */
  --streak-active-bg:#1a1408;
  --streak-active-border:#5c4a1d;
  /* Truth-table result cells (table.tt td.match/.mismatch below) - same reasoning
     again: a near-black tint reads as literally dark in Light mode, not just "a subtly
     tinted version of the panel behind it". */
  --tt-match-bg:#0e2418;
  --tt-mismatch-bg:#2a1013;
  --head:'Roboto','Helvetica Neue',Arial,sans-serif;
  --mono:'JetBrains Mono',monospace;
  --caveat:'Caveat',cursive;
}

/* ===================================================================
   Light mode (Settings > Appearance) - see setTheme in components.js for the toggle,
   and the inline <script> at the top of index.html that applies this class before
   first paint if Light was the last choice, so there's no dark-then-light flash.
   Overrides the same semantic variables above rather than a parallel set of rules, so
   every component that already reads var(--bg)/var(--text)/etc. picks this up for free
   - only a handful of places that hardcoded a literal colour instead of a variable (the
   header's own gradient, the dev-credit divider) needed a direct follow-up override,
   listed at the very end of this block. Colours are generally deepened/darkened
   relative to their dark-mode counterparts (e.g. --amber, --green, --sky, --red) since
   the same bright, glow-friendly values that pop against a near-black canvas would be
   low-contrast, washed-out text/borders against a white one. */
html.theme-light{
  --bg:#eef1f4;
  --panel:#ffffff;
  --panel2:#f4f6f8;
  --comp-fill:#ffffff;
  --knob-off:#8798a5;
  --line:#e1e6ea;
  --line-strong:#c3ccd4;
  --comp-outline:#55636f;
  --text:#26313b;
  --text-dim:#5c6b78;
  --text-dimmer:#8996a1;
  --amber:#a85f09;
  --amber-dim:#f6e6c8;
  --green:#188a4d;
  --green-dim:#cdead9;
  --red:#d3312c;
  --red-dim:#f6d3d1;
  --sky:#0f7fb0;
  --sky-dim:#cfeaf6;
  --amber-active-bg:#f6e6c8;
  --red-active-bg:#f6d3d1;
  --off:#748290;
  --canvas-bg:#ecf0f6;
  --heading:#101a22;
  --text-hover:#101a22;
  --accent-line:#5c6b78;
  /* A brighter, more saturated green than --green above, specifically for wires/pins/
     switches/LEDs/etc - see the --circuit-hi-color comment in :root. Without this, a lit
     default wire or LED used --green at reduced opacity, which read as a dull, muddy
     dark green against a white canvas instead of something that visibly looks "on". */
  --circuit-hi-color:#22c55e;
  /* A light, airy green tint rather than a near-black one with a faint green cast - see
     the --cat-solved-bg comment in :root. */
  --cat-solved-bg:#e3f5e9;
  --cat-solved-border:#a8dbb8;
  /* A light, warm amber tint rather than a near-black one with a faint amber cast. */
  --streak-active-bg:#fdf1de;
  --streak-active-border:#f0cd93;
  --tt-match-bg:#dff5e6;
  --tt-mismatch-bg:#fbe0e0;
}
html.theme-light header{background:linear-gradient(180deg,var(--panel2),var(--panel));}
html.theme-light .dev-credit{border-top-color:var(--accent-line);}
html.theme-light ::selection{background:rgba(201,114,10,.25);}
*{box-sizing:border-box;}
html,body{margin:0;height:100%;background:var(--bg);color:var(--text);font-family:var(--mono);
  -webkit-user-select:none;-moz-user-select:none;user-select:none;overflow-x:auto;}
input, textarea, select, #modalMsg{
  -webkit-user-select:text;-moz-user-select:text;user-select:text;}
/* 560px is the floor - below that, the page stops shrinking/reflowing altogether and the
   browser scrolls horizontally to reach the rest of it instead, rather than continuing
   to compress the header/palette/canvas into an unusable sliver.
   --palette-w/--side-w (defaulting to the original 210px/340px) are set as inline custom
   properties on this same element by the drag-to-resize handles in interactions.js -
   every other spot that used to hardcode 210px (.palette-footer, plus the
   .side-collapsed variant below) now reads --palette-w too, so they stay aligned with
   the palette's actual current width instead of just its original default. */
.app{display:grid;grid-template-columns:var(--palette-w,210px) 1fr var(--side-w,340px);grid-template-rows:auto 1fr;height:100vh;min-width:560px;overflow:hidden;}
header{grid-column:1/4;min-width:0;background:linear-gradient(180deg,#111a22,var(--panel));border-bottom:1px solid var(--line);
  display:flex;flex-wrap:nowrap;align-items:center;position:relative;padding:9px 138px 9px 16px;gap:9px;
  box-shadow:0 2px 12px rgba(0,0,0,.35);z-index:50;}

/* Clear canvas: pinned to the header's own right edge via position:absolute (fully out
   of the flex flow, not just "last in the row"), so it can never be pushed out of view,
   clipped, or swept into the hamburger/nav-actions collapse - the header's own
   padding-right above reserves a lane for it so nothing else in the flex row renders
   underneath it. right:14px (rather than a larger gap) keeps it close to Save as
   component, matching .nav-actions's own 10px inter-button spacing, while the lane
   itself still leaves enough clearance that the two never actually touch. Sheds its
   text label down to icon-only at the same width the nav-actions group collapses
   (1100px), shrinking its own reserved lane to match. */
#clearBtn{position:absolute;top:50%;right:10px;transform:translateY(-50%);z-index:60;}
/* Tab bar: a floating pill at the top of the CANVAS area only (not the full app width -
   .canvas-outer is its positioning parent, same idea as the zoom controls/minimap
   floating over the canvas elsewhere) - each tab is a fully independent canvas + undo
   history (see js/tabs.js). Sized to its own content (inline-flex, not a full-width
   bar), so with a single tab it's just a small "current tab name + add" pill and the
   rest of the canvas stays clear; it only grows as more tabs are added. */
.tab-bar{position:absolute;top:0;left:0;z-index:30;display:flex;align-items:center;
  gap:6px;background:var(--panel2);border:1px solid var(--line-strong);border-top:none;border-left:none;
  border-radius:0 0 8px 0;padding:4px;max-width:calc(100% - 12px);box-shadow:0 4px 18px rgba(0,0,0,.35);}
/* Only the tab list itself scrolls when there are too many to fit - the add button
   (a sibling, outside this scrolling area) stays fixed just to its right, so it's
   always reachable in one place instead of scrolling along with the tabs or getting
   squeezed against/behind the last one. */
.tab-list{display:flex;align-items:center;gap:5px;min-width:0;overflow-x:auto;flex:0 1 auto;}
.tab-item{display:flex;align-items:center;gap:6px;padding:6px 8px 6px 12px;border-radius:6px;
  background:none;border:1px solid var(--line-strong);cursor:pointer;flex:none;
  font-family:var(--head);font-size:12.5px;color:var(--text-dim);white-space:nowrap;max-width:180px;}
.tab-item:hover{color:var(--text);background:var(--panel);border-color:var(--accent-line);}
.tab-item.active{background:var(--bg);color:var(--heading);border-color:var(--amber);}
.tab-item[draggable="true"]{cursor:grab;}
.tab-item.drop-hover{outline:2px dashed var(--sky);outline-offset:-2px;}
.tab-item-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:130px;}
.tab-item-close{flex:none;background:none;border:none;color:var(--text-dimmer);cursor:pointer;
  font-size:14px;line-height:1;padding:1px 2px;border-radius:3px;}
.tab-item-close:hover{color:var(--red);background:rgba(255,90,90,.14);}
.tab-add-btn{flex:none;width:26px;height:26px;display:flex;align-items:center;justify-content:center;
  background:none;border:1px dashed var(--line-strong);border-radius:6px;color:var(--text-dim);
  font-size:15px;line-height:1;cursor:pointer;}
.tab-add-btn:hover{border-color:var(--amber);color:var(--amber);}

/* A reasonable floor so the logo+wordmark never gets squeezed down to nothing as the
   header's flex row runs out of room, regardless of how wide "GATE FORGE" itself
   actually renders in any given font. */
.brand{display:flex;align-items:center;gap:8px;flex:0 0 auto;min-width:190px;}
.brand .logo{flex:0 0 auto;display:block;height:34px;width:auto;object-fit:contain;}
.brand-text{display:flex;flex-direction:column;line-height:1.15;}
header h1{font-family:var(--head);font-size:18px;font-weight:700;letter-spacing:.05em;margin:0;color:var(--heading);white-space:nowrap;}
header .spacer{flex:1;}

header select, header button{
  background:var(--panel2);color:var(--text);border:1px solid var(--line-strong);
  font-family:var(--head);font-weight:500;font-size:13px;padding:7px 10px;border-radius:6px;cursor:pointer;white-space:nowrap;
  transition:border-color .15s, color .15s, background .15s;
}
/* Fixed, deliberately narrow width - a native <select> otherwise sizes itself to fit
   its widest OPTION text (e.g. a long example name), not just the short placeholder
   actually showing most of the time, which was pushing the rest of the header's
   buttons (Clear canvas especially) off the right edge of the screen. Truncates with
   an ellipsis instead of growing when a longer example name is selected. */
#exampleSelect{width:118px;overflow:hidden;text-overflow:ellipsis;flex:0 0 auto;}
header select:hover, header button:hover{border-color:var(--amber);color:var(--text-hover);}
header button.accent{border-color:var(--amber);color:var(--amber);}
header button.danger{display:inline-flex;align-items:center;gap:7px;border-color:var(--red);color:var(--red);}
header button.danger:hover{color:var(--text-hover);border-color:var(--red);}
header button.danger .icon{width:14px;height:14px;flex:0 0 auto;}

/* A touch more breathing room before the undo/redo group starts - the header's own
   uniform gap alone read a little tight right after the brand block. */
#historyGroup{margin-left:6px;}

.annotate-menu-wrap{position:relative;flex:0 0 auto;}
#annotateMenuBtn{display:none;} /* shown only once the toolbar itself collapses - see the 1150px breakpoint below. Uses an ID selector (not just .annotate-menu-btn) since .icon-only-btn's own display:flex, defined later in this file, would otherwise win the tie on specificity alone. */
.annotate-toolbar{display:flex;align-items:center;gap:8px;flex:0 0 auto;flex-wrap:wrap;row-gap:6px;max-width:100%;}
.toolbar-label{font-family:var(--head);font-size:11px;font-weight:600;letter-spacing:.08em;
  text-transform:uppercase;color:var(--text-dim);white-space:nowrap;}
.tool-group{display:flex;align-items:center;gap:2px;background:var(--panel2);border:1px solid var(--line-strong);border-radius:7px;padding:2px;}
.tool-group .tool-btn{background:none;border:1px solid transparent;padding:6px;border-radius:5px;
  width:28px;height:28px;display:flex;align-items:center;justify-content:center;color:var(--text-dim);}
.tool-group .tool-btn:hover{border-color:transparent;background:var(--panel);color:var(--text-hover);}
.tool-group .tool-btn.active{border-color:var(--amber);color:var(--amber);background:var(--amber-active-bg);}
.icon{width:15px;height:15px;flex:0 0 auto;pointer-events:none;}
.tool-label{display:flex;align-items:center;gap:5px;font-family:var(--head);font-size:12px;
  color:var(--text-dim);white-space:nowrap;padding:0 3px;}
.tool-label .icon{width:14px;height:14px;color:var(--text-dim);}
.tool-label input{width:30px;background:var(--panel);border:1px solid var(--line-strong);color:var(--text);
  border-radius:4px;padding:5px 4px;font-family:var(--mono);font-size:12px;text-align:center;
  -moz-appearance:textfield;appearance:textfield;}

/* Auto Pencil tool + its vanish-seconds timer are one idea (pick the tool, tune its delay),
   so they're fused into a single pill instead of reading as two separate controls. */
.tool-subgroup{display:flex;align-items:center;background:var(--panel);border:1px solid var(--line-strong);
  border-radius:6px;overflow:hidden;margin:0 1px;}
.tool-subgroup .tool-btn{border-radius:0;background:none;}
.tool-subgroup .tool-btn:hover{background:var(--bg);}
.tool-subgroup .tool-btn.active{background:var(--amber-active-bg);color:var(--amber);}
.tool-subgroup .tool-label{padding:0 6px 0 0;border-left:1px solid var(--line-strong);}
.tool-subgroup .tool-label input{border:none;background:none;border-radius:0;width:24px;padding:6px 2px;}
.tool-label input::-webkit-outer-spin-button,
.tool-label input::-webkit-inner-spin-button{appearance:none;-webkit-appearance:none;margin:0;}
.tool-group #clearAnnotationsBtn.icon-btn{background:none;border:1px solid transparent;
  width:28px;height:28px;padding:0;border-radius:5px;display:flex;align-items:center;justify-content:center;color:var(--text-dim);}
.tool-group #clearAnnotationsBtn.icon-btn:hover{background:var(--panel);border-color:transparent;color:var(--red);}

.tool-group .icon-btn{background:none;border:1px solid transparent;
  width:28px;height:28px;padding:0;border-radius:5px;display:flex;align-items:center;justify-content:center;
  color:var(--text-dim);cursor:pointer;}
.tool-group .icon-btn:hover{background:var(--panel);border-color:transparent;color:var(--text-hover);}
.tool-group .icon-btn:disabled{opacity:.32;cursor:default;}
.tool-group .icon-btn:disabled:hover{background:none;color:var(--text-dim);}

.nav-actions{display:flex;align-items:center;gap:8px;min-width:0;overflow:hidden;}
.nav-menu-btn{display:none;flex-direction:column;justify-content:center;align-items:center;gap:4px;
  width:34px;height:34px;background:var(--panel2);border:1px solid var(--line-strong);border-radius:6px;cursor:pointer;padding:0;flex:0 0 auto;}
.nav-menu-btn span{display:block;width:16px;height:2px;background:var(--text);border-radius:1px;}
.nav-menu-btn:hover{border-color:var(--amber);}
.nav-menu-btn:hover span{background:var(--amber);}

.icon-only-btn{display:flex;align-items:center;justify-content:center;width:34px;height:34px;
  background:var(--panel2);border:1px solid var(--line-strong);border-radius:6px;cursor:pointer;padding:0;
  flex:0 0 auto;color:var(--text-dim);}
.icon-only-btn:hover{border-color:var(--amber);color:var(--amber);}
.icon-only-btn.active{border-color:var(--amber);color:var(--amber);background:var(--amber-active-bg);}

/* File menu - a professional dropdown, matching what a "File" menu looks like in a
   desktop app: trigger button + a list of rows, destructive actions set off by a divider. */
.nav-menu-item{position:relative;flex:0 0 auto;}
.menu-trigger-btn{display:flex;align-items:center;gap:5px;height:34px;padding:0 12px;
  background:var(--panel2);border:1px solid var(--line-strong);border-radius:6px;cursor:pointer;
  color:var(--text);font-family:var(--head);font-weight:500;font-size:13px;}
.menu-trigger-btn:hover{border-color:var(--amber);color:var(--text-hover);}
.menu-trigger-btn .caret{font-size:10px;color:var(--text-dim);transition:transform .15s;}
.nav-menu-item.open .menu-trigger-btn{border-color:var(--amber);}
.nav-menu-item.open .caret{transform:rotate(180deg);}
.dropdown-menu{display:none;position:absolute;top:100%;left:0;margin-top:6px;min-width:190px;
  background:var(--panel);border:1px solid var(--line-strong);border-radius:8px;padding:6px;
  z-index:200;box-shadow:0 10px 30px rgba(0,0,0,.5);}
.nav-menu-item.open .dropdown-menu{display:block;}
.dropdown-item{display:flex;align-items:center;gap:9px;width:100%;padding:9px 10px;background:none;
  border:none;border-radius:5px;color:var(--text);font-family:var(--head);font-size:13px;
  text-align:left;cursor:pointer;}
.dropdown-item .icon{width:15px;height:15px;color:var(--text-dim);flex:0 0 auto;}
.dropdown-item:hover{background:var(--panel2);color:var(--text-hover);}
.dropdown-item:hover .icon{color:var(--amber);}
.dropdown-item.danger:hover{background:var(--red-active-bg);color:var(--red);}
.dropdown-item.danger:hover .icon{color:var(--red);}
.dropdown-divider{height:1px;background:var(--line);margin:6px 4px;}

/* Settings - a centered modal, matching the same visual language as the other modals in
   the app (Set bit splits, rename, etc) rather than a dropdown anchored to one button. */
.center-modal-overlay{display:none;position:fixed;inset:0;background:rgba(4,8,12,.6);z-index:2000;
  align-items:center;justify-content:center;}
.center-modal-overlay.open{display:flex;}
.center-modal{background:var(--panel);border:1px solid var(--line-strong);border-radius:10px;
  padding:22px;width:380px;max-width:90vw;max-height:85vh;overflow-y:auto;font-family:var(--head);
  box-shadow:0 20px 60px rgba(0,0,0,.6);}
.center-modal-header{display:flex;align-items:center;justify-content:space-between;
  margin-bottom:18px;padding-bottom:14px;border-bottom:1px solid var(--line);}
.center-modal-header h2{font-size:19px;font-weight:700;color:var(--heading);margin:0;}
.modal-close-btn{display:flex;align-items:center;justify-content:center;width:28px;height:28px;
  background:none;border:1px solid var(--line-strong);border-radius:6px;color:var(--text-dim);
  font-size:18px;line-height:1;cursor:pointer;padding:0;}
.modal-close-btn:hover{border-color:var(--red);color:var(--red);}

.settings-group + .settings-group{margin-top:20px;padding-top:18px;border-top:1px solid var(--line);}
.settings-title{display:flex;align-items:center;gap:8px;font-family:var(--head);font-size:15px;
  font-weight:700;color:var(--heading);margin-bottom:7px;}
.settings-title .icon{width:17px;height:17px;color:var(--amber);flex:0 0 auto;}
.settings-desc{font-family:var(--head);font-size:12.5px;color:var(--text-dim);line-height:1.55;margin-bottom:12px;}
.settings-subtitle{font-family:var(--head);font-size:11px;font-weight:700;color:var(--text-dim);
  text-transform:uppercase;letter-spacing:.05em;margin:14px 0 8px;}
.settings-subtitle:first-of-type{margin-top:2px;}
.settings-palette-row{display:flex;flex-wrap:wrap;gap:7px;}
.settings-swatch{width:24px;height:24px;min-width:24px;border-radius:5px;padding:0;cursor:pointer;
  background:var(--sw-color);border:2px solid transparent;box-shadow:0 0 0 1px var(--line-strong) inset;}
.settings-swatch:hover{transform:scale(1.1);}
.settings-swatch.active{border-color:var(--heading);box-shadow:0 0 0 2px var(--panel), 0 0 0 3px var(--sw-color);}
.settings-swatch.none-swatch{background:var(--panel);position:relative;}
.settings-swatch.none-swatch::after{content:'';position:absolute;left:4px;right:4px;top:11px;
  height:2px;background:var(--red);transform:rotate(45deg);}
.settings-slider-row{display:flex;align-items:center;gap:10px;width:100%;font-family:var(--head);
  font-size:12.5px;color:var(--text);}
.settings-slider-row span:first-child{flex:0 0 auto;color:var(--text-dim);}
.settings-slider-row input[type="range"]{flex:1;accent-color:var(--amber);cursor:pointer;}
.settings-slider-val{flex:0 0 auto;font-family:var(--mono);font-size:11px;color:var(--text-dim);min-width:34px;text-align:right;}
.settings-row{display:flex;align-items:center;}
.settings-row + .settings-row{margin-top:8px;}
.settings-mode-btn{flex:1;text-align:center;padding:7px 12px;background:none;border:1px solid transparent;
  border-radius:5px;color:var(--text-dim);font-family:var(--head);font-size:12.5px;cursor:pointer;}
.settings-mode-btn:hover{background:var(--panel2);color:var(--text-hover);}
.settings-mode-btn.active{border-color:var(--amber);color:var(--amber);background:var(--amber-active-bg);}
#delayModeGroup{width:100%;}
.settings-row button.danger{width:100%;background:var(--panel2);border:1px solid var(--red);color:var(--red);
  font-family:var(--head);font-weight:500;font-size:13px;padding:9px;border-radius:5px;cursor:pointer;}
.settings-row button.danger:hover{background:var(--red-active-bg);color:var(--text-hover);}
#delayMsRow .tool-label{width:100%;justify-content:space-between;}
#delayMsRow .tool-label input{width:60px;}

/* .palette itself no longer scrolls or carries the inner padding - both moved onto
   .palette-scroll (index.html) so the resize handle below, a direct child of .palette
   sitting outside that scrolling div, spans the panel's full height no matter how far
   the list inside has been scrolled. */
/* overflow:hidden here (rather than the default visible) is load-bearing, not
   decorative: without it, a CSS grid item's automatic minimum size is its content's
   min-content size, so .palette-scroll's tall content would stretch this whole grid row
   - and the fixed-height .app layout with it - to fit, instead of .palette-scroll
   scrolling internally within the row's actual 1fr height. */
.palette{background:var(--panel);border-right:1px solid var(--line);position:relative;overflow:hidden;}
.palette-scroll{height:100%;box-sizing:border-box;overflow-y:auto;padding:16px 12px 170px;}

/* Drag-to-resize handles for the palette (left) and side panel (right) - a thin strip
   along the panel's own inner edge. Swaps the cursor to a resize icon on hover, and its
   data-tip (index.html) is picked up automatically by the shared tooltip system (see
   js/tooltip.js) so the "drag to resize" hint appears with no extra wiring here. The
   actual drag math (updating --palette-w/--side-w on .app, clamped to a sensible minimum
   and to a per-panel max share of the viewport width) lives in interactions.js - this is
   just the hit target and its hover affordance. Deliberately no visual highlight on
   hover/drag beyond the cursor swap - .resize-handle.dragging still gets toggled in JS
   (it's what the body.resizing-ew cursor override keys off of) but nothing here paints
   it. Sits on .palette/.side directly rather than on their scrolling inner div, so it's
   unaffected by that div's own overflow-x and never gets clipped or spawns a scrollbar. */
.resize-handle{position:absolute;top:0;bottom:0;width:8px;cursor:col-resize;z-index:20;
  background:transparent;}
.palette-resize-handle{right:0;}
.side-resize-handle{left:0;}
/* While actively dragging, keep the resize cursor over the WHOLE page (not just the thin
   8px strip) so a fast drag that briefly outruns the strip doesn't flicker back to a
   plain pointer, and suppress text selection for the same reason - a drag this size
   otherwise tends to select palette/panel text as collateral damage. */
body.resizing-ew, body.resizing-ew *{cursor:col-resize!important;user-select:none!important;}
/* Palette search box - filters both the built-in gate-btn palette and the Saved
   components folder tree by name (see applyPaletteSearch() in components.js). Styled
   like #folderPickerNewInput (the folder-name text field) for consistency - this is
   one of the few places in the app where an actual bordered input field is correct,
   unlike the chip/badge language used elsewhere, since it needs to read as editable
   text entry rather than a status indicator. */
.palette-search-row{position:relative;margin-bottom:12px;}
.palette-search-input{width:100%;box-sizing:border-box;background:var(--panel2);color:var(--heading);
  border:1px solid var(--line-strong);border-radius:6px;padding:8px 30px 8px 10px;
  font-family:var(--head);font-size:12.5px;outline:none;transition:border-color .15s;}
.palette-search-input::placeholder{color:var(--text-dimmer);}
.palette-search-input:focus{border-color:var(--sky);}
.palette-search-clear{position:absolute;right:6px;top:50%;transform:translateY(-50%);
  background:none;border:none;color:var(--text-dim);cursor:pointer;font-size:15px;
  line-height:1;padding:3px 5px;border-radius:4px;}
.palette-search-clear:hover{color:var(--red);background:rgba(255,90,90,.14);}
.palette-section{margin-bottom:4px;}
.palette-header{width:100%;background:none;border:none;color:var(--heading);font-family:var(--head);
  font-size:13px;font-weight:600;letter-spacing:.08em;text-transform:uppercase;
  display:flex;align-items:center;justify-content:space-between;padding:7px 2px;cursor:pointer;margin:0;}
.palette-header:hover{color:var(--amber);}
.palette-header .chevron{font-size:11px;color:var(--text-dim);transition:transform .15s;}
.palette-section.collapsed .chevron{transform:rotate(-90deg);}
.palette-section.collapsed .palette-body{display:none;}
.palette-body{padding-bottom:6px;}

.tool-btn{background:var(--panel2);border:1px solid var(--line-strong);color:var(--text);
  font-family:var(--head);font-size:12px;padding:7px 10px;border-radius:5px;cursor:pointer;white-space:nowrap;}
.tool-btn:hover{border-color:var(--amber);color:var(--text-hover);}
.tool-btn.active{border-color:var(--amber);color:var(--amber);background:var(--amber-active-bg);}

.pencil-stroke{fill:none;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:round;pointer-events:none;}
/* Auto Pencil strokes glow in their own colour (see --pencil-glow-color, set per-stroke
   in annotate.js) - a visual cue distinguishing them from permanent Pencil strokes at a
   glance, on top of the fade-out itself. The glow fades along with the stroke since
   opacity is applied to the whole element (see renderAnnotations), filter included. */
.pencil-stroke.pencil-temp{
  filter:
    drop-shadow(0 0 2px var(--pencil-glow-color, currentColor))
    drop-shadow(0 0 6px var(--pencil-glow-color, currentColor));
}
/* Line/rectangle/circle annotation shapes (see currentTool==='shape-line'/'shape-rect'/
   'shape-circle' in annotate.js) - same visual weight as a pencil stroke (outline only,
   no fill), just drawn as a real SVG line/rect/circle instead of a freehand path. */
.anno-shape{fill:none;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:round;pointer-events:none;}

/* Selection highlight for annotations (Ctrl+A while on a pencil/eraser/text tool - see
   selectedAnnotations in annotate.js). A dashed box behind selected text notes, and a
   thick, faded halo behind selected pencil strokes, both drawn before the real content
   so they read as a highlight rather than replacing it. */
.anno-selected-outline{fill:none;stroke:var(--amber);stroke-width:1.5;stroke-dasharray:5,4;pointer-events:none;}
.anno-selected-outline-stroke{fill:none;stroke:var(--amber);stroke-width:11;stroke-linecap:round;
  stroke-linejoin:round;opacity:.3;pointer-events:none;}

/* Text notes: rendered as a foreignObject div, formatted with bold/italic/bullets/font
   from the editor toolbar below. Kept pointer-events:none, same as pencil strokes above,
   so hit-testing for the text tool and eraser goes through hitTextAt()/eraseNear() in
   annotate.js rather than the DOM - clicks fall through to gates/wires underneath exactly
   like pencil strokes do. */
.text-anno-fo{pointer-events:none;}
.text-annotation{
  font-size:23px;line-height:1.3;font-weight:500;
  white-space:pre-wrap;word-wrap:break-word;pointer-events:none;
}
/* list-style-position:inside makes the marker part of the li's own inline content instead
   of a separately-positioned box that sits in the (fallible) padding area to its left -
   so a wide marker like "10." can never render past the box's own left edge. */
.text-annotation ul, .text-annotation ol{margin:2px 0;padding-left:20px;list-style-position:inside;}
/* Tab/Shift+Tab (see currentListItem/onKeydown in annotate.js) nests a point into a
   sub-list - give each nesting level its own marker style, like a classic outline:
   1,2,3 -> a,b,c -> i,ii,iii for numbers; bullet -> circle -> square for bullets. */
.text-annotation ol ol{list-style-type:lower-alpha;}
.text-annotation ol ol ol{list-style-type:lower-roman;}
.text-annotation ul ul{list-style-type:circle;}
.text-annotation ul ul ul{list-style-type:square;}

/* Inline text editor: a contentEditable box laid exactly over the note's foreignObject
   position (see startTextEdit in annotate.js), same "edit in place" approach as
   .inline-num-edit below. Resizable so a note's box can be grown to fit more text. */
.inline-text-edit{
  position:fixed;box-sizing:border-box;z-index:500;
  background:var(--panel);border:1.5px solid var(--amber);border-radius:5px;
  font-size:23px;line-height:1.3;font-weight:500;
  padding:5px 8px;margin:0;outline:none;resize:both;overflow:auto;caret-color:var(--amber);
  white-space:pre-wrap;word-wrap:break-word;
}
.inline-text-edit ul, .inline-text-edit ol{margin:2px 0;padding-left:20px;list-style-position:inside;}
.inline-text-edit ol ol{list-style-type:lower-alpha;}
.inline-text-edit ol ol ol{list-style-type:lower-roman;}
.inline-text-edit ul ul{list-style-type:circle;}
.inline-text-edit ul ul ul{list-style-type:square;}

/* Table option for notes (truth tables etc.) - inserted via the table button in the
   note toolbar (see buildAnnoTableHtml in annotate.js). Shared by both the live
   contentEditable box and the static saved note, so it looks identical either way. */
.text-annotation table.anno-table, .inline-text-edit table.anno-table{
  border-collapse:collapse;margin:4px 0;font-size:.85em;line-height:1.25;
}
.text-annotation table.anno-table th, .text-annotation table.anno-table td,
.inline-text-edit table.anno-table th, .inline-text-edit table.anno-table td{
  border:1.5px solid currentColor;padding:3px 9px;min-width:20px;text-align:center;
}
.text-annotation table.anno-table th, .inline-text-edit table.anno-table th{
  font-weight:700;background:rgba(255,255,255,.12);
}
/* Same "white wash on an already-light background is a no-op" issue as
   .timing-row-bg above - these tables sit directly on the canvas, whose
   background also flips with the theme. */
html.theme-light .text-annotation table.anno-table th,
html.theme-light .inline-text-edit table.anno-table th{
  background:rgba(0,0,0,.08);
}

/* Floating toolbar above the note being edited: two rows (drag handle + bold/italic/
   underline/strike/bullets/numbering on top; font/size/color pickers below) rather than
   one long line. Each row is justified between its outer items (space-between), so
   row1's and row2's left AND right edges line up no matter which row is naturally wider. */
.text-edit-toolbar{
  position:fixed;z-index:501;display:inline-flex;flex-direction:column;gap:4px;
  background:var(--panel2);border:1px solid var(--line-strong);border-radius:6px;
  padding:4px;box-shadow:0 8px 20px rgba(0,0,0,.5);
}
.text-edit-row{display:flex;align-items:center;justify-content:space-between;gap:6px;}
.text-edit-btn-group{display:flex;align-items:center;gap:3px;}
.text-edit-drag{
  width:16px;height:24px;display:flex;align-items:center;justify-content:center;
  cursor:grab;color:var(--text-dim);font-size:12px;letter-spacing:-2px;user-select:none;
}
.text-edit-drag:active{cursor:grabbing;}
.text-edit-btn{
  width:26px;height:26px;display:flex;align-items:center;justify-content:center;
  background:none;border:1px solid transparent;border-radius:4px;color:var(--text);
  font-family:var(--head);font-size:13px;font-weight:700;cursor:pointer;padding:0;
}
.text-edit-btn.is-italic{font-style:italic;font-weight:500;}
.text-edit-btn.is-underline{text-decoration:underline;font-weight:500;}
.text-edit-btn.is-strike{text-decoration:line-through;font-weight:500;}
.text-edit-btn:hover{background:var(--panel);border-color:var(--line-strong);}
.text-edit-btn.active{border-color:var(--amber);color:var(--amber);background:var(--amber-active-bg);}
.text-edit-delete{color:var(--text-dim);font-size:11px;}
.text-edit-delete:hover{background:rgba(255,90,90,.14);border-color:var(--red);color:var(--red);}
.text-edit-font, .text-edit-size{
  background:var(--panel);border:1px solid var(--line-strong);color:var(--text);
  font-family:var(--head);font-size:12px;border-radius:4px;padding:4px;
  cursor:pointer;
}
.text-edit-font{max-width:118px;}
.text-edit-size{max-width:52px;}

/* Text color: a single swatch showing the current color; clicking it pops the other
   choices open in a small panel instead of showing all of them all the time. */
.text-edit-color-wrap{position:relative;}
.text-edit-color-btn{
  width:20px;height:20px;border-radius:50%;padding:0;cursor:pointer;
  border:1.5px solid var(--line-strong);
}
.text-edit-color-btn:hover{border-color:var(--heading);}
.text-edit-color-popup{
  display:none;position:absolute;top:calc(100% + 6px);right:0;z-index:502;
  background:var(--panel2);border:1px solid var(--line-strong);border-radius:6px;
  padding:6px;box-shadow:0 8px 20px rgba(0,0,0,.5);
  gap:5px;
}
.text-edit-color-popup.open{display:flex;}
.text-edit-swatch{
  width:18px;height:18px;border-radius:50%;padding:0;cursor:pointer;
  border:1.5px solid var(--line-strong);
}
.text-edit-swatch:hover{border-color:var(--text-dim);}
.text-edit-swatch.active{border-color:var(--heading);box-shadow:0 0 0 2px var(--amber);}
/* Custom colour swatch in the text note's colour popup - same wrapped-input-plus-'+'
   approach as .pencil-color-custom-wrap, just sized to match .text-edit-swatch (18px)
   instead of the pencil popover's 19px. */
.text-edit-color-custom-wrap{position:relative;width:18px;height:18px;flex:none;}
.text-edit-color-custom-wrap .text-edit-color-custom{
  width:100%;height:100%;padding:0;cursor:pointer;background:none;overflow:hidden;
}
.text-edit-color-custom::-webkit-color-swatch-wrapper{padding:0;}
.text-edit-color-custom::-webkit-color-swatch{border:none;border-radius:50%;}
.text-edit-color-custom::-moz-color-swatch{border:none;border-radius:50%;}
.text-edit-color-custom-plus{
  position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
  font-size:12px;line-height:1;font-weight:700;color:#fff;
  text-shadow:0 0 2px rgba(0,0,0,.85),0 0 1px rgba(0,0,0,.85);
  pointer-events:none;
}

/* Table-size popup (rows x cols), opened from the table button - same "click to pop
   open a small panel" pattern as the color picker above. */
.text-edit-table-wrap{position:relative;}
.text-edit-table-popup{
  display:none;position:absolute;top:calc(100% + 6px);left:0;z-index:502;
  align-items:center;gap:5px;
  background:var(--panel2);border:1px solid var(--line-strong);border-radius:6px;
  padding:6px;box-shadow:0 8px 20px rgba(0,0,0,.5);
}
.text-edit-table-popup.open{display:flex;}
.text-edit-table-input{
  width:38px;background:var(--panel);border:1px solid var(--line-strong);color:var(--text);
  font-family:var(--mono);font-size:12px;border-radius:4px;padding:4px;text-align:center;
}
.text-edit-table-x{color:var(--text-dim);font-size:12px;}
.text-edit-table-insert{
  background:none;border:1px solid var(--amber);color:var(--amber);border-radius:4px;
  font-family:var(--head);font-size:11px;font-weight:600;padding:5px 8px;cursor:pointer;
  white-space:nowrap;
}
.text-edit-table-insert:hover{background:var(--amber-active-bg);}

/* Pen colour palette popover (annotate toolbar) - same "click a swatch to pop open a
   small panel" pattern as the text-editor's own colour picker above, just with two
   independent rows (Pencil, Auto Pencil) since each tool remembers its own colour. */
.pencil-color-wrap{position:relative;}
/* Shapes dropdown (Line/Rectangle/Circle bundled behind one toolbar button, rather than
   three separate ones, to keep the annotate toolbar from running wide enough to force
   the whole header to wrap awkwardly) - same "click to pop open a small panel" pattern
   as the pencil colour popover below, just a vertical list of labelled options instead
   of a row of swatches. */
.shapes-tool-wrap{position:relative;}
.shapes-tool-popup{
  display:none;position:absolute;top:calc(100% + 6px);left:0;z-index:502;
  flex-direction:column;gap:2px;min-width:130px;
  background:var(--panel2);border:1px solid var(--line-strong);border-radius:8px;
  padding:5px;box-shadow:0 8px 22px rgba(0,0,0,.5);
}
.shapes-tool-wrap.open .shapes-tool-popup{display:flex;}
.tool-group .shapes-tool-popup .shapes-tool-option{
  width:100%;height:auto;background:none;border:1px solid transparent;border-radius:5px;
  display:flex;align-items:center;gap:9px;padding:7px 8px;cursor:pointer;
  color:var(--text);font-family:var(--head);font-size:12.5px;justify-content:flex-start;
}
.tool-group .shapes-tool-popup .shapes-tool-option:hover{background:var(--panel);}
.tool-group .shapes-tool-popup .shapes-tool-option.active{border-color:var(--amber);color:var(--amber);background:var(--amber-active-bg);}
.shapes-tool-option .icon{width:15px;height:15px;flex:none;}

.pencil-color-popup{
  display:none;position:absolute;top:calc(100% + 6px);left:0;z-index:502;
  flex-direction:column;gap:10px;
  background:var(--panel2);border:1px solid var(--line-strong);border-radius:8px;
  padding:10px;box-shadow:0 8px 22px rgba(0,0,0,.5);min-width:180px;
}
.pencil-color-wrap.open .pencil-color-popup{display:flex;}
.pencil-color-label{font-family:var(--head);font-size:10.5px;font-weight:600;color:var(--text-dim);
  text-transform:uppercase;letter-spacing:.05em;margin-bottom:6px;}
.pencil-color-row{display:flex;flex-wrap:wrap;align-items:center;gap:6px;}
.pencil-color-swatch{
  width:19px;height:19px;border-radius:50%;padding:0;cursor:pointer;flex:none;
  background:var(--sw-color);border:1.5px solid var(--line-strong);
}
.pencil-color-swatch:hover{border-color:var(--text-dim);}
.pencil-color-swatch.active{border-color:var(--heading);box-shadow:0 0 0 2px var(--amber);}
.pencil-color-custom{
  width:19px;height:19px;padding:0;border:1.5px solid var(--line-strong);border-radius:50%;
  cursor:pointer;background:none;flex:none;overflow:hidden;
}
.pencil-color-custom::-webkit-color-swatch-wrapper{padding:0;}
.pencil-color-custom::-webkit-color-swatch{border:none;border-radius:50%;}
.pencil-color-custom::-moz-color-swatch{border:none;border-radius:50%;}
/* Wraps the native colour input so a '+' can sit visually on top of it (see
   buildPencilColorRow, annotate.js) - the input fills the wrapper exactly, and the mark
   is pointer-events:none so clicks pass straight through to the input underneath. White
   with a dark text-shadow keeps the '+' legible over light or dark custom colours alike,
   the same problem solved with a similar approach elsewhere for the onboarding arrow. */
.pencil-color-custom-wrap{position:relative;width:19px;height:19px;flex:none;}
.pencil-color-custom-wrap .pencil-color-custom{width:100%;height:100%;}
.pencil-color-custom-plus{
  position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
  font-size:13px;line-height:1;font-weight:700;color:#fff;
  text-shadow:0 0 2px rgba(0,0,0,.85),0 0 1px rgba(0,0,0,.85);
  pointer-events:none;
}
.gate-btn{
  width:100%;background:var(--panel2);border:1px solid var(--line-strong);color:var(--text);
  font-family:var(--mono);font-size:14px;padding:8px 12px;border-radius:5px;margin-bottom:4px;
  cursor:pointer;text-align:left;display:flex;align-items:center;justify-content:space-between;gap:8px;
}
.gate-btn:hover{border-color:var(--amber);color:var(--text-hover);}
.gate-btn .tag{font-family:var(--head);font-size:11px;color:var(--text-dim);}
.gate-btn .del-custom{background:none;border:none;color:var(--text-dim);cursor:pointer;font-size:16px;line-height:1;padding:0 2px;display:none;flex:none;}
.gate-btn .del-custom:hover{color:var(--red);}
.gate-btn .edit-custom-pins{background:none;border:none;color:var(--text-dim);cursor:pointer;font-size:13px;line-height:1;padding:0 2px;display:none;flex:none;}
.gate-btn .edit-custom-pins:hover{color:var(--sky);}
/* Edit/delete only take up room once the row's actually hovered - hidden the rest of
   the time (display:none, not just invisible, so the name below actually gets to use
   that freed-up width instead of just leaving blank space where they'd have been). */
.gate-btn.custom-gate-btn:hover .edit-custom-pins, .gate-btn.custom-gate-btn:hover .del-custom{display:inline-block;}
/* Saved-circuits palette entries get the same --sky accent as their on-canvas
   instances (.gate-body.custom-body), so they're recognizable as "components" in the
   sidebar too, not just once placed. */
.gate-btn.custom-gate-btn{border-left:3px solid var(--sky);}
.gate-btn.custom-gate-btn:hover{border-left-color:var(--sky);}
.gate-btn.custom-gate-btn[draggable="true"]{cursor:grab;}
.gate-btn.custom-gate-btn:active{cursor:grabbing;}

/* ===================================================================
   Saved-components folder tree
   =================================================================== */
.custom-tree-toolbar{display:flex;justify-content:flex-end;margin-bottom:6px;}
.custom-tree-newfolder{
  background:none;border:1px dashed var(--line-strong);color:var(--text-dim);
  font-family:var(--head);font-size:11.5px;padding:4px 9px;border-radius:5px;cursor:pointer;
}
.custom-tree-newfolder:hover{border-color:var(--sky);color:var(--sky);}
.custom-tree-level{min-height:4px;border-radius:5px;}
.custom-tree-level.drop-hover{outline:2px dashed var(--sky);outline-offset:2px;background:rgba(90,180,255,.06);}
/* A thin divider between sibling folders/components at the same level, so a flat list
   of several saved components (or several folders) reads as distinct rows rather than
   running together - the alphabetical sort above is easier to scan with these in place. */
.custom-tree-level > *:not(:last-child){border-bottom:1px solid var(--line);}
.custom-folder{margin-bottom:2px;}
.custom-folder-row{
  display:flex;align-items:center;gap:6px;padding:7px 8px;border-radius:5px;cursor:pointer;
  border:1px solid transparent;border-left:3px solid var(--line-strong);
}
.custom-folder-row:hover{background:var(--panel2);border-left-color:var(--sky);}
.custom-folder-row.drop-hover{outline:2px dashed var(--sky);outline-offset:-2px;background:rgba(90,180,255,.1);}
.custom-folder-chevron{
  display:inline-block;font-size:10px;color:var(--text-dim);transition:transform .1s;
  transform:rotate(0deg);flex:none;
}
.custom-folder-chevron.expanded{transform:rotate(90deg);}
.custom-folder-icon{width:14px;height:14px;flex:none;color:var(--sky);}
.custom-folder-name{flex:1 1 auto;font-family:var(--head);font-weight:700;font-size:13px;color:var(--text);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.custom-folder-row:hover .custom-folder-name{color:var(--amber);}
.custom-folder-action{
  background:none;border:none;color:var(--text-dim);cursor:pointer;font-size:12px;
  line-height:1;padding:2px 3px;flex:none;display:none;
}
/* All three (+, edit, delete) only show once the row's actually hovered, same reasoning
   as the saved-components' own edit/delete buttons above - the name gets that width to
   itself the rest of the time instead of being squeezed by three buttons it doesn't
   need most of the time. */
.custom-folder-row:hover .custom-folder-action{display:inline-block;}
.custom-folder-action:hover{color:var(--sky);}
.custom-folder-delete:hover{color:var(--red);}
.custom-folder-children{padding-left:11px;border-left:1px solid var(--line);margin-left:6px;}
.custom-tree-level .gate-btn.custom-gate-btn{margin-left:0;}

/* Gate symbols in the Gates palette (see index.html) - small static SVGs mirroring the
   same shapes render.js draws on the canvas (bodyPath/xorExtraPath in state.js), just
   simplified to flat markup since these never change at runtime. */
.gate-btn-label{display:flex;align-items:center;gap:9px;}
.gate-icon{width:30px;height:19px;flex:none;overflow:visible;}
.gate-icon-shape{fill:var(--comp-fill);stroke:var(--comp-outline);stroke-width:1.6;}
.gate-icon-extra{fill:none;stroke:var(--line-strong);stroke-width:1.6;}
.gate-icon-bubble{fill:var(--comp-fill);stroke:var(--comp-outline);stroke-width:1.6;}

/* I/O palette symbols (see the I/O and Numeric I/O sections in index.html) - larger
   than the plain logic-gate icons above (38x24 vs 30x19), since these components read
   less obviously from a name alone than a gate's own shape does. Mirrors each
   component's actual on-canvas look (switch knob, button cap, LED, clock waveform,
   etc.) rather than being arbitrary glyphs. */
.io-icon{width:38px;height:24px;flex:none;overflow:visible;}
.io-icon-shape{fill:var(--comp-fill);stroke:var(--comp-outline);stroke-width:1.6;}
.io-icon-knob{fill:var(--knob-off);}
.io-icon-knob.hi{fill:var(--circuit-hi-color);}
.io-icon-text{fill:var(--text);font-family:var(--mono);font-size:11px;font-weight:700;
  text-anchor:middle;dominant-baseline:middle;}
.io-icon-extra{fill:none;stroke:var(--line-strong);stroke-width:1.6;stroke-linecap:round;}
.io-icon-wave{fill:none;stroke:var(--comp-outline);stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round;}
/* Instructions card: raised panel with a shadow along its lower edge so it
   reads as "popped up" off the palette surface behind it. Uses a grid-rows
   collapse (not max-height) so full content is always shown when expanded,
   never clipped, regardless of how tall the text ends up being. */
.hint{
  font-family:var(--head);font-size:13px;color:var(--text-dim);line-height:1.75;
  margin-top:20px;background:var(--panel2);border:1px solid var(--line-strong);border-radius:8px;
  box-shadow:0 14px 20px -8px rgba(0,0,0,.6), 0 4px 6px -2px rgba(0,0,0,.35);
  display:grid;grid-template-rows:1fr;
  transition:grid-template-rows .28s ease, box-shadow .28s ease, border-color .28s ease, margin .28s ease;
}
.hint-body{overflow:hidden;min-height:0;padding:14px 14px 16px;transition:padding .28s ease;}
.hint.collapsed{grid-template-rows:0fr;box-shadow:none;border-color:transparent;margin-top:12px;}
.hint.collapsed .hint-body{padding-top:0;padding-bottom:0;}
.hint b{color:var(--amber);font-weight:600;}

/* Shortcuts/options sub-sections inside the Instructions card (see index.html) - a
   small heading plus a compact list, visually nested under the free-text instructions
   above them via the top border + indent. */
.hint-subsection{margin-top:12px;padding-top:10px;border-top:1px dashed var(--line-strong);}
.hint-subsection-title{color:var(--amber);font-weight:600;font-size:12px;text-transform:uppercase;
  letter-spacing:.04em;margin-bottom:6px;}
.hint-shortcut-list{margin:0;padding-left:16px;}
.hint-shortcut-list li{margin-bottom:4px;}
.hint-shortcut-list li:last-child{margin-bottom:0;}

/* Fixed footer: sits above everything else in the palette, always visible.
   Stacks the collapse toggle directly above the developer credit. Tracks the palette's
   current (drag-resizable) width via --palette-w rather than the old fixed 210px, so it
   never falls out of alignment with the palette itself. */
.palette-footer{position:fixed;left:0;bottom:0;width:var(--palette-w,210px);box-sizing:border-box;
  background:var(--panel);border-top:1px solid var(--line);
  display:flex;flex-direction:column;z-index:40;}

/* Reuses the exact .palette-header look (see the I/O / Gates section headers
   above) so the instructions toggle matches them instead of looking like a
   separate control. */
.hint-toggle-btn{padding:10px 12px;}
.hint-toggle-btn .chevron{font-size:11px;color:var(--text-dim);transition:transform .15s;}
.hint-toggle-btn[aria-expanded="false"] .chevron{transform:rotate(-90deg);}
/* Matches .hint-toggle-btn's own left padding (12px, vs .palette-header's base 2px)
   so "Replay tutorial" lines up with "Instructions" directly above it instead of
   sitting noticeably closer to the left edge. */
#replayTutorialBtn{padding:10px 12px;}

/* Developer credit: a single amber accent line on top, nothing else tinted,
   and the name picked out in the same green used for "solved" / active
   states elsewhere in the app. */
.dev-credit{
  font-family:var(--head);font-size:11.5px;color:var(--text-dim);line-height:1.7;
  box-sizing:border-box;padding:13px 12px;background:var(--panel2);
  border-top:2px solid #8fa3b0;
}
.dev-credit b{color:var(--amber);font-weight:700;}
.dev-credit a{color:var(--amber);text-decoration:none;}
.dev-credit a:hover{text-decoration:underline;}

.canvas-outer{position:relative;overflow:hidden;}
.canvas-wrap{position:absolute;inset:0;overflow:auto;background:
   linear-gradient(var(--line) 1px, transparent 1px) 0 0/28px 28px,
   linear-gradient(90deg, var(--line) 1px, transparent 1px) 0 0/28px 28px,
   var(--canvas-bg);
}
svg{display:block;}
.marquee-rect{fill:rgba(255,180,84,.12);stroke:var(--amber);stroke-width:1.5;stroke-dasharray:4,3;}
.switch-body.selected, .led.selected{stroke:var(--amber) !important;}

.minimap{position:absolute;bottom:16px;right:16px;width:230px;z-index:20;
  background:var(--panel2);border:1px solid var(--line-strong);border-radius:6px;
  overflow:hidden;box-shadow:0 4px 18px rgba(0,0,0,.45);}
.minimap.collapsed{width:auto;}
.minimap-header{display:flex;align-items:center;justify-content:space-between;gap:10px;
  padding:6px 10px;background:var(--panel);border-bottom:1px solid var(--line-strong);
  font-family:var(--head);font-size:11px;font-weight:600;color:var(--text-dim);
  text-transform:uppercase;letter-spacing:.06em;}
.minimap.collapsed .minimap-header{border-bottom:none;}
.minimap-header button{background:none;border:1px solid var(--line-strong);color:var(--text);
  width:20px;height:20px;border-radius:3px;cursor:pointer;font-size:12px;line-height:1;padding:0;}
.minimap-header button:hover{border-color:var(--amber);color:var(--amber);}
.minimap svg{display:block;width:230px;height:136px;background:var(--bg);cursor:crosshair;}
.minimap.collapsed svg{display:none;}
.minimap-viewport{fill:rgba(255,180,84,.1);stroke:var(--amber);stroke-width:8;}

/* Zoom controls: its own independent floating panel, bottom-left of the canvas -
   deliberately separate from the minimap (which lives bottom-right) so collapsing
   or moving one never affects the other. Same visual language as the minimap panel. */
.zoom-controls{position:absolute;bottom:16px;left:16px;z-index:20;
  display:flex;align-items:center;gap:6px;
  background:var(--panel2);border:1px solid var(--line-strong);border-radius:6px;
  padding:7px 9px;box-shadow:0 4px 18px rgba(0,0,0,.45);}
.zoom-controls button{background:none;border:1px solid var(--line-strong);color:var(--text);
  width:22px;height:22px;border-radius:3px;cursor:pointer;font-size:13px;line-height:1;
  padding:0;font-family:var(--head);}
.zoom-controls button:hover{border-color:var(--amber);color:var(--amber);}
.zoom-controls button:disabled{opacity:.4;cursor:default;border-color:var(--line-strong);color:var(--text);}
#zoomResetBtn{width:auto;padding:0 8px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;}
#zoomPct{min-width:38px;text-align:center;font-family:var(--mono);font-size:12px;color:var(--text-dim);
  user-select:none;}
.gate-body{fill:var(--comp-fill);stroke:var(--comp-outline);stroke-width:2.6;cursor:grab;}
.clock-symbol{fill:none;stroke:var(--comp-outline);stroke-width:2.2;}
.gate-body.selected{stroke:var(--amber);}
.gate-body.num-io{fill:var(--comp-fill);}
.gate-body.splitter-body{fill:var(--comp-fill);stroke:var(--comp-outline);}
/* .gate-body.splitter-body (above) and .gate-body.selected (above that) are both
   2-class selectors of equal specificity, so with both classes present on a selected
   splitter/combiner, source order alone decided the tie - and since splitter-body comes
   second, it was silently winning and the amber selection outline never showed. Same
   bug, same fix as .custom-body below: a 3-class selector outranks both 2-class ones. */
.gate-body.splitter-body.selected{stroke:var(--amber) !important;}
/* Saved components (see CUSTOM in render.js) - same bright --sky outline as
   splitter/combiner, plus a slightly heavier stroke, so a component's box reads as
   distinct from an ordinary gate even from across the canvas. .selected (amber) still
   wins - see the ordering below, which matches .gate-body.selected's specificity. */
.gate-body.custom-body{stroke:var(--sky);stroke-width:3.2;}
.gate-body.custom-body.selected{stroke:var(--amber);}
.splitter-fan{fill:none;stroke:var(--line-strong);stroke-width:1.4;pointer-events:none;}
.num-value-chip{fill:var(--bg);stroke:var(--line-strong);stroke-width:1.8;}
.num-value-chip.editable{cursor:text;}
.num-format-badge{fill:var(--panel2);stroke:var(--line-strong);stroke-width:1.8;}
[data-role="format-badge"]:hover .num-format-badge{stroke:var(--amber);}
.num-format-badge-text{fill:var(--amber);font-family:var(--mono);font-size:10px;font-weight:700;text-anchor:middle;pointer-events:none;}

/* Inline value editor: an HTML <input> laid exactly over the SVG value chip (see
   numChipRect/startInlineNumEdit) so numbers are edited in place, no popup dialog. */
.inline-num-edit{
  position:fixed;box-sizing:border-box;z-index:500;
  background:var(--bg);color:var(--heading);border:1.5px solid var(--amber);border-radius:4px;
  font-family:var(--mono);font-size:13px;font-weight:700;padding:0 0 0 7px;margin:0;
  outline:none;caret-color:var(--amber);
}
.clock-wave{fill:none;stroke:var(--off);stroke-width:2;}
.clock-wave.hi{stroke:var(--circuit-hi-color);}
.switch-body.clock-body{fill:var(--comp-fill);stroke:var(--line-strong);stroke-width:2.6;cursor:default;}
.switch-body.clock-body.hi{stroke:var(--circuit-hi-color);}
/* Constant 0/1 source - same box as an input switch, but fixed: no knob, no pointer
   cursor, since clicking it does nothing. */
.switch-body.const-body{cursor:default;}
.switch-body.const-body.hi{stroke:var(--circuit-hi-color);}
.const-label{font-size:16px;font-weight:700;font-family:var(--mono);text-anchor:middle;pointer-events:none;fill:var(--text-dim);}
.const-label.hi{fill:var(--circuit-hi-color);}
/* XOR/XNOR's extra input curve is part of the gate's own outline, not a separate
   internal detail, so it now matches .gate-body's outline colour (--comp-outline)
   instead of the muted --line-strong it used to sit in - previously it read as a
   different, uncoloured element even when the rest of the body's stroke changed. */
.gate-extra{fill:none;stroke:var(--comp-outline);stroke-width:2.6;}
.bubble{fill:var(--comp-fill);stroke:var(--comp-outline);stroke-width:2.6;}
.gate-label{fill:var(--comp-outline);font-size:11px;font-family:var(--mono);text-anchor:middle;pointer-events:none;}
.io-label{fill:var(--heading);font-size:12px;font-family:var(--mono);text-anchor:middle;font-weight:700;pointer-events:none;}
/* A gate/component's own NAME label specifically (as opposed to a value badge, bit-width
   caption or per-pin port label, which reuse .gate-label/.io-label for layout but are
   built directly rather than through labelText() - see render.js) - set in the same bold
   UI face as the rest of the app instead of the canvas's technical monospace, and picked
   out in the accent colour on hover so it's obvious which gate's name you're pointing at. */
.comp-name-label{font-family:var(--head);font-weight:700;}
g[data-gate]:hover .comp-name-label{fill:var(--amber);}
.pin{fill:none;stroke:var(--off);stroke-width:2.6;cursor:crosshair;}
.pin.hi{stroke:var(--circuit-hi-color);}
.pin.lo{stroke:var(--off);}
.pin.undef{stroke:var(--red);}
.pin.neutral{stroke:var(--line-strong);}
.pin-dot{stroke:none;cursor:crosshair;}
.pin-dot.hi{fill:var(--circuit-hi-color);}
.pin-dot.lo{fill:var(--off);}
.pin-dot.undef{fill:var(--red);}
.pin-dot.neutral{fill:var(--line-strong);}
.lead{stroke:var(--off);stroke-width:2.5;}
.lead.hi{stroke:var(--circuit-hi-color);}
.wire{fill:none;stroke:var(--off);stroke-width:2.5;cursor:pointer;}
.wire.hi{stroke:var(--circuit-hi-color);}
.wire.undef{stroke:var(--red);stroke-dasharray:5,4;}
/* A wire with a chosen colour (see the swatches in showWireCtxMenu, interactions.js)
   stays grey by default otherwise - --wire-color is only set inline when one's actually
   picked. 0-logic renders as a faint, mostly-transparent tint of that colour (opacity
   controlled globally by --wire-off-opacity - see the Settings > "Wire & LED colours" >
   Transparency slider, applied in components.js), 1-logic as the full, bright version -
   flat colour only, no glow (glow is handled separately below, and only ever turns on
   while the wire is actually carrying a 1 - see the val===true check in render.js).
   !important plus :not(.selected) is used here (rather than relying on selector
   specificity/source order against .wire.hi, which has the exact same specificity and
   would otherwise depend on which rule happens to come later) so the chosen colour
   reliably wins at every logic state, instantly, the moment it's picked - not only once
   the wire happens to carry a 1. .selected stays excluded so the amber selection
   highlight still always wins over any wire colour. */
.wire.custom-color:not(.selected){stroke:var(--wire-color) !important;stroke-opacity:var(--wire-off-opacity, .3);}
.wire.custom-color.hi:not(.selected){stroke-opacity:1 !important;}
.wire.custom-color.undef:not(.selected){stroke:var(--red) !important;stroke-opacity:1;}
/* Glow slider (0-100%, default 0) - a separate control from colour, see the "Glow" row
   in showWireCtxMenu. 0% is completely flat, matching .wire's plain default with no
   filter at all; only .glow-on (added in render.js, but only while the wire is actually
   carrying a 1 - see the val===true check there) turns a drop-shadow on, scaled by
   --wire-glow via calc() so the halo genuinely widens as it's raised, not just
   brightens. Also scaled by --wire-off-opacity (the global Settings > "Wire & LED
   colours" > Transparency slider) - that slider used to only affect the 0-logic tint
   above and leave a lit (1-logic) wire's glow completely alone, so turning
   transparency down didn't visually dim a glowing wire at all; multiplying both
   factors together means glow now shrinks right along with transparency, and at the
   slider's own maximum (100%) the glow looks exactly as it always did (the multiplier
   is 1 there), so nothing changes for anyone who leaves it at full. --wire-glow-color
   is computed in render.js from whatever the wire is actually showing right now (its
   chosen colour, or green by default), so the glow always matches. Declared after
   .custom-color above and !important so it wins over that rule's filter:none
   precedent for a wire that has both a colour and glow. */
.wire.glow-on:not(.selected){
  filter:
    drop-shadow(0 0 calc(var(--wire-glow,0) * var(--wire-off-opacity,.3) * 3px) var(--wire-glow-color))
    drop-shadow(0 0 calc(var(--wire-glow,0) * var(--wire-off-opacity,.3) * 8px) var(--wire-glow-color)) !important;
}
.wire.selected{stroke:var(--amber) !important;stroke-width:3.5;filter:none !important;}
.draft-wire{fill:none;stroke:var(--amber);stroke-width:2;stroke-dasharray:4,4;pointer-events:none;}
/* Shift+click-started wire that's waiting for a second click to land on its other end
   (see wireDraft.pinned in interactions.js) - a faster dash + a pulsing dot right at
   its start, so it visibly reads as "still open" rather than "mid-drag". */
.draft-wire.pinned{stroke-dasharray:3,3;animation:draft-wire-pinned-dash 0.6s linear infinite;}
@keyframes draft-wire-pinned-dash{to{stroke-dashoffset:-12;}}
.draft-wire-pin-dot{fill:none;stroke:var(--amber);stroke-width:2;pointer-events:none;
  animation:draft-wire-pin-pulse 1.1s ease-out infinite;}
@keyframes draft-wire-pin-pulse{
  0%{r:5;opacity:1;}
  100%{r:12;opacity:0;}
}
.switch-body{fill:var(--comp-fill);stroke:var(--line-strong);stroke-width:2.6;cursor:pointer;}
.switch-body.hi{stroke:var(--circuit-hi-color);}
.knob{fill:var(--knob-off);}
.knob.hi{fill:var(--circuit-hi-color);}
/* Push button (see BUTTON in render.js) - same body as an INPUT switch, but a single
   centered round cap instead of a sliding knob, so it doesn't visually suggest an
   on/off position the way the switch's left/right knob does. */
.switch-body.button-body{cursor:pointer;}
.button-cap{fill:var(--knob-off);stroke:var(--line-strong);stroke-width:2;}
.button-cap.hi{fill:var(--circuit-hi-color);stroke:var(--circuit-hi-color);}
.led{stroke-width:2.6;}
/* Default/green LED keeps its original, understated look, at a fill opacity controlled
   globally by --led-base-opacity (default 50% - see Settings > "Wire & LED colours" >
   Transparency slider, applied in components.js; this used to be a solid --green-dim
   fill, then a hardcoded 50%, and is now a user-adjustable dilution of --led-default-
   color, which is what "transparency" means here - see the --circuit-hi-color comment
   in :root for why that's a separate variable from --green rather than reusing it).
   Picking a colour from the swatches (.custom-color, from render.js) just swaps which
   colour that same base-opacity look uses - it's the separate Glow slider (0-100%,
   default 0) that actually adds any halo. .glow-on is only added once that slider is
   above 0, so an untouched LED, of any colour, stays at exactly this same flat
   base-opacity look - matching "0% glow" literally. From there up to 100%, the center
   keeps climbing past that baseline (fill-opacity) while the halo (drop-shadow blur
   radius) widens alongside it via calc(), so it reads as one continuous scale rather
   than a jump. */
.led.hi{fill:var(--circuit-hi-color);fill-opacity:var(--led-base-opacity, .5);stroke:var(--circuit-hi-color);}
.led.hi.custom-color{fill:var(--led-color);fill-opacity:var(--led-base-opacity, .5);stroke:var(--led-color);}
.led.hi.glow-on{
  fill:var(--led-color, var(--circuit-hi-color));
  fill-opacity:calc(var(--led-base-opacity, .5) + var(--led-glow,0) * 0.4);
  stroke:var(--led-color, var(--circuit-hi-color));
  filter:
    drop-shadow(0 0 calc(var(--led-glow,0) * 4px) var(--led-color, var(--circuit-hi-color)))
    drop-shadow(0 0 calc(var(--led-glow,0) * 11px) var(--led-color, var(--circuit-hi-color)));
}
.led.lo{fill:var(--comp-fill);stroke:var(--line-strong);}
.led.undef{fill:var(--red-dim);stroke:var(--red);}
/* Soft white highlight layered on top of a lit LED (see the OUTPUT branch, render.js) -
   a plain flat fill reads as a coloured disc, not something actually glowing; fading to
   fully transparent by its own edge (see #ledHighlightGradient, also render.js) means
   it brightens the centre of whatever colour the LED actually is without ever look like
   a separate white blob sitting on top of it. pointer-events:none so it never steals a
   click meant for the LED body underneath. */
.led-highlight{pointer-events:none;}
/* Light mode's canvas is bright, so a lit LED doesn't get the same "glow against
   darkness" contrast it does in dark mode by default - a soft dark shadow underneath
   gives it something to visually pop off of instead, the way a light bulb still reads
   as bright even in a well-lit room because of the shadow it casts. Layered before
   (i.e. rendered underneath, since SVG stacks filter functions in order) the coloured
   glow itself, so glow-on LEDs get both at once rather than one replacing the other. */
html.theme-light .led.hi{filter:drop-shadow(0 2px 5px rgba(15,23,20,.4));}
html.theme-light .led.hi.glow-on{
  filter:
    drop-shadow(0 2px 5px rgba(15,23,20,.4))
    drop-shadow(0 0 calc(var(--led-glow,0) * 4px) var(--led-color, var(--circuit-hi-color)))
    drop-shadow(0 0 calc(var(--led-glow,0) * 11px) var(--led-color, var(--circuit-hi-color)));
}

/* .side itself no longer scrolls - that's moved onto .side-content below, so the
   resize handle (a direct child of .side, sibling to .side-content) spans the panel's
   full height regardless of how far .side-content has been scrolled. */
/* overflow:hidden here is load-bearing the same way as .palette's above - it stops this
   grid item's automatic minimum size from being .side-content's (potentially tall)
   min-content size, which would otherwise stretch the row - and the fixed-height .app
   layout with it - instead of .side-content scrolling internally. */
.side{background:var(--panel);border-left:1px solid var(--line);position:relative;overflow:hidden;}
/* No top padding here on purpose - .side-content is now also the scroll container
   (see the .side comment above), and a sticky child can't reliably be pulled up past
   its own scrolling parent's top padding with a negative margin the way it could when
   the padding and the scrolling lived on two different elements. Simplest fix is to
   just not put any padding above the sticky tabs bar in the first place, so it renders
   flush under the header with no gap and .side-sticky-top needs no top offset at all. */
.side-content{padding:0 16px 16px;height:100%;box-sizing:border-box;overflow-y:auto;}

/* Sticky header: keeps the Truth Table / Problems tabs (and, inside a problem,
   the "All problems" back button) pinned to the top of the sidebar while the
   rest of the panel scrolls underneath. Negative left/right margins cancel out
   side-content's left/right padding so this bar spans the panel's full width
   (border-bottom included) instead of stopping short at the padding edge. The
   top/right/left padding here is this same element's OWN padding rather than a
   top margin pulled from .side-content - a margin here would face the identical
   sticky/scroll-container quirk that broke the old version of this rule, where
   .side-content was both the padded parent and the scrolling ancestor at once;
   padding on the sticky box itself sidesteps that entirely and renders identically
   whether the panel is scrolled or not. */
.side-sticky-top{position:sticky;top:0;z-index:5;background:var(--panel);
  margin:0 -16px 14px;padding:10px 10px 5px;border-bottom:1px solid var(--line);}
.side-sticky-top .tabs{margin-bottom:0;}
#backToListWrap:empty{display:none;}
#backToListWrap{margin-top:3px;}
#backToListWrap button{margin-bottom:0;}
.tabs{display:flex;gap:6px;margin-bottom:16px;align-items:stretch;}
.tab-btn{flex:1;display:flex;align-items:center;justify-content:center;gap:6px;
  background:var(--panel2);border:1px solid var(--line-strong);color:var(--text-dim);
  font-family:var(--head);font-weight:600;font-size:12.5px;padding:9px;border-radius:5px;cursor:pointer;}
.tab-btn.active{border-color:var(--amber);color:var(--amber);}
.tab-btn:hover{color:var(--text-hover);}
/* Same line-drawn glyphs as .side-collapsed-icon (style.css, near .side-collapsed-icons)
   - showing them here too, right next to the text, is what makes the icon-only buttons
   in the collapsed panel actually legible later: by the time someone collapses the
   panel they've already seen each icon paired with its label here. */
.tab-btn-icon{width:14px;height:14px;flex:none;fill:none;stroke:currentColor;
  stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round;overflow:visible;}
#sideToggle, #sideExpand{
  display:flex;align-items:center;justify-content:center;
  width:32px;padding:0;line-height:1;
  background:var(--panel2);border:1px solid var(--line-strong);color:var(--text);
  border-radius:5px;cursor:pointer;font-size:15px;
}
/* #sideToggle sits inside .tabs (align-items:stretch) next to the Truth Table /
   Problems buttons, so it deliberately has no height of its own here - it stretches
   to match .tab-btn's actual rendered height instead of a hardcoded guess that would
   drift out of sync the moment .tab-btn's own font-size/padding changes. #sideExpand,
   by contrast, sits alone in the collapsed 44px column with no taller siblings to
   stretch to, so it keeps an explicit height. */
#sideToggle{flex:0 0 auto;}
#sideExpand{height:32px;display:none;}
#sideToggle:hover, #sideExpand:hover{border-color:var(--amber);color:var(--amber);}
/* Collapsing the side panel only ever affects its own column - the palette keeps
   whatever width it's currently at (--palette-w), same reasoning as everywhere else
   that used to hardcode 210px. */
.app.side-collapsed{grid-template-columns:var(--palette-w,210px) 1fr 44px;}
.app.side-collapsed .side-content{display:none;}
.app.side-collapsed .side-resize-handle{display:none;}
.app.side-collapsed #sideExpand{display:flex;position:sticky;top:12px;margin:12px auto;}
/* Truth Table / Problems shortcuts for the collapsed column - hidden whenever the
   panel is actually open (.side-content covers the same job there via the real
   tabs), shown stacked under #sideExpand once collapsed. Sticky (not just a static
   margin-top) so they stay reachable near the top of the column - matching
   #sideExpand's own positioning - if this column ever grows scrollable content of
   its own in the future. */
.side-collapsed-icons{display:none;}
.app.side-collapsed .side-collapsed-icons{display:flex;flex-direction:column;gap:8px;
  align-items:center;position:sticky;top:56px;margin:0 auto;}
/* IDs, not just .side-collapsed-icon-btn (kept in the markup only as a JS hook for
   interactions.js), because .side button (below) sets width:100%, padding:10px,
   margin-bottom:9px and more with class+element specificity that beats a single class
   - the exact same reason #sideToggle/#sideExpand are IDs. Without this these two
   buttons silently stretch to the column's full width instead of staying 32px square. */
#sideCollapsedTTBtn, #sideCollapsedProblemsBtn{display:flex;align-items:center;justify-content:center;
  width:32px;height:32px;padding:0;margin:0;background:var(--panel2);border:1px solid var(--line-strong);
  color:var(--text-dim);border-radius:5px;cursor:pointer;}
#sideCollapsedTTBtn:hover, #sideCollapsedProblemsBtn:hover{border-color:var(--amber);color:var(--amber);}
.side-collapsed-icon{width:18px;height:18px;fill:none;stroke:currentColor;
  stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round;overflow:visible;}
.side h2{font-family:var(--head);font-size:15px;font-weight:600;letter-spacing:.03em;color:var(--heading);text-transform:uppercase;margin:0 0 6px;}
.side .desc{font-family:var(--head);font-size:13px;color:var(--text-dimmer);margin-bottom:14px;line-height:1.6;}
.side .desc p{margin:0 0 10px;}
.side .desc p:last-child{margin-bottom:0;}
.side button{width:100%;background:var(--panel2);border:1px solid var(--line-strong);color:var(--text);
  font-family:var(--head);font-weight:500;font-size:13px;padding:10px;border-radius:5px;cursor:pointer;margin-bottom:9px;}
.side button.primary{border-color:var(--amber);color:var(--amber);}
.side button:hover{color:var(--text-hover);border-color:var(--amber);}

/* Problem detail: clearly separated sections (heading + divider) instead of one
   long unbroken stack, so Reference / Build & Test / Resources read as distinct
   blocks at a glance. */
.problem-header{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:10px;}
.problem-header h2{margin:0;}
/* Inputs/Outputs panel - sits right after the description, ahead of the tip block, so
   the circuit's required pins are the first concrete thing read after the prose. No
   border/box around the panel or its chips (deliberately not boxed) - highlighted with
   colour alone: a tinted background panel plus coloured monospace chips, amber for
   inputs and sky for outputs, matching the same colour language the timing-diagram
   legend already uses for in/out signals. */
.problem-io-panel{display:flex;flex-direction:column;gap:6px;background:var(--panel2);
  border-radius:6px;padding:10px 12px;margin-bottom:14px;}
.io-row{display:flex;align-items:baseline;flex-wrap:wrap;gap:8px 6px;}
.io-row-label{flex:0 0 auto;font-family:var(--head);font-size:10.5px;font-weight:700;
  color:var(--text-dim);text-transform:uppercase;letter-spacing:.07em;min-width:52px;}
.io-chips{display:flex;flex-wrap:wrap;gap:5px;}
.io-chip{font-family:var(--mono);font-size:12px;font-weight:700;padding:2px 8px;
  border-radius:9px;white-space:nowrap;}
.io-chip-in{color:var(--amber);background:var(--amber-dim);}
.io-chip-out{color:var(--sky);background:var(--sky-dim);}
/* Prerequisites chip row - only rendered when a problem's `prereqs` array is set (see
   prereqBlockHtml() in problems.js). Same no-border, colour-only philosophy as the I/O
   chips above, but these are clickable navigation buttons (jump to that problem), not
   static data, so they get a hover state and a colour swap once solved. Scoped through
   the parent class so `width:auto` beats the blanket .side button{width:100%} rule -
   same trick used for the I/O/tip/filter buttons elsewhere in this file. */
.prereq-block{margin-bottom:14px;}
.prereq-label{display:block;font-family:var(--head);font-size:10.5px;font-weight:700;
  color:var(--text-dim);text-transform:uppercase;letter-spacing:.07em;margin-bottom:6px;}
.prereq-chips{display:flex;flex-wrap:wrap;gap:6px;}
.prereq-chips .prereq-chip{width:auto;flex:0 0 auto;font-family:var(--head);font-size:12px;
  font-weight:600;padding:4px 10px;border-radius:12px;cursor:pointer;background:var(--panel2);
  color:var(--sky);transition:background .15s,color .15s;}
.prereq-chips .prereq-chip:hover{background:var(--sky-dim);}
.prereq-chips .prereq-chip.solved{color:var(--green);background:var(--green-dim);}
.problem-section{margin-top:24px;padding-top:18px;border-top:1px solid var(--line-strong);}
.problem-section-title{font-family:var(--head);font-size:15px;font-weight:600;letter-spacing:.03em;
  text-transform:uppercase;color:var(--heading);margin:0 0 10px;}

table.tt{width:100%;border-collapse:collapse;font-size:11.5px;margin-top:10px;}
table.tt th, table.tt td{border:1px solid var(--line-strong);padding:5px 3px;text-align:center;}
table.tt th{background:var(--panel2);color:var(--text-dim);font-weight:500;font-size:10.5px;font-family:var(--head);}
table.tt td.in{color:var(--text-dim);}
table.tt td input{width:24px;background:var(--panel2);border:1px solid var(--line-strong);color:var(--amber);
  text-align:center;font-family:var(--mono);font-size:11.5px;border-radius:3px;padding:3px 0;}
table.tt td.act{font-weight:700;}
table.tt td.match{background:var(--tt-match-bg);color:var(--green);}
table.tt td.mismatch{background:var(--tt-mismatch-bg);color:var(--red);}
/* Karnaugh map grid (see kmapHtml(), problems.js) - deliberately an actual bordered
   grid, unlike the borderless chip language used for I/O pins/prereqs/tips elsewhere
   in the problem detail view: a K-map's whole pedagogical point is a literal grid
   where adjacent cells (sharing an edge, including wrapping around top/bottom and
   left/right) differ by exactly one input bit, so borders that make "adjacent" a
   visually obvious property are doing real work here, not just decoration. Column/row
   headers are the Gray-code bit pairs themselves (kept in monospace, like the I/O
   chips) so the adjacency is traceable back to the actual input values, not just
   colour. */
.kmap-subheading{font-family:var(--head);font-size:11px;font-weight:700;color:var(--text-dim);
  text-transform:uppercase;letter-spacing:.06em;margin:14px 0 6px;}
.kmap-subheading:first-child{margin-top:0;}
.kmap-wrap{display:flex;justify-content:center;margin-bottom:6px;}
table.kmap-table{border-collapse:collapse;font-family:var(--mono);font-size:12.5px;}
table.kmap-table th, table.kmap-table td{border:1px solid var(--line-strong);width:38px;height:32px;
  text-align:center;padding:0;}
table.kmap-table th.kmap-corner{background:var(--panel2);color:var(--text-dimmer);font-size:10px;
  font-family:var(--head);position:relative;}
.kmap-corner-slash{position:absolute;left:4px;top:2px;color:var(--line-strong);font-size:16px;line-height:1;}
table.kmap-table th.kmap-colhead, table.kmap-table th.kmap-rowhead{background:var(--panel2);
  color:var(--text-dim);font-weight:600;}
td.kmap-cell{font-weight:700;}
td.kmap-one{background:var(--green-dim);color:var(--green);}
td.kmap-zero{color:var(--text-dimmer);}
td.kmap-dc{background:var(--amber-dim);color:var(--amber);}
#ttSummary{font-family:var(--head);font-size:13px;margin-top:10px;margin-bottom:10px;padding:9px;border-radius:5px;background:var(--panel2);border:1px solid var(--line-strong);}
#ttSummary.pass{color:var(--green);border-color:var(--green-dim);}
#ttSummary.fail{color:var(--red);border-color:var(--red-dim);}
#ttWarn{font-family:var(--head);font-size:12.5px;color:var(--amber);margin-top:6px;}

/* ===================================================================
   Timing diagram (Truth table tab)
   =================================================================== */
.timing-empty{font-family:var(--head);font-size:12.5px;color:var(--text-dim);
  background:var(--panel2);border:1px dashed var(--line-strong);border-radius:6px;
  padding:16px;text-align:center;margin-top:4px;}

.timing-legend{display:flex;flex-wrap:wrap;gap:12px 16px;font-family:var(--head);
  font-size:11px;color:var(--text-dim);margin:6px 0 8px;}
.legend-item{display:inline-flex;align-items:center;gap:5px;}
.legend-swatch{display:inline-block;width:14px;height:3px;border-radius:2px;}
.legend-in{background:var(--amber);}
.legend-ref{background:var(--text-dim);}
.legend-out{background:var(--sky);}
.legend-diff-lo{background:var(--green);}
.legend-diff-hi{background:var(--red);}
.legend-undef{background:none;border-top:2px dashed var(--text-dim);height:0;}

.timing-diagram-flex{display:flex;border:1px solid var(--line-strong);border-radius:6px;
  overflow:hidden;background:var(--panel2);}
.timing-labels{flex:0 0 auto;width:124px;border-right:1px solid var(--line-strong);
  background:var(--panel2);}
/* Classic single-line-truncation pattern, deliberately NOT flexbox this time (an
   earlier flex-based attempt at this - flex-grow/shrink plus a flex child holding the
   text - turned out fragile and left the text invisible at this component's smaller,
   inline size, even though the exact same CSS looked fine at the popup's larger size).
   The hide button is pulled OUT of the row's normal flow entirely (position:absolute),
   so the row's own text content is the ONLY thing overflow/ellipsis has to reason
   about - vertical centering comes from line-height matching the row's own fixed
   height (set inline per-row, see buildTimingDiagramMarkup), not flex's align-items. */
.timing-label{position:relative;font-family:var(--head);font-size:11px;
  padding:0 22px 0 8px;box-sizing:border-box;
  border-bottom:1px solid var(--line);color:var(--text-dim);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  cursor:grab;}
.timing-label:active{cursor:grabbing;}
.timing-label-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.timing-label.timing-drop-hover{background:rgba(90,180,255,.14);outline:1.5px dashed var(--sky);outline-offset:-2px;}
.timing-hide-btn{position:absolute;right:3px;top:50%;transform:translateY(-50%);
  background:none;border:none;color:var(--text-dimmer);cursor:pointer;
  font-size:12px;line-height:1;padding:2px 3px;border-radius:3px;opacity:0;transition:opacity .12s;}
/* Both this and .timing-show-btn below also render inside the side panel (the sidebar
   Truth Table tab, and the per-problem timing view in Problems tab) as well as the
   non-side timing popup, so they inherit the blanket .side button{width:100%} rule
   there - same bug class as the difficulty filter chips and the splitter/combiner
   selection outline. Without an override, .timing-hide-btn (position:absolute,
   right:3px, no left) stretches to the full row width and visually overlaps the label
   text/drag handle instead of sitting tucked in the corner, and .timing-show-btn loses
   its flex-wrap chip layout and stacks one per line instead. Scoping through the
   parent class (two classes beats .side button's class+element on specificity) fixes
   both without needing an ID, since this markup isn't only ever inside .side. */
.timing-label .timing-hide-btn{width:auto;}
.timing-label:hover .timing-hide-btn{opacity:1;}
.timing-hide-btn:hover{color:var(--red);background:rgba(255,90,90,.14);}
.timing-hidden-row{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-bottom:8px;
  font-family:var(--head);font-size:11px;}
.timing-hidden-label{color:var(--text-dim);}
.timing-show-btn{background:var(--panel2);border:1px dashed var(--line-strong);color:var(--text-dim);
  font-family:var(--head);font-size:10.5px;padding:3px 7px;border-radius:10px;cursor:pointer;}
.timing-hidden-row .timing-show-btn{width:auto;flex:0 0 auto;}
.timing-show-btn:hover{border-color:var(--sky);color:var(--sky);}
.timing-label-in{color:var(--amber);}
.timing-label-ref{color:var(--text-dim);}
.timing-label-out{color:var(--sky);}
.timing-label-diff{color:var(--text);}
.timing-scroll{flex:1 1 auto;overflow-x:auto;overflow-y:hidden;}
.timing-svg{display:block;background:var(--panel);}
.timing-row-bg{fill:rgba(255,255,255,.02);}
/* The subtle alternating-row tint above is a white wash meant to lift a row a hair
   above a near-black canvas - on the light canvas it's mixing white into a
   background that's already close to white, so it's a total no-op there. Same
   striping intent, opposite-colour wash. */
html.theme-light .timing-row-bg{fill:rgba(0,0,0,.03);}
.timing-grid{stroke:var(--line);stroke-width:1;}
.timing-grid-major{stroke:var(--line-strong);stroke-width:1;}
.timing-axis-label{font-family:var(--mono, var(--head));font-size:9.5px;fill:var(--text-dim);}
.timing-line{fill:none;stroke-width:2;stroke-linejoin:round;}
.timing-in{stroke:var(--amber);}
.timing-ref{stroke:var(--text-dim);}
.timing-out{stroke:var(--sky);}
.timing-diff-lo{stroke:var(--green);}
.timing-diff-hi{stroke:var(--red);}
.timing-line-undef{stroke:var(--text-dim);stroke-width:1.5;stroke-dasharray:3,3;opacity:.55;}

#timingExpandBtn{margin-top:10px;}

.problem-timing-section{margin-top:16px;padding-top:14px;border-top:1px solid var(--line);}
.problem-timing-title{font-family:var(--head);font-size:12px;font-weight:600;letter-spacing:.06em;
  text-transform:uppercase;color:var(--text-dim);margin:0 0 10px;}
#problemTimingExpandBtn{margin-top:10px;}

/* ===================================================================
   First-run guided tour (js/onboarding.js)
   =================================================================== */
/* #onbOverlay itself is just a non-blocking positioning root (no size, no background).
   The dimming/blocking is four *real* panels (top/bottom/left/right) framing a
   rectangular hole around whatever the current step targets, each with
   pointer-events:auto - so only the hole itself stays genuinely clickable/
   scrollable, everywhere else is truly locked out for the duration of that step, not
   just visually dimmed while still passing clicks through underneath (the previous
   box-shadow-cutout version was purely an illusion: nothing ever actually blocked
   interaction outside it). #onbSpotlight is a separate, pointer-events:none outline
   drawn exactly around that same hole, animated for a clear "this is the boundary"
   cue on top of the bouncing arrow. z-index sits above #modalOverlay (2500) since the
   tour should never render underneath a leftover dialog. */
#onbOverlay{display:none;}
.onb-mask{position:fixed;display:none;background:rgba(4,8,12,.68);z-index:2700;pointer-events:auto;}
/* Same z-index/pointer-events as .onb-mask (it blocks clicks/scroll identically) but
   fully transparent - positioned exactly over the hole itself for a `noInteract`
   step (see onbPositionMask()), so that area stays genuinely bright/undimmed (the
   real spotlight effect other steps get) while still not being interactive. Without
   this, a `noInteract` step's only option was covering the ENTIRE viewport with the
   same dark mask as everywhere else, including the highlighted target itself - which
   is exactly why it read as "not focused, just uniformly dim" rather than "here's
   the bright, boxed-off area you should be reading". */
.onb-mask-center{position:fixed;display:none;background:transparent;z-index:2700;pointer-events:auto;}
#onbSpotlight{position:fixed;z-index:2701;border-radius:8px;pointer-events:none;
  border:2px solid var(--amber);box-shadow:0 0 0 3px rgba(255,180,84,.25);
  animation:onb-boundary-pulse 1.6s ease-in-out infinite;
  transition:left .2s ease,top .2s ease,width .2s ease,height .2s ease;}
@keyframes onb-boundary-pulse{
  0%,100%{box-shadow:0 0 0 3px rgba(255,180,84,.25);}
  50%{box-shadow:0 0 0 7px rgba(255,180,84,.05);}
}
/* Bouncing arrow that loops above whatever button a step wants clicked (see
   onbShowArrow(), js/onboarding.js) - a second, more insistent pointer on top of the
   spotlight outline, specifically for "there's exactly one right button here" steps
   (Add I/O, one specific gate, Check Solution) rather than the broader "look at this
   whole area" steps, which get the spotlight alone. transform:translateX(-50%) keeps
   it centred on its left/top position regardless of its own rendered width. A
   line-drawn SVG chevron (not a filled glyph/emoji) to match the app's own icon
   style throughout. */
#onbArrow{position:fixed;z-index:2703;display:none;transform:translate(-50%,-100%);
  width:22px;height:22px;color:var(--amber);pointer-events:none;
  filter:drop-shadow(0 2px 4px rgba(0,0,0,.5));animation:onb-arrow-bounce 1s ease-in-out infinite;}
#onbArrow svg{width:100%;height:100%;display:block;}
@keyframes onb-arrow-bounce{
  0%,100%{transform:translate(-50%,-100%);}
  50%{transform:translate(-50%,-140%);}
}
/* Animated click-and-drag demo dot for the wiring step (onbAnimateDrag(),
   js/onboarding.js) - loops smoothly from one real pin to another via a plain CSS
   transition on left/top (driven by JS, since the two endpoints are dynamic real
   screen positions, not something a fixed @keyframes path could know in advance),
   switching to .onb-dragging (bigger, sky-blue) partway through to read as "now
   pressed and moving" rather than just sliding for no visible reason. */
#onbDragCursor{position:fixed;z-index:2704;display:none;width:16px;height:16px;
  margin-left:-8px;margin-top:-8px;border-radius:50%;pointer-events:none;
  background:var(--amber);opacity:.9;box-shadow:0 0 0 4px rgba(255,180,84,.3);}
#onbDragCursor.onb-dragging{background:var(--sky);box-shadow:0 0 0 7px rgba(94,200,255,.35);
  transform:scale(1.3);}
/* Centred illustrated scene for the problem-statement step specifically (shown/
   hidden by that step's own onEnter/onExit, js/onboarding.js) - a large looping
   animation acting out the actual story behind the tutorial problem, not just
   describing it in text: a dark night scene (the "it's dark" input), a figure
   walking past the porch (the "motion" input, `#onbWalker` sliding across), and a
   manual override switch (`#onbSwitchKnob` sliding within `#onbOverrideSwitch`) -
   choreographed on one shared 8s loop so the porch light itself
   (`#onbPorchLight`) visibly turns on twice for two different reasons: once while
   the walker is passing by (motion AND dark), and once when the switch flips
   independent of the walker (the override), acting out the exact boolean logic
   the student is about to build rather than just narrating it. Centred over the
   canvas area (the card/spotlight for this step sit over the right-hand problem
   panel, so this doesn't overlap them) at a z-index above the dimming masks so it
   stays visible even while the rest of the screen is locked down for this step. */
#onbSideAnim{position:fixed;left:50%;top:50%;transform:translate(-50%,-50%);z-index:2704;
  display:none;pointer-events:none;filter:drop-shadow(0 10px 24px rgba(0,0,0,.55));
  border-radius:10px;overflow:hidden;}
/* Full 16s story, four scenarios in order (all animations share this one duration so
   everything stays in sync): (1) dark + a figure walking past (motion) -> light turns
   on, with an on-screen "DARK + MOTION" caption naming both conditions together, as
   requested; (2) the override switch flips on its own -> light turns on again,
   independent of the walker, captioned "OVERRIDE" (which is also permanently
   labelled on the switch graphic itself via the small <text> inside
   #onbOverrideSwitch); (3) the sky brightens to daytime; (4) a second, darker-
   silhouetted figure walks past in daylight -> the porch light stays off the whole
   time (no keyframe changes it from its resting/off fill during this window at all -
   the absence of a change *is* the demonstration), captioned "MOTION ONLY" in a
   dark-on-light colour so it stays readable against the now-bright sky. The two
   walker paths use separate elements (#onbWalker for night, #onbWalker2 for day)
   rather than trying to reuse one element for two differently-timed crossings.
   Captions sit at y=52 - low enough to clear the stars above (which run up to
   y=42) without dropping onto the roofline (which starts at y=60) - and the two
   walker-crossing captions ("DARK + MOTION"/"MOTION ONLY") are centred at x=85,
   matching where the walkers actually are (translateX 78px) when the porch light
   itself reacts, rather than a generic fixed centre point unrelated to the action.
   "ON"/"OFF" text sits directly beside the bulb (#onbLightOn/#onbLightOff, exact
   opacity-inverse of each other, both keyed to onb-porch-glow's own windows) so the
   bulb's state is legible even at a glance, not just inferred from its glow. The
   override switch (#onbOverrideSwitch) sits on the right side of the scene now
   (was bottom-left) - the "OVERRIDE" caption moved to x=112 to sit above it. */
.onb-star{animation:onb-twinkle 2.6s ease-in-out infinite;}
.onb-star-b{animation-delay:.6s;}
.onb-star-c{animation-delay:1.2s;}
.onb-star-d{animation-delay:1.8s;}
@keyframes onb-twinkle{
  0%,100%{opacity:.25;}
  50%{opacity:1;}
}
#onbSky{animation:onb-sky 16s ease-in-out infinite;}
@keyframes onb-sky{
  0%,59%{fill:#0c1622;}
  66%,91%{fill:#9fc8e0;}
  99%,100%{fill:#0c1622;}
}
#onbStars{animation:onb-stars-fade 16s ease-in-out infinite;}
@keyframes onb-stars-fade{
  0%,59%{opacity:1;}
  66%,91%{opacity:0;}
  99%,100%{opacity:1;}
}
#onbWalker{animation:onb-walk1 16s ease-in-out infinite;}
@keyframes onb-walk1{
  0%,3%{transform:translateX(-14px);opacity:0;}
  8%{opacity:1;}
  28%{transform:translateX(78px);opacity:1;}
  32%{opacity:1;}
  37%{transform:translateX(158px);opacity:0;}
  100%{transform:translateX(158px);opacity:0;}
}
#onbWalker2{animation:onb-walk2 16s ease-in-out infinite;}
@keyframes onb-walk2{
  0%,69%{transform:translateX(-14px);opacity:0;}
  72%{opacity:1;}
  81%{transform:translateX(78px);opacity:1;}
  85%{opacity:1;}
  91%{transform:translateX(158px);opacity:0;}
  100%{transform:translateX(158px);opacity:0;}
}
#onbPorchLight{animation:onb-porch-glow 16s ease-in-out infinite;}
@keyframes onb-porch-glow{
  0%,19%{fill:#454f59;filter:none;}
  24%,29%{fill:#ffd76a;filter:drop-shadow(0 0 6px #ffd76a);}
  34%,44%{fill:#454f59;filter:none;}
  47%,53%{fill:#ffd76a;filter:drop-shadow(0 0 6px #ffd76a);}
  56%,100%{fill:#454f59;filter:none;}
}
/* "ON"/"OFF" text beside the bulb, exactly mirroring onb-porch-glow's own on/off
   windows (one is simply the other's opacity inverted at every waypoint) so the
   label always matches what the bulb itself is actually doing, never a beat off. */
#onbLightOn{animation:onb-light-on-label 16s ease-in-out infinite;}
@keyframes onb-light-on-label{
  0%,19%{opacity:0;}
  24%,29%{opacity:1;}
  34%,44%{opacity:0;}
  47%,53%{opacity:1;}
  56%,100%{opacity:0;}
}
#onbLightOff{animation:onb-light-off-label 16s ease-in-out infinite;}
@keyframes onb-light-off-label{
  0%,19%{opacity:1;}
  24%,29%{opacity:0;}
  34%,44%{opacity:1;}
  47%,53%{opacity:0;}
  56%,100%{opacity:1;}
}
#onbSwitchKnob{animation:onb-switch-slide 16s ease-in-out infinite;transform-origin:center;}
@keyframes onb-switch-slide{
  0%,44%{transform:translateY(0);fill:#8aa0b3;}
  50%,53%{transform:translateY(-11px);fill:#ffd76a;}
  56%,100%{transform:translateY(0);fill:#8aa0b3;}
}
.onb-caption{opacity:0;}
#onbCaptionDarkMotion{animation:onb-caption-dark-motion 16s ease-in-out infinite;}
@keyframes onb-caption-dark-motion{
  0%,7%{opacity:0;}
  10%,35%{opacity:1;}
  38%,100%{opacity:0;}
}
#onbCaptionOverride{animation:onb-caption-override 16s ease-in-out infinite;}
@keyframes onb-caption-override{
  0%,43%{opacity:0;}
  46%,54%{opacity:1;}
  57%,100%{opacity:0;}
}
#onbCaptionMotionOnly{animation:onb-caption-motion-only 16s ease-in-out infinite;}
@keyframes onb-caption-motion-only{
  0%,70%{opacity:0;}
  73%,89%{opacity:1;}
  92%,100%{opacity:0;}
}
#onbCard{position:fixed;z-index:2702;width:320px;max-width:calc(100vw - 24px);
  background:var(--panel);border:1px solid var(--amber);border-radius:10px;
  padding:16px 18px 18px;font-family:var(--head);
  box-shadow:0 20px 50px rgba(0,0,0,.6);}
#onbCard.onb-centered{position:fixed;left:50% !important;top:50% !important;
  transform:translate(-50%,-50%);width:400px;}
.onb-card-top{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px;}
.onb-step-dots{display:flex;gap:5px;}
.onb-dot{width:6px;height:6px;border-radius:50%;background:var(--line-strong);}
.onb-dot.active{background:var(--amber);width:16px;border-radius:3px;}
.onb-dot.done{background:var(--text-dimmer);}
/* Groups the Read Aloud and Mute buttons together, next to each other - without this
   wrapper, .onb-card-top's own justify-content:space-between (needed to push the
   step-dots to the left) would spread all three flex children apart evenly instead,
   leaving a gap between the two icon buttons rather than keeping them paired. */
.onb-header-icons{display:flex;align-items:center;gap:2px;}
.onb-icon-btn{width:auto;background:none;border:none;color:var(--text-dim);cursor:pointer;
  line-height:1;padding:4px 5px;border-radius:4px;display:flex;align-items:center;}
.onb-icon-btn svg{width:15px;height:15px;}
.onb-icon-btn:hover{color:var(--heading);}
#onbTitle{margin:0 0 8px;font-size:16px;font-weight:700;color:var(--heading);
  display:flex;align-items:center;gap:7px;}
.onb-title-icon{width:18px;height:18px;flex:0 0 auto;color:var(--amber);}
.onb-title-icon svg{width:100%;height:100%;display:block;}
#onbBody{font-size:13px;color:var(--text-dim);line-height:1.6;margin-bottom:14px;}
#onbBody b{color:var(--text);}
#onbBody p{margin:0 0 10px;}
#onbBody p:last-child{margin-bottom:0;}
#onbActions{display:flex;gap:8px;justify-content:flex-end;flex-wrap:wrap;}
.onb-btn{width:auto;font-family:var(--head);font-weight:600;font-size:13px;
  padding:8px 16px;border-radius:6px;cursor:pointer;}
.onb-btn-secondary{background:none;border:1px solid var(--line-strong);color:var(--text-dim);}
.onb-btn-secondary:hover{color:var(--text-hover);border-color:var(--text-dim);}
.onb-btn-primary{background:var(--amber);border:1px solid var(--amber);color:#241704;}
.onb-btn-primary:hover{filter:brightness(1.08);}

/* ===================================================================
   Timing diagram pop-up
   =================================================================== */
#timingModalOverlay{position:fixed;inset:0;background:rgba(4,8,12,.7);z-index:2000;
  display:none;align-items:center;justify-content:center;padding:24px;}
#timingModalBox{background:var(--panel);border:1px solid var(--line-strong);border-radius:8px;
  width:min(1400px,94vw);max-height:88vh;display:flex;flex-direction:column;overflow:hidden;
  font-family:var(--head);box-shadow:0 20px 50px rgba(0,0,0,.6);}
.timing-modal-header{display:flex;align-items:center;justify-content:space-between;
  padding:14px 18px;border-bottom:1px solid var(--line);flex:0 0 auto;}
#timingModalTitle{font-size:15px;font-weight:600;color:var(--heading);text-transform:uppercase;
  letter-spacing:.03em;}
#timingModalClose{background:none;border:none;color:var(--text-dim);font-size:24px;
  line-height:1;cursor:pointer;padding:0 4px;}
#timingModalClose:hover{color:var(--red);}
#timingModalBody{overflow:auto;padding:18px;flex:1 1 auto;}
#timingModalBody .timing-diagram-flex{border-color:var(--line-strong);}
#timingModalBody .timing-labels{width:150px;}
#timingModalBody .timing-label{font-size:12.5px;}

/* ===================================================================
   Pin editor pop-up (saved-component pin/size layout)
   =================================================================== */
#pinEditorOverlay{position:fixed;inset:0;background:rgba(4,8,12,.7);z-index:2000;
  display:none;align-items:center;justify-content:center;padding:24px;}
#pinEditorOverlay.open{display:flex;}
#pinEditorBox{background:var(--panel);border:1px solid var(--line-strong);border-radius:8px;
  width:min(1100px,94vw);max-height:90vh;display:flex;flex-direction:column;overflow:hidden;
  font-family:var(--head);box-shadow:0 20px 50px rgba(0,0,0,.6);}

/* ===================================================================
   Folder picker (Save as component) - a tree view of the whole saved-components
   folder hierarchy at once (same expand/collapse idea as the palette's own folder
   tree, see .custom-tree-level etc. above), rather than drilling one level at a time -
   click any folder to select it as the destination.
   =================================================================== */
#folderPickerOverlay{position:fixed;inset:0;background:rgba(4,8,12,.7);z-index:2000;
  display:none;align-items:center;justify-content:center;padding:24px;}
#folderPickerOverlay.open{display:flex;}
#folderPickerBox{background:var(--panel);border:1px solid var(--line-strong);border-radius:8px;
  width:min(480px,94vw);max-height:80vh;display:flex;flex-direction:column;overflow:hidden;
  font-family:var(--head);box-shadow:0 20px 50px rgba(0,0,0,.6);}
#folderPickerBody{overflow:hidden;padding:14px 18px;flex:1 1 auto;display:flex;flex-direction:column;gap:10px;min-height:0;}
.folder-picker-current{font-size:12.5px;color:var(--text-dim);}
.folder-picker-current b{color:var(--heading);font-family:var(--mono);}
.folder-picker-tree{flex:1 1 auto;overflow-y:auto;min-height:200px;border:1px solid var(--line-strong);
  border-radius:6px;background:var(--panel2);padding:6px;}
.fp-node{margin:0;}
.fp-children{padding-left:16px;border-left:1px solid var(--line);margin-left:9px;}
.fp-row{display:flex;align-items:center;gap:6px;padding:6px 7px;border-radius:5px;
  cursor:pointer;color:var(--text);font-family:var(--head);font-size:12.5px;}
.fp-row:hover{background:var(--panel);}
.fp-row.selected{background:var(--bg);border:1px solid var(--amber);color:var(--heading);}
.fp-row.selected:hover{background:var(--bg);}
.fp-chevron{display:inline-block;width:12px;font-size:10px;color:var(--text-dim);
  transition:transform .1s;flex:none;text-align:center;}
.fp-chevron.expanded{transform:rotate(90deg);}
.fp-chevron.invisible{visibility:hidden;}
.fp-name{flex:1 1 auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.fp-root .fp-name{font-weight:600;}
.folder-picker-new{width:100%;display:flex;align-items:center;justify-content:center;gap:8px;
  background:none;border:1px dashed var(--line-strong);border-radius:5px;padding:8px 9px;cursor:pointer;
  color:var(--text-dim);font-family:var(--head);font-size:12.5px;}
.folder-picker-new:hover{border-color:var(--sky);color:var(--sky);}
/* Inline replacement for the button above while naming a new folder - deliberately part
   of this same popup rather than the separate shared prompt dialog (see
   showNewFolderForm, components.js), so there's no cross-overlay z-index/stacking
   dependency for something this small. */
.folder-picker-new-form{width:100%;display:flex;align-items:center;gap:6px;}
#folderPickerNewInput{flex:1 1 auto;min-width:0;background:var(--panel);color:var(--heading);
  border:1px solid var(--sky);border-radius:5px;padding:7px 9px;font-family:var(--head);
  font-size:12.5px;outline:none;}
.folder-picker-new-confirm, .folder-picker-new-cancel{flex:none;width:30px;height:30px;
  display:flex;align-items:center;justify-content:center;border-radius:5px;cursor:pointer;
  font-size:15px;line-height:1;border:1px solid var(--line-strong);background:var(--panel2);color:var(--text-dim);}
.folder-picker-new-confirm{color:var(--green);border-color:var(--green);}
.folder-picker-new-confirm:hover{background:var(--green);color:#0a1f13;}
.folder-picker-new-cancel:hover{color:var(--red);border-color:var(--red);}
.folder-picker-row-icon{width:14px;height:14px;flex:none;color:var(--sky);}
.folder-picker-note{font-size:11.5px;color:var(--text-dim);line-height:1.5;}
#folderPickerCancelBtn{background:var(--panel2);border:1px solid var(--line-strong);color:var(--text);}
#folderPickerCancelBtn:hover{color:var(--text-hover);}
#folderPickerSaveBtn{background:var(--amber);border:1px solid var(--amber);color:#241704;}
#folderPickerSaveBtn:hover{filter:brightness(1.08);}
#pinEditorTitle{font-size:15px;font-weight:600;color:var(--heading);text-transform:uppercase;letter-spacing:.03em;}
#pinEditorClose{background:none;border:none;color:var(--text-dim);font-size:24px;line-height:1;cursor:pointer;padding:0 4px;}
#pinEditorClose:hover{color:var(--red);}
#pinEditorBody{overflow:auto;padding:16px 18px;flex:1 1 auto;display:flex;flex-direction:column;gap:12px;}
.pe-toolbar{display:flex;align-items:center;gap:16px;flex-wrap:wrap;}
.pe-num-field{display:flex;align-items:center;gap:6px;font-size:12.5px;color:var(--text-dim);font-weight:600;}
.pe-num-field input{width:76px;background:var(--bg);border:1px solid var(--line-strong);border-radius:5px;
  color:var(--heading);font-family:var(--mono);font-size:12.5px;padding:5px 7px;}
.pe-toolbar-spacer{flex:1 1 auto;}
.pe-toolbar button{font-family:var(--head);font-weight:500;font-size:12.5px;padding:7px 12px;
  border-radius:5px;cursor:pointer;background:var(--panel2);border:1px solid var(--line-strong);color:var(--heading);}
.pe-toolbar button:hover{border-color:var(--sky);color:var(--sky);}
.pe-main{display:flex;gap:14px;min-height:360px;flex:1 1 auto;}
.pe-canvas-wrap{flex:1 1 auto;background:var(--bg);border:1px solid var(--line-strong);border-radius:8px;
  min-width:0;display:flex;}
#peSvg{width:100%;height:100%;min-height:360px;}
.pe-list{width:290px;flex:0 0 auto;overflow:auto;display:flex;flex-direction:column;gap:6px;
  max-height:520px;}
.pe-row{display:flex;align-items:center;gap:6px;background:var(--panel2);border:1px solid var(--line);
  border-radius:6px;padding:6px 8px;}
.pe-row .pe-kind{font-size:9.5px;font-weight:700;letter-spacing:.03em;border-radius:4px;padding:2px 5px;
  flex:0 0 auto;}
.pe-row .pe-kind.in{background:rgba(90,180,255,.16);color:var(--sky);}
.pe-row .pe-kind.out{background:rgba(255,180,90,.16);color:var(--amber);}
.pe-row .pe-name{flex:1 1 auto;font-family:var(--mono);font-size:11.5px;color:var(--heading);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.pe-row input{width:52px;background:var(--bg);border:1px solid var(--line-strong);border-radius:4px;
  color:var(--heading);font-family:var(--mono);font-size:11px;padding:4px 5px;}
.pe-note{font-size:11.5px;color:var(--text-dim);line-height:1.5;}
.pe-note b{color:var(--heading);}
.pe-pin-dot{cursor:grab;}
.pe-pin-dot:active{cursor:grabbing;}
.pe-pin-dot.in{fill:var(--sky);}
.pe-pin-dot.out{fill:var(--amber);}
.pe-pin-dot.dragging{filter:drop-shadow(0 0 4px rgba(255,255,255,.6));}
.pe-pin-label{font-family:var(--mono);font-size:9.5px;fill:var(--text);pointer-events:none;}
.pe-box{fill:var(--comp-fill);stroke:var(--sky);stroke-width:2.6;}
.pe-lead{stroke:var(--line-strong);stroke-width:1.6;}
.pe-crosshair{stroke:var(--line);stroke-width:1;stroke-dasharray:3 3;}

#problemResources{margin-top:4px;}
.resource-block{margin-top:16px;padding-top:14px;border-top:1px solid var(--line);}
.resource-block:first-child{margin-top:0;padding-top:0;border-top:none;}
.resource-block h3{font-family:var(--head);font-size:12px;font-weight:600;letter-spacing:.06em;
  text-transform:uppercase;color:var(--text-dim);margin:0 0 10px;}
.tutorial-links{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:7px;}
.tutorial-links li{margin:0;}
.tutorial-links a{display:flex;align-items:center;gap:7px;font-family:var(--head);font-size:13px;
  color:var(--amber);text-decoration:none;background:var(--panel2);border:1px solid var(--line-strong);
  border-radius:5px;padding:9px 10px;transition:border-color .15s,color .15s;}
.tutorial-links a:hover{border-color:var(--amber);color:var(--text-hover);}
.tutorial-links a::before{content:'\2197';flex:0 0 auto;font-size:13px;opacity:.7;}
.video-list{display:flex;flex-direction:column;gap:16px;}
.video-embed{background:var(--panel2);border:1px solid var(--line-strong);border-radius:6px;overflow:hidden;}
.video-embed a{display:block;padding:10px;font-family:var(--head);font-size:13px;color:var(--amber);text-decoration:none;}
.video-embed a:hover{color:var(--text-hover);}
.video-caption{font-family:var(--head);font-size:12.5px;color:var(--text);padding:9px 10px 0;}
.video-frame-wrap{position:relative;width:100%;padding-top:56.25%;}
.video-frame-wrap iframe{position:absolute;inset:0;width:100%;height:100%;border:0;}

/* Locked resources panel: shown instead of tutorials/videos until enough graded
   attempts have been made on a problem (see RESOURCE_UNLOCK_ATTEMPTS in problems.js). */
.resource-lock{background:var(--panel2);border:1px dashed var(--line-strong);border-radius:6px;
  padding:18px 16px;text-align:center;}
.resource-lock-icon{font-size:20px;opacity:.7;margin-bottom:6px;}
.resource-lock-text{font-family:var(--head);font-size:12.5px;color:var(--text-dim);line-height:1.5;}
.resource-lock-progress{font-family:var(--head);font-size:11px;color:var(--amber);margin-top:8px;font-weight:600;}

.attempts-counter{font-family:var(--head);font-size:11.5px;color:var(--text-dim);margin-left:8px;}

/* ===================================================================
   Gamification: points / streak stats bar (top of Problems tab)
   =================================================================== */
#gameStatsBar{display:flex;gap:10px;margin-bottom:14px;}
#gameStatsBar:empty{display:none;}
.game-stat{flex:1;display:flex;flex-direction:column;align-items:center;gap:2px;
  background:var(--panel2);border:1px solid var(--line-strong);border-radius:8px;padding:10px 6px;}
.game-stat-icon{font-size:16px;line-height:1;}
.game-stat-value{font-family:var(--head);font-size:17px;font-weight:700;color:var(--heading);line-height:1.2;}
.game-stat-label{font-family:var(--head);font-size:9.5px;color:var(--text-dim);text-transform:uppercase;letter-spacing:.05em;}
.game-stat.streak-active{border-color:var(--streak-active-border);background:var(--streak-active-bg);}
.game-stat.streak-active .game-stat-value{color:var(--amber);}

/* Difficulty filter chips (top of Problems tab, above the category list) - a "Filter"
   label on its own line, followed by a wrapped row of chips ("All" + one per difficulty
   + "Essential") on the line below. The selected chip gets a solid fill (not just a
   tint) so it's unmistakably "on" against the others. Reuses the same diff-easy/
   diff-medium/diff-hard colour language as the per-problem difficulty badges so an
   active chip visually matches the problems it's filtering down to; "Essential" isn't a
   difficulty, so it gets its own accent (--sky) rather than borrowing one of those three.
   NOTE: .side button{width:100%} (below) would otherwise stack these vertically, one
   per line - every rule here is prefixed with #problemDifficultyFilter so it outranks
   that on specificity (an ID beats a bare class/class+element selector), including the
   hover/active states - a bare .diff-filter-chip.active without the ID prefix would
   lose to the ID-prefixed base rule below and never actually show as highlighted,
   which is exactly the bug that shipped here once before. */
#problemDifficultyFilter{display:flex;flex-direction:column;align-items:flex-start;gap:6px;
  margin-bottom:14px;}
#problemDifficultyFilter:empty{display:none;}
.diff-filter-label{font-family:var(--head);font-size:11px;font-weight:700;color:var(--text-dim);
  text-transform:uppercase;letter-spacing:.06em;white-space:nowrap;flex:0 0 auto;}
.diff-filter-chips{display:flex;flex-wrap:nowrap;align-items:center;gap:4px;width:100%;
  overflow-x:auto;}
#problemDifficultyFilter .diff-filter-chip{width:auto;flex:0 0 auto;font-family:var(--head);
  font-size:12px;font-weight:600;padding:3px 10.5px;border-radius:11px;white-space:nowrap;
  cursor:pointer;background:none;border:1px solid var(--line-strong);color:var(--text-dim);
  transition:border-color .15s,color .15s,background .15s;}
#problemDifficultyFilter .diff-filter-chip:hover{color:var(--heading);border-color:var(--text-dim);}
#problemDifficultyFilter .diff-filter-chip.active{color:var(--bg);border-color:var(--heading);
  background:var(--heading);font-weight:700;}
#problemDifficultyFilter .diff-filter-chip.active.diff-filter-easy{border-color:var(--green);
  background:var(--green);color:var(--bg);}
#problemDifficultyFilter .diff-filter-chip.active.diff-filter-medium{border-color:var(--amber);
  background:var(--amber);color:var(--bg);}
#problemDifficultyFilter .diff-filter-chip.active.diff-filter-hard{border-color:var(--red);
  background:var(--red);color:var(--bg);}
#problemDifficultyFilter .diff-filter-chip.active.diff-filter-essential{border-color:var(--sky);
  background:var(--sky);color:var(--bg);}

/* Tip block (inside a problem's description) - a bulb button that reveals the inline
   "Tip: ..." hint split out of problem.description (see splitDescriptionTip() in
   problems.js), plus a note on the no-tip bonus at stake. Same width:auto override
   trick as the filter chips above, since this button also lives under .side. */
.tip-block{margin:2px 0 14px;}
#problemDetail .tip-toggle-btn{width:auto;display:inline-flex;align-items:center;gap:6px;
  font-family:var(--head);font-size:12.5px;font-weight:600;padding:6px 12px;border-radius:16px;
  cursor:pointer;background:none;border:1px solid var(--amber-dim);color:var(--amber);
  transition:border-color .15s,color .15s,background .15s;}
#problemDetail .tip-toggle-btn:hover{border-color:var(--amber);}
#problemDetail .tip-toggle-btn.viewed{border-color:var(--line-strong);color:var(--text-dim);}
.tip-bulb{font-size:14px;line-height:1;}
.tip-bonus-note{display:block;margin-top:5px;font-family:var(--head);font-size:10.5px;
  color:var(--text-dim);font-style:italic;}
.tip-panel{margin-top:8px;padding:9px 12px;background:var(--panel2);border:1px solid var(--amber-dim);
  border-radius:6px;}
.tip-label{display:inline-block;font-family:var(--head);font-size:10px;font-weight:700;
  color:var(--amber);text-transform:uppercase;letter-spacing:.07em;margin-right:8px;
  vertical-align:top;}
.tip-text{color:var(--text);font-size:13px;line-height:1.5;}

.problem-row{display:flex;align-items:center;gap:8px;background:var(--panel2);border:1px solid var(--line-strong);
  border-radius:6px;padding:11px 12px;margin:0 0 8px 18px;cursor:pointer;font-family:var(--head);font-size:13.5px;
  position:relative;}
.problem-category{display:flex;flex-direction:column;gap:5px;width:100%;background:none;border:1px solid transparent;
  font-family:var(--head);font-size:11px;font-weight:600;color:var(--text-dim);
  text-transform:uppercase;letter-spacing:.08em;margin:16px 0 8px;padding:6px 8px;cursor:pointer;border-radius:6px;}
.problem-category:hover{color:var(--amber);}
.problem-category:first-child{margin-top:0;}
.cat-header-row{display:flex;align-items:center;gap:6px;width:100%;}
.problem-category .chevron{font-size:11px;transition:transform .15s;}
.problem-category.expanded .chevron{transform:rotate(0deg);}
.problem-category:not(.expanded) .chevron{transform:rotate(-90deg);}
.problem-category-count{margin-left:auto;font-weight:500;color:var(--text-dim);letter-spacing:normal;
  text-transform:none;font-size:10.5px;}
/* Per-category mastery bar - a glanceable fill next to the "12/16" count text, so
   progress reads at a glance instead of doing the division yourself. Neutral sky
   while in progress, switches to green (matching .all-solved's own colour) the
   instant a category is fully solved - same threshold, no separate state to track. */
.cat-progress-track{display:block;width:100%;height:4px;border-radius:2px;background:var(--line);overflow:hidden;}
.cat-progress-fill{display:block;height:100%;background:var(--sky);border-radius:2px;transition:width .25s ease;}
.problem-category.all-solved .cat-progress-fill{background:var(--green);}
/* A category where every problem is solved gets its own clearly "done" treatment - a
   green-tinted border/background and a check in the count badge (added in problems.js). */
.problem-category.all-solved{border-color:var(--cat-solved-border);background:var(--cat-solved-bg);color:var(--green);}
.problem-category.all-solved:hover{color:var(--green);border-color:var(--green);}
.problem-category.all-solved .problem-category-count{color:var(--green);font-weight:700;}
.problem-cat-body{margin:0 0 4px 6px;padding-left:8px;border-left:1px solid var(--line);}
.problem-row:hover{border-color:var(--amber);}
.prob-status{width:16px;color:var(--green);font-weight:700;}
.prob-name{flex:1;color:var(--heading);font-weight:500;}
.prob-diff{font-size:10.5px;padding:3px 9px;border-radius:10px;font-family:var(--head);font-weight:600;white-space:nowrap;}
.diff-easy{background:none;border:1px solid var(--green-dim);color:var(--green);}
.diff-medium{background:none;border:1px solid #5c4a1d;color:var(--amber);}
.diff-hard{background:none;border:1px solid #5c1d24;color:var(--red);}


.tt-scroll{max-height:280px;overflow-y:auto;border:1px solid var(--line-strong);border-radius:5px;margin-bottom:14px;}
.tt-scroll table.tt{margin-top:0;}
.tt-scroll table.tt th{position:sticky;top:0;z-index:2;}

#ctxMenu{position:fixed;display:none;background:var(--panel2);border:1px solid var(--line-strong);border-radius:6px;
  width:188px;z-index:1001;padding:5px;box-shadow:0 8px 24px rgba(0,0,0,.5);}
#ctxMenu button{width:100%;background:none;border:none;color:var(--text);font-family:var(--head);font-size:13px;
  text-align:left;padding:8px 10px;border-radius:4px;cursor:pointer;white-space:normal;line-height:1.3;}
#ctxMenu button:hover{background:var(--line-strong);color:var(--text-hover);}
#ctxMenu button.danger:hover{background:var(--red-active-bg);color:var(--red);}

/* LED colour swatch row appended to an OUTPUT gate's context menu (see showCtxMenu,
   interactions.js). Each swatch's own colour comes from --sw-color, set inline per
   button so this stays one shared rule instead of one per colour.
   Selectors are scoped under #ctxMenu (not just .ctx-led-swatch) so they outrank the
   generic "#ctxMenu button" rule above - an ID selector beats a class selector no
   matter what order the rules are written in, and without this the swatches would
   inherit that rule's width:100%/padding and stretch into full-width blank bars
   instead of staying small squares. */
.ctx-led-swatches{border-top:1px solid var(--line-strong);margin-top:4px;padding:8px 10px;}
.ctx-led-swatches-label{font-family:var(--head);font-size:11px;color:var(--text-dim);
  text-transform:uppercase;letter-spacing:.04em;margin-bottom:6px;}
.ctx-led-swatches-row{display:flex;flex-wrap:wrap;gap:7px;}
#ctxMenu .ctx-led-swatch{width:20px;height:20px;min-width:20px;flex:none;border-radius:4px;
  padding:0;cursor:pointer;background:var(--sw-color);border:2px solid transparent;
  box-shadow:0 0 0 1px var(--line-strong) inset;}
#ctxMenu .ctx-led-swatch:hover{transform:scale(1.12);}
#ctxMenu .ctx-led-swatch.active{border-color:var(--heading);box-shadow:0 0 0 2px var(--panel2), 0 0 0 3px var(--sw-color);}

/* Brightness slider under the LED swatches (same context menu) - a plain range input,
   styled to match the panel and kept full-width unlike the swatches above. */
.ctx-led-brightness-row{display:flex;align-items:center;gap:8px;margin-top:10px;}
.ctx-led-brightness-row input[type="range"]{flex:1;accent-color:var(--amber);cursor:pointer;}
.ctx-led-brightness-val{font-family:var(--mono);font-size:11px;color:var(--text-dim);
  min-width:34px;text-align:right;}

/* Wire colour swatches, same look as the LED ones, appended to a wire's context menu
   (see showWireCtxMenu). */
.ctx-wire-swatches{border-top:1px solid var(--line-strong);margin-top:4px;padding:8px 10px;}
.ctx-wire-swatches-label{font-family:var(--head);font-size:11px;color:var(--text-dim);
  text-transform:uppercase;letter-spacing:.04em;margin-bottom:6px;}
.ctx-wire-swatches-row{display:flex;flex-wrap:wrap;gap:7px;}
#ctxMenu .ctx-wire-swatch{width:20px;height:20px;min-width:20px;flex:none;border-radius:4px;
  padding:0;cursor:pointer;background:var(--sw-color);border:2px solid transparent;box-shadow:0 0 0 1px var(--line-strong) inset;}
#ctxMenu .ctx-wire-swatch:hover{transform:scale(1.12);}
#ctxMenu .ctx-wire-swatch.active{border-color:var(--heading);box-shadow:0 0 0 2px var(--panel2), 0 0 0 3px var(--sw-color);}
#ctxMenu .ctx-wire-swatch.none-swatch{background:var(--panel);position:relative;}
#ctxMenu .ctx-wire-swatch.none-swatch::after{content:'';position:absolute;left:3px;right:3px;top:9px;
  height:2px;background:var(--red);transform:rotate(45deg);}

/* Higher than every other overlay's own z-index (2000 - see .center-modal-overlay,
   #timingModalOverlay, #pinEditorOverlay, #folderPickerOverlay above) since this is the
   shared prompt/confirm/alert/select dialog (showPrompt/showConfirm/showAlert/
   showSelect, js/modal.js) - things like typing a new folder's name call it FROM
   inside one of those other overlays, so it has to be able to render on top of
   whichever one is already open, not tie with it and lose on DOM order. */
#modalOverlay{position:fixed;inset:0;background:rgba(4,8,12,.6);z-index:2500;display:none;align-items:center;justify-content:center;}
#modalBox{background:var(--panel);border:1px solid var(--line-strong);border-radius:8px;padding:22px;width:340px;font-family:var(--head);}
#modalTitle{font-size:16px;font-weight:600;color:var(--heading);margin-bottom:8px;}
#modalMsg{font-size:13px;color:var(--text-dim);line-height:1.55;margin-bottom:14px;}
#modalInput{width:100%;background:var(--panel2);border:1px solid var(--line-strong);color:var(--text);
  padding:9px 10px;border-radius:5px;font-family:var(--mono);font-size:14px;margin-bottom:16px;}
#modalInput:focus{outline:none;border-color:var(--amber);}
#modalSelect{width:100%;background:var(--panel2);border:1px solid var(--line-strong);color:var(--text);
  padding:9px 10px;border-radius:5px;font-family:var(--head);font-size:14px;margin-bottom:16px;cursor:pointer;}
#modalSelect:focus{outline:none;border-color:var(--amber);}
.modal-actions{display:flex;gap:8px;justify-content:flex-end;}
.modal-actions button{font-family:var(--head);font-weight:500;font-size:13px;padding:8px 16px;border-radius:5px;cursor:pointer;}
#modalCancel{background:var(--panel2);border:1px solid var(--line-strong);color:var(--text);}
#modalCancel:hover{color:var(--text-hover);border-color:var(--line-strong);}
#modalOk{background:var(--amber);border:1px solid var(--amber);color:#241704;}
#modalOk:hover{filter:brightness(1.08);}
#peCancelBtn{background:var(--panel2);border:1px solid var(--line-strong);color:var(--text);}
#peCancelBtn:hover{color:var(--text-hover);}
#peSaveBtn{background:var(--amber);border:1px solid var(--amber);color:#241704;}
#peSaveBtn:hover{filter:brightness(1.08);}

#toast{position:fixed;bottom:22px;left:50%;transform:translateX(-50%) translateY(10px);background:var(--panel2);
  border:1px solid var(--amber);color:var(--heading);font-family:var(--head);font-size:13px;padding:11px 18px;
  border-radius:6px;z-index:2100;opacity:0;pointer-events:none;transition:opacity .2s, transform .2s;}
#toast.show{opacity:1;transform:translateX(-50%) translateY(0);}

/* Custom hover tooltip - replaces the plain OS tooltip that a native `title`/SVG `<title>`
   would otherwise draw, which can't be restyled. One shared floating element (see
   js/tooltip.js), positioned near the cursor and themed to match the rest of the app,
   used for every button, control and canvas element (gates/pins/wires) that carries a
   `data-tip` attribute. */
#customTooltip{
  position:fixed;z-index:3000;pointer-events:none;
  background:var(--panel2);color:var(--text);border:1px solid var(--line-strong);
  font-family:var(--head);font-size:12px;font-weight:500;line-height:1.45;
  padding:6px 10px;border-radius:6px;max-width:280px;
  box-shadow:0 8px 22px rgba(0,0,0,.5);
  opacity:0;transform:translateY(3px);
  transition:opacity .12s ease, transform .12s ease;
  white-space:normal;
}
#customTooltip.show{opacity:1;transform:translateY(0);}

/* Examples/Save Component/Clear canvas collapse FIRST now - before the annotation
   tools give way - since those are used far less often once a circuit's underway than
   the annotation tools are, so it's a better trade to fold the rarely-touched ones away
   first and keep the actively-used toolbar visible as long as possible. */
@media (max-width: 1250px){
  header{padding-right:62px;}
  .nav-menu-btn{display:flex;}
  .nav-actions{display:none;}
  header.nav-open .nav-actions{
    display:flex;flex-direction:column;align-items:stretch;gap:8px;
    position:absolute;top:100%;right:14px;margin-top:6px;
    background:var(--panel);border:1px solid var(--line-strong);border-radius:8px;
    padding:12px;z-index:100;box-shadow:0 10px 30px rgba(0,0,0,.5);min-width:230px;
  }
  header.nav-open .nav-actions > select, header.nav-open .nav-actions > button{width:100%;}
  #clearBtn{right:14px;padding:0;width:34px;height:34px;justify-content:center;}
  .clear-btn-label{display:none;}
}
/* Annotation tools collapse LAST, only once Examples/Save Component/Clear canvas have
   already folded away above and there's still not enough room - it's the toolbar
   someone's actually mid-task with while drawing/annotating, so it stays fully visible
   the longest rather than being the first thing tucked out of sight. */
@media (max-width: 900px){
  .annotate-toolbar{display:none;position:absolute;top:100%;left:0;margin-top:6px;
    background:var(--panel);border:1px solid var(--line-strong);border-radius:8px;
    padding:8px;z-index:100;box-shadow:0 10px 30px rgba(0,0,0,.5);width:max-content;
    max-width:calc(100vw - 40px);flex-wrap:wrap;row-gap:6px;}
  #annotateMenuBtn{display:flex;}
  #annotateMenuWrap.open .annotate-toolbar{display:flex;}
}
@media (max-width: 680px){
  .toolbar-label{display:none;}
}

/* ---------- Touch / mobile ---------- */
/* No double-tap-to-zoom delay on controls, no grey tap flash, no long-press text callout on
   the canvas. The canvas itself allows one-finger panning only: two-finger pinch is handled
   by js/touch.js (zooms the circuit, not the whole page). */
button, select, .gate-btn, .palette-header{touch-action:manipulation;}
#canvasWrap{touch-action:pan-x pan-y;}
svg{-webkit-touch-callout:none;-webkit-tap-highlight-color:transparent;}
#paletteFab{display:none;}
@media (pointer:coarse){
  .zoom-controls{padding:7px 9px;}
  .zoom-controls button{width:32px;height:32px;font-size:16px;}
  #zoomResetBtn{padding:0 10px;}
  .fbk-btn,.lnk-btn{height:48px !important;}
  .gate-btn{padding-top:10px;padding-bottom:10px;}
}
@media (max-width: 700px){
  .app{grid-template-columns:1fr 44px;min-width:0;height:100vh;height:100dvh;}
  .app.side-collapsed{grid-template-columns:1fr 44px;}
  header{grid-column:1/-1;padding:8px 46px 8px 8px;gap:5px;}
  #clearBtn{right:6px;width:32px;height:32px;}
  .brand-text{display:none;}
  .brand{min-width:0;}
  #fullscreenBtn{display:none;}
  /* Palette becomes a slide-over drawer, opened by #paletteFab (js/touch.js), so the
     canvas gets the full screen width. */
  .palette{position:fixed;left:0;top:var(--hdr-h,52px);bottom:0;width:min(280px,84vw);z-index:90;
    transform:translateX(-105%);transition:transform .2s ease;box-shadow:6px 0 24px rgba(0,0,0,.5);}
  .app.palette-open .palette{transform:translateX(0);}
  .palette-resize-handle{display:none;}
  .palette-footer{width:100%;position:absolute;}
  /* An expanded side panel overlays the canvas from the right instead of squeezing it. */
  .app:not(.side-collapsed) .side{position:fixed;right:0;top:var(--hdr-h,52px);bottom:0;
    width:min(360px,94vw);z-index:90;box-shadow:-6px 0 24px rgba(0,0,0,.5);}
  .side-resize-handle{display:none;}
  .minimap{display:none;}
  #paletteFab{display:flex;align-items:center;justify-content:center;position:absolute;left:16px;bottom:76px;z-index:22;
    height:44px;padding:0 14px;gap:7px;border-radius:6px;border:1px solid var(--line-strong);
    background:var(--panel2);color:var(--text);font:600 11px var(--head);text-transform:uppercase;
    letter-spacing:.04em;box-shadow:0 4px 18px rgba(0,0,0,.45);cursor:pointer;}
  #paletteFab.active{border-color:var(--amber);color:var(--amber);}
  .app.palette-open #paletteFab{z-index:22;}
  .fbk-btn span,.lnk-btn span{display:none;}
  .fbk-btn,.lnk-btn{padding:0 12px !important;}
}
