Overview#
Studio is a website for a small design studio, built as a WordPress block theme. The fictional
brand is Raster, a five-person studio in Zürich working across identity, digital product, editorial
and wayfinding. The theme sits on a strict 12-column grid with one typeface family, a monochrome
palette and a single red accent: a home page with a rising headline, selected work, the four
disciplines, a client wordmark grid, count-up numbers and journal teasers; a work index with
discipline filters, a year sort and grid/list views with a hover preview; case studies drawn from
the project data (/project/?id=fondo) with generated artwork in each project's palette; a
services page with deliverables and prices, three ways of working, a brief estimator and an FAQ; a
studio page with a floor plan, values, a process timeline, the team and open roles; a journal with
topic filters and expandable notes; a five-step project enquiry; and a 404 page. 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/studio.
Headings, copy, cards, lists, buttons and images are core WordPress blocks arranged in block
patterns, so you edit them in the block editor without touching code. The interactive parts (the
work index toolbar, the estimator, the enquiry, the journal's filter bar and note buttons, the
newsletter form) work as in the HTML version: they are small Custom HTML blocks driven by
assets/js/main.js. Colours and fonts are design tokens in theme.json: change them in
Appearance > Editor > Styles and the whole site follows.
Quick start#
Requirements: WordPress 7.1 or newer and PHP 7.4 or newer. No plugins are needed.
- Download
studio.zipfrom freeframe.dev/templates/studio (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 Studio.
- Go to Appearance > Studio setup and click Create the demo pages. This creates the Home, Work, Case study, Services, Studio, Journal 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.
- 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:
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 > Studio pages) made of section patterns. Every section is also offered on its own in the block inserter under Patterns > Studio.
| Page | Slug | Page pattern | Sections (pattern files in patterns/) |
|---|---|---|---|
| Home | / |
page-home.php |
home-hero (rising headline, availability), selected-work, what-we-do, clients, numbers (count-up), journal-teaser, cta |
| Work | /work/ |
page-work.php |
work-hero, work-index (filters, sort, grid/list views, nine projects), not-shown |
| Case study | /project/ |
page-project.php |
case-study (Seeline; other projects from ?id=), case-not-found |
| Services | /services/ |
page-services.php |
services-hero, disciplines, engagement, estimator, faq |
| Studio | /studio/ |
page-studio.php |
studio-hero, space (floor plan), values, process (timeline bars), team, jobs (open roles), visit |
| Journal | /journal/ |
page-journal.php |
journal-hero, notes (topic filter and eight notes), newsletter |
| Contact | /contact/ |
page-contact.php |
enquiry (five-step enquiry, contact details and studio hours) |
| 404 | any missing URL | templates/404.html |
404 |
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-studio/
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, page links and cover URLs),
editor styles, the `js` class script, theme colour and favicon,
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/site-setup.php Appearance > Studio 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 case study data and artwork, work index, estimator, enquiry, journal...
assets/icons/ SVG icons for the Icon block
assets/img/ project covers (work/), logo, favicon, social image
assets/fonts/ Host Grotesk and Fragment 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-studio.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:
| Colour | Default | Used for |
|---|---|---|
| Paper | #f6f6f3 |
the page background |
| Sheet | #ffffff |
raised surfaces: the enquiry panel, the estimate, choice tiles |
| Tint | #ecece7 |
quiet fills, image backgrounds, the estimator band |
| Rule | #d6d6cf |
hairlines |
| Faint | #8b8b85 |
decorative lines and field underlines (not for text) |
| Mute | #5b5b56 |
secondary text and labels (AA on Paper and Sheet) |
| Ink | #121211 |
text, buttons, dark sections |
| Ink 2 | #2a2a28 |
the ink button's hover |
| Signal | #e8381e |
the one accent, for small fills: dots, squares, bars |
| Signal ink | #c02a12 |
red text on Paper (enough contrast) |
| White, Black | for your own blocks |
The theme's stylesheet reads these colours from the palette (bg-ink, text-mute,
bg-signal...), so changing Signal in Styles recolours every dot, square and bar at once. Keep
Signal ink dark enough for text on Paper (4.5:1). The SVG artwork (the project covers, the floor
plan, the 404 grid and the case study gallery) carries its own colours in its markup.
Typography#
Two fonts are bundled in assets/fonts/ and declared in theme.json
(settings.typography.fontFamilies), so no request goes to Google: Host Grotesk (variable,
300 to 700, and italic 400; preset sans) for everything, and Fragment Mono (400; preset
mono) for the small labels, indexes and counts. 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 (--wp--preset--font-family--sans, --mono), so
keep those slugs. The generated case study artwork and the floor plan name the fonts directly
(SANS and MONO in main.js, font-family="Fragment Mono, …" in the plan).
Font sizes are the presets in theme.json (xs 0.75rem to 9xl 8rem, the Tailwind scale). The
classes display (large headline), h-section (section heading), lead, label (small mono
caps), idx (mono index numbers) and ff-dot (a red square before the text) give text the
template's styles.
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 > Studio.
What the script drives 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 |
|---|---|---|
work-index |
the filter, sort and view toolbar, the count, the preview, the empty state | see Work index |
case-study |
the breadcrumb, the three gallery drawings | see Case studies |
estimator |
the form and the estimate | see Brief estimator |
notes |
the topic chips and count, each "Read note" button | see Journal filters and notes |
newsletter |
the form and its thank-you message | see Forms |
enquiry |
the steps, the form and its success message | see Project enquiry |
space, 404 |
the floor plan, the grid drawing | SVG drawings (labels in the theme's fonts) |
header, footer |
the menu button, the copy and grid buttons, the wordmark | driven by the script; decorative |
Some blocks carry classes (Block settings > Advanced > Additional CSS class(es)) that the script
reads. Blocks can't store data- attributes, hidden or aria-hidden, so the script turns them
back when the page loads: a class starting with ffd- becomes a data attribute
(ffd-project ffd-year--2025 ffd-disciplines--identity_wayfinding on a work item becomes
data-project data-year="2025" data-disciplines="identity wayfinding"; _ stands for a space),
ff-hidden becomes hidden and ff-aria-hidden becomes aria-hidden="true".
reveal(withreveal-d1toreveal-d3for a delay) fades a block in as it scrolls into view;riseon a heading lifts each line (<span><span>…</span></span>in the heading's HTML view).link-cardon a group makes the whole group clickable: the first link inside it stretches over the group (project cards, discipline rows, journal teasers, previous/next projects).
Projects
A project appears in up to four places:
PROJECTSinassets/js/main.js: the case study itself (see Case studies). The array order is the order of the previous/next links.assets/img/work/<id>.svg: its cover (800 × 600).inc/setup.phppasses every SVG in that folder to the script.- The
work-indexpattern (Work page): one list item. - Optionally the
selected-workpattern (Home): one of the four cards.
To add one to the work index, select a project item in List View (a Group with the classes
work-item ffd-project ffd-order--9 ffd-year--2021 ffd-disciplines--identity) and duplicate it.
Change the cover, the name and its link (/project/?id=<id>), the title, the disciplines and the
year, and set the classes: ffd-year--<year> (the sort), ffd-order--<n> (the order within a year)
and ffd-disciplines--<keys> (keys of DISCIPLINES, joined with _). Then update the counts in
the chips (the chip-count numbers in the toolbar's Custom HTML), the (9) after the Work heading
and the "Showing all 9 projects" text.
Journal notes
Each note in the notes pattern is a Group with the anchor note-… and the classes
ffd-note ffd-tags--type_process (tags are keys of the topic chips, joined with _). Its hidden
text is the Group with the anchor note-…-body and the class ff-hidden; the "Read note" Custom
HTML button's aria-controls must point at that anchor. Update the chip counts when you add a note.
Count-up numbers
The home page numbers are <span data-count="140">140</span> in the paragraph's HTML view. Keep
the final value as the text: that is what visitors see without JavaScript or with reduced motion.
Rolling dates. The availability dates in the patterns are <span data-months-ahead="n">, which
assets/js/main.js fills with the month n months from now and its year: "From Feb 2027" (home hero) and
"Booking projects from February 2027" (call to action) at 4, the job start dates "From January 2027" (3)
and "From March 2027" (5), and the internship deadline "15 December 2026" (2, with data-day="15"). The
dates in the patterns are shown without JavaScript. For fixed dates, replace the spans with plain text.
Images and artwork#
There are no photos; all artwork is SVG.
| Artwork | Size / ratio | Notes |
|---|---|---|
assets/img/work/*.svg |
800 × 600 (4:3) | Project covers in Image blocks (home, work index, case study) and in the list preview. Replace with 4:3 images |
| Case study gallery | 1600 × 900 and 800 × 1000 | Seeline's three drawings are Custom HTML in case-study; the others are generated by ART in main.js from each project's palette and mark |
Floor plan (space), 404 grid (404) |
inline SVG | Custom HTML; replace with an Image block if you prefer |
assets/img/logo.svg |
18 × 18 | the logo in the header (an Image block) |
assets/img/og-image.png |
1200 × 630 | social image (source og-image.svg) |
assets/img/favicon.svg |
32 × 32 | used until you set a Site Icon in Settings > General |
Images in cover frames use object-cover, so other ratios still fit. Covers in cards have an empty
alt text because the link names the project. The arrow on links and buttons is drawn with CSS
(icon-after-arrow-right); other icons are Icon blocks (pick another from the Studio collection
or WordPress's own).
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 one in the
mobile menu. Edit the menu once in Appearance > Editor > Navigation (the setup creates "Main
menu": Work, Services, Studio, Journal, Contact). Each link's label ends with its number,
<span class="idx">01</span> (edit the label's HTML), shown in the mobile menu only. 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.
The footer's Index and Elsewhere menus are List blocks: click a link to change it, press Enter to
add one. The email address and phone number appear in the header's mobile menu, the footer, the
contact page and the copy button's data-copy; search for them when you change them.
Features#
Work index#
The Work page's work-index section is the ffd-work group. Each project is a list item with
ffd-project, ffd-order--n, ffd-year--yyyy and ffd-disciplines--… in the list with
work-list ffd-view--grid ffd-work-list.
- Filter. Chips with
data-filter="all|identity|digital|editorial|wayfinding"setaria-pressed. The choice is kept in the URL as?discipline=identity, so filtered views can be shared; unknown values fall back to all. - Sort. The
<select data-sort>sorts by year, newest (default) or oldest, kept as?sort=oldest. Items of the same year keep their order. - Count.
[data-work-count](aria-live="polite") reads "Showing all 9 projects" or "Showing 3 of 9 projects". An empty state with a Show all projects button appears if a discipline has nothing. - View. The Grid and List buttons switch
data-viewon the list; the choice is saved on the device (studio-work-view). Both layouts are CSS on the same blocks (.work-list[data-view=…]insrc/theme.css). - Preview. In list view at 768 px and wider, the project's cover follows the pointer; when a row
gets keyboard focus it sits beside the row. It hides on blur, on scroll and with Escape, and is
aria-hidden. On phones the list shows a small thumbnail instead.
Case studies#
The Case study page reads ?id=. Without it, and for ?id=seeline, it shows the page's own blocks
(the case-study pattern: Seeline, editable like any page). For any other id, the script draws the
matching PROJECTS entry with renderProject() in its place and updates the tab title (the project
and the site title). An unknown id removes the article and shows the case-not-found section, its
heading promoted to the page's h1. Previous and next links wrap around PROJECTS.
A project in PROJECTS looks like this (shortened):
{
id: 'seeline', // /project/?id=seeline and assets/img/work/seeline.svg
client: 'Seeline',
title: 'A calmer identity for the lake ferries',
disciplines: ['identity', 'wayfinding'], // keys of DISCIPLINES
year: 2025, sector: 'Public transport', location: 'Zürich', duration: '14 months',
services: ['Identity system', 'Wayfinding', 'Timetable design', 'Livery'],
summary: '…', challenge: '…',
approach: [{ title: 'Audit every touchpoint', text: '…' }, …], // three steps
results: [{ value: '−38%', label: '…' }, …], // three results
quote: { text: '…', name: 'Andrea Lutz', role: 'Head of customer experience, Seeline' },
credits: ['Noemi Imhof', 'Selin Karaca', 'Céline Odermatt'],
palette: { bg: '#1d3b57', ink: '#f1efe7', accent: '#ff7a3d', soft: '#8fb3c9' },
mark: '<circle cx="50" cy="50" r="38" …/>', // SVG in a 100×100 box, filled with currentColor
poster: { lines: ['Summer', 'timetable'], sub: '1 May – 30 Sept 2025' },
sign: ['3', 'Departures', 'Rapperswil · Thalwil · Küsnacht'],
gallery: [{ kind: 'board', caption: '…' }, { kind: 'signage', caption: '…' }, { kind: 'poster', caption: '…' }]
}Gallery kind values: board, screens and spread are wide (the first image); poster,
signage and mark are tall. screens reads ui: { headline, figure }, spread reads
spread: { title, sub }, poster reads poster and signage reads sign.
The projects' text stays in English in main.js (the labels around it, such as "Overview" and
"Next project", are translated). If you change the first project in PROJECTS, change the
case-study pattern's blocks too (they are what the page shows for it), or remove the class
ffd-rendered--seeline from its article group so the script always draws it from data.
Brief estimator#
The estimator on the Services page (/services/#estimator) is the estimator pattern's Custom
HTML. Its price model is at the top of the second part of main.js:
var SCOPE = {
strategy: { label: __('Brand strategy'), cost: [12000, 18000], weeks: [3, 4] },
identity: { label: __('Identity system'), cost: [28000, 45000], weeks: [6, 9] },
…
};
var SCALE = { focused: { label: __('Focused'), mult: 0.75, pace: 0.8 }, standard: …, extensive: … };
var URGENCY = { relaxed: { label: __('Relaxed'), mult: 0.95, pace: 1.2 }, standard: …, priority: … };- Budget = the sum of the selected services'
cost×SCALE.mult×URGENCY.mult, low rounded down and high rounded up to CHF 1’000, shown with a Swiss thousands separator (CHF 63’000–105’000). - Duration = the longest selected service plus half of the others (phases overlap), ×
SCALE.pace×URGENCY.pace, rounded to whole weeks. - Send this brief links to the Contact page with
?services=identity,website&budget=60-120&timeline=standard; the budget band comes from the midpoint of the range andBUDGETS.
The checkboxes and radios in the Custom HTML must use the same keys as SCOPE, SCALE and
URGENCY, and the price hints in their labels are text, so update them when you change the
numbers. The default selection is DEFAULT_BRIEF. Choices are saved on the device (see
Saved data); Reset restores the defaults. Results are announced through
a visually hidden aria-live region.
Project enquiry#
The form on the Contact page ([data-enquiry]) has five steps: Project, Budget and timeline,
Details, About you and Review. "Next" validates the current step before moving on; the step tabs
let visitors jump back to any step they have reached. Moving between steps focuses the new step's
heading and announces "Step 2 of 5: Budget and timeline". The Review step lists every answer with an
"Edit" button. The details field shows a live character count. The form is prefilled from the
estimator's link: ?services= (comma-separated SCOPE keys), ?budget= (under-30, 30-60,
60-120, 120-plus) and ?timeline= (relaxed, standard, priority); when anything is
prefilled, a note says so.
Journal filters and notes#
Topic chips (data-tag) filter the notes, kept in the URL as ?tag=process; the count announces
the result. Each note's "Read note" button toggles aria-expanded and the hidden text. Opening
/journal/#note-figures expands that note (clearing a filter that would hide it); the home page's
teasers link there.
FAQ and open roles#
The FAQ on the Services page and the open roles on the Studio page are Details blocks with the
class acc (a native <details>: keyboard and screen reader support built in). Add a question by
duplicating one in List View. The plus/minus is drawn by CSS (.acc > summary::after in
src/theme.css).
Clock, studio status and small extras#
- Zürich clock. Every
<time data-clock>(in the header and the mobile menu) shows the current time in Zürich, updated every 15 seconds. - Studio status.
<span data-studio-status>on the Contact page says whether the studio is open now, usingSTUDIO_HOURSinmain.js(dayswith 0 = Sunday,openandclosein 24-hour decimals,tz). Keep it in step with the hours written on the page. - Copy email. The footer's
<button data-copy="hello@raster.example">copies the address and briefly shows "Copied" (in anaria-livelabel). Browsers only allow copying onhttps://orlocalhost; elsewhere the label asks the visitor to copy it by hand. - Grid overlay. The footer's "Show grid" button (
aria-pressed) overlays the 12-column grid in translucent red. Delete its Custom HTML block to drop it.
Reveals and motion#
Blocks with reveal fade up as they scroll into view; rise headings lift each line into place;
the fill-bar bars of the process timeline grow from the left; numbers with data-count count up.
Everything above a revealed block is revealed too, so fast jumps never leave hidden content. With
reduced motion all of this is off, and without JavaScript nothing is hidden.
Header, mobile menu and back to top#
The header is sticky. Below 1024 px the Menu button (#menu-button, aria-expanded) opens a
full-height menu; Escape or a link closes it and focus returns to the button. A back-to-top button
appears after about one screen of scrolling and steps aside while the footer's "Back to top" link is
visible. To remove it, delete the "Back to top" part at the end 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 ink button), Line, Line (on dark), Paper (on dark) |
The class is-small on a Button block makes it the header's smaller size, and
icon-after-arrow-right adds the arrow.
Forms#
There are two forms. Both validate in the browser and neither sends anything until you connect it; their success messages say so.
| Form | Where | Validates |
|---|---|---|
Project enquiry ([data-enquiry]) |
Contact | at least one service; organisation; a budget and a timeline; details of 40+ characters; an optional link starting with http:// or https://; name; a valid email; the consent box |
Newsletter ([data-newsletter]) |
Journal | a valid email |
Validation is in checkField(), checkGroup() and validate() in assets/js/main.js. Errors
appear under each field (.field-error), linked with aria-describedby, and the first invalid
field gets focus.
To collect enquiries, use a form plugin (replace the Custom HTML with the plugin's block; give its
fields the classes field-label, input and its button btn btn-ink), or post the form in the
script (in a child theme). In the enquiry's submit handler, replace the lines after "Demo only:
nothing is sent" with a request, for example to Formspree:
fetch('https://formspree.io/f/your-form-id', {
method: 'POST',
body: new FormData(enquiry),
headers: { Accept: 'application/json' }
}).then(function (res) {
if (!res.ok) throw new Error('Request failed');
enquiry.parentElement.hidden = true;
success.querySelector('[data-success-name]').textContent = enquiry.elements.name.value.trim().split(/\s+/)[0];
success.hidden = false;
success.focus();
}).catch(function () {
alert('Sorry, something went wrong. Please email hello@raster.example.');
});Then change the success text in the enquiry pattern and remove the "This is a demo" note. The
newsletter works the same way: replace the lines after "Demo only: connect your newsletter provider
here" with a request to your provider, and update the thank-you text in the newsletter pattern.
Saved data (localStorage)#
| Key | Shape | Validation on read | Reset |
|---|---|---|---|
studio-work-view |
"grid" or "list" |
anything other than "list" means grid |
switch the view, or remove the key |
studio-brief-v1 |
{"scope":["identity","website"],"scale":"standard","urgency":"standard"} |
unknown or duplicate scope keys are dropped; an unknown scale or urgency falls back to the default; invalid JSON means defaults | the estimator's Reset button removes it |
These are the HTML version's keys. Storage errors (private browsing) are ignored. To reset everything, remove both keys in your browser's developer tools (Application > Local Storage).
SEO and sharing#
Page titles come from WordPress: the page title, an em dash and the site name (for example
"Services — Raster"); the home page shows the site name and tagline (Settings > General). A case
study's tab title is set by the script ("Fondo: Saving, made legible — Raster"). 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. Search engines index
Seeline's case study from the page's own blocks; the other case studies are drawn by the script, so
if they matter for search, give each its own page (duplicate the Case study page and edit its
blocks) and link to it.
Accessibility#
Built in: a skip link to the main content (added by WordPress, styled by the theme), landmarks
(header, labelled navigation, main, footer), one h1 per page, labelled sections, visible focus
rings in signal red, the menu button with aria-expanded, filter chips and the view switch with
aria-pressed and live result counts, a hover preview that also follows keyboard focus and is
hidden from screen readers, native <details> for the FAQ and roles, form steps that move focus to
their heading and announce progress, form errors announced through aria-describedby and
aria-invalid, labelled drawings and reduced-motion support for every animation. Red text on Paper
uses Signal ink for contrast. 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, and label any field you add to a form (<label for="...">).
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/:
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-studio, 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-studio. All text in patterns,
templates and PHP goes through WordPress's translation functions; the script's strings (counts,
estimator labels, step names, case study labels, form errors and messages) are translated in PHP
(freeframe_studio_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-studio wp-content/themes/freeframe-studio/languages/freeframe-studio.potSave freeframe-studio-de_DE.mo (for example) in wp-content/languages/themes/ or in the theme's
languages/ folder. Page content created by Studio setup is copied into your pages in the site's
language at that moment; translate it in the editor like any other content. The case studies in
PROJECTS and prices in Swiss francs stay as written in main.js.
Child themes#
Use a child theme for changes you want to keep when Studio is updated (your projects, prices and
hours in particular). Create wp-content/themes/studio-child/style.css:
/*
Theme Name: Studio Child
Template: freeframe-studio
*/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-studio) and enqueue yours in the child's functions.php, keeping the inline
window.freeframeStudio data (or copy it). The covers list (images) comes from the parent's
assets/img/work/; add your own entries if your covers live in the child theme. The parent's
stylesheet only contains the classes the parent uses: for 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 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 > Studio.
- A new project doesn't show under a filter or sorts in the wrong place. Its item needs
ffd-project,ffd-year--<year>,ffd-order--<n>andffd-disciplines--<keys>. See Projects. - A project link shows "We couldn't find that project". The
?id=doesn't match anidinPROJECTS. Add the project there (see Case studies). - Editing the Case study page changes nothing for
?id=fondo. Only Seeline is the page's own blocks; the others come fromPROJECTSinmain.js. - The estimate doesn't change after editing prices. The tiles show text from the pattern, but
the numbers come from
SCOPE,SCALEandURGENCYinassets/js/main.js. Change both. - Studio 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 > Studio pages.
- The forms don't send anything. They are demo forms; see Forms.
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 project covers, case study artwork, floor plan
and logo are included under the same license. Host Grotesk and Fragment Mono are licensed under the
SIL Open Font License 1.1 (OFL.txt in each folder of assets/fonts/).