/* Hallmark · genre: modern-minimal · scope: application UI · theme: Shop Instrument
 * Framing SaaS — Intake. Components and screens.
 * Consumes tokens.css. Declares no colour, font, radius or duration of its own.
 */

@import url("tokens.css");

html { overflow-x: clip; }
body { overflow-x: clip; }

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

/* Components below set `display` on elements that are also toggled with the
 * `hidden` attribute, and a class selector outranks the UA rule. Without this
 * a hidden panel renders anyway. */
[hidden] { display: none !important; }

body {
  margin: 0;
  background: var(--color-paper);
  color: var(--color-ink);
  font-family: var(--font-ui);
  font-size: var(--text-body);
  font-weight: var(--weight-body);
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4 {
  font-style: normal;
  font-weight: var(--weight-strong);
  letter-spacing: var(--track-tight);
  line-height: var(--lh-tight);
  margin: 0;
  overflow-wrap: anywhere;
  min-width: 0;
}
h1 { font-size: var(--text-heading); }
h2 { font-size: var(--text-md); }
h3 { font-size: var(--text-body); }
p  { margin: 0; }

/* Every dimension, every price, every caliper. Tabular so a live total cannot
 * jitter under a slider drag and `13½ × 17½` cannot reflow as digits change. */
.dim {
  font-family: var(--font-dim);
  font-variant-numeric: tabular-nums;
  line-height: var(--lh-dim);
  letter-spacing: 0;
}

.label {
  font-size: var(--text-micro);
  font-weight: var(--weight-medium);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--color-muted);
  line-height: var(--lh-dim);
}

.muted   { color: var(--color-muted); }
.neutral { color: var(--color-neutral); }

:focus { outline: none; }
:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* ══ Buttons ══════════════════════════════════════════════════════════════
 * Eight states, on every variant. Height is the 44px touch floor, matched by
 * every input on the page — a screen with 44px buttons and 38px fields feels
 * untuned before anyone can say why.
 */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-xs);
  min-height: var(--control-h);
  padding-inline: var(--space-md);
  border: var(--rule-hair) solid transparent;
  border-radius: var(--radius-md);
  background: var(--color-paper-2);
  color: var(--color-ink);
  font: inherit;
  font-weight: var(--weight-medium);
  line-height: 1;
  white-space: nowrap;      /* no two-line clickable text, at any width */
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out);
}
.btn--primary { background: var(--color-ink); color: var(--color-ink-ink); }
.btn--ghost   { background: transparent; border-color: var(--color-rule-2); }
.btn--quiet   { background: transparent; border-color: transparent; color: var(--color-neutral); padding-inline: var(--space-sm); }
.btn--sm      { min-height: 2rem; font-size: var(--text-micro); padding-inline: var(--space-sm); }

@media (hover: hover) {
  .btn:hover,        .btn.is-hover        { background: var(--color-paper-3); }
  .btn--primary:hover, .btn--primary.is-hover { background: var(--color-ink-2); }
  .btn--ghost:hover, .btn--ghost.is-hover  { background: var(--color-paper-2); }
  .btn--quiet:hover, .btn--quiet.is-hover  { background: var(--color-paper-2); color: var(--color-ink); }
}
.btn:active, .btn.is-active { transform: translateY(1px); }
.btn:focus-visible, .btn.is-focus { outline: 2px solid var(--color-focus); outline-offset: 2px; }

.btn[disabled], .btn[aria-disabled="true"], .btn.is-disabled {
  opacity: 0.5;
  cursor: not-allowed;
  transform: none;
}
.btn[data-state="loading"] { cursor: progress; }
.btn[data-state="loading"]::before {
  content: "";
  width: 0.875em; height: 0.875em;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  animation: spin 700ms linear infinite;
}
.btn[data-state="error"]   { box-shadow: inset 0 0 0 1px var(--color-block); color: var(--color-block); }
.btn[data-state="success"] { box-shadow: inset 0 0 0 1px var(--color-rule-2); }
.btn[data-state="success"]::before { content: "✓"; font-weight: var(--weight-strong); }

@keyframes spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .btn[data-state="loading"]::before { animation-duration: 2s; }
}

/* A disabled control always says why. Never a dead affordance. */
.why {
  display: block;
  font-size: var(--text-micro);
  color: var(--color-muted);
  margin-top: var(--space-2xs);
  max-width: 34ch;
}

/* ══ Measurement input ════════════════════════════════════════════════════
 * The highest-risk component in the system. Framers type 13 1/2, 13-1/2, 13.5
 * and 13½ interchangeably and think in sixteenths. Display state is the
 * fraction; editing state is whatever raw text is mid-typing; a third state
 * covers text the parser cannot resolve.
 */

/* The implicit track is `auto`, which sizes to max-content — in a narrow pane a
 * wide child pushes the whole field past its own box. Pin the column. */
.field { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--space-2xs); min-width: 0; }
.field__label { font-size: var(--text-micro); font-weight: var(--weight-medium);
                letter-spacing: var(--track-label); text-transform: uppercase;
                color: var(--color-neutral); }

.mi {
  position: relative;
  display: flex;
  align-items: center;
  min-height: var(--control-h);
  /* 1px in every state. Border width never changes — a geometry shift on focus
   * is the tell that separates a tuned field from a settings page. */
  border: var(--rule-hair) solid var(--color-rule-2);
  border-radius: var(--radius-md);
  background: var(--color-paper);
  outline: 2px solid transparent;   /* the ring's slot, reserved so nothing shifts */
  outline-offset: 1px;
  transition: background-color var(--dur-fast) var(--ease-out);
}
.mi__input {
  flex: 1;
  min-width: 0;
  border: 0;
  background: none;
  padding: 0 var(--space-sm);
  font-family: var(--font-dim);
  font-variant-numeric: tabular-nums;
  font-size: var(--text-body);
  color: var(--color-ink);
  outline: none;
}
.mi__input::placeholder { color: var(--color-muted); }
/* A ~24px right slot, always reserved, so an error glyph or a spinner appearing
 * never reflows the field. */
.mi__slot {
  flex: none;
  width: 1.5rem;
  display: grid;
  place-items: center;
  color: var(--color-muted);
  font-size: var(--text-micro);
}
.mi__unit { padding-right: var(--space-sm); color: var(--color-muted); font-family: var(--font-dim); }

@media (hover: hover) {
  .mi:hover, .mi.is-hover { background: var(--color-paper-2); }
}
.mi:focus-within, .mi.is-focus {
  outline-color: var(--color-focus);
  border-color: var(--color-ink-2);
  background: var(--color-paper);
}
.mi.is-disabled { opacity: 0.55; cursor: not-allowed; }
.mi.is-disabled .mi__input { cursor: not-allowed; }
.mi.is-error   { border-color: var(--color-block); }
.mi.is-error   .mi__slot { color: var(--color-block); }
.mi.is-success .mi__slot { color: var(--color-neutral); }

/* Helper reserves one line always, so an error appearing pushes nothing down.
 * Error replaces helper — never both. */
.field__help {
  min-height: 1lh;
  font-size: var(--text-micro);
  color: var(--color-muted);
  line-height: var(--lh-dim);
}
.field__help.is-error { color: var(--color-block); }

/* ══ Slider + paired numeric ══════════════════════════════════════════════
 * One component, two surfaces, always in sync. Coverage runs ⅛″ (hard stop) to
 * 3″. The default sits at ¼″ — near the left end, and that is the normal
 * resting position, not an edge case.
 */

.slider { display: grid; grid-template-columns: minmax(0, 1fr) 7.5rem; gap: var(--space-md); align-items: center; }
.slider__track {
  -webkit-appearance: none; appearance: none;
  width: 100%;
  height: var(--control-h);      /* 44px hit target; the visible rail is drawn inside */
  background: none;
  cursor: pointer;
}
.slider__track::-webkit-slider-runnable-track {
  height: 4px; border-radius: var(--radius-pill); background: var(--color-rule-2);
}
.slider__track::-moz-range-track {
  height: 4px; border-radius: var(--radius-pill); background: var(--color-rule-2);
}
.slider__track::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 22px; height: 22px; margin-top: -9px;
  border-radius: 50%;
  background: var(--color-ink);
  border: 2px solid var(--color-paper);
  box-shadow: 0 0 0 1px var(--color-rule-2);
  cursor: grab;
}
.slider__track::-moz-range-thumb {
  width: 22px; height: 22px;
  border-radius: 50%;
  background: var(--color-ink);
  border: 2px solid var(--color-paper);
  box-shadow: 0 0 0 1px var(--color-rule-2);
  cursor: grab;
}
.slider__track:active::-webkit-slider-thumb { cursor: grabbing; }
.slider__track:focus-visible::-webkit-slider-thumb { box-shadow: 0 0 0 2px var(--color-focus); }
.slider__track:focus-visible::-moz-range-thumb    { box-shadow: 0 0 0 2px var(--color-focus); }
.slider.is-atstop .slider__track::-webkit-slider-thumb { background: var(--color-block); }
.slider.is-atstop .slider__track::-moz-range-thumb     { background: var(--color-block); }

/* Sits below the slider as a sibling, so its end labels have to be inset by the
 * width of the paired numeric field to stay aligned with the track's ends. */
.slider__scale {
  display: flex; justify-content: space-between;
  font-family: var(--font-dim); font-size: var(--text-micro);
  color: var(--color-muted);
  margin-top: calc(-1 * var(--space-xs));
  padding-right: calc(7.5rem + var(--space-md));
}
@media (max-width: 700px) { .slider__scale { padding-right: 0; } }

/* ══ Stepper ══════════════════════════════════════════════════════════════
 * Bite and reveal. Increments in sixteenths.
 */

.stepper {
  display: inline-flex;
  justify-self: start;   /* a stretched grid item grows a full-width `+` button */
  align-items: stretch;
  border: var(--rule-hair) solid var(--color-rule-2);
  border-radius: var(--radius-md);
  background: var(--color-paper);
  overflow: hidden;
}
.stepper__btn {
  width: var(--control-h); min-height: var(--control-h);
  border: 0; background: none;
  color: var(--color-ink); font: inherit; font-size: var(--text-md);
  line-height: 1; cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-out);
}
@media (hover: hover) { .stepper__btn:hover { background: var(--color-paper-3); } }
.stepper__btn:active { transform: translateY(1px); }
.stepper__btn:focus-visible, .stepper__btn.is-focus {
  outline: 2px solid var(--color-focus); outline-offset: -3px; }
.stepper__btn[disabled] { opacity: 0.4; cursor: not-allowed; }
.stepper__val {
  min-width: 5rem; display: grid; place-items: center;
  border-inline: var(--rule-hair) solid var(--color-rule);
  font-family: var(--font-dim); font-variant-numeric: tabular-nums;
}
.stepper.is-atmin .stepper__val { color: var(--color-block); }

/* ══ Toggle ═══════════════════════════════════════════════════════════════
 * It is a checkbox. The visual does not change the contract.
 */

.toggle { display: inline-flex; align-items: center; gap: var(--space-sm);
          min-height: var(--control-h); cursor: pointer; }
.toggle input { position: absolute; opacity: 0; width: 0; height: 0; }
.toggle__track {
  flex: none; width: 2.75rem; height: 1.625rem;
  border: var(--rule-hair) solid var(--color-rule-2);
  border-radius: var(--radius-pill);
  background: var(--color-paper-3);
  position: relative;
  transition: background-color var(--dur-fast) var(--ease-out);
}
.toggle__track::after {
  content: ""; position: absolute; inset: 2px auto 2px 2px;
  width: 1.25rem; border-radius: 50%;
  background: var(--color-paper);
  box-shadow: 0 1px 2px oklch(var(--shade) / 0.25);
  transition: transform var(--dur-fast) var(--ease-out);
}
.toggle input:checked + .toggle__track { background: var(--color-ink); border-color: var(--color-ink); }
.toggle input:checked + .toggle__track::after { transform: translateX(1.125rem); }
.toggle input:focus-visible + .toggle__track, .toggle input.is-focus + .toggle__track {
  outline: 2px solid var(--color-focus); outline-offset: 2px; }
.toggle:has(input[disabled]) { cursor: not-allowed; opacity: 0.55; }
.toggle__text { display: grid; gap: 1px; }

/* ══ Segmented control ════════════════════════════════════════════════════ */

.seg {
  display: inline-flex;
  padding: 3px;
  border: var(--rule-hair) solid var(--color-rule-2);
  border-radius: var(--radius-md);
  background: var(--color-paper-3);
}
.seg__opt { position: relative; }
.seg__opt input { position: absolute; inset: 0; opacity: 0; cursor: pointer; }
.seg__opt span {
  display: grid; place-items: center;
  min-height: calc(var(--control-h) - 8px);
  padding-inline: var(--space-md);
  border-radius: var(--radius-sm);
  font-weight: var(--weight-medium);
  white-space: nowrap;
  color: var(--color-neutral);
  transition: background-color var(--dur-fast) var(--ease-out);
}
.seg__opt input:checked + span { background: var(--color-paper); color: var(--color-ink); box-shadow: var(--elev-raised); }
.seg__opt input:focus-visible + span, .seg__opt input.is-focus + span {
  outline: 2px solid var(--color-focus); outline-offset: 1px; }
.seg__opt input[disabled] + span { opacity: 0.45; cursor: not-allowed; }
@media (hover: hover) {
  .seg__opt input:not(:checked):not([disabled]):hover + span { color: var(--color-ink); }
}

/* ══ Option row ═══════════════════════════════════════════════════════════
 * One pattern, six uses. Collapsed rows carry their current value, because six
 * of them stacked is how the framer reads the whole spec at a glance.
 *
 * A row the framer has touched must not look like one still on shop defaults —
 * that distinction is the entire premise of the product. Two channels carry it,
 * neither of them colour alone: a solid left edge, and the value promoted from
 * neutral to ink. Both survive being read from three feet away.
 *
 * The accordion does not animate. One row is open at a time.
 */

.rows { display: grid; border-top: var(--rule-hair) solid var(--color-rule); }

.row { border-bottom: var(--rule-hair) solid var(--color-rule); border-left: 3px solid transparent; }
.row--modified { border-left-color: var(--color-ink); }

.row__head {
  display: grid;
  grid-template-columns: minmax(0, 11rem) minmax(0, 1fr) auto;
  gap: var(--space-md);
  align-items: center;
  width: 100%;
  min-height: var(--control-h);
  padding: var(--space-sm) var(--space-md);
  border: 0; background: none;
  font: inherit; text-align: left; cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-out);
}
@media (hover: hover) { .row__head:hover { background: var(--color-paper-2); } }
.row__head:active { transform: translateY(1px); }
.row__head:focus-visible { outline: 2px solid var(--color-focus); outline-offset: -2px; }
.row__head[disabled] { opacity: 0.5; cursor: not-allowed; }

.row__label { font-weight: var(--weight-medium); white-space: nowrap; }
.row__value {
  font-family: var(--font-dim);
  font-variant-numeric: tabular-nums;
  font-size: var(--text-body);
  color: var(--color-neutral);          /* still on shop defaults */
  overflow-wrap: anywhere;
}
.row--modified .row__value { color: var(--color-ink); font-weight: var(--weight-medium); }

.row__caret { flex: none; color: var(--color-muted); font-size: var(--text-micro); }
.row[open] .row__caret, .row.is-open .row__caret { color: var(--color-ink); }

.row__panel {
  display: none;
  padding: var(--space-xs) var(--space-md) var(--space-lg);
  background: var(--color-paper-2);
  border-top: var(--rule-hair) solid var(--color-rule);
}
.row.is-open > .row__panel { display: block; }
.row.is-open { background: var(--color-paper-2); }

.panel__grid { display: grid; gap: var(--space-lg); }
.panel__title { font-size: var(--text-micro); font-weight: var(--weight-medium);
                letter-spacing: var(--track-label); text-transform: uppercase;
                color: var(--color-muted); margin-bottom: var(--space-xs); }

/* Per-piece reset. Lives with the rows, not in a menu — it is used at the
 * counter, mid-conversation, when a carried-over treatment is wrong. */
.rows__foot { display: flex; flex-wrap: wrap; gap: var(--space-sm);
              align-items: center; justify-content: space-between;
              padding: var(--space-md); }

/* ══ Swatch picker ════════════════════════════════════════════════════════
 * Placeholder swatches, real proportions. Search + filter chips + a result
 * count are present now so scrolling behaviour is a decision rather than a
 * discovery when a real catalogue lands.
 *
 * The bed is paper-3 and every swatch carries a hairline, so a paper-white mat
 * still has an edge. Distinguishing bright white from antique white is the
 * single most common comparison made at this counter.
 */

.picker { display: grid; gap: var(--space-sm); }
.picker__bar { display: flex; flex-wrap: wrap; gap: var(--space-xs); align-items: center; }
.picker__search { flex: 1 1 12rem; min-width: 0; }
.chip {
  display: inline-flex; align-items: center; gap: var(--space-2xs);
  min-height: 2rem; padding-inline: var(--space-sm);
  border: var(--rule-hair) solid var(--color-rule-2);
  border-radius: var(--radius-pill);
  background: var(--color-paper);
  font: inherit; font-size: var(--text-micro);
  color: var(--color-neutral); cursor: pointer; white-space: nowrap;
}
.chip[aria-pressed="true"] { background: var(--color-ink); border-color: var(--color-ink); color: var(--color-ink-ink); }
.chip:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 2px; }
@media (hover: hover) { .chip:hover { border-color: var(--color-ink-2); } }

.swatches {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(76px, 1fr));
  gap: var(--space-xs);
  max-height: 15rem;
  overflow-y: auto;
  padding: var(--space-xs);
  background: var(--color-paper-3);
  border-radius: var(--radius-md);
}
.swatch {
  display: grid; gap: var(--space-2xs);
  padding: 0; border: 0; background: none; font: inherit;
  cursor: pointer; text-align: left; min-width: 0;
}
.swatch__chip {
  aspect-ratio: 1;
  border: var(--rule-hair) solid var(--color-rule-2);
  border-radius: var(--radius-sm);
  background: var(--swatch, var(--color-paper));
}
.swatch__name { font-size: var(--text-micro); color: var(--color-neutral); line-height: 1.3;
                overflow-wrap: anywhere; }
.swatch[aria-pressed="true"] .swatch__chip { box-shadow: 0 0 0 2px var(--color-ink); border-color: var(--color-ink); }
.swatch[aria-pressed="true"] .swatch__name { color: var(--color-ink); font-weight: var(--weight-medium); }
.swatch:focus-visible .swatch__chip { outline: 2px solid var(--color-focus); outline-offset: 2px; }
.picker__count { font-size: var(--text-micro); color: var(--color-muted); }

/* ══ Material rows — glazing, moulding ═══════════════════════════════════
 * A searchable list of spec'd materials. Mats get a swatch grid because colour
 * IS the choice; glazing and moulding differ by numbers, so they get rows that
 * put those numbers where the swatch would be.
 */

.mat-row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: var(--space-sm) var(--space-md);
  align-items: center;
  width: 100%;
  min-height: var(--control-h);
  padding: var(--space-xs) var(--space-sm);
  border: var(--rule-hair) solid var(--color-rule-2);
  border-radius: var(--radius-md);
  background: var(--color-paper);
  font: inherit; text-align: left; cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-out);
}
@media (hover: hover) { .mat-row:hover { background: var(--color-paper-2); } }
.mat-row:active { transform: translateY(1px); }
.mat-row:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 2px; }
.mat-row[aria-pressed="true"] { border-color: var(--color-ink); background: var(--color-paper-2); }
.mat-row__body { display: grid; gap: 1px; min-width: 0; }
/* A row with no leading swatch (mouldings) spans the slot the swatch would fill,
 * so the two lists align on the same left edge instead of one sitting inset. */
.mat-row > .mat-row__body:first-child { grid-column: 1 / 3; }
.mat-row__name { font-weight: var(--weight-medium); overflow-wrap: anywhere; }
.mat-row[aria-pressed="true"] .mat-row__name::after { content: " \2713"; }
.mat-row__spec { font-family: var(--font-dim); font-size: var(--text-micro);
                 color: var(--color-neutral); }
.mat-row__price { font-family: var(--font-dim); font-variant-numeric: tabular-nums;
                  white-space: nowrap; }
/* `grid-auto-rows: max-content` is load-bearing, not tidying. A scroll container
 * with a max-height shrinks its auto tracks, so each row was squeezed down to
 * `.mat-row`'s 44px min-height and the spec line spilled over the row below it.
 * Content sizes the track; the list scrolls. */
.mat-list { display: grid; grid-auto-rows: max-content; gap: var(--space-2xs);
            max-height: 17rem; overflow-y: auto; padding-right: var(--space-2xs); }

/* Glare, drawn. It is the question a customer actually asks about glazing, and
 * the only one of its four properties that is visible rather than numeric. */
.glaze {
  flex: none; width: 2.25rem; height: 2.75rem;
  border: var(--rule-hair) solid var(--color-rule-2);
  border-radius: var(--radius-sm);
  background: linear-gradient(148deg, var(--preview-artwork), var(--preview-glass));
  position: relative; overflow: hidden;
}
.glaze::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(118deg, transparent 18%,
              oklch(var(--paper-shade) / var(--glare, 0.5)) 34% 46%, transparent 62%);
}

/* ══ Preview ══════════════════════════════════════════════════════════════
 * Proportional at any aspect ratio — every inset below is a percentage of the
 * real frame outside dimension, set from the sizing chain. A layout that only
 * works at 4:5 fails on the second job of the day.
 *
 * The drop shadow is representational: it reads as a physical frame. That is
 * why it belongs to this component and is not an elevation token.
 */

.preview { display: grid; grid-template-rows: minmax(0, 1fr) auto;
           gap: var(--space-md); justify-items: center; min-width: 0; min-height: 0; }
.preview__stage {
  display: grid; place-items: center;
  width: 100%;
  padding: var(--space-lg);
  min-height: 0;
  container-type: size;    /* so the piece can size itself against both axes */
}
/* Fit-inside, at the real aspect ratio, whichever axis binds. `aspect-ratio`
 * alone collapses here — the box has no content and no definite axis — so the
 * width is solved explicitly against the container's own two dimensions. */
.preview__piece {
  position: relative;
  width: min(100cqw, calc(100cqh * var(--out-w) / var(--out-h)));
  aspect-ratio: var(--out-w) / var(--out-h);
  background: var(--preview-moulding);
  border-radius: 2px;
  box-shadow: 0 12px 28px -10px oklch(var(--shade) / 0.35);
}
.preview__piece > * { position: absolute; }

/* The package face — mat surface, or the top mat when there are two. */
.preview__mat {
  inset: var(--mat-y) var(--mat-x);
  background: var(--mat-top, var(--swatch-antique-white));
  box-shadow: inset 0 0 0 1px oklch(var(--shade) / 0.10);
}
/* The under mat. Inset to the TOP mat opening, so what shows of it is exactly
 * the reveal — the ¼″ ring the top mat opening is larger by. Four sides, not
 * two: the borders are independent and a weighted bottom is the common case. */
.preview__under {
  inset: var(--op-t) var(--op-r) var(--op-b) var(--op-l);
  background: var(--mat-under, var(--swatch-black));
}
.preview__under[hidden] { display: none; }
/* The window: artwork seen through the innermost opening. */
.preview__art {
  inset: var(--art-t) var(--art-r) var(--art-b) var(--art-l);
  background: linear-gradient(148deg, var(--preview-artwork), var(--preview-glass));
  box-shadow: inset 0 0 0 1px oklch(var(--shade) / 0.14);
}
.preview__art--empty {
  background: repeating-linear-gradient(45deg,
    var(--color-paper-3) 0 6px, var(--color-paper-2) 6px 12px);
}

/* Pinned numbers. With the overlay off these never leave the screen. */
.preview__pins {
  display: flex; flex-wrap: wrap; gap: var(--space-xs) var(--space-md);
  justify-content: center; align-items: baseline;
}
.preview__pins .dim { font-size: var(--text-body); }
.pin__sep { color: var(--color-rule-2); }

/* ══ Corner detail, 6× ════════════════════════════════════════════════════
 * At bench scale a ¼″ reveal is one to three pixels: visible as a line, not
 * legible as a width. The main preview stays true — the reveal renders as the
 * hairline it actually is — and this inset carries the legibility, at a
 * declared magnification, the way a framer already judges a corner sample.
 *
 * Window is 1½″ square: ¾″ of top mat, the reveal, then artwork.
 */

/* A strip, not a panel. The preview's band is fixed and the detail must never
 * be what shrinks it — at tablet that is the difference between a legible
 * preview and a stamp. */
.corner {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--space-sm) var(--space-md);
  align-items: center;
  padding: var(--space-sm);
  background: var(--color-paper-2);
  border: var(--rule-hair) solid var(--color-rule);
  border-radius: var(--radius-md);
}
.corner__box {
  position: relative;
  width: 5.5rem; aspect-ratio: 1;      /* 1½″ at 6× the bench preview */
  background: var(--mat-top, var(--swatch-antique-white));
  border: var(--rule-hair) solid var(--color-rule-2);
  border-radius: var(--radius-sm);
  overflow: hidden;
}
/* Under mat: the corner ¾″ of the window. Artwork: the corner ½″. The ¼″
 * L-band between them is the reveal, at 6× — about 20px, judgeable by eye. */
.corner__under { position: absolute; right: 0; bottom: 0; width: 50%; height: 50%;
                 background: var(--mat-under, var(--swatch-black)); }
.corner__under[hidden] { display: none; }
.corner__art   { position: absolute; right: 0; bottom: 0; width: 33.333%; height: 33.333%;
                 background: linear-gradient(148deg, var(--preview-artwork), var(--preview-glass)); }
.corner__legend { display: grid; gap: var(--space-2xs); font-size: var(--text-micro); min-width: 0; }
.corner__legend ul { margin: 0; padding: 0; display: flex; flex-wrap: wrap;
                     gap: var(--space-2xs) var(--space-md); }
.corner__legend li { display: inline-flex; gap: var(--space-2xs); align-items: center;
                     list-style: none; white-space: nowrap; }
.corner__key { flex: none; width: 0.75rem; height: 0.75rem; border-radius: 2px;
               border: var(--rule-hair) solid var(--color-rule-2); }
.corner__scale { font-family: var(--font-dim); color: var(--color-muted); }
.corner__note { color: var(--color-muted); max-width: 60ch; line-height: 1.45; }
@media (max-width: 900px) {
  .corner__box { width: 4.25rem; }
  .corner__note { display: none; }   /* the strip may not cost the preview height */
}

/* ══ Dimension overlay ════════════════════════════════════════════════════
 * Dimension lines and leaders attached to the edges they describe, not a
 * floating table. The overlay takes over the preview region rather than
 * layering on the drawing: the piece steps back into a reserved gutter so no
 * leader ever crosses the artwork or another label.
 *
 * Default is off. The customer is looking at this and dimension lines mean
 * nothing to them. Not sticky — each new piece starts as a sales conversation.
 */

.preview.is-overlay .preview__stage { padding: var(--space-2xl) var(--space-2xl) var(--space-xl); }
.preview.is-overlay .preview__piece { max-width: 74%; }

/* The overlay layer spans the piece, so every leader anchors to the real edge
 * rather than to a collapsed box in the corner. */
.preview__ovl { inset: 0; pointer-events: none; }
.preview__ovl[hidden] { display: none; }
.ovl { position: absolute; pointer-events: none; }
.ovl__label {
  font-family: var(--font-dim); font-variant-numeric: tabular-nums;
  font-size: var(--text-micro); line-height: 1;
  color: var(--color-ink);
  background: var(--color-paper);
  padding: 2px 5px; border-radius: 2px;
  white-space: nowrap;
  box-shadow: 0 0 0 1px var(--color-rule);
}
/* A dimension line: a rule with tick ends. Position comes from the element's
 * own inline top/right/bottom/left, driven by the same percentages as the
 * render — so a leader can never drift off the edge it describes. */
.ovl--h { border-top: 1px solid var(--color-ink-2); display: grid; place-items: center; }
.ovl--v { border-left: 1px solid var(--color-ink-2); display: grid; place-items: center; }
.ovl--h::before, .ovl--h::after { content: ""; position: absolute; top: -4px; height: 9px;
                                  border-left: 1px solid var(--color-ink-2); }
.ovl--h::before { left: 0; } .ovl--h::after { right: 0; }
.ovl--v::before, .ovl--v::after { content: ""; position: absolute; left: -4px; width: 9px;
                                  border-top: 1px solid var(--color-ink-2); }
.ovl--v::before { top: 0; } .ovl--v::after { bottom: 0; }
.ovl--v > .ovl__label { transform: translateX(calc(50% + 5px)); }
/* The buried artwork edge — coverage is the only dimension you cannot see,
 * so it is the only one drawn as a ghost rather than measured off a visible
 * boundary. */
.ovl--ghost { border: 1px dashed var(--color-block); }
.ovl--ghost > .ovl__label { position: absolute; top: -0.55rem; left: var(--space-xs);
                            color: var(--color-block); box-shadow: 0 0 0 1px var(--color-block); }

/* ══ Validation ═══════════════════════════════════════════════════════════
 * Three severities, three treatments. Block never dead-ends: the remedies are
 * controls inside the message, not prose. Info does not argue — cropping into
 * the image is legal and often deliberate, so it states the fact and stops.
 */

.flag {
  display: grid;
  grid-template-columns: 1.25rem minmax(0, 1fr);
  gap: var(--space-xs) var(--space-sm);
  padding: var(--space-sm) var(--space-md);
  border-radius: var(--radius-md);
  border-left: 3px solid var(--color-rule-2);
  background: var(--color-info-surface);
  font-size: var(--text-body);
  line-height: var(--lh-body);
}
.flag__icon { font-weight: var(--weight-strong); line-height: var(--lh-body); }
.flag__body { min-width: 0; }
.flag--block { background: var(--color-block-surface); border-left-color: var(--color-block); color: var(--color-block); }
.flag--warn  { background: var(--color-warn-surface);  border-left-color: var(--color-warn);  color: var(--color-warn); }
.flag--info  { background: var(--color-info-surface);  border-left-color: var(--color-rule-2); color: var(--color-neutral); }
.flag--block .flag__body strong, .flag--warn .flag__body strong { font-weight: var(--weight-strong); }

.remedies { display: flex; flex-wrap: wrap; gap: var(--space-xs); margin-top: var(--space-sm); }
.remedy {
  display: grid; gap: 2px; text-align: left;
  padding: var(--space-xs) var(--space-sm);
  min-height: var(--control-h);
  border: var(--rule-hair) solid var(--color-block);
  border-radius: var(--radius-md);
  background: var(--color-paper);
  color: var(--color-ink); font: inherit; cursor: pointer;
}
.remedy__name { font-weight: var(--weight-medium); white-space: nowrap; }
.remedy__out  { font-family: var(--font-dim); font-size: var(--text-micro); color: var(--color-neutral); }
.remedy[aria-pressed="true"] { background: var(--color-ink); border-color: var(--color-ink); color: var(--color-ink-ink); }
.remedy[aria-pressed="true"] .remedy__out { color: var(--color-ink-ink); opacity: 0.85; }
.remedy:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 2px; }
@media (hover: hover) { .remedy:hover { background: var(--color-paper-2); } }

/* The depth ledger. A pass is plain ink with a check — the absence of a block
 * is the pass signal, and a green would be the fourth semantic colour. */
.ledger { width: 100%; border-collapse: collapse; font-family: var(--font-dim);
          font-variant-numeric: tabular-nums; font-size: var(--text-micro); }
.ledger td { padding: 3px 0; }
.ledger td:last-child { text-align: right; }
.ledger tr.is-total td { border-top: var(--rule-hair) solid var(--color-rule-2);
                         font-weight: var(--weight-strong); padding-top: 5px; }
.ledger tr.is-limit td { color: var(--color-muted); }
.ledger tr.is-verdict td { padding-top: var(--space-xs); font-weight: var(--weight-strong); }
.ledger tr.is-verdict.is-bind td { color: var(--color-block); }

/* ══ Price region ═════════════════════════════════════════════════════════
 * Always visible, updates on every change. The value tracks a slider drag
 * live — it must, or the framer cannot price by feel. The change highlight is
 * debounced to the end of the drag, so the region never strobes.
 */

.total { display: grid; gap: var(--space-2xs); justify-items: start; }
.total__amount {
  font-family: var(--font-dim);
  font-variant-numeric: tabular-nums;
  font-size: var(--text-display);
  font-weight: var(--weight-medium);
  line-height: 1;
  letter-spacing: -0.02em;
  color: var(--color-ink);
  transition: color var(--dur-base) var(--ease-out);
}
.total.is-changed .total__amount { color: var(--color-ink-2); }
.total__meta { font-size: var(--text-micro); color: var(--color-muted); }

/* ══ Client slot and piece strip ══════════════════════════════════════════
 * The empty client slot reads as an obligation, not a failure. The framer may
 * legitimately work for ten minutes before taking a name. It uses neither the
 * block nor the warn treatment; it blocks only the save.
 */

.client {
  display: flex; align-items: center; gap: var(--space-sm);
  min-height: var(--control-h);
  padding: var(--space-xs) var(--space-md);
  border: 1px dashed var(--color-rule-2);
  border-radius: var(--radius-md);
  background: none; font: inherit; color: var(--color-neutral); cursor: pointer;
  white-space: nowrap;
}
.client.is-filled { border-style: solid; border-color: var(--color-rule); color: var(--color-ink);
                    background: var(--color-paper-2); }
.client:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 2px; }
@media (hover: hover) { .client:hover { border-color: var(--color-ink-2); color: var(--color-ink); } }

.strip { display: flex; align-items: center; gap: var(--space-xs); min-width: 0;
         overflow-x: auto; padding-bottom: 2px; }
.piece {
  display: grid; grid-template-columns: auto minmax(0, 1fr); gap: var(--space-xs);
  align-items: center;
  min-height: var(--control-h); padding: var(--space-2xs) var(--space-sm) var(--space-2xs) var(--space-2xs);
  border: var(--rule-hair) solid var(--color-rule-2);
  border-radius: var(--radius-md);
  background: var(--color-paper); font: inherit; cursor: pointer;
  white-space: nowrap;
  flex: none;   /* never shrink — `· carried` truncates first, and it is the point */
}
.piece[aria-current="true"] { border-color: var(--color-ink); background: var(--color-paper-2); }
.piece:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 2px; }
.piece__thumb { width: 1.75rem; height: 2.125rem; border-radius: 2px;
                background: var(--preview-moulding); position: relative; }
.piece__thumb::after { content: ""; position: absolute; inset: 18%;
                       background: var(--swatch-antique-white); }
.piece__n { font-size: var(--text-micro); color: var(--color-muted); }
.piece__price { font-family: var(--font-dim); font-size: var(--text-micro); }
.piece--carried .piece__n::after { content: " · carried"; color: var(--color-neutral); }

/* ══ The bench ════════════════════════════════════════════════════════════
 * Three things compete for the customer's eye. Ranked: the preview is the
 * sales object, the price is the decision, the rows are the framer's
 * instrument. The layout puts preview and price together on the customer's
 * side and the rows on the framer's — a bench where the rows dominate has
 * inverted the premise of the product.
 */

.app { display: grid; grid-template-rows: auto minmax(0, 1fr); min-block-size: 100dvh; }
/* The design file wraps the app in its own nav chrome. The bench still owns
 * exactly one viewport — it is a counter terminal, and it does not scroll. */
/* Height, not min-height: a counter terminal owns one viewport and does not
 * scroll as a page. The rows scroll inside their own pane; nothing else does. */
body:has(.app) { height: 100dvh; overflow: hidden; display: flex; flex-direction: column; }
body:has(.app) .app { flex: 1 1 auto; min-block-size: 0; }

.app__bar {
  display: flex; flex-wrap: wrap; gap: var(--space-md);
  align-items: center; justify-content: space-between;
  padding: var(--space-sm) var(--space-lg);
  border-bottom: var(--rule-hair) solid var(--color-rule);
  background: var(--color-paper);
  position: sticky; top: 0; z-index: var(--z-sticky);
}
.app__bar-group { display: flex; align-items: center; gap: var(--space-sm); min-width: 0; }
.wordmark { font-weight: var(--weight-strong); letter-spacing: var(--track-tight); white-space: nowrap; }

/* Desk: 58 / 42. Not a half — the sales object gets the larger share. */
.app__body { display: grid; grid-template-columns: minmax(0, 58fr) minmax(0, 42fr); min-height: 0; }

.stage {
  display: grid; grid-template-rows: minmax(0, 1fr) auto;
  min-height: 0; min-width: 0;
  border-right: var(--rule-hair) solid var(--color-rule);
  background: var(--color-paper-2);
}
.stage__foot {
  display: flex; flex-wrap: wrap; gap: var(--space-md);
  align-items: flex-end; justify-content: space-between;
  padding: var(--space-lg);
  border-top: var(--rule-hair) solid var(--color-rule);
  background: var(--color-paper);
}

.bench { display: grid; grid-template-rows: auto minmax(0, 1fr) auto;
         min-height: 0; min-width: 0; background: var(--color-paper); }
.bench__scroll { overflow-y: auto; min-height: 0; }
.bench__head { display: flex; align-items: center; justify-content: space-between;
               gap: var(--space-md); padding: var(--space-md) var(--space-md) var(--space-xs); }
.bench__foot { display: grid; gap: var(--space-sm);
               padding: var(--space-sm) var(--space-lg);
               border-top: var(--rule-hair) solid var(--color-rule); }
.bench__flags { display: grid; gap: var(--space-xs); }
.bench__flags:not(:empty) { padding: var(--space-md) var(--space-md) 0; }
.bench__flags .ledger { max-width: 20rem; }

/* ── Tablet: the hard split ────────────────────────────────────────────────
 * The preview owns a fixed 45% band that never resizes. A preview that shrinks
 * as the rows scroll changes the colour relationship the customer is judging
 * the mat against — which is the one job the preview has. The rows fit the
 * remaining pane because only one is ever open: six collapsed heads plus one
 * panel, in their own scroll context.
 */
@media (max-width: 900px) {
  .app__body { grid-template-columns: minmax(0, 1fr); grid-template-rows: 45fr 55fr; }
  .stage { border-right: 0; border-bottom: 2px solid var(--color-rule); }
  .preview__stage { padding: var(--space-md); }
  .preview.is-overlay .preview__stage { padding: var(--space-xl); }
  .preview.is-overlay .preview__piece { max-width: 68%; }
  .stage__foot { padding: var(--space-sm) var(--space-md); }
  .total__amount { font-size: var(--text-heading); }
  .row__head { grid-template-columns: minmax(0, 9rem) minmax(0, 1fr) auto; }
  .app__bar { padding: var(--space-xs) var(--space-md); gap: var(--space-sm); }
  .app__bar-group { gap: var(--space-xs); }
  .bench__flags:not(:empty) { padding: var(--space-sm) var(--space-sm) 0; }
}

@media (max-width: 700px) {
  .row__head { grid-template-columns: minmax(0, 1fr) auto; row-gap: var(--space-2xs); }
  .row__label { grid-column: 1; }
  .row__value { grid-column: 1; }
  .row__caret { grid-column: 2; grid-row: 1 / 3; }
  .slider { grid-template-columns: minmax(0, 1fr); }
  .app__body { grid-template-rows: 40fr 60fr; }
}

/* ══ Document pages ═══════════════════════════════════════════════════════
 * Foundations, handoff, wireframes and the states matrix are read, not
 * operated. They get a measure, not a bench.
 */

.page { max-width: 78rem; margin-inline: auto; padding: var(--space-xl) var(--space-lg) var(--space-2xl); }
.page__head { display: grid; gap: var(--space-xs); margin-bottom: var(--space-xl);
              padding-bottom: var(--space-md); border-bottom: 2px solid var(--color-ink); }
.page__head p { max-width: 68ch; color: var(--color-neutral); }
.page h2 { font-size: var(--text-heading); margin-top: var(--space-2xl); margin-bottom: var(--space-md);
           padding-bottom: var(--space-xs); border-bottom: var(--rule-hair) solid var(--color-rule); }
.page h3 { font-size: var(--text-md); margin-top: var(--space-xl); margin-bottom: var(--space-xs); }
.page p + p { margin-top: var(--space-sm); }
.page__lede { max-width: 68ch; color: var(--color-neutral); }

.specimen { display: grid; gap: var(--space-md); padding: var(--space-lg);
            background: var(--color-paper-2); border-radius: var(--radius-lg);
            border: var(--rule-hair) solid var(--color-rule); }
.specimen--tight { padding: var(--space-md); }

.grid-2 { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(22rem, 100%), 1fr)); gap: var(--space-lg); }
.grid-3 { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(16rem, 100%), 1fr)); gap: var(--space-md); }

.tbl { width: 100%; border-collapse: collapse; font-size: var(--text-body); }
.tbl th, .tbl td { text-align: left; padding: var(--space-xs) var(--space-sm);
                   border-bottom: var(--rule-hair) solid var(--color-rule); vertical-align: top; }
.tbl th { font-size: var(--text-micro); font-weight: var(--weight-medium);
          letter-spacing: var(--track-label); text-transform: uppercase; color: var(--color-muted); }
.tbl td.dim, .tbl td .dim { font-family: var(--font-dim); font-variant-numeric: tabular-nums; }
.tbl tbody tr:last-child td { border-bottom: 0; }

/* An annotation. Everything the mockups cannot carry visually is written on
 * them here rather than lost between the file and the developer. */
.note {
  display: grid; grid-template-columns: auto minmax(0, 1fr); gap: var(--space-xs) var(--space-sm);
  padding: var(--space-sm) var(--space-md);
  border: 1px dashed var(--color-rule-2);
  border-radius: var(--radius-md);
  background: var(--color-paper);
  font-size: var(--text-micro);
  line-height: 1.5;
  color: var(--color-neutral);
  max-width: 72ch;
}
/* Not nowrap: an `auto` grid track floors at min-content, so a tag that cannot
 * wrap sizes the column to its longest word and pushes the note past its
 * container. Wrapping is what keeps a four-word tag from breaking the layout. */
.note__tag { font-weight: var(--weight-strong); letter-spacing: var(--track-label);
             text-transform: uppercase; color: var(--color-ink); text-wrap: balance; }
.note strong { color: var(--color-ink); font-weight: var(--weight-medium); }
.note code, .page code { font-family: var(--font-dim); font-size: 0.95em;
                         background: var(--color-paper-3); padding: 1px 4px; border-radius: 3px; }

.state-caption { display: flex; flex-wrap: wrap; gap: var(--space-xs); align-items: baseline;
                 margin-bottom: var(--space-xs); }
.state-caption__n { font-family: var(--font-dim); font-size: var(--text-micro); color: var(--color-muted); }
.state-caption__t { font-weight: var(--weight-medium); }

/* A framed mockup on a document page — the app shell rendered at a fixed size
 * so desk and tablet can sit side by side on one sheet. */
.frame-desk, .frame-tablet {
  border: var(--rule-hair) solid var(--color-rule-2);
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--color-paper);
  box-shadow: var(--elev-raised);
}
.frame-cap { display: flex; justify-content: space-between; gap: var(--space-sm);
             font-size: var(--text-micro); color: var(--color-muted);
             font-family: var(--font-dim); margin-bottom: var(--space-2xs); }

/* Wireframe register for Tier 2 and Tier 3: no fills, no swatch colour, the
 * structure and the annotations only. */
.wire { background: var(--color-paper); border: var(--rule-hair) solid var(--color-rule-2);
        border-radius: var(--radius-md); padding: var(--space-md); display: grid; gap: var(--space-sm); }
.wire__box { border: 1px dashed var(--color-rule-2); border-radius: var(--radius-sm);
             padding: var(--space-sm); display: grid; gap: var(--space-xs); min-width: 0; }
.wire__bar { height: 0.6rem; border-radius: 2px; background: var(--color-paper-3); }
.wire__bar--short { width: 38%; }
.wire__bar--mid   { width: 62%; }
.wire__ph { display: grid; place-items: center; min-height: 4rem;
            background: repeating-linear-gradient(45deg, var(--color-paper-2) 0 6px, var(--color-paper-3) 6px 12px);
            border-radius: var(--radius-sm); font-size: var(--text-micro); color: var(--color-muted); }

.skip { position: absolute; left: -9999px; }
.skip:focus { left: var(--space-md); top: var(--space-md); z-index: var(--z-modal);
              background: var(--color-ink); color: var(--color-ink-ink);
              padding: var(--space-xs) var(--space-sm); border-radius: var(--radius-sm); }

/* Cross-page nav for the design file itself. Not part of the product. */
.filenav { display: flex; flex-wrap: wrap; gap: var(--space-2xs) var(--space-md);
           padding: var(--space-xs) var(--space-lg);
           border-bottom: var(--rule-hair) solid var(--color-rule);
           background: var(--color-paper-2);
           font-size: var(--text-micro); }
.filenav a { color: var(--color-neutral); text-decoration: none; white-space: nowrap;
             padding: var(--space-2xs) 0; }
.filenav a[aria-current="page"] { color: var(--color-ink); font-weight: var(--weight-medium); }
@media (hover: hover) { .filenav a:hover { color: var(--color-ink); text-decoration: underline; } }
.filenav a:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 2px; }
@media (max-width: 900px) {
  .filenav { padding: var(--space-2xs) var(--space-md); gap: 0 var(--space-sm); font-size: 0.7rem; }
}
