Quick Start
- Copy the whole folder to your project. It is static: no build step.
- Open
index.html. Replace the gallery name (Verge), the exhibition (Interval), the artwork captions, the curatorial copy, and the visit details with your own. - Deploy the folder as-is. The GSAP and Lenis tags load from a CDN; nothing else is required.
The scripts at the end of <body> are:
Code snippet omitted: it ships with the download.
Sections & options
Every section is found by a data-* hook and guarded independently, so you can delete any one and the rest still runs.
| Section | Hook | Notes |
|---|---|---|
| Nav | [data-nav] |
Sticky, translucent. Edit the links inline. |
| Hero | [data-hero] |
The exhibition title ([data-line]) rises on a masked reveal; the sub-copy, facts and figure ([data-intro]) fade in with it. |
| Exhibition wall | [data-wall], [data-gallery], [data-viewport], [data-track] |
The signature. See below. |
| Featured piece | [data-feature] |
Enlarges on Flip. See below. |
| About / statement | [data-about] |
Curatorial text and a pull quote. |
| Visit | [data-visit] |
Hours, address, admission. |
| Footer | — | Wordmark, links, contact. |
The exhibition wall
The section pins and vertical scroll pans the track of works ([data-track]) sideways, so scrolling reads as walking the hang. The pan length is the track's own horizontal overflow, read live so it re-evaluates on resize and after a filter, and the pin starts below the fixed nav so the filter bar stays clear. The work nearest the viewport centre gets is-focus and lifts; [data-progress] fills as you go. This is motion only: with no JavaScript or under reduced motion the same track is a native horizontal scroll strip you swipe by hand, so nothing is trapped behind the pin.
Each work is a <figure data-work data-medium="…"> with a .work-frame (the mat), a decorative .art .art--N composition, and a caption. Works carry a size class (is-sm / is-md / is-lg) for the hang's rhythm. To add a work, copy any figure, give the .art a new art--N block in style.css, set data-medium to one of the filter values, and pick a size class.
The filter buttons carry data-filter matching those data-medium values (all, painting, paper, sculpture, textile). To add a medium: add a data-medium to some works and a matching <button data-filter="…"> to [data-filters]. Nothing else is wired by index; the script pairs them by value.
Clicking a filter runs a short cross-dissolve: the track fades out, non-matching works get is-off, the pan length is recomputed for the new track width, the pan resets to the first work, and the track fades back. A set small enough to fit centres itself and hides the progress bar and scroll hint. [data-wall-count] reports the on-view count via aria-live.
The featured piece
[data-feature-open] moves [data-feature-piece] into [data-feature-target] in the overlay and Flips it from its inline rectangle to the enlarged one — it is one element growing, not a copy. Dismiss with the veil, the close control, or Escape; focus returns to where it was. Swap the featured artwork by changing the .art--feature block in style.css and the caption text in the feature-text and lightbox-cap.
Palette
The whole site is three colours at different values, plus greys for text. Change these tokens in :root (assets/style.css) and keep each -rgb triple in step with its hex, and the site reskins with no stray literals left behind.
Code snippet omitted: it ships with the download.
Measured contrast (as used, on the #F2F2EF ground)
| Pair | Ratio | Where |
|---|---|---|
--ink on ground |
15.8:1 | all body copy and headings |
--dim on ground |
5.05:1 | ledes, captions, secondary text |
--faint on ground |
4.8:1 | smallest labels, years, notes |
ground on --ink |
15.8:1 | featured band and footer |
--faint-ink on --ink |
6.8:1 | footer meta, address |
All body and label text clears the 4.5:1 AA floor at its actual size.
Vermilion is deliberately not a text colour. --accent measures 3.77:1 on the ground and 4.23:1 as white-on-vermilion — both below the 4.5:1 body floor. It is used only for:
- the hero display word (
Interval), which is ~6rem, so the 3:1 large-text rule applies and it clears at 3.77:1; - marks and rules (the diamond brand mark, the eyebrow ticks, the active-filter underline), which are non-text UI at the 3:1 bar;
and never for body copy, captions, small labels, or button text. Buttons are ink, not vermilion, for the same reason. If you swap in a lighter accent, keep it off small text or darken it (past roughly #C7331C) before using it as type.
Accessibility
- Reduced motion: the wall is never pinned — its default CSS is a native horizontal scroll strip — so the exhibition stays a swipeable wall of works. A CSS media query holds every reveal target visible, filtering toggles instantly, and the enlarge runs with no animation.
- No JavaScript: the filter row, the on-view count, and the enlarge control are hidden by CSS (
.has-jsgates them). All twelve works stay on the horizontal strip, and the featured piece sits at its inline size. Nothing is reachable only through a script. - Filters are real
<button>s witharia-pressed; the active medium is exposed to assistive technology, not just shown by the underline. - The enlarged view is a labelled
role="dialog"; focus moves to the close control on open and returns on close, and Escape dismisses it. - Keyboard focus is visible throughout via
:focus-visible.
Dependencies
- GSAP 3.15.0 core, ScrollTrigger, and Flip
- Lenis 1.3.17 smooth scroll (optional — remove the block in
script.jsand the CDN tag to drop it) - Google Fonts: Rozha One (display) and Hind (body)
Worked examples, the events and programmatic API, and the class reference ship with the download, alongside the full source.