2 cases built from this template
Same prompt, different [VARIABLES]. Click one to preview it above.About this template
A two-page website for a small boxing and strength gym that wants to look like a real place with sweat on the floor. The home page opens on a full-bleed photo of heavy bags in a warehouse gym with a huge condensed headline, then moves through six class types with intensity meters, a strip of today's sessions, membership pricing, coaches, member results, a free-trial booking form and a beginner FAQ. The second page is the full weekly timetable: 34 sessions in a days-by-times grid on desktop and day tabs on mobile, filterable by class type and level, with a slide-in drawer for every class showing the coach, capacity, what to bring and a button that books it as a trial.
It suits boxing clubs, kickboxing and Muay Thai gyms, strength studios and any small fitness business that runs a class schedule and sells memberships. The palette is near-black and concrete grey with one blood-orange accent, used for buttons, active filters and the diagonal tape-stripe dividers. Photos of gloves, ropes, rings and kettlebells get the same grainy duotone grade so the set feels shot in one building, and coaches appear as bold monograms instead of portraits.
The interactive pieces are the ones that are hard to get right in a static site: filters that sync to the URL, a focus-trapped drawer, a monthly / 10-class pack toggle that rolls the prices and recalculates the cost per class, and a trial form with inline validation that ends in a ticket-style confirmation. Everything runs client-side, with no backend.
To adapt it, change the gym name, discipline, city and audience. The prompt keeps all classes, sessions, prices and copy in one content file, so the builder rewrites them for your gym while the layout, motion and grade stay the same.
The template prompt
The live demo was built from exactly this text — nothing added. The other cases only change the values of the [VARIABLES].
# Boxing and Strength Gym Website
Build this website exactly as described below. Replace the [VARIABLES] with your own values; keep everything else.
## Brief
A two-page website for [BRAND], a boutique gym in [CITY] teaching [DISCIPLINE] to [AUDIENCE]. It looks bold and gritty: near-black and concrete grey with one blood-orange signal accent, huge condensed uppercase headlines, grainy duotone-graded photographs of bags, gloves, ropes, rings and kettlebells (never faces), and diagonal tape-stripe dividers. Page 1 (index.html) is a long single page: hero, class types, today's sessions, membership pricing with a monthly / 10-class pack toggle, coaches with monogram avatars, results and member quotes, a validated free-trial booking form with an on-page ticket confirmation, a beginner FAQ and a visit band. Page 2 (timetable.html) is a filterable weekly timetable (days × times on desktop, day tabs on mobile) with a slide-in class detail drawer. Everything is static and client-side.
- Audience: [AUDIENCE] in [CITY] who are curious about boxing but nervous about walking into a fight gym, plus members checking tonight's classes on their phone. They want to see what each class is like, whether it suits their level, what it costs, and how to try it free.
- Goal: Turn a nervous visitor into a booked free trial class. The hero, class cards, timetable drawer and pricing all lead to the trial form; the FAQ and coach cards remove the fear of getting hurt or being out of place. Secondary goal: make the timetable the fastest way for members to find a session by type and level.
- Tone: Short, direct and warm under the grit, like a good coach: imperative headlines in caps, plain sentences in the body, concrete numbers (times, prices, kilos, capacities), no hype words, no fight-promo bravado, no exclamation marks. Never mocking beginners.
## Stack
- Framework: Vite 5 + React 18 + TypeScript
- Styling: Tailwind CSS 3.4
- Libraries: react (MIT), react-dom (MIT), motion (motion.dev, import from 'motion/react') (MIT), lucide-react (ISC), tailwindcss (MIT), postcss (MIT), autoprefixer (MIT), vite (MIT), @vitejs/plugin-react (MIT), typescript (Apache-2.0), @fontsource/anton (OFL-1.1), @fontsource-variable/archivo (OFL-1.1), @fontsource/jetbrains-mono (OFL-1.1)
- Build: `npm run build` → static files in `dist/`. No backend, no server code, no external APIs.
- Setup:
- Scaffold with npm create vite@latest using the react-ts template. Two real HTML pages at the project root: index.html (entry src/main-home.tsx) and timetable.html (entry src/main-timetable.tsx). No router library.
- vite.config.ts: base './', plugins [react()], build { outDir 'dist', sourcemap false, assetsInlineLimit 0, rollupOptions { input { index: resolve(__dirname, 'index.html'), timetable: resolve(__dirname, 'timetable.html') } } }. Do not copy src/ into dist/.
- Tailwind 3.4 through PostCSS (tailwindcss + postcss + autoprefixer), content ['./index.html', './timetable.html', './src/**/*.{ts,tsx}']. In tailwind.config.ts add every Design color under theme.extend.colors with kebab-case keys (ink, coal, concrete, slab, dust, chalk, signal, signal-deep) and fontFamily display ['Anton', 'Impact', 'sans-serif'], sans ['Archivo Variable', 'system-ui', 'sans-serif'], mono ['JetBrains Mono', 'ui-monospace', 'monospace']. Set borderRadius DEFAULT to 0.
- In src/fonts.ts import '@fontsource/anton/400.css', '@fontsource-variable/archivo', '@fontsource/jetbrains-mono/400.css' and '@fontsource/jetbrains-mono/500.css'; import src/fonts.ts and src/index.css from both entry files.
- src/index.css holds the Tailwind layers, CSS custom properties for the easings (--ease-cut, --ease-out), the .duo, .grain, .tape and .reveal-cut utility classes, the marquee keyframes, and the prefers-reduced-motion overrides.
- File layout: src/content.ts (all copy and data), src/lib/schedule.ts, src/lib/useCountUp.ts, src/lib/useQueryState.ts (read/write URL query with replaceState), src/lib/useOpenNow.ts, src/lib/validate.ts (trial form rules).
- Components in src/components/: Header.tsx, MobileMenu.tsx, SectionHead.tsx, Mask.tsx (headline line mask), Reveal.tsx, DuoPhoto.tsx (img + .duo + .grain wrapper with alt), Tape.tsx, Marquee.tsx, Hero.tsx, ClassCards.tsx, Intensity.tsx, TodayStrip.tsx, Pricing.tsx, PlanCard.tsx, Coaches.tsx, Monogram.tsx, Results.tsx, TrialForm.tsx, TrialTicket.tsx, Faq.tsx, Visit.tsx, BlockMap.tsx (inline SVG), Footer.tsx, timetable/ (Masthead.tsx, FilterBar.tsx, WeekGrid.tsx, DayList.tsx, ClassDrawer.tsx, Rules.tsx). src/pages/Home.tsx and src/pages/Timetable.tsx compose them; the trial form reads ?class and ?plan, the timetable reads ?type, ?level and ?class.
- Before the first build, download every image listed under Assets into public/images/ with curl -L -o using the exact file names given. The build itself never fetches anything.
- public/favicon.svg as described under Assets. Both HTML files: lang en, <meta name='theme-color' content='#0C0C0B'>, <link rel='icon' href='./favicon.svg' type='image/svg+xml'>. Titles: index.html '<title>[BRAND] — [DISCIPLINE] in [CITY]</title>' (capitalize the first letter of the discipline), timetable.html '<title>Timetable — [BRAND]</title>'.
- Write ASSETS.md at the project root as described under Assets.
- dist/ holds index.html, timetable.html, hashed JS/CSS, the woff2 fonts, favicon.svg and the eight images. Keep it under 8 MB.
## Variables
- [BRAND] — Gym name (default: "Cinderblock Boxing Club"). Invented gym name, 2–4 words. Set in Anton uppercase as the wordmark (header, footer, titles); the initials of its first two words make the booking reference prefix (CB-).
- [DISCIPLINE] — What the gym teaches (default: "boxing and strength training"). Lowercase phrase. Drives the hero, the six class types and their descriptions, coach specialities, the marquee, results, quotes and FAQ answers. Rewrite all of them for a new discipline (for example Muay Thai, kickboxing, wrestling, kettlebell sport), keeping six types and 34 sessions.
- [CITY] — City (default: "Oakland, California"). Where the gym is. Used in the hero eyebrow and lead, the visit band, footer and titles. Rewrite the address, phone format, currency and time zone to fit; street names stay invented.
- [AUDIENCE] — Who trains there (default: "busy adults, from first-timers to amateur fighters"). Lowercase phrase. Shapes the hero lead, the trial form's reassurance copy, the quotes (members' jobs and ages) and the FAQ.
- [CREDIT] — keep a small "Template by VibeFather" link to https://vibefather.ai/t/boxing-and-strength-gym-website in the footer (default: yes; delete this line to remove it)
The copy under Pages & Sections is written for the default values. If your values differ, rewrite every headline, paragraph, label and image for them — same length, tone and structure. Layout, colors, type and motion stay as described.
Keep all copy, image paths and links in one content file (for example `src/content.ts`), keyed by section, so the content can be swapped later without touching components.
## Design
### Colors
- Ink `#0C0C0B` — Page background, header when scrolled, drawer and form card background, text on Signal.
- Coal `#151513` — Alternate section background (marquee, pricing, visit band, house rules), card backgrounds, inputs, full sessions.
- Concrete `#232320` — Raised panels: today strip, trial section, timetable session cells, monogram tiles, map blocks.
- Slab `#36352F` — All 1px borders and rules, empty intensity bars, capacity track, dashed empty cells.
- Dust `#8E8B82` — Muted text: labels, captions, coach names in cells, small print, 'FULL' states.
- Chalk `#EDEAE3` — Primary text and headlines; body text uses Chalk at 80%.
- Signal `#FF4F1F` — The only accent (blood orange): primary buttons, active chips and tabs, second headline lines, intensity bars, tape stripes, error states, focus rings, today markers.
- Signal Deep `#D63A0F` — Hover and pressed state of Signal buttons and chips.
### Typography
- Display: H1, H2, H3, wordmark, prices, counters, monograms, session times, class names on cards (family 'Anton'). Always uppercase.: Anton (OFL), weights 400
- Text: body, nav, buttons, chips, form fields, FAQ, quotes (family 'Archivo Variable'; buttons and chips use weight 600–700 with uppercase and 0.06em tracking).: Archivo (OFL), weights 400/500/600/700
- Mono: eyebrows, section indexes, stat labels, spots and capacity labels, per-class math, references, hours table (family 'JetBrains Mono', uppercase, letter-spacing 0.1em).: JetBrains Mono (OFL), weights 400/500
### Layout & motion
- Spacing: 8px base unit. Container max-width 1360px, centered, side padding 40px desktop, 24px tablet, 20px under 640px. 12-column grid, gap 24px. Section padding 128px vertical desktop, 88px under 768px. SectionHead on every home section: a 1px Slab rule across the container, 20px below it a row with the mono index ('01', JetBrains Mono 500 13px, Signal), the mono label next to it ('CLASSES', 13px, letter-spacing 0.16em, Chalk 70%) and an optional right-aligned mono count; H2 margin-top 40px. Type scale: H1 Anton clamp(76px, 13.5vw, 212px) line-height 0.86; H2 Anton clamp(52px, 7.4vw, 120px) line-height 0.9, each H2 written as two lines with the second in Signal; H3 Anton 40px line-height 1; body Archivo 400 17px/1.6 (16px under 768px), max-width 62ch; lead Archivo 400 20px/1.5 Chalk 85%. Buttons: height 54px, padding 0 26px, Archivo 700 14px uppercase, letter-spacing 0.08em; primary = Signal background, Ink text, on hover background Signal Deep, transform translate(-2px, -2px) and a hard Chalk shadow box-shadow 4px 4px 0 #EDEAE3, back to no shadow and no offset when pressed; secondary = 1.5px Chalk border, Chalk text, hover Chalk background with Ink text. Focus ring everywhere: 2px Signal outline, offset 3px.
- Radius: Square corners everywhere (0): cards, photos, buttons, chips, inputs, drawer, tickets. The only round shapes are the pulsing status dots and the map marker ring.
- Motion: Easings: --ease-cut cubic-bezier(0.7, 0, 0.2, 1) for masks, wipes and swaps (fast, hard); --ease-out cubic-bezier(0.2, 0.7, 0.2, 1) for photo scales.
- Motion: Headline mask (Mask.tsx): H1 and H2 lines are arrays in content.ts; each line sits in an overflow-hidden block and its inner span rises from translateY(110%) to 0 over 700ms --ease-cut with 80ms stagger, triggered when the heading is 30% visible (Motion whileInView, once).
- Motion: Hard-cut reveal (.reveal-cut / Reveal.tsx): blocks start with clip-path inset(0 0 100% 0) and translateY(24px) and animate to inset(0) and 0 over 560ms --ease-cut when 15% visible, once; lists stagger by index.
- Motion: Stripe wipe: every Tape divider wipes in from the left on enter (clip-path inset(0 100% 0 0) → inset(0), 600ms --ease-cut).
- Motion: Counter ticks (useCountUp, requestAnimationFrame): numeric parts of hero stats and results go from 0 to their value over 1200ms with easeOutExpo when 40% visible, once; thousands separators and suffixes ('%', '+') stay in place; tabular-nums so widths do not jump.
- Motion: Marquee: linear translateX(0 → -50%) in 38s, pause on hover.
- Motion: Photo hover: scale 1 → 1.05 over 500ms --ease-out inside an overflow-hidden frame. Hero photo scales 1.08 → 1 on load.
- Motion: Pricing roll, drawer spring, tab slide and ticket drop exactly as described in their sections.
- Motion: prefers-reduced-motion: reduce — disable masks, reveals, wipes, parallax, photo scales, marquee, pulses and counters (show final values); drawer and menus open instantly with a 120ms fade; smooth scrolling off. Color hover changes stay.
- Imagery: Real photographs only, never illustrations: six photos made for this template (warehouse gym floor with heavy bags, ring corner, hand wraps, swinging heavy bag, kettlebells with chalk, gloves and rope on a bench) plus two public-domain / CC0 photos (vintage black boxing gloves, coiled heavy ropes). No faces, no people beyond hands and forearms, no brand names. Every photo gets the same duotone grade through DuoPhoto.tsx: a wrapper with background #0C0C0B; the img has filter grayscale(1) contrast(1.3) brightness(0.78); a ::before overlay with background #FF4F1F, mix-blend-mode color, opacity 0.22 (Signal tint in the mid-tones); and the .grain overlay: an inline SVG data-URI with feTurbulence (type fractalNoise, baseFrequency 0.85, numOctaves 3, stitchTiles stitch) at 220px tile size, mix-blend-mode overlay, opacity 0.22, pointer-events none. object-fit cover everywhere. Coaches are typographic monograms. Everything else is drawn in code: tape stripes (repeating-linear-gradient -45deg Signal/Ink 16px), ticket notches, intensity bars, the CSS ✕ separators, the block map and the favicon. Icons from lucide-react only, stroke-width 2, sizes 14–20px.
## Pages & Sections
### Home (`index.html`)
1. **Content file and copy rules** — All copy below is written for the default values ([BRAND] = Cinderblock Boxing Club, [DISCIPLINE] = boxing and strength training, [CITY] = Oakland, California, [AUDIENCE] = busy adults, from first-timers to amateur fighters). Put every string, number, list, class, session, coach, price, quote, FAQ answer, image path and alt text for both pages in src/content.ts (typed) and render from it. If any variable value differs, rewrite every headline, class type, session name, coach speciality, price note, result, quote and FAQ answer so it is specific and believable for the new gym and discipline, keeping the same structure, counts and approximate lengths; keep the six class types, 34 weekly sessions, three plans, six coaches, three quotes and eight FAQs. Two-letter reference prefix = the initials of the first two words of [BRAND] (CB). The wordmark is [BRAND] set in type, never a drawn logo. All people, numbers and streets are invented. Address: 2210 Foundry Row, Oakland, CA 94607 (corner of Foundry Row and Tallow St). Phone (510) 555-0172. Email [email protected]. Hours (staffed front desk): Mon–Thu 05:45–21:30, Fri 05:45–19:30, Sat 08:30–13:00, Sun 08:30–11:30. Time zone America/Los_Angeles. Six class types (id · name · level · intensity 1–5 · duration · one-line description · what to bring), used on both pages: 'fundamentals' · Fundamentals · Beginner · 2 · 60 min · 'Stance, guard, footwork and the six basic punches, taught in small groups. Where everyone starts.' · 'Water, a towel, hand wraps (we lend them for your first month).'; 'bag-rounds' · Bag Rounds · Open to all · 4 · 45 min · 'Twelve three-minute rounds on the heavy bags with combinations called from the floor. Loud, sweaty, scalable.' · 'Wraps and 12–16 oz gloves (loaners on the wall).'; 'pad-lab' · Pad Lab · Intermediate · 3 · 60 min · 'Partner pad work: you hold, you hit, a coach corrects every round. Sharpens timing and defence.' · 'Wraps, gloves, and patience with your partner.'; 'sparring' · Sparring · Advanced · 5 · 75 min (Saturday 90 min) · 'Controlled, coach-matched technical sparring for members cleared by a coach. Headgear and mouthguard required.' · 'Mouthguard, headgear, 16 oz sparring gloves, coach sign-off.'; 'iron-hour' · Iron Hour · Open to all · 3 · 60 min · 'Barbell and kettlebell strength in small squads: squat, hinge, press, carry. Programmed in six-week blocks.' · 'Flat shoes or bare feet, a notebook if you like logging lifts.'; 'engine' · Engine · Open to all · 5 · 45 min · 'Conditioning circuits with ropes, sleds, skipping and bodyweight. Built to make round three feel like round one.' · 'Water. Lots of it.'. Levels in order: Beginner, Open to all, Intermediate, Advanced. Capacity: 16 per session (Sparring 12). Weekly schedule (day · time · class id · coach id · booked/capacity), exactly 34 sessions; render both the home 'Today' strip and the timetable page from it: Mon: 06:00 engine km 11/16; 07:15 fundamentals mr 9/16; 12:15 bag-rounds pa 14/16; 17:30 fundamentals mr 16/16 (full); 18:45 pad-lab tw 12/16; 20:00 iron-hour do 8/16. Tue: 06:00 iron-hour do 10/16; 07:15 bag-rounds pa 13/16; 12:15 engine km 7/16; 17:30 bag-rounds km 15/16; 18:45 sparring tw 10/12; 20:00 fundamentals ic 11/16. Wed: 06:00 engine km 12/16; 07:15 fundamentals ic 6/16; 12:15 iron-hour do 9/16; 17:30 fundamentals mr 14/16; 18:45 pad-lab pa 13/16; 20:00 bag-rounds km 9/16. Thu: 06:00 iron-hour do 11/16; 07:15 bag-rounds pa 10/16; 12:15 engine km 8/16; 17:30 pad-lab tw 15/16; 18:45 sparring tw 12/12 (full); 20:00 engine km 6/16. Fri: 06:00 engine km 9/16; 07:15 fundamentals ic 8/16; 12:15 bag-rounds pa 12/16; 17:30 iron-hour do 13/16; 18:45 bag-rounds mr 16/16 (full). Sat: 09:00 fundamentals mr 15/16; 10:15 pad-lab pa 11/16; 11:30 sparring tw 9/12 (90 min, 'Open sparring'). Sun: 09:00 iron-hour do 7/16; 10:15 bag-rounds ic 10/16. Session id = '<day>-<time without colon>' (e.g. 'mon-1745'). End time = start + duration. Spots left = capacity − booked; 'Full · join waitlist' when 0, 'Last 2 spots' when ≤ 2. Six coaches (id · name · role · specialities · years coaching · one-line bio): mr · Marco Ruelas · Head coach · Footwork, Fundamentals, Southpaw stance · 14 · 'Built the beginner programme. Will fix your feet before he lets you hit anything.'; pa · Priya Anand · Coach · Bag Rounds, Pad work, Combinations · 8 · 'Calls the loudest rounds in the building and remembers everyone's favourite combo.'; tw · Tomasz Wierzba · Ring coach · Sparring, Defence, Ring craft · 11 · 'Runs every sparring session and matches partners by weight, level and temperament.'; do · Dee Okafor · Strength lead · Barbell, Kettlebells, Mobility · 9 · 'Writes the six-week Iron Hour blocks. Former competitive powerlifter, permanent chalk on her hands.'; km · Kofi Mensah · Conditioning coach · Engine, Ropes & sleds, Recovery · 6 · 'Designs Engine so the last round hurts less every week. Keeps the rope wall tidy.'; ic · Inés Calderón · Coach · Fundamentals, Technique, Early-morning classes · 5 · 'Teaches the 07:15 crowd and the Tuesday night beginners. Patient, precise, relentless about guard.'. Monogram = first letter of first name + first letter of last name.
Layout: No visual output. src/content.ts exports typed objects: gym, nav, hero, marquee, classTypes, sessions, coaches, plans, results, quotes, trial, faq, visit, footer, images. src/lib/schedule.ts exports helpers: sessionsFor(day), endTime(session), spotsLabel(session), todayKey() (Mon–Sun from the visitor's local clock, 'mon'…'sun').
2. **Header** — Wordmark left: a 14px Signal square followed by [BRAND] in Anton 22px uppercase, letter-spacing 0.02em, Chalk; links to ./index.html. Nav center-right in Archivo 600 14px uppercase, letter-spacing 0.08em: 'Classes' (./index.html#classes), 'Timetable' (./timetable.html), 'Pricing' (./index.html#pricing), 'Coaches' (./index.html#coaches), 'FAQ' (./index.html#faq). Right: primary button 'Free trial' (./index.html#trial). On timetable.html the 'Timetable' link is Signal with a 2px Signal underline. Under 1024px: wordmark + 'Free trial' small button + a 44px menu button (Lucide Menu / X). The mobile menu is a full-screen Ink panel with the five links in Anton 48px uppercase stacked, each preceded by its mono index '01'–'05' in Signal, then the address and phone in JetBrains Mono 13px Dust at the bottom; Escape and link clicks close it, body scroll is locked while open.
Layout: Fixed top, height 72px (64px under 768px), full width, container inside, flex space-between, z-index 40. Transparent over the hero; after 40px of scroll it gets background Ink at 92% opacity, backdrop-filter blur(10px) and a 1px Slab bottom border. On timetable.html it is always in the scrolled state.
Motion: Background/border transition 200ms. Mobile menu: clip-path inset(0 0 100% 0) → inset(0) over 420ms --ease-cut, links rise 24px with 50ms stagger. Nav links: 2px Signal underline scales from scaleX(0) to 1 (origin left) over 180ms on hover.
3. **Hero (#top)** — Background photo gym-floor.webp with the .duo treatment and the .grain overlay, plus a left-to-right gradient from Ink 88% to Ink 10% so the text reads. Eyebrow (JetBrains Mono 13px uppercase, letter-spacing 0.16em, Signal): '● [CITY] · BOXING & STRENGTH · EST. 2017' (keep the city upper-cased, render the dot as an 8px Signal square). H1 in three lines: 'THROW' / 'THE FIRST' / 'PUNCH.' — the word 'PUNCH.' in Signal. Lead (Archivo 400 20px, Chalk 85%, max-width 46ch): 'Boxing and strength classes for busy adults in Oakland. Sixteen people per class, a coach on every round, and your first session is free.' Two buttons: primary 'Book trial' (Lucide ArrowRight 18px after the text) → #trial; secondary outline 'See timetable' → ./timetable.html. Below, a stat row of four items separated by 1px Slab verticals, each a number in Anton 44px and a label in JetBrains Mono 12px uppercase Dust: '34' 'classes a week'; '16' 'max per class'; '06' 'coaches'; '1st' 'class free'. Bottom-right corner: a small mono badge computed from the schedule: the next session today that has not started and has spots left, e.g. 'TONIGHT 18:45 · PAD LAB · 4 SPOTS' ('TONIGHT' from 17:00, otherwise 'TODAY'); if none is left today, the first session of the next day with spots, e.g. 'TOMORROW 06:00 · IRON HOUR · 6 SPOTS'.
Layout: min-height 100svh (min 680px), content bottom-aligned with padding-bottom 72px, container 1360px, side padding 40px (20px under 640px). 12-column grid: text occupies columns 1–9. H1 Anton clamp(76px, 13.5vw, 212px), line-height 0.86, uppercase, letter-spacing -0.01em, Chalk. Lead margin-top 28px, buttons margin-top 36px with 12px gap, stat row margin-top 56px (2×2 grid under 640px). The badge sits absolutely at bottom 72px right 40px on ≥1024px, hidden below. Photo object-position 35% 50%.
Motion: On load: photo scales 1.08 → 1 over 1800ms --ease-out; each H1 line is masked (overflow hidden) and slides up from translateY(110%) with 80ms stagger, 700ms --ease-cut, starting at 150ms; eyebrow, lead, buttons and stats fade-up 16px with 60ms stagger after the headline. Stat numbers tick from 0 (see Design motion). The badge's dot pulses (opacity 1 → 0.35, 1.2s infinite).
4. **Tape divider and class marquee** — A tape-stripe band, then a marquee of the six class names plus 'Free trial', 'Gloves on loan', '16 max' in Anton 56px uppercase, alternating filled Chalk and outlined (color transparent, -webkit-text-stroke 1.5px Chalk at 60%), separated by a 20px Signal '✕' drawn as two rotated bars in CSS (not a glyph).
Layout: Tape band: height 18px, full width, background repeating-linear-gradient(-45deg, #FF4F1F 0 16px, #0C0C0B 16px 32px), rotated -1.5deg and scaled 1.04 on the x axis so the edges never show, margin -9px 0. Marquee: Coal background, padding 22px 0, overflow hidden, one track containing the list twice, gap 40px, aria-hidden, with a visually-hidden plain list of class names for screen readers.
Motion: Track translates -50% linearly in 38s, infinite; hover pauses it (animation-play-state). Reduced motion: no animation, the list wraps centered on two lines.
5. **Classes (#classes)** — SectionHead: mono index '01' in Signal, label 'CLASSES', right count '6 TYPES · 34 SESSIONS / WEEK'. H2 'SIX WAYS' / 'TO EMPTY THE TANK.' (second line in Signal). Lead 'Every class is capped, coached and scaled. Start at Fundamentals, add what you like, come as often as your plan allows.' Six cards in classTypes order, each: photo (fundamentals → hand-wraps.webp, bag-rounds → heavy-bag.webp, pad-lab → bench-gloves.webp, sparring → ring-corner.webp, iron-hour → kettlebells.webp, engine → heavy-ropes.jpg) with .duo + .grain; on the photo top-left a mono tag '0n / 06'; below: name in Anton 34px uppercase; a row of chips: level chip and duration chip ('60 MIN'); an intensity meter: label 'INTENSITY' in mono 11px Dust and five 18×6px bars, filled bars Signal, empty bars Slab; the one-line description in Archivo 16px Chalk 80%; footer row with the weekly count computed from the schedule ('7 SESSIONS / WEEK' for Fundamentals) left and a text link 'Times →' right to ./timetable.html?type=<id>.
Layout: Ink background, section padding 128px 0 (88px under 768px). SectionHead as in Design spacing. H2 and lead in a two-column header: H2 columns 1–7, lead columns 9–12 bottom-aligned. Cards grid margin-top 64px: 3 columns gap 20px (2 under 1024px, 1 under 640px). Card: Coal background, 1px Slab border, square corners; photo aspect 4/3 (object-fit cover); body padding 24px; chips row margin 16px 0; description min-height 3 lines on desktop. Whole card is not a link; only 'Times →' is.
Motion: Cards reveal with the hard-cut reveal (Design motion) in 80ms stagger by index. Hover: border turns Signal, photo scales 1.05 inside its frame over 500ms --ease-out, the '→' moves 4px right, and a 4px Signal bar grows from the left along the card's top edge (scaleX 0 → 1, 260ms).
6. **Today on the floor** — A compact strip that makes the gym feel live. Left: label 'TODAY · <weekday in full, uppercase>' in mono 13px Signal and H3 'ON THE FLOOR' in Anton 40px. Right: horizontally scrolling row of today's sessions from the schedule (todayKey()), each a ticket: time '17:30' in Anton 32px, class name in Archivo 600 16px, coach name in 13px Dust, spots label in mono 12px ('7 SPOTS', 'LAST 2 SPOTS' in Signal, 'FULL' in Dust with line-through on the time). Sessions that already ended today (by local time) are shown at 40% opacity with 'DONE'. Last item is a link tile 'FULL WEEK →' to ./timetable.html. If today has no sessions (never with the default data), show 'REST DAY. BACK TOMORROW 06:00.'
Layout: Concrete background with a 1px Slab top and bottom border, padding 40px 0. Container flex: left block fixed 280px, right block flex 1 with overflow-x auto, scroll-snap-type x mandatory, gap 12px, thin custom scrollbar. Ticket: width 200px, padding 18px, Ink background, 1px Slab border, a 6px-wide dashed vertical perforation on the left edge drawn with a repeating-linear-gradient of Concrete and transparent. Under 768px the left block stacks above the row.
Motion: Tickets reveal with 50ms stagger. Hover lifts the ticket by -4px and swaps its border to Signal over 160ms.
7. **Pricing (#pricing)** — SectionHead '02' 'MEMBERSHIP' right 'NO JOINING FEE'. H2 'PICK YOUR' / 'ROUND COUNT.' (second line Signal). Segmented toggle centered: 'Monthly' / '10-class pack' (role radiogroup, arrow keys switch). Three plan cards (name · monthly price · monthly allowance · weekly visits used for the per-class math · pack price · pack terms · features): 'Off-peak' · $99/month · unlimited classes before 16:00 and at weekends · 3 · $120 · 10 off-peak classes, use within 3 months · ['Every class type before 16:00', 'Weekend classes', 'Gloves and wraps on loan for month one', 'Freeze up to 4 weeks a year']; 'Anytime' (featured, tag 'MOST PICKED') · $149/month · unlimited classes, any time · 6 · $160 · 10 classes, any time, use within 3 months · ['All 34 weekly sessions', 'Sparring once cleared by a coach', 'Bring a friend free once a month', 'Freeze up to 4 weeks a year']; 'Anytime + Coaching' · $259/month · unlimited classes plus two 30-minute private sessions a month · 6 · $240 · 10 classes plus two 30-minute private sessions, use within 3 months · ['Everything in Anytime', 'Two private sessions with any coach', 'A written six-week strength block', 'Priority booking on full classes']. Each card shows: plan name (Anton 28px uppercase), price (Anton 72px) with suffix ('/MONTH' or '/ 10 CLASSES' in mono 13px Dust), the allowance line, then the per-class math line in mono 13px with a Signal '=' prefix: monthly → '= $8.25 per class at 3 classes a week' computed as price / (weeklyVisits × 4), rounded to cents (99/12 = 8.25, 149/24 = 6.21, 259/24 = 10.79); pack → '= $12.00 per class, never auto-renews' computed as price / 10 (12.00, 16.00, 24.00). Then the feature list with Lucide Check 16px Signal, and a button 'Start free' (primary on the featured card, outline on the others) linking to #trial with ?plan=<id> appended before the hash (./index.html?plan=anytime#trial). Footnote under the cards in 14px Dust: 'Monthly plans roll month to month: cancel with 30 days' notice at the desk or by email. Packs never auto-renew. Students and first responders get 15% off monthly plans with ID.'
Layout: Coal background, padding 128px 0. Toggle margin 48px auto 0, width 360px (100% under 480px), height 52px, 1px Slab border, two equal cells, a Signal thumb (absolutely positioned, 50% width) sits behind the active label; active label Ink on Signal, inactive Chalk 70%. Cards grid margin-top 48px: 3 columns gap 20px, align-items stretch (stacked under 1024px, featured first on mobile). Card: Ink background, 1px Slab border, padding 32px, flex column, button pushed to the bottom (margin-top auto, full width). Featured card: 2px Signal border, translateY(-12px) on desktop, and the 'MOST PICKED' tag as a Signal label hanging from the top edge (top -14px, left 32px, mono 12px Ink on Signal, padding 4px 10px). Footnote margin-top 32px, centered, max-width 70ch.
Motion: Toggle thumb slides between cells with a spring (stiffness 500, damping 38). On switch, each price, suffix and per-class line swaps with a vertical roll: old value exits up (y -100%, opacity 0), new value enters from below (y 100% → 0), 220ms --ease-cut, inside an overflow-hidden box, using AnimatePresence mode 'popLayout'. Cards reveal with 80ms stagger.
8. **Coaches (#coaches)** — SectionHead '03' 'COACHES' right '6 COACHES · 53 YEARS COMBINED' (sum computed from content). H2 'CORNERED BY' / 'PEOPLE WHO CARE.' (second line Signal). Six coach cards from content: monogram tile (initials in Anton 88px, Chalk on Concrete, with a diagonal 10px tape-stripe ribbon across the bottom-right corner of the tile), role in mono 12px uppercase Signal, name in Anton 30px uppercase, '14 YEARS COACHING' in mono 12px Dust, speciality tags as outline chips, bio in Archivo 15px Chalk 80%, and 'Teaches:' followed by the class names this coach leads, derived from the schedule (e.g. 'Fundamentals · Bag Rounds').
Layout: Ink background, padding 128px 0. Grid 3 columns gap 20px (2 under 1024px, 1 under 640px). Card: 1px Slab border, no background; monogram tile aspect 1/1, full card width, grain overlay at 10%; body padding 24px. No photos or AI portraits of coaches.
Motion: Reveal stagger 70ms. Hover: monogram tile background goes Concrete → Signal and initials Chalk → Ink over 220ms; the tape ribbon slides 12px along its diagonal.
9. **Results and member quotes (#results)** — Full-bleed band with ring-corner.webp in .duo at 35% opacity behind an Ink overlay. SectionHead '04' 'RESULTS'. Four counters in a row (number Anton clamp(64px, 8vw, 120px) Chalk, label mono 12px uppercase Dust, a 32×4px Signal bar above each number): '412' 'active members'; '86%' 'still training after 6 months'; '11,600+' 'classes coached since 2017'; '38' 'members' first amateur bouts'. Below, three quote cards (quote in Archivo 500 20px Chalk, opening mark as a 40px Signal Anton '“', then the result fact in mono 13px Signal, then name and context in 14px Dust): 'I walked in not knowing how to make a fist. Fourteen months later I box three nights a week and sleep like a stone.' · 'DOWN 9 KG · 3 CLASSES / WEEK' · 'Renata V. — member since 2023, accountant'; 'Iron Hour fixed my back and my deadlift in the same year. Dee writes the blocks, I just show up and do them.' · 'DEADLIFT 60 → 115 KG IN 9 MONTHS' · 'Jonah P. — member since 2022, line cook'; 'Tomasz never let me spar until I was ready, and then he matched me perfectly. My first bout felt like a Saturday class.' · 'FIRST AMATEUR BOUT · WON ON POINTS' · 'Lucía M. — member since 2021, nurse'. Small print under the quotes in 13px Dust: 'Results from real routines of individual members; yours depend on how often you train and what you eat.'
Layout: Relative section, padding 136px 0, background photo absolutely positioned cover with an Ink 80% overlay and grain. Counters: 4-column grid gap 24px (2×2 under 1024px). Quotes margin-top 80px: 3 columns gap 20px (stacked under 1024px), each card 1px Slab border, Ink 70% background, padding 32px.
Motion: Counters tick when 40% visible (Design motion); the Signal bars wipe in from the left (scaleX 0 → 1, 500ms) just before. Quote cards reveal with 90ms stagger.
10. **Free trial form (#trial)** — SectionHead '05' 'FREE TRIAL' right '1 CLASS · NO CARD'. Left column: H2 'YOUR FIRST' / 'ROUND IS ON US.' (second line Signal); lead 'Pick a class, tell us a little about you and show up 15 minutes early. A coach meets you at the desk, wraps your hands and stays with you all session.'; checklist with Lucide Check 16px Signal: 'Gloves and wraps on loan', 'Showers, lockers and towels', 'No contract, no card, no hard sell'; photo bench-gloves.webp (.duo, aspect 1/1). Right column: the form card titled 'BOOK YOUR TRIAL' (Anton 32px). Fields, in order: First name (required, error 'Tell us what to call you.'); Last name (required, error 'Add your last name.'); Email (required, regex /^[^\s@]+@[^\s@]+\.[^\s@]+$/, error 'Enter an email like [email protected].'); Phone (optional, if filled must contain at least 10 digits, error 'Add a number with at least 10 digits, or leave it empty.'); 'Which class?' select listing every non-full session grouped by day in <optgroup>s, option text 'Mon 17:30 · Fundamentals · Marco' (full sessions omitted), required, error 'Pick a class for your trial.'; 'Boxing experience' radio tiles 'Never thrown a punch' / 'A few classes' / 'I've competed', required, error 'Choose the closest one.' — if 'Never thrown a punch' and the chosen session is Sparring or Pad Lab, show an inline Signal-bordered note: 'Most first-timers start at Fundamentals. You can keep this class, but we'd suggest Wed 17:30 or Sat 09:00.' (the two times are the next two Fundamentals sessions that are not full, counted from now); 'Injuries or anything we should know?' textarea (optional, max 300 chars, live counter '0 / 300'); checkbox 'I'm 16 or older and will fill in a short health form at the desk.' (required, error 'Please confirm to continue.'). Prefill: if the URL has ?class=<session id>, preselect it; if it has ?plan=<id>, show a small line above the submit button 'Interested in: Anytime' (plan name). Submit button full width 'Book my free class'. Validation: on blur per field once touched, and all fields on submit; on submit with errors, focus the first invalid field and show a summary line at the top in Signal: 'Fix 2 fields to book your class.' (count). On valid submit: button text 'Booking…' with a 14px spinner for 800ms, then the form card is replaced by a ticket-style success panel: mono 'TRIAL CONFIRMED' in Signal, H3 'YOU'RE IN, {FIRST NAME}.' (uppercase), a row of three fields 'CLASS' (e.g. 'Fundamentals'), 'WHEN' ('Mon 17:30–18:30'), 'COACH' ('Marco Ruelas'), the reference 'CB-' + 4 random digits in JetBrains Mono 20px, text 'Arrive 15 minutes early at 2210 Foundry Row. Bring water and wear trainers; we'll handle the rest. A confirmation is on its way to {email}.' Nothing is actually sent. Button 'Book another' resets the form.
Layout: Concrete background, padding 128px 0. 12-column grid: left columns 1–5 (H2, lead margin-top 24px, checklist margin-top 28px gap 12px, photo margin-top 40px, hidden under 1024px); right columns 7–12 the card: Ink background, 1px Slab border, padding 40px (24px under 640px). Two-column row for first/last name (stack under 640px). Labels Archivo 600 13px uppercase letter-spacing 0.06em Chalk 80% above inputs; inputs height 52px, background Coal, 1px Slab border, Chalk text 16px, padding 0 16px, square; focus: border Signal plus 2px Signal outline offset 2px; invalid: border Signal, message 13px Signal with a Lucide AlertTriangle 14px, role alert, aria-invalid and aria-describedby set. Radio tiles: 3 equal columns (stacked under 640px), selected tile Signal border 2px and Chalk text, others Dust text. Ticket success panel: same card size, a dashed 1px Slab line separating the header and the fields row, two 24px half-circle notches cut into the left and right edges at the dashed line (radial-gradient masks), min-height equal to the form's so the page does not jump. Real <form noValidate> with onSubmit preventDefault; nothing is sent or stored.
Motion: Error messages slide down (height auto, 180ms). Success: the form fades out (150ms), the ticket drops in from y -24px with a slight rotate(-1.5deg) → 0 and opacity 0 → 1 over 420ms --ease-cut, then a 6px Signal tape stripe wipes across its top edge (scaleX 0 → 1, 360ms).
11. **Beginner FAQ (#faq)** — SectionHead '06' 'FIRST-TIMER FAQ'. Left: H2 'ASK BEFORE' / 'YOU ASK.' (second line Signal) and the public-domain photo black-gloves.jpg (.duo, aspect 4/5, object-position 30% 50%) with a mono caption 'Loaner gloves live on the wall by the desk.'. Right: eight accordion items: 'I've never boxed. Am I too unfit to start?' → 'No. Fundamentals is built for day one: technique first, conditioning second, and every drill has an easier version. Most members came in unable to finish a three-minute round.'; 'Will I get hit?' → 'Not unless you choose to. Fundamentals, Bag Rounds, Pad Lab, Iron Hour and Engine are no-contact. Sparring is optional and only open once a coach has cleared you, usually after three to six months.'; 'What should I wear and bring?' → 'Trainers or bare feet, clothes you can move in, water and a towel. We lend gloves and wraps for your first month; after that, 12–16 oz gloves cost around $60.'; 'How big are the classes?' → 'Sixteen people maximum, twelve for Sparring, with at least one coach on the floor and two in Fundamentals on weekday evenings.'; 'I'm 47. Is this a young person's gym?' → 'Our members range from 16 to 68. The median age is 34, and the 07:15 crowd skews older and very punctual.'; 'How often should a beginner train?' → 'Two or three times a week: two Fundamentals and one Iron Hour or Engine is the mix we recommend for the first eight weeks.'; 'Can I freeze or cancel?' → 'Monthly plans can be frozen for up to four weeks a year and cancelled with 30 days' notice. Packs never auto-renew and last three months.'; 'Is there parking?' → 'Twelve free spots in the yard off Tallow Street, a covered bike rack inside the door, and the 14 bus stops at Foundry Row and 3rd.'.
Layout: Ink background, padding 128px 0. Grid: left columns 1–5 sticky at top 104px on ≥1024px (H2 then photo margin-top 40px), right columns 7–12 the accordion. Item: 1px Slab bottom border, question button full width, padding 24px 0, Archivo 600 19px Chalk, a 28px square toggle at the right with a CSS plus (two 2px bars) that turns Signal when open; answer Archivo 16px/1.65 Chalk 80%, padding 0 48px 24px 0. One item open at a time; first item open by default. Buttons use aria-expanded and aria-controls.
Motion: Answer height animates 0 → auto with opacity over 280ms --ease-cut (AnimatePresence); the plus rotates 45° into an X.
12. **Visit band** — Three columns: 'FIND US' — '2210 Foundry Row', 'Oakland, CA 94607', 'Entrance on Tallow St, roller door with the orange stripe'; 'DESK HOURS' — the four hour lines from content in a two-column mono table, today's row in Signal with a small 'TODAY' tag, plus a live line 'Open now · until 21:30' or 'Closed · opens Sat 08:30' computed in America/Los_Angeles; 'CALL OR WRITE' — '(510) 555-0172' (tel: link) and '[email protected]' (mailto: link). Right of them a code-drawn SVG block map: a dark grid of 8 street rectangles in Concrete on Coal, street labels 'FOUNDRY ROW', 'TALLOW ST', '3RD ST' in mono 10px Dust, the gym's block filled with the tape-stripe pattern and a 12px Signal square marker with the label 'YOU'RE HERE SOON'.
Layout: Coal background, 1px Slab top border, padding 88px 0. Grid 12 columns: three info columns spanning 1–7 (each with a mono 12px Signal heading), map spanning 8–12 with aspect 4/3 and a 1px Slab border. Under 1024px everything stacks, map last.
Motion: Map marker pulses (scale 1 → 1.6, opacity 0.6 → 0, 1.6s infinite) on a ring behind the square.
13. **Footer** — Tape-stripe band on top. Huge wordmark [BRAND] uppercase in Anton clamp(64px, 12vw, 200px), line-height 0.85, Chalk, the last word in Signal, breaking naturally. Four columns: 'TRAIN' (the six class names → ./timetable.html?type=<id>); 'CLUB' (Pricing, Coaches, Results, FAQ → ./index.html#<id>); 'VISIT' (address lines and phone); 'HOURS' (four lines). Bottom row: '© 2017–present [BRAND]. Train hard, tap gloves, put the weights back.' left; right, if [CREDIT] is kept, the small credit link in mono 11px Dust.
Layout: Ink background, padding 72px 0 32px. Wordmark margin-bottom 56px with a 1px Slab rule under it. 4-column grid gap 32px (2 under 1024px, 1 under 640px); column headings mono 12px Signal, items Archivo 15px Chalk 80%. Bottom row margin-top 56px, padding-top 20px, 1px Slab top border, flex space-between, stacked under 640px.
Motion: Wordmark lines rise with the headline mask when they enter.
### Timetable (`timetable.html`)
1. **Header** — Same Header component as index.html, always in the scrolled state, 'Timetable' active.
Layout: Fixed, 72px. Page content starts with padding-top 72px.
2. **Masthead** — Eyebrow 'TIMETABLE · 34 SESSIONS · MON–SUN' in mono 13px Signal (count computed from the schedule). H1 'THE WEEK.' Lead: '34 classes, six types, sixteen people max. Tap any class for the details, then book it as your free trial.' Right side: a vertical legend of the six class types with a two-letter code each in a 28px Concrete square (FU, BR, PL, SP, IH, EN) and the intensity bars, so the codes used in compact grid cells are explained. Below the masthead, a full-width band with heavy-ropes.jpg (.duo, aspect 16/4 on desktop, 16/7 on mobile, object-position 50% 40%) and the tape stripe under it.
Layout: Ink background, padding 64px 0 48px. Grid: text columns 1–7 (H1 Anton clamp(72px, 12vw, 180px), line-height 0.86), legend columns 9–12 as a 2×3 grid on desktop, a horizontal scrolling row under 1024px. Photo band full width below, margin-top 48px.
Motion: H1 headline mask on load; legend items fade in with 40ms stagger; band photo parallax: the image moves from 0 to -8% of its height across the band's scroll progress (Motion useScroll + useTransform).
3. **Filter bar** — Two chip groups. 'CLASS' (mono 11px Dust label): 'All', then the six class names. 'LEVEL': 'Any level', 'Beginner', 'Open to all', 'Intermediate', 'Advanced'. Class chips are multi-select (selecting any deselects 'All'; deselecting the last one reselects 'All'); level is single-select. Right side: live count 'SHOWING 12 OF 34' in mono 13px (aria-live polite) and a text button 'Clear' (Lucide X 14px) shown only when a filter is active. Filters sync to the URL query (?type=bag-rounds,engine&level=beginner) with history.replaceState, and are read from it on load, so links from the home page land pre-filtered.
Layout: Sticky at top 72px, z-index 30, Ink at 95% with backdrop-filter blur(8px), 1px Slab bottom border, padding 16px 0. Container flex wrap gap 12px 32px. Chip: height 36px, padding 0 14px, 1px Slab border, Archivo 600 13px uppercase letter-spacing 0.06em, Chalk 75%; selected chip Signal background, Ink text, Signal border; buttons with aria-pressed. Under 768px each group is a single horizontally scrolling row (overflow-x auto, no wrap) and the count moves under them.
Motion: Chip background and color transition 140ms; a pressed chip scales to 0.96 for 80ms. The count number rolls like the pricing prices (220ms).
4. **Weekly grid (desktop ≥1024px)** — Eight columns: a time column and Mon–Sun. Column headers: day short name in Anton 28px ('MON') and the session count in mono 11px Dust ('6 CLASSES'); today's column header is Signal with a 3px Signal underline and the label 'TODAY', and today's column body has a Coal background. Rows: the nine distinct start times in the schedule, sorted (06:00, 07:15, 09:00, 10:15, 11:30, 12:15, 17:30, 18:45, 20:00), each row labelled in mono 13px Dust. Each session cell is a button: two-letter code chip, class name Archivo 700 15px uppercase, '17:30–18:30' in mono 12px, coach first name in 13px Dust, and the spots label at the bottom ('7 SPOTS' Chalk 70%, 'LAST 2 SPOTS' Signal, 'FULL · WAITLIST' Dust). Empty slots are a faint 1px dashed Slab cell with no content. Sessions that do not match the filters stay in place at 18% opacity, lose their hover effect and become non-focusable (tabIndex -1, aria-hidden). Sessions earlier today than the current local time show 'DONE' instead of spots.
Layout: Container 1360px. CSS grid: grid-template-columns 72px repeat(7, minmax(0, 1fr)), gap 6px. Session cell min-height 112px, padding 12px, Concrete background, 1px Slab border, square, a 3px left border in Signal for Beginner sessions only (marks beginner-friendly). Full sessions get Coal background and Dust text.
Motion: Grid cells reveal in reading order with 12ms stagger on first paint. Filter changes animate opacity (200ms) without layout shift. Hover/focus on a matching cell: border Signal, translateY(-2px), 140ms.
5. **Day list (mobile and tablet <1024px)** — Seven day tabs 'MON'…'SUN' with today's tab selected on load (role tablist, arrow keys move between tabs). Below, the selected day's matching sessions as full-width rows: time block left (start time Anton 30px, end time mono 12px Dust), then class name, level chip, coach and spots, and a Lucide ChevronRight 18px at the right. Each tab shows a small count of matching sessions for that day ('MON 4'). If a day has no matching sessions: 'No classes match on Thursday.' plus a button 'Clear filters'.
Layout: Tabs: a 7-column grid sticky under the filter bar, height 56px, Anton 18px; selected tab Signal text with a 3px Signal bottom border. Rows: padding 16px 0, 1px Slab bottom border, grid-template-columns 76px 1fr 18px, gap 16px. Non-matching sessions are removed from the list (not dimmed) on mobile.
Motion: Switching tabs: the list crossfades and slides 16px in the tab direction over 220ms (AnimatePresence mode 'wait').
6. **Class detail drawer** — Opening any session (cell, row or a link with ?class=<session id> on load) opens a drawer showing: top bar with the two-letter code chip, the day and time 'MONDAY · 17:30–18:30' in mono 13px Signal and a close button (Lucide X 20px, aria-label 'Close'); the class photo (same mapping as the home Classes cards, .duo, aspect 16/9); class name Anton 48px uppercase; chips: level, duration, 'MAX 16' ('MAX 12' for Sparring); the intensity meter; the class description (from content, plus a second sentence of detail written per type, e.g. for Fundamentals 'Each session ends with three rounds on the bag so you leave having hit something.'); 'WHAT TO BRING' with the type's bring line; coach block: 56px monogram square, name, role and specialities; capacity bar: a 6px Slab track with a Signal fill at booked/capacity and the text '9 OF 16 BOOKED · 7 SPOTS LEFT'; 'ALSO THIS WEEK' list of the other sessions of the same type as small buttons that switch the drawer content; footer with primary button 'Book as free trial' → ./index.html?class=<session id>#trial (for full sessions the button reads 'Join waitlist at the desk' and is a tel: link) and secondary 'Close'.
Layout: Right-side panel, width 460px (100vw under 640px), full height, Ink background, 1px Slab left border, its own scroll; header and footer sticky inside it. Backdrop: Ink at 70% covering the page. The drawer is a role dialog with aria-modal, labelled by the class name; focus moves to the close button on open, Tab is trapped inside, Escape and backdrop click close it, focus returns to the cell that opened it, body scroll is locked. Selected session id is mirrored in the URL (?class=…) with replaceState and removed on close.
Motion: Drawer slides from x 100% to 0 with a spring (stiffness 420, damping 40); backdrop fades 0 → 1 over 200ms; inner blocks fade-up 12px with 40ms stagger after the panel lands. Switching to another session from 'Also this week' crossfades the content (160ms). Reduced motion: instant open/close with a 120ms fade.
7. **House rules and call to action** — Four rule tiles with a mono index and Lucide icon (Clock, Hand, Droplets, ShieldCheck), titles in Anton 24px uppercase and one sentence each: 'BE EARLY' — 'Doors to the floor close 5 minutes after start. Ten minutes early gets you wrapped and warm.'; 'TAP GLOVES' — 'Partners rotate every round. Respect the person holding the pads.'; 'WIPE IT DOWN' — 'Bags, benches and bars get wiped after use. Spray bottles hang on every pillar.'; 'TELL THE COACH' — 'Injury, bad night's sleep, first time back: say so before the warm-up and we'll scale it.'. Then a CTA band: H2 'FIRST CLASS' / 'IS FREE.' (second line Signal) with button 'Book trial' → ./index.html#trial and outline 'See prices' → ./index.html#pricing.
Layout: Coal background, padding 96px 0. Tiles: 4 columns gap 20px (2 under 1024px, 1 under 640px), each 1px Slab border, padding 28px. CTA band margin-top 96px: Ink panel with a 10px tape stripe along its top edge, padding 56px, flex space-between align-end, stacked under 768px.
Motion: Tiles reveal with 70ms stagger; CTA H2 uses the headline mask.
8. **Footer** — Same Footer component as index.html.
Layout: Identical to the home footer.
## Assets
- image: Template photos made for this design (free to redistribute with the template). Download each with curl -L -o into public/images/: https://media.vibefather.ai/assets/780d94b9-915b-4c06-9d9c-b81d44fea230/gym-floor.webp → public/images/gym-floor.webp (Home hero background; alt 'Row of worn heavy bags hanging from a steel beam in a dark warehouse gym'); https://media.vibefather.ai/assets/780d94b9-915b-4c06-9d9c-b81d44fea230/ring-corner.webp → public/images/ring-corner.webp (Sparring class card, drawer for Sparring, Results background; alt 'Corner of a boxing ring with taped post and dark ropes under a single light'); https://media.vibefather.ai/assets/780d94b9-915b-4c06-9d9c-b81d44fea230/hand-wraps.webp → public/images/hand-wraps.webp (Fundamentals class card and drawer; alt 'Hands wrapping a black hand wrap around the knuckles on a gym bench'); https://media.vibefather.ai/assets/780d94b9-915b-4c06-9d9c-b81d44fea230/heavy-bag.webp → public/images/heavy-bag.webp (Bag Rounds class card and drawer; alt 'Black heavy bag swinging on its chain with chalk dust in the air'); https://media.vibefather.ai/assets/780d94b9-915b-4c06-9d9c-b81d44fea230/kettlebells.webp → public/images/kettlebells.webp (Iron Hour class card and drawer; alt 'Row of cast-iron kettlebells and a bowl of chalk on rubber flooring'); https://media.vibefather.ai/assets/780d94b9-915b-4c06-9d9c-b81d44fea230/bench-gloves.webp → public/images/bench-gloves.webp (Pad Lab class card and drawer, Free trial section; alt 'Worn boxing gloves, skipping rope and hand wrap on a wooden bench').
- image: Public-domain photo: https://thumb.wikimedia.org/wikipedia/commons/thumb/2/2a/Black_boxing_gloves.jpg/1920px-Black_boxing_gloves.jpg → public/images/black-gloves.jpg. Used in the Beginner FAQ (aspect 4/5, object-position 30% 50%; alt 'Old pair of black leather boxing gloves with loose laces'). Source page https://commons.wikimedia.org/wiki/File:Black_boxing_gloves.jpg, author Airman 1st Class Kerelin Molina (U.S. Air Force, via Wikimedia Commons), license Public domain.
- image: CC0 photo: https://thumb.wikimedia.org/wikipedia/commons/thumb/2/2b/Heavy_ropes._%2816589110973%29.jpg/1920px-Heavy_ropes._%2816589110973%29.jpg → public/images/heavy-ropes.jpg. Used on the Engine class card and drawer and as the timetable masthead band (alt 'Close-up of thick coiled training ropes'). Source page https://commons.wikimedia.org/wiki/File:Heavy_ropes._(16589110973).jpg, author Bernard Spragg (Christchurch, New Zealand, via Wikimedia Commons), license CC0 1.0.
- image: ASSETS.md at the project root lists all eight images with path, source URL, author and license: the six template photos as 'Made for this template, redistributable with it' with their media.vibefather.ai URLs, black-gloves.jpg (Public domain, Airman 1st Class Kerelin Molina, U.S. Air Force) and heavy-ropes.jpg (CC0 1.0, Bernard Spragg), plus the three font packages (OFL-1.1) and lucide-react (ISC).
- icon: public/favicon.svg hand-written: viewBox 0 0 32 32, a 32×32 square filled #0C0C0B, a 32×8 band at the bottom filled with three diagonal #FF4F1F stripes (polygons, 45°), and a centered bold uppercase 'C' (first letter of [BRAND]) as SVG <text> x 16 y 21, text-anchor middle, font-family Impact, sans-serif, font-size 20, fill #EDEAE3. All UI icons come from lucide-react; no other icon sets and no logos.
- font: Install @fontsource/anton, @fontsource-variable/archivo and @fontsource/jetbrains-mono and import them in src/fonts.ts as listed in Setup. Vite bundles the woff2 files into dist/.
## Don'ts
- No backend of any kind: no server code, API routes, databases, auth, analytics, booking services, payment links, email services or third-party APIs. The trial form calls preventDefault and only changes local React state; it never submits, stores or sends anything.
- Do not load fonts from fonts.googleapis.com, fonts.gstatic.com, Adobe Fonts or any CDN. Self-host through the Fontsource packages only.
- No CDN <script>, <link> or import maps. Every script, style, font, icon and image is in the project and bundled into dist/.
- No hotlinked images: every photo is downloaded into public/images/ once and served from there, and every third-party file is listed in ASSETS.md. No Unsplash, Pexels, Pixabay or other stock sites with their own licenses.
- No source maps in the production build (build.sourcemap false), and src/ is not copied into dist/.
- Relative asset paths (Vite base './'). Internal links are hash anchors or file names such as ./index.html, ./index.html#trial, ./index.html?class=mon-1730#trial and ./timetable.html?type=engine, never a bare directory or a leading slash.
- Do not change the stack (Vite 5 + React 18 + TypeScript + Tailwind CSS 3.4 + Motion + lucide-react + Fontsource, two HTML entries) unless the user asks. No router library, no UI kit, no calendar or date library.
- No logos, names or signage of any real gym, boxing promotion, governing body or fitness franchise, and no trademarked class or programme names belonging to fitness chains. Class names are the six invented ones given here.
- No real fighters' names, titles, records or quotes. Every coach, member, quote, number and street is invented.
- No photos with identifiable faces, readable text, equipment brand names or logos on gloves, bags, mats or apparel. Use only the eight listed images; coaches are monograms, never photos or AI portraits.
- No commercial or custom display fonts associated with sports brands: only Anton, Archivo and JetBrains Mono (OFL).
- Only dependencies under MIT, ISC, BSD, Apache-2.0, 0BSD, CC0 or Unlicense (OFL for fonts). No Aceternity UI, Tailwind Plus / Tailwind UI or purchased template code; write every effect from scratch.
- One accent only: Signal (#FF4F1F) and its hover shade. No decorative gradients (the only gradients are the tape stripes, the grain and the hero legibility overlay), no glows, no glassmorphism cards, no rounded corners, no second accent color.
- Respect prefers-reduced-motion exactly as described under Design motion. Every interactive element is keyboard reachable with the 2px Signal focus ring; chips use aria-pressed, tabs use role tab, accordions use aria-expanded, the drawer is a focus-trapped modal dialog, form errors use role alert.
- Must render at 1440×900 and 390×844 with zero console errors and no horizontal page scroll (only the marquee, the today strip, the legend and the mobile chip rows scroll inside themselves).
- Keep dist/ under 20 MB (target under 8 MB).
- No lorem ipsum, filler or hype words. Use the exact headlines, schedule, prices, numbers and CTAs given here for the default values. No meta descriptions, Open Graph tags, JSON-LD or sitemap: only the <title> and favicon.
---
— Prompt by VibeFather · https://vibefather.ai/t/boxing-and-strength-gym-website · CC BY-NC 4.0 (non-commercial; commercial use needs a VibeFather Pro license) · Request removal: https://vibefather.ai/takedown
Free for personal projects under CC BY-NC 4.0. Commercial use comes with Pro.
DESIGN.md
The design system on its own — colors, type, spacing and motion. Apply this look to a project you already have.
---
name: "Boxing and Strength Gym Website"
colors:
ink: "#0C0C0B"
coal: "#151513"
concrete: "#232320"
slab: "#36352F"
dust: "#8E8B82"
chalk: "#EDEAE3"
signal: "#FF4F1F"
signal_deep: "#D63A0F"
typography:
display_h1_h2_h3_wordmark_prices_counters_monograms_session_times_class_names_on_cards_family_anton_always_uppercase: { family: "Anton", weights: [400] }
text_body_nav_buttons_chips_form_fields_faq_quotes_family_archivo_variable_buttons_and_chips_use_weight_600_700_with_uppercase_and_0_06em_tracking: { family: "Archivo", weights: [400, 500, 600, 700] }
mono_eyebrows_section_indexes_stat_labels_spots_and_capacity_labels_per_class_math_references_hours_table_family_jetbrains_mono_uppercase_letter_spacing_0_1em: { family: "JetBrains Mono", weights: [400, 500] }
spacing: "8px base unit. Container max-width 1360px, centered, side padding 40px desktop, 24px tablet, 20px under 640px. 12-column grid, gap 24px. Section padding 128px vertical desktop, 88px under 768px. SectionHead on every home section: a 1px Slab rule across the container, 20px below it a row with the mono index ('01', JetBrains Mono 500 13px, Signal), the mono label next to it ('CLASSES', 13px, letter-spacing 0.16em, Chalk 70%) and an optional right-aligned mono count; H2 margin-top 40px. Type scale: H1 Anton clamp(76px, 13.5vw, 212px) line-height 0.86; H2 Anton clamp(52px, 7.4vw, 120px) line-height 0.9, each H2 written as two lines with the second in Signal; H3 Anton 40px line-height 1; body Archivo 400 17px/1.6 (16px under 768px), max-width 62ch; lead Archivo 400 20px/1.5 Chalk 85%. Buttons: height 54px, padding 0 26px, Archivo 700 14px uppercase, letter-spacing 0.08em; primary = Signal background, Ink text, on hover background Signal Deep, transform translate(-2px, -2px) and a hard Chalk shadow box-shadow 4px 4px 0 #EDEAE3, back to no shadow and no offset when pressed; secondary = 1.5px Chalk border, Chalk text, hover Chalk background with Ink text. Focus ring everywhere: 2px Signal outline, offset 3px."
radius: "Square corners everywhere (0): cards, photos, buttons, chips, inputs, drawer, tickets. The only round shapes are the pulsing status dots and the map marker ring."
---
# Boxing and Strength Gym Website — Design System
Apply this design system to an existing project. It describes the look and feel only; for the full site, use prompt.md.
## Colors
- **Ink** `#0C0C0B` — Page background, header when scrolled, drawer and form card background, text on Signal.
- **Coal** `#151513` — Alternate section background (marquee, pricing, visit band, house rules), card backgrounds, inputs, full sessions.
- **Concrete** `#232320` — Raised panels: today strip, trial section, timetable session cells, monogram tiles, map blocks.
- **Slab** `#36352F` — All 1px borders and rules, empty intensity bars, capacity track, dashed empty cells.
- **Dust** `#8E8B82` — Muted text: labels, captions, coach names in cells, small print, 'FULL' states.
- **Chalk** `#EDEAE3` — Primary text and headlines; body text uses Chalk at 80%.
- **Signal** `#FF4F1F` — The only accent (blood orange): primary buttons, active chips and tabs, second headline lines, intensity bars, tape stripes, error states, focus rings, today markers.
- **Signal Deep** `#D63A0F` — Hover and pressed state of Signal buttons and chips.
## Typography
- **Display: H1, H2, H3, wordmark, prices, counters, monograms, session times, class names on cards (family 'Anton'). Always uppercase.** — Anton (OFL), weights 400. Self-host the font files.
- **Text: body, nav, buttons, chips, form fields, FAQ, quotes (family 'Archivo Variable'; buttons and chips use weight 600–700 with uppercase and 0.06em tracking).** — Archivo (OFL), weights 400/500/600/700. Self-host the font files.
- **Mono: eyebrows, section indexes, stat labels, spots and capacity labels, per-class math, references, hours table (family 'JetBrains Mono', uppercase, letter-spacing 0.1em).** — JetBrains Mono (OFL), weights 400/500. Self-host the font files.
## Spacing & shape
- Spacing: 8px base unit. Container max-width 1360px, centered, side padding 40px desktop, 24px tablet, 20px under 640px. 12-column grid, gap 24px. Section padding 128px vertical desktop, 88px under 768px. SectionHead on every home section: a 1px Slab rule across the container, 20px below it a row with the mono index ('01', JetBrains Mono 500 13px, Signal), the mono label next to it ('CLASSES', 13px, letter-spacing 0.16em, Chalk 70%) and an optional right-aligned mono count; H2 margin-top 40px. Type scale: H1 Anton clamp(76px, 13.5vw, 212px) line-height 0.86; H2 Anton clamp(52px, 7.4vw, 120px) line-height 0.9, each H2 written as two lines with the second in Signal; H3 Anton 40px line-height 1; body Archivo 400 17px/1.6 (16px under 768px), max-width 62ch; lead Archivo 400 20px/1.5 Chalk 85%. Buttons: height 54px, padding 0 26px, Archivo 700 14px uppercase, letter-spacing 0.08em; primary = Signal background, Ink text, on hover background Signal Deep, transform translate(-2px, -2px) and a hard Chalk shadow box-shadow 4px 4px 0 #EDEAE3, back to no shadow and no offset when pressed; secondary = 1.5px Chalk border, Chalk text, hover Chalk background with Ink text. Focus ring everywhere: 2px Signal outline, offset 3px.
- Radius: Square corners everywhere (0): cards, photos, buttons, chips, inputs, drawer, tickets. The only round shapes are the pulsing status dots and the map marker ring.
## Motion
- Easings: --ease-cut cubic-bezier(0.7, 0, 0.2, 1) for masks, wipes and swaps (fast, hard); --ease-out cubic-bezier(0.2, 0.7, 0.2, 1) for photo scales.
- Headline mask (Mask.tsx): H1 and H2 lines are arrays in content.ts; each line sits in an overflow-hidden block and its inner span rises from translateY(110%) to 0 over 700ms --ease-cut with 80ms stagger, triggered when the heading is 30% visible (Motion whileInView, once).
- Hard-cut reveal (.reveal-cut / Reveal.tsx): blocks start with clip-path inset(0 0 100% 0) and translateY(24px) and animate to inset(0) and 0 over 560ms --ease-cut when 15% visible, once; lists stagger by index.
- Stripe wipe: every Tape divider wipes in from the left on enter (clip-path inset(0 100% 0 0) → inset(0), 600ms --ease-cut).
- Counter ticks (useCountUp, requestAnimationFrame): numeric parts of hero stats and results go from 0 to their value over 1200ms with easeOutExpo when 40% visible, once; thousands separators and suffixes ('%', '+') stay in place; tabular-nums so widths do not jump.
- Marquee: linear translateX(0 → -50%) in 38s, pause on hover.
- Photo hover: scale 1 → 1.05 over 500ms --ease-out inside an overflow-hidden frame. Hero photo scales 1.08 → 1 on load.
- Pricing roll, drawer spring, tab slide and ticket drop exactly as described in their sections.
- prefers-reduced-motion: reduce — disable masks, reveals, wipes, parallax, photo scales, marquee, pulses and counters (show final values); drawer and menus open instantly with a 120ms fade; smooth scrolling off. Color hover changes stay.
## Imagery
Real photographs only, never illustrations: six photos made for this template (warehouse gym floor with heavy bags, ring corner, hand wraps, swinging heavy bag, kettlebells with chalk, gloves and rope on a bench) plus two public-domain / CC0 photos (vintage black boxing gloves, coiled heavy ropes). No faces, no people beyond hands and forearms, no brand names. Every photo gets the same duotone grade through DuoPhoto.tsx: a wrapper with background #0C0C0B; the img has filter grayscale(1) contrast(1.3) brightness(0.78); a ::before overlay with background #FF4F1F, mix-blend-mode color, opacity 0.22 (Signal tint in the mid-tones); and the .grain overlay: an inline SVG data-URI with feTurbulence (type fractalNoise, baseFrequency 0.85, numOctaves 3, stitchTiles stitch) at 220px tile size, mix-blend-mode overlay, opacity 0.22, pointer-events none. object-fit cover everywhere. Coaches are typographic monograms. Everything else is drawn in code: tape stripes (repeating-linear-gradient -45deg Signal/Ink 16px), ticket notches, intensity bars, the CSS ✕ separators, the block map and the favicon. Icons from lucide-react only, stroke-width 2, sizes 14–20px.
## Tone
Short, direct and warm under the grit, like a good coach: imperative headlines in caps, plain sentences in the body, concrete numbers (times, prices, kilos, capacities), no hype words, no fight-promo bravado, no exclamation marks. Never mocking beginners.
---
— Prompt by VibeFather · https://vibefather.ai/t/boxing-and-strength-gym-website · CC BY-NC 4.0 (non-commercial; commercial use needs a VibeFather Pro license) · Request removal: https://vibefather.ai/takedown