Home: Built from widgets. This page is the six widgets of the home group, put together in Elementor: a hero, an events list, three calls to action and a blog list. 6 home widgets.
Home: Links for each location. The hero's location bar is a list the editors fill in: each location gets its own links behind Book a bay, Lesson and Event. Set per location in Elementor.
Home: It remembers the location. The header's location switcher keeps the visitor's choice in a cookie and in the browser. On a phone the same header opens a full-screen panel with the menu, the switcher and the social links. Cookie + browser storage.
Home: Phone to desktop, one stylesheet. The designs are drawn 1440 wide for a desktop and 376 for a phone. The plugin's SCSS mixins scale type and spacing between those widths and compile to 2,510 clamp() rules. 2,510 clamp() rules.
Home: Motion the editors control. All 35 widgets share one animation trait: in Elementor's Advanced tab the editor picks each part's animation, timing and stagger, and the page plays it as the part scrolls into view. 1 trait · 35 widgets.
How it works: Five steps, five widgets. This page is the learn group: a title, a widget for each of the five steps and a call to action, so each step is edited on its own. 7 learn widgets.
Location: A page per location. A location page is the location group (a hero, the section nav, info, pricing and a call to action) plus that location's trainers, lessons and events from the shared lists. 8 widgets, shared lists.
Location: A nav that stays. The section nav stays at the top as the page scrolls, with Info, Pricing, Trainers, Events and Lessons. Location nav widget.
Location: Prices by the day. Pricing is its own widget in the location group: a tab for each day, then the open bays, premium bays and VIP rooms with their hours and prices. Pricing widget.
Location: Lists shared by location. Further down, the trainers, events and group lessons are the shared lists: three data widgets that each pick up the location from the page. Staff photos and names are blurred here. 3 data widgets.
Lesson: The account header. Lesson, Event, My account and the booking app's page share one account header widget: tabs for Booking, Lesson, Event and Account, and a location select. 4 pages, 1 widget.
Lesson: Lessons from the shared list. The trainers and the lessons below them come from the location's shared lists: a trainer list and a data accordion whose rows open for the details. Staff photos are blurred here. Trainer list + data accordion.
Event: Events, the same accordion. The event page is the account header and one data accordion: the location's events with their date, time and price. 2 widgets.
Booking: A booking app, edited. Under the account header sits a licensed React booking app from another vendor. I edited it in 39 marked places: a prompt when a location is picked, a length of play that books a run of slots, and its header, footer and slot styles. 39 marked edits in 6 files.
Trainer: A page per trainer. A trainer's page opens with the trainer profile widget: photo, languages, background, training and contact. Staff details are blurred here. Trainer profile widget.
Trainer: Their lessons, then the team. Below the profile come two data accordions with that trainer's lessons, and the trainer list closes the page. 2 accordions + trainer list.
Blog post: A post from blog widgets. Posts are put together from the blog group's five widgets (a hero, text, an image carousel, a video carousel and a call to action), all edited in Elementor. 5 blog widgets.
Contact: A form with a location. The contact form widget wraps a Contact Form 7 form, adds a location select and caps the message at 500 characters. Contact form widget.
Log in: WooCommerce, in the brand. Log in and register is WooCommerce's account page. The plugin swaps its login form, dashboard and account navigation for its own templates through woocommerce_locate_template. 3 template overrides.
Payment: Checkout, styled by filters. Checkout stays WooCommerce's, with Stripe's card payment restyled through its filters: the payment title, a Powered by Stripe badge and the card fields' styling. Stripe filters.
Behind it
How it was built, run and handed over.
01
02
03
04
05
06
07
08
09
10
01 · The approach
A new design, on top of the old theme.
The contract was a frontend redesign from the new Figma designs. Everything new ships in one plugin, so it survives theme and WordPress updates and the client's theme stays untouched: a theme layer, a widget library for the editors, the account and checkout templates, and its own updater. The plugin and the booking data behind the shared lists went beyond the contract; an addendum on 17 January 2026 covered them.
One plugin, birdiegolf-core
02 · What stays
The theme, Elementor and the forms.
The client's ThemeREX theme stays the active theme, with Elementor and Elementor Pro to build pages and Contact Form 7 for the forms. The plugin layers on top of each of them.
Nothing replaced
03 · The theme layer
A new look, the theme left alone.
38 PHP template parts; the design's palette as SCSS tokens (ten browns, golds and creams, five darks) and fluid mixins, compiled to one style.css; the header as ES modules (location switcher, mobile panel, accessibility); and 31 self-hosted font files behind a loading guard.
38 template parts · 31 font files
04 · Inside a widget
Controls in a class, markup in a part.
Each widget is a class that holds only its Elementor controls, with its markup in a template part and its styles in an SCSS partial (37 of them, in 9 groups). An animation trait adds type, timing and stagger, and scoped CSS with an IntersectionObserver plays it.
Class · template part · SCSS partial
05 · Shared lists
One list per location.
Lessons, events and trainers are kept in wp_options as one shared list per location. Each data widget works out its location from the page and its list type from the section title; editors add, rename and delete locations in Elementor.
wp_options, per location
06 · Account and updates
Checkout in the brand, updates in WordPress.
WooCommerce's account dashboard, login and navigation templates are swapped through woocommerce_locate_template, with Stripe's title, badge and card fields styled through filters. New versions arrive on the normal update screen.
Template overrides + updater
07 · For the editors
31 widgets, in 8 groups.
I built 35 Elementor widgets; 31 are registered for the client's editors, in the groups they see in Elementor, so a new page is put together from parts that already look right.
35 built · 31 registered
08 · Delivery
A column for every page, on the design file.
The work was run from a board on the design file's second page: four phases, from collecting the client's material through review and delivery to archive, then a column for each group of pages with its tasks and its deliverables, ticked as they shipped. The last column is the plugin's own work: page modules, module editing, card editing and extra menu data.
4 phases · 9 columns
09 · Releases
37 releases, no zips by hand.
One script publishes a release to a storage bucket; the plugin's own updater finds it, and the client installs it from the normal Plugins screen. 1.0 to 1.8.5, the last on 25 April 2026.
Self-hosted update channel
10 · Handover
A manual, 12 page templates and two helper plugins.
The editors got a widget guide, a manual in 11 chapters (laid out in the same design file and exported as PDFs) and 12 Elementor page templates to start new pages from. Two small plugins, made with AI tools (their headers name OpenAI and Antigravity), helped the move: one copies the Elementor content, media and the plugin's settings between sites without the orders, the other puts a product in an empty cart to test the checkout.
11 chapters · 12 templates · 2 helpers
01 · The approach
birdiegolf-core1.8.5
Client's site
UnchangedThemeREX “N7 Golf Club” theme
Page builderElementor + Elementor Pro
FormsContact Form 7
Databasewp_options
CommerceWooCommerce + Stripe
birdiegolf-core
Theme layerlayers on top of
Widget libraryregisters widgets
Contact Form widgetwraps a CF7 form
Data widgetsreads / saves lists
Account and checkouttemplate overrides
Self-hosted updates
One plugin builds on what was there.
01 · The approach
A new design, on top of the old theme.
The contract was a frontend redesign from the new Figma designs. Everything new ships in one plugin, so it survives theme and WordPress updates and the client's theme stays untouched: a theme layer, a widget library for the editors, the account and checkout templates, and its own updater. The plugin and the booking data behind the shared lists went beyond the contract; an addendum on 17 January 2026 covered them.
One plugin, birdiegolf-core
birdiegolf-corewhat stays
0 of 3 kept as they were
Unchanged
ThemeREX “N7 Golf Club” + child theme
Still the active theme.
Unchanged
Elementor + Elementor Pro
Editors compose pages, header and footer.
Unchanged
Contact Form 7
The form, set up in Contact Form 7.
02 · What stays
The theme, Elementor and the forms.
The client's ThemeREX theme stays the active theme, with Elementor and Elementor Pro to build pages and Contact Form 7 for the forms. The plugin layers on top of each of them.
Nothing replaced
birdiegolf-corebirdiegolf/
Birdie Golf
Birdie Golf
<html class="birdiegolf-fonts-ready">
0 s3 s fallback
Brand fonts in
Header as ES modules
index.js
nav.js
location.js
mobile-panel.js
a11y.js
type="module" defer
League Gothic
Inria Sans 400
Inria Sans 700
Inter
03 · The theme layer
A new look, the theme left alone.
38 PHP template parts; the design's palette as SCSS tokens (ten browns, golds and creams, five darks) and fluid mixins, compiled to one style.css; the header as ES modules (location switcher, mobile panel, accessibility); and 31 self-hosted font files behind a loading guard.
Each widget is a class that holds only its Elementor controls, with its markup in a template part and its styles in an SCSS partial (37 of them, in 9 groups). An animation trait adds type, timing and stagger, and scoped CSS with an IntersectionObserver plays it.
Class · template part · SCSS partial
birdiegolf-corewp_options
Page the widget is on
Section title
1Page slug or permalink
2?location= in the URL
3Request path
4Widget's default
Reads
birdiegolf_global_lessons_location-b
Lesson 1location-b
Lesson 2location-b
Lesson 3location-b
3 of 6 shown
birdiegolf_locations: location-a, location-b
05 · Shared lists
One list per location.
Lessons, events and trainers are kept in wp_options as one shared list per location. Each data widget works out its location from the page and its list type from the section title; editors add, rename and delete locations in Elementor.
wp_options, per location
birdiegolf-corebirdiegolf/woocommerce/
woocommerce_locate_template
WooCommerce asks for
myaccount/dashboard.php
as WooCommerce found it
myaccount/form-login.php
as WooCommerce found it
myaccount/navigation.php
as WooCommerce found it
checkout/form-checkout.php
as WooCommerce found it
Stripe title, badge and card fields: filters
Plugins
Birdie Golf Core
Version 1.8.4 |
How a release arrives
deploy.js puts the zip and info.json in an R2 bucket
2The updater reads info.json, cached for a day
3WordPress lists the update
4Installed from the Plugins screen
06 · Account and updates
Checkout in the brand, updates in WordPress.
WooCommerce's account dashboard, login and navigation templates are swapped through woocommerce_locate_template, with Stripe's title, badge and card fields styled through filters. New versions arrive on the normal update screen.
Template overrides + updater
Widget library31 registered · 35 built
Home
Hero
Call to action 1
Events list
Blog list
Call to action 2
Call to action 3
Learn
Title
Call to action
Step 1
Step 2
Step 3
Step 4
Step 5
Location
Hero
Info
Pricing
Call to action
Section nav
Blog
Hero
Call to action
Video
Image
Text
Trainer
Profile
Trainer list
Account
Account header
Accordion
Data accordion
Other
Header
Footer
Contact banner
Contact form
07 · For the editors
31 widgets, in 8 groups.
I built 35 Elementor widgets; 31 are registered for the client's editors, in the groups they see in Elementor, so a new page is put together from parts that already look right.
35 built · 31 registered
Delivery boardPage 2 of the design file, as last left
Collect
Manage / Review
Deliver
Store / Archive
Main componentsHeader and footer
Ticked on the boardHeader
Ticked on the boardHeader, phone
Ticked on the boardLocation switcher
Ticked on the boardFooter
Ticked on the boardFooter, phone
Home
Ticked on the boardHero
Ticked on the boardEvents
Ticked on the boardCTA 1
Ticked on the boardCTA 2
Ticked on the boardCTA 3
Ticked on the boardBlogs
LearnHow it works
Ticked on the boardTitle
Ticked on the boardSteps 1 to 5
Ticked on the boardCTA
GreenjoyBlog post
Hero, text, video, CTA
or a longer post with images
Trainer
Profile
Private lessons
Group lessons
Trainer cards
OtherContact
Contact form
AccountNo tasks
LocationNo tasks
Plugin
Ticked on the boardPage modules
Module editing
Ticked on the boardCard editing
Ticked on the boardExtra menu data
Ticked on the boardTicked on the board
08 · Delivery
A column for every page, on the design file.
The work was run from a board on the design file's second page: four phases, from collecting the client's material through review and delivery to archive, then a column for each group of pages with its tasks and its deliverables, ticked as they shipped. The last column is the plugin's own work: page modules, module editing, card editing and extra menu data.
4 phases · 9 columns
Releases37 of 37
1.8.5
Prevent visible fallback font swap when optimization plugins rewrite font-display.
1.0Drag along the releases1.8.5 · 25 Apr 2026
09 · Releases
37 releases, no zips by hand.
One script publishes a release to a storage bucket; the plugin's own updater finds it, and the client installs it from the normal Plugins screen. 1.0 to 1.8.5, the last on 25 April 2026.
Self-hosted update channel
Handover kitGuides · 11-chapter manual
Home
How it works
Location
Lesson
Event
Trainer
Blog
Contact
My account
Booking app
Header
Footer
10 · Handover
A manual, 12 page templates and two helper plugins.
The editors got a widget guide, a manual in 11 chapters (laid out in the same design file and exported as PDFs) and 12 Elementor page templates to start new pages from. Two small plugins, made with AI tools (their headers name OpenAI and Antigravity), helped the move: one copies the Elementor content, media and the plugin's settings between sites without the orders, the other puts a product in an empty cart to test the checkout.