thermograph/static/style.css
Emi Griffith 27a6508b72 Calendar totals strip: wider bars, 0.1%-precision small shares (#5)
Widen the per-category deviation bars (3px→10px) so each reads
clearly, and print sub-1% shares as e.g. "0.4%" instead of "<1%".


Claude-Session: https://claude.ai/code/session_019VP23wKmjS2ozk1g5a9g1Z

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 18:38:34 -07:00

789 lines
37 KiB
CSS

:root {
--bg: #0f1216;
--surface: #171b21;
--surface-2: #1e242c;
--border: #2a323c;
--text: #e7ecf2;
--muted: #9aa6b2;
--accent: #f0803c;
/* temperature grades: 9-step diverging cold -> green -> hot (colorblind-safe).
rec-* are the "Near Record" danger tiers — deliberately dark/saturated. */
--rec-cold: #0a2f6b;
--very-cold: #2166ac;
--cold: #4393c3;
--cool: #92c5de;
--normal: #4a9d5b; /* middle of the diverging temp scale = green */
--warm: #f6c18a;
--hot: #ef9351;
--very-hot: #dd6b52;
--rec-hot: #8f0e20;
/* precipitation: "dry" + 9 rain-intensity tiers, light green -> teal -> dark navy */
--dry: #c9a24a;
--wet-1: #d9f0d3;
--wet-2: #b7e2b1;
--wet-3: #8fd18f;
--wet-4: #5cba9f;
--wet-5: #35a1c0; /* middle of the rain scale */
--wet-6: #2b8cbe;
--wet-7: #226bb3;
--wet-8: #164a97;
--wet-9: #08306b;
}
@media (prefers-color-scheme: light) {
:root {
--bg: #f4f6f9;
--surface: #ffffff;
--surface-2: #eef1f5;
--border: #dde3ea;
--text: #1a2029;
--muted: #5b6673;
}
}
* { box-sizing: border-box; }
body {
margin: 0;
font-family: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
background: var(--bg);
color: var(--text);
line-height: 1.45;
}
header {
padding: 18px 24px;
border-bottom: 1px solid var(--border);
background: var(--surface);
}
.brand { display: flex; align-items: center; gap: 14px; }
.logo {
font-size: 30px;
color: var(--accent);
transform: rotate(90deg);
display: inline-block;
}
h1 { margin: 0; font-size: 22px; letter-spacing: -0.02em; }
.tag { margin: 2px 0 0; color: var(--muted); font-size: 13px; }
main { max-width: 1200px; margin: 0 auto; padding: 20px 24px 60px; }
.controls { margin-bottom: 18px; display: flex; flex-wrap: wrap; gap: 16px; align-items: flex-start; }
.search { position: relative; flex: 1 1 340px; display: flex; gap: 8px; }
.search input {
flex: 1; min-width: 0; padding: 11px 14px; border-radius: 10px;
border: 1px solid var(--border); background: var(--surface); color: var(--text);
font-size: 16px; /* >=16px keeps iOS Safari from zooming on focus */
}
.search button, .date-row button {
padding: 11px 18px; border-radius: 10px; border: none;
background: var(--accent); color: #1a1206; font-weight: 600; cursor: pointer;
}
.suggestions {
position: absolute; top: 52px; left: 0; right: 78px; z-index: 500;
list-style: none; margin: 0; padding: 4px;
background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
box-shadow: 0 12px 30px rgba(0,0,0,.35);
}
.suggestions li { padding: 9px 12px; border-radius: 7px; cursor: pointer; font-size: 14px; }
.suggestions li:hover { background: var(--surface-2); }
.suggestions .sub { color: var(--muted); font-size: 12px; }
.date-row { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.date-row label { font-size: 13px; color: var(--muted); display: flex; flex-direction: column; gap: 4px; }
.date-row input {
padding: 9px 12px; border-radius: 10px; border: 1px solid var(--border);
background: var(--surface); color: var(--text); font-size: 16px;
}
/* "Today" quick-reset chip beside the target date. Outlined when the target is a
past date (a clickable "jump to today"); filled accent while already on today. */
.date-row .today-chip {
align-self: flex-end; padding: 9px 14px; border-radius: 10px; min-height: 40px;
font-size: 13px; font-weight: 600; cursor: pointer;
background: transparent; color: var(--muted); border: 1px solid var(--border);
transition: background .12s ease, color .12s ease, border-color .12s ease;
}
.date-row .today-chip:hover { border-color: var(--accent); color: var(--text); }
.date-row .today-chip.active {
background: var(--accent); color: #1a1206; border-color: var(--accent); cursor: default;
}
.hint { color: var(--muted); font-size: 12.5px; max-width: 220px; }
.layout { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
@media (max-width: 900px) { .layout { grid-template-columns: 1fr; } }
#map {
height: 560px; border-radius: 14px; border: 1px solid var(--border);
z-index: 1;
}
.panel {
height: 560px; overflow-y: auto;
background: var(--surface); border: 1px solid var(--border); border-radius: 14px;
padding: 20px;
}
@media (max-width: 900px) { .panel { height: auto; } }
.placeholder p { color: var(--text); }
.muted { color: var(--muted); font-size: 13.5px; }
/* --- results --- */
.loc-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; margin-bottom: 16px; }
.loc-head h2 { margin: 0 0 2px; font-size: 18px; }
.loc-head .meta { color: var(--muted); font-size: 12.5px; margin: 0; }
.share { display: flex; gap: 6px; flex-shrink: 0; flex-wrap: wrap; }
.share button, .share .share-btn {
padding: 6px 11px; border-radius: 8px; border: 1px solid var(--border);
background: var(--surface-2); color: var(--text); font-size: 12px; cursor: pointer;
white-space: nowrap; text-decoration: none; display: inline-flex; align-items: center; gap: 5px;
}
/* Inline monochrome icons (currentColor) used in place of emoji. */
.ic { width: 1em; height: 1em; vertical-align: -0.14em; flex-shrink: 0; }
.ic-lg { width: 26px; height: 26px; }
.wx { width: 1.15em; height: 1.15em; vertical-align: -0.2em; }
.share button:hover, .share .share-btn:hover { border-color: var(--accent); }
/* Prominent call-to-action into the 2-year calendar — the primary next step,
so it reads unmistakably as a tappable button (full width, big touch target). */
.cta-cal {
display: flex; align-items: center; gap: 13px;
width: 100%; margin: 0 0 20px; padding: 14px 16px; min-height: 60px;
border: none; border-radius: 13px; text-decoration: none;
color: #1a1206; background: linear-gradient(135deg, #f79556, #ea722c);
box-shadow: 0 4px 16px rgba(240, 128, 60, .3);
transition: transform .12s ease, box-shadow .12s ease, filter .12s ease;
}
.cta-cal:hover { transform: translateY(-1px); box-shadow: 0 7px 22px rgba(240, 128, 60, .42); filter: brightness(1.04); }
.cta-cal:active { transform: translateY(0); box-shadow: 0 3px 10px rgba(240, 128, 60, .34); }
.cta-cal:focus-visible { outline: 3px solid var(--text); outline-offset: 2px; }
.cta-cal-icon { font-size: 26px; line-height: 1; flex-shrink: 0; }
.cta-cal-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.cta-cal-title { font-size: 16px; font-weight: 800; letter-spacing: .01em; }
.cta-cal-sub { font-size: 12.5px; font-weight: 600; opacity: .8; }
.cta-cal-arrow { margin-left: auto; font-size: 22px; font-weight: 800; flex-shrink: 0; }
#chart-wrap { position: relative; margin-bottom: 22px; }
#chart-wrap svg { width: 100%; height: auto; display: block; border-radius: 10px; touch-action: pan-y; }
/* High / Low / Weather metric toggle above the trend chart; active tab takes the
metric's own color so the control matches the chart it drives. */
.chart-toggle {
display: flex; flex-wrap: wrap; border: 1px solid var(--border); border-radius: 10px;
overflow: hidden; margin-bottom: 10px;
}
.chart-toggle button {
padding: 8px 18px; background: var(--surface); color: var(--text); border: none;
cursor: pointer; font-size: 13.5px; font-weight: 600; min-height: 40px;
}
.chart-toggle button + button { border-left: 1px solid var(--border); }
.chart-toggle button.active[data-metric="tmax"] { background: var(--very-hot); color: #1a1206; }
.chart-toggle button.active[data-metric="tmin"] { background: var(--cold); color: #10222f; }
.chart-toggle button.active[data-metric="feels"] { background: var(--accent); color: #1a1206; }
.chart-toggle button.active[data-metric="humid"] { background: #2ea9bf; color: #04222a; }
.chart-toggle button.active[data-metric="wind"] { background: #5aa9a3; color: #08201e; }
.chart-toggle button.active[data-metric="gust"] { background: #8f86d8; color: #100c26; }
.chart-toggle button.active[data-metric="precip"] { background: var(--wet-6); color: #fff; }
.chart-toggle button.active[data-metric="dry"] { background: #c9a24a; color: #241a04; }
.chart-legend {
display: flex; flex-wrap: wrap; gap: 14px; font-size: 12px; color: var(--muted); margin-bottom: 8px;
}
.chart-legend span { display: inline-flex; align-items: center; gap: 6px; }
.chart-legend i { width: 12px; height: 12px; border-radius: 3px; display: inline-block; }
.chart-legend i.swatch-band { border: 1px solid; border-radius: 3px; }
.chart-legend .legend-note { font-style: italic; opacity: .85; }
.chart-tip {
position: absolute; z-index: 10; pointer-events: none;
background: var(--surface); border: 1px solid var(--border); border-radius: 9px;
padding: 8px 10px; font-size: 12px; line-height: 1.5; min-width: 148px;
box-shadow: 0 8px 24px rgba(0,0,0,.35);
}
.chart-tip .tt-date { font-weight: 700; margin-bottom: 3px; }
.chart-tip .tt-grade { color: var(--muted); }
.chart-tip .tt-norm { color: var(--muted); margin-top: 3px; border-top: 1px solid var(--border); padding-top: 3px; }
/* Flex (not fixed 3-col) so 7 cards fill each row evenly — no lonely trailing card. */
.normals {
display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 22px;
}
.normal-card {
flex: 1 1 130px;
background: var(--surface-2); border: 1px solid var(--border);
border-radius: 11px; padding: 12px 14px;
text-decoration: none; color: var(--text); cursor: pointer;
transition: border-color .12s ease, background .12s ease;
}
.normal-card:hover { border-color: var(--accent); background: var(--surface); }
.normal-card h3 { margin: 0 0 6px; font-size: 12px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); }
/* Big value is the day's own reading, tinted by its grade (lifted toward the text
color so dark tiers stay legible); the sub line shows the grade + normal median. */
.normal-card .big { font-size: 22px; font-weight: 800; color: color-mix(in oklab, var(--cat, var(--text)), var(--text) 15%); }
.normal-card .range { font-size: 12px; color: var(--muted); }
.normal-card .nc-grade { font-weight: 700; color: color-mix(in oklab, var(--cat, var(--muted)), var(--text) 28%); }
.section-title {
font-size: 12px; text-transform: uppercase; letter-spacing: .05em;
color: var(--muted); margin: 4px 0 12px;
}
/* grade-scale key (low -> high, relative categories) — swatch + name only;
the percentile figures live on the full guide, not here. */
.tier-key { display: flex; flex-wrap: wrap; gap: 6px 14px; margin: -2px 0 8px; }
.tk-seg { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; }
.tk-swatch { width: 14px; height: 10px; border-radius: 3px; flex-shrink: 0; }
.tk-label { font-weight: 600; }
.tk-note { margin: 0 0 16px; font-size: 12.5px; }
/* --- shared header view switcher (Map · Calendar · Day) --- */
/* The bar is split into equal, fully-clickable segments (one per page) with a
clear divider between them, so the whole width is a set of obvious tabs. */
.view-nav {
margin-left: auto; align-self: center; display: inline-flex; gap: 0;
background: var(--surface-2); border: 1px solid var(--border);
border-radius: 11px; padding: 3px;
}
.view-nav a {
flex: 1; color: var(--muted); text-decoration: none; font-size: 14px; font-weight: 600;
padding: 8px 18px; border-radius: 8px; white-space: nowrap; position: relative;
display: inline-flex; align-items: center; justify-content: center; min-height: 38px;
}
/* Divider line between adjacent tabs. Hidden around the highlighted (active) tab
so its pill reads clean against the bar. */
.view-nav a + a::before {
content: ""; position: absolute; left: 0; top: 16%; bottom: 16%;
width: 2px; border-radius: 2px; background: var(--border);
}
.view-nav a.active::before,
.view-nav a.active + a::before { background: transparent; }
.view-nav a:hover { color: var(--text); }
.view-nav a.active { background: var(--accent); color: #fff; }
/* --- calendar subpage --- */
.metric-toggle {
display: flex; flex-wrap: wrap; border: 1px solid var(--border); border-radius: 10px; overflow: hidden;
}
.metric-toggle button {
padding: 10px 16px; background: var(--surface); color: var(--text);
border: none; cursor: pointer; font-size: 14px; min-height: 44px; white-space: nowrap;
}
.metric-toggle button + button { border-left: 1px solid var(--border); }
.metric-toggle button.active { background: var(--accent); color: #1a1206; font-weight: 600; }
/* Metric selector block — sits below the time filters, labeling the grid it colors. */
.metric-block { margin: 0 0 18px; }
.metric-block .cal-filter-label { display: block; margin: 0 0 8px; }
/* Month/year range picker (calendar). 16px inputs avoid iOS zoom-on-focus. */
.cal-range { display: grid; grid-template-columns: 1fr 1fr; gap: 8px 12px; }
/* Range label + "up to 3 years" hint share one row spanning both input columns. */
.cal-range-head {
grid-column: 1 / -1;
display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
}
.cal-range label {
display: flex; flex-direction: column; gap: 4px; font-size: 13px; color: var(--muted);
}
.cal-range input {
width: 100%; font-size: 16px; padding: 8px 10px; border-radius: 8px; min-height: 40px;
border: 1px solid var(--border); background: var(--surface); color: var(--text);
}
.cal-range .hint { margin: 0; flex: 0 0 auto; max-width: none; font-size: 12px; color: var(--muted); }
/* Appears under the pickers once the user edits a date; no request is made until
it's tapped. Spans both columns so it reads as a clear confirm step. */
.range-refresh {
grid-column: 1 / -1; margin-top: 2px;
padding: 10px 16px; min-height: 44px; border-radius: 9px; cursor: pointer;
font-size: 15px; font-weight: 700;
background: var(--accent); color: #1a1206; border: 1px solid var(--accent);
}
.range-refresh:hover { filter: brightness(1.05); }
/* Time-filter panel: month/year range + season + year checklists, stacked in a
tidy column so nothing balloons on wide screens or leaves gaps on phones. */
.cal-filters {
display: flex; flex-direction: column; gap: 16px;
max-width: 560px; margin: 0 0 18px; padding: 14px 16px;
border: 1px solid var(--border); border-radius: 12px; background: var(--surface);
}
/* Season + year dropdowns fill the row evenly — no dead space to their right. */
.cal-seasonyear { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.cal-filter-group { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.cal-filter-label {
font-size: 11px; text-transform: uppercase; letter-spacing: .04em;
color: var(--muted); font-weight: 700; margin-right: 2px;
}
.cal-chip {
display: inline-flex; align-items: center; gap: 7px; cursor: pointer; user-select: none;
font-size: 13px; padding: 6px 11px; min-height: 36px;
border: 1px solid var(--border); border-radius: 9px; background: var(--surface);
}
.cal-chip input { accent-color: var(--accent); width: 16px; height: 16px; margin: 0; }
.cal-chip:has(input:checked) {
border-color: var(--accent);
background: color-mix(in oklab, var(--accent), transparent 88%);
}
.cal-head { margin-bottom: 16px; }
.cal-head h2 { margin: 0 0 2px; font-size: 18px; }
.cal-head .meta { color: var(--muted); font-size: 12.5px; margin: 0; }
/* "loading N more blocks…" note while a long span streams in chunk by chunk. */
.cal-loading { color: var(--accent); font-weight: 600; }
/* Nudge to interact with the grid — wording covers both tap (mobile) and click. */
.cal-hint {
display: inline-flex; align-items: center; gap: 6px;
margin: 10px 0 0; padding: 6px 11px; font-size: 13px; font-weight: 600;
color: var(--accent); background: color-mix(in oklab, var(--accent), transparent 88%);
border: 1px solid color-mix(in oklab, var(--accent), transparent 70%);
border-radius: 999px;
}
/* Months stacked vertically (one per row), with roomier cells. */
.calendar { display: flex; flex-direction: column; gap: 18px; margin-bottom: 22px; align-items: stretch; }
/* Each month fills the available width (capped on wide desktops so the squares
don't balloon); the 7 columns split it evenly and cells stay square. */
.cal-month { width: 100%; max-width: 560px; }
.cal-month h4 { margin: 0 0 6px; font-size: 14px; color: var(--text); font-weight: 700; }
.cal-dows, .cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 5px; }
.cal-dows { margin-bottom: 3px; }
.cal-dows span { font-size: 11px; color: var(--muted); text-align: center; line-height: 1; }
.cal-cell { position: relative; aspect-ratio: 1; border-radius: 6px; background: var(--surface-2); }
.cal-cell.empty { background: transparent; }
.cal-cell.filled { cursor: pointer; }
/* Day-of-month number, corner-anchored. White with a dark halo so it reads on any
tier color; on transparent (no-record) tiles fall back to a plain muted number. */
.cal-daynum {
position: absolute; top: 2px; left: 4px; font-size: 10px; font-weight: 700;
line-height: 1; color: #fff; pointer-events: none;
text-shadow: 0 0 2px rgba(0, 0, 0, .9), 0 1px 1px rgba(0, 0, 0, .7);
}
.cal-cell.empty .cal-daynum { color: var(--muted); text-shadow: none; }
.cal-cell.filled:hover { outline: 2px solid var(--text); outline-offset: 1px; }
/* Fixed positioning so the tooltip tracks the viewport (clientX/Y) even when the
page is scrolled far down the calendar. */
#cal-tip { position: fixed; max-width: calc(100vw - 20px); }
/* Mini grid of the day's stats: metric name · value(+pct) · category. */
#cal-tip .tt-grid {
display: grid; grid-template-columns: auto auto minmax(0, 1fr);
column-gap: 12px; row-gap: 3px; align-items: baseline;
}
#cal-tip .tt-name { font-weight: 700; }
#cal-tip .tt-val { white-space: nowrap; }
#cal-tip .tt-pct { color: var(--muted); }
/* Dry streak has no percentile, so its text spans the value + category columns. */
#cal-tip .tt-span2 { grid-column: 2 / -1; }
/* The stat currently being viewed is bolded and separated from the rest, so it's
easy to match a cell's color to the stat it represents. The border spans all
three columns because it's set on every cell of the active row. */
#cal-tip .tt-name.tt-r-active, #cal-tip .tt-val.tt-r-active { font-weight: 800; }
#cal-tip .tt-r-active { padding-bottom: 5px; border-bottom: 1px solid var(--border); }
/* Category (grade) text, right-aligned, is tinted by --cat — the color that
represents that day's value for the metric. The active calendar metric gets it
bold and near full saturation; the others are lifted toward --text so they stay
readable and read as secondary. color-mix keeps every tier legible on both. */
#cal-tip .tt-cat {
justify-self: end; text-align: right; font-weight: 600;
color: color-mix(in oklab, var(--cat, var(--muted)), var(--text) 50%);
}
#cal-tip .tt-cat-active {
font-weight: 800;
color: color-mix(in oklab, var(--cat, var(--muted)), var(--text) 12%);
}
/* Plain-language "what the day was like" summary title, sits just under the date. */
#cal-tip .tt-type {
font-size: 15px; font-weight: 800; margin: 2px 0 6px;
padding-bottom: 6px; border-bottom: 1px solid var(--border);
}
/* --- single-day detail subpage --- */
.day-weather { margin: 2px 0 4px; font-size: 16px; font-weight: 800; }
.day-nav { display: flex; align-items: flex-end; gap: 8px; }
.day-date { font-size: 13px; color: var(--muted); display: flex; flex-direction: column; gap: 4px; }
.day-date input {
padding: 9px 12px; border-radius: 10px; border: 1px solid var(--border);
background: var(--surface); color: var(--text); font-size: 16px;
}
.day-step {
min-width: 44px; min-height: 44px; padding: 0 12px; border-radius: 10px;
border: 1px solid var(--border); background: var(--surface-2); color: var(--text);
font-size: 22px; line-height: 1; cursor: pointer;
}
.day-step:hover:not(:disabled) { border-color: var(--accent); }
.day-step:disabled { opacity: .4; cursor: default; }
.ladder-card {
background: var(--surface); border: 1px solid var(--border);
border-radius: 13px; padding: 16px 16px 12px; margin-bottom: 16px;
}
.ladder-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.ladder-head h3 { margin: 0; font-size: 15px; }
.ladder-summary { display: inline-flex; align-items: baseline; gap: 8px; }
.day-obs {
font-size: 20px; font-weight: 800;
color: color-mix(in oklab, var(--cat, var(--text)), var(--text) 12%);
}
.day-obs-meta { font-size: 12.5px; color: var(--muted); }
.ladder-note {
display: flex; justify-content: space-between; align-items: baseline; gap: 12px;
margin: 4px 0 12px; font-size: 12px; color: var(--muted);
}
.ladder-note-left { min-width: 0; }
/* Historical range: bold, pinned to the right edge of the card. */
.ladder-range { font-weight: 700; color: var(--text); white-space: nowrap; }
/* Tier ladder: swatch · label · percentile range · value range · observed marker.
Fixed column widths (not auto) so every row shares the same tracks — the
percentile ranges and value ranges line up down the whole ladder. The negative
margin cancels the row's own padding so the swatches sit on the same left edge
as the card title and the subheader note above. */
.ladder { display: flex; flex-direction: column; gap: 3px; }
.ladder-row {
display: grid; grid-template-columns: 14px 128px 64px 1fr 60px;
align-items: center; column-gap: 10px; padding: 6px 8px; margin: 0 -8px;
border-radius: 8px; font-size: 13px;
}
.ladder-row.active {
background: color-mix(in oklab, var(--accent), transparent 86%);
outline: 1px solid color-mix(in oklab, var(--accent), transparent 55%);
}
.ladder-sw { width: 14px; height: 14px; border-radius: 4px; }
.ladder-label { font-weight: 700; color: color-mix(in oklab, var(--cat, var(--text)), var(--text) 22%); }
.ladder-pct { color: var(--muted); font-size: 12px; }
.ladder-val { justify-self: end; font-variant-numeric: tabular-nums; white-space: nowrap; }
/* Observed marker: stays in the rightmost column but reads from its left edge,
so the arrow sits right beside the value range it points at. */
.ladder-mk { justify-self: start; min-width: 0; }
.ladder-mark { color: var(--accent); font-weight: 800; white-space: nowrap; font-size: 12.5px; }
@media (max-width: 640px) {
.calendar { gap: 16px; }
.metric-toggle { width: 100%; }
/* 7 metrics wrap to ~4 per row on a phone instead of overflowing one line. */
.metric-toggle button { flex: 1 0 25%; padding: 11px 4px; font-size: 12px; }
/* Compress the day tooltip so it fits narrow phone screens without cutting off. */
#cal-tip { padding: 7px 9px; min-width: 0; font-size: 11.5px; line-height: 1.4; }
#cal-tip .tt-type { font-size: 13.5px; margin: 1px 0 5px; padding-bottom: 5px; }
#cal-tip .tt-grid { column-gap: 8px; row-gap: 2px; }
/* Day ladder on mobile: tighten the fixed tracks but keep every column (including
the percentile range) so the ranges still line up down the ladder. */
.day-nav { width: 100%; }
.day-date { flex: 1; }
.ladder-row { grid-template-columns: 13px 90px 54px 1fr 48px; column-gap: 8px; font-size: 12.5px; }
}
/* Recent | Forecast time-range switch above the trend chart. */
.series-toggle {
display: inline-flex; border: 1px solid var(--border); border-radius: 10px;
overflow: hidden; margin-bottom: 14px;
}
.series-toggle button {
padding: 8px 22px; background: var(--surface); color: var(--text); border: none;
cursor: pointer; font-size: 13.5px; font-weight: 600; min-height: 40px;
}
.series-toggle button + button { border-left: 1px solid var(--border); }
.series-toggle button.active { background: var(--accent); color: #1a1206; }
/* Compact "graded days" table: a ROW per metric, a COLUMN per day. Wide runs
scroll horizontally in their own container; the metric-label column is pinned. */
.rd-scroll { position: relative; overflow-x: auto; margin: 0 0 6px; padding-bottom: 4px; }
.rd-grid { display: grid; gap: 3px; align-items: stretch; min-width: min-content; }
/* Simple scroll-orientation cue for the timeline: oldest at left, newest at right. */
.rd-orient {
display: flex; justify-content: space-between; margin: 0 0 8px;
font-size: 11.5px; font-weight: 600; letter-spacing: .03em; color: var(--muted);
}
/* Forecast (future) columns: accent-tinted headers. The accent divider marks the
start of *today's* column (the anchored day), with the forecast to its right. */
.rd-colh.rd-fc b { color: var(--accent); }
.rd-colh.rd-fc span { color: color-mix(in oklab, var(--accent), var(--muted) 45%); }
.rd-colh.rd-today b { color: var(--accent); }
.rd-c.rd-fc { opacity: .9; }
.rd-colh.rd-today,
.rd-c.rd-today { box-shadow: inset 2px 0 0 var(--accent); }
.rd-corner {
position: sticky; left: 0; z-index: 3; background: var(--surface);
}
.rd-mlabel {
position: sticky; left: 0; z-index: 2; background: var(--surface);
display: flex; align-items: center; padding-right: 6px;
font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .03em;
color: var(--muted);
}
.rd-colh {
display: flex; flex-direction: column; align-items: center; justify-content: flex-end;
gap: 0; padding-bottom: 3px; font-size: 10px; color: var(--muted);
line-height: 1.15; white-space: nowrap; cursor: pointer;
}
.rd-colh b { color: var(--text); font-size: 11.5px; font-weight: 700; }
.rd-c {
display: flex; align-items: center; justify-content: center;
min-height: 30px; border-radius: 6px; font-size: 12px; font-weight: 700;
font-variant-numeric: tabular-nums; cursor: pointer;
background: color-mix(in oklab, var(--c, var(--surface-2)), transparent 74%);
color: color-mix(in oklab, var(--c, var(--text)), var(--text) 28%);
}
.rd-c[data-date]:hover { filter: brightness(1.18); }
/* grade colors */
.g-rec-cold { color: var(--rec-cold); } .m-rec-cold { background: var(--rec-cold); }
.g-very-cold { color: var(--very-cold); } .m-very-cold { background: var(--very-cold); }
.g-cold { color: var(--cold); } .m-cold { background: var(--cold); }
.g-cool { color: var(--cool); } .m-cool { background: var(--cool); }
.g-normal { color: var(--normal); } .m-normal { background: var(--normal); }
.g-warm { color: var(--warm); } .m-warm { background: var(--warm); }
.g-hot { color: var(--hot); } .m-hot { background: var(--hot); }
.g-very-hot { color: var(--very-hot); } .m-very-hot { background: var(--very-hot); }
.g-rec-hot { color: var(--rec-hot); } .m-rec-hot { background: var(--rec-hot); }
.g-dry { color: var(--dry); } .m-dry { background: var(--dry); }
.g-wet-1 { color: var(--wet-1); } .m-wet-1 { background: var(--wet-1); }
.g-wet-2 { color: var(--wet-2); } .m-wet-2 { background: var(--wet-2); }
.g-wet-3 { color: var(--wet-3); } .m-wet-3 { background: var(--wet-3); }
.g-wet-4 { color: var(--wet-4); } .m-wet-4 { background: var(--wet-4); }
.g-wet-5 { color: var(--wet-5); } .m-wet-5 { background: var(--wet-5); }
.g-wet-6 { color: var(--wet-6); } .m-wet-6 { background: var(--wet-6); }
.g-wet-7 { color: var(--wet-7); } .m-wet-7 { background: var(--wet-7); }
.g-wet-8 { color: var(--wet-8); } .m-wet-8 { background: var(--wet-8); }
.g-wet-9 { color: var(--wet-9); } .m-wet-9 { background: var(--wet-9); }
/* "Near Record" danger tiers: fill the whole grade cell to make it pop. */
.grade.danger { border-color: transparent; }
.grade.danger .lbl, .grade.danger .val, .grade.danger .band { color: #fff; }
.grade.danger .meter { background: rgba(255,255,255,.28); }
.grade.d-rec-hot { background: var(--rec-hot); }
.grade.d-rec-cold { background: var(--rec-cold); }
.error { color: #e06a6a; font-size: 14px; }
.spinner { color: var(--muted); font-size: 14px; }
.legend { margin-top: 18px; font-size: 12px; color: var(--muted); }
.legend b { color: var(--text); font-weight: 600; }
/* --- phones / narrow screens --- */
@media (max-width: 640px) {
header { padding: 14px 16px; }
h1 { font-size: 19px; }
.tag { font-size: 12px; }
/* Let the header wrap so the view switcher drops to its own full-width row
(and stays a comfortable touch target) instead of crowding the title. */
.brand { flex-wrap: wrap; }
/* Full-width bar; each tab is an equal, fully-tappable third. */
.view-nav { margin-left: 0; width: 100%; }
.view-nav a { min-height: 44px; padding: 8px 12px; }
main { padding: 14px 14px 40px; }
.controls { gap: 12px; margin-bottom: 14px; }
/* Map + panel stack (from the 900px rule); shrink the map so results are
reachable with less scrolling on a phone. */
#map { height: 44vh; min-height: 240px; }
.panel { padding: 16px; }
/* Give buttons a comfortable touch target (~44px). */
.search button, .date-row button { padding: 12px 16px; min-height: 44px; }
.share button { padding: 9px 12px; font-size: 12.5px; min-height: 38px; }
.date-row { gap: 10px; width: 100%; }
.hint { max-width: none; flex-basis: 100%; }
.loc-head { flex-wrap: wrap; }
.share { flex-wrap: wrap; }
/* Keep the normal cards legible when very narrow. */
.normals { gap: 8px; }
.normal-card { padding: 10px 11px; }
.normal-card .big { font-size: 19px; }
/* Compact table + series toggle tuned for phone width. */
.series-toggle { width: 100%; }
.series-toggle button { flex: 1; }
/* Chart metric toggle: force an even 4-per-row grid so the buttons line up
(without this the flex-wrap sizes each to its label and the rows go ragged). */
.chart-toggle { width: 100%; }
.chart-toggle button { flex: 1 0 25%; padding: 9px 4px; font-size: 12px; }
.rd-c { min-height: 30px; font-size: 11px; border-radius: 5px; }
.rd-mlabel { font-size: 10px; padding-right: 4px; }
.rd-colh { font-size: 9px; }
.rd-colh b { font-size: 10.5px; }
}
/* --- Find button + current-place label (opens the modal map picker) --- */
.find-bar { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; flex: 1 1 340px; }
.find-btn {
display: inline-flex; align-items: center; gap: 8px;
padding: 11px 18px; border-radius: 10px; border: none; cursor: pointer;
background: var(--accent); color: #1a1206; font-weight: 700; font-size: 15px; min-height: 44px;
}
.find-btn svg { width: 18px; height: 18px; flex-shrink: 0; }
.find-btn:hover { filter: brightness(1.05); }
.loc-label { color: var(--text); font-weight: 600; font-size: 14px; }
/* Weekly panel is now full width (the map lives in the picker, not inline). */
.panel-full { height: auto; overflow: visible; }
/* Link styling for the compact legend notes + guide page. */
.tk-note a, .hint a, .guide-back a { color: var(--accent); text-decoration: none; font-weight: 600; }
.tk-note a:hover, .hint a:hover, .guide-back a:hover { text-decoration: underline; }
/* --- shared modal location picker --- */
.mp-overlay {
position: fixed; inset: 0; z-index: 1000;
background: rgba(0, 0, 0, .55);
display: flex; align-items: center; justify-content: center; padding: 16px;
}
.mp-overlay[hidden] { display: none; }
.mp-modal {
width: 100%; max-width: 560px; max-height: 92vh;
display: flex; flex-direction: column;
background: var(--surface); border: 1px solid var(--border);
border-radius: 16px; overflow: hidden; box-shadow: 0 24px 60px rgba(0, 0, 0, .5);
}
.mp-head {
display: flex; align-items: center; justify-content: space-between;
padding: 14px 16px; border-bottom: 1px solid var(--border);
}
.mp-head h2 { margin: 0; font-size: 16px; }
.mp-close {
border: none; background: transparent; color: var(--muted); cursor: pointer;
font-size: 26px; line-height: 1; padding: 0 4px; min-width: 40px; min-height: 40px;
}
.mp-close:hover { color: var(--text); }
.mp-search { position: relative; display: flex; gap: 8px; padding: 12px 16px 8px; }
.mp-search input {
flex: 1; min-width: 0; padding: 11px 14px; border-radius: 10px;
border: 1px solid var(--border); background: var(--bg); color: var(--text); font-size: 16px;
}
.mp-search button {
padding: 11px 16px; border-radius: 10px; border: none; min-height: 44px; cursor: pointer;
background: var(--accent); color: #1a1206; font-weight: 600;
}
.mp-sug {
position: absolute; top: 100%; left: 16px; right: 16px; z-index: 5;
list-style: none; margin: 4px 0 0; padding: 4px; max-height: 240px; overflow-y: auto;
background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
box-shadow: 0 12px 30px rgba(0, 0, 0, .4);
}
.mp-sug li { padding: 10px 12px; border-radius: 7px; cursor: pointer; font-size: 14px; }
.mp-sug li:hover { background: var(--surface-2); }
.mp-sug .sub { color: var(--muted); font-size: 12px; }
.mp-map {
flex: 1 1 auto; min-height: 300px; margin: 4px 16px; z-index: 0;
border-radius: 12px; overflow: hidden; border: 1px solid var(--border);
}
.mp-foot {
display: flex; align-items: center; justify-content: space-between; gap: 12px;
padding: 12px 16px 16px; flex-wrap: wrap;
}
.mp-hint { color: var(--muted); font-size: 12.5px; flex: 1 1 auto; min-width: 0; }
.mp-confirm {
padding: 11px 18px; border-radius: 10px; border: none; min-height: 44px; cursor: pointer;
background: var(--accent); color: #1a1206; font-weight: 700;
}
.mp-confirm:disabled { opacity: .45; cursor: default; }
/* --- calendar Season/Year filters as compact dropdowns (native <details>) --- */
.cal-dd { position: relative; min-width: 0; }
.cal-dd summary {
display: flex; align-items: center; gap: 8px; cursor: pointer; user-select: none;
list-style: none; padding: 8px 12px; min-height: 40px;
border: 1px solid var(--border); border-radius: 9px; background: var(--surface);
}
.cal-dd summary::-webkit-details-marker { display: none; }
.cal-dd summary .cal-filter-label { flex: 0 0 auto; }
.cal-dd[open] summary { border-color: var(--accent); }
/* Summary value truncates rather than wrapping to a second line in the narrow cell. */
.cal-dd-sum {
flex: 1 1 auto; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
font-size: 13px; color: var(--text); font-weight: 600;
}
/* Push the caret to the right edge so summaries read cleanly at full width. */
.cal-dd-caret { margin-left: auto; color: var(--muted); font-size: 11px; transition: transform .15s ease; }
.cal-dd[open] .cal-dd-caret { transform: rotate(180deg); }
.cal-dd-panel {
position: absolute; top: calc(100% + 6px); left: 0; z-index: 20; min-width: 168px;
display: flex; flex-direction: column; gap: 2px; padding: 6px;
background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
box-shadow: 0 12px 30px rgba(0, 0, 0, .35);
}
.cal-dd-opt {
display: flex; align-items: center; gap: 9px; cursor: pointer;
font-size: 14px; padding: 8px 10px; border-radius: 7px; min-height: 40px;
}
.cal-dd-opt:hover { background: var(--surface-2); }
.cal-dd-opt input { accent-color: var(--accent); width: 17px; height: 17px; margin: 0; }
/* Group headings inside a dropdown panel (the weather filter's Feel / Sky halves). */
.cal-dd-group {
font-size: 11px; text-transform: uppercase; letter-spacing: .04em;
color: var(--muted); font-weight: 700; padding: 8px 10px 2px;
}
/* The weather filter spans the full filter row beneath Seasons + Years, and its
panel is two columns so 13 checkboxes don't make a skyscraper on phones. */
.cal-weather { grid-column: 1 / -1; }
.cal-weather .cal-dd-panel {
left: 0; right: 0; display: grid; grid-template-columns: 1fr 1fr; column-gap: 6px;
}
.cal-weather .cal-dd-group { grid-column: 1 / -1; }
/* Comfort temperature slider (Feels metric). 44px-tall slider = full touch target. */
.comfort-row { display: flex; align-items: center; flex-wrap: wrap; gap: 4px 12px; margin: 10px 0 0; }
.comfort-row[hidden] { display: none; } /* display:flex would defeat the hidden attr */
.comfort-row label { font-size: 13px; color: var(--muted); flex: 0 0 auto; }
.comfort-row label b { color: var(--text); font-size: 14px; }
.comfort-row input[type="range"] {
flex: 1 1 180px; min-width: 150px; height: 44px; margin: 0;
accent-color: var(--accent); background: transparent;
}
.comfort-row .hint { flex: 1 1 100%; margin: 0; font-size: 12px; color: var(--muted); }
/* Category totals above the grid: a compact deviation strip. Each category is a
column with its share printed at the top (in that category's own status color)
and a thin status-colored line rising from a shared baseline (height ∝ share).
The median tier draws no line — just a faint baseline tick as the neutral anchor. */
.cal-totals { max-width: 560px; margin: 0 0 18px; }
.ct-strip {
display: flex; align-items: flex-end; gap: 6px;
height: calc(var(--line-max, 30px) + 22px); margin: 8px 0 6px;
}
.ct-col { position: relative; flex: 1 1 0; min-width: 0; height: 100%; }
.ct-num {
position: absolute; top: 0; left: 0; right: 0; text-align: center;
font-size: 11.5px; font-weight: 700; line-height: 1;
color: color-mix(in oklab, var(--c, var(--muted)), var(--text) 40%);
}
.ct-num-zero { font-weight: 600; opacity: .38; }
.ct-line {
position: absolute; bottom: 0; left: 50%; transform: translateX(-50%);
width: 10px; border-radius: 3px 3px 0 0; background: var(--c);
}
.ct-col-median .ct-num { color: var(--muted); font-weight: 800; }
.ct-mid {
position: absolute; bottom: 0; left: 50%; transform: translateX(-50%);
width: 7px; height: 3px; border-radius: 2px; background: var(--border);
}
/* Days filtered out by the weather filter fade back so matches pop. */
.cal-cell.dim { opacity: .15; }
/* --- guide / legend page --- */
.guide { max-width: 760px; }
.guide-block { margin: 0 0 26px; }
.guide-block h2 { font-size: 16px; margin: 0 0 10px; }
.guide-block p { font-size: 14px; margin: 0 0 10px; }
.guide-scale { display: flex; flex-wrap: wrap; gap: 8px 18px; }
.guide-seg { display: inline-flex; align-items: center; gap: 8px; font-size: 13.5px; }
.guide-sw { width: 22px; height: 14px; border-radius: 4px; flex-shrink: 0; }
.guide-lbl { font-weight: 600; }
.guide-rng { color: var(--muted); font-size: 12px; }
.guide-metrics { display: grid; grid-template-columns: max-content 1fr; gap: 8px 18px; margin: 0; }
.guide-metrics dt { font-weight: 700; color: var(--text); }
.guide-metrics dd { margin: 0; color: var(--muted); font-size: 14px; }
.guide-back { margin-top: 8px; }
@media (max-width: 640px) {
.mp-overlay { padding: 0; }
.mp-modal { max-width: none; max-height: 100vh; height: 100vh; border-radius: 0; border: none; }
.find-bar { width: 100%; }
.guide-metrics { grid-template-columns: 1fr; gap: 2px 0; }
.guide-metrics dt { margin-top: 10px; }
}