On narrow screens the single-word "Moderate" label sits between the two-line "Light–Mod" and "Mod–Heavy" labels; its centered box is wider than the column and spilled over their first line. Tag each rain column with its tier key and nudge the "Moderate" (wet-5) label up into the empty gap above to clear it.
943 lines
47 KiB
CSS
943 lines
47 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);
|
||
}
|
||
/* Header content shares the main column's width so the logo and tabs line up
|
||
with the content edges instead of hugging the monitor corners. (1152 = main's
|
||
1200 max minus its 24px side paddings; the header carries its own padding.) */
|
||
.brand { display: flex; align-items: center; gap: 14px; max-width: 1152px; margin: 0 auto; }
|
||
/* The title block (logo's neighbour) grows and may shrink, so the unit toggle is
|
||
pushed to the top-right on the logo's row and the long tagline wraps inside it
|
||
instead of bumping the toggle onto its own line. */
|
||
.brand > div:not(.unit-toggle) { flex: 1 1 0; min-width: 0; }
|
||
.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; }
|
||
/* Compact header (e.g. Calendar): no tagline, roughly half the height.
|
||
Only the vertical padding is trimmed so the responsive side padding stands. */
|
||
header.compact { padding-top: 9px; padding-bottom: 9px; }
|
||
header.compact .logo { font-size: 22px; }
|
||
header.compact h1 { font-size: 18px; }
|
||
|
||
main { max-width: 1200px; margin: 0 auto; padding: 20px 24px 60px; }
|
||
|
||
/* Larger monitors: widen the content column in steps (1440p, 2K, 4K) so pages
|
||
use the screen instead of a 1200px strip. Each page's own grids (normals,
|
||
calendar months, day cards) flow into the extra room. */
|
||
@media (min-width: 1680px) {
|
||
main { max-width: 1440px; }
|
||
.brand { max-width: 1392px; }
|
||
}
|
||
@media (min-width: 2400px) {
|
||
main { max-width: 1640px; }
|
||
.brand { max-width: 1592px; }
|
||
}
|
||
@media (min-width: 3400px) {
|
||
main { max-width: 1880px; }
|
||
.brand { max-width: 1832px; }
|
||
}
|
||
|
||
.controls { margin-bottom: 18px; display: flex; flex-wrap: wrap; gap: 16px; align-items: flex-start; }
|
||
/* ---- shared control recipes ----
|
||
One home for the accent-button and text-input look every view reuses. The
|
||
16px input font is load-bearing (below it iOS zooms on focus); buttons keep
|
||
their ≥44px touch targets in the per-component rules, which carry only each
|
||
control's differences from these. */
|
||
.date-row button, .range-refresh, .find-btn, .mp-search button, .mp-confirm {
|
||
border-radius: 10px; border: none; cursor: pointer;
|
||
background: var(--accent); color: #1a1206; font-weight: 600;
|
||
}
|
||
.date-row input, .cal-range input, .day-date input, .mp-search input {
|
||
padding: 9px 12px; border-radius: 10px; border: 1px solid var(--border);
|
||
background: var(--surface); color: var(--text); font-size: 16px;
|
||
}
|
||
.date-row button { padding: 11px 18px; }
|
||
|
||
.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; }
|
||
/* "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; }
|
||
|
||
.panel {
|
||
background: var(--surface); border: 1px solid var(--border); border-radius: 14px;
|
||
padding: 20px;
|
||
}
|
||
|
||
.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; }
|
||
/* Context stacked top-down, one fact per line, instead of dot-joined tags. */
|
||
.loc-meta { list-style: none; margin: 4px 0 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
|
||
.loc-meta li { color: var(--muted); font-size: 12.5px; line-height: 1.35; }
|
||
.share { display: flex; gap: 6px; flex-shrink: 0; flex-wrap: wrap; }
|
||
.share button {
|
||
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 { border-color: var(--accent); }
|
||
|
||
/* Call-to-action into the 2-year calendar — a compact button-sized link, so the
|
||
graded results stay the page's focal point. */
|
||
.cta-cal {
|
||
display: inline-flex; align-items: center; gap: 9px;
|
||
margin: 0 0 20px; padding: 10px 16px; min-height: 44px;
|
||
border: none; border-radius: 10px; text-decoration: none;
|
||
font-size: 14px; font-weight: 700;
|
||
color: #1a1206; background: linear-gradient(135deg, #f79556, #ea722c);
|
||
box-shadow: 0 2px 8px rgba(240, 128, 60, .25);
|
||
transition: transform .12s ease, box-shadow .12s ease, filter .12s ease;
|
||
}
|
||
.cta-cal .ic-lg { width: 18px; height: 18px; }
|
||
.cta-cal:hover { transform: translateY(-1px); box-shadow: 0 4px 12px rgba(240, 128, 60, .35); filter: brightness(1.04); }
|
||
.cta-cal:active { transform: translateY(0); box-shadow: 0 2px 6px rgba(240, 128, 60, .3); }
|
||
.cta-cal:focus-visible { outline: 3px solid var(--text); outline-offset: 2px; }
|
||
.cta-cal-icon { line-height: 1; flex-shrink: 0; display: inline-flex; }
|
||
.cta-cal-arrow { font-size: 16px; 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); }
|
||
/* Beyond phones the switch shrinks to its tabs and sits centered over the chart
|
||
(phones keep the full-width 4-per-row grid from the 640px rules). */
|
||
@media (min-width: 641px) {
|
||
.chart-toggle { width: fit-content; margin-left: auto; margin-right: auto; }
|
||
}
|
||
.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); }
|
||
/* Title on the left, grade status pinned to the top-right corner of the card. */
|
||
.normal-card .nc-head { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; margin-bottom: 6px; }
|
||
.normal-card h3 { margin: 0; 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 line under it shows the 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); margin-top: 2px; }
|
||
.normal-card .nc-grade { font-size: 11px; font-weight: 700; text-align: right; line-height: 1.2; 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; }
|
||
|
||
/* --- header °F/°C unit toggle (sits at the top-right, left of the tabs) --- */
|
||
.unit-toggle {
|
||
margin-left: auto; align-self: center; flex-shrink: 0; display: inline-flex; gap: 2px;
|
||
background: var(--surface-2); border: 1px solid var(--border);
|
||
border-radius: 11px; padding: 3px;
|
||
}
|
||
.unit-toggle button {
|
||
border: 0; background: transparent; color: var(--muted); cursor: pointer;
|
||
font-size: 14px; font-weight: 700; padding: 8px 12px; border-radius: 8px;
|
||
min-height: 38px; min-width: 44px; line-height: 1;
|
||
}
|
||
.unit-toggle button.active { background: var(--accent); color: #fff; }
|
||
.unit-toggle button:hover:not(.active) { color: var(--text); }
|
||
/* The view switcher already claims the right edge; with the toggle to its left,
|
||
drop its auto-margin so the two sit together in the corner. */
|
||
.unit-toggle + .view-nav { margin-left: 8px; }
|
||
|
||
/* --- 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; }
|
||
/* Beyond phones the selector shrinks to its tabs and sits centered over the
|
||
month grid (scoped under .metric-block — the Compare page's basis toggle
|
||
shares .metric-toggle and must keep filling its param cell). */
|
||
@media (min-width: 641px) {
|
||
.metric-block { display: flex; flex-direction: column; align-items: center; }
|
||
.metric-block .cal-filter-label { text-align: center; }
|
||
}
|
||
|
||
/* 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%; padding: 8px 10px; border-radius: 8px; min-height: 40px; }
|
||
.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;
|
||
font-size: 15px; font-weight: 700; 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 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; }
|
||
/* Desktop: the panel spans the content width as one row — the date range on the
|
||
left, the season/year/weather dropdowns filling the rest — instead of a narrow
|
||
stacked box with dead space beside it. */
|
||
@media (min-width: 960px) {
|
||
.cal-filters { max-width: none; flex-direction: row; align-items: flex-start; gap: 14px 28px; }
|
||
.cal-range { flex: 0 1 400px; }
|
||
.cal-seasonyear { flex: 1 1 500px; grid-template-columns: 1fr 1fr 1.6fr; align-content: start; }
|
||
/* A head row mirroring the Range label, so both halves read as label + controls. */
|
||
.cal-seasonyear::before {
|
||
content: "Filter by"; grid-column: 1 / -1;
|
||
font-size: 11px; text-transform: uppercase; letter-spacing: .04em;
|
||
color: var(--muted); font-weight: 700; line-height: 1.45;
|
||
}
|
||
/* Weather joins the same row as Seasons + Years (it only spans the full width
|
||
in the stacked phone layout — the extra .cal-seasonyear qualifier outranks
|
||
that base rule); its panel keeps a usable two-column width, anchored right. */
|
||
.cal-seasonyear .cal-weather { grid-column: auto; }
|
||
.cal-seasonyear .cal-weather .cal-dd-panel { left: auto; right: 0; min-width: 420px; }
|
||
}
|
||
.cal-filter-label {
|
||
font-size: 11px; text-transform: uppercase; letter-spacing: .04em;
|
||
color: var(--muted); font-weight: 700; margin-right: 2px;
|
||
}
|
||
|
||
.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 flow into as many columns as fit (one on phones, several on monitors),
|
||
so a multi-year span reads as a year-at-a-glance wall instead of one long
|
||
thin strip. Cells stay square via aspect-ratio. */
|
||
.calendar {
|
||
display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||
gap: 20px 24px; margin-bottom: 22px; align-items: start;
|
||
}
|
||
.cal-month { width: 100%; }
|
||
.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-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; }
|
||
|
||
/* Metric cards: one column on phones, side by side on monitors so the page
|
||
doesn't run as a single long strip of full-width ladders. minmax(0, 1fr)
|
||
(not bare 1fr, whose min is the content) so one wide ladder row can't inflate
|
||
the shared column track past the viewport and push the whole page off-center. */
|
||
#day-body { display: grid; grid-template-columns: minmax(0, 1fr); gap: 16px; align-items: start; }
|
||
@media (min-width: 1000px) { #day-body { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
|
||
@media (min-width: 3400px) { #day-body { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
|
||
|
||
.ladder-card {
|
||
background: var(--surface); border: 1px solid var(--border);
|
||
border-radius: 13px; padding: 16px 16px 12px;
|
||
}
|
||
.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; }
|
||
/* Value ranges may wrap (long unit strings like "8.1 g/m³–12.9 g/m³" on narrow
|
||
phones) — nowrap here would widen the row past the card and scroll the page. */
|
||
.ladder-val { justify-self: end; text-align: right; font-variant-numeric: tabular-nums; }
|
||
/* 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; }
|
||
/* Markers are unit-less ("◀ 11.5") so nowrap can't push them past the card —
|
||
the widest (precip, ◀ 0.25") bleeds a few px into the row padding at most. */
|
||
.ladder-mark { color: var(--accent); font-weight: 800; white-space: nowrap; font-size: 12.5px; }
|
||
|
||
|
||
/* 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 .rd-today column is the
|
||
graded target day (the orange chart marker), flagged with an accent left edge;
|
||
forecast columns sit to the right of today. */
|
||
.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. */
|
||
.error { color: #e06a6a; font-size: 14px; }
|
||
.spinner { color: var(--muted); font-size: 14px; }
|
||
/* --- phones / narrow screens --- */
|
||
|
||
/* --- 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; 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). */
|
||
|
||
/* 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; background: var(--bg); }
|
||
.mp-search button { padding: 11px 16px; min-height: 44px; }
|
||
.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, .mp-sug li.active { 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);
|
||
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .25), 0 6px 18px rgba(0, 0, 0, .25);
|
||
}
|
||
.mp-map .leaflet-container { background: var(--surface-2); }
|
||
/* Base map only (roads/land/water): deepen the roads and push saturation. The
|
||
labels layer (.mp-labels) is left unfiltered so city names keep full contrast. */
|
||
.mp-map .mp-base { filter: saturate(1.3) contrast(1.12) brightness(0.92); }
|
||
/* Branded accent teardrop pin (an L.divIcon wrapping the Find-button glyph),
|
||
replacing Leaflet's default blue raster marker. */
|
||
.mp-pin { background: none; border: none; filter: drop-shadow(0 3px 4px rgba(0, 0, 0, .45)); }
|
||
.mp-pin svg { width: 32px; height: 32px; display: block; }
|
||
.mp-pin path { fill: var(--accent); stroke: rgba(0, 0, 0, .35); stroke-width: 1.25; }
|
||
.mp-pin circle { fill: #fff; stroke: none; }
|
||
/* Theme the Leaflet zoom buttons + attribution to sit in the app's surfaces
|
||
instead of Leaflet's stock white chrome. */
|
||
.mp-map .leaflet-bar { border: none; box-shadow: 0 2px 8px rgba(0, 0, 0, .3); }
|
||
.mp-map .leaflet-bar a {
|
||
background: var(--surface); color: var(--text); border-bottom-color: var(--border);
|
||
}
|
||
.mp-map .leaflet-bar a:hover { background: var(--surface-2); color: var(--accent); }
|
||
.mp-map .leaflet-control-attribution {
|
||
background: color-mix(in srgb, var(--surface) 80%, transparent);
|
||
color: var(--muted); backdrop-filter: blur(2px); border-top-left-radius: 6px;
|
||
}
|
||
.mp-map .leaflet-control-attribution a { color: var(--accent); }
|
||
.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; min-height: 44px; 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; }
|
||
|
||
/* Category totals above the grid: a compact distribution strip. Each category is a
|
||
column captioned with its name over its value (share by default, raw count when
|
||
"Show count" is ticked, in that category's own status color) above a thin status-
|
||
colored line rising from a shared baseline (height ∝ share). Every category draws
|
||
a line — including the neutral "Normal" center of the temperature scale. */
|
||
.cal-totals { max-width: 960px; margin: 0 auto 18px; }
|
||
/* Header row: the "N days shown — by X" title, with the share/count toggle at the
|
||
far end. Wraps to its own line on narrow screens rather than crushing the title. */
|
||
.ct-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px 16px; flex-wrap: wrap; }
|
||
.ct-head .section-title { margin-bottom: 4px; }
|
||
.ct-count-toggle {
|
||
display: inline-flex; align-items: center; gap: 6px; flex-shrink: 0;
|
||
font-size: 12.5px; color: var(--muted); cursor: pointer; user-select: none;
|
||
margin-bottom: 4px;
|
||
}
|
||
.ct-count-toggle input { accent-color: var(--accent); width: 15px; height: 15px; margin: 0; cursor: pointer; }
|
||
.ct-strip {
|
||
display: flex; align-items: flex-end; gap: 6px;
|
||
height: calc(var(--line-max, 30px) + 44px); margin: 8px 0 6px;
|
||
}
|
||
.ct-col { position: relative; flex: 1 1 0; min-width: 0; height: 100%; }
|
||
/* Caption pins to the top of the column: the category name (two reserved lines so
|
||
values line up whether the name wraps or not) over the value figure. */
|
||
.ct-cap {
|
||
position: absolute; top: 0; left: 0; right: 0;
|
||
display: flex; flex-direction: column; align-items: center; gap: 3px;
|
||
}
|
||
.ct-label {
|
||
min-height: 2.2em; font-size: 10px; line-height: 1.1; font-weight: 600;
|
||
color: var(--muted); text-align: center; overflow: hidden;
|
||
}
|
||
/* "Moderate" is a wide single word (can't wrap) sandwiched between the two-line
|
||
"Light–Mod"/"Mod–Heavy" labels, so on narrow screens its centered box spills over
|
||
their first line. Lift it into the empty gap above to clear the collision. */
|
||
.ct-wet-5 .ct-label { transform: translateY(-0.85em); }
|
||
.ct-num {
|
||
font-size: 12px; 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);
|
||
}
|
||
|
||
/* 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; }
|
||
|
||
|
||
/* ===== Compare view ===== */
|
||
.cmp-locs { flex: 1 1 340px; }
|
||
.cmp-loc-list { list-style: none; margin: 8px 0 12px; padding: 0; display: flex; flex-wrap: wrap; gap: 8px; }
|
||
.cmp-loc-list:empty { margin: 0; }
|
||
.cmp-chip {
|
||
display: inline-flex; align-items: center; gap: 6px;
|
||
background: var(--surface-2); border: 1px solid var(--border);
|
||
border-radius: 999px; padding: 5px 5px 5px 14px; font-size: 14px;
|
||
}
|
||
.cmp-chip.loading { color: var(--muted); }
|
||
.cmp-chip.pending { color: var(--muted); border-style: dashed; }
|
||
.cmp-chip.error { border-color: var(--very-hot); color: var(--very-hot); }
|
||
|
||
/* Add-location + Refresh sit together; Refresh only shows while changes are pending. */
|
||
.cmp-loc-actions { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
|
||
.cmp-refresh-btn {
|
||
padding: 11px 18px; border-radius: 10px; cursor: pointer;
|
||
font-weight: 700; font-size: 15px; min-height: 44px;
|
||
background: var(--accent); color: #1a1206; border: 1px solid var(--accent);
|
||
}
|
||
.cmp-refresh-btn[hidden] { display: none; }
|
||
.cmp-refresh-btn:hover { filter: brightness(1.05); }
|
||
.cmp-chip-name { font-weight: 600; }
|
||
.cmp-chip-x {
|
||
border: none; background: transparent; color: var(--muted); cursor: pointer;
|
||
font-size: 20px; line-height: 1; width: 30px; height: 30px; border-radius: 999px;
|
||
}
|
||
.cmp-chip-x:hover { color: var(--text); background: var(--border); }
|
||
|
||
/* Parameter panel: comfort target, band, judged temperature, date range. */
|
||
.cmp-params {
|
||
display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
|
||
gap: 18px 24px; margin: 0 0 22px; padding: 16px;
|
||
border: 1px solid var(--border); border-radius: 12px; background: var(--surface);
|
||
}
|
||
.cmp-params[hidden] { display: none; }
|
||
.cmp-param > label { display: block; font-size: 13px; color: var(--muted); margin: 0 0 8px; }
|
||
.cmp-param > label b { color: var(--text); font-size: 15px; }
|
||
.cmp-param input[type="range"] {
|
||
width: 100%; height: 44px; margin: 0; accent-color: var(--accent); background: transparent;
|
||
}
|
||
.cmp-param .hint { display: block; margin: 4px 0 0; font-size: 12px; color: var(--muted); max-width: none; }
|
||
.cmp-basis-block .metric-toggle { margin-top: 2px; }
|
||
.cmp-basis-block .metric-toggle button { flex: 1 0 auto; padding: 10px 14px; }
|
||
.cmp-range { align-content: start; }
|
||
|
||
/* Ranked result cards. */
|
||
.cmp-results { display: grid; gap: 14px; }
|
||
.cmp-card { border: 1px solid var(--border); border-radius: 12px; background: var(--surface); padding: 16px 18px; }
|
||
.cmp-card:first-child { border-color: var(--accent); }
|
||
.cmp-card-top { display: flex; align-items: center; gap: 12px; }
|
||
.cmp-rank { font-size: 15px; font-weight: 700; color: var(--muted); flex: 0 0 auto; }
|
||
.cmp-card:first-child .cmp-rank { color: var(--accent); }
|
||
.cmp-card-name { flex: 1 1 auto; min-width: 0; }
|
||
.cmp-name { display: block; font-size: 16px; font-weight: 700; overflow: hidden; text-overflow: ellipsis; }
|
||
.cmp-daycount { font-size: 12px; color: var(--muted); }
|
||
.cmp-big { flex: 0 0 auto; text-align: right; }
|
||
.cmp-big b { display: block; font-size: 22px; color: var(--normal); line-height: 1.1; }
|
||
.cmp-big span { font-size: 10.5px; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; }
|
||
|
||
.cmp-bar { display: flex; height: 14px; border-radius: 7px; overflow: hidden; margin: 12px 0 10px; background: var(--surface-2); }
|
||
.cmp-seg { display: block; height: 100%; }
|
||
/* Scoped to the bar segments — .cmp-comfort is also the comfort-slider param's
|
||
class, which must not inherit the green fill. */
|
||
.cmp-seg.cmp-below { background: var(--cold); }
|
||
.cmp-seg.cmp-comfort { background: var(--normal); }
|
||
.cmp-seg.cmp-above { background: var(--hot); }
|
||
|
||
.cmp-stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
|
||
.cmp-stat { text-align: center; padding: 8px 4px; border-radius: 8px; background: var(--surface-2); }
|
||
.cmp-k { display: block; font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: .03em; }
|
||
.cmp-v { display: block; font-size: 18px; font-weight: 700; }
|
||
.cmp-d { display: block; font-size: 11.5px; color: var(--muted); }
|
||
.cmp-s-below .cmp-v { color: var(--cold); }
|
||
.cmp-s-comfort .cmp-v { color: var(--normal); }
|
||
.cmp-s-above .cmp-v { color: var(--hot); }
|
||
.cmp-mad { margin: 12px 0 0; font-size: 13px; color: var(--muted); }
|
||
.cmp-mad b { color: var(--text); }
|
||
|
||
/* --- phones / narrow screens — every mobile override lives in this one
|
||
block (they used to be scattered across four) --- */
|
||
@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. Slack moved
|
||
from the pct/marker tracks to the value column keeps the longest range
|
||
("17.3–19.9 g/m³", ~88px) on one line down to ~375px screens; narrower than
|
||
that it wraps at the unit. The label track can't give ("Above Normal" ≈ 89px). */
|
||
.day-nav { width: 100%; }
|
||
.day-date { flex: 1; }
|
||
.ladder-row { grid-template-columns: 13px 90px 46px 1fr 42px; column-gap: 8px; font-size: 12.5px; }
|
||
|
||
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. The unit toggle
|
||
stays on the title row (pushed to the top-right); the tabs wrap below it. */
|
||
.view-nav { margin-left: 0; width: 100%; }
|
||
.unit-toggle + .view-nav { margin-left: 0; }
|
||
.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. */
|
||
.panel { padding: 16px; }
|
||
|
||
/* Give buttons a comfortable touch target (~44px). */
|
||
.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 tuned for phone width. */
|
||
/* 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; }
|
||
|
||
.mp-overlay { padding: 0; }
|
||
/* Full-screen sheet. Size to the *dynamic* viewport (dvh) — plain 100vh on
|
||
iOS Safari is the large-viewport height (toolbars retracted), so with the
|
||
chrome showing the footer's "Use this location" button falls behind it and
|
||
gets cut off. dvh tracks the currently-visible height, and the flex map
|
||
gives up the space so the footer stays on-screen. vh kept as a fallback. */
|
||
.mp-modal {
|
||
max-width: none; border-radius: 0; border: none;
|
||
max-height: 100vh; height: 100vh;
|
||
max-height: 100dvh; height: 100dvh;
|
||
}
|
||
/* Clear the home-indicator inset once the bottom toolbar retracts. */
|
||
.mp-foot { padding-bottom: calc(16px + env(safe-area-inset-bottom)); }
|
||
.find-bar { width: 100%; }
|
||
.guide-metrics { grid-template-columns: 1fr; gap: 2px 0; }
|
||
.guide-metrics dt { margin-top: 10px; }
|
||
|
||
.cmp-params { grid-template-columns: 1fr; padding: 14px; }
|
||
.cmp-basis-block .metric-toggle button { flex: 1 0 22%; padding: 10px 4px; }
|
||
.cmp-big b { font-size: 20px; }
|
||
}
|