Overview#
Fitness is a bold, dark website for a gym or fitness studio, built as a WordPress block theme. The
demo club is Forge Athletic Club, a fictional gym with two locations. The theme has a
high-energy home page with "next up today" classes and count-up stats, a weekly class timetable with
filters and a booking dialog, class and coach profile pages filled from the URL, a coaches page with
a specialty filter, membership plans with a monthly/yearly switch, a comparison table and a BMI &
calorie calculator, a three-step free trial sign-up, a locations page with live open-now status and
a 404 page. It is version 1.0.2 (see template.json and style.css), works with WordPress 7.1 and
PHP 7.4 or newer, and is the WordPress version of the HTML template of the same name. See it live at
freeframe.dev/templates/fitness.
Everything you see is made of core WordPress blocks arranged in block patterns, so you edit text,
links, buttons, lists and images in the block editor without touching code. Colours, the fonts and
font sizes are design tokens in theme.json: change them in Appearance > Editor > Styles and the
whole site follows. The classes, coaches, clubs and the weekly timetable are data in
assets/js/main.js (see Classes, coaches and the timetable).
Quick start#
Requirements: WordPress 7.1 or newer and PHP 7.4 or newer. No plugins are needed.
- Download
fitness.zipfrom freeframe.dev/templates/fitness (the WordPress tab). - In your WordPress admin go to Appearance > Themes > Add New Theme > Upload Theme, choose the zip and click Install Now, then Activate. The theme is listed as Freeframe Fitness.
- Go to Appearance > Fitness setup and click Create the demo pages. This creates the pages below from the theme's page patterns, sets Home as the front page (Settings > Reading) and creates the header menu. Pages that already exist with the same slug are left alone, so it is safe to click it again.
- Set the site title to your club's name in Settings > General (the demo uses "Forge Athletic Club"), then edit any page in Pages, and the header, footer and templates in Appearance > Editor.
You can also try the theme without installing anything: the Try it in WordPress button on freeframe.dev opens it in WordPress Playground (WordPress running in your browser) with the demo pages already set up.
To work on the theme's files locally you need Node.js 24 and pnpm; WordPress itself runs in WordPress Playground, so there is no PHP, web server or database to install:
pnpm install
pnpm dev # builds the CSS, then starts WordPress with the theme at http://127.0.0.1:9400pnpm dev signs you in as admin (password password) and creates the demo pages. The site is
rebuilt from scratch every time you start it; your edits to theme files are live, edits made in the
WordPress admin are not kept.
Pages#
Each page is a page pattern (in Patterns > Fitness pages) made of section patterns. Every section is also offered on its own in the block inserter under Patterns > Fitness.
| Page | Slug | Page pattern | Sections (pattern files in patterns/) |
|---|---|---|---|
| Home | / |
page-home.php |
home-hero (hero, stats, class ticker), next-up, disciplines, results (count-up numbers), plans-teaser, coaches-teaser, stories, facilities, trial-cta |
| Class timetable | /classes/ |
page-classes.php |
timetable-hero, timetable (filters, day tabs, the week), booking-modal (the booking dialog) |
| Strength Lab (class detail) | /class/ |
page-class.php |
class-hero, class-about, class-sessions, other-classes |
| Coaches | /trainers/ |
page-trainers.php |
coaches-hero, coaches-grid (specialty filter and coaches), pt-cta |
| Maya Torres (coach profile) | /trainer/ |
page-trainer.php |
coach-profile |
| Membership & pricing | /membership/ |
page-membership.php |
membership-hero (monthly/yearly switch), plans, compare-plans, add-ons, calculator, membership-faq |
| Free trial week | /trial/ |
page-trial.php |
trial (benefits and the three-step form) |
| Locations & contact | /contact/ |
page-contact.php |
contact-hero, locations (maps, open-now status, hours), contact-form |
| 404 | any missing URL | templates/404.html |
404 |
The class and coach pages show one class (Strength Lab) and one coach (Maya Torres) as blocks, so
they work without JavaScript; with ?type= or ?id= in the address the script shows another one
(see Class and coach pages). That is why those two pages are named after
the class and coach they show by default.
Posts use templates/single.html; the blog, archives and search results use templates/index.html,
templates/archive.html and templates/search.html.
Project structure#
freeframe-fitness/
style.css theme header (name, version, requirements, license); no styles
theme.json design tokens: palette, fonts, font sizes, spacing, layout widths, button
functions.php loads the files in inc/
inc/setup.php stylesheet and script, editor styles, the "js" class script, script strings
and settings, pattern categories
inc/icons.php registers assets/icons/*.svg for the Icon block
inc/icon-labels.php the icons' names shown in the editor
inc/block-styles.php button block styles
inc/navigation.php keeps Classes / Trainers current in the menu on the class and coach pages
inc/site-setup.php Appearance > Fitness setup: creates the demo pages and the menu
templates/ page.html (pages), page-with-title.html, single.html, index.html,
archive.html, search.html, 404.html
parts/ header.html and footer.html (they show the header and footer patterns)
patterns/ every section and page (PHP files with block markup)
assets/css/theme.css the compiled stylesheet (do not edit: built from src/)
assets/css/editor.css the same for the block editor (generated by src/editor-css.mjs)
assets/js/main.js the site's data (classes, coaches, clubs, timetable) and JavaScript
assets/js/editor.js block editor tweaks
assets/icons/ SVG icons for the Icon block
assets/img/ logo, class covers (class-<id>.svg), coach portraits (coach-<id>.svg),
hero, maps, favicon, social image
assets/fonts/ the Anton and Inter font files and their licenses (OFL.txt)
src/theme.css the Tailwind CSS source of assets/css/theme.css
src/icons.css icons drawn before or after text with CSS (generated)
src/editor-css.mjs writes assets/css/editor.css after each build
languages/ freeframe-fitness.pot, the template for translations
blueprint.json WordPress Playground setup used by `pnpm dev`
screenshot.png the picture shown in Appearance > Themes
package.json the CSS build and the local WordPress (Node.js tooling)
readme.txt, README.md, DOCS.md, CHANGELOG.md, LICENSE.txtCustomising#
Brand and colours#
The colours are in theme.json (settings.color.palette) and in Appearance > Editor > Styles >
Colors > Palette:
| Colours | Used for |
|---|---|
| Base | the page background (near black) |
| Surface, Raised | card and panel backgrounds |
| Line | borders |
| Contrast, Muted | text and secondary text |
| Volt | the main accent: buttons, highlights, focus rings |
| Volt Dim | the accent's hover |
| Heat | high intensity and "few spots left" |
| Cool | low intensity |
| White, Black | plain colours for your own blocks |
The theme's stylesheet reads these colours from the palette, so changing Volt in Styles recolours
every button, highlight and accent at once. The accent sits behind dark text, so keep it bright;
change Volt Dim with it. In theme.json:
{ "slug": "volt", "name": "Volt", "color": "#c8ff2e" }A few details have the colour written in: the logo (assets/img/logo.svg), the pictograms (Custom
HTML, #c8ff2e), the maps and other illustrations in assets/img/, the BMI scale and the browser bar
colour (freeframe_fitness_head_meta() in inc/setup.php).
Typography#
Headlines use Anton (the display class, a tall condensed face) and text uses Inter (variable,
weights 400 to 800), bundled in assets/fonts/ and declared in theme.json
(settings.typography.fontFamilies), so no request goes to Google. To use other fonts, install them
in Appearance > Editor > Styles > Typography > Fonts (the Font Library) and select them, or
replace the fontFace entries in theme.json; the stylesheet reads --wp--preset--font-family--anton
and --wp--preset--font-family--inter, so rename those slugs only together with src/theme.css.
Font sizes are the presets in theme.json (xs 0.75rem to 9xl 8rem, the Tailwind scale). The
stylesheet uses them too: changing Large in Styles changes every text-lg in the design.
Content#
Edit pages in Pages: click any text to change it, select a button to change its link, select an image and use Replace. Sections are groups; open List View (the stacked lines icon at the top left) to select a section, move it, duplicate it or delete it. To add a section from another page, use the block inserter (+) > Patterns > Fitness.
Some content is in Custom HTML blocks, because blocks can't express it or a script drives it: edit those in the block's HTML view.
| Pattern | Custom HTML | What it is |
|---|---|---|
home-hero |
the class ticker | a scrolling line of class names (decorative) |
disciplines, class-hero |
the intensity meters | three bars with data-level 1–3 |
timetable |
the filters, the count, the legend, the day tabs and the week | see Timetable |
booking-modal |
the booking dialog | see Booking dialog |
coaches-grid |
the filter buttons and the count | see Coach filter |
membership-hero |
the Monthly / Yearly switch | see Membership prices |
compare-plans |
the comparison table | a table that scrolls sideways on small screens |
calculator |
the calculator and its results | see BMI and calorie calculator |
trial |
the sign-up card | see Free trial sign-up |
locations |
the open-now badges and the hours lists | see Open-now status |
contact-form |
the contact form | see Forms |
trial-cta, 404 |
the athlete pictograms | multi-colour SVG artwork |
header, footer |
the menu button; the clubs' status, the newsletter form and the toast | driven by assets/js/main.js |
A few blocks carry a class (Block settings > Advanced > Additional CSS class(es)) that the script or stylesheet reads:
revealfades a block in as it scrolls into view; a class such as[transition-delay:80ms]delays it.ff-counton the number of a statistic counts it up when it scrolls into view (write the final number:2,400).link-cardon a group makes the whole group clickable: the first link inside it stretches over the group.ff-c-<field>andff-t-<field>on the class and coach page blocks the script fills in, andff-class-page ff-default-strength/ff-trainer-page ff-default-mayaon the first section of those pages (see Class and coach pages).ff-tag-<specialty>on each coach card of the coaches page (see Coach filter).ff-next-up,ff-today-label(home page),ff-class-sessions(class page) andff-other-<id>(the "other classes" buttons): lists and buttons the script fills in or marks.billing-monthlyandbilling-annualon the two price groups of each plan (see Membership prices).ff-grid-lines,ff-grid-hero,ff-grid-top,ff-grid-centerandff-volt-glowdraw the grid lines and the glow behind the page headers.
Classes, coaches and the timetable#
The class types, coaches, clubs and the weekly timetable are data in the FORGE object at the very
top of assets/js/main.js. The timetable, the "next up" list, the booking dialog, the class and coach
pages (for ?type= / ?id=), the open-now status and the coach filter all read it:
| Key | Contents |
|---|---|
FORGE.classes |
Class types keyed by id: strength, ignite, ride, box, flow, engine |
FORGE.coaches |
Coaches keyed by id: maya, dev, jonah, lena, sami, chloe |
FORGE.locations |
Clubs keyed by id (harbor, northgate) with weekly opening hours |
FORGE.sessions |
The weekly timetable (36 sessions) |
A class type:
"strength": {
"name": __("Strength Lab"),
"short": __("Small-group barbell training with coached progressions every week."),
"desc": __("Strength Lab is where Forge members get genuinely strong..."),
"intensity": 2, // 1 = low, 2 = medium, 3 = high
"duration": 60, // minutes
"kcal": "350–500",
"expect": [__("A 10-minute movement-prep warm-up..."), …],
"equipment": [__("Olympic barbell & bumper plates"), …],
"who": __("All levels. New lifters start on technique blocks with a lighter bar."),
"tag": "Strength"
}__() translates the text (see Translations): when you change or add a text, add it
to freeframe_fitness_script_strings() in inc/setup.php too if the site is translated. A coach has
name, role, tags (specialties), classes (class ids they teach), years, certs, quote,
bio (an array of paragraphs) and handle (social handle without @). A location's hours is an
array of seven [open, close] pairs in 24-hour "HH:MM" text, starting on Monday. A session:
{ "id": "s00", "day": 0, "time": "06:00", "type": "ignite", "coach": "dev", "loc": "northgate", "cap": 20, "booked": 9 }day counts from Monday (0) to Sunday (6); type, coach and loc are ids from the other keys;
cap is the class size and booked the number of spots already taken (demo data). Give every session
a unique id: bookings are saved by id.
Adding a class type needs: an entry in FORGE.classes, a cover at assets/img/class-<id>.svg (the
theme passes the URLs of its class-* and coach-* images to the script), its id in some coaches'
classes, and sessions using it. The class cards on the home page, the class filter in the timetable's
Custom HTML and the "other classes" buttons on the class page are blocks: add the new class there too
(the button needs the class ff-other-<id>). Adding a coach is the same: an entry in
FORGE.coaches, assets/img/coach-<id>.svg, a card on the coaches page (with ff-tag-… classes) and
an option in the timetable's coach filter. Make these changes in a child theme (see
Child themes) so a theme update doesn't overwrite them.
The printed opening hours (the locations pattern) are Custom HTML; keep them in step with
FORGE.locations.
Time zone. TIMEZONE near the top of the script (default 'America/New_York') is the clubs' IANA
time zone. The open/closed status, today's row, the timetable's starting day, "next up" and the
earliest free trial date use the time there, the same for every visitor.
Images and artwork#
- Class covers and coach portraits are SVG illustrations in
assets/img/(class-<id>.svg,coach-<id>.svg) shown with Image blocks. Replace them with photos from the Media Library in the same proportions (4:3 covers, 5:6 portraits). The class and coach pages swap the picture for the class or coach in the URL using the theme's files, so to change those, replace the files in the theme (child theme), keeping their names. - Hero (
hero.svg) and the maps on the locations page (stylised-map-showing-forge-harbor.svg…) are Image blocks: replace them with a photo and map images, or embed a map. - Pictograms (the athletes on the call to action, the 404 page and the trial goals) are SVG in Custom HTML blocks.
- Logo: the header and footer show
assets/img/logo.svgin an Image block next to the name "Forge". Edit both in Appearance > Editor > Patterns > Header (and Footer). - Icons: icons are Icon blocks. Select one and pick another icon from the Fitness collection
(the theme's icons) or WordPress's own. Icons inside buttons (the arrows) are drawn by CSS: the
class
icon-after-arrow-rightadds one; the available names are insrc/icons.css(and the social iconsicon-before-instagram,-youtubeand-stravainsrc/theme.css). - Social image:
assets/img/og-image.png(1200 × 630, sourceog-image.svg). WordPress doesn't output Open Graph tags by itself; see SEO and sharing. - Favicon:
assets/img/favicon.svgis used until you set a Site Icon in Settings > General.
Navigation, header and footer#
The header and footer are template parts (parts/header.html, parts/footer.html) showing the
header and footer patterns. Edit them in Appearance > Editor > Patterns > Header / Footer.
The header has two Navigation blocks showing the same menu: the desktop one and the full-screen one
in the mobile menu. Edit the menu once in Appearance > Editor > Navigation (the setup creates
"Main menu" with Classes, Trainers, Membership and Contact). Each label ends with its number,
<span class="ff-num">01</span>, which only the mobile menu shows; give a new link the next number.
A Navigation block that doesn't point at a menu shows the most recently created one; to pin a menu,
select the Navigation block and choose it in the block settings, for both blocks. WordPress marks the
current page's link; the theme keeps Classes current on the class page and Trainers on the
coach page (freeframe_fitness_menu_parents() in inc/navigation.php).
The footer's link columns are List blocks: click a link to change it, press Enter to add one. The social links are Button blocks with the Icon only style.
Features#
Timetable#
The timetable page renders the week from FORGE.sessions: on desktop all seven days side by side; on
phones day tabs (a tablist with Left/Right, Home and End keys) show one day at a time, starting on
today. The filters (class, intensity, coach) narrow the sessions, Reset clears them, and a live
count shows "24 classes this week". Each session shows its time, intensity, coach, club and a badge:
"12 spots", "2 left", "Full · waitlist" or "Booked". Links can preselect filters and the day:
/classes/?type=box, ?coach=lena, ?day=4 (Monday = 0); ids match regardless of case and unknown
ids are ignored, so the timetable then shows every class.
Booking dialog#
Clicking a session opens the dialog (a native <dialog>, in the booking-modal pattern) with the
class details and one action: Book my spot, Cancel booking, or Join waitlist when full.
Bookings are saved in the visitor's browser (see Saved data) and show as
"Booked" after a reload; the waitlist only shows a message. Escape, the close button or a click
outside closes the dialog, and focus returns to the session. "Clear my bookings" removes them all.
This is a demo: bookings live only in the visitor's browser. For real bookings, connect a
gym-management or class-booking service (Mindbody, Glofox, TeamUp…) and replace the action button's
handler in assets/js/main.js, or link each session to their booking page.
Next up today#
On the home page the list with the class ff-next-up shows the next four sessions later today (or
tomorrow's first four), each linking to that day on the timetable.
Class and coach pages#
The class page shows Strength Lab and the coach page Maya Torres as blocks. With ?type=box or
?id=lena the script fills in that class or coach from FORGE: every block with an ff-c-<field> (or
ff-t-<field>) class gets that field (name, short, desc, duration, kcal, expect,
equipment, who, coaches, img, book…; role, quote, bio, tags, certs, classes,
years, weekly, handle…), as well as the browser tab title. The class page also lists that class's
upcoming sessions and marks it in "Explore other classes". Ids match in any letter case (?type=BOX,
?id=DEV); unknown values (?type=constructor) show the default. Search engines that don't run JavaScript only see the default class and coach: create one
page per class or coach (duplicate the page and edit its blocks) if those pages matter for search.
Coach filter#
The buttons with data-coach-filter (Custom HTML) show the coach cards whose classes include
ff-tag-<specialty> (for example ff-tag-strength ff-tag-nutrition); "All" shows everyone. Matching
ignores case.
Membership prices#
Each plan's price and note exist twice: a group with the class billing-monthly and one with
billing-annual. The Monthly / Yearly switch (role="switch", aria-checked) shows one of them (it
adds ff-annual to <html>). In the editor both groups are shown, the yearly one marked "Yearly".
BMI and calorie calculator#
The calculator (form[data-calc]) takes units (metric or imperial), height, weight, age, sex and
activity level and shows the BMI with its category and a marker on a scale, the resting metabolic
rate (Mifflin–St Jeor), daily calories (BMR × the activity factor) and suggested targets for cutting
(−450 kcal) and building (+300 kcal). Results are announced to screen readers. The figures are general
estimates, not medical advice; keep the disclaimer.
Free trial sign-up#
The trial page's form (form[data-trial]) has three steps: goal (radio tiles), details (name,
email, phone, home club, start date from today, experience and plan) and review, then a required
terms box. Each step is validated before moving on, focus moves to the step's legend, and
/trial/?plan=elite preselects the plan (the value must match an option in #t-plan, in any letter
case: ?plan=Elite works too). The confirmation says nothing was sent.
Open-now status and today's hours#
Elements with data-open-status="harbor" (or another club id) show "Open now · until 22:00" or
"Closed · opens 06:00"; hours lists with data-hours highlight today's row (counted from Monday) and
mark it with aria-current="date".
Sticky header, mobile menu, reveals and counters#
The header gains a background on scroll. The menu button (data-menu-toggle) opens the full-screen
mobile menu, updates aria-expanded and its label, and closes with Escape (returning focus to the
button). The mobile menu Group starts with the class hidden; the script turns it into the hidden
attribute it toggles. Blocks with reveal fade in when scrolled into view; ff-count numbers count up
once. With reduced motion, reveals and counters appear immediately.
Back to top#
A button appears after scrolling about one screen down; it scrolls to the top (instantly with
reduced motion), moves keyboard focus to the main content and lifts above the toast. It is created by
assets/js/main.js; to remove it, delete the "Back to top" part of that file and the .back-to-top
styles in src/theme.css, then run pnpm build.
Block styles#
In the block settings' Styles panel:
| Block | Styles |
|---|---|
| Button | Fill (default, volt), Outline, Dark (on volt), Chip, Icon only (WordPress's own Outline style is replaced by the theme's) |
Add the class is-large (or is-small) to a Button block for the large (small) size.
Forms#
| Form | Where | Notes |
|---|---|---|
| Contact | Locations & contact page (contact-form pattern) |
?topic=membership, pt, corporate or other preselects the topic (any letter case) |
| Newsletter | Footer | email only |
Free trial (data-trial) |
Free trial page | see Free trial sign-up |
Calculator (data-calc) |
Membership page | calculates in the browser; sends nothing |
Forms use the browser's constraints (required, type="email", pattern, minlength, min/max)
with friendly messages from fieldMessage() in assets/js/main.js. Errors appear under each field,
fields get aria-invalid, and the first problem is focused. Simple forms show their [data-success]
message on success. Nothing is sent anywhere until you connect the forms.
Option 1: a form plugin#
Install a form plugin (WPForms, Gravity Forms, Fluent Forms, Contact Form 7, Jetpack Forms...), build
the form there, then replace the form's Custom HTML block with the plugin's block or shortcode. To
make its fields look like the theme's, add the theme's classes where the plugin allows custom classes:
field on fields, label on labels, btn btn-primary on the submit button.
Option 2: a form service#
Keep the theme's form, add an action (for example a Formspree endpoint) to
the form tag in its Custom HTML block, and send the data in onValid() in assets/js/main.js:
function onValid(form) {
fetch(form.action, { method: 'POST', body: new FormData(form), headers: { Accept: 'application/json' } })
.then(function (res) {
if (!res.ok) throw new Error('Request failed');
form.reset();
var ok = $('[data-success]', form);
ok.classList.remove('hidden');
ok.setAttribute('tabindex', '-1');
ok.focus();
})
.catch(function () { toast('Sorry, that didn’t send. Please try again.'); });
}For the free trial, follow the comment "To send for real" in the trial's submit handler and post
new FormData(trial) before showing the confirmation. Make script changes in a child theme (copy
assets/js/main.js there and load it instead), or they are overwritten when you update the theme.
Saved data (localStorage)#
| Key | Shape | Purpose |
|---|---|---|
forge-bookings |
Array of session ids, e.g. ["s04", "s17"] |
Classes the visitor booked |
The value is checked on every read: anything that isn't an array of known, unique session ids is
dropped and the stored value repaired. Visitors clear it with "Clear my bookings"; developers can run
localStorage.removeItem('forge-bookings').
SEO and sharing#
Page titles come from WordPress: the page title, an em dash and the site name (for example
"Membership & pricing — Forge Athletic Club"); the home page shows the site name and tagline
(Settings > General). The class and coach pages change the browser tab title for ?type= / ?id=.
The theme doesn't output meta descriptions or Open Graph tags; use an SEO plugin such as Yoast SEO,
Rank Math or The SEO Framework, and give it assets/img/og-image.png (or your own 1200 × 630 image) as
the default social image. Consider ExerciseGym structured data with
each club's address and hours. WordPress already generates a sitemap at /wp-sitemap.xml.
Accessibility#
Built in: a skip link to the main content (added by WordPress, styled by the theme), labelled
landmarks, one h1 per page, strong focus rings, day tabs following the tabs pattern, a native
booking dialog with focus return, filter buttons with aria-pressed, the pricing switch with
role="switch" and aria-checked, intensity shown as a meter with a text label (not colour alone),
multi-step forms that move focus to each step's legend and mark the current step, form errors linked
to their fields, and reduced-motion support for reveals, counters, the ticker and smooth scrolling.
The demo passes axe (WCAG 2.1 A and AA).
When you edit: keep text contrast at WCAG AA on the dark background (the accent should only sit behind dark text), give images alt text (or mark them decorative), label every new form field and keep one Heading 1 per page.
Building and deploying#
You only need this if you change the theme's files. The stylesheet assets/css/theme.css is
compiled with Tailwind CSS from src/theme.css and the classes used in patterns/, parts/,
templates/, inc/ and assets/js/ (including the classes inside the HTML the script writes):
pnpm install
pnpm build # compile assets/css/theme.css and assets/css/editor.css
pnpm watch # recompile assets/css/theme.css on every change (run pnpm build before you commit)Run pnpm build after adding a utility class to a pattern or template, otherwise the class has no
style. Classes you type in the editor's Additional CSS class(es) field are only styled if the
stylesheet already contains them.
To deploy, zip the theme folder (named freeframe-fitness, without node_modules/) and upload it
in Appearance > Themes, or copy the folder to wp-content/themes/ on your server.
Translations#
The theme is translation-ready with the text domain freeframe-fitness. All text in patterns,
templates and PHP goes through WordPress's translation functions; the script's strings (class and
coach texts, day names, badges, dialog texts, form errors) are translated in PHP
(freeframe_fitness_script_strings() in inc/setup.php). Create a translation with Loco Translate, or
with WP-CLI and Poedit:
wp i18n make-pot wp-content/themes/freeframe-fitness wp-content/themes/freeframe-fitness/languages/freeframe-fitness.potSave freeframe-fitness-de_DE.mo (for example) in wp-content/languages/themes/ or in the theme's
languages/ folder. Page content created by Fitness setup is copied into your pages in the site's
language at that moment; translate it in the editor like any other content.
Child themes#
Use a child theme for changes you want to keep when Fitness is updated. Create
wp-content/themes/fitness-child/style.css:
/*
Theme Name: Fitness Child
Template: freeframe-fitness
*/Templates, parts, patterns and theme.json in the child theme override the parent's files of the
same name. To change the classes, coaches or timetable, copy assets/js/main.js to the child theme
and load it instead of the parent's:
add_action( 'wp_enqueue_scripts', function () {
wp_deregister_script( 'freeframe-fitness' );
wp_register_script( 'freeframe-fitness', get_stylesheet_directory_uri() . '/assets/js/main.js', array(), '1.0.0', array( 'in_footer' => true, 'strategy' => 'defer' ) );
wp_enqueue_script( 'freeframe-fitness' );
}, 15 );The parent's stylesheet only contains the classes the parent uses: if your child theme's patterns
use new utility classes, compile a stylesheet for the child (copy package.json and
src/theme.css, point the @source lines at the child's folders) and enqueue it.
Troubleshooting#
- A session doesn't appear. Its
type,coachandlocmust be existing ids, anddaymust be 0–6 (Monday–Sunday). - The class page shows Strength Lab instead of the class I linked. The
?type=value must be a key inFORGE.classes(any letter case:?type=BOXworks like?type=box, as on the timetable). - The coach page shows Maya Torres instead of the coach I linked. The
?id=value must be a key inFORGE.coaches(any letter case:?id=DEVworks like?id=dev, as with the timetable's?coach=). - A coach card disappears with every filter. Its group needs
ff-tag-<specialty>classes matching the filter buttons. - The open/closed status is an hour or more out. Set
TIMEZONEinassets/js/main.jsto the clubs' IANA zone name. - Bookings vanished. They're stored per browser; private windows and cleared site data reset them.
- The pages look unstyled or new classes do nothing. The stylesheet only contains classes it
found when it was built. Run
pnpm buildafter editing patterns, then reload. - "This block contains unexpected or invalid content". A pattern or page was edited by hand with markup that doesn't match the block. Click Attempt recovery, or re-insert the section from Patterns > Fitness.
- The header menu shows other pages, or an old menu. A Navigation block without a chosen menu shows the most recent one. Select it and choose "Main menu" (or yours) in the block settings, in both the desktop and the mobile Navigation block.
- Fitness setup says the pages exist, but they are empty. It never overwrites existing pages. Delete (and empty the trash of) the pages with those slugs, or open each page and insert its page pattern from Patterns > Fitness pages.
- The forms don't send anything. They are demo forms; see Forms.
- The theme can't be installed on an older WordPress. Fitness uses the Icon block's icon collections from WordPress 7.1; update WordPress first.
- A page shows the title twice or no title. Pages use a template without a title, because the page patterns start with their own heading. For a plain text page (privacy policy, terms), choose the Page with title template in the page's settings.
License#
MIT, which is compatible with the GPL that WordPress requires (see LICENSE.txt in the theme
folder). Free for personal and commercial use. The illustrations, portraits and pictograms are
included under the same license. Anton and Inter are licensed under the SIL Open Font License 1.1
(assets/fonts/anton/OFL.txt, assets/fonts/inter/OFL.txt).