Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 44 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,49 @@
# Changelog

All notable changes to SnowSignal are documented here. This project is in **public
beta**; versions follow [semantic versioning](https://semver.org) with pre-release tags.
All notable changes to SnowSignal are documented here. Versions follow
[semantic versioning](https://semver.org).

## [1.0.0] — 2026-07-04

The first **stable release**. SnowSignal remains what it has been through the betas —
a transparent, deterministic, fully static, client-only estimator with no tracking —
now with a smarter engine, a finished UI, and a full accessibility and compatibility pass.
Every result is still an estimate, never an official closure announcement.

### Engine
- **Refreeze detection** — a wet prior evening followed by a below-freezing commute now
registers as black-ice risk: it raises the closure estimate, feeds the delay profile
strongly (the textbook "2-hour delay to let crews treat roads"), and counts as a real
winter hazard in the plausibility gate.
- **Trend into school start** — a storm winding down before buses roll now *lowers* the
closure estimate (crews get a clearing window) and nudges toward a delay instead; one
ramping up into the commute raises it. Gated by storm presence so clear days are unaffected.
- **Snowfall intensity** — heavy hourly bursts (approaching 1"/hr) score higher than the
same total spread thinly, because plows can't keep up with bursts.
- **Sharper confidence** — confidence now drops when the precipitation type is genuinely
ambiguous near the freezing line, and rises when several independent strong signals
(snow totals, ice, an official alert, dangerous cold) agree. Tooltips explain what each
confidence label means.
- **Plain-language drivers** — each result surfaces the top 2–4 reasons behind the number,
including the biggest factor holding it *down*.
- **Window fix** — the 5 AM and 9 AM boundary hours were counted in two forecast windows
at once; buckets are now disjoint (overnight 6 PM–4:59 AM, commute 5–8:59 AM, daytime
9 AM–5 PM).

### UI
- Factor breakdown is now grouped under **"Raising the estimate" / "Lowering the
estimate"** headings, so direction never relies on color alone.
- Probability dials gained a subtle halo; the wordmark a restrained accent sweep.
- Settings modal: more breathing room between groups, clearly marked selected pills
(check mark + stronger ring), a separated **"can't be undone"** zone for destructive
actions, and a finished About tab with the app mark, tagline, and description.
- The hourly timeline is keyboard-focusable and scrollable.
- Added a static `manifest.webmanifest` + theme-color and social-sharing meta tags, a
`<noscript>` notice, and a `/` shortcut that focuses search.

### Fixed / cleaned
- Removed the orphaned `atm-rise` keyframe left behind by the beta.4 atmosphere work.
- Audited for stale code, debug output, and dead selectors (now guarded by a test).

## [1.0.0-beta.4] — 2026-06-06

Expand Down
35 changes: 21 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ deploys to GitHub Pages as-is. The only third-party calls are Open-Meteo (weathe
geocoding), the NWS alerts API (U.S. only), and BigDataCloud's free, keyless
reverse-geocode endpoint for friendly "My location" labels.

> **Status:** seasonal-polish beta (`v1.0.0-beta.4`) — not a stable release. See
> [Versioning](#versioning), [CHANGELOG.md](CHANGELOG.md), and [ROADMAP.md](ROADMAP.md).
> **Status:** stable release (`v1.0.0`). See [Versioning](#versioning),
> [CHANGELOG.md](CHANGELOG.md), and [ROADMAP.md](ROADMAP.md).
> The GitHub Pages URL is served from the `/snowsignal/` project subpath and all asset
> paths are relative so the app keeps working there.

Expand Down Expand Up @@ -61,10 +61,17 @@ than a black box.
- **Closure %** — weighted sum of all factors, scaled and capped at 99%. A small, isolated
`hasMeaningfulWinterHazard()` gate forces both the closure and delay to **0%** when the forecast
window holds no real winter hazard, so the score stays honest in benign weather.
- **Delay %** — a separate profile that favors morning-timed storms that clear; it's suppressed when a
full closure is already likely (a district would just close instead).
- **Confidence** — reflects how *clear-cut the inputs are* (alert agreement, precip certainty, whether
the result sits in a mushy middle), **not** a probability that the estimate is correct.
- **Timing-aware** — beyond raw totals, the engine reads the *shape* of the storm: a wet evening
refreezing into black ice by bus time, a storm winding down before school (crews get a clearing
window) vs. worsening into the commute, and heavy hourly bursts that outrun the plows.
- **Delay %** — a separate profile that favors morning-timed storms that clear and refreeze
mornings; it's suppressed when a full closure is already likely (a district would just close
instead).
- **Confidence** — reflects how *clear-cut the inputs are* (alert agreement, precip certainty,
rain-vs-snow ambiguity near freezing, whether several strong signals agree, whether the result
sits in a mushy middle), **not** a probability that the estimate is correct.
- **Drivers** — every result lists the top few plain-language reasons behind the number, including
the biggest factor holding it down.

The weights live at the top of `js/engine.js` and are documented inline so you can tune them.

Expand Down Expand Up @@ -151,14 +158,14 @@ Notes:

## Versioning

This is **`v1.0.0-beta.4`** — a **seasonal-polish beta** (the last planned visual-polish release for
a while), still not a stable release. `beta.1` was the first public beta of the rebuilt app;
`beta.2`–`beta.4` keep the same transparent prediction engine. `beta.4` adds the Seasonal palette
system with location-aware Auto and safe accent ranges, subtle time-of-day ambient intensity, refined
per-season atmosphere, Settings-tab motion polish, smoother/subtler hourly-forecast scrolling, and a
clearer seasonal empty state. Please keep treating predictions as estimates while the engine is tuned
against real outcomes. Full notes are in [CHANGELOG.md](CHANGELOG.md); the road to a stable `v1.0.0`
and beyond is in [ROADMAP.md](ROADMAP.md).
This is **`v1.0.0`** — the first **stable release**. The `beta.1`–`beta.4` line rebuilt the
app module by module and polished it release over release; `v1.0.0` finishes the job with a
smarter engine (refreeze/black-ice detection, trend into the commute, snowfall intensity,
sharper confidence, plain-language drivers), a finished UI, and a full accessibility and
compatibility pass. Stable still means *estimate*: SnowSignal can't know local policies or an
administrator's 5 AM judgment call, so always rely on official announcements. Full notes,
including all beta history, are in [CHANGELOG.md](CHANGELOG.md); ideas for `v1.1+` live in
[ROADMAP.md](ROADMAP.md).

## Attribution

Expand Down
16 changes: 10 additions & 6 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,20 @@ a wider tabbed Settings modal, no unnecessary animation replays, result date/fre
a collapsible Weather details panel, a device-local estimate counter, friendlier loading/error
states, and open-source (MIT) documentation. See [CHANGELOG.md](CHANGELOG.md).

## v1.0.0-beta.4 — current
The last planned visual-polish beta (not a stable release) on the same engine: a Seasonal palette
system (Auto/Winter/Spring/Summer/Fall/Custom) with location-aware Auto and a safe accent range,
## v1.0.0-beta.4
The last visual-polish beta on the same engine: a Seasonal palette system
(Auto/Winter/Spring/Summer/Fall/Custom) with location-aware Auto and a safe accent range,
subtle time-of-day ambient intensity under Auto, refined per-season atmosphere, Settings-tab motion
polish, smoother/subtler hourly-forecast scrolling with optional desktop arrows, and a clearer
seasonal empty state. See [CHANGELOG.md](CHANGELOG.md).

## v1.0.0 — stable
Promote to a stable release after bug fixes and real-world testing across a range
of locations, devices, and storms.
## v1.0.0 — current, stable
The first stable release: a smarter engine (refreeze/black-ice detection, trend into the
commute, snowfall intensity, sharper confidence, plain-language drivers), a finished UI
(grouped factor breakdown, polished settings and About, web-app manifest), and a full
accessibility, compatibility, and code-quality pass. Still a transparent estimate — never
official closure data — client-only, no tracking, MIT licensed.
See [CHANGELOG.md](CHANGELOG.md).

## v1.1.0
- Optional **local** outcome feedback ("was school actually closed?") stored only on-device
Expand Down
10 changes: 10 additions & 0 deletions css/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,16 @@ svg {
border: 0;
}

.noscript-note {
max-width: var(--maxw);
margin: 0 auto 12px;
padding: 12px 16px;
border-radius: var(--radius-md);
border: 1px solid var(--border);
background: var(--surface);
font-size: 0.9rem;
}

.skip-link {
position: absolute;
left: 8px;
Expand Down
115 changes: 107 additions & 8 deletions css/components.css
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,16 @@
50% { transform: translateY(-2px) rotate(4deg); }
}

/* Subtle accent sweep across the wordmark (only where text-clipping is safe). */
@supports ((background-clip: text) or (-webkit-background-clip: text)) {
.brand h1 {
background: linear-gradient(100deg, var(--text) 55%, var(--accent-strong));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
}

.subtitle {
font-size: 0.82rem;
color: var(--muted);
Expand Down Expand Up @@ -381,6 +391,8 @@ input[type="range"]::-moz-range-thumb {
stroke-dasharray: 326.7;
stroke-dashoffset: 326.7;
transition: stroke-dashoffset 0.8s cubic-bezier(0.22, 1, 0.36, 1), stroke 0.4s ease;
/* A faint halo makes the ring read as the primary object on both themes. */
filter: drop-shadow(0 0 4px var(--accent-ring));
}

#dial-delay .dial-fill {
Expand Down Expand Up @@ -485,6 +497,29 @@ input[type="range"]::-moz-range-thumb {
margin-left: auto;
}

/* Plain-language top drivers under the recommendation. */
.drivers {
list-style: none;
display: flex;
flex-direction: column;
gap: 4px;
font-size: 0.84rem;
color: var(--muted);
max-width: 60ch;
}

.drivers li {
position: relative;
padding-left: 16px;
}

.drivers li::before {
content: "→";
position: absolute;
left: 0;
color: var(--accent-strong);
}

/* Plausibility-gate explanation (estimate resolved to 0%). */
.gate-note {
font-size: 0.82rem;
Expand Down Expand Up @@ -670,6 +705,19 @@ input[type="range"]::-moz-range-thumb {
gap: 10px;
}

/* Group headings: direction is stated in text, never by color alone. */
.factor-heading {
font-size: 0.68rem;
text-transform: uppercase;
letter-spacing: 0.1em;
color: var(--muted);
margin-top: 6px;
}

.factor-heading:first-child {
margin-top: 0;
}

.factor {
display: grid;
grid-template-columns: 1fr;
Expand Down Expand Up @@ -1044,10 +1092,15 @@ input[type="range"]::-moz-range-thumb {

.settings-group {
border: none;
padding: 14px 0 0;
padding: 16px 0 0;
margin: 0;
}

.settings-group + .settings-group {
margin-top: 6px;
border-top: 1px solid color-mix(in srgb, var(--border) 55%, transparent);
}

.settings-group legend {
font-size: 0.74rem;
text-transform: uppercase;
Expand Down Expand Up @@ -1094,6 +1147,13 @@ input[type="range"]::-moz-range-thumb {
border-color: var(--accent);
background: color-mix(in srgb, var(--accent) 16%, var(--surface));
color: var(--accent-strong);
font-weight: 600;
box-shadow: 0 0 0 1px var(--accent), 0 2px 8px var(--accent-ring);
}

/* Selection is also marked with a leading check, not color alone. */
.pill:has(input:checked) span::before {
content: "✓ ";
}

.pill:has(input:focus-visible) {
Expand Down Expand Up @@ -1160,6 +1220,52 @@ input[type="range"]::-moz-range-thumb {
background: color-mix(in srgb, var(--bad) 14%, var(--surface));
}

/* Destructive actions live in their own clearly separated zone. */
.settings-danger {
margin-top: 18px;
padding-top: 12px;
border-top: 1px dashed color-mix(in srgb, var(--bad) 45%, transparent);
}

.settings-danger-label {
font-size: 0.72rem;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--bad);
margin-bottom: 4px;
}

/* About tab */
.about-hero {
display: flex;
align-items: center;
gap: 12px;
margin-top: 12px;
}

.about-icon {
width: 44px;
height: 44px;
flex-shrink: 0;
}

.about-hero .app-version {
font-size: 1.05rem;
}

.about-tagline {
font-size: 0.8rem;
color: var(--muted);
}

.about-blurb {
margin-top: 10px;
font-size: 0.85rem;
line-height: 1.5;
color: var(--muted);
max-width: 60ch;
}

.settings-about {
display: flex;
flex-direction: column;
Expand Down Expand Up @@ -1271,13 +1377,6 @@ input[type="range"]::-moz-range-thumb {
100% { transform: translate3d(var(--x, 0), 108vh, 0) rotate(360deg); }
}

@keyframes atm-rise {
0% { transform: translate3d(0, 112vh, 0); opacity: 0; }
12% { opacity: var(--o, 0.3); }
88% { opacity: var(--o, 0.3); }
100% { transform: translate3d(var(--x, 0), -8vh, 0); opacity: 0; }
}

/* Spring: a gentle side-to-side sway while drifting down. */
@keyframes atm-sway-fall {
0% { transform: translate3d(0, -6vh, 0) rotate(0deg); }
Expand Down
Loading
Loading