:root {
  /* Native widgets follow the theme: without this, the dark UI grew stock-light scrollbars,
     a white ground-picker dropdown and white number spinners -- the first thing every
     Windows laptop saw. One declaration themes them all. */
  color-scheme: dark;
  --bg: #14161a;
  --panel: #1b1e24;
  --raise: #22262d;       /* chips and part rows sit a step above the panel */
  --line: #2b3038;
  --line-soft: #23272e;
  --ink: #e6e8ec;
  --dim: #8b93a1;
  /* 4.5:1 on the panel. The old #5c6472 sat at 2.8:1, and this tier carries the smallest
     text in the app -- search counts, category counts, every popover's header, the hint. */
  --faint: #7e8795;
  --cell: #2e343d;
  --cell-top: #7d5a34;
  /* Blue, not orange: the parts themselves are bamboo and teak -- orange-brown -- so an orange
     selection sat on the one colour it could not stand out from. Blue is wood's complement, and
     it is what every design tool already uses to say "selected". */
  --accent: #5b8def;
  --accent-hi: #7aa5f5;
  /* The soft washes, as tokens: both were hardcoded rgba of the DARK palette and stayed
     dark-orange / dark-red when the theme went light. */
  --accent-soft: rgba(91, 141, 239, .16);
  --bad-soft: rgba(226, 99, 90, .14);
  /* Two radii, drawn six ways before: controls sit on --r-ctl, floating panels on --r-pop. */
  --r-ctl: 6px;
  --r-pop: 10px;
  --on-accent: #14161a;   /* text/glyph sitting on an accent fill */
  --ok: #5fbf8f;
  --bad: #e2635a;
  --warn-bg: #2a1f1f;
  --warn-ink: #f0b4ae;
  --info-bg: #1a2233;
  --info-ink: #b9cdf5;
  --caution: #b8863a;
  --caution-ink: #d8a34a;
  --shadow: rgba(0, 0, 0, .6);
  --scene: #14161a;       /* the 3D canvas clear colour -- read by app.js too */
  --sunken: #0e1013;      /* photo and thumbnail wells, a step BELOW the panel */
  --overlay: rgba(14, 16, 19, .82);   /* the tag strip laid over a thumbnail */
  --sel-bg: #1c2740;      /* a selected row's accent-tinted fill (part bench) */
  /* One stack, used everywhere (the font: shorthands below say var(--mono), because a
     shorthand's own family list would override the inherited one). Consolas is the load-
     bearing entry: ui-monospace resolves only in Safari, SF Mono / Menlo are macOS, and
     Cascadia ships with Windows Terminal without registering as a system font -- so a
     Windows Firefox fell all the way through to Courier New. Roboto/Noto cover Android. */
  --mono: ui-monospace, "SF Mono", "Cascadia Mono", Consolas, Menlo, "Roboto Mono", "Noto Sans Mono", monospace;
  /* The UI face. Monospace everywhere read as a terminal, not a design tool; it is kept for the
     NUMBERS (mm, part numbers, coordinates) where columns should line up, and the words get a
     system sans -- Segoe UI Variable on Windows 11, SF on Apple, Roboto/Noto on Android, and the
     Japanese UI faces so a JP title doesn't drop to a fallback serif. No web font: nothing to fetch. */
  --sans: system-ui, -apple-system, "Segoe UI Variable Text", "Segoe UI", Roboto, "Helvetica Neue",
    "Noto Sans", "Hiragino Sans", "Yu Gothic UI", Meiryo, sans-serif;
  font-family: var(--sans);
  -webkit-font-smoothing: antialiased;
}

/* Light theme. Same structure, a paper-and-ink palette. The 3D scene reads --scene off this
   root (getComputedStyle) so the canvas follows the panels. */
:root[data-theme="light"] {
  color-scheme: light;
  --bg: #f7f8fa;
  --panel: #ffffff;
  --raise: #f1f3f6;
  --line: #e2e5ea;
  --line-soft: #eef0f3;
  --ink: #1b1e24;
  --dim: #5b6472;
  --faint: #6e7785;      /* 4.5:1 on white; #8a93a1 was 3.1:1 */
  --cell: #ccd2da;
  --cell-top: #cda877;
  --accent: #2563eb;
  --accent-hi: #3b82f6;
  --accent-soft: rgba(37, 99, 235, .10);
  --bad-soft: rgba(207, 77, 68, .12);
  --on-accent: #ffffff;
  --ok: #2f9e6a;
  --bad: #cf4d44;
  --warn-bg: #fbeceb;
  --warn-ink: #9c342c;
  --info-bg: #eef4ff;
  --info-ink: #1e40af;
  --caution: #c08a2e;
  --caution-ink: #8f6716;
  --shadow: rgba(16, 24, 40, .10);
  --scene: #eef0f3;
  --sunken: #e4e7ea;
  --overlay: rgba(245, 246, 248, .86);
  --sel-bg: #e8f0fe;
}

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

/* Quiet scrollbars on the theme's own palette (color-scheme above handles the WebKit
   defaults; this is the Firefox half, and thinner everywhere it applies). */
* { scrollbar-width: thin; scrollbar-color: var(--line) transparent; }

/* One visible focus for keyboard users. The two inputs used to outline:none their focus
   away, and nothing anywhere declared :focus-visible -- tabbing was flying blind. */
:where(button, a, input, select, [tabindex]):focus-visible {
  outline: 2px solid var(--accent-hi);
  outline-offset: 1px;
}

body {
  background: var(--bg);
  color: var(--ink);
  height: 100vh;
  /* dvh, where it exists: on iOS Safari 100vh is the LARGE viewport, and with overflow
     hidden the page can never scroll the toolbar away -- the bottom slice of the app
     (the build totals) sat permanently behind it. */
  height: 100dvh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

header {
  display: flex;
  align-items: baseline;
  gap: 1rem;
  align-items: center;
  padding: .6rem 1rem;
  background: var(--panel);
  border-bottom: 1px solid var(--line);
  flex: none;
}
h1 { font-size: 1rem; font-weight: 650; letter-spacing: -.01em; }
.disclaimer { font-size: .72rem; color: var(--faint); white-space: nowrap; }
@media (max-width: 700px) { .disclaimer { display: none; } }
/* The one thing anyone ever clicked in the old header line. */
.benchlink { font-size: .72rem; color: var(--dim); text-decoration: underline; text-underline-offset: 3px; }
.benchlink:hover { color: var(--accent); }
/* Links + ground picker + theme cluster to the right. Only .hlinks carries margin-left:auto -- two auto
   margins would SPLIT the free space and push the group apart instead of grouping them. */
.hlinks { margin-left: auto; display: flex; gap: .35rem; align-items: center; }
.hlink {
  font-size: .78rem; color: var(--dim); text-decoration: none; white-space: nowrap;
  padding: .32rem .6rem; border-radius: var(--r-ctl); border: 1px solid transparent;
}
.hlink:hover { color: var(--ink); background: var(--raise); }
.hlink.ai { color: var(--accent); border-color: var(--accent); font-weight: 600; }
.hlink.ai:hover { background: var(--accent-soft); color: var(--accent); }
.hlink.gh { display: inline-flex; padding: .3rem .4rem; }
#groundsel {
  margin: .25rem 0;
  max-width: 10rem;      /* "Alpine meadow" fits; on a phone it must leave the tabs their room */
  font: .78rem var(--sans);
  color: var(--dim);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-ctl);
  padding: .32rem .5rem;
  cursor: pointer;
}
#groundsel:hover { color: var(--ink); }
#theme { font-size: .9rem; line-height: 1; padding: .25rem .45rem; }

/* Pages. A tab strip between the header and the canvas -- each tab a whole independent design, the
   active one wearing the accent on its top edge. New / rename / delete live on the tabs. */
#pagebar {
  flex: none;
  display: flex;
  align-items: center;
  gap: .6rem;
  padding: 0 .7rem;
  border-bottom: 1px solid var(--line);
  background: var(--panel);
}
#pager {
  flex: 1;
  min-width: 0;          /* lets a long row of tabs scroll instead of pushing the scene picker off */
  display: flex;
  align-items: stretch;
  gap: 3px;
  overflow-x: auto;
}
/* The scene picker rides on the view, top-left: a floating pill like the view bar's, so it reads as a
   control OF the picture. The select inside keeps native behaviour (keyboard, phone wheel picker). */
#scenechip {
  position: absolute; top: .7rem; left: .7rem; z-index: 4;
  display: inline-flex; align-items: center; gap: .35rem;
  padding: 2px 2px 2px .55rem;
  background: var(--panel); color: var(--dim);
  border: 1px solid var(--line-soft); border-radius: var(--r-pop);
  box-shadow: 0 4px 18px var(--shadow);
}
#scenechip:hover { color: var(--ink); }
#scenechip select { border: none; background: transparent; margin: 0; padding: .3rem .4rem; color: var(--ink); font-weight: 500; }
#pager .ptab {
  display: inline-flex; align-items: center; gap: .4rem;
  font: .78rem var(--sans);
  color: var(--dim);
  background: transparent;
  border: none;
  border-bottom: 2px solid transparent;
  padding: .4rem .7rem .35rem;
  cursor: pointer;
  white-space: nowrap;
  max-width: 220px;
}
#pager .ptab:hover { color: var(--ink); }
#pager .ptab.on { color: var(--ink); font-weight: 600; border-bottom-color: var(--accent); }
#pager .pname { overflow: hidden; text-overflow: ellipsis; }
#pager .pclose { color: var(--faint); font-size: .85rem; line-height: 1; border-radius: 3px; padding: 0 .15rem; }
#pager .pclose:hover { color: var(--bad); background: var(--raise); }
#pager .padd {
  font: 1rem var(--sans); color: var(--dim);
  background: transparent; border: none; cursor: pointer;
  padding: .2rem .55rem; align-self: center;
}
#pager .padd:hover { color: var(--accent); }
@media (pointer: coarse) {
  #pager .ptab { min-height: 40px; }
  #pager .pclose { min-width: 28px; min-height: 28px; display: inline-flex; align-items: center; justify-content: center; }
  #pager .padd { min-width: 40px; min-height: 40px; }
}

main {
  flex: 1;
  display: grid;
  grid-template-columns: 280px 1fr 330px;
  /* minmax(0, 1fr), not auto: the implicit auto row sizes to its CONTENT, and the canvas's
     backing store -- set from a boot-time measurement taken before the pager populated --
     became that content's min-height. The stage overflowed main by the pager's height and
     the hint line + view bar lived below the fold on every 900px-tall laptop. */
  grid-template-rows: minmax(0, 1fr);
  min-height: 0;
}
/* Either side panel folds away to a thin strip, so the viewport can have the room. */
main.left-collapsed  { grid-template-columns: 22px 1fr 330px; }
main.right-collapsed { grid-template-columns: 280px 1fr 22px; }
main.left-collapsed.right-collapsed { grid-template-columns: 22px 1fr 22px; }

aside {
  overflow-y: auto;
  padding: 1rem;
  background: var(--panel);
  position: relative;
}
/* The extra inner padding keeps the panel's content clear of its collapse toggle. */
#palette { border-right: 1px solid var(--line); padding-right: 1.7rem; }
#bom { border-left: 1px solid var(--line); padding-left: 1.7rem; }
aside.collapsed { overflow: hidden; padding: 0; }
aside.collapsed > *:not(.panel-toggle) { display: none; }

.panel-toggle {
  position: absolute;
  top: .45rem;
  width: 16px; height: 26px;
  display: grid; place-items: center;
  font: .75rem var(--sans);
  color: var(--faint);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-ctl);
  cursor: pointer;
  z-index: 3;
}
.panel-toggle:hover { color: var(--ink); border-color: var(--accent); }
#palette .panel-toggle { right: .35rem; }
#bom .panel-toggle { left: .35rem; }
aside.collapsed .panel-toggle { left: 50%; transform: translateX(-50%); right: auto; }

section { margin-bottom: 1.1rem; }
h2 {
  font-size: .8rem;
  font-weight: 600;
  color: var(--ink);
  margin-bottom: .5rem;
}
.hint { font-weight: 400; color: var(--faint); font-size: .74rem; }

/* Collapsible library categories. Click a header to fold it; during a search every body is forced
   open (see .searching) so matches are never hidden behind a collapsed section. */
.cathead { cursor: pointer; user-select: none; display: flex; align-items: baseline; gap: .35rem; }
/* One line per head: the name holds, the hint after it gives way with an ellipsis. Wrapping put
   "Hook-on / boards" over two lines beside a two-line hint -- four lines of label for one list. */
.cathead { white-space: nowrap; }
.cathead .hint { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.cathead .catcount { flex: none; }
.cathead:hover { color: var(--ink); }
.caret { font-size: .7rem; color: var(--faint); transition: transform .12s; }
.cat.collapsed .caret { transform: rotate(-90deg); display: inline-block; }
.cat.collapsed .catbody { display: none; }
/* Count of parts in the category, right-aligned so the numbers line up down the panel edge. */
.catcount { margin-left: auto; font-size: .72rem; font-weight: 400; color: var(--faint);
  font-variant-numeric: tabular-nums; }

/* Super-group divider: one tier ABOVE the foldable category heads. A label with a rule trailing off
   it, so it reads as a phase of the build, never as another category you can click to collapse. */
.catgroup {
  display: flex; align-items: center; gap: .55rem;
  font-size: .72rem; font-weight: 500;
  color: var(--faint); margin: 1.6rem 0 .7rem;
}
.catgroup::after { content: ""; flex: 1; height: 1px; background: var(--line); }
.catgroup:first-of-type { margin-top: .35rem; }

#palette.searching .catbody { display: block; }
#palette.searching .cat.empty { display: none; }   /* hide categories with no match while searching */
#palette.searching .catgroup { display: none; }     /* the results view is a flat list -- no phase labels */
.searchcount { font-size: .72rem; color: var(--faint); margin: -.3rem 0 .6rem; }

/* Find a part by name or number -- used in both the planner palette and the bench list. */
.search {
  width: 100%;
  padding: .5rem .65rem;
  margin-bottom: .7rem;
  font: .82rem var(--sans);
  border: 1px solid var(--line);
  border-radius: var(--r-ctl);
  background: var(--panel);
  color: var(--ink);
}
.search::placeholder { color: var(--faint); }
.search:focus { border-color: var(--accent); }   /* the global :focus-visible draws the outline */

.chips { display: flex; flex-wrap: wrap; gap: .35rem; }
.chip {
  padding: .32rem .6rem;
  font: .78rem var(--sans);
  border: 1px solid var(--line);
  border-radius: var(--r-ctl);
  background: var(--panel);
  color: var(--ink);
  cursor: pointer;
}
.chip:hover { background: var(--raise); }
.chip.on { background: var(--accent); border-color: var(--accent); color: var(--on-accent); font-weight: 600; }
.chip.dead { opacity: .35; cursor: not-allowed; }
.chip.dead:hover { background: var(--panel); }

.parts { display: flex; flex-direction: column; gap: 1px; }
.part {
  display: flex;
  align-items: center;
  gap: .6rem;
  padding: .42rem .5rem;
  font-size: .82rem;
  border: 1px solid transparent;
  border-radius: var(--r-ctl);
  background: transparent;
  cursor: pointer;
}
.part:hover { background: var(--raise); }
.part.on { background: var(--accent-soft); }
.part .sw { width: 12px; height: 12px; border-radius: 3px; flex: none; box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .08); }
.part .nm { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.part .sp { color: var(--faint); font: .72rem var(--mono); flex: none; }
.part.dead { opacity: .35; cursor: not-allowed; }
/* The manual lists specific hosts and this one is not among them -- but the whitelist may
   simply predate it (the collapsible frames are omitted yet fit). A caution, not a block. */
.part.caution { border-color: var(--caution); }
.part.caution .sp { color: var(--caution-ink); }
/* A placed-footprint row: click the name to select (works even when the layer is locked), × to remove. */
.part .del { color: var(--dim); font-size: .85rem; line-height: 1; flex: none; padding: .1rem .3rem; border-radius: 3px; cursor: pointer; }
.part .del:hover { color: var(--bad); background: var(--bad-soft); }
.subhead { font-size: .72rem; font-weight: 500; color: var(--faint); margin: .6rem 0 .2rem; }

/* The scene outliner: the placed objects as a tree, nested by what hooks onto what. Click a row to
   select it in the 3D view (and get its floating toolbar); the × removes it and its children. */
#scene { display: flex; flex-direction: column; gap: 1px; margin-bottom: .3rem; }
.tree-row {
  display: flex;
  align-items: center;
  gap: .4rem;
  padding: .38rem .45rem;
  font-size: .8rem;
  border: 1px solid transparent;
  border-radius: var(--r-ctl);
  cursor: pointer;
}
.tree-row:hover, .tree-row.hover { background: var(--raise); }
.tree-row.on { background: var(--accent-soft); color: var(--ink); font-weight: 600; }
.tree-row .tw { color: var(--faint); flex: none; width: 1em; text-align: center; }
.tree-row .sw { width: 10px; height: 10px; border-radius: 3px; flex: none; }
.tree-row .nm { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tree-row.module .nm { color: var(--dim); }
.tree-row .del { color: var(--faint); font-size: .82rem; line-height: 1; flex: none; padding: 0 .25rem; border-radius: 3px; opacity: 0; }
.tree-row:hover .del { opacity: 1; }
.tree-row .del:hover { color: var(--bad); background: var(--warn-bg); }
.tree-empty { font-size: .76rem; color: var(--faint); padding: .3rem .1rem; }

#stage { position: relative; min-width: 0; }
#canvas { width: 100%; height: 100%; display: block; }

/* The rubber band. Drawn over the canvas while you sweep empty ground. */
#marquee[hidden] { display: none; }
#marquee {
  position: absolute;
  border: 1px solid var(--accent);
  background: var(--accent-soft);   /* tracks the theme; the fill was the dark accent baked in */
  border-radius: 2px;
  pointer-events: none;
  z-index: 5;
}

/* Dimensions read-out, top-left of the viewport: the layout's overall extent, and the selected
   object's size. A design tool should always tell you how big the thing is. */
/* One stack in the corner. `right` keeps the whole column clear of the view bar without anyone
   having to know how tall the column is. */
.stagefoot {
  position: absolute;
  /* right clears the view bar, which grew words ("select" / "measure") and is ~26rem now --
     15rem let the hint run underneath it on a narrow window. */
  bottom: .7rem; left: .7rem; right: 27rem;
  display: flex; flex-direction: column; gap: .3rem;
  align-items: flex-start;
  pointer-events: none;
}
#dimhud[hidden] { display: none; }
#dimhud {
  font-size: .74rem;
  font-variant-numeric: tabular-nums;
  color: var(--dim);
  pointer-events: none;
  line-height: 1.5;
  text-shadow: 0 1px 2px var(--bg), 0 0 2px var(--bg);
}
#dimhud .sel { color: var(--accent); }
#dimhud b { color: var(--ink); font-weight: 600; }

#slotbar {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  bottom: 3.4rem;   /* clear of the view bar's top edge even when the centred bar drifts over it */
  display: flex;
  gap: 2px;
  /* The same floating-panel ground as the view bar beside it. Bare on the canvas, the empty
     cells sat at 1.2:1 -- the reading is "n OF N half-slots" and the N was invisible. */
  padding: 3px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-pop);
  box-shadow: 0 2px 10px var(--shadow);
}
#slotbar:empty { display: none; }   /* no frame selected = no bar, not an empty pill */
.cell {
  width: 26px; height: 10px;
  background: var(--cell);
  border-radius: 1px;
}
.cell.used { background: var(--accent); }
.cell.top { background: var(--cell-top); }
.cell.over { background: var(--bad); }

#hint {
  font-size: .74rem;
  color: var(--faint);
}

/* Preset-view toolbar, bottom-right of the viewport. (Named #viewnav, NOT #viewbar -- the part
   bench has its own #viewbar and shares this stylesheet.) */
#viewnav {
  position: absolute;
  right: .7rem; bottom: .7rem;
  display: flex;
  gap: 2px;
  padding: 4px;
  background: var(--panel);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-pop);
  box-shadow: 0 4px 18px var(--shadow);
  z-index: 4;
}
#viewnav button {
  font: .76rem var(--sans);
  color: var(--dim);
  background: transparent;
  border: none;
  border-radius: var(--r-ctl);
  padding: .32rem .55rem;
  cursor: pointer;
}
#viewnav button:hover { background: var(--raise); color: var(--ink); }
#viewnav .sep { width: 1px; background: var(--line); margin: .18rem .18rem; }
/* The band tool is a MODE, so it has to look like one -- a pressed button, not a thing you poked. */
#bandtool { font-size: .82rem; line-height: 1; padding: .22rem .4rem; }
#bandtool.on, #bandtool.on:hover { background: var(--accent); color: var(--panel); }
#rulertool { font-size: .82rem; line-height: 1; padding: .22rem .4rem; }
#rulertool.on, #rulertool.on:hover { background: var(--accent); color: var(--panel); }

/* Measurements. The SVG carries the lines and end ticks and never takes the pointer -- it is a
   drawing, not a control. The label is the only part you touch, and only its × is live, so a
   measurement can sit right over a table without stealing a drag from it. */
#rulersvg {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  pointer-events: none;
  overflow: visible;
  z-index: 4;
}
#rulersvg line { stroke: var(--accent); stroke-width: 1.5; stroke-linecap: round; }
#rulersvg line.draft { stroke-dasharray: 5 4; opacity: .85; }
#rulersvg circle { fill: var(--accent); }
#rulerlabels { position: absolute; inset: 0; pointer-events: none; z-index: 6; }
.rlabel {
  position: absolute; transform: translate(-50%, -50%);
  display: inline-flex; align-items: center; gap: .3rem;
  padding: .1rem .2rem .1rem .38rem;
  font: .66rem var(--mono);
  color: var(--ink);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-ctl);
  box-shadow: 0 1px 5px var(--shadow);
  white-space: nowrap;
  pointer-events: none;
}
.rlabel .dx { color: var(--dim); font-size: .64rem; }
.rlabel button {
  pointer-events: auto;
  border: none; background: transparent; color: var(--dim);
  cursor: pointer; font-size: .82rem; line-height: 1; padding: 0 .12rem;
}
.rlabel button:hover { color: var(--accent); }
#rulerdraftlabel[hidden] { display: none; }
#rulerdraftlabel {
  position: absolute; transform: translate(-50%, -50%);
  z-index: 6; pointer-events: none;
  padding: .1rem .35rem;
  font: .66rem var(--mono);
  color: var(--panel); background: var(--accent);
  border-radius: var(--r-ctl); white-space: nowrap;
  box-shadow: 0 1px 5px var(--shadow);
}
@media (pointer: coarse) {
  #rulertool { font-size: 1.1rem; }
  .rlabel button { min-width: 32px; min-height: 32px; }
}

/* TOUCH. A finger is not a mouse pointer: it has no hover, and it is about 9mm wide. Anything you
   are meant to press gets a 44px box (Apple's number, and the one everyone else copied), and the
   viewport bar comes off the corner where a thumb cannot reach it. Nothing here changes the mouse. */
@media (pointer: coarse) {
  /* The bar grew words; on the narrowest phones it wraps to a second row instead of
     running off the left edge of the screen. */
  #viewnav { right: .5rem; bottom: .5rem; gap: 4px; padding: 4px; flex-wrap: wrap; justify-content: flex-end; max-width: calc(100vw - 1rem); }
  #viewnav button { min-width: 44px; min-height: 44px; font-size: .7rem; padding: .4rem; }
  #bandtool { font-size: 1.1rem; }
  #seltools button, #toolpop button, .panel-toggle { min-width: 44px; min-height: 44px; }
  /* The other half of the tappable surface -- palette rows, the hook-on menus (which reuse
     .part for their rows), chips, fold headers, tree rows -- was still 26-29px. The final
     "pick which board to hook on" tap deserves the same 44px rule as the buttons around it. */
  .part, .chip, .cathead, .tree-row { min-height: 42px; }
  /* min-*, not width/height: the base #edgebtn rule sets width/height 26px LATER in the file
     and a media query adds no specificity, so plain width here would lose the cascade.
     (The menus' 45vh max-height lives in its own coarse block after their base rules,
     for the same reason.) */
  #edgebtn { min-width: 44px; min-height: 44px; }
  /* The grown view bar spans most of a phone's width; lift the slot bar and the dimension
     stack clear of it instead of letting it paint over them. The slotbar sits a band ABOVE
     the stagefoot, because the hint line is now visible on touch and both are centred low. */
  #slotbar { bottom: 7rem; }
  .stagefoot { right: .7rem; bottom: 4.6rem; }
}

/* Fixed layout, or a long part name ("IGT Standing Height 830mm Leg Set") shoves
   the price column clean out of the panel. */
#bomtable { width: 100%; table-layout: fixed; border-collapse: collapse; font-size: .8rem; }
#bomtable td { padding: .42rem .2rem; border-bottom: 1px solid var(--line-soft); }
/* The count column. The BOM no longer deletes -- the scene tree above it already does, and one
   object with two delete buttons in one panel is a panel nobody re-read. */
#bomtable .qty { color: var(--dim); width: 1.9rem; text-align: right; padding-right: .3rem; white-space: nowrap; }
#bomtable .nm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#bomtable .p { text-align: right; color: var(--dim); white-space: nowrap; width: 3.8rem; font-variant-numeric: tabular-nums; }

#totals { margin-top: .8rem; font-size: .8rem; font-variant-numeric: tabular-nums; }
#totals .row { display: flex; justify-content: space-between; padding: .2rem 0; }
#totals .row.big { border-top: 1px solid var(--line); margin-top: .3rem; padding-top: .4rem; }
#totals .note { color: var(--faint); font-size: .74rem; margin-top: .6rem; line-height: 1.5; }

#warnings { margin-bottom: .6rem; }
.warn {
  font-size: .76rem;
  padding: .5rem .65rem;
  margin-bottom: .4rem;
  border-radius: var(--r-ctl);
  border-left: 3px solid var(--bad);
  background: var(--warn-bg);
  color: var(--warn-ink);
  line-height: 1.4;
}
.warn.info { border-color: var(--accent); background: var(--info-bg); color: var(--info-ink); }

@media (max-width: 1000px) {
  /* The collapse rules above carry the same specificity and would re-assert their THREE
     desktop columns inside this one-column layout -- a panel-toggle press on a phone
     squeezed the canvas to a 38px sliver between two full-width panels. Same selectors,
     later in the file, so one column always wins here. */
  main, main.left-collapsed, main.right-collapsed, main.left-collapsed.right-collapsed {
    grid-template-columns: 1fr;
  }
  /* fr rows, not `auto 55vh auto`: auto split the leftover evenly and handed the palette a
     136px letterbox over its ~2900px of library. A collapsed panel's row drops to auto so
     its space goes to the stage instead of a hidden list. */
  main { grid-template-rows: minmax(0, 1.2fr) minmax(0, 2.2fr) minmax(0, 1fr); }
  main.left-collapsed  { grid-template-rows: auto minmax(0, 2.2fr) minmax(0, 1fr); }
  main.right-collapsed { grid-template-rows: minmax(0, 1.2fr) minmax(0, 2.2fr) auto; }
  main.left-collapsed.right-collapsed { grid-template-rows: auto minmax(0, 1fr) auto; }
  /* A collapsed aside's only visible child is the absolutely-positioned toggle, which an
     auto row cannot see -- the row fell to ~1px and overflow:hidden clipped the one control
     that brings the panel back. The floor keeps the way back on screen. */
  aside.collapsed { min-height: calc(44px + .9rem); }
  #palette, #bom { border: none; border-top: 1px solid var(--line); }
  /* Narrow WINDOWS get the phone treatment too: 27rem of view-bar clearance is wider than
     the screen here, and it was wrapping the hint one word per line. */
  .stagefoot { right: .7rem; bottom: 4.6rem; }
  #slotbar { bottom: 7rem; }
  /* Three header rows ("IGT / LAYOUT / PLANNER") spent the height the panels are starving for. */
  header h1 { white-space: nowrap; font-size: .9rem; }
}

/* Hover an edge, get a button; press it, get the parts that can hook there. The button is
   centred ON the edge, so it reads as belonging to that edge and not to the table.

   `display: grid`/`flex` on an ID selector BEATS the browser's `[hidden] { display: none }`,
   so `el.hidden = true` set the attribute and changed nothing on screen. The element was
   there the whole time and I spent three rounds calling the screenshots stale. Re-state
   the rule at a specificity that wins. */
#edgebtn[hidden], #edgemenu[hidden] { display: none; }

#edgebtn {
  position: absolute;
  transform: translate(-50%, -50%);
  width: 26px; height: 26px;
  display: grid; place-items: center;
  font: 600 15px/1 var(--mono);
  color: var(--on-accent);
  background: var(--accent);
  border: 2px solid var(--accent-hi);
  border-radius: 50%;
  cursor: pointer;
  box-shadow: 0 2px 10px var(--shadow);
  z-index: 5;
}
#edgebtn:hover { background: var(--accent-hi); }

#edgemenu {
  position: absolute;
  width: 236px;
  max-height: 210px;
  overflow-y: auto;
  padding: .4rem;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-pop);
  box-shadow: 0 6px 22px var(--shadow);
  z-index: 6;
  display: flex;
  flex-direction: column;
  gap: .25rem;
}
#edgemenu .mhead {
  font-size: .74rem;
  color: var(--faint);
  padding: .1rem .1rem .25rem;
  line-height: 1.35;
}

/* The selected object's floating action toolbar -- a pill of buttons that tracks the object in
   the viewport, so acting on a selection never means a trip to a side panel. */
#seltools[hidden], #replacemenu[hidden] { display: none; }
#seltools {
  position: absolute;
  transform: translate(-50%, -100%);
  display: flex;
  gap: 2px;
  padding: 3px;
  background: var(--panel);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-pop);
  box-shadow: 0 6px 24px var(--shadow);
  z-index: 6;
}
#seltools button {
  display: grid;
  place-items: center;
  width: 30px; height: 30px;
  font: 14px/1 var(--sans);
  color: var(--ink);
  background: transparent;
  border: none;
  border-radius: var(--r-ctl);
  cursor: pointer;
}
#seltools button:hover { background: var(--raise); }
#seltools button.danger:hover { background: var(--warn-bg); color: var(--warn-ink); }
#seltools .sep { width: 1px; background: var(--line); margin: 4px 1px; }
#seltools .count { font-size: .76rem; color: var(--dim); padding: 0 .5rem; align-self: center; white-space: nowrap; }

#replacemenu, #toolpop {
  position: absolute;
  width: 220px;
  max-height: 260px;
  overflow-y: auto;
  padding: .35rem;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-pop);
  box-shadow: 0 8px 26px var(--shadow);
  z-index: 8;
  display: flex;
  flex-direction: column;
  gap: .2rem;
}
#toolpop[hidden] { display: none; }
#toolpop { width: 232px; }
/* After the base max-heights above, so this one wins on touch: 42px rows in a 210/260px
   box showed four items; the menus deserve the screen a phone actually has. */
@media (pointer: coarse) {
  #edgemenu, #replacemenu, #toolpop { max-height: 45vh; }
}
#replacemenu .mhead, #toolpop .mhead {
  font-size: .74rem; color: var(--faint);
  padding: .1rem .1rem .3rem; line-height: 1.35;
}
#seltools button.wide { width: auto; padding: 0 .55rem; font-size: .78rem; }
/* Icon buttons. The word rides in .lbl: hidden on a mouse unless the button is "keep"; always shown on
   touch (no tooltip ever reaches a finger). */
.ico { display: block; flex: none; }
#seltools button.ib { width: auto; min-width: 30px; padding: 0 .45rem; display: inline-flex; align-items: center; justify-content: center; gap: .3rem; font-size: .78rem; }
#seltools button.ib .lbl { display: none; }
#seltools button.ib.keep .lbl { display: inline; }
#seltools button .num { font-variant-numeric: tabular-nums; }
/* Touch: the word UNDER the icon, small -- seven icon+word pairs side by side ran off a phone. */
@media (pointer: coarse) {
  #seltools button.ib { flex-direction: column; gap: 1px; padding: 3px 4px; font-size: .62rem; line-height: 1.1; }
  #seltools button.ib .lbl { display: inline; }
}
/* Header chips, view bar, page tabs: icon + word in a row. */
.chip, #viewnav button, #edgebtn { display: inline-flex; align-items: center; justify-content: center; gap: .35rem; }
#undo, #redo, #foldall, #theme { padding: .32rem .45rem; }
#pager .pclose, #pager .padd, .del, .fdel, .rlabel button { display: inline-flex; align-items: center; justify-content: center; }

/* A menu row carries only a swatch, a name and a span. Passing over it opens this card
   beside the menu with the thumbnail and the numbers that would not fit -- name, sku,
   assembled size, price. */
#preview[hidden] { display: none; }
#preview {
  position: absolute;
  width: 190px;
  padding: .5rem;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-pop);
  box-shadow: 0 8px 26px var(--shadow);
  z-index: 7;
  pointer-events: none;
  font-size: .76rem;
}
#preview img {
  width: 100%;
  height: 118px;
  object-fit: contain;
  background: var(--raise);
  border-radius: 3px;
  margin-bottom: .4rem;
}
#preview .pv-name { color: var(--ink); font-weight: 600; line-height: 1.3; margin-bottom: .2rem; }
#preview .pv-row { display: flex; justify-content: space-between; gap: .5rem; color: var(--dim); padding: .08rem 0; }
#preview .pv-row b { color: var(--ink); font-weight: 600; }
#preview .pv-sku { color: var(--faint); font-family: var(--mono); }
#preview .pv-jp { color: var(--dim); font-size: .72rem; line-height: 1.35; margin: -.1rem 0 .25rem; }
#preview .pv-why { margin-top: .4rem; padding-top: .4rem; border-top: 1px solid var(--line-soft); color: var(--warn-ink); line-height: 1.4; }

/* Right-click / long-press a placed module: a little action menu at the pointer. */
#modmenu[hidden] { display: none; }
#modmenu {
  position: absolute;
  min-width: 160px;
  padding: .3rem;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-pop);
  box-shadow: 0 6px 22px var(--shadow);
  z-index: 8;
  display: flex;
  flex-direction: column;
  gap: .2rem;
}
#modmenu .mhead {
  font-size: .74rem; color: var(--faint);
  padding: .15rem .3rem .25rem; line-height: 1.3;
}
#modmenu .act {
  display: flex; align-items: center; gap: .4rem;
  padding: .42rem .5rem; font-size: .8rem;
  border: 1px solid transparent; border-radius: var(--r-ctl);
  background: transparent; cursor: pointer;
}
#modmenu .act:hover { background: var(--raise); }
#modmenu .act.on { border-color: var(--accent); background: var(--sel-bg); font-weight: 600; }
#modmenu .act.del:hover { background: var(--warn-bg); color: var(--warn-ink); border-color: var(--bad); }

/* layout-system additions */
.muted { color: var(--faint); }
#hint b { color: var(--accent); font-weight: 600; }
a { color: var(--accent); }
h3 {
  font-size: .74rem; font-weight: 500;
  color: var(--faint); margin: .6rem 0 .3rem;
}
.selbox { border-top: 1px solid var(--line); padding-top: .8rem; }
#selname { color: var(--accent); font-weight: 600; }

/* Numeric placement of the selected free object. */
#transform[hidden] { display: none; }
.xform { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
.xform label { display: flex; align-items: center; gap: .3rem; font-size: .76rem; color: var(--dim); }
.xform input {
  width: 3.9rem;
  font: .76rem var(--mono);
  color: var(--ink); background: var(--panel);
  border: 1px solid var(--line); border-radius: var(--r-ctl);
  padding: .2rem .3rem;
}
.xform input:focus { border-color: var(--accent); }
.xform span { color: var(--faint); }
.xform .rot { margin-left: auto; color: var(--dim); }

/* ---- layouts menu, blocks, and the one-line note ------------------------------------------- */
.filebar { display: flex; gap: 4px; margin-left: auto; }

#filemenu[hidden] { display: none; }
#filemenu {
  position: fixed;
  z-index: 20;
  min-width: 260px;
  padding: 4px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-pop);
  box-shadow: 0 8px 28px var(--shadow);
}
#filemenu .fhead {
  font: 600 .74rem var(--sans);
  color: var(--dim);
  padding: .5rem .5rem .25rem;
}
#filemenu .frow {
  display: flex; align-items: baseline; gap: .5rem;
  padding: .38rem .5rem;
  border-radius: var(--r-ctl);
  cursor: pointer;
  font-size: .84rem;
}
#filemenu .frow:hover { background: var(--raise); }
#filemenu .fsub { margin-left: auto; font-size: .72rem; color: var(--dim); }
/* The true thing about localStorage, where the word "save" is -- not in a help page nobody opens. */
#filemenu .fnote {
  padding: .5rem;
  font-size: .74rem; line-height: 1.5;
  color: var(--dim);
  border-top: 1px solid var(--line);
  margin-top: .25rem;
}
.fdel {
  margin-left: .3rem;
  border: none; background: transparent;
  color: var(--dim); cursor: pointer;
  font-size: .8rem; line-height: 1;
  padding: 0 .2rem;
}
.fdel:hover { color: var(--bad); }   /* was var(--danger, …) — a variable defined nowhere, so the fallback always won */
.hint.pad { padding: .5rem; line-height: 1.5; display: block; }

#note[hidden] { display: none; }
#note {
  position: absolute;
  left: 50%; top: .8rem;
  transform: translateX(-50%);
  z-index: 12;
  max-width: min(70ch, 90%);
  padding: .5rem .9rem;
  font-size: .8rem;
  background: var(--panel);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-pop);
  box-shadow: 0 6px 24px var(--shadow);
}
@media (pointer: coarse) {
  #filemenu .frow { padding: .7rem .5rem; }
  .filebar .chip { min-height: 44px; }
}

/* The pound line: the same number, quieter, because it is the same number. */
#totals .row b.alt { color: var(--dim); font-weight: 400; }

/* ---- the header cards (AI, photo, share) ------------------------------------------------------- */
#popcard[hidden] { display: none; }
#popcard {
  position: fixed; z-index: 20;
  width: min(380px, calc(100vw - 16px));
  padding: .9rem;
  background: var(--panel);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-pop);
  box-shadow: 0 12px 36px var(--shadow);
  display: flex; flex-direction: column; gap: .65rem;
  font-size: .82rem;
}
.chead { display: flex; flex-direction: column; gap: .15rem; }
.chead b { font-size: .9rem; font-weight: 650; }
.chead span { color: var(--dim); font-size: .76rem; line-height: 1.45; }
.cnote { color: var(--dim); font-size: .76rem; line-height: 1.45; margin: 0; }
.cnote.warn { color: var(--warn-ink); }
.crow { display: flex; gap: .4rem; align-items: center; flex-wrap: wrap; }
.crow.end { justify-content: flex-end; }
.cbtn {
  display: inline-flex; align-items: center; gap: .4rem;
  font: .8rem var(--sans); color: var(--ink);
  padding: .45rem .7rem; border-radius: var(--r-ctl);
  border: 1px solid var(--line); background: var(--panel); cursor: pointer;
}
.cbtn:hover { background: var(--raise); }
.cbtn.primary { background: var(--accent); border-color: var(--accent); color: var(--on-accent); font-weight: 600; }
.cbtn.primary:hover { background: var(--accent-hi); }
.cbtn.link { border: none; background: none; padding: .2rem 0; color: var(--accent); }
.cbtn.link:hover { text-decoration: underline; background: none; }
.cfield { flex: 1; min-width: 0; font: .76rem var(--mono); color: var(--dim);
  padding: .45rem .55rem; border: 1px solid var(--line); border-radius: var(--r-ctl); background: var(--raise); }
.cgrid { display: grid; grid-template-columns: repeat(4, 1fr); gap: .35rem; }
.csocial {
  display: flex; flex-direction: column; align-items: center; gap: .3rem;
  padding: .55rem .2rem; border-radius: var(--r-ctl); border: 1px solid var(--line-soft);
  color: var(--ink); text-decoration: none; font: .7rem var(--sans); background: var(--panel); cursor: pointer;
}
.csocial:hover { background: var(--raise); }
.csocial.dead { opacity: .35; cursor: not-allowed; }
.cfield-row { display: flex; align-items: center; gap: .6rem; }
.cfield-row[hidden] { display: none; }
.cfield-row > label { width: 3.6rem; flex: none; color: var(--dim); font-size: .76rem; }
.seg { display: inline-flex; border: 1px solid var(--line); border-radius: var(--r-ctl); overflow: hidden; }
.seg button { font: .76rem var(--sans); color: var(--dim); background: var(--panel); border: none;
  padding: .35rem .6rem; cursor: pointer; border-right: 1px solid var(--line-soft); }
.seg button:last-child { border-right: none; }
.seg button.on { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
.ccheck { display: flex; align-items: center; gap: .3rem; color: var(--dim); font-size: .76rem; cursor: pointer; }
.csep { height: 1px; background: var(--line-soft); margin: .2rem 0; }
.carea { width: 100%; min-height: 4.5rem; resize: vertical; font: .74rem var(--mono); color: var(--ink);
  padding: .5rem; border: 1px solid var(--line); border-radius: var(--r-ctl); background: var(--raise); }
.cfoot { display: flex; gap: 1rem; flex-wrap: wrap; font-size: .76rem; }
.cfoot a { color: var(--dim); text-decoration: none; } .cfoot a:hover { color: var(--accent); }

/* The corner read-outs sit on the scene, and a place puts grass or a sunset behind them -- a text-shadow
   was enough on the bare grid, not on a meadow. A soft pill of the panel colour carries them anywhere. */
#dimhud:not([hidden]), #hint {
  background: color-mix(in srgb, var(--panel) 82%, transparent);
  padding: .15rem .45rem; border-radius: var(--r-ctl);
  text-shadow: none;
}
.cqr[hidden] { display: none; }
.cqr { display: flex; flex-direction: column; align-items: center; gap: .4rem; padding: .6rem;
  border: 1px solid var(--line-soft); border-radius: var(--r-ctl); background: #fff; color: #333; }
.cqr svg { width: 200px; height: 200px; }
.cqr p { margin: 0; font-size: .74rem; line-height: 1.45; text-align: center; color: #555; }
.cqr p.warn { color: #9c342c; }

/* ---- the build messages, folded and grouped ------------------------------------------------------ */
.warnbox { border: 1px solid var(--line-soft); border-radius: var(--r-ctl); margin-bottom: .7rem; }
.warnbox > summary {
  list-style: none; cursor: pointer; user-select: none;
  display: flex; flex-wrap: wrap; gap: .3rem .8rem; align-items: center;
  padding: .45rem .6rem; font-size: .76rem; color: var(--dim);
}
.warnbox > summary::-webkit-details-marker { display: none; }
.warnbox > summary::after { content: "▾"; margin-left: auto; color: var(--faint); transition: transform .12s; }
.warnbox:not([open]) > summary::after { transform: rotate(-90deg); }
.warnbox > summary:hover { color: var(--ink); }
.wc { display: inline-flex; align-items: center; gap: .3rem; white-space: nowrap; }
.wc i { width: 7px; height: 7px; border-radius: 50%; background: var(--faint); }
.wc.problem { color: var(--bad); font-weight: 600; } .wc.problem i { background: var(--bad); }
.wc.check i { background: var(--caution); }
.wc.added i { background: var(--accent); }
.wgroup { padding: .2rem .6rem .5rem; border-top: 1px solid var(--line-soft); }
.wghead { font-size: .7rem; font-weight: 600; color: var(--faint); margin: .35rem 0 .25rem; }
.wgroup.problem .wghead { color: var(--bad); }
.witem { font-size: .76rem; line-height: 1.45; padding: .2rem 0 .2rem .6rem; border-left: 2px solid var(--line); color: var(--ink); margin-bottom: .25rem; }
.wgroup.problem .witem { border-left-color: var(--bad); }
.wgroup.check .witem { border-left-color: var(--caution); }
.wgroup.added .witem { border-left-color: var(--accent); }
.wgroup.note .witem { color: var(--dim); }
