Quick Start
- Copy the whole folder to your web host (or open
index.htmllocally). - Replace the truck name, dishes, prices and pitches: search
index.htmlfor "Patty Wagon", the£amounts, and the seven<li class="pitch">entries. - Point the booking and contact links at your own inbox: search for
@pattywagon.co.uk. - Swap the photos in
assets/img/for your own (details below).
The page keeps working as you cut it down: every section (#menu, #where, #scene, #catering, #follow) can be deleted wholesale and the script skips what it can't find.
Files
Code snippet omitted: it ships with the download.
The route map
Three things make the map, all in index.html:
- The route: one SVG path (
data-route-path) snaking through the streets, drawn twice: a dotted smoke base and a reddata-route-tracecopy that highlights the travelled part. - The pins: seven
<g class="map-pin">groups, one per day, each carryingdata-pin,data-xanddata-y. The tip of each pin should sit on the route. Each group also holds an invisible.pin-hitcircle so hovers land without pixel-hunting. - The van: a
<g data-truck>group whose local origin rides the path. The markup parks it at the yard, which is where it sits without JavaScript.
The van finds its own way. At load, the script measures where each pin sits along the route by sampling the path for the nearest point. So to move a pitch: drag its pin coordinates (data-x/data-y and the translate(...) to match), make sure the route passes nearby, and you're done; no progress values to keep in sync. To redraw the route entirely, edit the two d attributes (keep them identical) and the pins will re-measure against it.
The order of the week is the order of the day-picker buttons, and the drive passes through intermediate stops the way a real van would. Duration scales with distance (0.85–3.4s), in drive() in script.js.
The pitch photos live in the list, not the map. Each <li class="pitch"> starts with a .pitch-photo image; that is the accessible record and what no-JS visitors see. At load the script clones those images into paper-framed popovers over the map, one per pin, positioned from the pin's data-x/data-y (clamped at the edges; pins in the top third open downward). Hover a pin to preview its photo, select a day to keep one open, tap or click a pin to select that day. Change a photo in the list and the map follows; there is nothing to edit twice.
The map is a picture, not the record. It's aria-hidden, popovers included; the pitch list below carries all the information. Editing a pitch means editing its <li class="pitch">: photo, day, place, hours, note, plus the data-short attribute that fills the hero's "Today ▸" chip.
To relabel the city, edit the <text class="m-label"> elements and the block/park/water shapes; they're ordinary SVG rectangles and paths in a 900×620 viewBox.
The picker is a real radiogroup: click or tap any day, or focus it and use the arrow keys, Home and End. The default selected day is the real current weekday.
Per-section options
- Hero: the photo panel shows two crops of the same image:
hero.webpwide on desktop,hero-tight.webpunder 720px (the<source>in the<picture>). The caption sits on a gradient scrim; if you swap the photo, keep the caption inside the darkest band or re-measure the contrast (see the stylesheet header). The "Today ▸" chip fills itself from the pitch list; with JavaScript off it reads as a link to the map. - Marquee: the mustard strip between hero and menu. Edit the sentence in both
.marquee-runspans (they must match; the pair is what makes the loop seamless). Delete the whole.marqueediv freely. - Menu: each dish is a
.board-itemwith a dotted leader between name and price. Thevbadge is a.board-tagspan; copy it for gf/vg/anything. Columns are just two.board-coldivs. - Where: see above.
- The scene: a lead photo spanning the grid plus three tiles, each a
.scene-photofigure with a mono caption. Add or remove tiles freely; the grid reflows. This is the section that shows the crowd, so keep it people-first when you swap images. - Catering: bullet list, price line and a
mailto:CTA. No form by design: trucks triage by email. - Follow: the handle is the hero of the section; the chips are plain links.
Photography
The images are bundled, optimised WebP derivatives: Unsplash sources with this template's bright, warm grade baked into the pixels (saturation lifted, hue nudged toward red, per-image brightness matched so mixed sources read as one sunny campaign). They are not the original files and should not be treated as a photo library.
To swap in your own photos, just replace the files in assets/img/ (same names), or change the <img> tags. A background-color sits behind every photo, so a missing image never leaves a hole. The hero and the Monday yard.webp tile are crops of the same source on purpose: they read as the same van. Keep that continuity if you can, since the map photos claim to be your pitches.
To re-run the grade (GSAP Vault repo tooling): edit assets/img-manifest.json (source URL, crop, size, per-image brightness) and run:
Code snippet omitted: it ships with the download.
Image credits (Unsplash sources)
hero.webp,hero-tight.webp,yard.webp(three crops of one photo): https://images.unsplash.com/photo-1565123409695-7b5ef63a2efbquay.webp: https://images.unsplash.com/photo-1553713821-897179833050green.webp: https://images.unsplash.com/photo-1690402960121-16afb5d36e65finzels.webp: https://images.unsplash.com/photo-1588888689244-1f24c30f5419wharf.webp: https://images.unsplash.com/photo-1512258113-32962c013e66market.webp: https://images.unsplash.com/photo-1622915835346-ae2e13269f32downs.webp: https://images.unsplash.com/photo-1762656669458-75bf76a45f4bscene-day.webp: https://images.unsplash.com/photo-1758388531306-3ce603fdd007scene-van.webp: https://images.unsplash.com/photo-1778664216440-4edca6861879scene-tables.webp: https://images.unsplash.com/photo-1770564512606-a255ed5cfaa3scene-dusk.webp: https://images.unsplash.com/photo-1505426101273-b50e6f3ff9a7smash.webp: https://images.unsplash.com/photo-1568901346375-23c9450c58cdboard.webp: https://images.unsplash.com/photo-1561758033-d89a9ad46330fries.webp: https://images.unsplash.com/photo-1573080496219-bb080dd4f877tray.webp: https://images.unsplash.com/photo-1594212699903-ec8a3eca50f5
Palette
There is deliberately no palette switcher: the chilli red is the identity. A reskin is still a small edit, because every colour on the page resolves from these tokens (plus their RGB triples, which feed every translucent value) at the top of assets/style.css:
Code snippet omitted: it ships with the download.
Change a hex and its triple together (--red-deep, --smoke-lift and --block are the same colours pushed a step; move them with their parents). The stylesheet header records the measured contrast of every pair as used, including the hero caption composited over the baked photo; re-measure yours against those floors before shipping, especially the two standing bans: dimmed cream never carries text on the red ground, and mustard is never text on red.
Accessibility
- Keyboard: the day picker is a radiogroup with roving tabindex and arrow-key/Home/End support; every interactive element is a real
<button>or<a>; focus rings are always visible and flip colour per surface. Pin hover is a preview only; everything it shows is reachable through the picker and the pitch card. - Reduced motion: honoured twice: a CSS media query removes all reveal start states and stops the marquee, and the script's matchMedia reduce branch places the van at the selected pin instantly (heading read from the path tangent), highlights the route statically, shows the pitch photo without animation, and never creates Lenis. The picker stays fully functional.
- No JavaScript: the whole page reads. All seven days' pitches render as a plain ordered list with their photos, the van sits parked at the yard, and the hero chip links to the map section. The map popovers are never created.
- Screen readers: the map SVG and its popover layer are
aria-hiddendecoration; the pitch list and picker carry all the information, photos included; the marquee isaria-hiddenand repeats nothing that matters.
Dependencies
Loaded from CDN at the end of index.html:
- GSAP core, ScrollTrigger, MotionPathPlugin (3.15.0)
- Lenis smooth scroll (1.3.17), optional; delete its tag and the page falls back to native scrolling
Fonts: Shrikhand, Outfit, Ubuntu Mono via Google Fonts.
Worked examples, the events and programmatic API, and the class reference ship with the download, alongside the full source.