Freeframe

Documentation

Tempo

v1.0.3Updated Oct 17, 2026A bold, poster-printed website for a band or musician, with a real track player, tour dates, a merch store and a press kit.

Overview#

Tempo is a website for a band or solo musician, built as a WordPress block theme. The fictional band is Marigold Static, a four-piece indie band from the Southside of Glasgow (Isla Carrick, Reuben Oyelaran, Fenna de Wit and Callum Shiach) whose second album, Weather for Two, is about to come out on the fictional Lantern House Records. The theme has a home page with a release countdown, a remembered pre-save button, the new album's track list, a click-to-play video poster, the next four shows, a merch preview and a fan-club sign-up; a discography with a working track player; tour dates with filters and calendar downloads; a merch store with a bag; an about page; an electronic press kit (EPK); a contact form that routes messages by topic; and a 404 page. The look is a printed gig poster: warm paper, black ink, fluorescent pink, cobalt and sun-yellow "riso" inks, condensed poster headlines and SVG artwork throughout. It is version 1.0.0 (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/tempo.

The pages (headings, copy, the band, the timeline, quotes, contacts, buttons) are core WordPress blocks arranged in block patterns, so you edit them in the block editor without touching code. The music works as in the HTML version: the releases, tour dates, products and contact topics are lists in assets/js/main.js, and the script draws the album covers, track lists, the discography, the tour list, the merch store, the player bar and the bag from them. There are no photos: every cover, mock-up and portrait is SVG. Colours and fonts are design tokens in theme.json: change them in Appearance > Editor > Styles and the whole site follows. The design is light only.

Quick start#

Requirements: WordPress 7.1 or newer and PHP 7.4 or newer. No plugins are needed.

  1. Download tempo.zip from freeframe.dev/templates/tempo (the WordPress tab).
  2. 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 Tempo.
  3. Go to Appearance > Tempo setup and click Create the demo pages. This creates the Home, Music, Tour, Merch, About, Press kit and Contact pages 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.
  4. In Settings > General, the demo uses "Marigold Static" as the Site Title: the header and footer show it next to the logo, and the script uses it as the band's name in calendar files and the fact sheet download. Set your own.
  5. Visit your site. 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:

sh
pnpm install
pnpm dev     # builds the CSS, then starts WordPress with the theme at http://127.0.0.1:9400

pnpm 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 > Tempo pages) made of section patterns. Every section is also offered on its own in the block inserter under Patterns > Tempo.

Page Slug Page pattern Sections (pattern files in patterns/)
Home / page-home.php home-hero (#presave: countdown, pre-save, "Play Glasshouse", platform links), ticker, the-record (album notes and track list), video, next-shows, merch-preview, press-quote, fan-club (#join)
Music /music/ page-music.php music-hero (release counts), releases (type filter and grid), release-detail (#release), hold-it
Tour /tour/ page-tour.php tour-hero, tour-dates (#dates: filters, dates, past shows), tour-notify (#notify), tour-faq
Merch /merch/ page-merch.php merch-hero, merch-products (category filter and cards), shipping-sizing
About /about/ page-about.php about-hero, story, members, timeline, what-people-say
Press kit /press/ page-press.php press-hero, fact-sheet (fact sheet and bio tabs), downloads (#downloads, tech rider), press-quotes, press-contacts (#contacts)
Contact /contact/ page-contact.php contact-hero, contact-form (topic-routed form and direct contacts)
404 any missing URL templates/404.html 404

Links worth knowing: /music/?type=ep&release=night-bus-hymns#release, /tour/?region=europe&country=Germany, /tour/?notify=paris#notify, /merch/?cat=apparel, /merch/#p-glasshouse-tee and /contact/?topic=booking. The script links to the Music, Tour and Merch pages by their slugs: if you rename one, change its URL in freeframe_tempo_page_urls() in inc/setup.php too. 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-tempo/
  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 (with its strings and page URLs), editor styles,
                            the `js` class script, the paper grain body class, 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      the Dark and Ghost button styles
  inc/site-setup.php        Appearance > Tempo 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         band data, artwork, audio, player, pages, cart, press kit, forms
  assets/icons/             SVG icons for the Icon block and the platform links
  assets/img/               logo, band and member portraits, favicon, social image
  assets/fonts/             Familjen Grotesk, Big Shoulders Display and Space Mono, with their licenses
  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-tempo.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.txt

assets/js/main.js starts with a short WordPress adapter, then follows the HTML version's numbered sections: band data, helpers, artwork, audio, player interfaces, home page, music page, tour page, merch and cart, press kit, forms, header and reveals, back to top. Search for a section title to find your way around.

Customising#

Brand and colours#

The colours are in theme.json (settings.color.palette) and in Appearance > Editor > Styles > Colors > Palette:

Colour Default Used for
Base (Paper) #f4efe6 the page background
Paper 2, Card, Line #ebe4d6, #fbf8f2, #d9d0bf alternate sections; cards, fields and the bag; hairlines on paper
Contrast (Ink) #15130f text, borders, hard shadows and the dark sections
Ink 2, Ink line #24211b, #3a352c raised surfaces and hairlines on ink
Soft, Subtle #4b463d, #686155 secondary text and captions on paper
Fog, Haze #bdb5a6, #9c9485 secondary text and captions on ink
Pink, Pink deep, Pink soft #ff4d94, #c4155f, #ffc2da the brand: primary buttons, chips and highlights (with ink text); pink text and links on paper; chosen options
Cobalt, Cobalt soft #2b3fd6, #c9cff7 the second ink: the fan-club section and focus rings
Sun #ffcf33 the third ink: highlights, stickers and success boxes

The stylesheet reads these colours from the palette (bg-pink, text-soft...), so changing Pink in Styles recolours every primary button and chip at once. Keep Pink deep at a 4.5:1 contrast with the paper. A few colours are written directly into SVG: the logo (assets/img/logo.svg and logo-light.svg), the hero record, the video poster, the 404 record, the bag illustrations, the album covers (each release's art) and the merch mock-ups in assets/js/main.js; search for #ff4d94 to update them too.

Dark sections carry the class on-ink. Inside them buttons, links, meta text and form fields switch to their light-on-dark versions automatically.

Typography#

Three fonts are bundled in assets/fonts/ and declared in theme.json (settings.typography.fontFamilies), so no request goes to Google: Familjen Grotesk (variable, 400 to 700 with italics; preset familjen-grotesk) for text, Big Shoulders Display (700 to 900; preset big-shoulders-display) for the condensed poster headlines (poster, h-hero, h-page, h-section, font-display) and Space Mono (400 and 700; preset space-mono) for labels, dates and prices (meta, eyebrow, chip). To use other fonts, install them in Styles > Typography > Fonts (the Font Library) and select them, or replace the fontFace entries in theme.json. The utility classes read the presets, so keep those slugs. The generated album covers name Big Shoulders Display and Space Mono directly (font-family="Big Shoulders Display, …").

Font sizes are the presets in theme.json (xs 0.75rem to 9xl 8rem, the Tailwind scale).

Content#

Edit pages in Pages: click any text to change it, select a button to change its link. 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 > Tempo.

What the script draws or fills is in Custom HTML blocks: edit those in the block's HTML view, and keep their ids and data- attributes.

Pattern Custom HTML What it is
home-hero the rings, the countdown (data-countdown), the pre-save and play buttons, the record and cover (.hero-art) see New release countdown and pre-save
ticker the scrolling list of tour cities hidden from screen readers (decoration)
the-record <div data-new-tracklist> the new album's track list
video the poster (data-video) and its note see Video poster
next-shows, merch-preview <ul data-next-shows>, <ul data-merch-preview> the next four shows and three products from the data
fan-club, tour-notify the forms and their success boxes see Forms
releases, release-detail the type filter, the count, <ul data-release-grid>, <div data-release-detail> see Discography
tour-dates the region filter, the country select, the count, <ul data-tour-list>, the empty state and the past shows see Tour dates and calendar files
merch-products the category filter, the count and <ul data-merch-grid> see Merch and cart
fact-sheet, downloads, press-contacts the word count, tabs, bio panels, copy and download buttons, the cover and logo art, the tech rider see Press kit
contact-form the form and its success box see Forms
404 the record illustration decoration
header, footer the bag and menu buttons, the big band name, the player bar, the bag drawer and the toast driven by the script

A few blocks carry a class (Block settings > Advanced > Additional CSS class(es)) that the script or stylesheet reads:

  • ffd-<name>--<value> is turned into the attribute data-<name>="<value>" when the page loads (blocks can't store data attributes): ffd-release-state--soon and ffd-release-state--out on the home hero's two text groups, ffd-release-date on the release date in the hero text and ffd-fact on each fact sheet row (the fact sheet download reads them). On a Button block the attribute goes on its link.
  • ff-hidden hides a block until the script decides to show it (the "out now" text, the mobile menu); the editor always shows it, with a dashed outline.
  • reveal fades a block in as it scrolls into view.
  • link-card on a group makes the whole group clickable (the 404 cards, the logo).
  • eyebrow (mono label with a pink dot), meta (mono caption), lead, h-hero, h-page, h-section, poster, card, card-shadow, chip (with chip-pink, chip-sun, chip-ink, chip-paper), hl (the sun highlighter), stroke-text, prose-bio and dropcap are the HTML template's text and surface styles.

The band data

Everything that is listed, filtered or played is at the top of assets/js/main.js:

Constant What it holds
BAND The name (the Site Title), site URL, email, currency and locale for prices and dates, freeShippingFrom, shipping, keepDemoDatesFresh
RELEASES Every release, newest first: id, type (album, ep, single), title, date, label, cat, art, merch, blurb, credits, tracks
REGIONS, TOUR Tour regions and shows: id, date, time (doors), city, country, region, venue, status, onSale, note, support, optional tickets
PRODUCTS, MERCH_CATS Merch: id, name, cat, kind, price, badge, desc, optional release, colours, sizes, soldOut
CONTACT_TOPICS Contact form topics, who each one goes to and the reply time

Add a release by putting a new object at the start of RELEASES:

js
{ id: 'harbour-ep', type: 'ep', title: 'Harbour', date: '2027-05-07', label: 'Lantern House Records', cat: 'LHR034',
  art: { bg: '#c9cff7', a: '#ff4d94', b: '#15130f', ink: '#15130f', motif: 'moons' },
  blurb: 'Four songs recorded on a ferry.', credits: 'Produced by Ailsa Braid.',
  tracks: [{ title: 'Harbour', len: '3:40', early: true }, { title: 'Tide Tables', len: '4:05' }] }

The first release in the list is the "new release" the home page counts down to. A release dated in the future shows "Out <date>"; only tracks with early: true can be played before release day. Track ids are generated as <release id>-<track number> (for example weather-for-two-2). The home hero's headline, copy and sticker and the the-record notes are page content: update them in the editor when you change the new release.

Add a show to TOUR:

js
{ id: 'oslo', date: '2026-12-13', time: '20:00', city: 'Oslo', country: 'Norway', region: 'europe',
  venue: 'Kulturhuset Sal 2', status: 'on-sale', support: 'Twin Ferries', tickets: 'https://tickets.example/oslo' }

status is on-sale, low (shows "Last tickets"), sold-out (shows a waitlist link) or soon (shows "On sale <onSale>" and a reminder link). Without tickets, ticketUrl() builds a placeholder address. Shows dated before today move to "Past shows" automatically, above the past shows written in the tour-dates pattern. The counts on the filter buttons are filled in by the script.

Demo dates: BAND.keepDemoDatesFresh moves the whole tour, and the new album (the release with campaign: true), forward to the same weeks of the next year once every tour date has passed, by whole weeks so weekdays never change (Friday 13 November 2026 becomes Friday 12 November 2027). The demo then has an album on the way and upcoming shows again. Set it to false on your real site.

The dates written in the patterns follow RELEASES and TOUR (initDemoText()): the announcement bar's data-release-note ("Weather for Two is out 13 November", then "is out now"), data-release-day (the fact sheet, the bios and the about timeline, with data-release-year), data-tour-years ("2026–27", also in the tour page title) and data-tour-from ("November 2026"). Once the new album is out, the buttons with the class ffd-release-cta--<key> (the "Pre-save" buttons, "Pre-order the LP") and the "Hear the first single" link read their text from RELEASE_CTA instead (listen, listen-album, listen-now, buy-lp; translated in inc/setup.php), products with the "Pre-order" badge show "Out now", and a show marked status: 'soon' goes on sale on its onSale date.

Add a product to PRODUCTS:

js
{ id: 'beanie', name: 'Static beanie', cat: 'apparel', kind: 'tee', price: 20,
  desc: 'Ribbed knit with a woven label.', colours: [{ name: 'Pink', hex: '#ff4d94' }],
  sizes: ['One size'] }

kind picks the SVG mock-up (lp, cd, cassette, tee, hoodie, poster, tote or pins); lp and cd use the cover of the release you set. Products with more than one colour get swatches; products with sizes require a size before they can be added. Sizes listed in soldOut are shown crossed out and disabled. The home page's merch preview shows the products listed in initHome() (['wft-lp', 'glasshouse-tee', 'tour-poster']).

Edit assets/js/main.js in a child theme (copy it there and load it instead, see Child themes), or your releases, dates and products are overwritten when you update the theme. Release, show and product names and descriptions are data, not translatable strings.

Images and artwork#

There are no photos. All artwork is SVG:

  • Album covers are drawn by cover() in assets/js/main.js from each release's art object: bg (paper colour), a and b (two inks), ink (title colour) and motif (weather, glass, river, signal, moons, bus, sun or grid). Any element with data-cover="<release id>" gets the cover painted into it (the home hero and the press kit's download card); add data-cover-label="" when the cover is decorative. To use real artwork, return an <img src="…" alt=""> string from cover() instead (square, at least 1200×1200).
  • Merch mock-ups are drawn by productArt() for the selected colour, in a 4:5 frame. Replace them with 4:5 product photos the same way.
  • Band and member portraits are assets/img/band.svg (3:2, 1200×800) and assets/img/member-*.svg (4:5, 400×500) in Image blocks: select one and Replace it with a photo of the same ratio, and keep the alt text accurate. The press kit's band illustration download link points at assets/img/band.svg in its Custom HTML block.
  • The hero record, the video poster, the 404 record and the bag illustrations are SVG in Custom HTML blocks.

The logo is assets/img/logo.svg (and logo-light.svg in the footer) in an Image block next to the Site Title block in the header and footer. The platform icons before the "Pre-save or listen on" links and the arrow in "All tour dates" are drawn with CSS masks (icon-before-spindle, icon-after-arrow-right...; artwork in src/icons.css), and the icons in the merch hero and the fan-club list are Icon blocks: select one and pick another from the Tempo collection or WordPress's own. The social image is assets/img/og-image.png (1200 × 630, source og-image.svg); the favicon assets/img/favicon.svg is used until you set a Site Icon in Settings > General.

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 pattern starts with the "New album" announcement bar; edit or delete it there.

The header has two Navigation blocks showing the same menu: the desktop one and the one in the mobile menu. Edit the menu once in Appearance > Editor > Navigation (the setup creates "Main menu": Home, Music, Tour, Merch, About, Press kit, Contact). Its first link (Home) is hidden in the desktop header, as in the HTML version, because the logo links home; keep that order, or remove the .site-nav … :first-child rule in src/theme.css. 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, for both blocks.

The footer's three link columns are List blocks: click a link to change it, press Enter to add one. The copyright year is printed by PHP. The big band name, the player bar, the bag drawer and the toast are Custom HTML blocks at the end of the footer pattern; keep them on every page, because the player and the bag work everywhere.

Features#

The player#

Any button with data-track="<track id>" plays that track; data-play-release="<release id>" plays a release from its first playable track (add data-play-track="<track id>" to start somewhere else, and data-label-play for its label). Pressing a playing button again pauses it. Playing a track queues the rest of its release, so previous and next work.

The player bar (#player, in the footer pattern) slides up when something is loaded. It has previous, play/pause, next, a seek slider, mute and close. Every track list shows the current track with an equaliser and aria-current, and play buttons switch to pause.

Previews without audio files: each track's preview is a 30-second piece of music synthesised in the browser by Synth (a pad, bass, arpeggio and drums in a key, tempo and chord progression picked from the track id), encoded as a WAV file and played by a normal <audio> element. To play your real audio, upload the files to Media and give the track a src with the file's URL:

js
{ title: 'Glasshouse', len: '3:41', early: true, src: 'https://your-site.example/wp-content/uploads/2026/10/glasshouse-preview.mp3' }

Remove the "30-second preview" labels in trackRow() and the "Preview" chip in the footer pattern's player bar if you play full tracks. The player also sets Media Session metadata, so lock screens and media keys work.

Keyboard: all controls are buttons and the seek slider takes arrow keys. Space or K plays and pauses, Shift+N and Shift+P skip tracks (ignored while typing or when a button or link has focus). A polite live region announces the track that starts playing.

New release countdown and pre-save#

initRelease() reads the first release in RELEASES. Before its date the hero shows the countdown (data-countdown), the pre-save button and "Play Glasshouse"; on release day the group with the class ffd-release-state--soon hides and the one with ffd-release-state--out shows (in the editor both are visible; the "out" one has a dashed outline). The date in the hero text (ffd-release-date) is written from the release's date. The pre-save button (data-presave) toggles and remembers its state in localStorage; point it at your pre-save service by replacing it with a link, or call that service in its click handler.

Video poster#

The video section (data-video) shows an SVG poster and loads nothing until someone presses play. Put your embed address in data-video-embed (in the video pattern's Custom HTML) and the button swaps the poster for an iframe:

html
<div data-video data-video-embed="https://www.youtube-nocookie.com/embed/VIDEO_ID" data-video-title="Glasshouse (official video)" …>

With data-video-embed empty (the demo), pressing play plays the preview of the release named in data-video-release and animates the poster.

Discography#

The Music page renders the release grid and detail from RELEASES. The filter buttons (data-type-filter) update the grid, the live count and the ?type= parameter; any link with data-open-release shows that release in the detail section, moves focus to its title and updates ?release=. Without ?release= the detail shows the newest album that is out. "Copy link" copies a link straight to the release. The counts in the music-hero pattern are page content: update them when you add releases.

Tour dates and calendar files#

The Tour page renders upcoming shows from TOUR. The region buttons (data-region) and the country select (#tour-country) filter the list and keep ?region= and ?country= in the address; an empty state offers a reset. "Calendar" buttons (data-ics) download an .ics file built by icsFor() with the doors time, venue, support act and ticket link; it works in Apple Calendar, Google Calendar and Outlook. "Waitlist" and "Remind me" link to /tour/?notify=<show id>#notify, which fills in the notify form's town and country. The home page shows the next four shows with the same markup.

Merch and cart#

Product cards are rendered from PRODUCTS. Choosing a colour redraws the mock-up; "Add to bag" checks that a size is chosen, adds the item (merging identical colour and size) and opens the bag. The bag (#cart, in the footer pattern) is a modal dialog: focus moves into it and is kept there, Escape or the backdrop closes it and focus returns to the button that opened it. It has quantity steppers (1–10), remove buttons, the subtotal, a free-shipping meter based on BAND.freeShippingFrom and a shipping line based on BAND.shipping. Every bag button (data-cart-open) shows the item count.

Checkout is a demo step. "Checkout" shows an order summary; "Place demo order" empties the bag and says clearly that nothing was charged. To sell for real, replace the data-place-order branch in initCart() with a redirect to your store, for example a Shopify cart permalink or a Stripe Payment Link built from cart:

js
if (e.target.closest('[data-place-order]')) {
  window.location.href = 'https://your-shop.example/cart/' + cart.map(function (l) { return l.id + ':' + l.qty; }).join(',');
}

If you sell with WooCommerce instead, link the merch buttons to your shop pages and remove the merch-products pattern's Custom HTML.

Press kit#

The bio tabs follow the ARIA tabs pattern (arrow keys, Home and End move between Short, Medium and Long). "Copy this bio" copies the visible bio as plain paragraphs and the word count updates with the tab. The bios are in Custom HTML blocks (#bio-short, #bio-medium, #bio-long); edit their paragraphs there. The downloads are generated in the browser: data-download="cover" saves the new album's cover as SVG, logo saves #logo-art, bio saves all three bios as text, facts saves every fact sheet row (the groups with the class ffd-fact: the first paragraph is the label, the second the value) and rider saves #rider-text. The band illustration is a plain download link to assets/img/band.svg. Contact cards have data-copy buttons that copy an email address.

Header, menu, reveals and back to top#

The header gets a border once you scroll. Below 1024 px the menu button opens a full-height menu (aria-expanded, Escape closes it and returns focus; "Your bag" closes it and opens the bag). Blocks with reveal fade in as they scroll into view; with reduced motion they are visible straight away and the ticker, equalisers and vinyl stop moving. The back-to-top button appears after one screen, lifts itself above the player bar and toasts, and moves focus to the main content. To remove it, delete the "Back to top" part of assets/js/main.js 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, the pink button with a hard shadow), Dark (ink), Ghost (and Outline)

The classes is-large and is-small on a Button block are the HTML template's btn-lg and btn-sm; icon-after-arrow-right adds the arrow. The FAQ answers on the Tour page are Details blocks with the class faq; the plus that turns into a cross is drawn by CSS. Add one by duplicating an answer in List View.

Forms#

All forms with data-validate are checked by initForms() in assets/js/main.js: required fields, email format, minimum lengths, patterns, dates that can't be in the past and required checkboxes and radio groups. Errors appear under each field (linked with aria-describedby), the first invalid field gets focus, and fields re-check as you fix them. Field names in messages come from data-name; checkbox and radio messages from data-required-msg. On success the form is replaced by its success box (data-success="<id>"), which takes focus.

Form Where Checks
#join-form (Static Club) Home (fan-club) Region, email, consent checkbox
#notify-form (tour alerts) Tour (tour-notify) Email, town or city, country, travel distance
#contact-form Contact (contact-form) Topic, name, email, message (30+ characters, with a live counter) and the topic's own fields

Contact routing: choosing a topic shows that topic's fields (data-topic-fields) and says who the message goes to, from CONTACT_TOPICS. Booking is routed by the "Where is the show?" select: North America goes to the US agent, everything else to the UK and Europe agent. Fields marked data-required are only required while their topic is selected; dates marked data-future can't be in the past. /contact/?topic=booking preselects a topic (the footer's Booking and Sync links use it).

Nothing is sent, and the success messages say so. To collect submissions, use a form plugin (replace the form's Custom HTML block with the plugin's block; add the classes field, field-label and btn-primary to its fields, labels and button), or post the form in onValid() in section 11 of assets/js/main.js (in a child theme), for example to Formspree:

js
function onValid(form) {
  fetch('https://formspree.io/f/your-form-id', {
    method: 'POST', body: new FormData(form), headers: { Accept: 'application/json' }
  }).then(function (res) {
    if (!res.ok) throw new Error();
    // then show the success box as before
  }).catch(function () { toast('Sorry, that didn’t send. Please email us instead.'); });
}

Use a different endpoint per form by checking form.id, then change the success text in the patterns to say the message was received. For the fan-club and tour-alert forms you can post to your mailing list provider's form endpoint instead. Every field has a name; keep one on any field you add.

Saved data (localStorage)#

Key Shape Notes
tempo-cart [{ "id": "glasshouse-tee", "colour": "Pink", "size": "L", "qty": 2 }] Read by readCart(), which drops unknown products, colours or sizes, sold-out sizes and bad quantities, and caps quantities at 10
tempo-presave true or false The pre-save button's state

Values are read with readJSON(), which falls back to defaults on missing or broken data, and written with writeJSON(), which ignores storage errors. To reset, run localStorage.removeItem('tempo-cart') (or tempo-presave) in the browser console, or clear the site's data. "Place demo order" also empties the bag.

SEO and sharing#

Page titles come from WordPress: the page title, an em dash and the site name (for example "Tour — Marigold Static"); the home page shows the site name and tagline (Settings > General). 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. WordPress already generates a sitemap at /wp-sitemap.xml. For richer search results you can add MusicGroup and MusicEvent structured data built from your band details and tour dates (some SEO plugins let you add custom schema). Releases and shows are drawn by JavaScript; if search visibility for each release matters, give each one a page or post of its own.

Accessibility#

Built in: a skip link to the main content (added by WordPress, styled by the theme), landmarks (header, labelled navigation, main, footer, the labelled player bar), one h1 per page, labelled sections, visible focus rings (cobalt on paper, yellow on ink), aria-current="page" on the current menu item, the menu button with aria-expanded (Escape closes the menu), filters as toggle buttons with aria-pressed and live result counts, track buttons that say "Play" or "Pause" with the track name, a player that announces the new track, the bag as a modal dialog with focus trapping and Escape, the press bios as ARIA tabs, FAQ answers in <details>, form errors announced through aria-describedby and aria-invalid, success boxes that receive focus, and reduced-motion support for every animation. The demo passes axe (WCAG 2.1 A and AA).

When you edit: keep one Heading 1 per page, give images alt text (or mark them decorative), keep link text meaningful, label any field you add to a form (<label for="...">) and check the contrast of any new colour pairs.

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/:

sh
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, template or the script's markup, 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-tempo, 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-tempo. All text in patterns, templates and PHP goes through WordPress's translation functions; the script's strings (player labels, tour and bag text, release types and regions, contact routes, toasts and form messages) are translated in PHP (freeframe_tempo_script_strings() in inc/setup.php). Release, show and product data are in assets/js/main.js, and prices and dates are formatted with BAND.locale (en-GB). Create a translation with Loco Translate, or with WP-CLI and Poedit:

sh
wp i18n make-pot wp-content/themes/freeframe-tempo wp-content/themes/freeframe-tempo/languages/freeframe-tempo.pot

Save freeframe-tempo-de_DE.mo (for example) in wp-content/languages/themes/ or in the theme's languages/ folder. Page content created by Tempo 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 Tempo is updated (your releases, dates and products in particular). Create wp-content/themes/tempo-child/style.css:

css
/*
Theme Name: Tempo Child
Template: freeframe-tempo
*/

Templates, parts, patterns and theme.json in the child theme override the parent's files of the same name. To use your own copy of assets/js/main.js, dequeue the parent's script (freeframe-tempo) and enqueue yours in the child's functions.php, keeping the inline freeframeTempoL10n and freeframeTempoPages objects (freeframe_tempo_script_strings(), freeframe_tempo_page_urls()). The parent's stylesheet only contains the classes the parent uses: if your child theme uses 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#

  • The pages look unstyled or new classes do nothing. The stylesheet only contains classes it found when it was built. Run pnpm build after 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 > Tempo.
  • Release, merch or waitlist links go to a page that doesn't exist. The script links to /music/, /tour/ and /merch/. If you renamed those pages, update freeframe_tempo_page_urls() in inc/setup.php.
  • No sound when I press play. Check the tab isn't muted and the player's mute button isn't on. If you added a src, check the URL opens in the browser and is a format the browser plays (MP3, AAC or Ogg); a toast says when a file can't be loaded.
  • The tour list is empty. Every date is in the past. Add new shows, or leave BAND.keepDemoDatesFresh on while you build the site.
  • Copy buttons say "Select the text and copy it". The clipboard API needs https:// (or localhost).
  • Home is missing from the desktop menu. The desktop header hides the menu's first link. See Navigation, header and footer.
  • Tempo 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 > Tempo pages.

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 album covers, merch mock-ups, portraits, record and video artwork, logo and social image are included under the same license. Familjen Grotesk, Big Shoulders Display and Space Mono are licensed under the SIL Open Font License 1.1 (OFL.txt in each folder of assets/fonts/).

Search documentation

Search the guides and every template's documentation