/* Ember design system — ONE tokenized system for two densities.
 *
 * ember:task:0089. The contract is PROJECT/design/design-system.json and the gallery that renders
 * every component in every state is client/design-system.html; tools/tests/test_design_system.py
 * measures the rendered result against the contract rather than reading this file back.
 *
 * TWO DENSITIES, NOT ONE SCALED. `compact` and `wide` carry their own numbers. A desktop built as
 * mobile times a constant is a phone layout on a monitor: it wastes the width it was given and it
 * keeps thumb-sized targets under a mouse that can hit four pixels. The guard refuses a wide scale
 * that is any single multiple of the compact one, so the two cannot quietly collapse back together.
 *
 * SIZING IS rem. Every type and target token is expressed against the root font size, so a browser
 * set to 200% text enlarges the whole system instead of overflowing a px-locked shell. Only the
 * hairlines and the safe-area insets are px, because those are device facts rather than text.
 */

:root {
  /* ---- colour: the ownership triple and the state ramp the visual language fixes ---- */
  --ds-ink: #f2eee7;
  --ds-muted: #b4b1ab;
  --ds-faint: #7d7c77;
  --ds-surface: rgba(16, 17, 20, 0.86);
  --ds-surface-raised: rgba(26, 27, 31, 0.94);
  --ds-surface-sunken: rgba(9, 10, 12, 0.92);
  /* The carrier a world mark is read AGAINST, over a live camera. Its alpha is a legibility
   * measurement (tools/visual_pipeline.py scores the composite the ground reaches through), not a
   * taste call — four hundredths of transparency here moves the recorded contrast of every scene. */
  --ds-surface-strong: rgba(12, 13, 15, 0.96);
  --ds-line: rgba(255, 255, 255, 0.14);
  --ds-line-hot: rgba(244, 98, 42, 0.55);
  --ds-accent: #f4622a;
  --ds-accent-soft: #ff9b63;
  --ds-on-accent: #14100d;
  --ds-danger: #ff8a8a;
  --ds-warn: #ffc078;
  --ds-ok: #8fd6a4;

  /* ---- type: the compact scale is the default, because the narrow surface is the one that
   *      cannot absorb a mistake. rem, so 200% text moves all of it. ---- */
  --ds-font-stack: Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  --ds-type-micro: 0.6875rem;
  --ds-type-meta: 0.75rem;
  --ds-type-body: 0.9375rem;
  --ds-type-title: 1.1875rem;
  --ds-type-display: 1.625rem;
  --ds-leading-tight: 1.2;
  --ds-leading-body: 1.45;

  /* ---- space ---- */
  --ds-space-hair: 0.125rem;
  --ds-space-tight: 0.375rem;
  --ds-space-snug: 0.625rem;
  --ds-space-base: 0.875rem;
  --ds-space-loose: 1.25rem;
  --ds-space-room: 1.75rem;
  --ds-gutter: 0.875rem;

  /* ---- shape ---- */
  --ds-radius-sharp: 0.25rem;
  --ds-radius-soft: 0.625rem;
  --ds-radius-round: 999px;
  --ds-border: 1px;
  --ds-focus-ring: 0.125rem;

  /* ---- target: 44px at the default root size, and it grows with the text ---- */
  --ds-hit: 2.75rem;
  --ds-pointer-gap: 0.5rem;

  /* ---- safe area: device facts, so px, and never smaller than the gutter ---- */
  --ds-safe-top: max(1.125rem, env(safe-area-inset-top));
  --ds-safe-right: max(1.125rem, env(safe-area-inset-right));
  --ds-safe-bottom: max(1.125rem, env(safe-area-inset-bottom));
  --ds-safe-left: max(1.125rem, env(safe-area-inset-left));

  /* ---- motion ---- */
  --ds-motion-fast: 90ms;
  --ds-motion-base: 160ms;
  --ds-motion-slow: 260ms;
  --ds-motion-ease: cubic-bezier(0.2, 0.7, 0.3, 1);

  /* ---- elevation ---- */
  --ds-shadow-panel: 0 0.5rem 1.5rem rgba(0, 0, 0, 0.45);
  --ds-shadow-sheet: 0 -0.75rem 2.5rem rgba(0, 0, 0, 0.6);
  --ds-scrim: rgba(6, 6, 8, 0.66);
}

/* THE WIDE DENSITY. Gated on BOTH a fine pointer and the desktop breakpoint: a touch laptop at
 * 1400px keeps thumb-sized targets, and a mouse on a 600px window keeps the compact layout it has
 * room for. Every value below is chosen for a mouse and a wide canvas, not derived from the block
 * above — that independence is the whole of "desktop is not a scaled mobile canvas". */
@media (min-width: 768px) and (pointer: fine) {
  :root {
    --ds-type-meta: 0.8125rem;
    --ds-type-body: 0.875rem;
    --ds-type-title: 1.375rem;
    --ds-type-display: 2.125rem;

    --ds-space-tight: 0.25rem;
    --ds-space-snug: 0.5rem;
    --ds-space-base: 0.75rem;
    --ds-space-loose: 1.125rem;
    --ds-space-room: 2rem;
    --ds-gutter: 1.5rem;

    --ds-radius-sharp: 0.1875rem;
    --ds-radius-soft: 0.5rem;

    --ds-hit: 2rem;
    --ds-pointer-gap: 0.25rem;
  }
}

/* ---------------------------------------------------------------- foundations */

/* border-box across everything the system owns. A `width: 100%` field inside a padded panel is
 * wider than the panel under the default content-box, and the overflow shows up as a clipped label
 * three viewports later rather than as anything you would look for here. */
.ds-root, .ds-root *, .ds-root *::before, .ds-root *::after,
.ds-sheet, .ds-sheet *, .ds-scrim { box-sizing: border-box; }

.ds-root {
  color: var(--ds-ink);
  font-family: var(--ds-font-stack);
  font-size: var(--ds-type-body);
  line-height: var(--ds-leading-body);
  padding-left: var(--ds-safe-left);
  padding-right: var(--ds-safe-right);
}

/* The keyboard's own signal, and never the hover treatment: a focus style borrowed from hover is
 * invisible to exactly the person who cannot hover. */
.ds-btn:focus-visible,
.ds-card:focus-visible,
.ds-nav a:focus-visible,
.ds-field input:focus-visible,
.ds-field textarea:focus-visible,
.ds-field select:focus-visible,
.ds-map:focus-visible,
.ds-ops [tabindex]:focus-visible {
  outline: var(--ds-focus-ring) solid var(--ds-accent-soft);
  outline-offset: var(--ds-focus-ring);
}

/* `hidden` has to beat the component's own display. Every surface in this system is a flex or grid
 * container, and `display: flex` outranks the user-agent's `[hidden] { display: none }` — so a
 * hidden sheet stays laid out over the whole viewport and swallows every click aimed at the page
 * behind it. The element is invisible, the bug is not. */
[hidden] { display: none !important; }

/* Screen-reader-only: present to the accessibility tree, absent from the picture. */
.ds-sr {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ---------------------------------------------------------------- panel */

.ds-panel {
  background: var(--ds-surface);
  border: var(--ds-border) solid var(--ds-line);
  border-radius: var(--ds-radius-soft);
  box-shadow: var(--ds-shadow-panel);
  padding: var(--ds-space-base);
  display: flex;
  flex-direction: column;
  gap: var(--ds-space-snug);
  min-width: 0;
}
.ds-panel[data-state="selected"] { border-color: var(--ds-line-hot); }
/* BUSY IS A BORDER, NOT AN OPACITY. Dimming the whole panel is the obvious way to say "working",
 * and it drags every label on it under its contrast ratio at the exact moment the player is
 * waiting to read one. */
.ds-panel[data-state="busy"] { border-color: var(--ds-muted); border-style: dashed; }
.ds-panel > .ds-eyebrow {
  font-size: var(--ds-type-micro);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ds-faint);
}
.ds-panel > h2, .ds-panel > h3 {
  margin: 0;
  font-size: var(--ds-type-title);
  line-height: var(--ds-leading-tight);
}

/* ---------------------------------------------------------------- sheet
 * One component, two shapes. Coarse pointer: a bottom sheet within thumb reach, its actions above
 * the safe area. Fine pointer on a wide screen: a centred dialog. Neither is the other scaled. */

.ds-sheet {
  position: fixed;
  z-index: 40;
  background: var(--ds-surface-raised);
  border: var(--ds-border) solid var(--ds-line);
  box-shadow: var(--ds-shadow-sheet);
  padding: var(--ds-space-loose);
  display: flex;
  flex-direction: column;
  gap: var(--ds-space-base);
  overflow-y: auto;
  left: var(--ds-safe-left);
  right: var(--ds-safe-right);
  bottom: 0;
  border-radius: var(--ds-radius-soft) var(--ds-radius-soft) 0 0;
  padding-bottom: calc(var(--ds-space-loose) + var(--ds-safe-bottom));
  max-height: 80vh;
}
.ds-sheet > .ds-sheet-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ds-pointer-gap);
  margin-top: auto;
}
/* A short landscape viewport is where a bottom sheet buries its own actions. The sheet gives back
 * its height rather than its buttons. */
@media (orientation: landscape) and (max-height: 520px) {
  .ds-sheet { max-height: 92vh; padding-top: var(--ds-space-snug); }
}
@media (min-width: 768px) and (pointer: fine) {
  .ds-sheet {
    left: 50%;
    right: auto;
    bottom: auto;
    top: 50%;
    transform: translate(-50%, -50%);
    width: min(34rem, calc(100vw - 2 * var(--ds-gutter)));
    max-height: 82vh;
    border-radius: var(--ds-radius-soft);
    padding-bottom: var(--ds-space-loose);
  }
  .ds-sheet > .ds-sheet-actions { justify-content: flex-end; }
}
.ds-scrim {
  position: fixed;
  inset: 0;
  z-index: 39;
  background: var(--ds-scrim);
}

/* ---------------------------------------------------------------- button */

.ds-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--ds-space-tight);
  min-height: var(--ds-hit);
  min-width: var(--ds-hit);
  padding: 0 var(--ds-space-base);
  font-family: inherit;
  font-size: var(--ds-type-body);
  line-height: var(--ds-leading-tight);
  color: var(--ds-ink);
  background: var(--ds-surface-raised);
  border: var(--ds-border) solid var(--ds-line);
  border-radius: var(--ds-radius-sharp);
  cursor: pointer;
  transition: background var(--ds-motion-fast) var(--ds-motion-ease),
              border-color var(--ds-motion-fast) var(--ds-motion-ease);
  /* A label three times its English length must wrap inside the button, never past it. */
  max-width: 100%;
  overflow-wrap: anywhere;
  text-align: center;
}
.ds-btn--primary {
  background: var(--ds-accent);
  border-color: var(--ds-accent);
  color: var(--ds-on-accent);
  font-weight: 600;
}
.ds-btn--quiet { background: transparent; }
/* Hover is an ENHANCEMENT, declared only where hover exists. On a touch surface it would latch on
 * first tap and read as a selection nobody made. */
@media (hover: hover) {
  .ds-btn:hover:not(:disabled) { background: var(--ds-surface); border-color: var(--ds-line-hot); }
  .ds-btn--primary:hover:not(:disabled) { background: var(--ds-accent-soft); }
}
.ds-btn:active:not(:disabled) { transform: translateY(1px); }
.ds-btn:disabled, .ds-btn[aria-disabled="true"] {
  opacity: 0.45;
  cursor: not-allowed;
}
.ds-btn[data-state="busy"] { cursor: progress; }
.ds-btn[aria-pressed="true"], .ds-btn[data-state="selected"] {
  border-color: var(--ds-accent);
  background: var(--ds-surface-sunken);
}
/* Adjacent actions keep the density's pointer gap between them: a 44px target with a hairline
 * beside it is one target as far as a thumb is concerned. */
.ds-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ds-pointer-gap);
}

/* ---------------------------------------------------------------- field */

.ds-field {
  display: flex;
  flex-direction: column;
  gap: var(--ds-space-tight);
  min-width: 0;
}
.ds-field > label {
  font-size: var(--ds-type-meta);
  color: var(--ds-muted);
}
.ds-field input,
.ds-field textarea,
.ds-field select {
  min-height: var(--ds-hit);
  width: 100%;
  padding: var(--ds-space-tight) var(--ds-space-snug);
  font-family: inherit;
  /* 16px floor on a coarse pointer: iOS Safari zooms the whole page on any smaller field, which
   * silently breaks every width claim below. */
  font-size: max(1rem, var(--ds-type-body));
  color: var(--ds-ink);
  background: var(--ds-surface-sunken);
  border: var(--ds-border) solid var(--ds-line);
  border-radius: var(--ds-radius-sharp);
}
@media (min-width: 768px) and (pointer: fine) {
  .ds-field input, .ds-field textarea, .ds-field select { font-size: var(--ds-type-body); }
}
@media (hover: hover) {
  .ds-field input:hover:not(:disabled) { border-color: var(--ds-line-hot); }
}
.ds-field input:disabled, .ds-field textarea:disabled, .ds-field select:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}
.ds-field[data-state="invalid"] input,
.ds-field input[aria-invalid="true"] { border-color: var(--ds-danger); }
.ds-field > .ds-hint { font-size: var(--ds-type-meta); color: var(--ds-faint); }
.ds-field > .ds-error { font-size: var(--ds-type-meta); color: var(--ds-danger); }

/* ---------------------------------------------------------------- nav */

.ds-nav {
  display: flex;
  gap: var(--ds-pointer-gap);
  padding: var(--ds-space-tight);
  background: var(--ds-surface);
  border: var(--ds-border) solid var(--ds-line);
  border-radius: var(--ds-radius-soft);
  overflow-x: auto;
}
.ds-nav a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* Never shrink below the label. In a flex row the default is to squeeze items to fit, so a
     translated label loses its last characters instead of making the nav scroll — which is what
     `overflow-x: auto` above is for. */
  flex: 0 0 auto;
  min-height: var(--ds-hit);
  min-width: var(--ds-hit);
  padding: 0 var(--ds-space-base);
  color: var(--ds-muted);
  text-decoration: none;
  border-radius: var(--ds-radius-sharp);
  white-space: nowrap;
}
@media (hover: hover) { .ds-nav a:hover { color: var(--ds-ink); background: var(--ds-surface-raised); } }
/* CURRENT is not colour alone: a rule under the label survives every colour-vision alternative
 * the visual language commits to. */
.ds-nav a[aria-current="page"] {
  color: var(--ds-ink);
  box-shadow: inset 0 -0.1875rem 0 var(--ds-accent);
}

/* ---------------------------------------------------------------- HUD */

.ds-hud {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--ds-space-base);
  padding: var(--ds-space-snug) var(--ds-space-base);
  background: var(--ds-surface);
  border: var(--ds-border) solid var(--ds-line);
  border-radius: var(--ds-radius-round);
  font-size: var(--ds-type-meta);
}
.ds-hud[data-state="alert"] { border-color: var(--ds-warn); }
.ds-hud .ds-hud-value { color: var(--ds-ink); font-variant-numeric: tabular-nums; }
.ds-hud .ds-hud-label { color: var(--ds-faint); }

/* ---------------------------------------------------------------- map */

.ds-map {
  position: relative;
  display: block;
  width: 100%;
  min-height: 12rem;
  background: var(--ds-surface-sunken);
  border: var(--ds-border) solid var(--ds-line);
  border-radius: var(--ds-radius-soft);
  overflow: hidden;
}
.ds-map .ds-legend {
  position: absolute;
  left: var(--ds-space-snug);
  bottom: var(--ds-space-snug);
  display: flex;
  flex-wrap: wrap;
  gap: var(--ds-space-snug);
  padding: var(--ds-space-tight) var(--ds-space-snug);
  background: var(--ds-surface-raised);
  border-radius: var(--ds-radius-sharp);
  font-size: var(--ds-type-micro);
}
.ds-map .ds-legend span { display: inline-flex; align-items: center; gap: var(--ds-space-hair); }

/* ---------------------------------------------------------------- card */

.ds-card {
  display: flex;
  flex-direction: column;
  gap: var(--ds-space-tight);
  min-height: var(--ds-hit);
  padding: var(--ds-space-base);
  text-align: left;
  font: inherit;
  color: var(--ds-ink);
  background: var(--ds-surface);
  border: var(--ds-border) solid var(--ds-line);
  border-radius: var(--ds-radius-soft);
  cursor: pointer;
  min-width: 0;
}
@media (hover: hover) { .ds-card:hover:not(:disabled) { border-color: var(--ds-line-hot); } }
/* aria-pressed, not aria-selected: a <button> is a toggle, and `aria-selected` on one is an ARIA
 * attribute its role does not support — the screen reader drops it and the state is unannounced. */
.ds-card[aria-pressed="true"], .ds-card[data-state="selected"] {
  border-color: var(--ds-accent);
  background: var(--ds-surface-raised);
}
.ds-card:disabled, .ds-card[aria-disabled="true"] { opacity: 0.45; cursor: not-allowed; }
.ds-card .ds-card-meta { font-size: var(--ds-type-meta); color: var(--ds-muted); }
.ds-cards {
  display: grid;
  gap: var(--ds-gutter);
  grid-template-columns: repeat(auto-fill, minmax(min(16rem, 100%), 1fr));
}

/* ---------------------------------------------------------------- feedback */

.ds-feedback {
  display: flex;
  align-items: flex-start;
  gap: var(--ds-space-snug);
  padding: var(--ds-space-snug) var(--ds-space-base);
  border: var(--ds-border) solid var(--ds-line);
  border-left-width: 0.1875rem;
  border-radius: var(--ds-radius-sharp);
  background: var(--ds-surface);
  font-size: var(--ds-type-meta);
  min-width: 0;
  overflow-wrap: anywhere;
}
/* Severity is a border AND a prefix, never colour alone. */
.ds-feedback[data-state="notice"] { border-left-color: var(--ds-accent-soft); }
.ds-feedback[data-state="warn"] { border-left-color: var(--ds-warn); }
.ds-feedback[data-state="error"] { border-left-color: var(--ds-danger); }
.ds-feedback[data-state="ok"] { border-left-color: var(--ds-ok); }
.ds-feedback[data-state="busy"] { border-left-color: var(--ds-muted); }
.ds-feedback .ds-feedback-kind { color: var(--ds-muted); font-weight: 600; }

/* ---------------------------------------------------------------- operator GUI
 * Dense rows on a wide screen; the SAME rows stacked with their headers as labels on a narrow one.
 * A table that keeps its columns at 320px is a table nobody can read. */

.ds-ops { display: flex; flex-direction: column; gap: var(--ds-space-snug); min-width: 0; }
.ds-ops table { width: 100%; border-collapse: collapse; font-size: var(--ds-type-meta); }
.ds-ops th, .ds-ops td {
  padding: var(--ds-space-tight) var(--ds-space-snug);
  border-bottom: var(--ds-border) solid var(--ds-line);
  text-align: left;
}
.ds-ops th { color: var(--ds-faint); font-weight: 600; }
.ds-ops td { font-variant-numeric: tabular-nums; }
.ds-ops[data-state="alert"] { border-left: 0.1875rem solid var(--ds-warn); padding-left: var(--ds-space-snug); }
@media (max-width: 767px) {
  .ds-ops thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
  .ds-ops tr {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--ds-space-hair);
    padding: var(--ds-space-snug) 0;
    border-bottom: var(--ds-border) solid var(--ds-line);
  }
  .ds-ops td { border: 0; padding: 0; display: flex; justify-content: space-between; gap: var(--ds-space-snug); }
  .ds-ops td::before { content: attr(data-label); color: var(--ds-faint); }
}

/* ---------------------------------------------------------------- affordance layers */

/* HIGH CONTRAST. Opaque surfaces and lines, brighter secondary ink: the translucent panels that
 * read beautifully over a live camera are exactly what fails at 7:1. */
@media (prefers-contrast: more) {
  :root {
    --ds-ink: #ffffff;
    --ds-muted: #e8e6e2;
    --ds-faint: #cfcdc8;
    --ds-surface: #0b0c0e;
    --ds-surface-raised: #16171a;
    --ds-surface-sunken: #000000;
    --ds-line: #ffffff;
    --ds-line-hot: #ffb083;
    --ds-accent: #ff8f5a;
    --ds-accent-soft: #ffc7a6;
    --ds-on-accent: #000000;
    --ds-danger: #ffb3b3;
    --ds-warn: #ffd9a3;
    --ds-ok: #b6f0c4;
    --ds-scrim: #000000;
  }
  .ds-panel, .ds-sheet, .ds-btn, .ds-card, .ds-nav, .ds-hud, .ds-feedback, .ds-map { box-shadow: none; }
}

/* REDUCED MOTION: off, not shortened. A 1ms animation still moves. */
@media (prefers-reduced-motion: reduce) {
  .ds-root *, .ds-root *::before, .ds-root *::after {
    animation: none !important;
    transition: none !important;
    scroll-behavior: auto !important;
  }
  .ds-btn:active:not(:disabled) { transform: none; }
}

/* ULTRAWIDE: a measure, not a stretch. Text that runs 2500px wide is text nobody tracks back to
 * the start of. */
@media (min-width: 2560px) {
  .ds-root { max-width: 128rem; margin-inline: auto; }
}
