Skip to main content

02 · Client work

Birdie Golf.

The WordPress plugin behind an indoor golf center's redesign: 35 widgets, 31 of them for its editors, shipped in 37 releases.

Full Stack EngineerNov 2025 ⁠–⁠ May 2026Seasonal, remote from Canada

Designs by Jingwen (Catherine) Zhang

35
Elementor widgets built
31
for the editors, in 8 groups
37
releases, 1.0 to 1.8.5
2,510
fluid clamp() rules
  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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.
  7. 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.
  8. 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.
  9. 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.
  10. 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.
  11. 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.
  12. 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.
  13. 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.
  14. 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.
  15. 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.
  16. 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.
  17. 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.
  18. 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.
  19. 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.
  20. 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.

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.

38 template parts · 31 font files
birdiegolf-coreinc/elementor/traits/
  1. Widget class

    Elementor controls only

  2. Template part

    Markup in birdiegolf/parts/

  3. Animation trait

    Type, timing, stagger

  4. Scoped CSS + observer

    Plays at 15% in view

Advanced › Entry Animations

Animation Type

Widget on the page
Card 1
Card 2
Card 3
Card 4

Generated CSS

.elementor-element-3f2a > * {  opacity: 0; transform: translateY(30px);  transition: opacity 800ms ease-out,    transform 800ms ease-out;}/* + inline delays, 150 ms apart */

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
birdiegolf-corewp_options

Page the widget is on

Section title

  1. 1Page slug or permalink
  2. 2?location= in the URL
  3. 3Request path
  4. 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

  1. deploy.js puts the zip and info.json in an R2 bucket
  2. 2The updater reads info.json, cached for a day
  3. 3WordPress lists the update
  4. 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
  1. Collect
  2. Manage / Review
  3. Deliver
  4. 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

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

11 chapters · 12 templates · 2 helpers

Stack

Figma · WordPress · Elementor · WooCommerce · PHP · SCSS · JavaScript · Cloudflare R2

Record

Addendum on 17 Jan 2026; 114 commits on GitHub, Dec 2025 ⁠–⁠ Jan 2026; the last release, 1.8.5, on 25 April 2026.

Next case study · 03 · Data and MLWhy-Fires.