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 5e01cd0..6e915f1 100644 Binary files a/docs/gallery/assets/swatch-grid-hero.webp and b/docs/gallery/assets/swatch-grid-hero.webp differ 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")