Overview#
Sidecar is a website for a product and engineering studio, an agency that startups hire to design, build and
launch their first product, built as a WordPress block theme. The fictional Sidecar is a 14-person studio at 1517
South Main Street in Little Rock, Arkansas, working in Central time (America/Chicago) with seed-stage founders
across the US. The theme has a home page with a sketch-to-shipped drawing, client wordmarks, services, featured
work, a 12-week ruler, founder quotes and the next open start dates; a work page with eight projects filtered by
service and funding stage in a grid or list; a full case study with a sketch-to-shipped compare slider; services
with an engagement model helper; an estimator that prices a fixed sprint or a monthly retainer with a sample
timeline; a week-by-week process; a team page with 14 drawn portraits and a live studio clock; open roles; a
five-step project brief; 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/sidecar.
Headings, copy, lists, cards, quotes, buttons and the services questions are core WordPress blocks arranged in
block patterns, so you edit them in the block editor without touching code. The parts that are worked out in the
browser (the project list and its filters, the compare slider, the model helper, the estimator, the week tabs and
founder hours chart, the team list, the role list, the project brief and the start date list) work as in the HTML
version: they are Custom HTML blocks driven by assets/js/main.js, which reads the studio's content from
assets/js/data.js. The drawings are Custom HTML blocks with inline SVG, so their pen notes use the theme's
fonts. Colors and fonts are design tokens in theme.json: change them in Appearance > Editor > Styles and the
whole site follows. Every date is worked out in Central time from rules rather than fixed dates, so the demo never
goes stale.
The look is a workshop build log: warm paper with a faint engineering grid, ink outlines with hard offset shadows, one green accent ("Sidecar green"), masking tape, stamped labels and handwritten margin notes. It is a light-only design.
Quick start#
Requirements: WordPress 7.1 or newer and PHP 7.4 or newer. No plugins are needed.
- Download
sidecar.zipfrom freeframe.dev/templates/sidecar (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 Sidecar.
- Go to Appearance > Sidecar setup and click Create the demo pages. This creates the Home, Work, Case study, Services, Pricing, Process, About, Careers and Start a project 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 > Sidecar pages) made of section patterns. Every section is also offered on its own in the block inserter under Patterns > Sidecar.
| Page | Slug | Page pattern | Sections (pattern files in patterns/) |
|---|---|---|---|
| Home | / |
page-home.php |
home-hero, client-logos, what-we-do, featured-work, home-process, founder-quotes, home-cta (next open start dates) |
| Work | /work/ |
page-work.php |
work-header, work-projects (filters and the list from CASES), work-cta |
| Case study | /case-study/ |
page-case-study.php |
case-header, case-image, case-problem, case-compare (slider), case-shipped, case-timeline, case-results, case-quote |
| Services | /services/ |
page-services.php |
services-hero, service-design (#design), service-engineering (#engineering), service-launch (#launch), engagement-models (#models, with the model helper), services-faq |
| Pricing and estimator | /pricing/ |
page-pricing.php |
pricing-header, estimator, pricing-included, payment-terms |
| Process | /process/ |
page-process.php |
process-header, week-by-week (tabs, #week-5 opens a week), handoff, process-cta |
| About | /about/ |
page-about.php |
about-hero, values, team (portraits from TEAM), studio (live clock) |
| Careers | /careers/ |
page-careers.php |
careers-hero, open-roles (from ROLES), hiring-steps |
| Start a project | /contact/ |
page-contact.php |
contact-header, project-brief |
| 404 | any missing URL | templates/404.html |
404 |
WordPress 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-sidecar/
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 scripts (with the script's strings, page and drawing URLs),
editor styles, the `js` class script, theme color and favicon, page titles,
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 > Sidecar 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/data.js the studio's content (window.SIDECAR): CASES, SERVICES, ESTIMATE, PHASES,
MODELS, START_RULE, WEEKS, ROLES, TEAM, BRIEF and more
assets/js/main.js every interaction (the HTML version's script plus a WordPress adapter)
assets/icons/ the icons used by Icon blocks (outline SVG paths)
assets/img/ logo.svg, logo-footer.svg, favicon.svg, the social image, work/*.svg
assets/fonts/ Recursive and Architects Daughter, with their licenses
src/theme.css the Tailwind CSS source of assets/css/theme.css
src/icons.css the arrow drawn after button text with CSS (generated)
src/editor-css.mjs writes assets/css/editor.css after each build
languages/ freeframe-sidecar.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.txtCustomizing#
Brand and colors#
The palette is in theme.json (settings.color.palette) and shows in Appearance > Editor > Styles > Colors.
The stylesheet maps every Tailwind color to these presets (--color-pine: var(--wp--preset--color--pine) in
src/theme.css), so changing Pine in Styles changes every button, link, pen note and focus ring that uses pine.
| Color | Value | Used for |
|---|---|---|
| Paper / Paper 2 | #f5f0e4 / #ece5d4 |
Page background / alternate bands |
| Card | #fcfaf4 |
Sheets and cards |
| Ink, Ink soft, Ink faint | #1d1b16, #4b463b, #625c4e |
Text, outlines and hard shadows (soft and faint are AA on paper) |
| Line / Line strong | #d6ccb6 / #8b8271 |
Hairlines / inputs and rulers |
| Pine / Pine deep | #17663f / #0f4a2d |
The accent: buttons, links, notes, focus / hover |
| Sprout / Sprout soft | #86d6a0 / #dcecd8 |
Light green fills with ink text, selected choices |
| Tape / Tape soft | #f1d867 / #f7ebb4 |
Masking tape, highlighter, the price on dark |
| Night, Night 2, Night line, Mist | #1d1b16, #2b2820, #3d392e, #cfc6b1 |
Footer and dark bands, text on them |
| Danger / Danger soft | #a3361a / #f7dfd4 |
Form errors |
The drawings (inline SVG in the patterns, the files in assets/img/work/, the portraits drawn by main.js) use
hex values directly (#17663f, #86d6a0, #f1d867); search for them if you change the palette completely.
The repeated pieces are classes in src/theme.css: .sheet and .sheet-lift (ink-outlined cards with an offset
shadow), .tape, .stamp, .marker, .grid-paper (the engineering grid), .eyebrow, .label, .h-page,
.h-section, .lead, .link, .hand, .chip, .filter-btn, .seg, .choice, .wk-tab and the form
fields (.field, .field-label, .field-error, .field-hint). Add them to any block under Advanced >
Additional CSS class(es).
Typography#
Two families are bundled in assets/fonts/ and declared in theme.json (no requests to Google), the same files
Google Fonts serves to the HTML version:
- Recursive (
font-sans, the body font; weights 400–800) is a variable font. Headings use a little of its casual axis (CASL 0.35); themonoclass switches on its monospaced axis for labels, chips and numbers;casualsetsCASL 0.5(the wordmark). - Architects Daughter (
font-hand, or the.handclass in pine) is for margin notes and quotes in pen.
Swap a family in Styles > Typography, or add your own fontFace files to theme.json. If the new body font
has no MONO axis, change the mono utility in src/theme.css to use a monospaced family and run pnpm build.
Content#
Page copy (headings, paragraphs, service details, the founder quotes, the case study, the questions) is in the
pages: edit it in the block editor. Everything the script renders lives in assets/js/data.js on
window.SIDECAR, as in the HTML version:
| Constant | What it holds |
|---|---|
STUDIO |
Name, city, timeZone (America/Chicago), phone, emails and studio hours (Mon–Fri, 09:00–17:30) |
SERVICES, SERVICE_ORDER |
design, engineering, launch: names and the rate per person-week ($4,500, $5,000, $4,000) |
STAGES, STAGE_ORDER |
pre-seed, seed, series-a |
CASES |
The eight projects on the work page |
ESTIMATE |
Estimator settings: team sizes, weeks range, defaults, sprint and retainer multipliers, hints |
PHASES |
Phases of the estimator's sample timeline, with weights |
MODELS |
The three engagement models suggested by the helper on the services page |
START_RULE |
How the next open start dates are worked out |
WEEKS |
The 12 weeks on the process page |
DISCIPLINES, MODES, ROLES |
Open roles on the careers page |
TEAM, TEAM_GROUPS |
The 14 people on the about page and their drawn portraits |
BRIEF |
Labels for the brief form and the budget ranges used to match an estimate |
To add a project, add an object to CASES and a 640×420 drawing (or image) at assets/img/work/<id>.svg:
{
id: 'ferrylog', name: 'Ferrylog', what: 'Crew scheduling for river ferries',
city: 'Helena, AR', stage: 'seed', services: ['design', 'engineering'], weeks: 10, team: 3, year: 2026,
summary: 'One or two sentences about the problem and what we did.',
shipped: ['First thing shipped', 'Second thing', 'Third thing'],
metric: { value: '3×', label: 'faster crew changes' }
}The work page finds the drawing by the project's id (freeframe_sidecar_work_images() in inc/setup.php
lists every file in assets/img/work/). Add page: 'case-study.html' to link the case study page; the script turns the HTML file names used in data.js
(case-study.html, pricing.html?model=sprint, contact.html) into your WordPress page URLs with
freeframe_sidecar_page_urls() in inc/setup.php. To link another case study, create the page and add its name
there. To add a role, copy an entry in ROLES (discipline must be a key of DISCIPLINES, mode a key of
MODES). To add a team member, copy an entry in TEAM; the portrait settings are skin 0–4, hair 0–5 (crop,
side part, bob, long, curls, bun), hairColor 0–4, glasses and bg 0–3. Edit data.js in a child theme (see
Child themes) so an update doesn't overwrite it.
Images and artwork#
All artwork is SVG drawn for this template; there are no photos. The header logo is an Image block
(assets/img/logo.svg), the footer logo another (assets/img/logo-footer.svg, white lines): select one and
Replace to use your own. The hero sketch and phone, the client wordmarks, the service drawings, the workbench,
the compare slider screens, the case study drawings, the studio front and the 404 sketch are Custom HTML blocks
with inline SVG, so their handwritten notes use the theme's fonts; replace a block with an Image block (keep
roughly the same aspect ratio) when you have real pictures. The project drawings are files in assets/img/work/
(640×420, shown at a 64:42 ratio); they load as images, so their text uses the system sans rather than the theme's
fonts. On the home and case study pages they are Image blocks; on the work page the script shows them from
CASES.
Team portraits are drawn by drawAvatar() in main.js from the TEAM settings. To use photos, replace the
drawAvatar(p, i) call in initTeam() with an <img>.
The social image is assets/img/og-image.png (1200 x 630, source og-image.svg); the favicon
assets/img/favicon.svg is used until you set a Site Icon in Settings > General.
Navigation, header and footer#
The header and footer are template parts (parts/header.html, parts/footer.html) showing the header and
footer patterns. Edit them in Appearance > Editor > Patterns > Header / Footer.
The header has two Navigation blocks showing the same menu: the desktop one (from 1024 px) and the one in the
phone menu. Edit the menu once in Appearance > Editor > Navigation (the setup creates "Main menu": Work,
Services, Process, Pricing, About, Careers and Contact). A menu item with the class ff-mobile-only (Contact,
under the link's Advanced settings) only shows in the phone menu, as in the HTML version, where the desktop
header has the "Start a project" button instead. WordPress marks the current page's link with
aria-current="page" (drawn as a strip of tape under it), and the script marks the header's "Start a project"
button on the contact page (class ff-current-link). 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 link columns are List blocks: click a link to change it, press Enter to add one. The copyright year is written by PHP and kept current by the script. The phone number, email and address appear in the footer and on the about and contact pages; search for them when you change them.
Features#
Next open start dates#
Shown on the home page and the work page (paragraphs with the classes ffd-start-date--0 and
ffd-start-open--0, which the script turns into data-start-date and data-start-open), and on the pricing and
contact pages (data-start-list, the start date select). startDates() in main.js reads START_RULE:
- the first Monday at least
leadDays(21) days after today in Central time, - then every
everyWeeks(4) weeks,count(3) dates in all, - a start between
closed.fromandclosed.to(Dec 19 to Jan 3, inclusive) moves to the first Monday after it, openings[1, 2, 2]gives "1 of 2 pods open", "2 of 2 pods open"... by position.
On Nov 10, 2026 this gives Monday, Dec 7, 2026; Monday, Jan 4, 2027; Monday, Feb 1, 2027. On Nov 10, 2027: Dec 6, 2027; Jan 10, 2028 (Jan 3 is in the break); Feb 7, 2028. See Keeping the demo current.
Work filters and layout#
initWork() renders CASES into [data-work-list] (the work-projects pattern). The service buttons
(data-filter-service) and stage buttons (data-filter-stage) combine (a project must match both); the count reads
"Showing 2 of 8 projects". The Grid and List buttons (data-view) switch the layout; the choice is saved in
sidecar:work-view. Filters and layout live in the URL so a filtered view can be linked:
| Parameter | Values | Default |
|---|---|---|
service |
design, engineering, launch |
all |
stage |
pre-seed, seed, series-a |
all |
view |
grid, list |
saved choice, else grid |
For example /work/?service=launch&stage=seed shows Brineworks and Mulecart. Unknown values are ignored. When
nothing matches, a message with "Show all projects" appears. Filter buttons use aria-pressed, and the count is a
live region.
Compare slider#
On the case study page a range input ([data-compare-range], 0–100, starting at 50) clips the sketch layer
([data-compare-top], clip-path: inset(0 <100-v>% 0 0)) and moves the divider. It works with arrow keys and
announces "50% sketch". The two screens are inline SVG in the case-compare pattern's Custom HTML block.
Engagement model helper#
On the services page, three yes/no questions (scope, engineers, team) suggest a model from MODELS once all
three are answered: wanting a team within a year with no engineers on staff suggests Build to hire; otherwise a
clearly scoped release suggests Fixed sprint; otherwise Monthly retainer. The matching model card (a Group
block with the class ffd-model-card--sprint, --retainer or --hire) gets a green shadow, and the link opens
the estimator with that model.
Engagement estimator#
initEstimator() on the pricing page. Inputs: services (checkboxes, at least one; unchecking the last one keeps it
and shows "Pick at least one service"), team size (2 to 6), weeks (range 4 to 24, step 2) and the payment model
(Fixed sprint or Monthly retainer, aria-pressed buttons). The math, in computeEstimate():
- rate = the average
SERVICES[...].rateof the services picked, - fixed sprint: total = rate × people × weeks; shown as total × 0.9 to total × 1.15, each rounded to $1,000,
- retainer: monthly = rate × people × 4 × 0.9 (10% off); shown as monthly × 0.95 to × 1.05, rounded to $500, for max(3, ceil(weeks ÷ 4)) months, with the total for those months.
| Settings | Shows |
|---|---|
| Design + Engineering, 3 people, 12 weeks, sprint (the default) | $154,000 to $197,000; 3 people for 12 weeks at $4,750 per person-week |
| Same, retainer | $48,500 to $54,000 a month; every 4 weeks for 3 months: $145,500 to $162,000 in total |
| All three services, 4 people, 16 weeks, sprint | $259,000 to $331,000 at $4,500 per person-week |
| Launch only, 4 people, 4 weeks, sprint | $58,000 to $74,000 |
The sample timeline (planPhases()) shares the weeks among the PHASES that apply (Discovery and Handoff always,
Design, Build and Launch when that service is picked) by weight, largest remainder first, at least one week each;
if there are more phases than weeks, Handoff and then Discovery are dropped. The default reads Discovery weeks 1–2,
Design 3–5, Build 6–11, Handoff week 12. Dates start on the next open start date and end on the Friday of the last
week (start + weeks × 7 − 3 days): Dec 7 to Feb 26 for the default on Nov 10, 2026.
Settings are saved in sidecar:estimate and written to the URL (?services=design,engineering&team=3&weeks=12&model=sprint)
after a change, so an estimate can be shared. The URL wins over the saved settings. "Send this with a project brief"
opens the Start a project page with the same parameters; "Reset the estimator" clears both. The yellow note says it
is an estimate, not a quote; keep it. The form and the result panel are Custom HTML blocks in the estimator
pattern.
The 12-week plan#
initWeeks() on the process page builds 12 tabs (role="tab", arrow keys, Home and End) from WEEKS and fills one
panel: phase, title, text, who is on it, what you get and the founder hours with a bar. The founder hours chart has
one button per week; selecting a bar opens that week. The total reads "39 hours in total, about 3.3 a week".
#week-5 in the URL opens week 5, and the hash follows the selected week.
Team and studio clock#
initTeam() draws the 14 portraits and filters them by group (lead, design, engineering, launch), with a
count such as "5 people in engineering". initClock() writes "It is 10:42 a.m. in Little Rock. The studio is
open." from STUDIO.hours into the paragraph with the class ffd-studio-clock, every 30 seconds, with "opens at
9:00 a.m.", "done for the day" or "out for the weekend" outside hours.
Open roles#
initRoles() on the careers page renders ROLES with filters by discipline (data-role-discipline) and location
(data-role-mode), kept in the URL as ?discipline=engineering&mode=hybrid (2 open roles). "Read the role" expands
the details (aria-expanded, aria-controls); the open role goes into the hash (#role-mobile-engineer), so a link
opens that role.
Menu, header and motion#
The menu button (#menu-toggle) opens the phone menu with aria-expanded; Escape closes it and returns focus. The
header gets a soft shadow once the page scrolls. Elements with .reveal fade in once as they scroll into view, and
everything above them is revealed too. The hero's hand-drawn underline (class ff-underline) draws itself in. With
prefers-reduced-motion: reduce there is no reveal, underline drawing or smooth scrolling.
Keeping the demo current#
Nothing dated goes stale:
- Start dates follow
START_RULEfrom today in Central time (above). ChangeleadDays,everyWeeks,closedoropeningsindata.jsto match your real calendar. For fixed dates on a real site, replace the body ofstartDates()with a list, for examplereturn [{ day: dayNumber(2027, 3, 1), open: 1 }, { day: dayNumber(2027, 4, 5), open: 2 }];. - Estimator dates start on the first open start date.
- The copyright year is written by PHP and filled again in the browser in Central time.
- The studio clock uses the current Central time.
- Past content keeps its real dates: project years in
CASESand "Since 2019".
The patterns contain the text for Nov 10, 2026, so the page reads correctly before the script runs. A preview
parameter pretends it is another moment in Central time, for reviews: /contact/?now=2027-11-10T09:00. It is
controlled by ALLOW_NOW_PARAM at the top of main.js; set it to false on a live site if you prefer.
Block styles#
In the block settings' Styles panel:
| Block | Styles |
|---|---|
| Button | Fill (default, the green button with an ink shadow), Outline, Green (on dark), Ghost (on dark) |
The class is-small on a Button block makes it the smaller size. The arrow after button text is drawn with CSS
(icon-after-arrow-right, from src/icons.css). The services questions are Details blocks with the class faq,
which draws the summary and its "+" sign.
Forms#
The project brief on the Start a project page ([data-brief], a Custom HTML block in the project-brief
pattern) has five steps: Stage, Need, Budget, Timing and You. It doesn't send anything until you connect it.
"Next" validates the current step and shows inline errors:
| Step | Rules | Message |
|---|---|---|
| Stage | one of stage |
"Pick the stage that fits best." |
| Need | at least one need; about 20 characters or more (1,200 max, with a counter) |
"Pick at least one: design, engineering or launch." / "Tell us what you are building." / "Tell us a little more: at least 20 characters." |
| Budget | one of budget |
"Pick a range, or “Not sure yet”." |
| Timing | one of timeline; start (the next open start dates or "I’m flexible", hidden for "Just exploring") |
"Pick when you would like to start." |
| You | name, email (valid), company; role optional |
"Tell us your name." / "We need an email to reply to." / "Enter an email like name@company.example." / "Which company is this for?" |
Opened from the estimator (?services=…&team=…&weeks=…&model=…), it ticks the services, picks the budget range the
estimate's middle falls in (BRIEF.budgetCaps: up to $75,000, $150,000, $300,000, then more) and shows a note:
"From the estimator: Design and Engineering, 3 people for 12 weeks, fixed sprint, $154,000 to $197,000. We filled in
what we could." It only prefills when services names at least one real service: ?services=nope&team=2 fills in
nothing, and ?services=launch,nope ticks Launch only.
On success the form is replaced by "Thanks, Leilani. Your brief is in." with a reference (SC- and five
characters), a summary and what happens next. To use a form service such as Formspree, change the submit handler in
section 13 of assets/js/main.js (in a child theme):
fetch('https://formspree.io/f/your-id', { method: 'POST', body: new FormData(form), headers: { Accept: 'application/json' } })
.then(function (r) {
if (r.ok) showDone(new FormData(form));
else showSummary('Something went wrong. Please try again.');
});Show the success panel only when the service answers OK. You can also replace the brief's Custom HTML block with a
form plugin's block; add the classes field-label, field, choice and btn-primary to its labels, fields,
options and button so it matches. The model helper and the estimator are calculators and send nothing.
Saved data (localStorage)#
All storage goes through safeStorage in main.js, which falls back to memory when localStorage is blocked
(private modes, sandboxed frames), so the features keep working for the visit.
| Key | Shape | Validation |
|---|---|---|
sidecar:estimate |
{ v: 1, services: ['design', 'engineering'], team: 3, weeks: 12, model: 'sprint' } |
v must be 1; services must be keys of SERVICES (at least one), team in teamSizes, weeks in range and on the step, model sprint or retainer; otherwise the defaults are used |
sidecar:work-view |
'grid' or 'list' |
anything else means grid |
These are the HTML version's keys. "Reset the estimator" removes sidecar:estimate. To clear everything, remove
both keys in the browser's developer tools (Application > Local Storage). All values read from the URL are checked
with own-key lookups, so ?service=toString is ignored.
SEO and sharing#
Page titles come from WordPress: the page title, a vertical bar and the site name (for example "Pricing and
engagement estimator | Sidecar"); the home page shows "Sidecar: product studio for seed-stage startups in Little
Rock", built from the site title and tagline (Settings > General) by freeframe_sidecar_front_title(). 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 x 630 image) as the default social image.
WordPress already generates a sitemap at /wp-sitemap.xml. Organization or ProfessionalService structured
data with the studio's address is a good addition once you have real details.
Accessibility#
Built in: a skip link to the main content (added by WordPress, styled by the theme), landmarks (header, labeled
navigation, main, footer), one h1 per page, sections labeled by their headings, visible focus outlines in Sidecar
green (sprout green on dark bands), choice cards that show focus from their input, filters and toggles with
aria-pressed, week tabs that follow the tabs pattern with arrow keys, role details with aria-expanded and
aria-controls, counts and results in live regions, form errors linked with aria-describedby and fields marked
aria-invalid, focus moved to the first problem and to the success heading, drawings with role="img" and a
description (or hidden when decorative), and reduced-motion support. The demo passes axe (WCAG 2.1 A and AA) in its
checks.
When you edit: keep one Heading 1 per page, give images alt text (or mark them decorative), label every field you add, and keep body text at the Ink soft color or darker.
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; keep each class name whole in the script (not built from pieces). 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-sidecar, without node_modules/) and upload it in
Appearance > Themes, or copy the folder to wp-content/themes/ on your server. Use HTTPS, and connect the brief
before you go live.
Translations#
The theme is translation-ready with the text domain freeframe-sidecar. All text in patterns, templates and PHP
goes through WordPress's translation functions; the script's own labels and messages (validation errors, the menu
button, "Read the role", the estimator's label) are translated in PHP (freeframe_sidecar_script_strings() in
inc/setup.php). Sentences the script builds from pieces (dates, counts, estimate lines, the studio clock) are
written in English in main.js; translate them there. The studio's content in assets/js/data.js is edited there
directly. Create a translation with Loco Translate, or with WP-CLI and Poedit:
wp i18n make-pot wp-content/themes/freeframe-sidecar wp-content/themes/freeframe-sidecar/languages/freeframe-sidecar.potSave freeframe-sidecar-es_ES.mo (for example) in wp-content/languages/themes/ or in the theme's languages/
folder. Page content created by Sidecar 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 Sidecar is updated (your projects, prices, roles and team in
particular). Create wp-content/themes/sidecar-child/style.css:
/*
Theme Name: Sidecar Child
Template: freeframe-sidecar
*/Templates, parts, patterns and theme.json in the child theme override the parent's files of the same name. To use
your own data.js, replace the parent's data script (freeframe-sidecar-data) under the same handle in the
child's functions.php:
add_action( 'wp_enqueue_scripts', function () {
wp_deregister_script( 'freeframe-sidecar-data' );
wp_register_script( 'freeframe-sidecar-data', get_stylesheet_directory_uri() . '/data.js', array(), '1.0', array( 'in_footer' => true, 'strategy' => 'defer' ) );
}, 15 );Do the same for assets/js/main.js (handle freeframe-sidecar), keeping the inline window.freeframeSidecar
strings and URLs. The project drawings are listed by freeframe_sidecar_work_images() from the parent's
assets/img/work/ (a file of the same name in the child's assets/img/work/ replaces it); for a new project, add
its drawing to that list with the wp_add_inline_script() output or put the file in the parent folder. 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 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 > Sidecar.
- The estimator opens with old settings. The URL or
sidecar:estimatewins over the defaults. Use "Reset the estimator", or open the pricing page without parameters after clearing storage. - Start dates look wrong for my calendar. Edit
START_RULEindata.js, or hard-code dates instartDates(). Check for a leftover?now=in the URL. - Case study, estimator or brief links go to the wrong page. The script links to
/case-study/,/pricing/and/contact/. If you renamed those pages, change their URLs infreeframe_sidecar_page_urls()ininc/setup.php(in a child theme, filter the inline script). - The brief says "received" but nothing arrives. The demo form is not connected; see Forms.
- Project drawings show a different font. They load as images, which cannot use the theme's fonts; paste the SVG into a Custom HTML block if you need them.
- Sidecar 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 > Sidecar pages.
License#
Split license: the PHP code and block markup are licensed under the GNU GPL, version 2 or later, and the CSS,
JavaScript, images and design under the Freeframe Commercial License (unlimited sites for you and your
clients; no resale or redistribution as a theme). The full terms, with your licensee details, are in
LICENSE.txt and at https://freeframe.dev/license#commercial. Third-party components keep their own licenses.
Fonts: Recursive and Architects Daughter are licensed under the SIL Open Font License 1.1 (OFL.txt in each
folder of assets/fonts/). Sidecar, its clients, founders and team are fictional.