Skip to content

feat: add search, density toggle, and sticky controls to gallery index#113

Merged
TMHSDigital merged 2 commits into
mainfrom
feat/gallery-index-navigation
Jul 24, 2026
Merged

feat: add search, density toggle, and sticky controls to gallery index#113
TMHSDigital merged 2 commits into
mainfrom
feat/gallery-index-navigation

Conversation

@TMHSDigital

@TMHSDigital TMHSDigital commented Jul 24, 2026

Copy link
Copy Markdown
Owner

What this is

Quality-of-life pass on the generated examples gallery (scripts/build_gallery.py and 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)

  1. Compact/detailed density toggle — compact is the JS default: hero + name + single-line teaser. The description and WITNESSES text are not removed from the DOM — visually collapsed via the clip pattern (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.
  2. Client-side search — substring match over each card's own DOM text (name + teaches + WITNESSES), AND-combined with the active tag. Live match count (N of 40), × clear control, and a readable empty state with a reset button.
  3. Sticky controls bar — search + count + density toggle + tag chips pinned under the topbar (top: 46px). On viewports <720px the 36-chip row collapses behind a Tags toggle (JS-gated class, so no-JS keeps the wrapped row).
  4. Responsive columns — compact: 1/2/3/4 columns at 560/900/1280(3 col ≥900)/1560px (main widens to 1400px for the 4-col tier). Detailed keeps the existing 2-col ≥720px. Card proportions and 16/9 hero aspect unchanged.
  5. URL hash state#q=…&tag=…&d=… written via history.replaceState on every change; restored on load. Hash wins over localStorage; an unknown tag in the hash is discarded rather than showing a silent zero.
  6. Back-to-top — appears after 600px of scroll; respects prefers-reduced-motion.
  7. Keyboard/ 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)

Build Full page height
Current committed build 13,003px
New build, compact (default) 4,233px (−67.4%)
New build, detailed 12,966px (parity)

Verification — live run (Playwright, Chromium 148, local python -m http.server over docs/)

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):

  • Default load compact (3-col grid, witnesses clipped to 1px but in DOM) and detailed toggle (2-col, full card) at both widths.
  • Search matching several (mesh → 20), exactly one (needle → only damped-track-aim — a WITNESSES-only word, proving collapsed text is still searched), and none (zzzzqq0 of 40 + empty state + reset).
  • Tag alone (bmesh → 6) and tag AND search (bmesh+gear → 1); clear via ×, via "All", and via the empty-state reset.
  • Hash state: applied filters, copied URL, fresh page → query, active chip, density, and count all restored; #d=compact beats a stored detailed preference.
  • Density persistence across reloads in both directions.
  • Sticky bar pinned at top: 46px while scrolled 2000px (desktop) / 1200px (mobile); topbar still at 0.
  • Back-to-top appears and returns to top; / focuses, Escape clears and blurs.
  • Mobile: chips collapsed behind Tags toggle, toggle opens/closes the row, tag + search combine, sticky holds.

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 no js/density-compact class. Native loading="lazy" is untouched and still works.

Alt text: re-read all 40 generated <img alt>s in docs/gallery/index.html — none truncated at dotted API paths; the generator's assert_alts_survive_dotted_paths guard 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-checked bmesh-gear detail: 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)

  • Firefox/Safari rendering (Chromium only). The 1560px+ 4-column compact tier was additionally live-checked at 1600px: 4 columns with main widened 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.

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>
@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Jul 24, 2026
@TMHSDigital
TMHSDigital merged commit 087bdf1 into main Jul 24, 2026
9 checks passed
@TMHSDigital
TMHSDigital deleted the feat/gallery-index-navigation branch July 24, 2026 01:03
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant