/* Modular Builder — showcase demo
 *
 * One light look, committed to. The renderer's exposure, tone mapping and panel
 * albedo are calibrated against a pale studio backdrop, so the page background
 * is not a style choice here: a dark ground would put the powder-coat colours
 * off, and the colours are the thing being shown.
 *
 * The canvas is transparent and covers the window. Everything below draws on
 * top of it, and nothing below is allowed to take more of the screen than it
 * has to. */

:root {
  --ground: #f2f1ee;
  --ground-low: #e4e2dc;
  --ink: #17181a;
  --ink-soft: rgba(23, 24, 26, 0.55);
  --ink-faint: rgba(23, 24, 26, 0.32);
  --line: rgba(23, 24, 26, 0.13);
  --sheet: rgba(255, 255, 255, 0.82);
  --sheet-solid: #ffffff;
  --shadow: 0 10px 30px rgba(17, 20, 24, 0.1), 0 1px 3px rgba(17, 20, 24, 0.06);
  --radius: 4px;
  --font: "Helvetica Neue", Helvetica, -apple-system, BlinkMacSystemFont, Arial, sans-serif;
  --dock-gap: 7px;
}

*, *::before, *::after { box-sizing: border-box; }

html, body {
  margin: 0;
  height: 100%;
  overflow: hidden;
  /* A phone that rubber-bands the page while someone is orbiting the piece
     feels broken, and the gesture is the whole demo. */
  overscroll-behavior: none;
}

body {
  color: var(--ink);
  font-family: var(--font);
  -webkit-font-smoothing: antialiased;
  -webkit-tap-highlight-color: transparent;
  /* Painted here, before any script runs, so the first frame the browser shows
     is the backdrop rather than white. */
  background:
    radial-gradient(120% 85% at 50% 8%, #f8f7f5 0%, var(--ground) 46%, var(--ground-low) 100%),
    var(--ground);
}

#app { position: fixed; inset: 0; }

.stage {
  position: absolute;
  inset: 0;
  /* The orbit controls need the raw pointer stream; without this the browser
     claims vertical drags for scrolling and the piece will not turn. */
  touch-action: none;
}

.stage canvas { display: block; }

/* --- Chrome ---------------------------------------------------------------
 * One layer over the canvas, transparent to the pointer except where there is
 * something to press, so an orbit that starts on empty chrome still reaches
 * the canvas underneath. */

.chrome {
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0;
  transition: opacity 520ms ease;
}

.chrome.is-live { opacity: 1; }

.chrome > * { pointer-events: auto; }

.wordmark {
  position: absolute;
  top: calc(14px + env(safe-area-inset-top));
  left: calc(16px + env(safe-area-inset-left));
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-soft);
  pointer-events: none;
}

.actions {
  position: absolute;
  top: calc(10px + env(safe-area-inset-top));
  right: calc(12px + env(safe-area-inset-right));
  display: flex;
  gap: 6px;
}

.pill {
  appearance: none;
  border: 1px solid var(--line);
  background: var(--sheet);
  backdrop-filter: blur(10px);
  color: var(--ink);
  font-family: inherit;
  font-size: 11px;
  letter-spacing: 0.02em;
  padding: 7px 12px;
  border-radius: 999px;
  cursor: pointer;
  transition: background 140ms ease, color 140ms ease, border-color 140ms ease;
}

.pill:hover { border-color: rgba(23, 24, 26, 0.3); }
.pill:active { background: var(--sheet-solid); }
.pill.is-done { background: var(--ink); border-color: var(--ink); color: #fff; }

/* --- Dock ---------------------------------------------------------------- */

.dock {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  bottom: calc(12px + env(safe-area-inset-bottom));
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--dock-gap);
  padding: 9px 11px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--sheet);
  backdrop-filter: blur(14px);
  box-shadow: var(--shadow);
  max-width: calc(100vw - 20px);
}

.dock-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
  flex-wrap: wrap;
}

.chip {
  appearance: none;
  border: 1px solid transparent;
  background: transparent;
  color: var(--ink-soft);
  font-family: inherit;
  font-size: 11px;
  letter-spacing: 0.03em;
  padding: 5px 11px;
  border-radius: 999px;
  cursor: pointer;
  transition: background 140ms ease, color 140ms ease;
}

.chip:hover { color: var(--ink); background: rgba(23, 24, 26, 0.05); }
.chip.is-active { background: var(--ink); color: #fff; }

/* The palette. Thirty-eight pixels with five of gap gives a 43px hit pitch,
   which a thumb finds reliably, and the row of eight plus the divider still
   fits across a 360px phone. */
.tile {
  appearance: none;
  width: 38px;
  height: 38px;
  display: grid;
  place-items: center;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: rgba(255, 255, 255, 0.6);
  color: var(--ink);
  cursor: pointer;
  padding: 0;
  transition: background 140ms ease, border-color 140ms ease, color 140ms ease, opacity 140ms ease;
}

.tile:hover:not(:disabled) { border-color: rgba(23, 24, 26, 0.32); }
.tile.is-active { background: var(--ink); border-color: var(--ink); color: #fff; }

/* Shown, not hidden: a palette that changes length as the selection moves is
   harder to read than one with a dead key in it. */
.tile:disabled { opacity: 0.28; cursor: default; }

.tile-icon {
  width: 22px;
  height: 22px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Fronts on the left, what goes inside on the right.
 *
 * The separator is a real flex item with a real width rather than a margin or a
 * border on the seventh tile. The dock is shrink-to-fit, and the width it asks
 * for does not count margins on the items inside a wrapping row — which showed
 * up as the palette breaking one tile early, on a dock with room to spare. An
 * item with a declared width is counted. `nowrap` then makes it impossible for
 * a few pixels of disagreement to break the row at all. */
.dock-palette { flex-wrap: nowrap; }

.palette-group {
  display: flex;
  align-items: center;
  gap: 5px;
}

.palette-divider {
  flex: 0 0 auto;
  width: 13px;
  align-self: stretch;
  background: linear-gradient(var(--line), var(--line)) center / 1px 62% no-repeat;
}

.swatch {
  appearance: none;
  width: 30px;
  height: 24px;
  padding: 2px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: rgba(255, 255, 255, 0.5);
  cursor: pointer;
  transition: border-color 140ms ease, transform 140ms ease;
}

.swatch span {
  display: block;
  width: 100%;
  height: 100%;
  border-radius: 2px;
  box-shadow: inset 0 0 0 1px rgba(23, 24, 26, 0.09);
}

.swatch:hover { border-color: rgba(23, 24, 26, 0.34); }
.swatch.is-active {
  border-color: var(--ink);
  box-shadow: 0 0 0 1px var(--ink);
}

/* --- Credit -------------------------------------------------------------- */

.credit {
  position: absolute;
  right: calc(14px + env(safe-area-inset-right));
  bottom: calc(14px + env(safe-area-inset-bottom));
  font-size: 10px;
  letter-spacing: 0.04em;
  color: var(--ink-faint);
  text-decoration: none;
  transition: color 140ms ease;
}

.credit:hover { color: var(--ink); }

/* --- Toast --------------------------------------------------------------- */

.toast {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  top: calc(52px + env(safe-area-inset-top));
  max-width: min(420px, calc(100vw - 32px));
  padding: 8px 14px;
  border-radius: 999px;
  background: var(--ink);
  color: #fff;
  font-size: 11px;
  letter-spacing: 0.02em;
  text-align: center;
  box-shadow: var(--shadow);
  pointer-events: none;
}

.copy-fallback {
  position: fixed;
  top: -100px;
  left: -100px;
  opacity: 0;
}

/* --- Loader -------------------------------------------------------------- */
/* No spinner and no wordmark. The backdrop is already painted by the time this
   is visible, so all that is missing is a sign that something is coming. */

.loader {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  pointer-events: none;
  opacity: 1;
  transition: opacity 340ms ease;
}

.loader.is-gone { opacity: 0; }

.loader-bar {
  width: min(180px, 40vw);
  height: 2px;
  border-radius: 2px;
  background: rgba(23, 24, 26, 0.12);
  overflow: hidden;
}

.loader-bar span {
  display: block;
  width: 100%;
  height: 100%;
  background: var(--ink);
  transform-origin: left center;
  transform: scaleX(0);
  transition: transform 180ms linear;
}

/* --- Phones -------------------------------------------------------------- */
/* The dock becomes a full-width sheet: rows of eight and of four centre badly
   in a narrow floating box, and the piece is framed above whatever the dock
   turns out to be — main.js measures it rather than assuming. */

@media (max-width: 560px) {
  .dock {
    left: 0;
    right: 0;
    bottom: 0;
    transform: none;
    max-width: none;
    border-radius: 14px 14px 0 0;
    border-left: none;
    border-right: none;
    border-bottom: none;
    padding: 10px 10px calc(10px + env(safe-area-inset-bottom));
  }

  /* The dock is full width down here, so the credit cannot sit beside it. It
     goes directly above, on the line the dock's own top edge sets — measured,
     because the rows reflow and the height is not a constant. */
  .credit {
    right: auto;
    left: 50%;
    transform: translateX(-50%);
    bottom: calc(var(--dock-height, 132px) + 10px);
    white-space: nowrap;
  }

  .wordmark { font-size: 10px; }
  .tile { width: 34px; height: 34px; }
  .tile-icon { width: 20px; height: 20px; }
}

/* Landscape on a phone leaves almost no height, so the dock loses a row's
   worth of padding rather than the piece losing the frame. */
@media (max-height: 460px) {
  .dock { --dock-gap: 4px; padding: 6px 8px; }
  .tile { width: 30px; height: 30px; }
  .tile-icon { width: 18px; height: 18px; }
  .swatch { height: 20px; }
  .credit { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .chrome, .loader, .loader-bar span { transition-duration: 1ms; }
}
