/* components.css — styles for web/ui.ts's factories, keyed to the class names
   that module owns. Nothing else may set these classes.

   Every colour is a :root token from aerzeit.css. No literal hex belongs in
   this file -- web/tokens.test.ts is what keeps the palette in one place, and
   a literal here would route around it. */

.ui-card {
  background: var(--navy-secondary);
  border-radius: 0.75rem;
  padding: 1rem;
  display: grid;
  gap: 0.75rem;
  --card-accent: var(--teal);
  /* Matches the site's .card and iOS's CardModifier: 3px at full strength.
     inline-start, never left -- the app ships in six RTL locales too. */
  border-inline-start: 3px solid var(--card-accent);
}

.ui-card-title {
  margin: 0;
  font-size: 1.05rem;
  font-weight: 600;
  color: var(--card-accent, var(--teal));
}

.ui-subcard {
  background: var(--navy);
  border-radius: 0.5rem;
  padding: 0.75rem;
  display: grid;
  gap: 0.5rem;
  /* Attenuated, not repeated: iOS's subCard is 2pt at 0.5 opacity where its
     parent is 3pt at 1.0 (spec §3.0a). renderSettings.ts:583 is
     card({children:[subCard x 4]}), so a full-strength border here would
     stack two equal teal bars a few pixels apart, four times over.
     color-mix rather than `opacity`, which would fade the content too.
     --card-accent is READ, not re-declared, so a subcard inherits a variant
     parent's accent; the fallback covers a standalone subcard. */
  border-inline-start: 2px solid
    color-mix(in srgb, var(--card-accent, var(--teal)) 50%, transparent);
}

/* SPACING IS THE GRID GAP'S JOB, and only the grid gap's. A <p>/<ul> child
   arrives carrying the UA sheet's 1em block margins, and a grid container does
   NOT collapse its items' margins — so every paragraph in a card silently sat
   16px further from its neighbours than the 12px gap above says, and iOS's
   own card interior is VStack(spacing: 12) (SettingsView.swift:2331), i.e.
   exactly that gap and nothing more. Zeroing here is the parity fix. */
.ui-card > :is(p, ul, ol),
.ui-subcard > :is(p, ul, ol) {
  margin-block: 0;
}

/* An OPTIONAL line that has nothing to say still claims a grid row and the gap
   above it. The Forecast card carries three of them under the window status
   (staleness, coverage, unavailable-filter list) and they are all empty in the
   ordinary case, which is how ~160px of blank space accumulated between the
   amber status line and the chart.
   Collapsed in CSS, not by a `hidden` toggle at each call site, because a rule
   cannot be forgotten by a new call site the way an assignment can.
   The `:not([role="status"])` carve-out is load-bearing: a live region that is
   display:none at the moment its text arrives may never be announced, so the
   two role="status" paragraphs stay rendered and merely empty — costing one
   12px gap, which is the price of the announcement working. */
.ui-card > :empty:not([role="status"]),
.ui-subcard > :empty:not([role="status"]) {
  display: none;
}

.ui-subcard-head {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.ui-subcard-title {
  margin: 0;
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--card-accent, var(--teal));
}

.ui-row {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: 0.5rem 0.75rem;
}

/* Below ~480px (iPhone SE/8 and narrower), 1fr auto has no room left once
   .ui-card (1rem padding) nests inside .ui-subcard (0.75rem) inside
   main.site-main's own 24px/side gutter -- the row's content overflows the
   viewport horizontally. Stack instead of shrinking: a bare min-width: 0
   removes the overflow but collapses the label under the control instead. */
@media (max-width: 30rem) {
  .ui-row {
    grid-template-columns: 1fr;
  }

  .ui-row > * {
    min-width: 0;
  }
}

.ui-row-label {
  color: var(--text-primary);
}

.ui-row-hint {
  grid-column: 1 / -1;
  margin: 0;
  font-size: 0.85rem;
  color: var(--text-secondary);
}

/* BUTTONS — synavistra's button system (src/assets/css/synavistra.css `.btn`),
   with its metrics taken from iOS where the two disagree.
   
   The two platforms AGREE on the part that matters and that aerzeit was
   missing: a teal fill whose INTERACTION state is coral. iOS's
   `CoralButtonStyle` (ViewModifiers.swift) does it on `isPressed`, synavistra's
   `.btn:hover` on hover — the same idea in whichever state each medium has.
   Before this, aerzeit's buttons were flat outlines with no interaction state
   at all.

   Where they conflict, iOS wins, which is the precedent the CARD system already
   set (ported from iOS's CardModifier, "deliberately NOT from synavistra's
   .card ... the app is what users actually see"):
       radius   iOS 8         vs synavistra 4    -> 0.5rem
       padding  iOS 20/10 pt  vs synavistra 24/12 -> 1.25rem / 0.625rem
       weight   iOS semibold  vs synavistra 500   -> 600
       timing   iOS easeInOut 0.1s                -> --transition-speed/-ease

   Every variant carries a 2px border, transparent where it is not wanted.
   synavistra sets `border: none` on `.btn` and 2px on `.btn-secondary`, which
   makes the two variants differ in height by 4px — a visible jump wherever they
   sit side by side, which in this app is every row of the locations list. */
.ui-button {
  font: inherit;
  padding: 0.625rem 1.25rem;
  border-radius: 0.5rem;
  border: 2px solid transparent;
  background: transparent;
  color: var(--text-primary);
  cursor: pointer;
  transition:
    background-color var(--transition-speed) var(--transition-ease),
    border-color var(--transition-speed) var(--transition-ease),
    color var(--transition-speed) var(--transition-ease);
}

.ui-button--primary {
  background: var(--teal);
  border-color: var(--teal);
  color: var(--navy);
  font-weight: 600;
}

/* Coral on hover AND on active, not hover alone: a touch device has no hover,
   so hover-only would leave the primary action with no interaction state at all
   on the devices this app is mostly used from. `:active` is the touch analogue
   of iOS's `isPressed`, which is what CoralButtonStyle actually keys on. */
.ui-button--primary:hover,
.ui-button--primary:active {
  background: var(--coral);
  border-color: var(--coral);
  color: var(--navy);
}

/* synavistra's `.btn-secondary`: an outline in the accent colour that FILLS on
   interaction, rather than aerzeit's previous grey hairline. */
.ui-button--secondary {
  border-color: var(--teal);
  color: var(--teal);
}

.ui-button--secondary:hover,
.ui-button--secondary:active {
  background: var(--teal);
  color: var(--navy);
}

.ui-button--destructive {
  color: var(--danger);
  border-color: var(--danger);
}

.ui-button--destructive:hover,
.ui-button--destructive:active {
  background: var(--danger);
  color: var(--navy);
}

/* The transition is decoration; the colour change is the information. Someone
   who asked for less motion still needs to see the state. */
@media (prefers-reduced-motion: reduce) {
  .ui-button {
    transition: none;
  }
}

.ui-field {
  display: grid;
  gap: 0.25rem;
}

.ui-field-label {
  font-size: 0.85rem;
  color: var(--text-secondary);
}

.ui-field-input {
  font: inherit;
  padding: 0.5rem 0.6rem;
  border-radius: 0.5rem;
  border: 1px solid var(--text-secondary);
  background: var(--field-background);
  color: var(--text-primary);
}

.ui-toggle {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  cursor: pointer;
}

.ui-toggle-input {
  accent-color: var(--teal);
  width: 1.1rem;
  height: 1.1rem;
}

.ui-toggle-label {
  color: var(--text-primary);
}

/* Focus is visible on every interactive primitive. Keyboard is the only way
   the wave selector's handles are reachable individually (waveSelector.ts). */
.ui-button:focus-visible,
.ui-field-input:focus-visible,
.ui-toggle-input:focus-visible {
  outline: 2px solid var(--teal);
  outline-offset: 2px;
}

/* The wave selector. `touch-action: pan-y` is load-bearing: a horizontal drag
   is ours, but the page's vertical scroll must still pass through on touch --
   the same rule and the same reason as .chart-plot-host in app.css. */
.ui-wave {
  position: relative;
  width: 100%;
  max-width: 420px;
  height: 48px;
}

.ui-wave--interactive {
  touch-action: pan-y;
  cursor: pointer;
}

/* overflow: visible -- FIX ROUND (review, task 4): a right-aligned
   `upper-only` label at the track's leftmost step (e.g. "Calm" at wind
   index 0) grows leftward from x=22 and its ink starts before x=0 -- inside
   the root <svg>'s own box, which clips by default (the outermost <svg>'s
   UA-stylesheet overflow is `hidden`, unlike the nested viewBox case below).
   iOS has no equivalent clip here; the label spills into the card's own
   padding and stays fully readable. This is scoped to the root <svg>
   specifically, for a different element and a different reason than
   `.ui-wave-terminator`'s explicit absence of the same property just below
   -- that one was compensating for a viewBox bug in a NESTED <svg> and its
   absence is now a deliberate safety signal for that bug's class; this one
   is compensating for the outermost <svg>'s own default clip against text
   that legitimately extends past x=0 at the track's edge. */
.ui-wave-svg {
  width: 100%;
  height: 100%;
  display: block;
  overflow: visible;
}

/* Snap-position affordance. Deliberately faint — iOS uses white at 12%
   opacity. Absent on `dual-readonly`, which is how a display-only wave reads
   as undraggable. */
.ui-wave-ticks {
  stroke: var(--text-primary);
  stroke-opacity: 0.12;
  stroke-width: 1;
  fill: none;
}

.ui-wave-sine {
  fill: none;
  stroke: var(--teal);
  stroke-width: 3;
}

.ui-wave-band {
  fill: none;
  stroke: var(--coral);
  stroke-width: 4;
}

.ui-wave:focus-within {
  outline: 2px solid var(--teal);
  outline-offset: 4px;
}

/* The wave's handles. Teal, matching iOS's .foregroundStyle(synavistraTeal).
   No overflow: visible -- FIX ROUND 1 (review): that rule was only
   load-bearing because a viewBox bug in waveSelector.ts's terminator()
   clipped the glyph out of its box, and this compensated by letting it
   paint outside anyway (in the wrong place). With the viewBox removed
   (waveSelector.ts), the glyph draws inside its own box where place()
   put it, and this rule has nothing left to compensate for -- its
   absence is now a safety signal: a future coordinate bug would clip
   visibly instead of silently painting elsewhere. */
.ui-wave-terminator {
  fill: var(--teal);
}

/* Value readouts under each handle. Teal and bold, matching iOS's caption2. */
.ui-wave-label {
  fill: var(--teal);
  font-size: 10px;
  font-weight: 700;
}

/* SEGMENTED CONTROL — iOS's `.pickerStyle(.segmented)` over a radio group.
   The markup is a <fieldset> of <label><input type=radio><span>, so the
   browser supplies arrow-key navigation, one tab stop and the "1 of 2"
   announcement; everything below is appearance only. The radio itself is
   hidden with `appearance: none` rather than `display: none` — a display:none
   input is not focusable, which would take the keyboard behaviour away along
   with the dot. */
.ui-segmented {
  display: inline-flex;
  /* SHRINK-WRAPPED, and it takes three declarations because the card this
     sits in is a flex container. A flex item's `inline-flex` is BLOCKIFIED to
     `flex` and then stretched to the container's cross size, so `inline-flex`
     alone drew a full-card-width border with the two segments bunched at its
     left edge. `width: fit-content` covers the block case, `align-self` the
     flex one and `justify-self` grid — the card system has used all three. */
  width: fit-content;
  align-self: start;
  justify-self: start;
  margin: 0;
  padding: 2px;
  border: 2px solid var(--teal);
  border-radius: 0.5rem;
  gap: 2px;
}

.ui-segmented-option {
  display: block;
  cursor: pointer;
}

.ui-segmented-option input {
  position: absolute;
  appearance: none;
  margin: 0;
  width: 0;
  height: 0;
}

.ui-segmented-label {
  display: block;
  padding: 0.375rem 1rem;
  border-radius: 0.375rem;
  color: var(--teal);
  font-weight: 600;
  transition:
    background-color var(--transition-speed) var(--transition-ease),
    color var(--transition-speed) var(--transition-ease);
}

.ui-segmented-option input:checked + .ui-segmented-label {
  background: var(--teal);
  color: var(--navy);
}

/* Coral on interaction, matching .ui-button--primary: hover AND active, since
   a touch device has no hover. */
.ui-segmented-option:hover input:not(:checked) + .ui-segmented-label,
.ui-segmented-option:active input:not(:checked) + .ui-segmented-label {
  background: var(--coral);
  color: var(--navy);
}

/* The focus ring has to come from the LABEL, because the input it belongs to
   is zero-sized and invisible. */
.ui-segmented-option input:focus-visible + .ui-segmented-label {
  outline: 2px solid var(--coral);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  .ui-segmented-label { transition: none; }
}

/* SUBCARD TITLE ICON — iOS's `subSectionCard(label:icon:)` puts a 12x12 teal
   glyph before its subsection title, the same shape as the pane headings'.
   Sized in em so it tracks the title rather than a fixed pixel value. */
.ui-subcard-icon {
  width: 1em;
  height: 1em;
  margin-right: 0.5em;
  fill: currentColor;
  vertical-align: -0.08em;
  flex: none;
}

/* A row's own explanation — iOS's per-control `*Footer` text. Follows the
   hint, so a row reads label, control, value, then why. Full width because it
   is a sentence, not a value: it wraps under both columns rather than being
   squeezed into the control's. */
.ui-row-description {
  grid-column: 1 / -1;
  margin: 0.25rem 0 0;
  font-size: 0.85rem;
  color: var(--text-secondary);
}
