/* Light and dark are both first-class: this is a page you check at 6am and at
 * 11pm. Colours live here as custom properties and are referenced by the tone-*
 * classes the chart emits, so config.js never names a colour. */

/* The palette aims at one register: HIGH CHROMA, LOW LUMINANCE CONTRAST. Hues are
 * distinct and vivid, but held at similar brightness and pulled slightly toward
 * the background, which is how telemetry and mission displays read -- saturated
 * without being neon, muted without going grey. Nothing here is a pure primary,
 * and nothing is allowed to shout: the dark theme is the one this page is mostly
 * looked at in, and every tone there sits within a narrow lightness band so no
 * single column grabs the eye first. */
:root {
  --bg: #fbfbfa;
  --panel-bg: #ffffff;
  --ink: #1a1a19;
  --dim: #6b6b68;
  --faint: #e6e7ea;
  --line: #d5d7dc;
  /* Night must always DARKEN, in either theme. */
  --night: rgba(58, 74, 110, 0.085);
  --now: #d6402f;
  --horizon: #9a8cc4;

  --warm: #d9663f;
  --cold: #4a8fd4;
  --snow: #8fa6bd;  /* must be unmistakable against --rain */
  --freeze: #a052c0; /* freezing rain: alarming, not merely cold */
  --rain: #3a7fc8;
  --cloud: #868c98;
  --ice: #7fb4d8;
  --sun: #cf9f2c;
  --uv: #a862bd;
  --wind: #3f9e84;
  --water: #3399b0;
  --storm: #cc5a42;
  --haze: #9a8d6f;
  /* Doubt has no hue: the model-spread envelope is the one thing on the page
     that is not a measurement, so it is the one tone with no colour opinion. */
  --spread: #8c93a3;
  --aqi: #789a44;
  /* US AQI categories, held to the same muted register as everything else. */
  --aqi1: #6d9a52; /* 0-50    good */
  --aqi2: #c2a63e; /* 51-100  moderate */
  --aqi3: #cc8340; /* 101-150 unhealthy for sensitive groups */
  --aqi4: #c25446; /* 151-200 unhealthy */
  --aqi5: #96509e; /* 201-300 very unhealthy */
  --aqi6: #86444c; /* 301+    hazardous */
  --neutral: #7b7d88;

  color-scheme: light dark;
}

@media (prefers-color-scheme: dark) {
  :root {
    /* A blue-black, not a neutral grey: the whole point of the register. */
    --bg: #0b0e15;
    --panel-bg: #11141d;
    --ink: #e6e8ee;
    --dim: #8d93a3;
    --faint: #1d212c;
    --line: #2a3040;
    /* DARKER than the panel, not lighter. Night reading as a brighter band was
     * backwards, and on a near-black page it was the brightest thing on it. */
    --night: rgba(0, 1, 6, 0.5);
    --now: #ff6a55;
    --horizon: #a294cf;

    --warm: #f0805a;
    --cold: #6aa8e0;
    --snow: #cfdff0;
    --freeze: #b98ae0;
    --rain: #5a9ada;
    --cloud: #98a0b0;
    --ice: #8ec0e0;
    --sun: #e8bb55;
    --uv: #b57ad0;
    --wind: #4fb89e;
    --water: #47b8d0;
    --storm: #dd6a58;
    --haze: #b0a173;
    --spread: #737f9c;
    --aqi: #9ab85c;
    --aqi1: #8ab868;
    --aqi2: #dcc257;
    --aqi3: #e09a55;
    --aqi4: #d96a5c;
    --aqi5: #b26bba;
    --aqi6: #a35d66;
    --neutral: #8a90a0;
  }
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font: 13px/1.4 ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  font-variant-numeric: tabular-nums;
  display: flex;
  flex-direction: column;
  height: 100vh;
  overflow: hidden;
}

/* ------------------------------- header --------------------------------- */

.bar {
  /* Its own stacking context, above the grid: the sticky column headers inside
   * .scroller paint after this element in DOM order, so the search dropdown was
   * appearing behind them. */
  position: relative;
  z-index: 30;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 8px 12px;
  border-bottom: 1px solid var(--line);
  flex-wrap: wrap;
}

.place { display: flex; align-items: center; gap: 8px; position: relative; }
.place h1 { font-size: 15px; font-weight: 600; margin: 0; }

input[type="search"] {
  width: 130px;
  padding: 4px 7px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--panel-bg);
  color: inherit;
  font: inherit;
}
input[type="search"]:focus { outline: 2px solid var(--rain); outline-offset: -1px; }

.results {
  position: absolute;
  top: 30px;
  left: 34px;
  z-index: 20;
  margin: 0;
  padding: 4px;
  list-style: none;
  min-width: 220px;
  background: var(--panel-bg);
  border: 1px solid var(--line);
  border-radius: 8px;
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.14);
}
.results button {
  display: block;
  width: 100%;
  text-align: left;
  padding: 5px 7px;
  border: 0;
  border-radius: 5px;
  background: none;
  color: inherit;
  font: inherit;
  cursor: pointer;
}
.results button:hover { background: var(--faint); }

.controls { display: flex; align-items: center; gap: 8px; }

.seg { display: flex; border: 1px solid var(--line); border-radius: 6px; overflow: hidden; }
.seg button {
  padding: 4px 9px;
  border: 0;
  border-right: 1px solid var(--line);
  background: var(--panel-bg);
  color: var(--dim);
  font: inherit;
  cursor: pointer;
}
.seg button:last-child { border-right: 0; }
.seg button.on { background: var(--ink); color: var(--bg); }

.ghost {
  padding: 4px 9px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--panel-bg);
  color: var(--dim);
  font: inherit;
  cursor: pointer;
}
.ghost:hover { color: var(--ink); }

.age { color: var(--dim); font-size: 12px; min-width: 60px; }

/* ------------------------------- alerts --------------------------------- */

.alerts { display: flex; flex-direction: column; }
.alert {
  border-bottom: 1px solid var(--line);
  background: color-mix(in srgb, var(--warm) 12%, var(--bg));
  font-size: 12px;
}
.alert.sev-severe, .alert.sev-extreme {
  background: color-mix(in srgb, var(--storm) 22%, var(--bg));
}
.alert-head {
  display: flex;
  gap: 10px;
  align-items: baseline;
  width: 100%;
  padding: 6px 12px;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.alert-head:hover { background: rgba(127, 127, 127, 0.12); }
.alert-when { color: var(--dim); font-variant-numeric: tabular-nums; }
.alert-more { margin-left: auto; color: var(--dim); }
.alert-body {
  padding: 0 12px 8px;
  margin: 0;
  max-height: 30vh;
  overflow-y: auto;
  color: var(--dim);
  font-size: 11.5px;
  line-height: 1.45;
  white-space: pre-wrap;
}

/* The window an alert covers, shaded across every panel while its row is
 * hovered: the point of a shared time axis is that a period can be pointed at. */
.alert-band {
  position: absolute;
  left: 0;
  width: 100%;
  z-index: 2;
  pointer-events: none;
  /* Faint: an alert often spans most of the visible axis, and at any real
   * strength the tint recoloured every curve under it. The edges and the label
   * mark the period; the wash only has to hint at it. */
  background: color-mix(in srgb, var(--storm) 7%, transparent);
  border-top: 1.5px solid var(--storm);
  border-bottom: 1.5px solid var(--storm);
}
.alert-band span {
  position: sticky;
  left: calc(var(--gutter, 62px) + 6px);
  display: inline-block;
  padding: 0 4px;
  background: var(--bg);
  color: var(--storm);
  font-size: 10px;
  font-weight: 600;
}

/* ------------------------------ readout --------------------------------- */

.readout {
  padding: 4px 12px;
  border-bottom: 1px solid var(--line);
  background: var(--panel-bg);
  color: var(--dim);
  font-size: 12px;
}

/* -------------------------------- grid ---------------------------------- */

.scroller {
  flex: 1;
  overflow: auto;
  position: relative;
  overscroll-behavior: contain;
}

.grid {
  display: grid;
  grid-template-columns: var(--gutter, 52px) repeat(var(--cols, 1), var(--panel, 92px));
  align-items: start;
  position: relative;
  width: max-content;
}

.loading { padding: 24px; color: var(--dim); grid-column: 1 / -1; }
.loading.error { color: var(--storm); }

/* Column headers stay put while the week scrolls under them; they are also the
 * scrub readout, so they must never leave the screen. */
.head {
  position: sticky;
  top: 0;
  z-index: 3;
  padding: 5px 6px 6px;
  background: var(--panel-bg);
  border-bottom: 1px solid var(--line);
  border-left: 1px solid var(--line);
}
.head.corner {
  left: 0;
  white-space: nowrap;
  overflow: hidden;
  z-index: 5;
  border-left: 0;
  color: var(--dim);
  font-size: 11px;
  display: flex;
  align-items: flex-end;
}
.head-title { font-size: 11px; color: var(--dim); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.head-value { font-size: 13px; font-weight: 600; min-height: 19px; white-space: nowrap; }
/* The unit, riding its number rather than occupying a row of its own: smaller and
 * dimmer, so the value still reads first and the pair reads as one thing. */
.head-value .u { font-size: 10px; font-weight: 400; color: var(--dim); margin-left: 2px; }

/* The value ruler. Absolutely positioned ticks, so each label sits at the exact
 * x of the gridline it names; the panel and the ruler share one tick source. */
.head-ruler { position: relative; height: 11px; margin-top: 1px; }
.head-ruler .tick.tone-uv { color: var(--uv); opacity: 1; }
.head-ruler .tick.tone-rain { color: var(--rain); opacity: 1; }
.head-ruler .tick.tone-aqi { color: var(--aqi); opacity: 1; }
.head-ruler .tick {
  position: absolute;
  top: 0;
  transform: translateX(-50%);
  font-size: 9px;
  line-height: 11px;
  color: var(--dim);
  opacity: 0.85;
  white-space: nowrap;
}

.cell {
  border-left: 1px solid var(--line);
  background: var(--panel-bg);
  box-shadow: inset -1px 0 0 var(--faint);
}
.cell.gutter {
  position: sticky;
  left: 0;
  z-index: 2;
  border-left: 0;
  background: var(--bg);
}

.panel, .gutter-svg { display: block; }

/* ---------------------------- chart elements ----------------------------- */

.night { fill: var(--night); }
.hour-line { stroke: var(--faint); stroke-width: 1; }
.day-line { stroke: var(--line); stroke-width: 1; }
.now-line { stroke: var(--now); stroke-width: 1.5; }
.spine { stroke: var(--line); stroke-width: 1; }
/* Value gridlines: enough to judge a threshold against, never enough to compete
 * with the data drawn over them. */
.value-line { stroke: var(--faint); stroke-width: 1; }
/* A threshold belonging to one series rather than to the panel's own scale:
 * tinted like that series and dashed like its curve. */
.value-line.threshold { stroke-dasharray: 2 3; opacity: 0.5; fill: none; }
.horizon-line { stroke: var(--horizon); stroke-width: 1; stroke-dasharray: 3 3; }

/* Tones carry both stroke and fill so one class serves lines, areas and bars.
 * The kind rules below MUST stay after them: a line has to win `fill: none`
 * against its own tone, and later-and-equal-specificity is how it does. */
.tone-warm { stroke: var(--warm); fill: var(--warm); }
.tone-cold { stroke: var(--cold); fill: var(--cold); }
.tone-snow { stroke: var(--snow); fill: var(--snow); }
.tone-freeze { stroke: var(--freeze); fill: var(--freeze); }
.tone-rain { stroke: var(--rain); fill: var(--rain); }
.tone-cloud { stroke: var(--cloud); fill: var(--cloud); }
.tone-ice { stroke: var(--ice); fill: var(--ice); }
.tone-sun { stroke: var(--sun); fill: var(--sun); }
.tone-uv { stroke: var(--uv); fill: var(--uv); }
.tone-wind { stroke: var(--wind); fill: var(--wind); }
.tone-water { stroke: var(--water); fill: var(--water); }
.tone-storm { stroke: var(--storm); fill: var(--storm); }
.tone-haze { stroke: var(--haze); fill: var(--haze); }
.tone-spread { stroke: var(--spread); fill: var(--spread); }
.tone-aqi { stroke: var(--aqi); fill: var(--aqi); }
.tone-neutral { stroke: var(--neutral); fill: var(--neutral); }
.tone-aqi1 { stroke: var(--aqi1); fill: var(--aqi1); }
.tone-aqi2 { stroke: var(--aqi2); fill: var(--aqi2); }
.tone-aqi3 { stroke: var(--aqi3); fill: var(--aqi3); }
.tone-aqi4 { stroke: var(--aqi4); fill: var(--aqi4); }
.tone-aqi5 { stroke: var(--aqi5); fill: var(--aqi5); }
.tone-aqi6 { stroke: var(--aqi6); fill: var(--aqi6); }
.tone-fall { stroke: var(--storm); fill: var(--storm); }
.tone-rise { stroke: var(--wind); fill: var(--wind); }

.line { fill: none; stroke-width: 1.6; stroke-linejoin: round; vector-effect: non-scaling-stroke; }
.line.dashed { stroke-width: 1.2; stroke-dasharray: 3 2.5; opacity: 0.8; }
/* Quieter still than dashed: a curve that is there to be read AGAINST, not
 * followed. Round caps so it reads as dots rather than a broken line. */
.line.casing {
  stroke: var(--panel-bg);
  stroke-width: 3.6;
  opacity: 0.9;
}
.line.dotted {
  stroke-width: 1.8;
  stroke-dasharray: 0.1 3.2;
  stroke-linecap: round;
  opacity: 1;
}
.area { stroke: none; opacity: 0.5; }
/* A fill that something else is drawn inside: chance behind rain bars, AQI
 * behind PM2.5. */
.area.faint { opacity: 0.22; }
/* Fainter still. The model-spread envelope is not a measurement, and at the dry
 * margin's weight it competed with it for the column. It should read as a shadow
 * the curve sits inside: visible when looked for, ignorable otherwise. */
.area.tone-spread { opacity: 0.13; }
/* A supporting curve: the edge of a fill something else is drawn inside, so it
 * stays findable without competing with the measurement on top of it. */
.line.minor { opacity: 0.5; stroke-width: 1.1; }
.bar { stroke: none; opacity: 0.85; }  /* a rect would inherit its tone's stroke */

.arrow path { stroke: var(--wind); stroke-width: 1.2; fill: none; }
.glyph { font-size: 13px; text-anchor: middle; }

/* Daily high and low, marked on the curve where they occur. */
.extreme-dot { stroke: none; }
.extreme-label { font-size: 10px; font-weight: 600; stroke: none; }

/* An empty column is kept and labelled, never removed. */
.nodata { fill: var(--dim); font-size: 10px; text-anchor: middle; opacity: 0.7; }

.day-label { fill: var(--ink); font-size: 11px; font-weight: 600; }
/* The date below the line the weekday sits above: quieter, since the weekday is
 * what a person scans for. */
.day-date { fill: var(--dim); font-size: 10px; }
.hour-label { fill: var(--dim); font-size: 10px; text-anchor: end; }
.now-label { fill: var(--now); font-size: 10px; font-weight: 600; }

/* The one annotation that is about the data's honesty rather than its value. */
.horizon-note {
  position: absolute;
  left: var(--gutter, 52px);
  transform: translateY(-100%);
  padding: 1px 6px;
  pointer-events: none;
  z-index: 2;
  font-size: 10px;
  color: var(--horizon);
  white-space: nowrap;
  background: var(--bg);
  border-radius: 3px;
  opacity: 0.92;
}

/* Spans the whole grid, not the viewport, so it stays aligned when the panels
 * are scrolled sideways. Above the sticky time gutter but below the sticky
 * column headers, which have to stay readable. */
.crosshair {
  position: absolute;
  left: 0;
  width: 100%;
  height: 0;
  border-top: 1px solid var(--ink);
  opacity: 0.35;
  pointer-events: none;
  z-index: 2;
}

/* -------------------------------- footer -------------------------------- */

.foot {
  padding: 5px 12px;
  border-top: 1px solid var(--line);
  color: var(--dim);
  font-size: 11px;
  white-space: nowrap;
  overflow-x: auto;
}
