All checks were successful
secrets-guard / encrypted (pull_request) Successful in 5s
PR build (required check) / validate-observability (pull_request) Has been skipped
PR build (required check) / changes (pull_request) Successful in 6s
PR build (required check) / build-backend (pull_request) Has been skipped
shell-lint / shellcheck (pull_request) Successful in 6s
PR build (required check) / build-frontend (pull_request) Successful in 59s
PR build (required check) / gate (pull_request) Successful in 2s
185 lines
No EOL
10 KiB
Cheetah
185 lines
No EOL
10 KiB
Cheetah
{{/* Ported from templates/home.html.j2.
|
|
|
|
The Weekly view — the product itself — with the distribution surfaces built
|
|
around it. Everything structural is server-rendered so crawlers and no-JS
|
|
readers get a complete page; app.js hydrates the live numbers in place.
|
|
|
|
The interactive controls below (#find-btn, #date-input, #panel, #results, …)
|
|
are app.js's DOM contract, carried over verbatim from the old static
|
|
frontend/index.html. Do not rename these ids.
|
|
|
|
Section / BrandTag ("p") / MainClass ("") come from the route handler's
|
|
render data, not the template — the SEO pages already pass Section that way.
|
|
|
|
Extra data: .Unusual *contentapi.HomeUnusual; .Stale bool;
|
|
.Ranked []contentapi.HomeRanked (Lat/Lon are json.Number, printed raw);
|
|
.Cities []contentapi.HomeCity;
|
|
.JSONLDStr template.JS — _HOME_JSONLD + "url" appended, serialized like
|
|
Python's plain json.dumps: ", "/": " separators, ensure_ascii=True. */}}{{template "base_head_start" .}} <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
|
{{template "base_head_end" .}} <script type="application/ld+json">
|
|
{{.JSONLDStr}}
|
|
</script>
|
|
{{template "base_body_start" .}}{{/* --- Hero ------------------------------------------------------------
|
|
The grade card is the LCP element: its frame is server-rendered and its
|
|
slots are sized here, so hydrating the live numbers causes no layout
|
|
shift. Band colour is always paired with the band word — colour is never
|
|
the only signal. */}} <section id="hero" class="hero">
|
|
<div class="hero-copy">
|
|
<h1>How unusual is your weather?</h1>
|
|
<p class="hero-sub">Any day, anywhere on Earth, graded against 45 years of that place's own history.</p>
|
|
</div>
|
|
|
|
<div class="hero-card">{{/* --cat carries the band colour, the same convention .normal-card uses,
|
|
so the band token stays the single source of truth. */}}
|
|
<div class="grade-card" id="hero-grade" aria-live="polite"
|
|
{{if .Unusual}}style="--cat: var(--{{.Unusual.Cls}})"{{end}}>
|
|
<p class="grade-where" id="hero-where">
|
|
{{- if .Unusual -}}
|
|
Today in {{.Unusual.Display}}
|
|
{{- else -}}
|
|
Today
|
|
{{- end -}}
|
|
</p>
|
|
<p class="grade-band" id="hero-band">{{if .Unusual}}{{.Unusual.Grade}}{{else}}—{{end}}</p>
|
|
<p class="grade-detail" id="hero-detail">
|
|
{{- if .Unusual -}}
|
|
{{.Unusual.MetricLabel}} in the {{ordinal .Unusual.Percentile}} percentile for {{.Unusual.WindowLabel}}
|
|
{{- else -}}
|
|
Pick a place to see how unusual its weather is today.
|
|
{{- end -}}
|
|
</p>
|
|
{{if and .Unusual .Unusual.IsDefault}} <p class="grade-why muted">the most unusual weather we're tracking{{if .Stale}} · as of {{.Unusual.Date}}{{end}}</p>
|
|
{{end}} </div>
|
|
<div class="hero-actions">{{/* app.js hides the locate button outside a secure context, where the
|
|
browser refuses geolocation and it could only ever fail. */}}
|
|
<button type="button" id="hero-locate" class="btn-primary">Use my location</button>
|
|
<button type="button" id="hero-pick" class="btn-ghost">Pick on the map</button>
|
|
</div>
|
|
<p class="hero-locate-msg" id="hero-locate-msg" role="status" aria-live="polite" hidden></p>
|
|
<p class="hero-jump"><a href="#panel">See your week ↓</a></p>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="controls">
|
|
<div class="find-bar">
|
|
<button type="button" id="find-btn" class="find-btn"></button>
|
|
<span id="loc-label" class="loc-label" hidden></span>
|
|
</div>
|
|
<div class="date-row">
|
|
<label>Target date
|
|
<input id="date-input" type="date" />
|
|
</label>
|
|
<button type="button" id="today-btn" class="today-chip" title="Reset the target date to today">Today</button>
|
|
<span class="hint"><a href="{{.AssetBase}}/legend">What do the grades mean? →</a></span>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="panel" class="panel panel-full">
|
|
<div class="placeholder" id="placeholder">
|
|
<p>Find a location to begin.</p>
|
|
<p class="muted">Thermograph fetches decades of daily highs, lows, and precipitation
|
|
for your ~4 sq mi cell, builds a ±7-day seasonal distribution for each
|
|
day of the year, and grades recent weather by where it falls on that distribution.</p>
|
|
</div>
|
|
<div id="results" hidden></div>
|
|
</section>
|
|
|
|
{{/* Sits outside #panel on purpose: app.js rewrites results.innerHTML
|
|
wholesale on every grade, so anything inside it would be destroyed. */}} <p class="stance-line muted">
|
|
Free. No ads. No tracking. No account needed.
|
|
<a href="{{.Base}}/about">How we work →</a>
|
|
</p>
|
|
|
|
{{/* --- Unusual right now ----------------------------------------------
|
|
Scroll-snap row, no JS carousel. Both tails are represented whenever a
|
|
cold-tail city qualifies — two-sided honesty is product identity. */}}{{if .Ranked}} <section class="unusual-strip" aria-labelledby="records-title">
|
|
<h2 id="records-title">Unusual right now</h2>
|
|
<ul class="unusual-row">
|
|
{{range .Ranked}} <li class="unusual-card" style="--cat: var(--{{.Cls}})">
|
|
<a href="{{$.AssetBase}}/day#lat={{.Lat}}&lon={{.Lon}}"
|
|
data-event="home.records_click">
|
|
<span class="unusual-city">{{.Display}}</span>{{/* The reading itself, then what it is and what it should be —
|
|
a percentile means little without the value it describes. */}}
|
|
<span class="unusual-value">{{$.Fmt.Temp .Value}}</span>
|
|
<span class="unusual-band">{{.GradeLabel}}</span>
|
|
<span class="unusual-meta">
|
|
{{.MetricLabel}} · {{.PctLabel}} pct
|
|
{{- if .Normal}} · normal {{$.Fmt.Temp .Normal}}{{end}}
|
|
{{- if .DateLabel}} · {{.DateLabel}}{{end}} </span>
|
|
</a>
|
|
</li>
|
|
{{end}} </ul>
|
|
<p class="unusual-more"><a href="{{.Base}}/climate">See all records →</a></p>
|
|
</section>
|
|
{{end}}
|
|
{{/* --- How it works ---------------------------------------------------- */}} <section class="how-it-works" aria-labelledby="how-title">
|
|
<h2 id="how-title">How it works</h2>
|
|
<ol class="how-steps">
|
|
<li>We keep 45 years of climate history (ERA5 reanalysis) for your exact ~2-mile grid square.</li>
|
|
<li>Today gets compared to the same two weeks of the year, every year back to 1980.</li>
|
|
<li>The result is a percentile and a plain-language grade, from Near Record cold to Near Record hot.</li>
|
|
</ol>
|
|
<p class="muted">
|
|
<a href="{{.AssetBase}}/legend">Full methodology →</a>
|
|
· Data: ERA5 via Open-Meteo · NASA POWER · MET Norway
|
|
</p>
|
|
</section>
|
|
|
|
{{/* --- Explore --------------------------------------------------------- */}} <section class="explore" aria-labelledby="explore-title">
|
|
<h2 id="explore-title">Explore</h2>{{/* Each card takes a colour from the grade ramp rather than a decorative
|
|
palette, so the section reads as part of the product's own scale.
|
|
Calendar carries the ramp itself — it IS the heatmap. */}}
|
|
<div class="explore-cards">
|
|
<a class="explore-card" href="{{.AssetBase}}/calendar" data-event="home.nav_calendar"
|
|
style="--cat: var(--warm)">
|
|
<span class="explore-name">Calendar</span>
|
|
<span class="explore-ramp" aria-hidden="true">{{/* Jinja looped over the nine tier
|
|
names here; unrolled so the CSS custom-property references stay literal. */}}
|
|
<i style="background: var(--rec-cold)"></i>
|
|
<i style="background: var(--very-cold)"></i>
|
|
<i style="background: var(--cold)"></i>
|
|
<i style="background: var(--cool)"></i>
|
|
<i style="background: var(--normal)"></i>
|
|
<i style="background: var(--warm)"></i>
|
|
<i style="background: var(--hot)"></i>
|
|
<i style="background: var(--very-hot)"></i>
|
|
<i style="background: var(--rec-hot)"></i>
|
|
</span>
|
|
<span class="explore-desc">Two years of your days, each colored by how unusual it was.</span>
|
|
</a>
|
|
<a class="explore-card" href="{{.AssetBase}}/compare" data-event="home.nav_compare"
|
|
style="--cat: var(--normal)">
|
|
<span class="explore-name">Compare</span>
|
|
<span class="explore-desc">Rank any places by comfort for a trip or a move.</span>
|
|
</a>
|
|
<a class="explore-card" href="{{.AssetBase}}/day" data-event="home.nav_day"
|
|
style="--cat: var(--cool)">
|
|
<span class="explore-name">Day</span>
|
|
<span class="explore-desc">Any date since 1980, anywhere: what was normal, what happened.</span>
|
|
</a>
|
|
<a class="explore-card" href="{{.AssetBase}}/alerts" data-event="home.nav_alerts"
|
|
style="--cat: var(--rec-hot)">
|
|
<span class="explore-name">Alerts</span>
|
|
<span class="explore-desc">Free percentile alerts: get pinged when your weather goes statistically weird.</span>
|
|
</a>
|
|
</div>
|
|
</section>
|
|
|
|
{{/* --- City hub links --------------------------------------------------
|
|
Real HTML links funnelling homepage authority into the ~1000-page
|
|
/climate surface. */}} <section class="city-hub" aria-labelledby="cities-title">
|
|
<h2 id="cities-title">Climate context for 1,000 cities</h2>
|
|
<ul class="city-chips">
|
|
{{range .Cities}} <li><a href="{{$.Base}}/climate/{{.Slug}}" data-event="home.nav_city">{{.Name}}</a></li>
|
|
{{end}} </ul>
|
|
<p><a href="{{.Base}}/climate" data-event="home.nav_cities">Browse all cities →</a></p>
|
|
</section>
|
|
{{template "base_body_end" .}} <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
|
<script type="module" src="{{.AssetBase}}/app.js"></script>{{/* Bookmarked locations: a star toggle in the results share row + a "Saved"
|
|
dropdown by the Find button. Loaded as its own module alongside app.js
|
|
(not merged into it) — see bookmarks-ui.js's header comment for why. */}}
|
|
<script type="module" src="{{.AssetBase}}/bookmarks-ui.js"></script>{{/* The records strip renders real temperatures server-side as .temp spans, so
|
|
it needs the same converter the SEO pages use or they'd stay in °F while
|
|
the toggle says °C. Both import units.js, which the module loader dedupes. */}}
|
|
<script type="module" src="{{.AssetBase}}/climate.js"></script>
|
|
{{template "base_tail" .}} |