/* 3V3 — chrome styles.
 *
 * Owns: everything outside the canvas. The canvas draws itself (render.js).
 *
 * The :root block below is copied verbatim from the brand handoff
 * (c42 branding/handoff/tokens.css v1) so the app carries its own tokens and
 * never depends on the branding canvas at runtime. Never write a raw hex below
 * this block — if a colour is missing, it belongs in the palette or it doesn't
 * belong at all.
 */

/* ─────────────────────────── the typeface ───────────────────────────
 *
 * IBM Plex for the interface and Chakra Petch 600 for display and headings
 * (the 3v3.dev brand identity v1), served from this box, because
 * the product promise is a tool that works on a LAN with no internet — so the
 * files ship with the app rather than coming from a CDN that isn't there.
 *
 * Latin subset only (the Google Fonts `latin` unicode-range): 91 KB for all
 * seven weights. The four state glyphs ✓ ▲ ✕ ○ sit outside that range and come
 * from the system font, exactly as they did before — Plex has no opinion about
 * them either.
 *
 * Sans is one variable file covering 400–700; Mono is three static weights.
 * `font-display: swap` so text is readable from the first frame.
 */
@font-face {
  font-family: "IBM Plex Sans";
  font-style: normal;
  font-weight: 400 700;
  font-stretch: 100%;
  font-display: swap;
  src: url("/assets/fonts/ibm-plex-sans-latin-var.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/fonts/ibm-plex-mono-latin-400.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("/assets/fonts/ibm-plex-mono-latin-500.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("/assets/fonts/ibm-plex-mono-latin-600.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Chakra Petch";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("/assets/fonts/chakra-petch-latin-600.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ── design tokens — dark-first, 3v3.dev brand identity v1 (green board) ── */
:root {
  /* surfaces */
  --v3-substrate: #0D0D0C;
  --v3-canvas: #080807;
  --v3-panel: #161614;
  --v3-panel-2: #1d1d1a;
  --v3-line: #2b2a26;
  /* text */
  --v3-ink: #F4F3EE;
  --v3-dim: #aeada4;
  --v3-mute: #78776f;
  /* accents */
  --v3-green: #2F7A4F;          /* structure + go + primary buttons */
  --v3-green-soft: rgba(47,122,79,.20);
  --v3-copper: #C97B4B;         /* interactive */
  --v3-copper-soft: rgba(201,123,75,.15);
  --v3-copper-bright: #e08e5a;  /* live route preview only */
  --v3-gold: #D8B25E;           /* achievement only */
  /* status (always pair with glyphs: red ✕, amber ▲, green ✓) */
  --v3-red: #e5484d;
  --v3-amber: #e2a33c;
  /* on-fill text */
  --v3-on-green: #F4F3EE;
  --v3-on-copper: #121110;
  --v3-on-amber: #241a06;
  /* board render (canvas only) */
  --v3-mask: #1D5236;
  --v3-pad: #D8B25E;
  --v3-silk: #F4F3EE;
  --v3-tin: #94a0ab;
  --v3-ratsnest: #8fa3b0;
  /* type */
  --v3-font-ui: "IBM Plex Sans", system-ui, sans-serif;
  --v3-font-mono: "IBM Plex Mono", ui-monospace, monospace;
  /* display and headings — the 3v3.dev brand identity v1 */
  --v3-font-display: "Chakra Petch", "IBM Plex Sans", system-ui, sans-serif;
  /* geometry */
  --v3-radius-chip: 4px;
  --v3-radius-input: 6px;
  --v3-radius-card: 8px;
  --v3-topbar-h: 48px;
  --v3-toolbar-w: 44px;
  --v3-statusbar-h: 28px;
  /* motion */
  --v3-ease: cubic-bezier(0, 0, .2, 1);
  --v3-dur-fast: 120ms;
  --v3-dur: 180ms;
  /* focus */
  --v3-focus-ring: 2px solid #C97B4B;
}

/* ─────────────────── a note on `mute`, and where it may go ───────────────────
 *
 * MEASURED (WCAG 2.1 relative luminance):
 *
 *   mute #78776f on panel   #161614 → 4.03:1     ← under the ≥4.5:1 for text
 *   mute #78776f on panel-2 #1d1d1a → 3.75:1     ← under
 *   dim  #aeada4 on panel   #161614 → 8.04:1     ← passes
 *   dim  #aeada4 on panel-2 #1d1d1a → 7.49:1     ← passes
 *   ink  #F4F3EE on panel   #161614 → 16.31:1
 *   silkscreen #F4F3EE on trace green #2F7A4F → 4.70:1 (primary buttons)
 *   carbon #121110 on copper #C97B4B → 5.76:1, on ENIG gold #D8B25E → 9.39:1
 *
 * Two ways out: lighten the token, or stop using it for essential text.
 *
 * WE MOVED THE TEXT, not the token. BRAND §3 fixes `mute` at #78776f and tells
 * agents to treat every value there as a constraint; BRAND §7 then says in its
 * own words that "mute is for non-essential only". So the palette was never
 * wrong — the app was, by painting the whole status bar, every section label,
 * every chip and count, every step label and every row description in a colour
 * the brand reserves for hints. All of that is now `dim`, which passes on both
 * panel surfaces with room to spare.
 *
 * `mute` survives in exactly three jobs, none of them essential text:
 *   · disabled controls (a slider thumb, a dimmed affordance);
 *   · 1px borders and small graphics, where the bar is 3:1 and 4.03 clears it;
 *   · the one genuinely decorative glyph — the "×" between width and height.
 *
 * If you add a `mute` rule, ask whether a maker who cannot read it has lost
 * information. If yes, it is `dim`.
 */

/* app-local additions, derived from the tokens above */
:root {
  --v3-strip-h: 62px;
  --v3-panel-w: clamp(280px, 25vw, 380px);
  --v3-red-soft: rgba(229, 72, 77, .10);
  --v3-amber-soft: rgba(226, 163, 60, .11);
  --v3-gold-soft: rgba(216, 178, 94, .10);
  --v3-tint-error: #2a1618;
}

/* ─────────────────────── the boot screen ───────────────────────
 * index.html shows this while the modules load, and removes it when the app
 * is alive. If the server never answers, the error line is all the maker sees
 * — so it has to be legible on its own.
 */
.boot {
  position: fixed;
  inset: 0;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: 10px;
  background: var(--v3-substrate);
  color: var(--v3-dim);
  font-family: var(--v3-font-ui);
  font-size: 12px;
  text-align: center;
  padding: 24px;
}
.boot p { margin: 0; }
.boot-err {
  max-width: 46ch;
  color: var(--v3-ink);
  line-height: 1.55;
}
.boot-err[hidden] { display: none; }

/* ─────────────────────────── base ─────────────────────────── */

* { box-sizing: border-box; }

html, body {
  height: 100%;
  margin: 0;
}

body {
  background: var(--v3-substrate);
  color: var(--v3-ink);
  font-family: var(--v3-font-ui);
  font-size: 12px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  overflow: hidden;
}

button, input, select, textarea { font: inherit; color: inherit; }

:focus-visible {
  outline: var(--v3-focus-ring);
  outline-offset: 2px;
  border-radius: 3px;
}

::selection { background: var(--v3-copper-soft); }

.v3-mono { font-family: var(--v3-font-mono); font-variant-numeric: tabular-nums; }

.v3-sr {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

.v3-label {
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--v3-dim);
}

.v3-dim { color: var(--v3-dim); }
.v3-mute { color: var(--v3-mute); }

/* scrollbars, quietly */
.v3-scroll { overflow-y: auto; scrollbar-width: thin; scrollbar-color: var(--v3-line) transparent; }
.v3-scroll::-webkit-scrollbar { width: 10px; }
.v3-scroll::-webkit-scrollbar-thumb { background: var(--v3-line); border-radius: 6px; border: 3px solid transparent; background-clip: content-box; }

/* ─────────────────────────── buttons ─────────────────────────── */

.v3-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  height: 28px;
  min-width: 24px;
  padding: 0 10px;
  border: 1px solid var(--v3-line);
  border-radius: 5px;
  background: transparent;
  color: var(--v3-dim);
  font-size: 11.5px;
  font-weight: 500;
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--v3-dur-fast) var(--v3-ease),
              color var(--v3-dur-fast) var(--v3-ease),
              border-color var(--v3-dur-fast) var(--v3-ease);
}
.v3-btn:hover:not(:disabled) { background: var(--v3-panel-2); color: var(--v3-ink); }
.v3-btn:disabled { opacity: .45; cursor: default; }

.v3-btn--primary {
  background: var(--v3-green);
  border-color: var(--v3-green);
  color: var(--v3-on-green);
  font-weight: 600;
}
.v3-btn--primary:hover:not(:disabled) { background: var(--v3-green); color: var(--v3-on-green); filter: brightness(1.08); }

.v3-btn--copper {
  background: var(--v3-copper);
  border-color: var(--v3-copper);
  color: var(--v3-on-copper);
  font-weight: 600;
}
.v3-btn--copper:hover:not(:disabled) { background: var(--v3-copper); color: var(--v3-on-copper); filter: brightness(1.08); }

/* BRAND §5: button radius is 4–5px, at every size. A big button is a bigger
 * button, not a rounder one. */
.v3-btn--big { height: 34px; padding: 0 16px; font-size: 13.5px; border-radius: 5px; }
.v3-btn--wide { width: 100%; }
.v3-btn--icon { width: 28px; padding: 0; }
.v3-btn--icon svg { width: 15px; height: 15px; }

.v3-link {
  background: none;
  border: 0;
  padding: 0;
  color: var(--v3-copper);
  font-size: 11.5px;
  font-weight: 500;
  text-align: left;
  cursor: pointer;
  border-radius: 3px;
}
.v3-link:hover { color: var(--v3-copper-bright); text-decoration: underline; }

.v3-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  height: 18px;
  padding: 0 6px;
  /* Chips are usually a <span>, but the signature chip on ① Board is a
     <button>, and a button with no background of its own falls back to the
     UA's `buttonface` — a near-white pill. Dim text on that measured 2.43:1,
     so the one chip that reports whether the 3V3 mark is going out on the
     board was the one chip a maker could not read. Say the background. */
  appearance: none;
  background: transparent;
  border: 1px solid var(--v3-line);
  border-radius: var(--v3-radius-chip);
  font-family: var(--v3-font-mono);
  font-size: 9.5px;
  color: var(--v3-dim);
  white-space: nowrap;
}

.v3-seg {
  display: inline-flex;
  border: 1px solid var(--v3-line);
  border-radius: 5px;
  overflow: hidden;
}
.v3-seg > button {
  height: 26px;
  min-width: 34px;
  padding: 0 8px;
  border: 0;
  background: transparent;
  color: var(--v3-dim);
  font-size: 11px;
  font-family: var(--v3-font-mono);
  cursor: pointer;
}
.v3-seg > button + button { border-left: 1px solid var(--v3-line); }
.v3-seg > button:hover { background: var(--v3-panel-2); color: var(--v3-ink); }
.v3-seg > button[aria-pressed="true"] {
  background: var(--v3-copper-soft);
  color: var(--v3-copper);
}

/* ─────────────────────────── inputs ─────────────────────────── */

.v3-field { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.v3-field > label { font-size: 10px; color: var(--v3-dim); }

.v3-input {
  height: 28px;
  padding: 0 8px;
  border: 1px solid var(--v3-line);
  border-radius: var(--v3-radius-input);
  background: var(--v3-panel-2);
  color: var(--v3-ink);
  font-size: 11.5px;
  min-width: 0;
  width: 100%;
}
.v3-input.v3-mono { font-size: 11px; }
.v3-input:hover { border-color: var(--v3-mute); }
.v3-input::placeholder { color: var(--v3-dim); }

.v3-row { display: flex; gap: 8px; align-items: flex-end; }
.v3-row--wrap { flex-wrap: wrap; }

/* ─────────────────────────── app frame ─────────────────────────── */

/* Five rows, and the second is `auto` on purpose: it is the sync bar (D-11),
 * which is `hidden` — and so absent from the grid — every moment everything is
 * saved. Each child names its own row for exactly that reason: a display:none
 * item is removed from the grid entirely, so auto-placement would slide the
 * strip, the body and the status bar up a row the moment the bar went away. */
.v3-app {
  display: grid;
  grid-template-rows: var(--v3-topbar-h) auto var(--v3-strip-h) minmax(0, 1fr) var(--v3-statusbar-h);
  height: 100vh;
  height: 100dvh;
}
.v3-app > .v3-top { grid-row: 1; }
.v3-app > .v3-sync { grid-row: 2; }
.v3-app > .v3-strip { grid-row: 3; }
.v3-app > .v3-body { grid-row: 4; }
.v3-app > .v3-status { grid-row: 5; }
.v3-app[hidden] { display: none; }

.v3-body {
  display: grid;
  grid-template-columns: var(--v3-toolbar-w) minmax(0, 1fr) var(--v3-panel-w);
  min-height: 0;
}

/* ─────────────────────────── top bar ─────────────────────────── */

.v3-top {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 10px;
  background: var(--v3-panel);
  border-bottom: 1px solid var(--v3-line);
}

/* The lockup, to BRAND §2's two hard numbers.
 *
 * Minimum width 90px, and clearspace of one pad diameter (20/96 of the mark's
 * height) on all sides. The mark is 23px high here, so a pad is 23 × 20/96 =
 * 4.8px and the padding below is 6px — the button used to give it 2px on the
 * left and the lockup measured 87.4px across, under both. The mark and
 * wordmark are a little larger so the lockup clears 90px on its own rather
 * than being padded out to it. Measured after the change: 92px, 6px of
 * clearspace on the sides and 5.5px above and below. */
.v3-lockup {
  display: inline-flex;
  align-items: center;
  gap: 9px;                        /* ≈0.4 × mark height, per §2 */
  height: 34px;
  padding: 0 6px;                  /* ≥ one pad diameter (4.8px) each side */
  border: 0;
  border-radius: 5px;
  background: transparent;
  cursor: pointer;
}
.v3-lockup:hover { background: var(--v3-panel-2); }
/* The wordmark at 18px cap height: its own clear space (the x-height, 60 of
 * 150 units) is the button's padding and the bar around it. */
.v3-lockup .v3-wordmark-svg { height: 20px; width: auto; display: block; color: var(--v3-ink); }

.v3-top__name {
  height: 28px;
  max-width: 30ch;
  padding: 0 8px;
  border: 1px solid transparent;
  border-radius: 5px;
  background: transparent;
  color: var(--v3-ink);
  font-size: 13.5px;
  font-weight: 500;
  text-align: left;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  cursor: text;
}
.v3-top__name:hover { border-color: var(--v3-line); background: var(--v3-panel-2); }

.v3-top__spacer { flex: 1 1 auto; }

.v3-save {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 10.5px;
  color: var(--v3-dim);
  font-family: var(--v3-font-mono);
}
.v3-save::before {
  content: "";
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--v3-mute);
}
.v3-save[data-state="saved"]::before { background: var(--v3-green); }
.v3-save[data-state="saving"]::before { background: var(--v3-amber); }
.v3-save[data-state="offline"]::before { background: var(--v3-red); }

.v3-menu-wrap { position: relative; }
.v3-menu {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 40;
  min-width: 200px;
  padding: 4px;
  background: var(--v3-panel);
  border: 1px solid var(--v3-line);
  border-radius: var(--v3-radius-card);
  box-shadow: 0 12px 32px rgba(0,0,0,.45);
}
.v3-menu button {
  display: flex;
  align-items: baseline;
  gap: 8px;
  width: 100%;
  padding: 7px 9px;
  border: 0;
  border-radius: 5px;
  background: transparent;
  color: var(--v3-ink);
  font-size: 11.5px;
  text-align: left;
  cursor: pointer;
}
.v3-menu button:hover { background: var(--v3-panel-2); }
.v3-menu button span { color: var(--v3-dim); font-size: 10px; }

/* ─────────────────────── the step strip ─────────────────────── */

.v3-strip {
  display: flex;
  align-items: flex-start;
  justify-content: center;
  gap: 0;
  padding: 8px 12px 0;
  background: var(--v3-panel);
  border-bottom: 1px solid var(--v3-line);
  overflow-x: auto;
  scrollbar-width: none;
}
.v3-strip::-webkit-scrollbar { display: none; }

/* BRAND §5: 2px copper connectors, ".7 done → .2 ahead". `data-behind` means
 * "the step before this one is done" — see renderStrip(); it deliberately does
 * not key off where the maker is standing. */
.v3-strip__conn {
  flex: 0 1 46px;
  min-width: 14px;
  height: 2px;
  margin-top: 9px;
  background: var(--v3-copper);
  opacity: .2;
  transition: opacity var(--v3-dur) var(--v3-ease);
}
.v3-strip__conn[data-behind="true"] { opacity: .7; }

.v3-step {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  width: 62px;
  padding: 0 2px 6px;
  border: 0;
  background: transparent;
  cursor: pointer;
  border-radius: 6px;
}
.v3-step__dot {
  position: relative;
  display: grid;
  place-items: center;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  border: 1.5px solid var(--v3-mute);
  color: var(--v3-mute);
  font-size: 9px;
  line-height: 1;
  transition: box-shadow var(--v3-dur) var(--v3-ease),
              background var(--v3-dur) var(--v3-ease);
}
.v3-step__dot svg { width: 10px; height: 10px; }

.v3-step[data-light="done"] .v3-step__dot {
  background: var(--v3-green);
  border-color: var(--v3-green);
  color: var(--v3-on-green);
}
.v3-step[data-light="inprogress"] .v3-step__dot {
  background: var(--v3-amber);
  border-color: var(--v3-amber);
  color: var(--v3-on-amber);
}
.v3-step[data-light="blocked"] .v3-step__dot {
  background: var(--v3-red);
  border-color: var(--v3-red);
  color: #fff;
}
.v3-step[data-light="untouched"] .v3-step__dot {
  background: transparent;
  border-color: var(--v3-mute);
  color: transparent;
}
.v3-step[data-optional="true"] .v3-step__dot { border-style: dashed; }
.v3-step[data-optional="true"] { opacity: .55; }
.v3-step[data-optional="true"]:hover,
.v3-step[data-optional="true"][aria-current="step"] { opacity: 1; }
.v3-step[data-gated="true"] .v3-step__dot { border-style: solid; color: var(--v3-dim); }

.v3-step[aria-current="step"] .v3-step__dot { box-shadow: 0 0 0 3px var(--v3-ring, rgba(154,156,141,.28)); }
.v3-step[data-light="done"] { --v3-ring: var(--v3-green-soft); }
.v3-step[data-light="inprogress"] { --v3-ring: var(--v3-amber-soft); }
.v3-step[data-light="blocked"] { --v3-ring: var(--v3-red-soft); }

.v3-step__badge {
  position: absolute;
  top: -5px;
  right: -9px;
  min-width: 14px;
  height: 14px;
  padding: 0 3px;
  border-radius: 7px;
  background: var(--v3-panel-2);
  border: 1px solid currentColor;
  color: var(--v3-ink);
  font-family: var(--v3-font-mono);
  font-size: 9px;
  line-height: 12px;
  text-align: center;
}
.v3-step[data-light="blocked"] .v3-step__badge { color: var(--v3-red); }
.v3-step[data-light="inprogress"] .v3-step__badge { color: var(--v3-amber); }

.v3-step__label {
  font-size: 9px;
  letter-spacing: .04em;
  color: var(--v3-dim);
  white-space: nowrap;
}
.v3-step[aria-current="step"] .v3-step__label { color: var(--v3-ink); font-weight: 600; }
.v3-step:hover .v3-step__label { color: var(--v3-ink); }

/* mini strip, on project cards */
.v3-ministrip { display: flex; align-items: center; }
.v3-ministrip__conn { width: 10px; height: 2px; background: var(--v3-copper); opacity: .2; }
.v3-ministrip__conn[data-behind="true"] { opacity: .7; }
.v3-ministrip__dot {
  width: 13px; height: 13px;
  border-radius: 50%;
  border: 1.5px solid var(--v3-mute);
  display: grid; place-items: center;
  font-size: 7px; line-height: 1;
  color: transparent;
}
.v3-ministrip__dot[data-light="done"] { background: var(--v3-green); border-color: var(--v3-green); color: var(--v3-on-green); }
.v3-ministrip__dot[data-light="inprogress"] { background: var(--v3-amber); border-color: var(--v3-amber); color: var(--v3-on-amber); }
.v3-ministrip__dot[data-light="blocked"] { background: var(--v3-red); border-color: var(--v3-red); color: #fff; }

/* ─────────────────────────── toolbar ─────────────────────────── */

.v3-tools {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 8px 0;
  background: var(--v3-panel);
  border-right: 1px solid var(--v3-line);
}
.v3-tool {
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  border: 1px solid transparent;
  border-radius: 6px;
  background: transparent;
  color: var(--v3-dim);
  cursor: pointer;
  transition: background var(--v3-dur-fast) var(--v3-ease), color var(--v3-dur-fast) var(--v3-ease);
}
.v3-tool svg { width: 17px; height: 17px; }
.v3-tool:hover { background: var(--v3-panel-2); color: var(--v3-ink); }
.v3-tool[aria-pressed="true"] {
  background: var(--v3-copper-soft);
  border-color: var(--v3-copper);
  color: var(--v3-copper);
}

/* ─────────────────────────── stage ─────────────────────────── */

.v3-stage {
  position: relative;
  min-width: 0;
  min-height: 0;
  background: var(--v3-canvas);
  overflow: hidden;
}
.v3-stage > canvas {
  display: block;
  width: 100%;
  height: 100%;
  grid-area: board;
}
/* `display: block` above outranks the UA's [hidden] rule, so full-screen 3D
 * has to say it again. */
.v3-stage > canvas[hidden] { display: none; }

/* ── the View control: the docked 3D view, and the back-view badge ──
 *
 * The stage is a grid so the canvas and the 3D view sit side by side rather
 * than one on top of the other (architecture §10: docked, not instead of).
 * `--v3-dock-size` is written by the grip and remembered per project. */
.v3-stage {
  --v3-dock-size: 340px;
  display: grid;
  grid-template-areas: "board";
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: minmax(0, 1fr);
}
.v3-stage[data-show="dock"] {
  grid-template-areas: "board dock";
  grid-template-columns: minmax(0, 1fr) var(--v3-dock-size);
}
.v3-stage[data-show="dock"]:has(> .v3-dock[data-edge="bottom"]) {
  grid-template-areas: "board" "dock";
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: minmax(0, 1fr) var(--v3-dock-size);
}
.v3-stage[data-show="full"] { grid-template-areas: "dock"; }

.v3-dock {
  position: relative;
  grid-area: dock;
  display: grid;
  grid-template-rows: 26px minmax(0, 1fr);
  min-width: 0;
  min-height: 0;
  background: var(--v3-canvas);
  border-left: 1px solid var(--v3-line);
}
.v3-dock[hidden] { display: none; }
.v3-dock[data-edge="bottom"] { border-left: 0; border-top: 1px solid var(--v3-line); }
.v3-dock[data-edge="full"] { border: 0; }

/* The grip: 6px of hit area over the border it sits on. */
.v3-dock__grip {
  position: absolute;
  top: 0;
  bottom: 0;
  left: -3px;
  width: 7px;
  z-index: 2;
  cursor: col-resize;
  background: transparent;
}
.v3-dock__grip:hover,
.v3-dock__grip:focus-visible { background: var(--v3-copper-soft); }
.v3-dock[data-edge="bottom"] > .v3-dock__grip {
  left: 0;
  right: 0;
  top: -3px;
  bottom: auto;
  width: auto;
  height: 7px;
  cursor: row-resize;
}
.v3-dock[data-edge="full"] > .v3-dock__grip { display: none; }

.v3-dock__head {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 0 6px 0 10px;
  background: var(--v3-panel);
  border-bottom: 1px solid var(--v3-line);
  font-size: 10.5px;
  color: var(--v3-dim);
}
/* One line, always. The header gains a button when the view pops out, and a
 * wrapped "3D view" in a 26px row is a broken header, not a tight one. The
 * "popped out" flag is the one thing allowed to shrink away — the away panel
 * below says the same thing at length, and the buttons must never be clipped. */
.v3-dock__head { overflow: hidden; }
.v3-dock__head > :not(.v3-dock__spacer):not(.v3-dock__flag) {
  flex: 0 0 auto;
  white-space: nowrap;
}
.v3-dock__title { font-weight: 600; color: var(--v3-ink); }
.v3-dock__side {
  padding: 1px 5px;
  border: 1px solid var(--v3-line);
  border-radius: var(--v3-radius-chip);
  font-size: 9.5px;
  color: var(--v3-dim);
}
.v3-dock__spacer { flex: 1 1 auto; }
.v3-dock__body { position: relative; min-width: 0; min-height: 0; }

/* ── the 3D view popped out to its own window (architecture §10) ──
 *
 * While it is over there, this panel does NOT draw the same board again: that
 * is a second geometry build and a second paint on the machine that also has to
 * route. It says where the view went instead, and offers the way back. */
.v3-dock__flag {
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 1px 6px;
  border: 1px solid var(--v3-copper);
  border-radius: var(--v3-radius-chip);
  color: var(--v3-copper);
  font-size: 9.5px;
  white-space: nowrap;
}
.v3-dock__flag-glyph { font-size: 10px; line-height: 1; }
.v3-dock__away {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 20px;
  text-align: center;
}
.v3-dock__away-glyph {
  width: 30px;
  height: 30px;
  display: grid;
  place-items: center;
  border: 1px solid var(--v3-copper);
  border-radius: 50%;
  color: var(--v3-copper);
  font-size: 14px;
}
.v3-dock__away-text {
  margin: 0;
  max-width: 30ch;
  color: var(--v3-dim);
  font-size: 11.5px;
  line-height: 1.55;
}
.v3-dock__away .v3-row { justify-content: center; flex-wrap: wrap; gap: 6px; }

/* Which way round you are looking — a label, not a pressed button. Getting this
 * wrong is how a board comes back from the fab mirrored. */
.v3-viewflag {
  position: absolute;
  top: 10px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 3;
  padding: 4px 10px;
  border: 1px solid var(--v3-copper);
  border-radius: var(--v3-radius-chip);
  background: var(--v3-copper-soft);
  color: var(--v3-copper);
  font-family: var(--v3-font-mono);
  font-size: 10px;
  letter-spacing: .04em;
  pointer-events: none;
}
.v3-viewflag[hidden] { display: none; }

/* ── the View panel itself ── */

.v3-viewpanel {
  width: 300px;
  /* As tall as the window allows under the top bar, so the whole control is
   * one glance rather than a scroll. */
  max-height: calc(100vh - var(--v3-topbar-h) - 24px);
  overflow-y: auto;
  padding: 10px;
  scrollbar-width: thin;
  scrollbar-color: var(--v3-line) transparent;
}
.v3-viewpanel__block { display: flex; flex-direction: column; gap: 6px; }
.v3-viewpanel__block + .v3-viewpanel__block { margin-top: 12px; }
.v3-viewpanel__head { display: flex; align-items: baseline; justify-content: space-between; }
.v3-viewpanel .v3-note { margin: 0; }
.v3-seg--wide { display: flex; }
.v3-seg--wide > button { flex: 1 1 0; min-width: 0; }

.v3-viewlist {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--v3-line);
  border-radius: var(--v3-radius-chip);
  overflow: hidden;
}
.v3-viewrow {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 26px;
  padding: 3px 8px;
  color: var(--v3-dim);
  font-size: 11.5px;
  cursor: pointer;
}
.v3-viewrow + .v3-viewrow { border-top: 1px solid var(--v3-line); }
.v3-viewrow:hover { background: var(--v3-panel-2); }
.v3-viewrow[data-on="true"] { color: var(--v3-ink); }
.v3-viewrow__box { width: 13px; height: 13px; accent-color: var(--v3-copper); cursor: pointer; }
/* One line per layer: the name never wraps and never shrinks — with min-width
   0 it shrank under the hint and its text spilled across it — and a long
   hint gives way (it is the one with overflow hidden and an ellipsis). */
.v3-viewrow__name { flex: 0 0 auto; white-space: nowrap; }
.v3-viewrow__hint {
  flex: 0 1 auto;
  margin-left: auto;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  color: var(--v3-mute);
  font-size: 9.5px;
}
/* The layer's own colour, so the list reads the way the board does. */
.v3-swatch {
  flex: 0 0 auto;
  width: 10px;
  height: 10px;
  border-radius: 2px;
  border: 1px solid rgba(0, 0, 0, .35);
}
.v3-viewrow[data-on="false"] .v3-swatch { opacity: .35; }

.v3-linkbtn {
  border: 0;
  background: transparent;
  color: var(--v3-copper);
  font-size: 10.5px;
  font-family: inherit;
  cursor: pointer;
  padding: 2px 4px;
}
.v3-linkbtn:disabled { color: var(--v3-mute); cursor: default; }

/* The status bar's view readout: which way round, and what is hidden. */
.v3-status__view { display: inline-flex; align-items: center; gap: 6px; }
.v3-status__side[data-side="back"] {
  border-color: var(--v3-copper);
  color: var(--v3-copper);
}

/* Which side the work lands on, in ② Parts and ⑤ Route (task #192). The back
 * is drawn in copper for the same reason the status readout is: it is the state
 * a maker forgets they are in, and forgetting it costs a board. */
.v3-sidechip[data-side="back"] {
  border-color: var(--v3-copper);
  color: var(--v3-copper);
}
.v3-stage__hint {
  position: absolute;
  left: 50%;
  bottom: 16px;
  transform: translateX(-50%);
  padding: 6px 10px;
  border: 1px solid var(--v3-line);
  border-radius: var(--v3-radius-chip);
  background: var(--v3-panel-2);
  color: var(--v3-dim);
  font-size: 11px;
  pointer-events: none;
  max-width: 80%;
  text-align: center;
}

/* ─────────────────────────── right panel ─────────────────────────── */

.v3-panel {
  display: flex;
  flex-direction: column;
  min-height: 0;
  background: var(--v3-panel);
  border-left: 1px solid var(--v3-line);
}
.v3-panel__head {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 12px 14px 8px;
}
.v3-panel__title { font-size: 16px; font-weight: 600; margin: 0; }
.v3-panel__detail { font-size: 11px; color: var(--v3-dim); }
.v3-panel__body {
  flex: 1 1 auto;
  min-height: 0;
  padding: 0 14px 16px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
/* The panel scrolls; its sections do not squash. Without this a flex column
 * shrinks its children to fit, and a long teaching panel or a long findings
 * list gets its last line silently clipped instead of scrolled to. */
.v3-panel__body > * { flex: 0 0 auto; }

.v3-section { display: flex; flex-direction: column; gap: 8px; }
.v3-section__head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }

.v3-empty {
  padding: 14px;
  border: 1px dashed var(--v3-line);
  border-radius: var(--v3-radius-card);
  color: var(--v3-dim);
  font-size: 11.5px;
}

.v3-note { font-size: 11px; color: var(--v3-dim); line-height: 1.55; }

/* ─────────────────────────── alignment ───────────────────────────
 *
 * Lining things up is a visual operation, so the control is a row of pictures.
 * The tiles borrow the toolbar's geometry (32px, 6px radius, copper when they
 * are the live one) because they do the same kind of job: one glance, one
 * click, no reading. BRAND §5's "3px soft ring" marks the one just applied —
 * the same affordance the active step node gets — and it comes off as soon as
 * the selection changes, because it describes THIS selection.
 */
.v3-align {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 4px;
  padding: 6px;
  border: 1px solid var(--v3-line);
  border-radius: var(--v3-radius-card);
  background: var(--v3-panel-2);
}
.v3-align__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-width: 32px;
  height: 32px;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 6px;
  background: transparent;
  color: var(--v3-dim);
  font-size: 11px;
  cursor: pointer;
  transition: background var(--v3-dur-fast) var(--v3-ease),
              color var(--v3-dur-fast) var(--v3-ease),
              border-color var(--v3-dur-fast) var(--v3-ease),
              box-shadow var(--v3-dur-fast) var(--v3-ease);
}
.v3-align__btn svg { width: 18px; height: 18px; flex: 0 0 auto; }
.v3-align__btn:hover:not(:disabled) {
  background: var(--v3-copper-soft);
  border-color: var(--v3-line);
  color: var(--v3-copper);
}
.v3-align__btn:disabled { opacity: .38; cursor: default; }
.v3-align__btn[aria-pressed="true"]:not(:disabled) {
  background: var(--v3-copper-soft);
  border-color: var(--v3-copper);
  color: var(--v3-copper);
  box-shadow: 0 0 0 3px var(--v3-copper-soft);
}
/* Six edges on the first row; spreading things out is a different kind of act,
 * so its two tiles take a row of their own and say what they do. */
.v3-align__btn--wide { grid-column: span 3; padding: 0 9px; }
.v3-align__caption { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* Centring on the board is the headline ask, so it gets its own tray: "both
 * ways" full width, copper-tinted, over the two single-axis buttons. */
.v3-align--board { grid-template-columns: repeat(2, 1fr); }
.v3-align--board .v3-align__btn { grid-column: span 1; height: 30px; padding: 0 9px; }
.v3-align--board .v3-align__btn:first-child {
  grid-column: span 2;
  border-color: var(--v3-copper);
  background: var(--v3-copper-soft);
  color: var(--v3-copper);
}
/* Dimmed is not enough on a filled button — it still reads as pressable. */
.v3-align--board .v3-align__btn:first-child:disabled {
  border-color: var(--v3-line);
  background: transparent;
  color: var(--v3-dim);
}

.v3-align__sub { margin-top: 2px; }

/* ── arrange ──────────────────────────────────────────────────────────────
 *
 * The alignment grid's sibling, and deliberately not its rival: the same tray,
 * the same tiles, the same disabled and pressed states, borrowed rather than
 * rewritten so the two can never drift apart. Only the shape of the grid
 * differs — an arrow can be a picture, but "renumber" cannot, so every tile
 * here carries its word and the columns are wide enough to hold one.
 */
.v3-align--verbs { grid-template-columns: repeat(2, 1fr); }
.v3-align--verbs .v3-align__btn {
  grid-column: span 1;
  justify-content: flex-start;
  height: 30px;
  padding: 0 9px;
  gap: 8px;
}
/* Renumber is last and full width, because it is the one tile here that is not
 * about what you picked — it sweeps the whole board. */
.v3-align--verbs .v3-align__btn:last-child { grid-column: span 2; }

/* Copy, cut and paste are one thought and a narrower one, so they share a row
 * under their own heading rather than lengthening the list above. */
.v3-align--clip { grid-template-columns: repeat(3, 1fr); }
.v3-align--clip .v3-align__btn {
  grid-column: span 1;
  height: 28px;
  padding: 0 8px;
  gap: 6px;
}
.v3-align--clip .v3-align__btn svg { width: 15px; height: 15px; }

/* teaching panel */
.v3-teach {
  border: 1px solid var(--v3-line);
  border-radius: var(--v3-radius-card);
  background: var(--v3-panel-2);
  overflow: hidden;
}
.v3-teach__toggle {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 8px 10px;
  border: 0;
  background: transparent;
  color: var(--v3-dim);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: .08em;
  text-transform: uppercase;
  cursor: pointer;
}
.v3-teach__toggle svg { width: 10px; height: 10px; transition: transform var(--v3-dur) var(--v3-ease); }
.v3-teach[data-open="false"] .v3-teach__toggle svg { transform: rotate(-90deg); }
.v3-teach__body { padding: 0 10px 10px; display: flex; flex-direction: column; gap: 6px; }
.v3-teach[data-open="false"] .v3-teach__body { display: none; }
.v3-teach__why { font-size: 11.5px; color: var(--v3-dim); line-height: 1.55; }
.v3-teach__goal { font-size: 12.5px; color: var(--v3-ink); line-height: 1.5; }
.v3-teach__green { font-size: 11.5px; color: var(--v3-dim); line-height: 1.5; }
.v3-teach__green b { color: var(--v3-green); font-weight: 600; }

/* generic list rows */
.v3-list { display: flex; flex-direction: column; gap: 4px; }
.v3-rowitem {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 7px 9px;
  border: 1px solid var(--v3-line);
  border-radius: 6px;
  background: var(--v3-panel-2);
  color: var(--v3-ink);
  text-align: left;
  font-size: 11.5px;
  cursor: pointer;
}
.v3-rowitem:hover { border-color: var(--v3-copper); }
.v3-rowitem[aria-selected="true"] { border-color: var(--v3-copper); background: var(--v3-copper-soft); }
.v3-rowitem__main { flex: 1 1 auto; min-width: 0; }
.v3-rowitem__name { font-family: var(--v3-font-mono); font-size: 11px; color: var(--v3-ink); }
.v3-rowitem__desc { font-size: 10.5px; color: var(--v3-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.v3-group { display: flex; flex-direction: column; gap: 5px; }
.v3-group__head { display: flex; align-items: center; justify-content: space-between; }

/* ─────────────────────── step ① : the board panel ───────────────────────
 *
 * The first decision a maker makes, so it is drawn as a set of choices rather
 * than a set of fields: four shape tiles, presets that say what they are for,
 * a radius you can drag, and the mounting holes listed one per row.
 */

/* what the board actually is, right now */
.v3-summary {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 8px 10px;
  border: 1px solid var(--v3-line);
  border-left: 2px solid var(--v3-green);
  border-radius: var(--v3-radius-card);
  background: var(--v3-panel-2);
}
.v3-summary__label {
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--v3-dim);
  flex: 0 0 auto;
}
.v3-summary__text { font-size: 11px; color: var(--v3-ink); line-height: 1.5; }

/* the shape chooser */
.v3-shapes {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 6px;
}
.v3-shape {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  padding: 9px 6px 7px;
  border: 1px solid var(--v3-line);
  border-radius: var(--v3-radius-card);
  background: var(--v3-panel-2);
  color: var(--v3-dim);
  cursor: pointer;
  transition: border-color var(--v3-dur-fast) var(--v3-ease),
              color var(--v3-dur-fast) var(--v3-ease),
              background var(--v3-dur-fast) var(--v3-ease);
}
.v3-shape:hover { border-color: var(--v3-copper); color: var(--v3-ink); }
.v3-shape[aria-pressed="true"] {
  border-color: var(--v3-copper);
  background: var(--v3-copper-soft);
  color: var(--v3-copper);
}
.v3-shape__pic { width: 46px; height: 32px; display: block; }
.v3-shape__pic rect, .v3-shape__pic circle, .v3-shape__pic path { stroke-width: 1.6; }
.v3-shape__name { font-size: 10px; line-height: 1.3; text-align: center; }
.v3-shape__tick {
  position: absolute;
  top: 4px;
  right: 6px;
  font-size: 9px;
  line-height: 1;
  color: var(--v3-copper);
}

/* size */
.v3-row__x { color: var(--v3-mute); font-size: 11px; padding-bottom: 7px; }

.v3-presets { display: flex; flex-direction: column; gap: 4px; }
.v3-preset {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  width: 100%;
  padding: 6px 9px;
  border: 1px solid var(--v3-line);
  border-radius: 6px;
  background: var(--v3-panel-2);
  color: var(--v3-ink);
  text-align: left;
  cursor: pointer;
}
.v3-preset:hover { border-color: var(--v3-copper); }
.v3-preset__size { font-size: 10.5px; white-space: nowrap; }
.v3-preset__why {
  font-size: 10.5px;
  color: var(--v3-dim);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* corner radius: a slider and a box, two views of one number */
.v3-row--slider { align-items: center; gap: 10px; }
.v3-range__value { font-size: 10.5px; color: var(--v3-dim); }
.v3-range__input { width: 68px; flex: 0 0 auto; text-align: right; }

.v3-range {
  flex: 1 1 auto;
  min-width: 0;
  height: 24px;
  padding: 0;
  background: transparent;
  -webkit-appearance: none;
  appearance: none;
  cursor: pointer;
}
.v3-range::-webkit-slider-runnable-track {
  height: 3px;
  border-radius: 2px;
  background: var(--v3-line);
}
.v3-range::-moz-range-track {
  height: 3px;
  border-radius: 2px;
  background: var(--v3-line);
}
.v3-range::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 14px;
  height: 14px;
  margin-top: -5.5px;
  border: 1px solid var(--v3-copper);
  border-radius: 50%;
  background: var(--v3-copper);
}
.v3-range::-moz-range-thumb {
  width: 12px;
  height: 12px;
  border: 1px solid var(--v3-copper);
  border-radius: 50%;
  background: var(--v3-copper);
}
.v3-range:disabled { cursor: default; opacity: .4; }
.v3-range:disabled::-webkit-slider-thumb { background: var(--v3-mute); border-color: var(--v3-mute); }
.v3-range:disabled::-moz-range-thumb { background: var(--v3-mute); border-color: var(--v3-mute); }
.v3-input:disabled { opacity: .45; cursor: default; }

/* screw sizes */
.v3-screws { display: flex; flex-direction: column; gap: 4px; }
.v3-screw {
  display: flex;
  align-items: baseline;
  gap: 8px;
  width: 100%;
  padding: 6px 9px;
  border: 1px solid var(--v3-line);
  border-radius: 6px;
  background: var(--v3-panel-2);
  color: var(--v3-ink);
  text-align: left;
  cursor: pointer;
}
.v3-screw:hover { border-color: var(--v3-copper); }
.v3-screw[aria-pressed="true"] {
  border-color: var(--v3-copper);
  background: var(--v3-copper-soft);
}
.v3-screw__name { flex: 0 0 auto; font-size: 11px; font-weight: 600; }
.v3-screw[aria-pressed="true"] .v3-screw__name { color: var(--v3-copper); }
.v3-screw__why {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 10.5px;
  color: var(--v3-dim);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.v3-screw__drill { flex: 0 0 auto; font-size: 10px; color: var(--v3-dim); white-space: nowrap; }

/* one row per hole on the board */
.v3-field > .v3-seg { align-self: flex-start; }

.v3-hole {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 52px auto auto auto;
  align-items: center;
  gap: 5px;
  padding: 5px 6px 5px 8px;
  border: 1px solid var(--v3-line);
  border-radius: 6px;
  background: var(--v3-panel-2);
}
.v3-hole:hover { border-color: var(--v3-copper); }
.v3-hole[data-selected="true"] { border-color: var(--v3-copper); background: var(--v3-copper-soft); }
.v3-hole__pick {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  text-align: left;
  cursor: pointer;
}
.v3-hole__pos {
  font-size: 10.5px;
  color: var(--v3-ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.v3-hole__meta { font-size: 9.5px; color: var(--v3-dim); }
.v3-hole__drill { height: 24px; padding: 0 5px; text-align: right; }
.v3-hole__unit { font-size: 9.5px; color: var(--v3-dim); }
.v3-hole__plated {
  height: 24px;
  padding: 0 6px;
  font-size: 10px;
}
.v3-hole__plated[aria-pressed="true"] {
  border-color: var(--v3-copper);
  color: var(--v3-copper);
}
.v3-hole__del { width: 24px; height: 24px; font-size: 10px; }
.v3-hole__del:hover:not(:disabled) { border-color: var(--v3-red); color: var(--v3-red); }

/* findings */
.v3-finding {
  display: grid;
  grid-template-columns: 16px 1fr;
  gap: 4px 8px;
  padding: 9px 10px;
  border: 1px solid var(--v3-line);
  border-radius: var(--v3-radius-card);
  background: var(--v3-panel-2);
}
.v3-finding[data-severity="error"] { border-color: var(--v3-red); background: var(--v3-red-soft); }
.v3-finding[data-severity="warning"] { border-color: rgba(226,163,60,.45); }
.v3-finding[data-source="aireview"] { border-color: rgba(211,169,79,.5); background: var(--v3-gold-soft); }
/* Every row of copy belongs in the second column. Auto-placement alone only
 * worked while a card had exactly three rows: the dot spans three rows of
 * column 1, so a fourth child — the AI-review footer, or the disclosure added
 * for the two-line clamp — dropped into the 16px column and set its sentence
 * one word per line. Pinning the column makes the card take any number of rows. */
.v3-finding > * { grid-column: 2; min-width: 0; }
.v3-finding__dot {
  grid-column: 1;
  grid-row: 1 / span 3;
  width: 16px; height: 16px;
  margin-top: 1px;
  border-radius: 50%;
  display: grid; place-items: center;
  font-size: 9px;
  background: var(--v3-line);
  color: var(--v3-ink);
}
.v3-finding[data-severity="error"] .v3-finding__dot { background: var(--v3-red); color: #fff; }
.v3-finding[data-severity="warning"] .v3-finding__dot { background: var(--v3-amber); color: var(--v3-on-amber); }
.v3-finding[data-source="aireview"] .v3-finding__dot { background: var(--v3-gold); color: var(--v3-on-amber); }
.v3-finding__head { display: flex; align-items: baseline; gap: 8px; }
.v3-finding__title { flex: 1 1 auto; font-size: 12px; font-weight: 600; line-height: 1.35; }
.v3-finding__src { font-family: var(--v3-font-mono); font-size: 9.5px; color: var(--v3-dim); white-space: nowrap; }
/* BRAND §5: a two-line explanation. The checks write three or four sentences,
 * so the card shows the first two lines — always the what and the why — and
 * "The rest of the why" unfolds the remainder. The fix below is never clamped. */
.v3-finding__why {
  font-size: 11px;
  color: var(--v3-dim);
  line-height: 1.5;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.v3-finding[data-full="true"] .v3-finding__why {
  display: block;
  -webkit-line-clamp: none;
  line-clamp: none;
  overflow: visible;
}
/* A 16px-tall button is a miss waiting to happen: the row keeps its size and
   the target gets the 24px every other control here has. */
.v3-finding__more {
  justify-self: stretch;
  font-size: 10.5px;
  line-height: 1.5;
  min-height: 24px;
  display: flex;
  align-items: center;
}
.v3-finding__fix { justify-self: stretch; line-height: 1.5; }
/* BRAND §9: the disclaimer is a product requirement, not fine print. It is the
 * last row of every AI card — italic, outside the two-line clamp, and separated
 * by a rule so it reads as the card's own framing rather than more of the copy. */
.v3-finding__ai {
  margin: 2px 0 0;
  padding-top: 5px;
  border-top: 1px solid rgba(211, 169, 79, .28);
  font-size: 10.5px;
  color: var(--v3-dim);
  font-style: italic;
  line-height: 1.5;
}

/* ── the AI review block (architecture §9, BRAND §5) ──
 *
 * Gold, because it is a different KIND of finding, not a different severity:
 * red is an error that stops you, amber is a warning that might, gold is an
 * opinion that never does. The block is framed rather than filled so the gold
 * tint stays on the cards, where BRAND §5 puts it, instead of doubling up. */
.v3-ai {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 10px;
  border: 1px solid rgba(211, 169, 79, .38);
  border-radius: var(--v3-radius-card);
}
.v3-ai__head { align-items: center; justify-content: flex-start; gap: 8px; }
.v3-ai__head .v3-label { color: var(--v3-gold); }
.v3-ai__never {
  margin-left: auto;
  font-family: var(--v3-font-mono);
  font-size: 9.5px;
  color: var(--v3-dim);
}
.v3-ai__note {
  display: flex;
  gap: 8px;
  align-items: flex-start;
  padding: 9px 10px;
  border: 1px solid var(--v3-line);
  border-radius: var(--v3-radius-card);
  background: var(--v3-panel-2);
}
.v3-ai__note-glyph {
  flex: 0 0 auto;
  width: 16px; height: 16px;
  margin-top: 1px;
  display: grid; place-items: center;
  border-radius: 50%;
  background: var(--v3-line);
  color: var(--v3-ink);
  font-size: 9px;
}
.v3-ai__note-text {
  margin: 0;
  font-size: 11px;
  color: var(--v3-dim);
  line-height: 1.55;
}
/* The one gold button in the app. Achievement is gold's job in BRAND §3, and an
 * AI review is the only place the maker asks for judgment rather than a rule. */
.v3-btn--gold {
  border-color: rgba(211, 169, 79, .55);
  color: var(--v3-gold);
}
.v3-btn--gold:hover:not(:disabled) {
  background: var(--v3-gold-soft);
  border-color: var(--v3-gold);
  color: var(--v3-gold);
}
.v3-btn--gold[aria-busy="true"] { opacity: 1; color: var(--v3-dim); }
.v3-ai__spin { font-size: 9px; line-height: 1; animation: v3-ai-pulse 1.4s ease-in-out infinite; }
.v3-ai__spin[hidden] { display: none; }
@keyframes v3-ai-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .3; } }
.v3-ai__wait {
  margin: 0;
  font-size: 10.5px;
  color: var(--v3-dim);
  line-height: 1.5;
}
.v3-ai__foot {
  margin: 0;
  padding-top: 7px;
  border-top: 1px solid var(--v3-line);
  font-size: 10.5px;
  color: var(--v3-dim);
  line-height: 1.5;
}

.v3-allclear {
  padding: 16px;
  border: 1px solid var(--v3-green);
  border-radius: var(--v3-radius-card);
  background: var(--v3-green-soft);
  text-align: center;
}
.v3-allclear__glyph { color: var(--v3-green); font-size: 20px; line-height: 1; }
.v3-allclear__text { margin-top: 6px; font-size: 13.5px; font-weight: 600; }

/* order */
.v3-locked {
  display: flex;
  gap: 10px;
  padding: 14px;
  border: 1px solid var(--v3-line);
  border-radius: var(--v3-radius-card);
  background: var(--v3-panel-2);
  color: var(--v3-dim);
  font-size: 11.5px;
  line-height: 1.5;
}
.v3-locked svg { width: 18px; height: 18px; flex: 0 0 auto; color: var(--v3-dim); }

.v3-steps-ol { margin: 0; padding-left: 18px; display: flex; flex-direction: column; gap: 7px; }
.v3-steps-ol li { font-size: 11.5px; color: var(--v3-dim); line-height: 1.55; }
.v3-steps-ol li b { color: var(--v3-ink); font-weight: 600; }

/* build */
.v3-check {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 7px 9px;
  border: 1px solid var(--v3-line);
  border-radius: 6px;
  background: var(--v3-panel-2);
}
.v3-check[data-done="true"] { border-color: var(--v3-green); background: var(--v3-green-soft); }
.v3-check__box {
  flex: 0 0 auto;
  width: 24px; height: 24px;
  display: grid; place-items: center;
  border: 1px solid var(--v3-line);
  border-radius: 5px;
  background: transparent;
  color: transparent;
  cursor: pointer;
  font-size: 11px;
}
.v3-check[data-done="true"] .v3-check__box { background: var(--v3-green); border-color: var(--v3-green); color: var(--v3-on-green); }
.v3-check__main { flex: 1 1 auto; min-width: 0; text-align: left; background: none; border: 0; cursor: pointer; color: inherit; padding: 0; }
.v3-check__refdes { font-family: var(--v3-font-mono); font-size: 11.5px; font-weight: 600; }
.v3-check__meta { font-size: 10.5px; color: var(--v3-dim); }
.v3-check[data-done="true"] .v3-check__refdes { color: var(--v3-dim); text-decoration: line-through; }

.v3-bands { display: inline-flex; align-items: center; gap: 2px; height: 14px; margin-left: 6px; vertical-align: -2px; }
.v3-bands i { display: block; width: 4px; height: 12px; border-radius: 1px; border: 1px solid rgba(0,0,0,.35); }

.v3-progressbar { height: 4px; border-radius: 2px; background: var(--v3-line); overflow: hidden; }
.v3-progressbar > i { display: block; height: 100%; background: var(--v3-green); transition: width var(--v3-dur) var(--v3-ease); }

.v3-banner {
  padding: 14px;
  border: 1px solid var(--v3-gold);
  border-radius: var(--v3-radius-card);
  background: var(--v3-gold-soft);
  text-align: center;
}
.v3-banner__title { font-size: 13.5px; font-weight: 600; color: var(--v3-gold); }
.v3-banner__sub { font-size: 11.5px; color: var(--v3-dim); margin-top: 4px; }

/* ─────────────────────────── status bar ─────────────────────────── */

.v3-status {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 0 12px;
  background: var(--v3-panel);
  border-top: 1px solid var(--v3-line);
  font-family: var(--v3-font-mono);
  font-size: 10px;
  /* The readout — where the cursor is, how far in, which layer, how wide the
   * track. A maker reads it mid-gesture, so it is the last text in the app
   * that may be hard to read. Values in ink, labels in dim: the hierarchy is
   * kept with weight and lightness inside the passing range, not by dropping
   * the values below it. */
  color: var(--v3-ink);
}
.v3-status b { color: var(--v3-dim); font-weight: 500; }
.v3-status__spacer { flex: 1 1 auto; }

/* The bar must not move while it is read. The coordinate readout changes on
 * every pointer move, and in a flex row a value that gets one character wider
 * pushes everything after it along — so the bar appeared to squirm whenever
 * the cursor moved, and to lurch when the pointer left the canvas and the
 * numbers collapsed to dashes (board task #195).
 *
 * Two things hold it still: figures of equal width, so 1 and 8 cost the same,
 * and a floor under each readout wide enough for the longest value it can
 * hold. The floor is a minimum rather than a fixed size, so an absurd zoom or
 * a board measured in inches still fits — it grows rather than truncating. */
.v3-status { font-variant-numeric: tabular-nums; }
.v3-status__pos {
  display: inline-block;
  /* The readout is padded to a fixed width in `cursorReadout`, and this is
   * what keeps the padding: without `pre` the browser collapses the spaces and
   * the box goes back to being content-sized. The floor matches the padded
   * string — two coordinates of eight characters, a comma, a space and the
   * unit — so nothing here can push the four readouts after it along. */
  white-space: pre;
  min-width: 22ch;
}

/* The other two readouts that change under the maker's hand. Same rule: a box
 * wide enough for the longest value it can hold, so changing it changes
 * nothing else on the row. */
.v3-status__layer {
  display: inline-block;
  min-width: 17ch;   /* "Bottom soldermask" */
}
.v3-status__track {
  display: inline-block;
  min-width: 9ch;    /* "39.37 mil" */
}
.v3-status__zoomval {
  display: inline-block;
  min-width: 5ch;    /* "1000%" */
  text-align: right;
}

/* The way back, beside the number that says you are lost. Two small buttons —
 * hit targets are 24px per BRAND §7, which is taller than the 28px bar allows
 * to be drawn, so the box is 20px and the touch area is padded out to 24. */
.v3-status__zoom { display: inline-flex; align-items: center; gap: 6px; }
.v3-zoombtn {
  position: relative;
  height: 20px;
  min-width: 24px;
  padding: 0 7px;
  border: 1px solid var(--v3-line);
  border-radius: 4px;
  background: transparent;
  color: var(--v3-dim);
  font-family: var(--v3-font-mono);
  font-size: 9.5px;
  line-height: 1;
  cursor: pointer;
  transition: background var(--v3-dur-fast) var(--v3-ease),
              color var(--v3-dur-fast) var(--v3-ease),
              border-color var(--v3-dur-fast) var(--v3-ease);
}
.v3-zoombtn::after {                 /* the hit target, ≥24px, invisible */
  content: "";
  position: absolute;
  inset: -2px;
}
.v3-zoombtn:hover { border-color: var(--v3-copper); color: var(--v3-copper); background: var(--v3-copper-soft); }
.v3-status__errors { color: var(--v3-dim); }
.v3-status__errors[data-errors="true"] { color: var(--v3-red); }

/* ─────────────────────────── project list ─────────────────────────── */

.v3-home {
  position: fixed;
  inset: 0;
  z-index: 30;
  background: var(--v3-substrate);
  overflow-y: auto;
}
.v3-home[hidden] { display: none; }
.v3-home__inner {
  max-width: 940px;
  margin: 0 auto;
  padding: 56px 24px 80px;
}
.v3-home__lockup { display: flex; align-items: center; gap: 10px; }
.v3-home__foot { margin-top: 40px; padding-top: 16px; border-top: 1px solid var(--v3-line); color: var(--v3-mute); font-size: 12px; }
.v3-home__foot a { color: var(--v3-dim); }
.v3-home__account { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 8px; color: var(--v3-dim); }

.v3-home__tag { margin-top: 10px; font-size: 13.5px; color: var(--v3-dim); }
.v3-home__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  margin: 34px 0 14px;
}
.v3-home__h2 { font-size: 16px; font-weight: 600; margin: 0; }
/* #1898: "Connect an agent" sits beside "New board", not mid-bar */
.v3-home__bar a.v3-btn { margin-left: auto; margin-right: -6px; text-decoration: none; }

.v3-cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(272px, 1fr));
  gap: 12px;
}
.v3-card {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 14px;
  border: 1px solid var(--v3-line);
  border-radius: var(--v3-radius-card);
  background: var(--v3-panel);
  color: inherit;
  text-align: left;
  cursor: pointer;
  transition: border-color var(--v3-dur-fast) var(--v3-ease), transform var(--v3-dur-fast) var(--v3-ease);
}
.v3-card:hover { border-color: var(--v3-copper); transform: translateY(-1px); }
.v3-card__name { font-size: 13.5px; font-weight: 600; }
.v3-card__meta { display: flex; gap: 10px; font-family: var(--v3-font-mono); font-size: 10px; color: var(--v3-dim); }

.v3-card--new {
  align-items: flex-start;
  justify-content: center;
  gap: 6px;
  border-style: dashed;
  background: transparent;
}
.v3-card--new .v3-card__name { color: var(--v3-copper); }

.v3-firstrun {
  display: grid;
  gap: 14px;
  padding: 34px;
  border: 1px dashed var(--v3-line);
  border-radius: var(--v3-radius-card);
  background: var(--v3-panel);
  text-align: center;
  justify-items: center;
}
.v3-firstrun h3 { margin: 0; font-size: 16px; font-weight: 600; }
.v3-firstrun p { margin: 0; max-width: 46ch; font-size: 12px; color: var(--v3-dim); line-height: 1.6; }

/* ─────────────────────────── overlays ─────────────────────────── */

.v3-scrim {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: grid;
  background: rgba(8, 9, 6, .6);
  backdrop-filter: blur(2px);
}
.v3-scrim[hidden] { display: none; }
.v3-scrim--top { align-content: start; justify-content: center; padding-top: 12vh; }
.v3-scrim--centre { place-content: center; }

.v3-dialog {
  width: min(460px, 92vw);
  padding: 18px;
  border: 1px solid var(--v3-line);
  border-radius: var(--v3-radius-card);
  background: var(--v3-panel);
  box-shadow: 0 24px 60px rgba(0,0,0,.5);
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.v3-dialog h2 { margin: 0; font-size: 16px; font-weight: 600; }
.v3-dialog__actions { display: flex; justify-content: flex-end; gap: 8px; }

/* What a dialog is about to make, and the first reason it cannot yet.
 *
 * The count is ink — it is the sentence the maker is deciding on, not a
 * footnote — with the number itself in copper. The refusal is amber AND
 * carries a ▲, because colour is never the only signal. */
.v3-dialog__count { margin: 0; font-size: 13px; color: var(--v3-ink); line-height: 1.5; }
.v3-dialog__count b { color: var(--v3-copper); font-weight: 600; }
.v3-dialog__why { margin: -6px 0 0; font-size: 11.5px; color: var(--v3-amber); line-height: 1.55; }
.v3-dialog__why[hidden] { display: none; }

/* command palette */
.v3-palette {
  width: min(620px, 94vw);
  border: 1px solid var(--v3-line);
  border-radius: var(--v3-radius-card);
  background: var(--v3-panel);
  box-shadow: 0 24px 60px rgba(0,0,0,.55);
  overflow: hidden;
}
.v3-palette__input {
  width: 100%;
  height: 46px;
  padding: 0 14px;
  border: 0;
  border-bottom: 1px solid var(--v3-line);
  background: transparent;
  color: var(--v3-ink);
  font-size: 14px;
}
.v3-palette__input::placeholder { color: var(--v3-dim); }
.v3-palette__list { max-height: min(52vh, 420px); overflow-y: auto; padding: 6px; }
.v3-palette__item {
  display: flex;
  align-items: baseline;
  gap: 10px;
  width: 100%;
  padding: 8px 10px;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--v3-ink);
  font-size: 12px;
  text-align: left;
  cursor: pointer;
}
.v3-palette__item[aria-selected="true"] { background: var(--v3-copper-soft); }
.v3-palette__kind {
  font-family: var(--v3-font-mono);
  font-size: 9.5px;
  color: var(--v3-dim);
  min-width: 46px;
}
.v3-palette__label { flex: 1 1 auto; }
.v3-palette__hint { color: var(--v3-dim); font-size: 10.5px; }
.v3-palette__empty { padding: 18px 14px; color: var(--v3-dim); font-size: 11.5px; }
.v3-palette__args { padding: 12px 14px 14px; display: flex; flex-direction: column; gap: 10px; }
.v3-palette__args .v3-note { margin-top: -4px; }

/* ── find (⌘F) ──
 *
 * The palette's chassis, because it is the same act: type, walk a list, take
 * one. It differs in one line — a count between the box and the list, so a
 * board with three hundred parts says how many of them matched before the
 * maker scrolls looking for the end. */
.v3-find .v3-palette__list { max-height: min(56vh, 460px); }
.v3-find__meta {
  padding: 4px 14px 6px;
  border-bottom: 1px solid var(--v3-line);
  font-size: 10px;
  color: var(--v3-dim);
}
.v3-find__meta:empty { display: none; }
.v3-find .v3-palette__kind { min-width: 54px; color: var(--v3-ink); }

kbd {
  display: inline-block;
  min-width: 18px;
  padding: 1px 5px;
  border: 1px solid var(--v3-line);
  border-bottom-width: 2px;
  border-radius: 4px;
  background: var(--v3-panel-2);
  font-family: var(--v3-font-mono);
  font-size: 9.5px;
  color: var(--v3-dim);
  text-align: center;
}

/* shortcut sheet */
.v3-sheet { width: min(640px, 94vw); max-height: 80vh; overflow-y: auto; }
.v3-sheet__grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 18px;
}
.v3-sheet__col { display: flex; flex-direction: column; gap: 6px; }
.v3-sheet__row { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; font-size: 11.5px; color: var(--v3-dim); }

/* toasts */
.v3-toasts {
  position: fixed;
  right: 16px;
  bottom: calc(var(--v3-statusbar-h) + 14px);
  z-index: 60;
  display: flex;
  flex-direction: column;
  gap: 8px;
  max-width: min(420px, 80vw);
}
.v3-toast {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  padding: 10px 12px;
  border: 1px solid var(--v3-line);
  border-radius: var(--v3-radius-card);
  background: var(--v3-panel);
  box-shadow: 0 10px 30px rgba(0,0,0,.45);
  font-size: 11.5px;
  line-height: 1.5;
  animation: v3-rise var(--v3-dur) var(--v3-ease);
}
.v3-toast[data-kind="error"] { border-color: var(--v3-red); background: var(--v3-tint-error); }
.v3-toast__glyph { color: var(--v3-dim); font-size: 11px; line-height: 1.45; }
.v3-toast[data-kind="error"] .v3-toast__glyph { color: var(--v3-red); }
.v3-toast__text { flex: 1 1 auto; }
.v3-toast__close {
  flex: 0 0 auto;
  width: 24px; height: 24px;            /* hit targets never go under 24px */
  margin: -3px -4px -3px 0;
  display: grid; place-items: center;
  border: 0; background: none; color: var(--v3-dim);
  cursor: pointer; font-size: 12px; line-height: 1; border-radius: 4px;
}
.v3-toast__close:hover { color: var(--v3-ink); }

@keyframes v3-rise {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: none; }
}

/* confetti */
.v3-confetti {
  position: fixed;
  inset: 0;
  z-index: 70;
  pointer-events: none;
}

/* ═══════════════════════ the project home ═══════════════════════
 *
 * Design handoff §6: the screen a board opens into. Same substrate as the
 * project list, same card radius, same 1px `line` borders — it is a third
 * screen of the same app, not a second app.
 */

.v3-hub {
  position: fixed;
  inset: 0;
  z-index: 28;
  background: var(--v3-substrate);
  overflow-y: auto;
}
.v3-hub[hidden] { display: none; }
.v3-hub__inner {
  max-width: 1080px;
  margin: 0 auto;
  padding: 34px 24px 80px;
}
.v3-hub__head { display: flex; flex-direction: column; gap: 8px; }
/* Every hit target on this screen clears 24px, including the quiet ones. */
.v3-hub .v3-linkbtn,
.v3-hubcard .v3-linkbtn {
  min-height: 24px;
  display: inline-flex;
  align-items: center;
  padding: 2px 6px;
}
.v3-hub__back { align-self: flex-start; }
.v3-hub__titlerow { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }
.v3-hub__name { margin: 0; font-size: 24px; font-weight: 600; line-height: 1.2; }
.v3-hub__spacer { flex: 1 1 auto; }
.v3-hub__meta { font-size: 10.5px; color: var(--v3-dim); margin: 0; }
.v3-hub__desc { margin: 0; font-size: 13px; color: var(--v3-ink); line-height: 1.6; max-width: 62ch; }

.v3-hub__grid {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
  gap: 16px;
  margin-top: 22px;
  align-items: start;
}
.v3-hub__col { display: flex; flex-direction: column; gap: 16px; min-width: 0; }

.v3-hubcard {
  border: 1px solid var(--v3-line);
  background: var(--v3-panel);
  border-radius: var(--v3-radius-card);
  padding: 14px 16px 16px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-width: 0;
}
.v3-hubcard__head { display: flex; align-items: center; gap: 8px; }
.v3-hubcard__title { margin: 0; font-size: 13.5px; font-weight: 600; }
.v3-hubcard__spacer { flex: 1 1 auto; }

.v3-textarea { height: auto; min-height: 64px; padding: 8px 10px; line-height: 1.5; resize: vertical; }
.v3-row--end { justify-content: flex-end; align-items: center; }

/* the nine lights, full size, on their own row */
.v3-strip--hub {
  display: flex;
  align-items: flex-start;
  gap: 0;
  overflow-x: auto;
  padding: 4px 0 2px;
  border: 0;
  background: none;
  height: auto;
}

/* — journal — */
.v3-jadd { display: flex; flex-direction: column; gap: 8px; }
.v3-jlist { display: flex; flex-direction: column; gap: 8px; }
.v3-jentry {
  border: 1px solid var(--v3-line);
  border-left: 3px solid var(--v3-line);
  border-radius: var(--v3-radius-chip);
  background: var(--v3-panel-2);
  padding: 8px 10px;
}
/* A milestone is the server's own line, and it is an earned moment: gold. */
.v3-jentry[data-kind="milestone"] { border-left-color: var(--v3-gold); background: var(--v3-gold-soft); }
.v3-jentry[data-kind="manual"] { border-left-color: var(--v3-copper); }
.v3-jentry__head { display: flex; align-items: baseline; gap: 10px; }
.v3-jentry__tag { font-size: 10px; letter-spacing: .04em; color: var(--v3-dim); }
.v3-jentry[data-kind="milestone"] .v3-jentry__tag { color: var(--v3-gold); }
.v3-jentry__time { font-size: 10px; color: var(--v3-dim); }
.v3-jentry__del { font-size: 10.5px; }
.v3-jentry__body { margin: 4px 0 0; font-size: 12.5px; line-height: 1.6; white-space: pre-wrap; }

.v3-btn--danger { border-color: var(--v3-red); color: var(--v3-red); }
.v3-btn--danger:hover:not(:disabled) { background: var(--v3-red-soft); color: var(--v3-red); }
/* — versions — */
.v3-rev {
  display: flex;
  align-items: center;
  gap: 8px;
  border: 1px solid var(--v3-line);
  border-radius: var(--v3-radius-chip);
  background: var(--v3-panel-2);
  padding: 7px 9px;
}
.v3-rev__main { flex: 1 1 auto; min-width: 0; }
.v3-rev__label { font-size: 12px; color: var(--v3-ink); }
.v3-rev__meta { display: flex; align-items: center; gap: 8px; font-size: 10px; color: var(--v3-dim); }
.v3-rev[data-kind="named"] { border-left: 3px solid var(--v3-copper); }
.v3-rev .v3-btn { min-height: 24px; }

.v3-dialog--wide { width: min(560px, 94vw); }
.v3-dialog__scroll { max-height: 46vh; display: flex; flex-direction: column; gap: 6px; }

/* ═══════════════════════ the sync bar (D-11) ═══════════════════════ */

.v3-sync {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 14px;
  border-bottom: 1px solid var(--v3-line);
  background: var(--v3-panel-2);
  font-size: 11.5px;
  color: var(--v3-ink);
}
.v3-sync[hidden] { display: none; }
.v3-sync__glyph { flex: 0 0 auto; color: var(--v3-dim); }
.v3-sync[data-state="offline"] { background: var(--v3-tint-error); }
.v3-sync[data-state="offline"] .v3-sync__glyph { color: var(--v3-red); }
.v3-sync[data-state="warning"] .v3-sync__glyph { color: var(--v3-amber); }
.v3-sync[data-state="syncing"] .v3-sync__glyph { color: var(--v3-copper); }
.v3-sync__text { flex: 0 1 auto; min-width: 0; }
.v3-sync__spacer { flex: 1 1 auto; }
.v3-sync .v3-btn { min-height: 24px; }

.v3-save__glyph { margin-right: 5px; color: var(--v3-dim); }
.v3-save[data-state="saved"] .v3-save__glyph { color: var(--v3-green); }
.v3-save[data-state="saving"] .v3-save__glyph { color: var(--v3-amber); }
.v3-save[data-state="syncing"] .v3-save__glyph { color: var(--v3-copper); }
.v3-save[data-state="offline"] .v3-save__glyph { color: var(--v3-red); }
.v3-save[data-state="syncing"]::before { background: var(--v3-copper); }
.v3-top__hub { flex: 0 0 auto; }

/* ═══════════════════════ the hover glossary (D-30) ═══════════════════════ */

.v3-gloss {
  text-decoration: none;
  border-bottom: 1px dotted var(--v3-copper);
  cursor: help;
  color: inherit;
}
.v3-gloss:hover { color: var(--v3-ink); border-bottom-style: solid; }
.v3-gloss:focus-visible { outline: var(--v3-focus-ring); outline-offset: 2px; }

.v3-glosstip {
  position: fixed;
  z-index: 80;
  max-width: 300px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 8px 10px;
  border: 1px solid var(--v3-line);
  border-radius: var(--v3-radius-card);
  background: var(--v3-panel-2);
  box-shadow: 0 8px 24px rgba(0,0,0,.45);
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--v3-dim);
  pointer-events: none;
}
.v3-glosstip b { color: var(--v3-ink); font-weight: 600; }
.v3-glosstip__more { color: var(--v3-mute); font-size: 10px; }
.v3-gloss__short { margin: 0; font-size: 13px; color: var(--v3-ink); line-height: 1.55; }
.v3-gloss__body { margin: 0; font-size: 12px; color: var(--v3-dim); line-height: 1.65; }

/* the bring-up checklist reuses the solder row, with a sentence in it. It is
   text, not a button, so it does not borrow the row's pointer cursor. */
.v3-check__text {
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--v3-ink);
  padding: 2px 0;
  cursor: default;
}
.v3-check[data-done="true"] .v3-check__text { color: var(--v3-dim); }

/* ─────────────────── a part from a photograph (#194) ─────────────────── */
/*
 * Framed in gold like the AI review, and for the same reason: this is a model's
 * reading, not a rule's answer, and the frame is what stops it being mistaken
 * for one. It is the only other place in the app where a maker asks for
 * judgment, so it borrows that block's furniture — the frame, the "advice only"
 * tag, the pulse on the button — rather than inventing a second visual language
 * for the same kind of thing.
 */
.v3-partimg {
  display: flex;
  flex-direction: column;
  gap: 9px;
  padding: 10px;
  border: 1px solid rgba(211, 169, 79, .38);
  border-radius: var(--v3-radius-card);
}
.v3-partimg .v3-group__head .v3-label { color: var(--v3-gold); }
.v3-partimg__framing {
  margin: 0;
  font-size: 10.5px;
  color: var(--v3-dim);
  font-style: italic;
}
.v3-partimg__foot {
  margin: 0;
  padding-top: 7px;
  border-top: 1px solid var(--v3-line);
  font-size: 10.5px;
  font-style: italic;
  color: var(--v3-dim);
  line-height: 1.55;
}

/* The paste target. It has to look like somewhere a picture goes, and it has to
 * say so in words — the founder's ask was to paste and have it work, and a
 * target nobody notices is a feature nobody finds. */
.v3-partdrop {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 18px 12px;
  border: 1px dashed var(--v3-line);
  border-radius: var(--v3-radius-card);
  background: var(--v3-panel-2);
  cursor: pointer;
  text-align: center;
  transition: border-color var(--v3-dur-fast) var(--v3-ease),
              background var(--v3-dur-fast) var(--v3-ease);
}
.v3-partdrop:hover,
.v3-partdrop:focus-visible,
.v3-partdrop[data-over="true"] {
  border-color: var(--v3-copper);
  border-style: solid;
  background: var(--v3-panel);
}
.v3-partdrop:focus-visible { outline: var(--v3-focus-ring); outline-offset: 2px; }
.v3-partdrop__key {
  font-family: var(--v3-font-mono);
  font-size: 11px;
  color: var(--v3-copper);
  padding: 2px 7px;
  border: 1px solid var(--v3-line);
  border-radius: var(--v3-radius-chip);
}
.v3-partdrop__lead { margin: 0; font-size: 12.5px; color: var(--v3-ink); font-weight: 500; }
.v3-partdrop__sub { margin: 0; font-size: 10.5px; color: var(--v3-dim); line-height: 1.5; }
.v3-partdrop--silk { padding: 14px 12px; }

/* The picture that is about to be read, so a maker can see they pasted the one
 * they meant before they spend anything on it. */
.v3-partimg__shot { display: flex; gap: 10px; align-items: flex-start; }
.v3-partimg__thumb {
  flex: 0 0 auto;
  width: 76px;
  max-height: 96px;
  object-fit: contain;
  border: 1px solid var(--v3-line);
  border-radius: var(--v3-radius-input);
  background: var(--v3-canvas);
}
.v3-partimg__shotside { display: flex; flex-direction: column; gap: 4px; min-width: 0; }

/* What the spec builds, drawn before anything is added. */
.v3-partfp {
  display: flex;
  flex-direction: column;
  gap: 7px;
  padding: 9px;
  border: 1px solid var(--v3-line);
  border-radius: var(--v3-radius-card);
  background: var(--v3-panel-2);
}
.v3-partfp__head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.v3-partfp__canvas {
  width: 100%;
  display: block;
  border-radius: var(--v3-radius-input);
  background: var(--v3-canvas);
}
.v3-partfp__canvas[hidden] { display: none; }
/* `[hidden]` is a UA rule, and `.v3-chip` sets `display` — which beats it. So
   a hidden pad count left an empty chip outline in the header while the spec
   was refused. Said again, more specifically, or it does not stick. */
.v3-partfp__count[hidden] { display: none; }
/* A refusal from the footprint builder: this spec cannot be a footprint, and
 * the sentence says which number to change. Red, because nothing will be added
 * until it is fixed — but inside the block, not as a toast, because the maker is
 * mid-edit and needs to be able to read it while they type. */
.v3-partfp__why {
  margin: 0;
  font-size: 11px;
  line-height: 1.55;
  color: var(--v3-red);
}
.v3-partfp__why[hidden] { display: none; }

/* One number that came from a default rather than from the picture. */
.v3-partcheck {
  display: flex;
  flex-direction: column;
  gap: 5px;
  padding: 9px;
  border: 1px solid var(--v3-amber-soft);
  border-left: 2px solid var(--v3-amber);
  border-radius: var(--v3-radius-card);
}
.v3-partcheck[data-checked="true"] {
  border-color: var(--v3-line);
  border-left-color: var(--v3-green);
}
.v3-partcheck__head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.v3-partcheck__label { display: flex; gap: 5px; align-items: baseline; font-size: 12px; color: var(--v3-ink); }
.v3-partcheck__unit { font-size: 10px; color: var(--v3-dim); }
.v3-partcheck__from {
  font-family: var(--v3-font-mono);
  font-size: 9.5px;
  color: var(--v3-dim);
  text-align: right;
}
/* A box the maker has moved off what was proposed. Copper is the interactive
 * colour, and this is the one thing on the card that is theirs and not the
 * server's. */
.v3-partcheck[data-edited="true"] .v3-input,
.v3-partgrid__cell[data-edited="true"] .v3-input { border-color: var(--v3-copper); }
.v3-partcheck__why { margin: 0; font-size: 11px; color: var(--v3-dim); line-height: 1.55; }
.v3-partcheck__how { margin: 0; font-size: 10.5px; color: var(--v3-dim); line-height: 1.55; }
.v3-partcheck__dep { margin: 0; font-size: 10.5px; color: var(--v3-amber); line-height: 1.5; }
.v3-partcheck__tickrow { display: flex; align-items: center; gap: 7px; }
.v3-partcheck__ticklabel { font-size: 10.5px; color: var(--v3-dim); cursor: pointer; }
.v3-partcheck__tick { accent-color: var(--v3-green); cursor: pointer; }
/* Two numbers bound by `depends_on` are one answer, and the bracket around them
 * is how that is said without a sentence. */
.v3-partcheck__pair {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 6px;
  border: 1px dashed var(--v3-amber-soft);
  border-radius: var(--v3-radius-card);
}

.v3-partgrid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 7px; }
.v3-partgrid__cell { min-width: 0; }
.v3-partlist { margin: 0; padding-left: 16px; font-size: 11px; color: var(--v3-dim); line-height: 1.6; }
.v3-partdetails { font-size: 11px; color: var(--v3-dim); }
.v3-partdetails summary { cursor: pointer; color: var(--v3-copper); font-size: 11px; }
.v3-partdetails summary:focus-visible { outline: var(--v3-focus-ring); outline-offset: 2px; }

/* "We may already have this." Green, because finding one is a success — the
 * maker wanted a part, not a new footprint. Amber when a caveat is concerning,
 * so the card cannot argue for the wrong part with a reassuring frame. */
.v3-partmatch {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 9px;
  border: 1px solid var(--v3-green);
  border-radius: var(--v3-radius-card);
  background: var(--v3-green-soft);
}
.v3-partmatch[data-concerning="true"] {
  border-color: var(--v3-amber);
  background: var(--v3-amber-soft);
}
.v3-partmatch .v3-label { color: var(--v3-ink); }
.v3-partmatch__name { font-size: 13px; color: var(--v3-ink); }
.v3-partmatch__caveat {
  margin: 0;
  font-size: 11px;
  line-height: 1.55;
  color: var(--v3-dim);
}
.v3-partmatch__caveat[data-concerning="true"] { color: var(--v3-ink); }

/* ─────────────────── artwork on the silkscreen (#196) ─────────────────── */
/*
 * One rule, and it is the whole block: a `skipped` sentence is not a footnote.
 * A maker whose logo lost its text has to read that here, at full size, or they
 * read it off a board in three weeks and it is too late. So these are `ink` on a
 * bordered row, not `dim` in a note — the same weight as anything else that
 * changes what arrives.
 */
.v3-silkskip {
  margin: 0;
  padding: 7px 9px;
  border: 1px solid var(--v3-amber-soft);
  border-left: 2px solid var(--v3-amber);
  border-radius: var(--v3-radius-card);
  font-size: 11px;
  line-height: 1.6;
  color: var(--v3-ink);
}
.v3-silkpaste { resize: vertical; min-height: 44px; font-family: var(--v3-font-mono); font-size: 10.5px; }

/* ═════════════ ⑨ Share, the people on a board, and the lock ═════════════ */
/*
 * The one outward-facing corner of the product, so it is deliberately calm:
 * nothing here is coloured to be exciting, and the only strong colour in the
 * whole block is the red on the button that turns a link off.
 */

/* the band under the top bar: who else is in here, and what you may do */
.v3-notice {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 14px;
  border-bottom: 1px solid var(--v3-line);
  background: var(--v3-panel-2);
  font-size: 11.5px;
  color: var(--v3-ink);
}
.v3-notice[hidden] { display: none; }
.v3-notice__glyph { flex: 0 0 auto; color: var(--v3-dim); }
.v3-notice__text { flex: 0 1 auto; min-width: 0; }
.v3-notice[data-kind="busy"] { background: var(--v3-amber-soft); }
.v3-plan[data-kind="trial"] { background: var(--v3-copper-soft); }
.v3-plan[data-kind="read-only"] { background: var(--v3-red-soft); }
.v3-plan .v3-btn { margin-left: auto; text-decoration: none; }
.v3-home__inner > .v3-plan { border: 1px solid var(--v3-line); border-radius: var(--v3-radius-card); margin: 12px 0; }
.v3-notice[data-kind="busy"] .v3-notice__glyph { color: var(--v3-amber); }
.v3-notice[data-kind="readonly"] .v3-notice__glyph { color: var(--v3-copper); }
.v3-notice .v3-btn { min-height: 24px; }

/* the licence picker. A native select, because eight choices with a sentence
 * each is a list to read, not a grid to admire — and because it is the one
 * control every screen reader and every keyboard already knows. */
.v3-select {
  width: 100%;
  height: 30px;
  padding: 0 8px;
  cursor: pointer;
}
.v3-select:disabled { color: var(--v3-dim); cursor: default; }
.v3-licence__summary { color: var(--v3-dim); }

.v3-share__url {
  width: 100%;
  font-size: 11px;
  overflow-wrap: anywhere;
}

/* what a link shows, and what it does not. The glyph carries the meaning —
 * colour is never the only signal — and the ✕ rows are not a warning, they
 * are the other half of an honest answer. */
.v3-facts {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 5px;
}
.v3-facts__row {
  display: flex;
  align-items: baseline;
  gap: 7px;
  font-size: 11.5px;
  line-height: 1.55;
  color: var(--v3-ink);
}
.v3-facts__glyph { flex: 0 0 auto; width: 12px; text-align: center; }
.v3-facts[data-kind="shows"] .v3-facts__glyph { color: var(--v3-green); }
.v3-facts[data-kind="hides"] .v3-facts__glyph { color: var(--v3-dim); }

/* — the people on a board — */

.v3-person {
  display: flex;
  align-items: center;
  gap: 8px;
  border: 1px solid var(--v3-line);
  border-radius: var(--v3-radius-chip);
  background: var(--v3-panel-2);
  padding: 7px 9px;
  min-width: 0;
}
.v3-person[data-role="owner"] { border-left: 3px solid var(--v3-gold); }
.v3-person__main { flex: 1 1 auto; min-width: 0; }
.v3-person__name { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--v3-ink); }
.v3-person__meta { font-size: 10px; color: var(--v3-dim); }
.v3-person__actions { display: flex; gap: 4px; flex: 0 0 auto; flex-wrap: wrap; justify-content: flex-end; }
.v3-person__actions .v3-btn { min-height: 24px; }
.v3-people__lock { color: var(--v3-ink); }

.v3-invite { display: flex; flex-direction: column; gap: 8px; }

/* the roles table — the one in docs/API.md, so nobody has to guess what an
 * editor is. It scrolls sideways inside its own box on a narrow panel rather
 * than pushing the card wider. */
.v3-roles__wrap { overflow-x: auto; }
.v3-roles {
  border-collapse: collapse;
  width: 100%;
  font-size: 10.5px;
}
.v3-roles th, .v3-roles td {
  border-bottom: 1px solid var(--v3-line);
  padding: 5px 6px;
  text-align: left;
  vertical-align: top;
}
.v3-roles thead th {
  color: var(--v3-dim);
  font-weight: 500;
  line-height: 1.35;
}
.v3-roles tbody th { min-width: 96px; }
.v3-roles__name { display: block; color: var(--v3-ink); font-weight: 600; }
.v3-roles__note { display: block; color: var(--v3-dim); font-weight: 400; line-height: 1.45; }
.v3-roles td { text-align: center; color: var(--v3-dim); }
.v3-roles td[data-can="true"] { color: var(--v3-green); }

/* the journal entry's one outward-facing switch */
.v3-jentry__public { font-size: 10.5px; }
.v3-jentry__public[aria-pressed="true"] { color: var(--v3-copper); }

/* ─────────────────────── motion & narrow screens ─────────────────────── */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
  }
}

@media (max-width: 1100px) {
  :root { --v3-panel-w: 280px; }
  .v3-step { width: 54px; }
  .v3-strip__conn { flex-basis: 26px; }
  .v3-hub__grid { grid-template-columns: minmax(0, 1fr); }
  /* Two spec boxes side by side stop being readable once the panel is 280px,
     and a number you cannot read is a number you cannot check. */
  .v3-partgrid { grid-template-columns: minmax(0, 1fr); }
}

/* silkscreen — the list of everything printed */
.v3-listrow {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 9px;
  width: 100%;
  padding: 6px 9px;
  text-align: left;
  border: 1px solid var(--v3-line);
  border-radius: 6px;
  background: var(--v3-panel-2);
  color: inherit;
  cursor: pointer;
}
.v3-listrow + .v3-listrow { margin-top: 4px; }
.v3-listrow:hover { border-color: var(--v3-copper); }
.v3-listrow[aria-pressed="true"] { border-color: var(--v3-copper); background: var(--v3-copper-soft); }
.v3-listrow__name { font-size: 11.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.v3-listrow__meta { flex: 0 0 auto; font-size: 10.5px; color: var(--v3-dim); }

/* the board's own colour — a palette, not a dropdown */
.v3-swatches { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px; }
/* Scoped to the palette: the View menu's layer rows use the same class name
   for their 10 px colour dot, and an unscoped rule here stretched that dot
   into a bar that squeezed every layer name to zero width (17 Aug – 22 Sep). */
.v3-swatches .v3-swatch {
  /* A grid item that is a <button> does not stretch on its own in every
     browser, and a swatch 18 px wide is a dot with its name squeezed out. */
  width: 100%;
  display: flex; align-items: center; gap: 7px;
  padding: 6px 8px;
  border: 1px solid var(--v3-line);
  border-radius: 6px;
  background: var(--v3-panel-2);
  color: inherit;
  cursor: pointer;
  text-align: left;
}
.v3-swatches .v3-swatch:hover { border-color: var(--v3-copper); }
.v3-swatches .v3-swatch[aria-pressed="true"] { border-color: var(--v3-copper); background: var(--v3-copper-soft); }
.v3-swatch__dot {
  flex: 0 0 auto; width: 15px; height: 15px; border-radius: 50%;
  background: var(--swatch); border: 1px solid rgba(255,255,255,.22);
}
.v3-swatch__name { flex: 1 1 auto; min-width: 0; font-size: 10.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* a muted finding: still there, visibly stood down */
.v3-finding[data-muted="true"] { opacity: .62; border-style: dashed; }
.v3-finding__mute { display: inline-block; margin-top: 5px; font-size: 10.5px; }
.v3-chip--btn { cursor: pointer; border-color: var(--v3-copper); color: var(--v3-copper); }

/* A mains module's companion parts (#1741). */
.v3-companions { list-style: none; margin: 0 0 12px; padding: 0; max-height: 55vh; overflow: auto; }
.v3-companions li { padding: 8px 0; border-top: 1px solid var(--line, #2b2a26); }
.v3-companions li:first-child { border-top: 0; }
.v3-companions__source { opacity: 0.7; font-size: 0.9em; }

/* One layer at a time (#1768): the solo button at the end of a layer row. */
.v3-viewrow__solo { margin-left: auto; font-size: 11px; padding: 1px 6px; border-radius: 4px;
  border: 1px solid var(--line, #2b2a26); background: transparent; color: inherit; cursor: pointer; }
.v3-viewrow__solo[aria-pressed="true"] { background: var(--accent, #2F7A4F); color: #fff; border-color: transparent; }
/* The mill's width box, inline in the stage hint (#1768). */
.v3-input--tiny { width: 4.5em; display: inline-block; padding: 1px 4px; margin: 0 2px; }

/* ─────────────────────── the brand: display type ───────────────────────
 * Chakra Petch 600 for display and headings (brand identity v1: display
 * 64/1.05, heading 32/1.15); IBM Plex Sans for everything a person reads at
 * length, IBM Plex Mono for specs.
 */
h1, h2, .v3-home__h2, .v3-firstrun h3, .v3-hubcard h2, .v3-hub__name, .v3-dialog h2 {
  font-family: var(--v3-font-display);
  font-weight: 600;
  letter-spacing: 0;
}
.v3-home__tag {
  font-family: var(--v3-font-mono);
  text-transform: none;
  letter-spacing: .02em;
}
.v3-wordmark-svg { display: block; height: 22px; width: auto; color: var(--v3-ink); }
.v3-home__lockup .v3-wordmark-svg { height: 40px; }

/* #1998: the Share panel's opt-in gallery tick */
.v3-share__gallery { gap: 10px; align-items: flex-start; cursor: pointer; min-height: 44px; }
.v3-share__gallery input { width: 18px; height: 18px; margin-top: 2px; accent-color: var(--v3-gold); flex: none; }
