/* ============================================================================
   hw-odds — "Will it hit {island}?"
   Owned by: odds.  Root: .hw-odds.  See CONTRACTS.md §6, REDESIGN-SPEC §4.5.

   Rendered inside the shared tile primitive (.hw-tile, css/app.css). This file
   styles only what is inside the tile body. Every colour is a token from
   css/app.css :root — a hardcoded hex survives the light theme visually intact
   and silently wrong. Tile-era tokens (--tile, --tile-2, --r-inner, the
   density type scale) carry a fallback to the older token they alias, so the
   tile reads correctly whether or not the shell's additions have landed.

   Severity is never colour alone here: every band carries a glyph (◆ ▲ ● ○)
   and its name in words; the "likely" cell carries an outline AND the word.

   Contents
     1  Root, density
     2  Headline
     3  Band cells
     4  Dot strip + axis
     5  Footer + "Why these bands?" drawer
     6  One-row state (no guidance)
     7  Every-island matrix (Full data)
     8  Touch, reduced motion
   ========================================================================= */

/* ============================================================================
   1  ROOT, DENSITY
   ========================================================================= */

.hw-odds {
  /* Container, not viewport, queries: the tile is 520 px wide in the Simple
     column at 1440, full width at 900, and ~600 px in the Full centre column —
     the viewport width says nothing about how much room the cells have. */
  container: hw-odds / inline-size;
  --odds-headline: 1.25rem;   /* 20 — spec §4.5 */
  --odds-num: 1.375rem;       /* 22 */
  --odds-tint-bg: var(--tile, var(--surface));
}

:root[data-density="compact"] .hw-odds {
  --odds-headline: 1.0625rem;
  --odds-num: 1.25rem;
}

.hw-odds .hw-tile__inner {
  display: flex;
  flex-direction: column;
  gap: var(--well-pad, 12px);
}

/* Slot wrappers js/odds.js patches in place (so the aria-live headline node
   survives island switches). The meta slot must not become a flex item of its
   own in the tile head — the pill inside it is the item. */
.hw-odds__meta { display: contents; }
.hw-odds__cellwrap { min-width: 0; }
.hw-odds__cellwrap:empty { display: none; }

/* Band hues. One place, so the cells, the counts, the drawer glyphs and the
   matrix headers cannot drift apart. `clear` is --ok rather than a neutral:
   "outside the wind field" is good news and the design says so in green. */
.hw-odds__cell--direct,  .hw-odds__count--direct,  .hw-odds__why-row--direct,  .hw-odds__mh--direct,  .hw-odds__mcell--direct  { --odds-band: var(--sev-extreme); }
.hw-odds__cell--warning, .hw-odds__count--warning, .hw-odds__why-row--warning, .hw-odds__mh--warning, .hw-odds__mcell--warning { --odds-band: var(--sev-severe); }
.hw-odds__cell--watch,   .hw-odds__count--watch,   .hw-odds__why-row--watch,   .hw-odds__mh--watch,   .hw-odds__mcell--watch   { --odds-band: var(--sev-moderate); }
.hw-odds__cell--clear,   .hw-odds__count--clear,   .hw-odds__why-row--clear,   .hw-odds__mh--clear,   .hw-odds__mcell--clear   { --odds-band: var(--ok); }

/* Tints per spec §2.2 — extreme 12 %, severe 16 %, moderate 14 %, clear is the
   inner-well grey. Mixed into the tile colour rather than laid over it as
   alpha, so the result is an opaque colour the contrast check can measure. */
.hw-odds__cell--direct,  .hw-odds__band--direct,  .hw-odds__mcell--direct  { background: color-mix(in srgb, var(--sev-extreme) 12%, var(--odds-tint-bg)); }
.hw-odds__cell--warning, .hw-odds__band--warning, .hw-odds__mcell--warning { background: color-mix(in srgb, var(--sev-severe) 16%, var(--odds-tint-bg)); }
.hw-odds__cell--watch,   .hw-odds__band--watch,   .hw-odds__mcell--watch   { background: color-mix(in srgb, var(--sev-moderate) 14%, var(--odds-tint-bg)); }
.hw-odds__cell--clear,   .hw-odds__band--clear,   .hw-odds__mcell--clear   { background: var(--tile-2, var(--surface-2)); }

/* ============================================================================
   2  HEADLINE
   ========================================================================= */

/* The answer, and the tile's aria-live region — re-read when the island
   changes, which is the only way a screen-reader user learns the switch did
   anything. */
.hw-odds__headline {
  margin: 0;
  font-family: var(--font-display, var(--font-ui));
  font-size: var(--odds-headline);
  font-weight: 800;
  line-height: 1.25;
  letter-spacing: -0.015em;
  color: var(--text);
  text-wrap: balance;
}

/* The count, in its band's hue AND with its band's glyph. */
.hw-odds__count {
  color: var(--odds-band);
  white-space: nowrap;
}
.hw-odds__count-glyph {
  font-size: 0.7em;
  margin-right: 0.25em;
  vertical-align: 0.12em;
}

/* ============================================================================
   3  BAND CELLS
   ========================================================================= */

.hw-odds__cells {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 6px;
}

/* 2 × 2 once a cell would drop below ~100 px (screen 3c): the one-liners are
   whole sentences and four columns of them at 390 px is one word per line. */
@container hw-odds (max-width: 440px) {
  .hw-odds__cells { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.hw-odds__cell {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  padding: 10px 10px 8px;
  border-radius: var(--r-inner, 12px);
}

/* The most likely band. Outline in --text, not a band hue: it has to read on
   all four tints, and the "· likely" suffix says it in words as well. */
.hw-odds__cell--likely {
  outline: 2px solid var(--text);
  outline-offset: -2px;
}

.hw-odds__cell-n {
  font-family: var(--font-display, var(--font-ui));
  font-size: var(--odds-num);
  font-weight: 800;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  color: var(--odds-band);
}

.hw-odds__cell-name {
  font-size: 0.75rem;   /* 12 */
  font-weight: 700;
  line-height: 1.3;
  color: var(--text);
}
.hw-odds__cell-glyph { color: var(--odds-band); }

/* --text-dim, not the spec's --text-faint: faint on the three severity tints
   measured under 4.5:1 in the light theme (see the report). Dim clears it in
   both themes and still reads as secondary beside the 12/700 name. */
.hw-odds__cell-sub {
  font-size: 0.6875rem;   /* 11 */
  line-height: 1.35;
  color: var(--text-dim);
  text-wrap: pretty;
}

/* ============================================================================
   4  DOT STRIP + AXIS
   ========================================================================= */

.hw-odds__stripwrap:empty { display: none; }
/* Room for the "+n" pile label above the strip. Only when there is one: in
   compact density the gap above the strip is ~8 px, and a 10 px label sat on
   the likely cell's outline (measured on the Cat 4 fixture, Full, 1440). */
@media not (pointer: coarse) {
  .hw-odds__stripwrap:has(.hw-odds__pile) { padding-top: 0.75rem; }
}

.hw-odds__strip {
  position: relative;
  height: 14px;
  /* THIS is what lets a finger sweep the strip without breaking the page.
     Vertical panning and zoom stay with the browser; horizontal movement comes
     to us as pointermove. The alternative — listening to everything and
     calling preventDefault once we have guessed the direction — loses the
     first frames of every scroll. */
  touch-action: pan-y pinch-zoom;
}

/* The bands fill the track's content box, which is inset by --odds-inset (set
   inline by js/odds.js, the one source of the number) — the same inset every
   dot and tick is positioned within, so band edges and dots still agree. The
   padding at each end takes its neighbouring band's tint from the gradient. */
.hw-odds__track {
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  height: 14px;
  transform: translateY(-50%);   /* centred, so the strip can grow for touch */
  display: flex;
  padding-inline: var(--odds-inset, 0px);
  border-radius: var(--r-pill, 999px);
  overflow: clip;
  background: linear-gradient(to right,
    color-mix(in srgb, var(--sev-extreme) 12%, var(--odds-tint-bg)) 50%,
    var(--tile-2, var(--surface-2)) 50%);
}
.hw-odds__band { height: 100%; flex: none; }
.hw-odds__band + .hw-odds__band { border-left: 1px solid var(--odds-tint-bg); }

/* One ensemble member: an option of the strip's listbox, so the keyboard and a
   screen reader reach it through the one focusable strip. --dy is the
   beeswarm row, set by js/odds.js. */
.hw-odds__mark {
  position: absolute;
  top: 50%;
  width: 6px;
  height: 6px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--text-faint);
  /* A hairline of tile colour so two touching dots still read as two. */
  box-shadow: 0 0 0 1px var(--odds-tint-bg);
  transform: translate(-50%, calc(-50% + var(--dy, 0px)));
  cursor: pointer;
  z-index: 1;
  transition: width var(--dur-1) var(--ease-out), height var(--dur-1) var(--ease-out),
              opacity var(--dur-1) var(--ease-out), box-shadow var(--dur-1) var(--ease-out);
}

/* The mouse target, larger than the mark. Touch goes through the nearest-dot
   logic in js/odds.js instead, so this only has to help a mouse. */
.hw-odds__mark::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 14px;
  height: 18px;
  transform: translate(-50%, -50%);
}

/* The strip is the focusable listbox; the active option carries the focus
   ring inside it, so a keyboard user sees which forecast the arrows are on. */
.hw-odds__strip:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: var(--r-xs, 4px);
}
.hw-odds__strip:focus-visible .hw-odds__mark--active {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  opacity: 1;
  z-index: 6;
}

/* The official NHC track: the only other mark on the strip (spec §4.5). */
.hw-odds__mark--nhc {
  width: 3px;
  height: 20px;
  border-radius: 2px;
  background: var(--accent);
  transform: translate(-50%, -50%);
  z-index: 3;
}

/* Past the end of the scale: flattened against the right edge so it reads as
   "off the scale", not as a measured position. The true distance is in the
   option's label. */
.hw-odds__mark--beyond {
  border-radius: 50% 0 0 50%;
  transform: translate(-100%, calc(-50% + var(--dy, 0px)));
}

/* Past the beeswarm's row cap: counted in the pile's "+n" label instead of
   drawn on top of its neighbours. Revealed while it is the one being pointed
   at, isolated or focused, so the map link still shows which dot it is. */
.hw-odds__mark--folded:not(.hw-odds__mark--hot):not(.hw-odds__mark--isolated) {
  opacity: 0;
}

/* "+24": how many members are folded into the pile below it. Sits in the gap
   above the strip; --text-dim because it is a count the reader must read. */
.hw-odds__pile {
  position: absolute;
  /* Just above the 14 px track, whatever height the strip itself is. */
  bottom: calc(50% + 8px);
  transform: translateX(-50%);
  font-family: var(--font-mono);
  font-size: 0.625rem;
  font-weight: 700;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  color: var(--text-dim);
  white-space: nowrap;
  pointer-events: none;
}
.hw-odds__pile--start { transform: translateX(-4px); }
.hw-odds__pile--end { transform: translateX(calc(-100% + 4px)); }

/* -- mark states ---------------------------------------------------------- */

/* Something else is isolated. Faded, not hidden — the pile is still the
   evidence. FIRST in this block on purpose: a dot can be muted AND hot (you
   isolate one member and hover another to compare), and written after --hot
   it won on source order and faded the very dot the reader was pointing at. */
.hw-odds__mark--muted { opacity: 0.35; }

/* Hovered or keyboard-focused: the track lit on the map. It grows as well as
   changes colour, so the link is legible without colour. */
.hw-odds__mark--hot {
  width: 10px;
  height: 10px;
  background: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
  opacity: 1;
  z-index: 5;
}

/* Clicked: the map draws this one alone. A ring rather than a fill, so
   "isolated" and "hovered" never look like the same state. */
.hw-odds__mark--isolated {
  width: 10px;
  height: 10px;
  background: var(--odds-tint-bg);
  box-shadow: 0 0 0 2px var(--accent);
  opacity: 1;
  z-index: 4;
}
.hw-odds__mark--nhc.hw-odds__mark--hot,
.hw-odds__mark--nhc.hw-odds__mark--isolated {
  width: 5px;
  height: 22px;
  background: var(--accent);
  border-radius: 2px;
}

/* -- axis ----------------------------------------------------------------- */

.hw-odds__axis {
  position: relative;
  height: 1rem;
  margin-top: 2px;
}

.hw-odds__tick {
  position: absolute;
  top: 0;
  transform: translateX(-50%);
  font-family: var(--font-mono);
  font-size: var(--fs-3xs, 0.625rem);
  font-variant-numeric: tabular-nums;
  color: var(--text-faint);
  white-space: nowrap;
}
/* The two end ticks would otherwise hang off the strip. */
.hw-odds__tick:first-child { transform: none; }
.hw-odds__tick--end { transform: translateX(-100%); }

/* At phone width the end tick collides with the watch edge, and it is the only
   tick that is not a named threshold. */
@container hw-odds (max-width: 380px) {
  .hw-odds__tick--end { display: none; }
}

/* ============================================================================
   5  FOOTER + "WHY THESE BANDS?" DRAWER
   ========================================================================= */

.hw-odds__foot {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 6px 12px;
}

.hw-odds__context {
  flex: 1 1 16rem;
  min-width: 0;
  margin: 0;
  font-size: 0.6875rem;   /* 11 */
  line-height: 1.45;
  color: var(--text-faint);
}
.hw-odds__context:empty { display: none; }
.hw-odds__context .hw-age { font-size: inherit; }

.hw-odds__whywrap { flex: none; margin-left: auto; }
.hw-odds__whywrap:empty { display: none; }
/* Open, the drawer takes the whole row (screen 4c) instead of a 120 px corner. */
.hw-odds__whywrap:has(.hw-odds__why[open]) { flex-basis: 100%; margin-left: 0; }

.hw-odds__why-sum {
  display: inline-flex;
  align-items: center;
  gap: 0.3em;
  min-height: 24px;
  list-style: none;
  cursor: pointer;
  font-size: 0.75rem;   /* 12 */
  font-weight: 700;
  color: var(--accent);
  border-radius: var(--r-xs, 4px);
}
.hw-odds__why-sum::-webkit-details-marker { display: none; }
.hw-odds__why-sum:hover { text-decoration: underline; text-underline-offset: 0.18em; }
.hw-odds__why-chev { display: inline-block; transition: rotate var(--dur-1) var(--ease-out); }
.hw-odds__why[open] .hw-odds__why-chev { rotate: 180deg; }

.hw-odds__why-body {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: 8px;
  padding: 14px 16px;
  border-radius: var(--r-inner, 12px);
  background: var(--tile-2, var(--surface-2));
  font-size: 0.75rem;
  line-height: 1.5;
  color: var(--text-dim);
}

.hw-odds__why-scroll { overflow-x: auto; }

.hw-odds__why-table {
  width: 100%;
  border-collapse: collapse;
}
.hw-odds__why-table th,
.hw-odds__why-table td {
  padding: 4px 10px 4px 0;
  text-align: left;
  vertical-align: top;
}
/* Top-aligned: the one-liners wrap to three or four lines at phone width, and
   a centred name floats away from the sentence it names. Set on the row so
   every cell inherits it. */
.hw-odds__why-row { vertical-align: top; }
.hw-odds__why-glyph { color: var(--odds-band); font-weight: 800; width: 1.2em; }
.hw-odds__why-name { font-size: 0.8125rem; font-weight: 700; color: var(--text); white-space: nowrap; }
.hw-odds__why-range {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  color: var(--text-dim);
  white-space: nowrap;
}
.hw-odds__why-text { color: var(--text-dim); }

.hw-odds__why-gefs {
  margin: 0;
  padding-top: 10px;
  border-top: 1px solid var(--border);
}
.hw-odds__why-omit {
  margin: 0;
  font-size: 0.6875rem;
  color: var(--text-dim);
}

/* ============================================================================
   6  ONE-ROW STATE (storm, but no model guidance)
   ========================================================================= */

/* Never an empty strip — an empty strip reads as "no forecast comes near",
   which is a claim we cannot make. */
.hw-odds--row .hw-tile__body { display: none; }

/* app.css shows the head's fact only on a COLLAPSED tile (the expanded body
   says it at length). This row has no body and cannot collapse, so the fact
   IS the content — without this override the row read "Will it hit Big
   Island?" and nothing else, which is a question with the answer hidden. */
.hw-odds--row > .hw-tile__head > .hw-tile__fact {
  display: block;
  flex: 1 1 12rem;
  min-width: 0;
  font-size: var(--fs-meta, 0.75rem);
  font-weight: 600;
  line-height: 1.4;
  color: var(--text-faint);
}
.hw-odds--row > .hw-tile__head > .hw-tile__fact::before { content: "· "; }
.hw-odds--row > .hw-tile__head { flex-wrap: wrap; }

/* ============================================================================
   7  EVERY-ISLAND MATRIX (Full data, screen 3b)
   ========================================================================= */

.hw-odds__matrixwrap:empty { display: none; }

.hw-odds__matrix {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding-top: var(--well-pad, 12px);
  border-top: 1px solid var(--border);
}

.hw-odds__mtitle {
  margin: 0;
  font-size: var(--fs-title, 0.875rem);
  font-weight: 700;
  color: var(--text);
}

.hw-odds__mscroll { overflow-x: auto; }

.hw-odds__mtable {
  /* The spacing is between cells, not around the table: pull it out by the
     spacing and widen by twice that, or the table is always 8 px wider than
     its scroller and scrolls sideways for nothing. */
  width: calc(100% + 8px);
  border-collapse: separate;
  border-spacing: 4px;
  margin: -4px;
  font-size: 0.75rem;
  font-variant-numeric: tabular-nums;
}

.hw-odds__mh {
  padding: 0 2px 2px;
  font-size: 0.625rem;   /* 10 */
  font-weight: 700;
  line-height: 1.3;
  text-align: center;
  vertical-align: bottom;
  color: var(--odds-band, var(--text-faint));
}
.hw-odds__mh-range {
  display: block;
  font-weight: 600;
  color: var(--text-faint);
  white-space: nowrap;
}
.hw-odds__mh--strip { text-align: left; color: var(--text-faint); }
.hw-odds__mh--median { text-align: right; color: var(--text-faint); }

.hw-odds__misland {
  padding: 0 6px;
  border-radius: 8px;
  text-align: left;
  font-weight: 800;
  color: var(--text);
  white-space: nowrap;
}

.hw-odds__mcell {
  height: 28px;
  min-width: 2.6em;
  padding: 0 4px;
  border-radius: 8px;
  text-align: center;
  font-weight: 800;
  color: var(--odds-band);
}
.hw-odds__mcell--likely {
  outline: 2px solid var(--text);
  outline-offset: -2px;
}

.hw-odds__mstrip {
  width: 100%;
  min-width: 9rem;
  padding: 0 6px;
  border-radius: 8px;
}

.hw-odds__mmedian {
  padding: 0 6px;
  border-radius: 8px;
  text-align: right;
  font-family: var(--font-mono);
  font-size: var(--fs-mono, 0.71875rem);
  font-weight: 700;
  color: var(--text);
  white-space: nowrap;
}

/* Centred, explicitly: under a coarse pointer the strip cell is a 44 px
   listbox while the count cells stay 28 px, and the counts must line up with
   the middle of the strip, not its top edge. */
.hw-odds__mrow { vertical-align: middle; }

/* The selected island's row: accent 6 % on its label, strip and median — the
   band cells keep their own tints, which are the data. */
.hw-odds__mrow--selected > .hw-odds__misland,
.hw-odds__mrow--selected > .hw-odds__mstrip,
.hw-odds__mrow--selected > .hw-odds__mmedian {
  background: color-mix(in srgb, var(--accent) 6%, var(--odds-tint-bg));
}
.hw-odds__mrow--selected > .hw-odds__misland { box-shadow: inset 3px 0 0 var(--accent); }

.hw-odds__mfoot {
  margin: 0;
  font-size: 0.6875rem;
  line-height: 1.45;
  color: var(--text-faint);
}
.hw-odds__mfoot-tick { color: var(--accent); font-weight: 800; }

/* A phone's Full-data column (screen 3d): the mini strips go, the counts and
   medians stay. Thirty-one dots in 90 px is not a reading anyone can make. */
@container hw-odds (max-width: 520px) {
  .hw-odds__mh--strip,
  .hw-odds__mstrip { display: none; }
  /* Without the strip the six remaining columns fit a 360 px tile, but only
     if the range captions may wrap — "beyond 249 mi" on one line pushed the
     median column out of the tile at 390. */
  .hw-odds__mh-range { white-space: normal; }
  .hw-odds__mcell { min-width: 2em; }
}

/* ============================================================================
   8  TOUCH, REDUCED MOTION
   ========================================================================= */

@media (pointer: coarse) {
  /* The strip IS the touch target: one listbox, where a tap anywhere picks the
     nearest forecast (js/odds.js). It is genuinely 44 px tall here — the track
     stays 14 px, centred — rather than an invisible pseudo-element hit zone,
     so what a finger hits and what an audit measures are the same box. The
     pile label then sits inside that height, so no extra room is needed. */
  .hw-odds__strip { height: 44px; }
  .hw-odds__why-sum { min-height: 44px; }
}

@media (prefers-reduced-motion: reduce) {
  .hw-odds__mark,
  .hw-odds__why-chev { transition: none; }
}
:root[data-motion="off"] .hw-odds__mark,
:root[data-motion="off"] .hw-odds__why-chev { transition: none; }
