feat: add search, density toggle, and sticky controls to gallery index#113
Merged
Conversation
At 40 examples the index was a 13003px two-column scroll with only a tag row to narrow it. The generator now emits a sticky controls bar (search, tag chips, compact/detailed density toggle, match count) plus hash-based shareable state, back-to-top, and '/'/Escape keyboard affordances. Compact density (the default with JS) collapses each card to hero + name + one-line teaser for a 67% page-height reduction; the description and WITNESSES text stay in the DOM via visual clipping, so search, screen readers, and no-JS rendering are unaffected. Everything is gated on JS-added classes: with JavaScript disabled all 40 cards render expanded and the controls are simply inert. No external dependencies; vanilla CSS/JS emitted by scripts/build_gallery.py. Signed-off-by: fOuttaMyPaint <TMhospitalitystrategies@gmail.com>
Output of python scripts/build_gallery.py. Index gains the controls bar markup; detail pages differ only in the shared stylesheet block (index-only rules that match nothing on those pages) - markup and behavior unchanged. Signed-off-by: fOuttaMyPaint <TMhospitalitystrategies@gmail.com>
TMHSDigital
added a commit
that referenced
this pull request
Jul 24, 2026
The landing page dumped all 40 example cards into its Render Result section, making it the longest scroll on the site (9948px at 1280px). A landing page should showcase, not enumerate - the full grid lives at /gallery/ with search, filters, and density controls since #113. Selection is data-driven: gallery.json entries may carry a featured_rank integer, build_site.py sorts by it into featured_examples, and the template renders that subset (falling back to the full list when nothing is curated). The panel counter is now honest about shown-vs-total ('8 of 40 frames'), and the gallery link moves from a below-the-fold footnote to a primary button above the grid. No controls on the landing page - that is what /gallery/ is for. Featured set (rank order): car-mirror-symmetry, soccer-ball-goldberg, damped-track-aim, light-link-studio, collision-hull-proxy, armature-bend, bmesh-gear, color-attribute-wheel - the pinned calibration members plus recent striking work, chosen for subject and palette range. Landing page height: 9948px -> 4847px at 1280px (-51%), 24788px -> 10203px at 390px (-59%). Signed-off-by: fOuttaMyPaint <TMhospitalitystrategies@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this is
Quality-of-life pass on the generated examples gallery (
scripts/build_gallery.pyand the CSS/JS it emits). At 40 examples the index was a 13,003px two-column scroll with only a tag row to narrow it. No example content, render, or check code is touched;docs/gallery/changes are 100% generator output (python scripts/build_gallery.py).Built (all 7 ranked items)
position:absolute; clip:rect(0 0 0 0)), so they stay searchable, screen-reader reachable, and present for no-JS. Detailed is the previous card, byte-for-byte in anatomy.N of 40), × clear control, and a readable empty state with a reset button.top: 46px). On viewports <720px the 36-chip row collapses behind a Tags toggle (JS-gated class, so no-JS keeps the wrapped row).#q=…&tag=…&d=…written viahistory.replaceStateon every change; restored on load. Hash wins over localStorage; an unknown tag in the hash is discarded rather than showing a silent zero.prefers-reduced-motion./focuses search (guard against typing in inputs), Escape clears + blurs.Density persists in
localStorage(bdt-gallery-density); a head script applies the class before first paint so there is no detailed→compact flash.Scroll-height reduction (live measurement, 1280×800 viewport, all 40 lazy images loaded)
Verification — live run (Playwright, Chromium 148, local
python -m http.serveroverdocs/)51 scripted assertions, all passing, at 1280px desktop and 390px mobile (
isMobile, touch). States exercised and captured (screenshots taken and reviewed, not just load-checked):mesh→ 20), exactly one (needle→ onlydamped-track-aim— a WITNESSES-only word, proving collapsed text is still searched), and none (zzzzqq→0 of 40+ empty state + reset).bmesh→ 6) and tag AND search (bmesh+gear→ 1); clear via ×, via "All", and via the empty-state reset.#d=compactbeats a storeddetailedpreference.top: 46pxwhile scrolled 2000px (desktop) / 1200px (mobile); topbar still at 0./focuses, Escape clears and blurs.No-JS (browser context with JavaScript disabled): all 40 cards render expanded with readable teaches/WITNESSES/link text (locator-verified and captured full-page after input-driven scrolling); chips row visible; count statically reads
40 examples; controls inert;<html>carries nojs/density-compactclass. Nativeloading="lazy"is untouched and still works.Alt text: re-read all 40 generated
<img alt>s indocs/gallery/index.html— none truncated at dotted API paths; the generator'sassert_alts_survive_dotted_pathsguard passes. Card template alt path untouched.Detail pages: regenerated markup is byte-identical except the shared
<style>block (index-only selectors that match nothing on detail pages). Live-checkedbmesh-geardetail: lightbox zoom, Escape, and copy-run-command all work. The CLAUDE.md Playwright note needs no update — lazy loading behavior is unchanged.Inspection only (not live-run)
mainwidened to 1400px, detailed stays 2-column at the same width.Release note
feat:subject — this cuts a minor release on merge per release.yml. Pages deploy will regenerate the gallery from the same script, so committed pages and served pages cannot drift.