/* ---------------------------------------------------------------------------
   Mockup Generator — visual system

   Tokens, type scale and surface treatment are shared with the paldo.dev
   fallback page (the 404 that links here), so the tool reads as part of the
   same family: warm-neutral greys rather than blue-greys, 14px card radius,
   two-stage shadows, and small uppercase letterspaced section headings.

   The palette is deliberately monochrome. Emphasis is carried by --ink fills
   and borders rather than a brand hue, which keeps the box artwork the only
   saturated thing on screen — the point of the app.
   --------------------------------------------------------------------------- */

:root {
  --bg: #fbfbf9;
  --panel: #ffffff;
  --ink: #16161a;
  --ink-invert: #fbfbf9;
  --muted: #6f6f76;
  --faint: #9a9aa1;
  --line: #e5e5e0;
  --line-strong: #cfcfc8;
  --chip: #f1f1ec;
  --chip-hover: #e8e8e2;
  --danger: #b4342a;
  --danger-ink: #ffffff;
  --stage-from: #efefea;
  --stage-to: #dcdcd4;
  --shadow: 0 1px 2px rgba(0, 0, 0, 0.04), 0 8px 24px rgba(0, 0, 0, 0.05);
  --shadow-lift: 0 2px 4px rgba(0, 0, 0, 0.05), 0 14px 34px rgba(0, 0, 0, 0.09);

  --radius-card: 14px;
  --radius: 9px;
  --radius-sm: 6px;

  --sans: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Inter, Roboto,
    'Helvetica Neue', Arial, sans-serif;
  --mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;

  --sidebar-w: 328px;
  --header-h: 60px;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #0d0d0f;
    --panel: #141417;
    --ink: #f3f3f1;
    --ink-invert: #0d0d0f;
    --muted: #9b9ba3;
    --faint: #6a6a72;
    --line: #26262b;
    --line-strong: #3a3a42;
    --chip: #1c1c21;
    --chip-hover: #24242a;
    --danger: #e5544a;
    --danger-ink: #140908;
    --stage-from: #17171b;
    --stage-to: #0b0b0d;
    --shadow: 0 1px 2px rgba(0, 0, 0, 0.4), 0 8px 24px rgba(0, 0, 0, 0.3);
    --shadow-lift: 0 2px 4px rgba(0, 0, 0, 0.45), 0 14px 34px rgba(0, 0, 0, 0.45);
  }
}

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

html,
body {
  height: 100%;
}

body {
  background: var(--bg);
  color: var(--ink);
  font: 15px/1.55 var(--sans);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

/* One focus treatment for everything, matching the fallback page's cards. */
:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* ---- type ---------------------------------------------------------------- */

/* The small uppercase label used for every section heading. Carried over from
   the fallback page's .eyebrow / h2, where it separates blocks without adding
   another type size to the scale. */
.eyebrow,
.control-group > h3 {
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--faint);
  margin: 0 0 0.85rem;
}

.mono {
  font-family: var(--mono);
}

/* ---- app shell ----------------------------------------------------------- */

.header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  height: var(--header-h);
  padding: 0 1rem 0 1.25rem;
  background: var(--panel);
  border-bottom: 1px solid var(--line);
  flex: none;
  z-index: 20;
}

.header h1 {
  font-size: 0.975rem;
  font-weight: 650;
  letter-spacing: -0.012em;
}

.header .actions {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 0.75rem;
}

.main-container {
  display: flex;
  flex: 1;
  min-height: 0;
  overflow: hidden;
}

.content-area {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-width: 0;
  overflow: hidden;
}

.sidebar {
  display: flex;
  flex-direction: column;
  width: var(--sidebar-w);
  flex: none;
  background: var(--panel);
  border-right: 1px solid var(--line);
  padding: 1.25rem;
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* ---- cards --------------------------------------------------------------- */

.control-group {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  padding: 1rem;
  margin-bottom: 1rem;
  box-shadow: var(--shadow);
}

.card-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.5rem;
}

.card-head > h3 {
  margin-bottom: 0;
}

/* A plain divider block for controls that do not warrant their own card. */
.tools-block {
  margin-top: 1.25rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--line);
}

/* ---- buttons ------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  padding: 0.5rem 0.85rem;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  background: var(--panel);
  color: var(--ink);
  font: inherit;
  font-size: 0.85rem;
  font-weight: 550;
  cursor: pointer;
  transition: background-color 0.15s ease, border-color 0.15s ease,
    transform 0.15s ease;
}

.btn:hover {
  background: var(--chip);
  border-color: var(--line-strong);
}

.btn:active {
  transform: translateY(1px);
}

.btn.primary {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--ink-invert);
}

.btn.primary:hover {
  background: var(--ink);
  opacity: 0.88;
}

.btn.ghost {
  background: transparent;
  border-color: transparent;
  color: var(--muted);
}

.btn.ghost:hover {
  background: var(--chip);
  color: var(--ink);
}

.btn.danger {
  background: transparent;
  border-color: var(--line-strong);
  color: var(--danger);
}

.btn.danger:hover {
  background: var(--danger);
  border-color: var(--danger);
  color: var(--danger-ink);
}

.btn.block {
  width: 100%;
}

.btn.tiny {
  padding: 0.25rem 0.5rem;
  font-size: 0.75rem;
}

.btn.icon {
  padding: 0.45rem;
  width: 2.1rem;
  height: 2.1rem;
}

/* Split button: primary action plus a disclosure arrow sharing one shape. */
.split {
  display: flex;
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--ink);
  color: var(--ink-invert);
}

.split button {
  border: none;
  background: transparent;
  color: inherit;
  font: inherit;
  font-size: 0.85rem;
  font-weight: 550;
  cursor: pointer;
  padding: 0.55rem 0.9rem;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background-color 0.15s ease;
}

.split button:hover {
  background: rgba(127, 127, 127, 0.22);
}

.split #btn-export-dropdown {
  padding: 0.55rem 0.5rem;
  border-left: 1px solid rgba(127, 127, 127, 0.35);
}

/* ---- segmented control (export tabs, formats, editor tools) -------------- */

.segmented {
  display: flex;
  gap: 2px;
  padding: 3px;
  background: var(--chip);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

.segmented button {
  flex: 1;
  border: none;
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: 0.8rem;
  font-weight: 550;
  padding: 0.35rem 0.5rem;
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease;
}

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

/* .is-active is toggled from JS. It used to be written as inline styles, which
   made the control impossible to restyle from CSS alone. */
.segmented button.is-active {
  background: var(--panel);
  color: var(--ink);
  box-shadow: var(--shadow);
}

.segmented.mono button {
  font-family: var(--mono);
  font-size: 0.75rem;
}

/* ---- form controls ------------------------------------------------------- */

.input-row + .input-row {
  margin-top: 0.55rem;
}

.input-row label {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.75rem;
  font-size: 0.85rem;
  color: var(--muted);
}

input[type='number'],
input[type='text'] {
  width: 92px;
  padding: 0.4rem 0.55rem;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: var(--bg);
  color: var(--ink);
  font: inherit;
  font-family: var(--mono);
  font-size: 0.8rem;
  text-align: right;
}

input[type='text'] {
  text-align: left;
  font-family: var(--sans);
  font-size: 0.85rem;
}

input[type='number']:focus,
input[type='text']:focus {
  border-color: var(--ink);
  outline: none;
}

.field-full {
  width: 100%;
}

input[type='color'] {
  width: 2.2rem;
  height: 2.2rem;
  padding: 2px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: var(--bg);
  cursor: pointer;
}

/* Checkboxes inherit the ink colour so they stay legible in dark mode, where
   the browser default renders a light-scheme control on a dark panel. */
input[type='checkbox'] {
  accent-color: var(--ink);
  width: 0.95rem;
  height: 0.95rem;
  cursor: pointer;
}

.check {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  font-size: 0.825rem;
  color: var(--muted);
  cursor: pointer;
  user-select: none;
}

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

/* ---- dropzone ------------------------------------------------------------ */

.dropzone {
  border: 1px dashed var(--line-strong);
  background: var(--chip);
  border-radius: var(--radius);
  padding: 1.5rem 1rem;
  text-align: center;
  cursor: pointer;
  color: var(--muted);
  font-size: 0.85rem;
  transition: background-color 0.15s ease, border-color 0.15s ease,
    color 0.15s ease;
}

.dropzone:hover,
.dropzone.dragover {
  background: var(--chip-hover);
  border-color: var(--ink);
  color: var(--ink);
}

/* The file input is driven by a click on the dropzone, so it never shows. */
.dropzone input[type='file'] {
  display: none;
}

/* ---- texture swatches ---------------------------------------------------- */

.texture-selector {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
}

.texture-option {
  width: 62px;
  height: 62px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  cursor: pointer;
  background-size: cover;
  background-position: center;
  position: relative;
  transition: border-color 0.15s ease, transform 0.15s ease;
}

.texture-option:hover {
  transform: translateY(-2px);
}

.texture-option.active {
  border-color: var(--ink);
  box-shadow: 0 0 0 2px var(--panel), 0 0 0 3px var(--ink);
}

/* ---- face grid ----------------------------------------------------------- */

.faces-grid {
  display: grid;
  grid-template-columns: repeat(4, 52px);
  grid-auto-rows: 52px;
  gap: 5px;
  justify-content: center;
}

.face-grid-item {
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--chip);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  color: var(--muted);
  font-size: 0.7rem;
  font-weight: 600;
  cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease,
    border-color 0.15s ease;
}

.face-grid-item:hover {
  border-color: var(--ink);
  color: var(--ink);
}

.face-grid-item.is-active {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--ink-invert);
}

/* ---- layers list --------------------------------------------------------- */

.layers-list {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.layer-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.4rem 0.5rem;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  font-size: 0.825rem;
  color: var(--muted);
  cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease;
}

.layer-item:hover {
  background: var(--chip);
  color: var(--ink);
}

.layer-item.is-active {
  background: var(--chip);
  border-color: var(--line-strong);
  color: var(--ink);
  font-weight: 550;
}

.layer-item .layer-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.layer-item .layer-actions {
  display: flex;
  gap: 2px;
  flex: none;
}

.layer-item .layer-actions button {
  border: 1px solid transparent;
  background: transparent;
  color: var(--muted);
  border-radius: 4px;
  width: 1.35rem;
  height: 1.35rem;
  font-size: 0.7rem;
  line-height: 1;
  cursor: pointer;
}

.layer-item .layer-actions button:hover:not(:disabled) {
  background: var(--panel);
  border-color: var(--line-strong);
  color: var(--ink);
}

.layer-item .layer-actions button:disabled {
  opacity: 0.3;
  cursor: default;
}

.empty-note {
  font-size: 0.8rem;
  color: var(--faint);
}

/* ---- face panel header --------------------------------------------------- */

.panel-head {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-bottom: 1rem;
}

.panel-head h2 {
  font-size: 0.975rem;
  font-weight: 650;
  letter-spacing: -0.012em;
}

/* ---- 3D stage ------------------------------------------------------------ */

.canvas-container {
  flex: 1;
  min-height: 0;
  position: relative;
  background: radial-gradient(
    circle at 50% 45%,
    var(--stage-from) 0%,
    var(--stage-to) 100%
  );
  overflow: hidden;
}

.canvas-container canvas {
  display: block;
  width: 100% !important;
  height: 100% !important;
  outline: none;
}

/* Hint sits over the stage rather than in the sidebar, where the instruction
   is furthest from the thing being described. */
.stage-hint {
  position: absolute;
  left: 50%;
  bottom: 1rem;
  transform: translateX(-50%);
  padding: 0.4rem 0.8rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--panel);
  color: var(--muted);
  font-size: 0.775rem;
  box-shadow: var(--shadow);
  pointer-events: none;
  white-space: nowrap;
}

/* ---- export popover ------------------------------------------------------ */

.popover {
  display: none;
  position: absolute;
  top: 100%;
  right: 0;
  margin-top: 0.5rem;
  width: 300px;
  padding: 0.9rem;
  background: var(--panel);
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-lift);
  z-index: 1000;
  flex-direction: column;
  gap: 0.75rem;
  text-align: left;
}

.popover.is-open {
  display: flex;
}

.export-preview {
  height: 210px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--chip);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  position: relative;
}

.export-preview img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
}

.export-preview span {
  color: var(--faint);
  font-size: 0.8rem;
}

/* ---- modal --------------------------------------------------------------- */

.modal {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.45);
  z-index: 9999;
  align-items: center;
  justify-content: center;
  padding: 1rem;
}

.modal.is-open {
  display: flex;
}

.modal-card {
  width: 340px;
  max-width: 100%;
  padding: 1.25rem;
  background: var(--panel);
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-lift);
}

.modal-card h3 {
  font-size: 1rem;
  font-weight: 650;
  letter-spacing: -0.012em;
  margin-bottom: 0.4rem;
}

.modal-card p {
  font-size: 0.875rem;
  color: var(--muted);
  margin-bottom: 1.1rem;
}

.modal-actions {
  display: flex;
  justify-content: flex-end;
  gap: 0.5rem;
}

/* ---- utility ------------------------------------------------------------- */

.hidden {
  display: none !important;
}

.spacer {
  flex: 1;
}

/* ---- responsive ---------------------------------------------------------- */

/* Below roughly a tablet the sidebar stops being a column and becomes a strip
   above the stage, which keeps the 3D view from collapsing to nothing. */
@media (max-width: 860px) {
  .main-container {
    flex-direction: column;
  }

  .sidebar {
    width: 100%;
    max-height: 45%;
    border-right: none;
    border-bottom: 1px solid var(--line);
  }

  .header {
    padding: 0 0.75rem;
  }

  .header .actions .check {
    display: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  * {
    transition: none !important;
  }

  .texture-option:hover,
  .btn:active {
    transform: none;
  }
}

/* ---------------------------------------------------------------------------
   Tier-1 UX additions: history controls, face thumbnails, toasts
   --------------------------------------------------------------------------- */

.history-group {
  display: flex;
  gap: 0.3rem;
}

.btn:disabled {
  opacity: 0.35;
  cursor: default;
}

.btn:disabled:hover {
  background: var(--panel);
  border-color: var(--line-strong);
}

/* ---- face selector ------------------------------------------------------- */

/* Tiles carry a thumbnail of the face as it currently looks, painted in as a
   background from JS. The letter sits on a scrim so it stays legible over
   whatever artwork lands underneath it. */
.face-grid-item {
  position: relative;
  background-size: cover;
  background-position: center;
  overflow: hidden;
}

.face-grid-item > span {
  position: relative;
  z-index: 1;
  padding: 0 0.25rem;
  border-radius: 4px;
  background: color-mix(in srgb, var(--panel) 78%, transparent);
  color: var(--ink);
  font-size: 0.68rem;
  font-weight: 600;
}

/* The dot is the answer to "which faces have I already done?" */
.face-grid-item.has-art::after {
  content: '';
  position: absolute;
  top: 4px;
  right: 4px;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--ink);
  box-shadow: 0 0 0 2px var(--panel);
}

.face-grid-item.is-active {
  border-color: var(--ink);
  box-shadow: 0 0 0 2px var(--panel), 0 0 0 3px var(--ink);
}

.face-grid-item.is-active > span {
  background: var(--ink);
  color: var(--ink-invert);
}

.hint-note {
  margin-top: 0.7rem;
  font-size: 0.75rem;
  color: var(--faint);
  text-align: center;
}

/* ---- toasts -------------------------------------------------------------- */

.toasts {
  position: fixed;
  bottom: 1rem;
  right: 1rem;
  z-index: 10000;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  max-width: min(380px, calc(100vw - 2rem));
}

.toast {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  padding: 0.7rem 0.8rem;
  border: 1px solid var(--line-strong);
  border-left-width: 3px;
  border-radius: var(--radius);
  background: var(--panel);
  color: var(--ink);
  font-size: 0.85rem;
  line-height: 1.4;
  box-shadow: var(--shadow-lift);
  animation: toast-in 0.18s ease;
}

.toast-warn {
  border-left-color: #b8860b;
}

.toast-error {
  border-left-color: var(--danger);
}

.toast.is-leaving {
  opacity: 0;
  transition: opacity 0.2s ease;
}

.toast-close {
  flex: none;
  margin-left: auto;
  border: none;
  background: transparent;
  color: var(--faint);
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
  padding: 0 0.15rem;
}

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

@keyframes toast-in {
  from {
    opacity: 0;
    transform: translateY(6px);
  }
}

@media (prefers-reduced-motion: reduce) {
  .toast {
    animation: none;
  }
}

/* ---------------------------------------------------------------------------
   On-box editing: selection overlay + context menu
   --------------------------------------------------------------------------- */

/* The overlay sits on top of the WebGL canvas and is repositioned every frame
   from projected face coordinates. DOM rather than scene geometry, so it stays
   crisp at any zoom and can never leak into an export. */
.face-edit-overlay {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 5;
}

.feo-box {
  position: absolute;
  border: 1.5px dashed var(--ink);
  /* A hairline of panel colour keeps the dash legible over any artwork. */
  box-shadow: 0 0 0 1px var(--panel);
  pointer-events: none;
  /* Rotated layers (and a rolled view) draw the box as a transformed rect
     anchored at its projected top-left corner. */
  transform-origin: 0 0;
}

.feo-handle {
  position: absolute;
  width: 13px;
  height: 13px;
  transform: translate(-50%, -50%);
  background: var(--panel);
  border: 1.5px solid var(--ink);
  border-radius: 3px;
  pointer-events: auto;
  z-index: 6;
}

.feo-handle[data-corner='tl'] { cursor: nw-resize; }
.feo-handle[data-corner='tr'] { cursor: ne-resize; }
.feo-handle[data-corner='bl'] { cursor: sw-resize; }
.feo-handle[data-corner='br'] { cursor: se-resize; }

/* ---- context menu --------------------------------------------------------- */

.ctx-menu {
  position: fixed;
  z-index: 10001;
  min-width: 210px;
  padding: 5px;
  background: var(--panel);
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lift);
  display: flex;
  flex-direction: column;
}

.ctx-heading {
  padding: 0.35rem 0.7rem 0.25rem;
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--faint);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.ctx-item {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  padding: 0.45rem 0.7rem;
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-size: 0.85rem;
  text-align: left;
  cursor: pointer;
}

.ctx-item:hover:not(:disabled),
.ctx-item:focus-visible {
  background: var(--chip);
  outline: none;
}

.ctx-item:disabled {
  color: var(--faint);
  cursor: default;
}

.ctx-item.is-danger {
  color: var(--danger);
}

.ctx-item.is-danger:hover:not(:disabled) {
  background: var(--danger);
  color: var(--danger-ink);
}

.ctx-item iconify-icon {
  font-size: 1rem;
  color: var(--muted);
}

.ctx-item.is-danger iconify-icon {
  color: inherit;
}

.ctx-label {
  flex: 1;
  white-space: nowrap;
}

.ctx-hint {
  font-family: var(--mono);
  font-size: 0.7rem;
  color: var(--faint);
}

.ctx-sep {
  height: 1px;
  margin: 4px 6px;
  background: var(--line);
}

/* ---- rotate handle -------------------------------------------------------- */

.feo-rotate {
  position: absolute;
  width: 13px;
  height: 13px;
  transform: translate(-50%, -50%);
  background: var(--panel);
  border: 1.5px solid var(--ink);
  border-radius: 50%;
  pointer-events: auto;
  cursor: grab;
  z-index: 6;
}

.feo-rotate:active {
  cursor: grabbing;
}

/* ---- mini preview ---------------------------------------------------------- */

/* Sits at the sidebar's bottom edge when content is short, scrolls with it
   when long. */
.preview-card {
  margin-top: auto;
  margin-bottom: 0;
  flex: none;
}

.preview-body {
  margin-top: 0.75rem;
}

.preview-body canvas {
  display: block;
  width: 100%;
  border-radius: var(--radius-sm);
  background: var(--chip);
}

.preview-card .btn.icon.tiny {
  width: 1.6rem;
  height: 1.6rem;
  padding: 0.15rem;
}

.preview-card .btn iconify-icon {
  transition: transform 0.18s ease;
  font-size: 1.1rem;
}

.preview-card.is-collapsed .btn iconify-icon {
  transform: rotate(180deg);
}

@media (prefers-reduced-motion: reduce) {
  .preview-card .btn iconify-icon {
    transition: none;
  }
}

/* ---- edit toolbar ---------------------------------------------------------- */

/* Floats over the stage while a face is locked: the face-level actions from
   the context menu, visible instead of discoverable. */
.edit-toolbar {
  position: absolute;
  top: 0.75rem;
  left: 50%;
  transform: translateX(-50%);
  z-index: 7;
  display: flex;
  align-items: center;
  gap: 2px;
  padding: 4px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 999px;
  box-shadow: var(--shadow-lift);
}

.tb-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  border: none;
  border-radius: 999px;
  background: transparent;
  color: var(--muted);
  font-size: 1.05rem;
  cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease;
}

.tb-btn:hover:not(:disabled),
.tb-btn:focus-visible {
  background: var(--chip);
  color: var(--ink);
}

.tb-btn:disabled {
  opacity: 0.35;
  cursor: default;
}

.tb-face {
  padding: 0 0.5rem 0 0.25rem;
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: -0.008em;
  color: var(--ink);
  white-space: nowrap;
}

.tb-sep {
  width: 1px;
  height: 1.1rem;
  margin: 0 3px;
  background: var(--line);
}

/* The mini preview takes the pointer: orbit, zoom, click-to-jump. */
.preview-body canvas {
  cursor: grab;
  touch-action: none;
}

.preview-body canvas:active {
  cursor: grabbing;
}

/* ---- toolbar groups and text tools ----------------------------------------- */

.tb-group {
  display: flex;
  align-items: center;
  gap: 2px;
}

.tb-btn.is-active {
  background: var(--ink);
  color: var(--ink-invert);
}

.tb-btn.tb-danger:hover:not(:disabled) {
  background: var(--danger);
  color: var(--danger-ink);
}

/* The font button previews the current family on itself. */
.tb-btn.tb-font {
  width: auto;
  padding: 0 0.6rem;
  font-size: 0.85rem;
  font-weight: 700;
}

.tb-input {
  width: 3.2rem;
  padding: 0.25rem 0.35rem;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: var(--bg);
  color: var(--ink);
  font-family: var(--mono);
  font-size: 0.75rem;
  text-align: right;
}

.tb-color {
  width: 1.7rem;
  height: 1.7rem;
  padding: 2px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: var(--bg);
  cursor: pointer;
}

/* The font picker is a long menu; let it scroll instead of leaving the screen. */
.ctx-menu {
  max-height: min(420px, 70vh);
  overflow-y: auto;
}

.ctx-item.is-active::after {
  content: '\2713';
  margin-left: auto;
  padding-left: 0.6rem;
  color: var(--faint);
  font-size: 0.75rem;
}

/* ---- pen preview ----------------------------------------------------------- */

.pen-preview {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 4;
}

/* Search row of a filterable menu (the font picker). */
.ctx-filter {
  margin: 2px 4px 6px;
  padding: 0.4rem 0.6rem;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: var(--bg);
  color: var(--ink);
  font: inherit;
  font-size: 0.8rem;
}

.ctx-filter:focus {
  border-color: var(--ink);
  outline: none;
}

/* ===========================================================================
   v9 — two rails, Figma-style panels, pixel fields
   =========================================================================== */

/* ---- rails ----------------------------------------------------------------
   Each sidebar is a flex column: a scrolling body plus, on the left, a docked
   preview. That is what pins the preview to the bottom edge — no sticky
   positioning, so it cannot overlap the content it is meant to sit below. */

.sidebar {
  display: flex;
  flex-direction: column;
  width: var(--sidebar-w);
  flex: none;
  padding: 0;
  overflow: hidden;
  background: var(--panel);
}

.sidebar-left {
  border-right: 1px solid var(--line);
  border-left: none;
}

.sidebar-right {
  --sidebar-w: 264px;
  border-left: 1px solid var(--line);
  border-right: none;
}

.sidebar-scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* ---- sections -------------------------------------------------------------
   Flat, divider-separated blocks rather than floating cards: at two rails the
   card shadows read as clutter, and the divider is what a design tool uses. */

.panel-section {
  padding: 0.85rem 0.9rem;
  border-bottom: 1px solid var(--line);
}

.panel-section:last-child {
  border-bottom: none;
}

.panel-section > h3 {
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--faint);
  margin: 0 0 0.6rem;
}

.section-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  margin-bottom: 0.6rem;
}

.section-head > h3 {
  margin: 0;
}

.panel-head {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.8rem 0.9rem 0.2rem;
}

.panel-head h2 {
  font-size: 0.9rem;
  font-weight: 650;
  letter-spacing: -0.012em;
}

/* ---- numeric fields -------------------------------------------------------
   A two-column grid of key+value chips, the shape every design tool uses for
   X/Y/W/H. The key is a hit target for nothing — it just names the axis. */

.field-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 4px;
}

.field {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.25rem 0.45rem;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: var(--chip);
  transition: border-color 0.12s ease;
}

.field:focus-within {
  border-color: var(--ink);
}

.field.is-static {
  justify-content: center;
  background: transparent;
}

.field-key {
  display: flex;
  align-items: center;
  flex: none;
  min-width: 0.9rem;
  color: var(--faint);
  font-size: 0.72rem;
  font-weight: 600;
  user-select: none;
}

.field-key iconify-icon {
  font-size: 0.85rem;
}

.field input[type='number'],
.field input[type='text'] {
  width: 100%;
  min-width: 0;
  padding: 0.1rem 0;
  border: none;
  background: transparent;
  color: var(--ink);
  font-family: var(--mono);
  font-size: 0.76rem;
  text-align: left;
}

.field input:focus {
  outline: none;
}

/* Spinners crowd a 60px field and nobody clicks them. */
.field input[type='number']::-webkit-outer-spin-button,
.field input[type='number']::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

.field input[type='number'] {
  -moz-appearance: textfield;
  appearance: textfield;
}

.field input[type='color'] {
  width: 100%;
  height: 1.15rem;
  padding: 0;
  border: none;
  border-radius: 3px;
  background: transparent;
}

.measure {
  margin: 0.5rem 0 0;
  padding: 0 0.9rem;
  font-family: var(--mono);
  font-size: 0.68rem;
  color: var(--faint);
}

.panel-section .measure {
  padding: 0;
}

.text-props {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-top: 4px;
}

.text-props input[type='text'],
.text-props textarea {
  width: 100%;
  padding: 0.3rem 0.45rem;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: var(--bg);
  color: var(--ink);
  font-size: 0.78rem;
  text-align: left;
}

/* The family button. The label is set in the family's own face, but it is a
   name rather than a specimen, so it stays legible when the face is a script
   or an all-caps display cut. */
.font-name {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.35rem;
  width: 100%;
  padding: 0.3rem 0.4rem 0.3rem 0.5rem;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: var(--chip);
  color: var(--ink);
  font-size: 0.78rem;
  text-align: left;
  cursor: pointer;
  transition: border-color 0.12s ease;
}

.font-name:hover {
  border-color: var(--line-strong);
}

.font-name span {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.font-name iconify-icon {
  flex: none;
  color: var(--faint);
  font-size: 0.9rem;
}

/* Marks a family whose weight is one continuous axis rather than a set of
   separate cuts — the reason the slider below it exists. */
.vf-badge {
  flex: none;
  margin-left: auto;
  padding: 0.05rem 0.25rem;
  border-radius: 3px;
  background: var(--accent);
  color: var(--bg);
  font-family: var(--mono, ui-monospace, monospace);
  font-size: 0.58rem;
  font-weight: 700;
  letter-spacing: 0.04em;
}

.axis-row {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.1rem 0.1rem 0.15rem;
}

.axis-row input[type='range'] {
  flex: 1;
  min-width: 0;
  height: 1rem;
  accent-color: var(--accent);
  cursor: ew-resize;
}

.axis-ends {
  flex: none;
  color: var(--faint);
  font-size: 0.62rem;
  font-variant-numeric: tabular-nums;
}

/* The weight picker sits in a .field like the numbers beside it. */

.field select {
  width: 100%;
  min-width: 0;
  padding: 0.1rem 0;
  border: none;
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-size: 0.78rem;
  cursor: pointer;
  appearance: none;
}

.field select:focus {
  outline: none;
}

.field select:disabled {
  color: var(--muted);
  cursor: default;
}

/* ---- layer rows ------------------------------------------------------------ */

.layer-item {
  gap: 0.4rem;
  padding: 0.3rem 0.4rem;
  font-size: 0.78rem;
}

.layer-icon {
  flex: none;
  font-size: 0.9rem;
  color: var(--faint);
}

.layer-item.is-active .layer-icon {
  color: var(--ink);
}

/* ---- docked preview -------------------------------------------------------- */

.preview-card {
  flex: none;
  margin: 0;
  padding: 0.7rem 0.9rem 0.9rem;
  border: none;
  border-top: 1px solid var(--line);
  border-radius: 0;
  background: var(--panel);
  box-shadow: none;
}

.preview-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}

.preview-head h3 {
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--faint);
  margin: 0;
}

.preview-body {
  margin-top: 0.6rem;
}

/* ---- edge handles ---------------------------------------------------------- */

/* Edge handles are the stretch affordance: they drive one axis and leave the
   other alone. Drawn as short bars so they read differently from the corners. */
.feo-handle.is-edge {
  width: 11px;
  height: 11px;
  border-radius: 2px;
  background: var(--panel);
  opacity: 0.95;
}

/* ---- narrow screens --------------------------------------------------------- */

@media (max-width: 1180px) {
  /* The right rail floats over the stage rather than squeezing it to nothing. */
  .sidebar-right {
    position: absolute;
    top: var(--header-h);
    right: 0;
    bottom: 0;
    z-index: 12;
    box-shadow: var(--shadow-lift);
  }

  .main-container {
    position: relative;
  }
}

@media (max-width: 860px) {
  .sidebar-left {
    max-height: 42%;
    width: 100%;
  }

  .sidebar-right {
    width: min(264px, 82vw);
  }
}

/* ===========================================================================
   v10 — bottom toolbar, blue selection, inline text, hand tool
   =========================================================================== */

:root {
  /* Selection blue. The palette is otherwise monochrome on purpose, so this
     is the one hue in the app — which is what makes it read as "selected"
     rather than as decoration. */
  --accent: #0a84ff;
  --accent-soft: rgba(10, 132, 255, 0.16);
}

@media (prefers-color-scheme: dark) {
  :root {
    --accent: #3b9dff;
    --accent-soft: rgba(59, 157, 255, 0.22);
  }
}

/* ---- the toolbar moves to the bottom --------------------------------------
   Down by the work rather than up by the header: it is a tool palette, and
   the stage hint takes the top edge where it does not compete. */

.edit-toolbar {
  top: auto;
  bottom: 1rem;
}

.stage-hint {
  top: 1rem;
  bottom: auto;
}

/* ---- selection chrome ------------------------------------------------------ */

.feo-box {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.55);
}

.feo-handle,
.feo-rotate {
  border-color: var(--accent);
  background: var(--panel);
}

.feo-handle.is-edge {
  border-color: var(--accent);
}

/* ---- inline text editing ---------------------------------------------------
   Transparent glyphs over the real texture: what is read is what will print,
   repainted on each keystroke. Only the caret and the selection show. */

/* The on-box text editor. Everything about its type is set from the layer in
   `placeTextEditor` — family, weight, size, line height, tracking, alignment —
   so its invisible text lays out on top of the painted glyphs and the caret
   lands where the letter is. Only what cannot come from the layer is here. */
textarea.inline-text {
  position: absolute;
  z-index: 8;
  margin: 0;
  border: none;
  outline: 1px solid var(--accent);
  outline-offset: 2px;
  background: transparent;
  color: transparent;
  caret-color: var(--accent);
  resize: none;
  overflow: hidden;
  /* Wrap the way the generator does: at spaces, honouring explicit breaks. */
  white-space: pre-wrap;
  overflow-wrap: normal;
}

textarea.inline-text::selection {
  background: var(--accent-soft);
  color: transparent;
}

/* The sidebar's copy of the same text. Two rows by default because one row
   hid the fact that a line break is allowed at all. */
textarea#input-text-value {
  min-height: 3.4rem;
  resize: vertical;
  font-family: inherit;
  line-height: 1.4;
}

/* The 2D preview keeps its own cursor affordances. */
.preview-body canvas {
  cursor: grab;
  touch-action: none;
}

.preview-body canvas:active {
  cursor: grabbing;
}

/* ===========================================================================
   v11 — projects landing screen
   =========================================================================== */

.landing {
  position: fixed;
  inset: 0;
  z-index: 50;
  overflow-y: auto;
  background: var(--bg);
  padding: clamp(1.5rem, 5vw, 3rem) clamp(1rem, 5vw, 3rem) 4rem;
}

/* The editor keeps running behind the landing screen — the scene is already
   built and the render loop is cheap — but it must not take the pointer. */
body.is-landing .header,
body.is-landing .main-container {
  pointer-events: none;
}

.landing-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  max-width: 1100px;
  margin: 0 auto 1.75rem;
}

.landing-head h1 {
  font-size: clamp(1.4rem, 3.5vw, 1.9rem);
  font-weight: 650;
  letter-spacing: -0.022em;
}

.landing-empty {
  max-width: 1100px;
  margin: 2rem auto;
  color: var(--muted);
  font-size: 0.9rem;
  text-align: center;
}

.project-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
  gap: clamp(0.8rem, 2vw, 1.15rem);
  max-width: 1100px;
  margin: 0 auto;
}

.project-card {
  display: flex;
  flex-direction: column;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  background: var(--panel);
  box-shadow: var(--shadow);
  cursor: pointer;
  overflow: hidden;
  text-align: left;
  font: inherit;
  color: inherit;
  transition: transform 0.15s ease, box-shadow 0.15s ease,
    border-color 0.15s ease;
}

.project-card:hover,
.project-card:focus-visible {
  transform: translateY(-2px);
  border-color: var(--line-strong);
  box-shadow: var(--shadow-lift);
}

.pc-shot {
  aspect-ratio: 16 / 10;
  background: var(--chip);
  background-size: cover;
  background-position: center;
  border-bottom: 1px solid var(--line);
}

.pc-row {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.55rem 0.6rem 0.6rem;
}

.pc-meta {
  display: flex;
  flex-direction: column;
  min-width: 0;
  flex: 1;
}

.pc-name {
  font-size: 0.85rem;
  font-weight: 600;
  letter-spacing: -0.008em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.pc-when {
  font-size: 0.72rem;
  color: var(--faint);
}

.pc-more {
  flex: none;
  width: 1.6rem;
  height: 1.6rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--muted);
  cursor: pointer;
}

.pc-more:hover {
  background: var(--chip);
  color: var(--ink);
}

.pc-rename {
  width: 100%;
  padding: 0.1rem 0.25rem;
  border: 1px solid var(--accent);
  border-radius: 4px;
  background: var(--bg);
  color: var(--ink);
  font: inherit;
  font-size: 0.85rem;
  font-weight: 600;
}

/* The "new project" tile is a card-shaped button, not a card with a picture. */
.project-card.is-new {
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  min-height: 168px;
  border-style: dashed;
  background: transparent;
  box-shadow: none;
  color: var(--muted);
}

.project-card.is-new:hover {
  border-color: var(--ink);
  color: var(--ink);
}

.pc-plus {
  font-size: 1.5rem;
  line-height: 1;
}

/* ---- header project identity ---------------------------------------------- */

.brand {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  min-width: 0;
}

.brand h1 {
  cursor: text;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 34vw;
}

.brand h1:hover {
  text-decoration: underline;
  text-decoration-color: var(--line-strong);
  text-underline-offset: 3px;
}

/* ---- toast action ---------------------------------------------------------- */

.toast-action {
  flex: none;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-size: 0.78rem;
  font-weight: 600;
  padding: 0.2rem 0.5rem;
  cursor: pointer;
}

.toast-action:hover {
  background: var(--chip);
}

/* ---- inspector: alignment, paint rows, effects ----------------------------- */

.align-row {
  display: flex;
  align-items: center;
  gap: 2px;
  margin-bottom: 0.5rem;
}

.align-gap {
  flex: 1;
}

.align-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--muted);
  font-size: 1rem;
  cursor: pointer;
}

.align-btn:hover {
  background: var(--chip);
  color: var(--ink);
}

.align-btn.is-active {
  background: var(--ink);
  color: var(--bg);
}

/* A colour + hex + amount row: the shape Figma uses for every paint. */
.paint-row {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 0.25rem 0.35rem;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: var(--chip);
}

.paint-row:focus-within {
  border-color: var(--ink);
}

.paint-row input[type='color'] {
  flex: none;
  width: 1.15rem;
  height: 1.15rem;
  padding: 0;
  border: 1px solid var(--line-strong);
  border-radius: 3px;
  background: transparent;
  cursor: pointer;
}

.paint-hex {
  flex: 1;
  min-width: 0;
  padding: 0;
  border: none;
  background: transparent;
  color: var(--ink);
  font-family: var(--mono);
  font-size: 0.75rem;
  text-transform: uppercase;
}

.paint-pct {
  flex: none;
  width: 2.6rem;
  padding: 0;
  border: none;
  background: transparent;
  color: var(--ink);
  font-family: var(--mono);
  font-size: 0.75rem;
  text-align: right;
}

.paint-hex:focus,
.paint-pct:focus {
  outline: none;
}

.paint-row input[type='number']::-webkit-outer-spin-button,
.paint-row input[type='number']::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

.font-btn {
  flex: none;
  padding: 0 0.4rem;
  height: 1.3rem;
  border: 1px solid var(--line-strong);
  border-radius: 3px;
  background: var(--panel);
  color: var(--ink);
  font-size: 0.72rem;
  font-weight: 700;
  cursor: pointer;
}

/* Small header affordances: eye for visibility, + for add. */
.icon-toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.4rem;
  height: 1.4rem;
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--muted);
  font-size: 0.95rem;
  cursor: pointer;
}

.icon-toggle:hover {
  background: var(--chip);
  color: var(--ink);
}

.icon-toggle.is-off {
  opacity: 0.4;
}

.icon-toggle.is-on {
  color: var(--accent);
}

.field.is-disabled {
  opacity: 0.4;
}

.effects-list {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.effect-row {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 0.4rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--chip);
}

.effect-head {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  font-size: 0.75rem;
  font-weight: 600;
}

.effect-head .effect-name {
  flex: 1;
}

.effect-fields {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 3px;
}

.effects-empty {
  font-size: 0.75rem;
  color: var(--faint);
}

/* ---- drag to drop ----------------------------------------------------------- */

.layer-item.is-dragging {
  opacity: 0.45;
}

.layer-item.drop-above {
  box-shadow: inset 0 2px 0 0 var(--accent);
}

.layer-item.drop-below {
  box-shadow: inset 0 -2px 0 0 var(--accent);
}

/* The rail accepts dropped artwork now that the dropzone is gone. */
.sidebar-right.is-drop-target {
  outline: 2px dashed var(--accent);
  outline-offset: -4px;
}

/* ---- v13: flat face view, guides, blend, tint, materials ------------------- */

/* The flat face sits where the WebGL canvas would be; the renderer is hidden
   rather than removed, so the camera it drives keeps working. */
.flat-face {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
}

.blend-field {
  margin-top: 4px;
}

.blend-field select {
  flex: 1;
  min-width: 0;
  border: none;
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-size: 0.76rem;
  cursor: pointer;
}

.blend-field select:focus {
  outline: none;
}

.tint-row {
  display: flex;
  align-items: center;
  gap: 4px;
  margin-top: 0.5rem;
}

.tint-row .field {
  flex: 1;
}

/* The upload tile matches a swatch but reads as an action. */
.texture-option.is-upload {
  display: flex;
  align-items: center;
  justify-content: center;
  border-style: dashed;
  background: transparent;
  color: var(--muted);
  font-size: 1.2rem;
  cursor: pointer;
}

.texture-option.is-upload:hover {
  border-color: var(--ink);
  color: var(--ink);
  transform: none;
}

/* ---- v14: permanent right rail, page background ---------------------------- */

/* The stage gradient defers to a project's own background colour once one is
   set, so the two never fight over the same paint. */
.canvas-container.has-custom-bg {
  background: var(--stage-bg, var(--stage-from));
}

/* Box properties fill the rail whenever nothing is selected. */
#box-properties > .panel-section:first-child {
  border-top: none;
}
