Jab
Jab is a site for a boxing or fitness gym: boxing clubs, conditioning gyms, martial arts and kickboxing studios, personal trainers with a studio. It is set edge to edge like a fight poster: newsprint with a halftone screen that drifts as you scroll, black ink, signal red, volt yellow and cobalt, and headlines set huge in an ultra-condensed face. On the home page the headline fills the width of the window and, as you scroll, a glove throws a three-punch combo at a heavy bag in a ring that runs across the whole window: bursts of halftone rays, the bag swinging, the ropes shuddering, the round counter ticking on. The visit page (and the home page) shows a map of the streets round the gym. The demo is a fictional club, Haymaker Boxing Club.
Jab comes in two variants, chosen when you create the site: files/Sanity (static pages, any host)
and EmDash (--cms emdash, rendered on request on Cloudflare). The pages are the
same in both.
Pages and their content
Section titled “Pages and their content”| Page | Edit it in |
|---|---|
| Home | Pages → Home page (a list of sections) |
| Classes, coaches, journal, 404 | Pages → the page’s own entry |
| Membership (plans, your first session, booking, passes and gift cards, questions) | Pages → Membership page |
| Visit (opening hours, what to bring, map, details, contact form, questions) | Pages → Visit page |
| Classes (level, intensity, how long, price, times, rounds, questions, photo) | Content → Classes |
| Coaches (nickname, role, record, qualifications, classes, biography, portrait) | Content → Coaches |
| Journal posts | Content → Journal |
| Name, phone, address, affiliation, navigation, header button, profiles | Pages → Site settings |
| Buttons, labels, timetable, chat, emails, game texts | Content → Interface texts |
| Privacy, terms, safety | Pages → Legal |
In headlines, words wrapped in *stars* are printed in white capitals on a strip of red tape, as in
“Train like a *fighter*”. The home page’s headline is set as wide as the window: on phones each line
break in it starts a new line, on wider screens it runs on one line.
Classes and the timetable
Section titled “Classes and the timetable”Each class has a line, a summary, a level (“Beginners”, “All levels”), an intensity from 1 to 5 (shown as five blocks), how long it takes, the drop-in price with an optional note, its times in the week (day, time and the coach) and its rounds: the steps of a class, shown on the red rounds band. The timetable is built from these times: the home page’s Timetable section and the classes page show the whole week on a scoreboard (a row you swipe on phones), and today’s column lights up. Change a time in the class and every page follows.
Coaches
Section titled “Coaches”Coaches are shown as fight posters: the portrait in black and white with a halftone screen (it turns to colour when you point at it), the nickname in quotes, the name, the role and the record, with a “VS” stamp between each pair. Use a 4:5 portrait from the waist up.
Membership, booking and payments
Section titled “Membership, booking and payments”The membership page holds the plans (mark one as the champion: it is shown bigger, under a championship belt), your first session as three rounds with a round clock that ticks to 3:00 as you scroll, booking a free trial with Cal.com and the passes and gift cards you sell with Stripe: each one gets a one-time Price id. Without keys, the booking shows a preview of the calendar and the payment buttons explain how to switch them on.
The map
Section titled “The map”The “Find us” map is drawn from OpenStreetMap in your
colors: streets, green spaces, water and buildings round the gym, a pin with your name, your address
and “Get directions” links to Google Maps, Apple Maps and OpenStreetMap. It is part of the page (no
embed, no cookies, nothing loaded from another site) and follows the theme and your accent. To draw
your own area, put the gym’s coordinates in map.config.json (center, and size in metres) and run
pnpm map -- fetch: it downloads the area once and writes src/data/location-map.json. The map’s title
and text are on the Visit page (and in the home page’s “Find us” section); the attribution link stays,
as OpenStreetMap’s licence asks.
Integrations
Section titled “Integrations”Everything works without keys; each feature switches on with its variables in .env.example.
| Feature | Where | Service |
|---|---|---|
| Book a free trial | Membership page, loads on click; a preview of the calendar until connected | Cal.com |
| Passes and gift cards | Membership page (one-time Price ids) | Stripe |
| Newsletter | Home and journal pages, with a welcome email | Supabase |
| Contact form | Visit page; messages go to the email in Site settings | Resend |
| AI chat | On every page, answers about classes, the timetable, membership, the coaches and opening hours; never gives medical advice | Claude |
| Search | ⌘K or the search button: classes, coaches, journal, membership, visit | built in, or Algolia |
| Analytics | Every page; Google Analytics only after consent | Plausible, Umami, Google Analytics |
Sections
Section titled “Sections”The home page is built from these sections, in any order: In the ring (the opening), Classes (bouts on a fight card that slam in), Giant words (rows that slide across the window as you scroll), Timetable, Your first session (three rounds on a red band), The coaches (fight posters), Numbers (a scoreboard whose figures roll up), Journal, Find us (the map; its streets draw themselves and the pin drops in), Newsletter and Closing call (a volt-yellow band with a photo on a turning starburst). With reduced motion, the ring stands still with the glove on the bag, the notes appear as tags and nothing moves.
Colors and brand
Section titled “Colors and brand”src/content/singletons/brand.json (Pages → Brand in Keystatic) holds three colors: the accent
(signal red: buttons, marks, the gloves and the bag), the second (volt yellow: the tape and bright
fills) and the third (cobalt: the ring floor, a rope and fills). Buttons are darkened until their
white text is readable, the second and third are lightened until ink text on them is; a color that
can’t get there stops the build with a message. Brand also sets the fonts (headings and text) and the
corners (square like a poster, or slightly rounded). The site opens in the day theme even when the
visitor’s system is dark; the toggle switches to the night, the poster printed in reverse on black,
and the browser remembers the choice.
Languages
Section titled “Languages”Jab is ready for more languages: see Languages.
The 404 page
Section titled “The 404 page”A small game, Slip: a sparring partner shows a tell, then throws a jab from your left, a cross from your right or a hook at head height. Slip the other way or duck before the glove lands, with the arrow keys, the three buttons or a swipe; moving during the tell is too early. Three rounds of eight punches, faster each round; the best score is kept in the visitor’s browser.
AI assistants and search engines
Section titled “AI assistants and search engines”AGENTS.mddescribes the project for coding assistants (Claude Code reads it throughCLAUDE.md, Cursor through.cursor/rules/)./llms.txtlists your pages, classes, coaches and journal for AI assistants, in every language.- Pages carry JSON-LD: the gym as an
ExerciseGymwith its plans as offers, classes asServices with their price, the coaches asPersons, your first session as aHowTo, questions as aFAQPage, and each journal post as aBlogPosting.
Commands
Section titled “Commands”pnpm dev # local site, editor at /keystaticpnpm build # production buildpnpm map -- fetch # draw the map round map.config.json from OpenStreetMappnpm cms sanity schema|seed # move content to Sanity (see Content → Sanity)In the EmDash variant: npm run dev, npm run deploy (Cloudflare) and npm run emdash:schema
(after changing the content model).