Skip to content

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.

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.

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 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.

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 “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.

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

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.

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.

Jab is ready for more languages: see Languages.

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.

  • AGENTS.md describes the project for coding assistants (Claude Code reads it through CLAUDE.md, Cursor through .cursor/rules/).
  • /llms.txt lists your pages, classes, coaches and journal for AI assistants, in every language.
  • Pages carry JSON-LD: the gym as an ExerciseGym with its plans as offers, classes as Services with their price, the coaches as Persons, your first session as a HowTo, questions as a FAQPage, and each journal post as a BlogPosting.
Terminal window
pnpm dev # local site, editor at /keystatic
pnpm build # production build
pnpm map -- fetch # draw the map round map.config.json from OpenStreetMap
pnpm 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).