Live at: MebaneWeather.com · File:
mw-weebly-embed.html
The Model Forecast Dashboard is a self-contained hourly forecast widget built for MebaneWeather.com. It pulls live data from four weather model families, blends them into a consensus forecast, and presents a midnight-to-midnight hourly picture of the day — precipitation probability, rainfall, thunderstorm risk, temperature, dewpoint, CAPE, Lifted Index, and estimated radar reflectivity.
The entire thing is a single HTML file with no build step, no framework, no external runtime dependencies. Paste it into a Weebly Embed Code block and it works.
The purple-to-blue gradient header shows the widget title, location (Mebane / Burlington NC), all-times-in-Eastern disclaimer, NWS model run age, and a pulsing green LIVE dot with the last-updated timestamp. A STALE badge appears automatically if data is more than 90 minutes old.
A green / amber / red bar directly below the header. Pulls from the NWS Alerts API for zone NCZ023 in real time. Green = no active alerts; amber = watch/advisory; red = warning. Includes a direct link to weather.gov to verify.
Shows today's Storm Prediction Center convective outlook risk level for Mebane's exact coordinates using GeoJSON point-in-polygon geometry — not HTML text parsing, which was previously reading Enhanced Risk text for Texas and applying it to North Carolina. Risk levels: NONE → MRGL → SLGT → ENH → MDT → HIGH, each with its official colour coding.
Seven scrollable tabs (Today through six days out). Switching tabs re-runs the full render pipeline for that day's data. Chevrons appear when tabs overflow the screen width.
A purple-bordered summary card showing the day's single most important numbers at a glance:
| Stat | What it shows |
|---|---|
| TEMP | Day's high (tap → jumps to warmest hour) / low (tap → jumps to coolest hour) |
| DEW PT | Day's highest dewpoint (muggiest hour) / lowest (driest hour) |
| WIND | Peak wind or gust speed for the day |
| PRCP% | Highest hourly precipitation probability |
| RAIN | Maximum hourly QPF (rainfall in inches) |
| TSTM | Peak thunderstorm probability |
Small SVG arrow and finger icons to the left of each value indicate it is tappable — no text labels needed. Tapping jumps the timeline slider and detail panel directly to that hour, and flashes the target hour card purple so you can see exactly which hour it landed on.
A full-width range slider spanning midnight to midnight (00:00 → 23:59 Eastern). Dragging it updates the hour cards, detail panels, and chart simultaneously. Quick-jump buttons beneath it (12am · 6am · 12pm · 6pm · NOW) let you snap to common reference points. The NOW button always returns to today's tab regardless of which day tab is active, then scrolls to the current hour.
The orange ring, card highlight, and red dashed chart line mark the current moment. Past hours are dimmed at 50% opacity — you can't un-rain the morning.
A horizontally scrollable row of 24 hourly cards, midnight to midnight in US Eastern time. Each card shows:
- Local time label
- Weather icon (derived from sky cover %, thunder probability, and WMO weather code)
- Temperature in °F
- A colour-coded precipitation probability bar
- Percentage and threat badge (CLEAR / LOW / SLIGHT / SHOWER / RAIN / TSTM)
The currently selected card is highlighted purple. Thunderstorm cards get a red border. Chevron buttons appear at the edges when content overflows. Past cards are dimmed.
The left detail panel for the selected hour: temperature, dewpoint, relative humidity, sky cover, wind speed, gusts, and a plain-English weather description.
Shows individual model precipitation probabilities (NWS, GFS, ECMWF, HRRR, ICON) as labelled progress bars, plus the weighted Consensus value. QPF (rainfall amount) is shown alongside each. This panel makes model agreement and disagreement immediately visible — when all five bars line up, confidence is high; when they scatter, treat the forecast with more caution.
Shows CAPE (Convective Available Potential Energy) from each model, the estimated radar reflectivity level in dBZ, the GFS Lifted Index, and a plain-English threat classification (CLEAR / LOW / SLIGHT / SHOWER / RAIN / TSTM). High CAPE + negative LI + elevated thunder% = the atmosphere has fuel and a trigger.
A Chart.js line chart with four tabs: Precip %, Rainfall, CAPE, and Temp/Dew. All five model lines are shown simultaneously with the Consensus in black and Thunder % as a dashed overlay. The orange NOW line is drawn directly on the canvas via Chart.js's afterDraw plugin hook — no external annotation library needed. The chart updates instantly when you switch tabs or drag the slider.
A horizontally scrollable row of hourly cells colour-coded by estimated radar reflectivity (dBZ), derived from model QPF, CAPE, and thunder probability. The current hour cell has an orange ring and a "NOW" pip and is automatically scrolled into view on load. Past cells are dimmed. Each cell shows the dBZ range and a numeric CAPE value for storm-energy context.
A collapsible accordion at the very bottom explaining every element in plain language — written in the spirit of Richard Feynman explaining things to a curious, intelligent person who doesn't need to be talked down to. Sections: What you're seeing · The five weather computers · Storm energy indicators · Radar & threat colours · Severe weather risk (SPC). Single-column on narrow phones, expanding to 2 / 3 / 4 columns on wider screens.
A collapsible 🔬 Diag panel in the footer with colour-coded log lines showing boot state, fetch results, render steps, and any errors. Visible only when opened; designed to make on-device debugging possible without opening browser dev tools.
| Source | What it provides | Weight in consensus |
|---|---|---|
NWS RAH Gridpoints (api.weather.gov/gridpoints/RAH/49,69) |
Precipitation probability, QPF, thunderstorm %, sky cover, temperature, dewpoint, wind, gusts, RH | 40% |
| Open-Meteo (GFS) | Precip probability, precipitation, CAPE, Lifted Index, weather code | 20% |
| Open-Meteo (ECMWF IFS) | Precip probability, precipitation, CAPE | 25% |
| Open-Meteo (HRRR) | Precip probability, precipitation, CAPE | 15% |
| Open-Meteo (ICON) | Precip probability, precipitation, CAPE | Reference only — shown for spread awareness, not in consensus math |
NWS Alerts API (api.weather.gov/alerts/active?zone=NCZ023) |
Active watches, advisories, warnings | N/A |
SPC GeoJSON (spc.noaa.gov/products/outlook/day1otlk_cat.lyr.geojson) |
Convective outlook risk polygon | N/A |
The Open-Meteo models are fetched in a single batched request to minimise latency and API load.
Consensus formula:
Consensus PoP = (NWS × 0.40) + (ECMWF × 0.25) + (GFS × 0.20) + (HRRR × 0.15)
NWS carries the most weight because it reflects a Raleigh meteorologist's judgement applied to local terrain — not just raw model output.
Everything is displayed in US Eastern time, DST-aware. Zero Zulu/UTC labels appear anywhere in the UI. The UTC offset is computed once at boot using the Intl API (America/New_York) and stored as ET_OFF (e.g. -240 minutes for EDT). All UTC-to-local conversions go through this offset.
The hour cards and chart are ordered local midnight → local midnight, not UTC midnight → UTC midnight. This required a localOrderedUTCHours() remapping function that reorders the records array (which is indexed by UTC hour 0–23) into the correct local display order. The tap-to-jump feature uses this same remapping to find the correct slot for each peak value — an easy bug to introduce if you index by UTC position directly.
The widget is a single HTML file, roughly 2,100 lines, organised in these sections:
<style> CSS — all scoped under #mwdb to avoid leaking into the host page
<div id="mwdb"> HTML skeleton — header, alert bars, tabs, strip placeholder, timeline, detail, chart, radar, legend
<script> All logic — no imports, no modules, just vanilla JS
JavaScript sections (in order):
Constants & state (S object)
ET offset computation
omV() UTC hour → Open-Meteo array index lookup
compile() Assembles one day's 24 records from all model sources
Fetch layer safeFetch(), fetchAll() — all network calls with timeouts + cache-busting
Render layer renderDay() → renderTL + renderDetail + renderRadar + renderChart + renderSummary
UI helpers selectH(), jumpCards(), jumpNow(), jumpToStat(), scrollRow(), updateScrollHints()
Boot IntersectionObserver — widget only activates when scrolled into view
safeFetch() — every network call goes through this wrapper. It never throws; it resolves null on any failure (timeout, CORS error, HTTP error, parse error). The render layer handles null gracefully. This is what makes the widget failure-resistant when one API is down.
unpackOM() — Open-Meteo returns all five model responses in one batched JSON object. This function extracts the hourly arrays for a named model (e.g. 'gfs_global') into a keyed map of "YYYY-MM-DDThh:00" → value for fast lookup during compile.
compile(dateStr) — builds an array of 24 record objects for one day, one per UTC hour, by joining NWS grid data, NWS hourly forecast data, and Open-Meteo model data. Each record contains ~25 fields used downstream by all render functions.
S.days[] upsert — when new data arrives (on refresh or day-tab switch), records are upserted into S.days keyed by date string. This prevents duplicate data accumulation across hourly refreshes.
localOrderedUTCHours() — returns an array of UTC hours reordered so index 0 = local midnight. e.g. at EDT (UTC-4): [4,5,6,...,23,0,1,2,3]. Used by slotToRec() to map display slot → correct record, and by peakSlot() / minSlot() in renderSummary() to find peak-value hours in local display order.
IntersectionObserver boot — the widget is completely inert until it scrolls into the viewport. This prevents it from blocking page load or interfering with other Weebly widgets. A _booted guard ensures init() runs exactly once.
Chart.js afterDraw hook — the orange NOW line on the chart is drawn directly on the canvas in the afterDraw callback rather than using the chartjs-plugin-annotation library. This removes an external dependency and eliminates the version-mismatch failures that killed earlier versions of the chart.
SPC point-in-polygon — the SPC Day 1 outlook is a GeoJSON FeatureCollection of risk polygons. The widget uses a ray-casting algorithm to test whether Mebane's coordinates (36.1°N, 79.3°W) fall inside any polygon. The previous implementation used HTML text search on the SPC outlook page, which incorrectly matched "Enhanced Risk" text referring to Texas.
| Concern | How it's handled |
|---|---|
| API timeout | safeFetch() with 8–15s per-endpoint AbortController timeout |
| Stale data | cache:'no-store' + _t= timestamp query param on all Open-Meteo URLs |
| Old service worker | Detected and unregistered on boot to clear previous caching layers |
file:// protocol |
Detected at boot; shows a clear message instead of silent failure |
| Render errors | renderDay() wraps each sub-function in individual try/catch so a chart failure can't prevent the summary strip from rendering |
| Weebly CSS interference | CSS class toggling (classList.remove('mw2-hidden')) rather than inline style= which Weebly strips |
| Non-blocking load | IntersectionObserver delays all work until widget is in viewport |
| Hourly refresh | NWS model-window-aware scheduler triggers refresh when new model data is expected, even if the page hasn't been reloaded |
- Find your NWS gridpoint at
https://api.weather.gov/points/{lat},{lon} - Replace the three NWS URLs near the top of the
<script>block (gridpoints, hourly, alerts zone) - Update the
LAT/LONconstants used for SPC point-in-polygon - Update the location display name in the header HTML
- Paste into your Weebly Embed Code block and publish
Part of the MebaneWeather.com open-source weather monitoring suite — built for Mebane, NC, adaptable anywhere in the US.
