: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
) --- */ .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(-1.4em); } .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-top { display: flex; align-items: center; gap: 12px; } /* Rank badge: a filled disc — accent for the winner, muted for the rest. */ .cmp-rank { flex: 0 0 auto; width: 26px; height: 26px; border-radius: 50%; display: grid; place-items: center; font-size: 13px; font-weight: 800; background: var(--surface-2); color: var(--muted); } .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; } /* Winner: accent frame + badge, and a "Best match" flag on the subline. */ .cmp-win { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); } .cmp-win .cmp-rank { background: var(--accent); color: #1a1206; } .cmp-best { color: var(--accent); font-weight: 700; } /* Diverging comfort bar: a fixed center marks the comfort zone; colder days grow left of it, warmer days grow right. Both halves share one 0–100% scale, so a cold-skewed place and a hot-skewed one mirror each other and compare at a glance. */ .cmp-dv { display: flex; align-items: stretch; height: 16px; margin: 14px 0 8px; } .cmp-dv-side { flex: 1 1 0; position: relative; background: var(--surface-2); overflow: hidden; } .cmp-dv-left { border-radius: 8px 0 0 8px; } .cmp-dv-right { border-radius: 0 8px 8px 0; } .cmp-dv-fill { position: absolute; top: 0; bottom: 0; } .cmp-dv-left .cmp-dv-fill { right: 0; } /* colder grows leftward from center */ .cmp-dv-right .cmp-dv-fill { left: 0; } /* warmer grows rightward from center */ .cmp-dv-fill.cmp-below { background: var(--cold); } .cmp-dv-fill.cmp-above { background: var(--hot); } .cmp-dv-mid { flex: 0 0 4px; background: var(--normal); } /* the comfort-zone marker */ .cmp-dv-cap { display: flex; justify-content: space-between; gap: 10px; font-size: 12px; } .cmp-cap-below { color: var(--cold); } .cmp-cap-above { color: var(--hot); text-align: right; } .cmp-mad { margin: 10px 0 0; font-size: 13px; color: var(--muted); } .cmp-mad b { color: var(--text); } /* Baseline strip: every place's typical basis temperature on one shared °F axis, with the comfort range shaded, so the places' baselines line up side by side. */ .cmp-baseline { margin: 0 0 18px; padding: 14px 16px; border: 1px solid var(--border); border-radius: 12px; background: var(--surface); } .cmp-baseline[hidden] { display: none; } .cmp-bl-head { font-size: 14px; font-weight: 700; } .cmp-bl-head .hint { display: block; font-weight: 400; font-size: 11.5px; color: var(--muted); margin: 2px 0 12px; max-width: none; } .cmp-bl-rows { display: grid; gap: 10px; } .cmp-bl-row { display: grid; grid-template-columns: 120px 1fr; align-items: center; gap: 12px; } .cmp-bl-name { font-size: 13px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .cmp-bl-track { position: relative; height: 26px; } .cmp-bl-band { position: absolute; top: 0; bottom: 0; border-radius: 3px; background: color-mix(in srgb, var(--normal) 20%, transparent); outline: 1px solid color-mix(in srgb, var(--normal) 50%, transparent); outline-offset: -1px; } .cmp-bl-spread { position: absolute; top: 17px; height: 4px; transform: translateY(-50%); border-radius: 2px; opacity: .6; } .cmp-bl-dot { position: absolute; top: 17px; width: 12px; height: 12px; border-radius: 50%; transform: translate(-50%, -50%); border: 2px solid var(--surface); } .cmp-bl-val { position: absolute; top: 1px; transform: translateX(-50%); font-size: 10.5px; font-weight: 700; color: var(--text); white-space: nowrap; } .cmp-bl-spread.cmp-below, .cmp-bl-dot.cmp-below { background: var(--cold); } .cmp-bl-spread.cmp-comfort, .cmp-bl-dot.cmp-comfort { background: var(--normal); } .cmp-bl-spread.cmp-above, .cmp-bl-dot.cmp-above { background: var(--hot); } .cmp-bl-axis { display: flex; justify-content: space-between; margin: 8px 0 0 132px; font-size: 11px; color: var(--muted); } /* Loading affordances: a spinning ring (chips + header) and shimmering skeleton cards, so a still-loading place is obvious and the results don't pop in cold. */ @keyframes cmp-spin { to { transform: rotate(360deg); } } @keyframes cmp-shimmer { 100% { background-position: -200% 0; } } .cmp-spinner { display: inline-block; width: 14px; height: 14px; border-radius: 50%; box-sizing: border-box; border: 2px solid var(--border); border-top-color: var(--accent); animation: cmp-spin .7s linear infinite; vertical-align: -2px; } .cmp-spinner-chip { width: 12px; height: 12px; } .cmp-chip-dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: var(--muted); opacity: .55; } .cmp-loading { display: flex; align-items: center; gap: 8px; margin: 0 0 14px; color: var(--muted); font-size: 14px; } .cmp-loading-inline { display: inline-flex; align-items: center; gap: 6px; } .cmp-loading-inline .cmp-spinner { width: 12px; height: 12px; } .cmp-skel-top { display: flex; align-items: center; gap: 12px; } .cmp-skel-badge, .cmp-skel-name, .cmp-skel-big, .cmp-skel-bar { display: block; border-radius: 6px; background: linear-gradient(90deg, var(--surface-2) 25%, var(--border) 37%, var(--surface-2) 63%); background-size: 200% 100%; animation: cmp-shimmer 1.3s ease-in-out infinite; } .cmp-skel-badge { width: 26px; height: 26px; border-radius: 50%; flex: 0 0 auto; } .cmp-skel-name { flex: 1 1 auto; height: 16px; } .cmp-skel-big { width: 56px; height: 22px; flex: 0 0 auto; } .cmp-skel-bar { height: 16px; margin-top: 16px; } @media (prefers-reduced-motion: reduce) { .cmp-spinner { animation-duration: 1.4s; } .cmp-skel-badge, .cmp-skel-name, .cmp-skel-big, .cmp-skel-bar { animation: none; } } /* Dual-handle comfort slicer: two range inputs overlaid on a shared track, with a fill marking the selected span. Only the thumbs take pointer events, so both handles stay grabbable even where the inputs overlap. */ .dual-range { position: relative; height: 44px; } .dual-range .dual-track { position: absolute; left: 0; right: 0; top: 50%; transform: translateY(-50%); height: 6px; border-radius: 3px; background: var(--surface-2); } .dual-range .dual-fill { position: absolute; top: 0; bottom: 0; border-radius: 3px; background: var(--accent); } .dual-range input[type="range"] { position: absolute; left: 0; top: 0; width: 100%; height: 100%; margin: 0; -webkit-appearance: none; appearance: none; background: transparent; pointer-events: none; } .dual-range input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; pointer-events: auto; width: 22px; height: 22px; border-radius: 50%; background: var(--accent); border: 2px solid var(--surface); box-shadow: 0 1px 3px rgba(0, 0, 0, .35); cursor: pointer; } .dual-range input[type="range"]::-moz-range-thumb { pointer-events: auto; width: 22px; height: 22px; border-radius: 50%; background: var(--accent); border: 2px solid var(--surface); box-shadow: 0 1px 3px rgba(0, 0, 0, .35); cursor: pointer; } .dual-range input[type="range"]::-webkit-slider-runnable-track { background: transparent; border: none; } .dual-range input[type="range"]::-moz-range-track { background: transparent; border: none; } /* Climate distribution below the ranked cards: a metric toggle + share/count toggle, then one calendar-style distribution strip (.ct-strip, shared) per location. */ .cmp-dist { margin: 24px 0 8px; } .cmp-dist[hidden] { display: none; } .cmp-dist .ct-head { align-items: center; } .cmp-dist-controls { display: flex; align-items: center; flex-wrap: wrap; gap: 8px 12px; } .cmp-dist-controls .cal-filter-label { margin: 0; } .cmp-dist .metric-toggle { margin: 0; } .cmp-dist-body { display: grid; gap: 12px; margin-top: 14px; } .cmp-dist-row { border: 1px solid var(--border); border-radius: 12px; background: var(--surface); padding: 12px 16px 6px; } .cmp-dist-name { display: flex; align-items: baseline; gap: 8px; font-size: 14px; font-weight: 700; } .cmp-dist-days { font-size: 12px; font-weight: 400; color: var(--muted); } /* --- 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; } /* Baseline strip: tighten the name gutter and shrink text so the shared axis still fits a phone; keep the axis ticks aligned to the narrower track. */ .cmp-bl-row { grid-template-columns: 84px 1fr; gap: 8px; } .cmp-bl-name { font-size: 12px; } .cmp-bl-axis { margin-left: 92px; } .cmp-dv-cap { font-size: 11px; } }