Full design · Business
Fitness studio
A booking app for a boutique movement studio: reformer, strength, flow and mobility, each with its own colour.
Live preview. Click around, it works.
About this template
A booking app for a boutique movement studio: reformer, strength, flow and mobility, each with its own colour. Members see a photography led home with the next classes and an intro offer, a week timetable filtered by practice, instructor and time of day with live spots left, class pages with what to expect, one tap booking, waitlists that promote automatically or on claim, class packs and memberships with a demo checkout, and an account with credits, an attendance streak, late cancel rules and calendar files. The owner gets a front desk to check people in and mark no shows, a recurring timetable editor with substitute instructors and cancelled dates, members with credit adjustments and notes, revenue and fill rate charts, and settings that change how booking behaves.
What the agents are asked
The first message sent when you use this template. You can edit the plan before anything is built.
Adapt this studio booking app to my business. Keep both sides and every working rule: the member side (Home, Timetable with filters and waitlists, Class page, Memberships and class packs with checkout, My account with the late cancel rule, credits, streak and calendar files) and the owner side (Today check in, Timetable editor with substitutes, Members, Revenue and attendance, Settings including the waitlist auto promote switch). Change the brand, colours, address, practices, class types, instructors, recurring timetable, capacities, prices and cancellation window to mine. Keep the sample data marked Sample and removable in one step, keep every price including GST, and keep the general fitness, not medical advice line. When I want real accounts or payments, wire in sign in, the tables and functions in schema.sql and Stripe Checkout as the README describes, with every secret key on the server.
A class booking app for a boutique movement studio with four practices (reformer, strength, flow and mobility), each with its own colour. Members see a photography led home with the next classes and an intro offer, a timetable they can filter by practice, instructor and time of day with live places left, a page for every class with what to expect, one tap booking, waitlists, packs and memberships with a working demo checkout, and an account with credits, an attendance streak, the late cancel rule and calendar files. The owner runs the front desk (check in by tapping members, no shows, walk ins, waitlist moves), edits the recurring timetable with substitute instructors and cancelled dates, looks after members and their credits, reads revenue and fill rate, and sets the rules that booking follows.
"Sawtooth Studio" is an invented business. The name, people, address, phone number and prices are made up for the template (the phone numbers come from the ranges ACMA sets aside for fiction). The member names are invented too. Classes here are general fitness, not medical advice, and the app says so where it matters.
Files
| File | What it is |
|---|---|
index.html | Shell: demo ribbon with Viewing as: Member / Owner, top bar, owner sidebar, main, footer, phone tab bar, one <dialog> for every sheet, toast, notification banner and chart tooltip. One <script type="module">. |
styles.css | Tokens first (light; dark by prefers-color-scheme; a manual override on <html data-theme>), then shell, components, screens, motion and a print stylesheet for the roster. Every colour is a token. |
script.js | One plain ES module, no dependencies. Sections: helpers, icons, seed data, store (the data layer) and rpc (the booking rules), shared pieces, member screens, owner screens, charts, router, events. |
schema.sql | Postgres tables, row level security, grants, the booking functions, seed rows and a VERIFY block. |
media.json | The photographs and the hero loop, with the prompts they were generated from. |
template.json | The catalogue entry and the identity (fonts, colours, motion moment). |
There is no build step. Serve the folder with any static server that also serves /template-media/fitness-studio/.
Screens
Member (bottom tab bar on a phone, top navigation on a desktop)
| Screen | What works |
|---|---|
Home #/ | Full bleed hero with the studio loop, the intro offer (or a best value card once it is used), Next up: the next six bookable classes with one tap booking, the four practices with a sticky photograph column that changes colour and picture as you read, the coaches, and the first class steps. |
Timetable #/timetable | A day strip across the booking window (count of matching classes and a mark on days you are booked), filters by practice (several at once), instructor and time of day (early, midday, evening), "only classes with spots", a Day or Week layout, places left as squares, and the right button for every state: Book, Booked, Join waitlist, Waitlist number, Claim spot, Started, Opens on a date. [ and ] change day; t goes to today. |
Class #/class/<id> | Practice photograph on its colour, level, time, room, places, a booking panel for every case (book, full, waitlist position, no credits, booked with cancel and calendar, started), what to expect, what to bring, the coach (or the cover coach), and other times for the same class. |
Memberships #/memberships | The intro offer, class packs as a price per class ladder, the two memberships, a side by side table, and checkout in a sheet. Coming from a class with no credits (?for=<id>) books that class straight after paying. |
My account #/account | Credits ring, attendance streak with twelve weeks of squares, classes this month, upcoming bookings as tickets (calendar file and cancel on each, with the cancel deadline spelled out), waitlists, recent classes, payments and the credit history. |
Owner (sidebar on a desktop, tab bar on a phone)
| Screen | What works |
|---|---|
Today #/owner | KPIs for the day, a rail of the day's classes (done, on now, in N minutes), and the roster: tap a member to check them in, tap again to undo; mark no shows one by one or "mark the rest"; flags for first class, notes and zero credits; add a walk in; move people in from the waitlist; print the roster. Previous and next day. |
Timetable #/owner/timetable | The recurring week by day. Add, edit (class, day, start, instructor, capacity, room) and retire classes. Each class sheet lists its next dates with a Teaching select for a substitute and Cancel date. Members booked are refunded and told. |
Members #/owner/members | Search (name, email, notes; / focuses it), filters (low credits, intro, unlimited, not seen in three weeks), sort, CSV export, add member. A member sheet with credit adjustments (append only, with a reason), coach notes, upcoming bookings (cancel with refund, book into a class) and recent attendance. |
Revenue and attendance #/owner/reports | Four or eight weeks: revenue, classes run, average fill, check ins, no show rate, intro to pack conversion; SVG charts for fill rate by class (bars in the practice colours), revenue by product and revenue by week, each with hover and keyboard tooltips and a table view; payments CSV. |
Settings #/owner/settings | Cancellation window, late cancel fee for Unlimited, booking window, waitlist auto promote on or off and its cut off, reminders, studio details, theme, and sample data removal. |
The rules (in rpc in script.js and the same names in schema.sql)
- Booking takes one credit, or nothing on an active Unlimited membership. A member cannot book twice, book a started class, or book beyond the booking window.
- Waitlist places take no credit. With auto promote on, when a place frees up more than the cut off before the class, the first person waiting who can pay is booked in, charged and told (people without credits are skipped and told why). With it off, everyone waiting is told and the first to claim the place gets it; the front desk can also move people in by hand.
- Cancelling outside the window refunds the credit. Inside the window it is a late cancel: the credit is kept, or on Unlimited a fee is recorded. Leaving a waitlist is always free. When the studio cancels, it always refunds.
- Credits are an append only ledger. Adjustments are new rows with a reason, never edits.
- Timetable edits apply to future dates only. Capacity cannot drop below what is already booked on a future date; moving a class to another day with people booked is refused with the reason.
- Every change that can be regretted shows Undo in the toast (a snapshot restored only if nothing else changed).
Art direction
One morning shoot in a converted brick mill with a sawtooth roof: 7 am sun through the roof glazing and tall steel windows from the left, warm 4000K highlights against cool grey concrete shadows, long real shadows, natural skin with no airbrushing, a single burnt orange towel as the only strong colour. Photographs are square cornered; the colour field behind each practice photograph is that practice's colour. Faces are round, everything else is square.
| Name | Used for |
|---|---|
studio-morning | Hero still and poster for studio-loop.mp4, og image. |
reformer-cue | Reformer practice, intro card, class pages for reformer classes. |
barbell-grip | Strength practice and its class pages. |
flow-class | Flow practice and its class pages. |
member-stretch | Mobility practice, its class pages, and the first class section. |
coach-mere, coach-sami, coach-anneke, coach-jun | Coach cards on Home, timetable rows, class pages. The cover instructor has a monogram until a photograph is supplied. |
The hero loop is the only video: muted, playsinline, the photograph as its poster, aria-hidden, not drawn at all under reduced motion, and paused when it leaves the screen or the tab is hidden.
Type and colour. Archivo, a variable face with a width axis, set wide (font-stretch: 125%) in capitals for display and for the big class times; Schibsted Grotesk for reading. Chalk #ECECE7, Iron #16171A, Flare #F2542D. Practice colours (reformer, strength, flow, mobility) were checked as a categorical palette for colour vision separation and contrast in both themes.
Motion, one job each. The orchestrated moment is the stretch: on first load the hero headline widens from condensed to expanded type line by line over the studio loop, then the intro offer rises. Also: a staggered entrance once, scroll reveals on the view timeline (IntersectionObserver fallback), clip reveals on photographs, the sticky practice column, view transitions between screens, and a booked place filling its square. All of it is off under prefers-reduced-motion.
Data model
Tables in schema.sql, mirrored by store.state in the browser:
studio_settings (one row: the rules), studio_staff (owner and coach accounts), instructors, class_types, schedule_slots (recurring classes), class_sessions (dated classes, id s-<slot>-<yyyymmdd>), products (intro, packs, memberships), members, orders, credit_ledger (append only), bookings (status booked, waitlist, attended, no_show, cancelled, late_cancel), notifications.
What is sample or simulated
- Sample data: 120 members, eight weeks of classes with bookings and attendance, orders and credits, all
is_sample, dated relative to today, stored under aSAMPLE_VERSION. Settings, Sample data, Remove sample data removes them in one step (with Undo); the timetable, class types, instructors and prices stay. On load, if the storedSAMPLE_VERSIONis older than the template's (or missing), the sample rows are replaced with a fresh batch dated around today while every row a visitor made, and sample data removal, are left as they were. If the demo is opened a week or more later, the sample is still on the current version and nobody has made real bookings, it is laid out again around today the same way. - Signed in member: the demo is signed in as Jordan Lee. With sample data removed, My account offers to create a profile, kept in this browser only.
- Payments: the checkout says "Demo checkout. Connect Stripe in project settings." Card 4242 4242 4242 4242 pays; a number ending 0002 shows a decline. Nothing is charged.
- Notifications and reminders: written to the member's bell and shown as a banner; a real app sends them by email or push from the server.
- Files:
.icscalendar files (one class, or all upcoming) and CSV exports download; where a preview blocks downloads, "View file" shows the text.
Plugging in a real backend
- Database. Run
schema.sqlon the project's Supabase database (rehearse it first). Add the owner:insert into studio_staff (user_id, role) values ('<auth user id>', 'owner');. Schedulematerialize_sessions(current_date, current_date + 28)nightly with the service role, next to the reminder job. - Swap the store. Views only read through
storeand write throughrpc. Replacestore.load()with selects of the same tables, and eachrpc.*withsupabase.rpc('<same name>', ...):rpc.book_class(id)becomessupabase.rpc('book_class', { p_session: id }), and so on forjoin_waitlist,cancel_booking,claim_spot,promote_waitlist,set_attendance,adjust_credits. The functions do the same checks as the browser, with a row lock so two people cannot take the last place. - Payments. Create Stripe prices for each product and store their ids in
products.stripe_price_id. The Buy button calls a server function that creates a Checkout Session; the Stripe webhook callsrecord_purchase(member, product, amount, checkout_session_id)with the service role. The secret key and the webhook secret live in project settings on the server, never in the browser. Late cancel fees on Unlimited are recorded bycancel_bookingand charged by the server to the saved card. - Auth and roles. Replace the Viewing as switch with sign in. A member row links to
auth.usersbyuser_id; staff rows decide who sees the owner side.
Adapting it
Change PRACTICES, seedClassTypes, seedInstructors, SCHEDULE_ROWS, seedProducts and seedSettings in script.js and the matching seed rows in schema.sql; the rest follows. Keep prices including GST, keep the general fitness line, and keep sample rows marked.