/*
 * herOS design system components.
 *
 * Every declaration reads a semantic token. No hex, no rgb(), no named colour: the census in
 * docs/design/DESIGN_SYSTEM.md treats a literal here as a defect, because it would mean a colour
 * had been declared somewhere other than src/brand/tokens.ts.
 *
 * Consumed by the vanilla surfaces - the desktop renderer and the landing -
 * after src/design-system/tokens/tokens.css. Expo reads the token object instead and gets its
 * components later.
 */

/* --- Button ------------------------------------------------------------------------------- */
.ds-button {
  border: 1px solid var(--ds-border-subtle);
  border-radius: var(--ds-radius-control);
  background: transparent;
  color: var(--ds-text-primary);
  font: inherit;
  /* Section 4: one primary CTA per decision point, with a minimum 44 x 44 hit area. The padding
     alone did not reach it - a 16px line in a 8px/14px box is 38px tall, which is a control a
     person with an unsteady hand misses. */
  min-height: var(--ds-control-min-target);
  padding: var(--ds-space-2) var(--ds-space-5);
  cursor: pointer;
}

.ds-button:hover:not(:disabled) { border-color: var(--ds-text-muted); }

/*
 * A visible focus ring is not optional: keyboard use is the case that loses it silently.
 *
 * The ring is `--ds-focus-ring`, NOT `--ds-accent`. The brand colour is a fill: it measures 3.27:1
 * on the canvas and 2.95:1 on a surface, so an accent ring would fall under the 3:1 a focus
 * indicator needs exactly where components sit - on panels. Measured, not assumed.
 */
.ds-button:focus-visible {
  outline: 2px solid var(--ds-focus-ring);
  outline-offset: 2px;
}

.ds-button[data-kind="primary"] {
  background: var(--ds-accent);
  border-color: var(--ds-accent);
  color: var(--ds-text-on-accent);
  font-weight: 600;
}

/* A primary action needs a hover and a disabled state of its own: inheriting the quiet button's
   made a filled control look unclickable on hover and identical to enabled when disabled. */
.ds-button[data-kind="primary"]:hover:not(:disabled) { filter: brightness(1.12); }

.ds-button[data-kind="primary"]:disabled {
  background: var(--ds-bg-surface);
  border-color: var(--ds-border-subtle);
  color: var(--ds-text-muted);
  filter: none;
}

.ds-button:disabled { color: var(--ds-text-muted); cursor: not-allowed; }

/* --- Input -------------------------------------------------------------------------------- */
.ds-input {
  border: 1px solid var(--ds-border-subtle);
  border-radius: 8px;
  background: var(--ds-bg-canvas);
  color: var(--ds-text-primary);
  font: inherit;
  padding: 8px 10px;
}

.ds-input::placeholder { color: var(--ds-text-muted); }
.ds-input:focus-visible { outline: 2px solid var(--ds-focus-ring); outline-offset: 2px; }

/*
 * Invalid is carried by the border AND by the message beside the field. Colour alone would be
 * invisible to anyone who cannot distinguish it, so `.ds-input-message` is part of the component
 * rather than a caller's afterthought.
 */
.ds-input[aria-invalid="true"] { border-color: var(--ds-status-attention); }
.ds-input-message { color: var(--ds-text-muted); font-size: 0.875em; }
.ds-input[aria-invalid="true"] ~ .ds-input-message { color: var(--ds-status-attention); }

/* --- Surface ------------------------------------------------------------------------------ */
.ds-surface {
  background: var(--ds-bg-surface);
  border: 1px solid var(--ds-border-subtle);
  border-radius: 12px;
  color: var(--ds-text-primary);
  padding: 16px;
}

.ds-surface[data-kind="sheet"] { border-radius: 16px; }

/* --- Status badge ------------------------------------------------------------------------- */
/*
 * The label IS the status. The tone reinforces it and never carries it alone, which is why there
 * is no tone without text in this component and no variant that hides the label.
 */
.ds-status {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border: 1px solid var(--ds-border-subtle);
  border-radius: 999px;
  color: var(--ds-text-primary);
  font-size: 0.875em;
  padding: 2px 10px;
}

.ds-status-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--ds-text-muted); }
.ds-status[data-tone="positive"] .ds-status-dot { background: var(--ds-status-positive); }
.ds-status[data-tone="attention"] .ds-status-dot { background: var(--ds-status-attention); }

/* --- Horizon status ----------------------------------------------------------------------- */
/*
 * Wraps the brand module's `.horizon`, which owns the mark and its ten motions. This component owns
 * only the pairing: the motion never appears without its text. BRAND.md section 6 requires that
 * pairing in its own right, and requires the same text under reduced motion.
 */
.ds-horizon-status { display: inline-flex; align-items: center; gap: 8px; color: var(--ds-text-primary); }
.ds-horizon-status .ds-horizon-label { color: var(--ds-text-muted); font-size: 0.875em; }

/* --- Text emphasis ------------------------------------------------------------------------ */
/*
 * An editorial gradient for one or two emphasis phrases per section.
 *
 * This is the ONLY component that reads illustration tokens rather than semantic ones. The spectrum
 * is artwork, not a role: it was chosen for how it reads across a phrase, and calling it "the
 * accent" would empty the semantic layer of meaning. Each colour is declared once, in tokens.json,
 * exempt by member with its reason.
 *
 * The solid colour is declared FIRST and the gradient paints over it. That is deliberate: the solid
 * is what a reader actually gets wherever `background-clip: text` cannot paint - forced colours, an
 * older engine, text copied out of the page - so it is the value the phrase is guaranteed to have
 * rather than a fallback nobody exercises.
 */
.ds-text-emphasis {
  color: var(--ds-illustration-emphasis-fallback);
  background-image: linear-gradient(110deg,
    var(--ds-illustration-emphasis-peach) 5%,
    var(--ds-illustration-emphasis-rose) 47%,
    var(--ds-illustration-emphasis-lilac) 90%);
  background-clip: text;
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  /* Italic descenders fall outside the painted box and get clipped without this. The negative
     margin cancels the padding, so the phrase still sits on its own line rather than pushing it. */
  padding: 0.06em 0.08em 0.2em;
  margin: -0.06em -0.08em -0.2em;
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
}

/* A phrase that wraps needs the line box to make room for the same painted area. */
.ds-text-emphasis[data-line] {
  line-height: 1.18;
  padding-bottom: 0.2em;
  margin-bottom: -0.2em;
}

/*
 * The motion variant lives entirely inside `no-preference`, so a reader who asked for less motion
 * never has the animation applied at all. That is stronger than declaring it and overriding it with
 * `animation: none !important` in a reduce block, which is one specificity accident away from
 * animating anyway.
 */
@media (prefers-reduced-motion: no-preference) {
  .ds-text-emphasis--motion {
    background-image: linear-gradient(90deg,
      var(--ds-illustration-emphasis-rose), var(--ds-illustration-emphasis-sand),
      var(--ds-illustration-emphasis-leaf), var(--ds-illustration-emphasis-sky),
      var(--ds-illustration-emphasis-lilac), var(--ds-illustration-emphasis-rose),
      var(--ds-illustration-emphasis-sand), var(--ds-illustration-emphasis-leaf),
      var(--ds-illustration-emphasis-sky), var(--ds-illustration-emphasis-lilac),
      var(--ds-illustration-emphasis-rose));
    background-size: 200% 100%;
    animation: ds-text-emphasis-spectrum 14s linear infinite;
    filter: drop-shadow(0 0 12px var(--ds-illustration-emphasis-glow));
  }

  /* The pause hook. A surface with its own pause control sets this on any ancestor; the animation
     is paused rather than removed, so resuming does not restart the phrase mid-sentence. */
  [data-ds-motion="paused"] .ds-text-emphasis--motion { animation-play-state: paused; }
}

@keyframes ds-text-emphasis-spectrum {
  from { background-position: 100% 50%; }
  to { background-position: 0% 50%; }
}

/* Forced colours: the system's own palette wins, and the gradient must get out of the way entirely
   or the text paints transparent over a background the page no longer controls. */
@media (forced-colors: active) {
  .ds-text-emphasis {
    background: none;
    filter: none;
    animation: none;
    -webkit-text-fill-color: CanvasText;
    color: CanvasText;
  }
}

/* --- Choice ------------------------------------------------------------------------------- */
/*
 * A set of cards where exactly one is selected, built on NATIVE radio inputs.
 *
 * Selection, arrow-key navigation, Home/End, single-selection within a `name` and the screen-reader
 * announcement are the platform's, not this component's. There is no JavaScript here. A card built
 * from buttons reads correctly and behaves wrong for anyone on a keyboard, which matters most on a
 * first-run surface.
 */
.ds-choice-group {
  border: 0;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--ds-space-2);
  /* Stretch, so a card whose detail line wraps lifts its whole row instead of leaving neighbours
     short. This is what makes a multi-column group look deliberate at an awkward width. */
  align-items: stretch;
}

.ds-choice-legend {
  padding: 0;
  margin-bottom: var(--ds-space-4);
  color: var(--ds-text-primary);
  font-size: var(--ds-type-legend);
  line-height: var(--ds-leading-title);
  font-weight: 700;
}

.ds-choice {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 12px;
  align-items: start;
  border: 1px solid var(--ds-border-subtle);
  border-radius: var(--ds-radius-panel);
  padding: var(--ds-space-3) var(--ds-space-4);
  background: var(--ds-bg-surface);
  color: var(--ds-text-primary);
  cursor: pointer;
}

/*
 * Visually hidden, NOT `display: none` and not `visibility: hidden`: either one removes the control
 * from the tab order and from arrow-key navigation, which is the entire reason this is a radio.
 */
.ds-choice-control {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* The selected state is a SHAPE as well as a colour - an empty ring becomes a filled one - because
   selection shown in colour alone is invisible to a reader who cannot separate these two. */
.ds-choice::before {
  content: "";
  width: 16px;
  height: 16px;
  margin-top: 2px;
  border: 2px solid var(--ds-border-strong);
  border-radius: 50%;
}

.ds-choice-text { display: grid; gap: 4px; }
.ds-choice-title { color: var(--ds-text-primary); font-weight: 600; }
.ds-choice-detail { color: var(--ds-text-muted); font-size: var(--ds-type-label); line-height: var(--ds-leading-body); }

.ds-choice:hover { border-color: var(--ds-text-muted); }

.ds-choice:has(.ds-choice-control:checked) { border-color: var(--ds-accent); }
.ds-choice:has(.ds-choice-control:checked)::before {
  border-color: var(--ds-accent);
  box-shadow: inset 0 0 0 3px var(--ds-accent);
}

/* The ring is drawn on the CARD, because that is where the eye is - the control it belongs to is
   one pixel wide and off in the corner. */
.ds-choice:has(.ds-choice-control:focus-visible) {
  outline: 2px solid var(--ds-focus-ring);
  outline-offset: 2px;
}

@media (forced-colors: active) {
  .ds-choice:has(.ds-choice-control:checked) { border-color: Highlight; }
  .ds-choice:has(.ds-choice-control:checked)::before { border-color: Highlight; box-shadow: inset 0 0 0 3px Highlight; }
}

/* --- Page, title and lede ------------------------------------------------------------------ */
/*
 * The base a window needs before any component can look right.
 *
 * This exists because the first-run window loaded the colour tokens and still rendered in Times on
 * a WHITE page: nothing in the system said what a page IS. Colours alone are not a design system -
 * a surface also needs a typeface, a size, a line height and a background, and without them every
 * consumer invents its own and the tokens underneath stop mattering.
 */
.ds-page {
  margin: 0;
  min-height: 100vh;
  background: var(--ds-bg-canvas);
  color: var(--ds-text-primary);
  font-family: var(--ds-font-sans);
  font-size: var(--ds-type-body);
  line-height: var(--ds-leading-body);
  -webkit-font-smoothing: antialiased;
}

/* One per view. Section 4: sentence case outside the hero, slight negative tracking on large type. */
.ds-title {
  margin: 0;
  font-size: var(--ds-type-title);
  line-height: var(--ds-leading-title);
  letter-spacing: -0.02em;
  font-weight: 700;
}

/* The sentence under a title. Muted on purpose: it explains, it does not compete. */
.ds-lede {
  margin: 0;
  color: var(--ds-text-muted);
  font-size: var(--ds-type-body);
  line-height: var(--ds-leading-body);
}

.ds-label {
  font-size: var(--ds-type-label);
  line-height: var(--ds-leading-label);
  color: var(--ds-text-muted);
}
