:root {
  --bg: #14161a;
  --panel: #1c1f26;
  --line: #2c313b;
  --text: #e7e9ee;
  --dim: #8d95a5;
  --accent: #4da3ff;
  --btn: #2a303a;       /* button fill — deliberately lighter than --panel so a
                           control reads as a control against the dark viewport */
  --btn-hi: #333a46;    /* button hover fill */
  --btn-line: #454d5c;  /* button edge, strong enough to see over the 3D scene */
}

* { box-sizing: border-box; }

/* An author `display` rule outranks the UA stylesheet's [hidden]{display:none},
   so any flex/grid panel here would ignore its own hidden attribute. */
[hidden] { display: none !important; }

html, body {
  margin: 0;
  height: 100%;
  overflow: hidden;
  background: var(--bg);
  color: var(--text);
  font: 13px/1.45 ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
}

#viewport { position: fixed; inset: 0; }
#viewport canvas { display: block; }

/* ---------- long-build spinner ---------- */
#spinner {
  position: fixed;
  left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  z-index: 4;
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 9px 14px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--panel);
  color: var(--dim);
  font-size: 12px;
  pointer-events: none;               /* informational only; never blocks the canvas */
  box-shadow: 0 4px 18px rgba(0, 0, 0, 0.4);
  /* Always laid out (never display:none) so the opacity transition fires every
     time; visibility keeps the idle spinner out of the a11y/hit tree. Toggling
     display:none instead skipped the fade unpredictably. */
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.5s ease, visibility 0s linear 0.5s;   /* fade out, then hide */
}
#spinner.show {
  opacity: 1;
  visibility: visible;
  transition: opacity 0.5s ease, visibility 0s;               /* show immediately, fade in */
}
@media (prefers-reduced-motion: reduce) {
  #spinner, #spinner.show { transition: none; }
}
#spinner .ring {
  width: 15px; height: 15px;
  border: 2px solid var(--btn-line);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { #spinner .ring { animation-duration: 2.4s; } }

/* ---------- top bar ---------- */

#topbar {
  position: fixed;
  z-index: 3;                /* above the empty-state scrim, or the controls read
                                as disabled on the one screen that must invite a click */
  top: 0; left: 0; right: 0;
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 10px 14px;
  background: linear-gradient(var(--bg), transparent);
  pointer-events: none;
}
#topbar > * { pointer-events: auto; }
#topbar .spacer { flex: 1; pointer-events: none; }

.brand {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-weight: 650;
  letter-spacing: .02em;
  margin-right: 4px;
}
.brand-mark { height: 20px; width: auto; display: block; }

.btn {
  display: inline-block;
  padding: 5px 12px;
  /* A plain button floats over the near-black viewport, so --panel/--line (both
     within a few points of --bg) made it read as a faint dark smudge. Lift it
     off the scene: a lighter fill, a defined edge, and a drop shadow. */
  border: 1px solid var(--btn-line);
  border-radius: 6px;
  background: var(--btn);
  color: var(--text);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45);
  cursor: pointer;
  user-select: none;
}
.btn:hover { border-color: var(--accent); color: var(--accent); background: var(--btn-hi); }

/* Keyboard focus was invisible: no control showed a focus ring, so tabbing
   through the tool was untrackable. One visible ring for every focusable thing
   -- buttons, the select, the slider, the file label -- shown only for keyboard
   focus (:focus-visible) so a mouse click doesn't leave a lingering outline. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.btn:focus-visible {
  border-color: var(--accent);
  color: var(--accent);
}
/* A disabled Undo/Redo shouldn't look clickable. */
.btn:disabled {
  opacity: 0.4;
  cursor: default;
  box-shadow: none;
}
.btn:disabled:hover { border-color: var(--btn-line); color: var(--text); background: var(--btn); }
.btn.primary {
  border-color: var(--accent);
  color: #06121f;
  background: var(--accent);
  font-weight: 600;
}
.btn.primary:hover { background: #6cb5ff; color: #06121f; }

.field { color: var(--dim); }
.field select {
  margin-left: 6px;
  padding: 4px 8px;
  border: 1px solid var(--btn-line);
  border-radius: 6px;
  background: var(--btn);
  color: var(--text);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45);
  font: inherit;
}

.badge {
  padding: 4px 10px;
  border: 1px solid var(--line);
  border-radius: 999px;
  color: var(--dim);
  font-size: 12px;
}

/* ---------- left rail ---------- */

/* One scrolling column holds the stats panel and the rotate panel, pinned from
   under the top bar to the bottom of the screen. When they fit, the rotate panel
   sits at the bottom (margin-top:auto) exactly as before; when the window is too
   short, the rail scrolls instead of the two panels overlapping. pointer-events
   are off on the rail so the empty gap between the panels still orbits the scene,
   and back on for the panels themselves. */
#left-rail {
  position: fixed;
  top: 52px; bottom: 14px; left: 14px;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 12px;
  overflow-y: auto;
  overflow-x: hidden;
  pointer-events: none;
  scrollbar-width: thin;
}
#left-rail > * { pointer-events: auto; }
/* Pins #orient to the bottom when the rail has spare height, but shrinks to 0
   under pressure so the rail scrolls cleanly instead of the panels overlapping. */
#left-rail > .rail-spacer { flex: 1 1 0; min-height: 0; pointer-events: none; }
.rail-spacer[hidden] { display: none; }

/* Right rail: same one-column-that-scrolls trick as #left-rail, for the fin
   options (top, grows DOWN as draw mode adds controls) and the status/receipt
   card (bottom). Independently-fixed, they used to grow into each other and off
   the bottom of the screen in draw mode; sharing a rail with a flex spacer keeps
   options up top, status pinned low, and scrolls the pair under a short window
   instead of overlapping. pointer-events off on the rail so the gap orbits the
   scene, back on for the cards. */
#right-rail {
  position: fixed;
  top: 52px; bottom: 34px; right: 14px;   /* bottom clears the fps readout */
  z-index: 3;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 12px;
  overflow-y: auto;
  overflow-x: hidden;
  pointer-events: none;
  scrollbar-width: thin;
}
#right-rail > * { pointer-events: auto; }
#right-rail > .rail-spacer { flex: 1 1 0; min-height: 0; pointer-events: none; }

/* ---------- stats ---------- */

#stats {
  /* Fixed width so a long value (a filename, the receipt's sentence) wraps
     inside the panel instead of stretching the whole left column. Shrinks on
     very narrow viewports. */
  width: 272px;
  max-width: calc(100vw - 28px);
  box-sizing: border-box;
  padding: 10px 14px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: color-mix(in srgb, var(--panel) 88%, transparent);
  backdrop-filter: blur(6px);
}
#stats dl {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 3px 16px;
  margin: 0;
}
#stats dt { color: var(--dim); }
#stats dd {
  margin: 0;
  font-variant-numeric: tabular-nums;
  min-width: 0;
  overflow-wrap: anywhere;   /* a long filename wraps rather than overflowing */
}
#stats dd.warn { color: #ffb454; }
#stats dd.good { color: #6fcf8b; }
/* Reserve two lines for the analysis timing: it wraps to a second line once the
   fin/pad time is appended, and that one-line growth used to nudge everything below
   the panel down when fins were added. Always two lines high = no shift. */
#stats #s-time { display: block; min-height: 2.6em; line-height: 1.3; }
#stats hr {
  border: 0;
  border-top: 1px solid var(--line);
  margin: 9px 0;
}

/* legend swatches, keyed to the shading colours in app.js */
.sw {
  display: inline-block;
  width: 8px; height: 8px;
  margin-right: 6px;
  border-radius: 2px;
  vertical-align: baseline;
}
.sw-over { background: #ff5a4d; }
.sw-bed  { background: #3f7fd0; }
.sw-fin  { background: #59d98e; }
.sw-pad  { background: #e8b64c; }

/* What the fins did and did not do. Wraps rather than truncating: every line
   here is a limitation the user needs before they commit to a print. */
.note {
  max-width: 26ch;
  margin: 0;
  color: var(--dim);
  font-size: 11px;
  line-height: 1.45;
}
.note:empty { display: none; }
.note.good { color: #6fcf8b; }   /* the "you don't need me" all-clear */

/* The receipt: the "what did this get me" payoff. The exact grams are the
   headline; the saving is the reason to bother. Set apart from the plain stats
   so the number reads as a result, not another row. */
#receipt {
  margin: 0;
  padding: 7px 12px;
  border: 1px solid var(--line);
  border-left: 3px solid #6fcf8b;
  border-radius: 6px;
  background: #171a20;
  display: flex;
  align-items: baseline;
  gap: 4px;
}
#receipt[hidden] { display: none; }
#r-grams {
  font-size: 20px;
  font-weight: 650;
  color: #6fcf8b;
  font-variant-numeric: tabular-nums;
}
#receipt .r-unit { color: var(--dim); font-size: 11px; }
/* The detail (methodology + the "20-45% less plastic" claim) lives behind this
   icon's tooltip -- long and repetitive to show every time, so it's opt-in. */
#receipt .r-info {
  margin-left: auto;
  align-self: center;
  width: 16px; height: 16px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: transparent;
  color: var(--dim);
  font: italic 600 11px/1 Georgia, serif;
  cursor: help;
}
#receipt .r-info:hover { color: var(--text); border-color: var(--dim); }

/* The (i) on the Fins row: same opt-in-detail idea as the receipt's, for the
   how-it-works / how-to-fix text that reads as a wall when it's always on. */
.note-info {
  margin-left: 5px;
  width: 15px; height: 15px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: transparent;
  color: var(--dim);
  font: italic 600 10px/1 Georgia, serif;
  cursor: help;
  vertical-align: middle;
}
.note-info[hidden] { display: none; }
.note-info:hover { color: var(--text); border-color: var(--dim); }

/* Fin options, anchored under the toggle rather than inside the topbar, so
   turning fins on never moves another control. */
#fin-opts {
  width: 264px;
  max-width: calc(100vw - 28px);
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: color-mix(in srgb, var(--panel) 92%, transparent);
  backdrop-filter: blur(6px);
}
#fin-opts .fld {
  display: flex;
  flex-direction: column;
  gap: 4px;
  color: var(--dim);
}
#fin-opts select {
  padding: 4px 8px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--panel);
  color: inherit;
}
.check {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--dim);
  cursor: pointer;
}

/* Draw-mode controls, revealed under the placement select when Draw is live. */
#draw-controls {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding-top: 8px;
  border-top: 1px solid var(--line);
}
#draw-controls[hidden] { display: none; }
.draw-hint {
  margin: 0;
  max-width: 22ch;
  color: var(--dim);
  font-size: 11px;
  line-height: 1.5;
}
.draw-hint strong { color: var(--text); }
.draw-hint kbd {
  padding: 0 4px;
  border: 1px solid var(--line);
  border-radius: 3px;
  background: var(--bg);
  font: inherit;
  font-size: 10px;
}
.draw-btns { display: flex; gap: 6px; }

/* Nothing in the topbar may wrap onto a second line: these are single controls,
   and a two-line button makes the bar jump in height as its contents change. */
#topbar .btn,
#topbar .badge,
#topbar .field { white-space: nowrap; }

.field input[type=range] {
  width: 92px;
  margin: 0 6px;
  vertical-align: middle;
  accent-color: var(--accent);
}
.field output { color: var(--text); font-variant-numeric: tabular-nums; }

.custom-vol { display: inline-flex; align-items: center; gap: 4px; }
.custom-vol input {
  width: 4.2em;
  padding: 4px 6px;
  border: 1px solid var(--btn-line);
  border-radius: 6px;
  background: var(--btn);
  color: var(--text);
  font: inherit;
  font-variant-numeric: tabular-nums;
}

#orient {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  max-width: 15rem;
  padding: 9px 12px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: color-mix(in srgb, var(--panel) 88%, transparent);
  backdrop-filter: blur(6px);
}
#orient .lbl { color: var(--dim); margin-right: 2px; }
#orient .readout {
  flex-basis: 100%;
  margin: 4px 0 0;
  font-variant-numeric: tabular-nums;
}
#orient .readout #rot-delta { color: var(--accent); margin-left: 8px; }
#orient kbd {
  padding: 0 4px;
  border: 1px solid var(--line);
  border-radius: 3px;
  background: var(--bg);
  font: inherit;
  font-size: 11px;
}
#orient .hint {
  flex-basis: 100%;
  margin: 2px 0 0;
  color: var(--dim);
  font-size: 12px;
}
.btn.sm { padding: 3px 9px; }

#orient .suggest { flex-basis: 100%; margin-top: 6px; }
.suggest-head { display: flex; align-items: center; gap: 6px; }
#orient .suggest #suggest-note { margin-top: 4px; }
#suggest-note.good { color: #6fcf8b; }   /* the "best support is no support" win */
/* Collapse/expand the results in place (keeps them, unlike a dismiss). A bare
   chevron that points right when folded and rotates down when open. */
.disclosure {
  flex: 0 0 auto;
  width: 20px; height: 20px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: transparent;
  color: var(--dim);
  font-size: 17px;
  line-height: 1;
  cursor: pointer;
  transition: transform .15s ease, color .15s ease;
  transform: rotate(90deg);            /* expanded: chevron points down */
}
.disclosure[aria-expanded="false"] { transform: rotate(0deg); }   /* collapsed: points right */
.disclosure[hidden] { display: none; }
.disclosure:hover { color: var(--text); }
#suggest-body[hidden] { display: none; }
#suggest-list {
  display: flex;
  flex-direction: column;
  gap: 5px;
  margin-top: 7px;
}
/* One tight line per pose, so three suggestions cost ~90px, not ~280. */
.suggest-row {
  display: flex;
  align-items: baseline;
  gap: 6px;
  text-align: left;
  padding: 5px 8px;
  line-height: 1.3;
}
.suggest-row .sr-rank {
  flex: 0 0 auto;
  min-width: 22px;
  color: var(--accent);
  font-weight: 600;
  font-size: 11px;
}
.suggest-row .sr-line {
  flex: 1 1 auto;
  min-width: 0;                 /* let the row wrap inside the panel instead of spilling past its border */
  font-variant-numeric: tabular-nums;
  font-size: 11px;
}
.suggest-row.active { border-color: var(--accent); background: var(--btn-hi); }
.suggest-row.active .sr-rank::after { content: ' ✓'; }
.suggest-row.bad .sr-rank { color: var(--dim); }
/* the "best support is no support" win: a pose that needs nothing, or that keeps
   every hole/bore support-free. Celebrated, not buried as a gray "0 fins". */
.suggest-row .sr-badge {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: .02em;
  color: #6fcf8b;
  border: 1px solid #6fcf8b66;
  border-radius: 3px;
  padding: 0 4px;
  margin-left: 6px;
  white-space: nowrap;
}
.suggest-row.free { border-color: #6fcf8b66; }
.suggest-row.free .sr-rank { color: #6fcf8b; }

/* ---------- automatic layer view ---------- */
#orient .layerview { flex-basis: 100%; margin-top: 8px; }
.layers-toggle { color: var(--dim); font-size: 12px; }

/* ---------- strength arrow (load direction) ---------- */
#orient .loadfx .loadfx-lede { flex-basis: 100%; margin: 0 0 3px;
  font-size: 13px; color: var(--text); }
#orient .loadfx .loadfx-lede strong { color: var(--accent); }
#orient .loadfx .strength-pad { flex-basis: 100%; display: grid;
  grid-template-columns: repeat(2, 1fr); gap: 4px; margin: 2px 0; }
#orient .loadfx .strength-pad .btn { padding: 5px 6px; font-size: 13px; }
/* keep the lay-flat button on its own row under the rotate hint */
#orient #lay-face { flex-basis: 100%; margin-top: 2px; }
#orient .loadfx { flex-basis: 100%; margin-top: 10px; padding-top: 8px;
  border-top: 1px solid var(--line); display: flex; flex-wrap: wrap; gap: 6px; }
#orient .loadfx #load-hint { flex-basis: 100%; margin: 2px 0 0; color: var(--dim); font-size: 12px; }
#orient .loadfx #load-hint strong { color: var(--text); }
.btn.sm.active { border-color: var(--accent); color: var(--accent); background: var(--btn-hi); }
.load-verdict {
  flex-basis: 100%;
  margin: 4px 0 0;
  padding: 6px 9px;
  border-left: 3px solid var(--dim);
  border-radius: 3px;
  background: var(--btn);
  font-size: 12px;
  line-height: 1.35;
}
.load-verdict.good  { border-left-color: #6fcf8b; color: #cdeccf; }
.load-verdict.mixed { border-left-color: #ffb454; color: #f0dcc0; }
.load-verdict.poor  { border-left-color: #ff6b6b; color: #f2cfcf; }
#orient .loadfx #load-suggest { flex-basis: 100%; margin-top: 2px; }

/* ---------- bottom-right status card ---------- */
#status {
  width: 264px;
  max-width: calc(100vw - 28px);
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: 6px;
  pointer-events: none;          /* clicks pass through the empty gaps to the canvas */
  text-align: left;
}
#status[hidden] { display: none; }
#status > * { pointer-events: auto; }
/* Overhang warning: the tool isn't holding these up, so it's an amber caution, not
   a green all-clear. Card look sets it apart from the plainer .note lines above. */
.over-warn {
  margin: 0;
  padding: 6px 9px;
  border-left: 3px solid #ffb454;
  border-radius: 3px;
  background: var(--btn);
  color: #f0dcc0;
  font-size: 12px;
  line-height: 1.35;
}
.over-warn:empty { display: none; }

#fps {
  position: fixed;
  right: 14px; bottom: 12px;
  color: var(--dim);
  font-variant-numeric: tabular-nums;
}

#hostedby-sign {
  position: fixed;
  left: 14px; bottom: 12px;
  color: var(--dim);
  font-size: 10px;
}

/* ---------- drop target ---------- */

#drop {
  position: fixed;
  inset: 0;
  display: grid;
  place-items: center;
  text-align: center;
  background: color-mix(in srgb, var(--bg) 72%, transparent);
  pointer-events: none;
}
#drop.hidden { display: none; }
#drop.armed { background: color-mix(in srgb, var(--accent) 12%, var(--bg) 70%); }

#drop .inner {
  padding: 30px 38px;
  border: 2px dashed var(--line);
  border-radius: 14px;
  max-width: 34rem;
  text-align: left;
}
#drop.armed .inner { border-color: var(--accent); }
#drop .big {
  margin: 0 0 10px;
  font-size: 22px;
  font-weight: 650;
  letter-spacing: -0.01em;
}
#drop .lede { margin: 0 0 16px; color: var(--text); }
#drop .steps {
  margin: 0 0 16px;
  padding-left: 1.25em;
  color: var(--dim);
}
#drop .steps li { margin-bottom: 4px; }
#drop .steps strong { color: var(--text); font-weight: 600; }
#drop .small { margin: 0; color: var(--dim); font-size: 12px; }

/* ---------- Hosted by --------- */
.hostedby-line { margin: 3rem 0 0; color: var(--dim); font-size: 10px; display:flex; justify-content: center; text-align: center; width: 100%; }
.hostedby { text-decoration: none; color: #ccc; }
.hostedby-line .fa { line-height: 1; vertical-align: middle; margin: 0 2px; }

/* ---------- Ko-fi support link ---------- */
/* Warm accent so the support link reads apart from the blue tool controls,
   without shouting — the tool is the gift, this is a soft "thanks" ask. */
.btn.kofi { border-color: #ff8a5b; color: #ffb492; }
.btn.kofi:hover { border-color: #ff8a5b; color: #06121f; background: #ff8a5b; }
#drop .kofi-line { margin: 14px 0 0; color: var(--dim); font-size: 12px; }
#drop .kofi-line .kofi {
  color: #ff9e73;
  text-decoration: none;
  border-bottom: 1px solid color-mix(in srgb, #ff8a5b 55%, transparent);
}
#drop .kofi-line .kofi:hover { color: #ffb492; border-bottom-color: #ff8a5b; }
