thermograph/frontend/server/internal/render/templates/home.html.tmpl
emi a316adadc4
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
frontend: load bookmarks-ui.js from the Go SSR home template too
2026-07-26 18:48:13 +00:00

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&nbsp;sq&nbsp;mi cell, builds a &plusmn;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}}&amp;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}} &middot; {{.PctLabel}} pct
{{- if .Normal}} &middot; normal {{$.Fmt.Temp .Normal}}{{end}}
{{- if .DateLabel}} &middot; {{.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>
&middot; Data: ERA5 via Open-Meteo &middot; NASA POWER &middot; 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" .}}