From 8d45e96c0124eecbd286e9369af33e762c791b29 Mon Sep 17 00:00:00 2001 From: fOuttaMyPaint Date: Sat, 20 Jun 2026 18:05:31 -0400 Subject: [PATCH] docs: uniform 16:9 gallery heroes + self-sufficient nav Screenshot review (headless Chrome, desktop+mobile, light+dark) showed a ragged empty gap under the short card on desktop: the swatch hero was 8:3 panoramic while the other two were 16:9, so the 2-col grid rows were uneven. - Re-render the swatch hero at 16:9 (1280x720) so all three heroes share an aspect; the 6 spheres now fill the frame (was a left-clustered panorama). - Gallery CSS: uniform 16:9 media (object-fit: cover, no crop now that heroes match), align-items: stretch, and a bottom-pinned "View example" link so paired cards are equal height with aligned footers. - Add a GitHub repo link to the top bar so the gallery is self-sufficient for navigation (the landing->gallery link is template-blocked); back-link to the landing already present. - DESIGN_NOTES updated to record the evolved direction (16:9 heroes, stretch, pinned footers, outbound nav) for the Option-2 fleet prototype. Co-Authored-By: Claude Opus 4.8 Signed-off-by: fOuttaMyPaint --- docs/gallery/DESIGN_NOTES.md | 10 +++++++--- docs/gallery/assets/swatch-grid-hero.webp | Bin 7354 -> 9946 bytes docs/gallery/index.html | 16 +++++++++++----- scripts/build_gallery.py | 20 ++++++++++++++------ 4 files changed, 32 insertions(+), 14 deletions(-) diff --git a/docs/gallery/DESIGN_NOTES.md b/docs/gallery/DESIGN_NOTES.md index d15fa05..a0f6718 100644 --- a/docs/gallery/DESIGN_NOTES.md +++ b/docs/gallery/DESIGN_NOTES.md @@ -27,13 +27,17 @@ migration; the **data** (`gallery.json`) and the **visual direction** below are ## Layout - Container `max-width: 1080px`, centered, `1.25rem` side padding. -- Grid: **1 column** mobile → **2 columns at ≥720px**, `align-items: start` (cards don't stretch to a shared row height — heroes have mixed aspect ratios, e.g. the 8:3 swatch panorama vs 16:9 stills, and must not be cropped). -- Card: hero image (full-bleed, natural aspect, never cropped) above a body block; hover lifts `-3px` with an accent-tinted border and soft shadow. +- Grid: **1 column** mobile → **2 columns at ≥720px**, `align-items: stretch` so paired cards share a row height. +- **Heroes are a uniform 16:9** (`aspect-ratio: 16/9; object-fit: cover`). This was a deliberate revision: the first cut used mixed aspects (8:3 swatch panorama + 16:9 stills) with `align-items: start`, which — confirmed by headless screenshots — left a ragged empty gap under the short card on desktop. The swatch hero was re-rendered at 16:9 so all three share an aspect; with uniform heroes `object-fit: cover` no longer crops anything. Keep new heroes 16:9. +- Card is a flex column; the "View example" link is pinned to the bottom (`margin-top: auto`) so footers align across a row. Hover lifts `-3px` with an accent-tinted border and soft shadow. ## Theme - Tri-state **auto / light / dark** mirroring the landing: `data-theme` on ``, persisted in `localStorage['theme']` — **the same key the landing uses**, so a visitor's choice carries across both pages. - FOUC guard runs before first paint. `theme-color` meta per scheme. Toggle cycles auto→light→dark with a sun/moon glyph and an accurate `aria-label`. +## Navigation / discoverability +- The landing→gallery link is **template-blocked** (the landing nav is fleet-generated). The gallery compensates with self-sufficient outbound nav: a top-bar **back link to the landing** (`../`) and a **GitHub repo link**, plus per-card links to each example on GitHub. A visitor who arrives via the README or a direct link can always navigate out. + ## Accessibility - Skip-to-content link; `:focus-visible` outlines (accent, 2px); `prefers-reduced-motion` disables hover transform + smooth scroll; descriptive `alt` per hero ("``"); single `h1`, card titles as `h2`. @@ -41,4 +45,4 @@ migration; the **data** (`gallery.json`) and the **visual direction** below are 1. The token set above as CSS variables, light/dark parity with the existing landing palette. 2. An **Examples grid** section reading `gallery.json` (per-entry `name/dir/teaches/witnessesFix/hero/preview`), cards styled as here. 3. A **nav link to the gallery** (closes the landing→gallery cross-link gap — impossible today without a template edit). -4. Keep heroes uncropped (`align-items: start`, natural aspect) — cropping loses content like the swatch panorama's outer spheres. +4. Standardize heroes at **16:9** with `object-fit: cover` and `align-items: stretch` for an even grid; pin card footers (`margin-top: auto`). (Supersedes the first cut's mixed-aspect/`align-items: start` approach, which read as ragged — see Layout.) diff --git a/docs/gallery/assets/swatch-grid-hero.webp b/docs/gallery/assets/swatch-grid-hero.webp index 5e01cd0ed2d7d1528b3ebbf285c7a906080d1d20..6e915f1f66279e3680217fbc4ce89bbf577b8aab 100644 GIT binary patch literal 9946 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 E131_vuK)l5 literal 7354 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 diff --git a/docs/gallery/index.html b/docs/gallery/index.html index b65dbcf..f062b4e 100644 --- a/docs/gallery/index.html +++ b/docs/gallery/index.html @@ -58,6 +58,9 @@ backdrop-filter: blur(10px); border-bottom: 1px solid var(--border); } .topbar .back { color: var(--text-dim); font-size: 0.9rem; font-weight: 500; } .topbar .back:hover { color: var(--accent-light); } + .topbar-right { display: flex; align-items: center; gap: 0.85rem; } + .topbar-right .ghlink { color: var(--text-dim); font-size: 0.9rem; font-weight: 500; } + .topbar-right .ghlink:hover { color: var(--accent-light); } .theme-toggle { background: var(--surface-2); border: 1px solid var(--border); color: var(--text); border-radius: var(--radius); padding: 0.35rem 0.6rem; font-size: 0.95rem; cursor: pointer; line-height: 1; transition: border-color 0.15s, background 0.15s; } @@ -68,15 +71,15 @@ header.hero p { color: var(--text-dim); max-width: 62ch; margin-top: 0.6rem; font-size: 1.02rem; } main { max-width: var(--maxw); margin: 0 auto; padding: 1rem 1.25rem 2rem; - display: grid; grid-template-columns: 1fr; gap: 1.5rem; align-items: start; } + display: grid; grid-template-columns: 1fr; gap: 1.5rem; align-items: stretch; } .card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); overflow: hidden; display: flex; flex-direction: column; transition: transform 0.18s ease, border-color 0.18s ease, box-shadow 0.18s ease; } .card:hover { transform: translateY(-3px); border-color: color-mix(in srgb, var(--accent) 55%, var(--border)); box-shadow: 0 8px 28px rgba(0,0,0,0.28); } .card-media { display: block; background: var(--bg2); line-height: 0; } - .card-media img { display: block; width: 100%; height: auto; } - .card-body { padding: 1.15rem 1.4rem 1.45rem; } + .card-media img { display: block; width: 100%; aspect-ratio: 16 / 9; object-fit: cover; } + .card-body { padding: 1.15rem 1.4rem 1.45rem; display: flex; flex-direction: column; flex: 1 1 auto; } .card-body h2 { font-size: 1.22rem; letter-spacing: -0.01em; margin-bottom: 0.5rem; } .card-body h2 a { color: var(--text); } .card-body h2 a:hover { color: var(--accent-light); text-decoration: none; } @@ -85,7 +88,7 @@ .tag { display: inline-block; font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--accent-light); border: 1px solid color-mix(in srgb, var(--accent) 60%, transparent); border-radius: 999px; padding: 0.08rem 0.55rem; margin-right: 0.4rem; vertical-align: 1px; } - .card-link { font-weight: 600; font-size: 0.95rem; } + .card-link { font-weight: 600; font-size: 0.95rem; margin-top: auto; } footer { max-width: var(--maxw); margin: 0 auto; padding: 2rem 1.25rem 3rem; color: var(--text-dim); font-size: 0.85rem; border-top: 1px solid var(--border); } @@ -103,7 +106,10 @@

Examples Gallery

diff --git a/scripts/build_gallery.py b/scripts/build_gallery.py index c0630db..3d62891 100644 --- a/scripts/build_gallery.py +++ b/scripts/build_gallery.py @@ -97,6 +97,9 @@ backdrop-filter: blur(10px); border-bottom: 1px solid var(--border); } .topbar .back { color: var(--text-dim); font-size: 0.9rem; font-weight: 500; } .topbar .back:hover { color: var(--accent-light); } + .topbar-right { display: flex; align-items: center; gap: 0.85rem; } + .topbar-right .ghlink { color: var(--text-dim); font-size: 0.9rem; font-weight: 500; } + .topbar-right .ghlink:hover { color: var(--accent-light); } .theme-toggle { background: var(--surface-2); border: 1px solid var(--border); color: var(--text); border-radius: var(--radius); padding: 0.35rem 0.6rem; font-size: 0.95rem; cursor: pointer; line-height: 1; transition: border-color 0.15s, background 0.15s; } @@ -107,15 +110,15 @@ header.hero p { color: var(--text-dim); max-width: 62ch; margin-top: 0.6rem; font-size: 1.02rem; } main { max-width: var(--maxw); margin: 0 auto; padding: 1rem 1.25rem 2rem; - display: grid; grid-template-columns: 1fr; gap: 1.5rem; align-items: start; } + display: grid; grid-template-columns: 1fr; gap: 1.5rem; align-items: stretch; } .card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); overflow: hidden; display: flex; flex-direction: column; transition: transform 0.18s ease, border-color 0.18s ease, box-shadow 0.18s ease; } .card:hover { transform: translateY(-3px); border-color: color-mix(in srgb, var(--accent) 55%, var(--border)); box-shadow: 0 8px 28px rgba(0,0,0,0.28); } .card-media { display: block; background: var(--bg2); line-height: 0; } - .card-media img { display: block; width: 100%; height: auto; } - .card-body { padding: 1.15rem 1.4rem 1.45rem; } + .card-media img { display: block; width: 100%; aspect-ratio: 16 / 9; object-fit: cover; } + .card-body { padding: 1.15rem 1.4rem 1.45rem; display: flex; flex-direction: column; flex: 1 1 auto; } .card-body h2 { font-size: 1.22rem; letter-spacing: -0.01em; margin-bottom: 0.5rem; } .card-body h2 a { color: var(--text); } .card-body h2 a:hover { color: var(--accent-light); text-decoration: none; } @@ -124,7 +127,7 @@ .tag { display: inline-block; font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--accent-light); border: 1px solid color-mix(in srgb, var(--accent) 60%, transparent); border-radius: 999px; padding: 0.08rem 0.55rem; margin-right: 0.4rem; vertical-align: 1px; } - .card-link { font-weight: 600; font-size: 0.95rem; } + .card-link { font-weight: 600; font-size: 0.95rem; margin-top: auto; } footer { max-width: var(--maxw); margin: 0 auto; padding: 2rem 1.25rem 3rem; color: var(--text-dim); font-size: 0.85rem; border-top: 1px solid var(--border); } @@ -142,7 +145,10 @@

__TITLE__

@@ -189,6 +195,7 @@ def page_relative(repo_rel: str) -> str: def main() -> int: data = json.loads(DATA.read_text(encoding="utf-8")) base = data["repoBaseUrl"].rstrip("/") + repo_root_url = base.split("/tree/")[0] # strip /tree/ -> repo home site = data.get("siteBaseUrl", "").rstrip("/") title = data.get("title", "Examples Gallery") desc = data.get("description", "") @@ -219,7 +226,8 @@ def main() -> int: .replace("__TITLE__", html.escape(title)) .replace("__DESC__", html.escape(desc)) .replace("__CANONICAL__", html.escape(canonical)) - .replace("__OGIMAGE__", html.escape(og_image))) + .replace("__OGIMAGE__", html.escape(og_image)) + .replace("__REPO__", html.escape(repo_root_url))) OUT.parent.mkdir(parents=True, exist_ok=True) OUT.write_text(out_html, encoding="utf-8")