/*
 * components.css — the app's component set, as CSS.
 *
 * One class per component in `Modera/DesignSystem/`, with the same name and
 * the same constraints. Where the SwiftUI version makes a mistake unspellable
 * through its API, the note says how that is enforced here — usually by
 * `src/ui.js` owning the markup so a call site cannot hand-roll a variant.
 *
 * Nothing in this file contains a raw colour, size, radius or spacing value.
 * Every one traces to a token in tokens.css. That is rubric H8 and it is the
 * rule that keeps a port from drifting into a different product.
 */

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

html { -webkit-text-size-adjust: 100%; }

body {
  min-height: 100dvh;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

img, canvas, svg { display: block; max-width: 100%; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }
a { color: inherit; text-decoration: none; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: var(--radius-marker); }

/* ─────────────────────────────── Type ───────────────────────────────
 *
 * A view never calls `.font(...)`; it applies a token. Same here — these
 * eight classes are the only places type is set, and there is no ninth.
 *
 * Banned treatments, enforced: all-caps labels, eyebrow labels stacked above
 * a heading, and meta strings joined with middle dots. No component in
 * `src/ui.js` accepts a string above its title, and `DSCopy.check` in that
 * file prints a named warning for a middle dot, a pipe or an all-caps run.
 */
.ds-display   { font: var(--type-display);    letter-spacing: var(--track-display); }
.ds-title     { font: var(--type-title);      letter-spacing: var(--track-title); }
.ds-heading   { font: var(--type-heading);    letter-spacing: var(--track-heading); }
.ds-body-lg   { font: var(--type-body-large); letter-spacing: var(--track-body-large); }
.ds-body      { font: var(--type-body);       letter-spacing: var(--track-body); }
.ds-label     { font: var(--type-label);      letter-spacing: var(--track-label); }
.ds-caption   { font: var(--type-caption);    letter-spacing: var(--track-caption); }
.ds-price     { font: var(--type-price);      letter-spacing: var(--track-price); color: var(--text); }

.ds-secondary { color: var(--text-secondary); }

/* ────────────────────────────── Surfaces ─────────────────────────────
 *
 * `dsPanel`. In modern it is material over the lit ground, with a specular
 * rim along the top edge — that rim is what separates glass from a grey
 * rectangle, and it is why the ground had to stop being flat first.
 */
/*
 * A pane of glass, in three layers.
 *
 *   THE FILL is the frosted body: the ground blurred and saturated behind a
 *   warm tint that is brightest where the key light falls.
 *
 *   ::before IS THE EDGE. A second, *more strongly* blurred and slightly
 *   scaled copy of the backdrop, masked to a band a couple of pixels wide
 *   around the border. That extra blur against the fill's is what a thick
 *   edge does to what is behind it — the ground showing through near the
 *   border does not line up with the ground showing through the middle. It is
 *   the cheapest honest refraction a browser can draw.
 *
 *   ::after IS THE RIM. A one-pixel gradient border that travels from bright
 *   at the corner nearest the key to nothing across the shape, and picks up
 *   again warm at the far edge. A rim at one uniform opacity is the tell of a
 *   CSS panel; an uneven one is the tell of an object.
 *
 * `isolation: isolate` so the layers compose against each other and not
 * against whatever the panel happens to be sitting over.
 */
.ds-panel {
  position: relative;
  background: var(--glass-fill);
  backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  border-radius: var(--radius-sheet);
  box-shadow: var(--elevation-floating-modern), var(--glass-inner);
  isolation: isolate;
}
.ds-panel::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  backdrop-filter: blur(calc(var(--glass-blur) * 1.9)) saturate(1.2);
  -webkit-backdrop-filter: blur(calc(var(--glass-blur) * 1.9)) saturate(1.2);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  padding: calc(var(--glass-edge-warp) * 3);
  pointer-events: none;
}
.ds-panel::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1px;
  background: var(--glass-rim);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  pointer-events: none;
}
.ds-panel--card { border-radius: var(--radius-card); box-shadow: var(--elevation-card-modern), var(--glass-inner); }

/* Everything smaller than a panel — a chip, a disc, a capsule — gets the same
 * material at the same three layers, without repeating them per component. */
.glassy {
  position: relative;
  background: var(--glass-fill);
  backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  box-shadow: var(--elevation-card-modern), var(--glass-inner);
  isolation: isolate;
}
.glassy::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1px;
  background: var(--glass-rim);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  pointer-events: none;
}

/* Opaque, for the rare surface that has nothing worth refracting behind it. */
.ds-solid {
  background: var(--surface);
  border-radius: var(--radius-card);
  box-shadow: var(--elevation-card-modern);
}

/* `DSSceneSurface` — the render plate. Scene radius, backdrop, floating. */
.ds-scene-surface {
  position: relative;
  background: var(--scene-backdrop);
  border-radius: var(--radius-scene);
  box-shadow: var(--elevation-floating-modern);
  overflow: hidden;
}

/* A well: an image placeholder or an empty slot. The one surface darker than
 * its ground in light. */
.ds-sunken { background: var(--surface-sunken); border-radius: var(--radius-thumbnail); }

/* ────────────────────────────── Actions ──────────────────────────────
 *
 * `DSPrimaryAction` is the ONLY accent in the system and there is one per
 * screen. In modern it runs accent → accentDeep with accentGlow beneath.
 */
.ds-primary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-xs);
  width: 100%;
  min-height: 52px;
  padding: 0 var(--space-lg);
  border-radius: var(--radius-control);
  background: linear-gradient(180deg, var(--accent), var(--accent-deep));
  color: var(--on-accent);
  font: var(--type-label);
  box-shadow: 0 8px 22px var(--accent-glow), var(--elevation-card-modern);
  transition: transform var(--motion-tap), box-shadow var(--motion-tap), filter var(--motion-tap);
}
.ds-primary:hover { filter: brightness(1.04); }
.ds-primary:active { transform: scale(0.985); box-shadow: 0 4px 12px var(--accent-glow); }
.ds-primary[disabled] { opacity: 0.45; cursor: default; box-shadow: none; }

/* `DSSecondaryAction` — the other button, so "not orange" has an obvious
 * right answer. Glass capsule in modern, never accent. */
.ds-secondary-action {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-xs);
  width: 100%;
  min-height: 48px;
  padding: 0 var(--space-lg);
  border-radius: var(--radius-control);
  color: var(--text);
  font: var(--type-label);
  transition: transform var(--motion-tap), box-shadow var(--motion-tap);
}
.ds-secondary-action:hover { box-shadow: var(--elevation-floating-modern), var(--glass-inner); }
.ds-secondary-action:active { transform: scale(0.985); }

/* `DSIconButton` — a bare glyph. A glass disc on a flat ground in modern. */
.ds-icon-button {
  display: inline-grid;
  place-items: center;
  width: 40px;
  height: 40px;
  border-radius: var(--radius-control);
  color: var(--text);
  transition: transform var(--motion-tap);
}
.ds-icon-button:active { transform: scale(0.94); }
.ds-icon-button svg { width: 18px; height: 18px; }

/* `DSChip` — a metadata chip. Over imagery only when it describes the space. */
.ds-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xxs);
  padding: var(--space-xxs) var(--space-sm);
  border-radius: var(--radius-control);
  font: var(--type-caption);
  color: var(--text-secondary);
  white-space: nowrap;
  transition: color var(--motion-tap), box-shadow var(--motion-tap);
}
.ds-chip--selected { color: var(--text); box-shadow: var(--elevation-card-modern), inset 0 0 0 1.5px var(--text); }

/*
 * A chip standing on the render rather than on the page.
 *
 * The same glass over a dark room is a dark capsule with dark text in it, and
 * the label disappears. `DSChip(over:)` in the app takes a substrate for this
 * reason; here the substrate is always the scene, so it is one modifier. Text
 * goes to `onScrim`, which is the token for type over imagery.
 */
.ds-chip--over {
  background: var(--scrim);
  color: var(--on-accent);
  box-shadow: none;
}
.ds-chip--over.ds-chip--selected {
  color: var(--on-accent);
  box-shadow: inset 0 0 0 1.5px var(--on-accent);
}

/* ───────────────────────── The create sequence ──────────────────────
 *
 * `SceneStepPanel`. One shape between every step: a panel along the bottom,
 * out of the way of the room. The room is the content; the interface is a
 * thin layer of light over it.
 *
 * THE PANEL STOPS. On a wide window this was a band the width of the viewport
 * with a heading at one end and a button marooned at the other. A step is one
 * column of instruction and one action under it, so it is held to the width
 * where those still read as one statement and the room gets the rest.
 */
.step-panel {
  width: min(100%, var(--panel-column));
  margin-inline: auto;
  padding: var(--space-lg);
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  /*
   * ONE ORCHESTRATED MOTION, AND ONLY ONE. The app's system has two everyday
   * curves and is explicit that scattered ambient animation and an entrance
   * transition on every card are the generic default. This is the sequence's
   * own moment — a step replacing a step — and nothing else in the package
   * animates on appear.
   */
  animation: panel-arrive var(--motion-arrive) both;
}

@keyframes panel-arrive {
  from { opacity: 0; transform: translateY(10px) scale(0.985); }
  to { opacity: 1; transform: none; }
}
.step-panel > .ds-heading { color: var(--text); }
/*
 * A chip is a chip, not a field. The panel is a flex column, so a lone chip
 * stretched to the full width and — once it had a glass fill behind it — read
 * as a text input. It sizes to its label.
 */
.step-panel > .ds-chip { align-self: flex-start; }

/* `CreateStepTrail` — where you are, as a row of short capsules. Never
 * accent: progress is not an action. */
.step-trail { display: flex; gap: var(--space-xxs); padding: var(--space-xs) var(--space-sm); }
.step-trail i {
  display: block;
  width: var(--space-lg);
  height: 2px;
  border-radius: var(--radius-control);
  background: color-mix(in srgb, var(--text) 20%, transparent);
  transition: background var(--motion-transition);
}
.step-trail i[data-done] { background: var(--text); }

/* `SwatchStrip` — a horizontal run of choices whose subject is a colour or a
 * set of them. Horizontal so a set that grows from four options to twelve
 * does not change the layout. Selection is a ring in ink, never the accent. */
.swatch-strip {
  display: flex;
  gap: var(--space-sm);
  overflow-x: auto;
  padding-block: var(--space-xxs);
  scrollbar-width: none;
}
.swatch-strip::-webkit-scrollbar { display: none; }
.swatch {
  flex: 0 0 auto;
  width: calc(var(--thumbnail) + var(--space-md));
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-xxs);
}
.swatch figure {
  width: var(--thumbnail);
  height: var(--thumbnail);
  border-radius: var(--radius-thumbnail);
  overflow: hidden;
  position: relative;
  box-shadow: inset 0 0 0 1px var(--edge);
  transition: box-shadow var(--motion-tap);
  background-size: cover;
  background-position: center;
}
.swatch[aria-pressed='true'] figure { box-shadow: inset 0 0 0 2px var(--text); }
.swatch span {
  font: var(--type-caption);
  color: var(--text-secondary);
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.swatch[aria-pressed='true'] span { color: var(--text); }

/* A rail: drag anywhere along it, and the room is the readout. No number. */
.rail { position: relative; height: var(--space-lg); cursor: pointer; touch-action: none; }
.rail .track { position: absolute; inset: 0; border-radius: var(--radius-control); }
.rail .knob {
  position: absolute;
  top: 0;
  width: var(--space-lg);
  height: var(--space-lg);
  border-radius: var(--radius-control);
  background: var(--surface);
  box-shadow: var(--elevation-card);
  pointer-events: none;
}

/* ────────────────────────── Cards and content ───────────────────────
 *
 * Commerce anatomy, followed closely because it is a solved problem:
 *   1. full-bleed image at the card's radius
 *   2. a like affordance top right OVER the image, and nothing else over it
 *   3. below the image, in plain type: what it is, ONE secondary line, price
 *
 * There is no overlay parameter that spans the image, so no card can wash its
 * own photograph. Renders and product images are never tinted, colour-filtered
 * or gradient-washed — someone is buying real furniture from what they see.
 */
.ds-card { display: flex; flex-direction: column; gap: var(--text-gap); text-align: left; }
.ds-card figure {
  position: relative;
  border-radius: var(--radius-card);
  overflow: hidden;
  background: var(--surface-sunken);
  aspect-ratio: var(--ratio-scene);
}
.ds-card figure > canvas, .ds-card figure > svg, .ds-card figure > img {
  width: 100%; height: 100%; object-fit: cover;
}
.ds-card--square figure { aspect-ratio: 1; }
.ds-card .like { position: absolute; top: var(--space-sm); right: var(--space-sm); }

/* The like affordance. Accent-filled when on — and this is the documented
 * exception to the budget being about buttons: a heart is a toggle on a
 * user's own object, not a claim about what the screen is for. It is the
 * same call the app makes on Home. */
.like {
  display: inline-grid;
  place-items: center;
  width: 36px;
  height: 36px;
  border-radius: var(--radius-control);
  color: var(--on-accent);
  filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.35));
  transition: transform var(--motion-tap);
}
.like svg { width: 22px; height: 22px; fill: none; stroke: currentColor; stroke-width: 1.8; }
.like[aria-pressed='true'] svg { fill: var(--accent); stroke: var(--accent); }
.like:active { transform: scale(0.88); }
.like--ink { color: var(--text); filter: none; }

/* `DSEmptyState` — an invitation with a route out. The action is not optional. */
.ds-empty {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-sm);
  padding: var(--space-xl) var(--space-lg);
  max-width: var(--panel-column);
}

/* A marker over the scene: a small label at marker radius. */
.scene-marker {
  position: absolute;
  padding: var(--space-xxs) var(--space-xs);
  border-radius: var(--radius-marker);
  background: var(--scrim);
  color: var(--on-accent);
  font: var(--type-caption);
  pointer-events: none;
  transform: translate(-50%, -50%);
  white-space: nowrap;
}

/* ─────────────────────────── Layout helpers ─────────────────────────── */
.content-margin { padding-inline: var(--content-margin); }
.screen-margin { padding-inline: var(--screen-margin); }
.column { width: min(100%, var(--panel-column)); margin-inline: auto; }
.stack { display: flex; flex-direction: column; }
.row { display: flex; align-items: center; }
.grow { flex: 1 1 auto; min-height: 0; }
.spread { justify-content: space-between; }
[hidden] { display: none !important; }

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