Freeframe

Documentation

Prism

v1.0.0Updated Oct 12, 2026A sleek, dark-first website for an AI startup, with a live streaming playground and a usage cost calculator.

Overview#

Prism is a sleek, dark-first website for an AI startup, built as a WordPress block theme. The demo company is Prism, a fictional research assistant with an API that answers questions with sources you can check. The theme has a home page that opens on an interactive playground streaming cited answers token by token, followed by a feature grid, a benchmark chart and testimonials; a product page with integrations, security and model stats; a developers page with code tabs and copy buttons; pricing with a monthly/annual switch and a usage cost calculator; a filterable changelog; a company and careers page; a talk-to-sales form 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/prism.

Everything you see is made of core WordPress blocks arranged in block patterns, so you edit text, links, buttons, lists and images in the block editor without touching code. Colours, fonts and font sizes are design tokens in theme.json: change them in Appearance > Editor > Styles and the whole site follows. The playground's answers, the benchmark scores and the prices the calculator uses are data at the top of assets/js/main.js (see Script data).

Quick start#

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

  1. Download prism.zip from freeframe.dev/templates/prism (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 Prism.
  3. Go to Appearance > Prism setup and click Create the demo pages. This creates the pages below 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. Set the site title and tagline in Settings > General (the demo uses "Prism" and "The research assistant that shows its work"), then 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 > Prism pages) made of section patterns. Every section is also offered on its own in the block inserter under Patterns > Prism.

Page Slug Page pattern Sections (pattern files in patterns/)
Home / page-home.php home-hero (playground), logo-strip, features, how-it-works, benchmarks (chart), testimonials, home-cta
Product /product/ page-product.php product-hero (section links), capabilities, integrations, security (retention table), models (latency chart), product-cta
Developers /developers/ page-developers.php developers-hero, quickstart (code tabs, copy buttons), endpoints, sdks, rate-limits, developers-cta
Pricing /pricing/ page-pricing.php pricing-plans (billing switch), api-pricing, cost-calculator, faq
Changelog /changelog/ page-changelog.php changelog-hero (topic filter), releases
Company & careers /about/ page-about.php about-hero, values, team, careers (team filter, open roles)
Talk to sales /contact/ page-contact.php contact (steps, offices, sales form)
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. For a plain text page (privacy policy, terms) choose the Page with title template in the page's settings.

Project structure#

freeframe-prism/
  style.css                 theme header (name, version, requirements, license); no styles
  theme.json                design tokens: palette, Spectrum gradient, fonts, font sizes, layout, button
  functions.php             loads the files in inc/
  inc/setup.php             stylesheet and script, editor styles, the "js" class script, theme colour,
                            the grain body class, script strings and page URLs, pattern categories
  inc/icons.php             registers assets/icons/*.svg for the Icon block
  inc/icon-labels.php       the icons' names shown in the editor (generated)
  inc/block-styles.php      the Ghost button style
  inc/site-setup.php        Appearance > Prism 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         the site's JavaScript and its data (playground, benchmarks, pricing)
  assets/icons/             SVG icons for the Icon block
  assets/img/               logo, favicon, prism illustration, portraits (people/), social image
  assets/fonts/             Geist, Geist Mono and Instrument Serif and their licenses (OFL.txt)
  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-prism.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

Customising#

Brand and colours#

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

Colours Used for
Base (Ink 950) the page background
Ink 900, Ink 850, Ink 800, Ink 700, Ink 600 cards, raised panels and inputs, hover states, strong borders
Contrast (Foreground), Muted, Subtle headings and body text, secondary text, captions
Brand 200 to Brand 700 the aqua accent: links, focus rings, check marks, highlights
Ray rose, amber, lemon, mint, sky, violet the spectrum: the logo, gradients, chart bars, icon tints
White plain white for your own blocks

The stylesheet reads these colours from the palette (--color-brand-400 is var(--wp--preset--color--brand-400) in src/theme.css), so changing Brand 400 in Styles changes every link, focus ring and check mark that uses it. The Spectrum gradient preset is the rainbow used by the code tab underline and the "How it works" line, and is available for your own blocks. A few colours are written into artwork: the logo (assets/img/logo.svg), the prism illustration (assets/img/prism-beam.svg), the gradient text (.text-spectrum), the glass card borders (.glass), the hero glow (.ff-aurora in src/theme.css), the benchmark colours (BENCHMARKS in assets/js/main.js) and the browser bar colour (freeframe_prism_head_meta() in inc/setup.php).

Prism is dark only: there is no light mode, and color-scheme: dark is set so form controls and scrollbars are dark too.

Typography#

Text uses Geist (variable, weights 300 to 700), code and labels Geist Mono (400 to 600) and the italic accent words in headlines Instrument Serif (the serif class). The fonts are bundled in assets/fonts/ and declared in theme.json (settings.typography.fontFamilies), so no request goes to Google. To use other fonts, install them in Appearance > Editor > Styles > Typography > Fonts (the Font Library) and select them, or replace the fontFace entries in theme.json; the stylesheet reads --wp--preset--font-family--geist, --geist-mono and --instrument-serif, so rename those slugs only together with src/theme.css.

Font sizes are the presets in theme.json (xs 0.75rem to 9xl 8rem, the Tailwind scale). The stylesheet uses them too: changing Large in Styles changes every text-lg in the design.

Content#

Edit pages in Pages: click any text to change it, select a button to change its link, select an image and use Replace. Sections are groups; open List View to select a section, move it, duplicate it or delete it. To add a section from another page, use the block inserter (+) > Patterns > Prism.

Some content is in Custom HTML blocks, because blocks can't express it or a script drives it: edit those in the block's HTML view.

Pattern Custom HTML What it is
home-hero the playground see Playground
logo-strip the customer logos SVG logos in a scrolling strip
features, capabilities the illustrations in the cards product screens drawn in HTML/SVG; replace with an Image block
benchmarks the chart figure see Benchmark chart
security, models the certification badges' artwork, the retention table, the latency chart
developers-hero, quickstart, sdks the JSON response, the code samples, the install commands see Code tabs and copy buttons
api-pricing, rate-limits the tables they scroll sideways on small screens
pricing-plans, cost-calculator the billing switch, the calculator and its result see Pricing switch and cost calculator
changelog-hero, careers the filter buttons see Changelog and open roles filters
contact, footer the forms see Forms
header the menu button driven by assets/js/main.js

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

  • reveal fades a block in as it scrolls into view; reveal-d1 to reveal-d3 delay it.
  • link-card on a group makes the whole group clickable: the first link inside it stretches over the group (the logo in the header and footer).
  • ff-grid and ff-aurora ff-aurora-<page> on a page header section draw the hairline grid and the coloured glow behind it; ff-spectrum-line draws the line behind the "How it works" steps; ff-timeline and ff-timeline-dot draw the changelog's timeline.
  • ffd-filter-item--changelog (or --roles) and ffd-tag--<topic> on a release or role: see Changelog and open roles filters.

Script data#

assets/js/main.js starts with the data its features use:

Constant Contents
PLAYGROUND The three examples: id, label, prompt, seconds, tokens, sources ({ kind, title }) and answer (blocks { p: '…' } or { ul: ['…'] }; **bold**, [n] cites source n)
PLAYGROUND_FALLBACK The answer for any other question ({prompt} is the visitor's text)
MODELS Streaming speed and time factor per model in the playground's menu
BENCHMARKS models (name, color or 'spectrum') and tests (name, scores in the same order)
PRICING Seat prices (monthly, annual), included tokens, the input/output mix, model prices per million tokens, the token slider steps and the Enterprise suggestion threshold

For example, a fourth playground example:

js
{
  id: 'churn',
  label: 'Explain last month’s churn',
  prompt: 'Why did churn go up in September?',
  seconds: 2.2, tokens: 120,
  sources: [{ kind: 'Analytics', title: 'Churn dashboard, September' }],
  answer: [{ p: 'Churn rose to **3.4%**, mostly from monthly plans [1].' }]
}

The prices shown in the pricing cards and the API price table are blocks: change them there too when you change PRICING, and the Team card's monthly and annual price in the price text's data-price-monthly and data-price-annual attributes (Code editor view). Make changes to assets/js/main.js in a child theme (see Child themes) so a theme update doesn't overwrite them.

Images and artwork#

  • Portraits of the customers and the team are SVG illustrations in assets/img/people/, shown with Image blocks. Replace them with photos with Replace: square, at least 400 × 400.
  • The prism illustration (assets/img/prism-beam.svg, 800 × 400) is behind the calls to action and on the 404 page, in an Image block.
  • Illustrations in the feature and product cards are HTML/SVG in Custom HTML blocks: replace them with an Image block (a screenshot of your product, about 16:10).
  • Icons are Icon blocks: select one and pick another icon from the Prism collection or WordPress's own. Arrows in buttons and links are drawn by CSS: the class icon-after-arrow-right adds one; the available names are in src/icons.css.
  • Logo: the header and footer show assets/img/logo.svg in an Image block next to the site title. Edit both in Appearance > Editor > Patterns > Header (and Footer).
  • Social image: assets/img/og-image.png (1200 × 630, source og-image.svg); see SEO and sharing.
  • 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 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" with Product, Developers, Pricing, Changelog, Company and Talk to sales). The last link only shows in the mobile menu, because the desktop header has its own Talk to sales link and Start free button next to the menu (.site-nav … :last-child 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 in the block settings, for both blocks.

WordPress marks the current page's menu link (aria-current="page"); the script marks the header's Talk to sales link the same way. The footer's link columns are List blocks: click a link to change it, press Enter to add one. The copyright year is the current year.

Features#

Playground#

The playground on the home page (Custom HTML, id playground) runs the first example once it scrolls into view. Run (or Ctrl/Cmd + Enter) shows the sources, then streams the answer word by word with a blinking caret and numbered citations that link to the sources; the button turns into Stop while it writes. Example buttons fill in the prompt and run it; the model menu changes the speed and the time in the meta line. Any other question gets PLAYGROUND_FALLBACK. Nothing is sent anywhere: to answer real questions, replace run() in the "Playground" section of assets/js/main.js with a request to your own API. The answer region is aria-live="polite" and aria-busy while writing; with reduced motion the answer appears at once.

Benchmark chart#

#bench-chart is drawn as inline SVG from BENCHMARKS, redrawn on resize so the text keeps its size, with an aria-label that reads every score, a legend and a View as a table disclosure with the same numbers. The bars grow in when the chart scrolls into view (not with reduced motion).

Pricing switch and cost calculator#

The Bill annually switch (#billing-toggle, role="switch") changes the Team price and its note between the data-price-monthly/data-price-annual and data-monthly/data-annual values, and the calculator's seat price. The calculator multiplies seats by the seat price and the API tokens above the included million by the model's blended price (75% input, 25% output), shows the monthly and yearly total, announces it to screen readers after a pause and suggests Enterprise from 150 seats. All numbers come from PRICING.

FAQ#

The FAQ is an Accordion block with Auto-close on, so one answer is open at a time, as in the HTML version. Add a question with the block's + button; WordPress's own script opens and closes the answers with the right ARIA states.

Code tabs and copy buttons#

Code samples with tabs (data-tabs) switch together: choosing Python in one shows Python in all of them, with arrow keys, Home and End, and the choice is remembered (see Saved data). Copy buttons (data-copy) copy their data-copy text, or the visible code of their group, show "Copied" and announce it. The code is written with coloured spans (t-k keywords, t-s strings, t-n numbers, t-c comments, t-f functions, t-p properties) inside <pre class="code">; edit it in the block's HTML view.

Changelog and open roles filters#

The topic buttons (data-filter="changelog") and team buttons (data-filter="roles") show the items whose tags match, with counts, an empty state, an announcement for screen readers and the choice in the URL (/changelog/?tag=api, /about/?team=engineering), so filtered views can be shared.

Blocks can't hold data attributes, so each release and role carries classes the script turns into them: ffd-filter-item--changelog (or --roles) and one ffd-tag--<value> per topic, matching a button's data-filter-value. To add a release, duplicate one in List View, change its text and its ffd-tag-- classes. The "No releases…" paragraph has ffd-filter-empty--changelog and ff-hidden (hidden until the filter needs it).

Scroll reveals, header and mobile menu#

Blocks with reveal fade in when scrolled into view; the logo strip scrolls slowly and pauses on hover. All motion stops with reduced motion. The header turns frosted once the page scrolls. The menu button (#menu-toggle) opens the mobile menu, updates aria-expanded and its label, and closes with Escape; the mobile menu Group starts with the class ff-hidden, which the script turns into the hidden attribute it toggles.

Back to top#

A button appears after scrolling about one screen down; it scrolls to the top (instantly with reduced motion) and moves keyboard focus to the main content. It is created by assets/js/main.js; to remove it, delete the "Back to top" part of that file and the .back-to-top styles in src/theme.css, then run pnpm build.

Block styles#

Block Styles
Button Fill (default, the light primary button), Ghost (WordPress's own Outline looks the same)
List Checklist (is-style-checklist): a check mark before each item

Add the class is-small to a Button block for the header's compact size. Large buttons have is-large, which, as in the HTML version, looks like the default size.

Forms#

Form (data-form) Where Fields
contact Talk to sales (contact pattern) first_name, last_name, email (work email), company, team_size, topic, interest (at least one), message (optional, 20+ characters), consent
newsletter Footer email

The forms check required fields, email format, minimum lengths and required checkboxes; the sales form also rejects free email providers (FREE_EMAIL in assets/js/main.js). Errors appear under each field, fields get aria-invalid, and the first problem is focused. The sales form picks its topic from the URL (/contact/?topic=security; the values are the topic menu's options). On success the form is replaced by a message. Nothing is sent anywhere until you connect the forms.

Option 1: a form plugin#

Install a form plugin (WPForms, Gravity Forms, Fluent Forms, Contact Form 7, Jetpack Forms...) and replace the form's Custom HTML block with the plugin's block or shortcode. Add the theme's classes where the plugin allows: input on fields, label on labels, btn-primary on the submit button.

Option 2: a form service#

In the "Forms" section of assets/js/main.js (initForms()), post the data before showing the message:

js
fetch('https://formspree.io/f/your-id', {
  method: 'POST',
  body: new FormData(form),
  headers: { Accept: 'application/json' }
}).then((r) => {
  if (r.ok) successSwap(form, '<p class="font-medium">Thanks, we will be in touch.</p>');
  else $('[data-form-status]', form).textContent = 'Something went wrong. Please try again.';
});

Make script changes in a child theme (copy assets/js/main.js there and load it instead), or they are overwritten when you update the theme.

Saved data (localStorage)#

Key Shape Purpose
prism-code-lang "curl", "js" or "python" The code-sample language chosen on the developers page

Any other stored value is ignored. Clear it in the browser's site data, or change LANG_KEY in assets/js/main.js.

SEO and sharing#

Page titles come from WordPress: the page title, an em dash and the site name (for example "Pricing — Prism"); 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. Consider SoftwareApplication structured data for your product. WordPress already generates a sitemap at /wp-sitemap.xml.

Accessibility#

Built in: a skip link to the main content (added by WordPress, styled by the theme), labelled landmarks and sections, one h1 per page, visible aqua focus rings, a live region for the playground answer, a switch with role="switch", code tabs following the tabs pattern, filter buttons with aria-pressed and announced results, an SVG chart with a text description and a table view, scrolling tables that are focusable regions, form errors linked to their fields, and reduced-motion support for reveals, the logo strip, the streaming answer and the chart. The demo passes axe (WCAG 2.1 A and AA).

When you edit: keep gradient text (text-spectrum) large and on dark backgrounds, give images alt text, label every new form field and keep one Heading 1 per page.

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/ (including the classes inside the HTML the script writes):

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 or template, 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-prism, 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-prism. All text in patterns, templates and PHP goes through WordPress's translation functions; the script's strings (labels, playground status, calculator lines, form errors) are translated in PHP (freeframe_prism_script_strings() in inc/setup.php). The playground answers and benchmark names are data in assets/js/main.js: translate them there. Create a translation with Loco Translate, or with WP-CLI and Poedit:

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

Save freeframe-prism-de_DE.mo (for example) in wp-content/languages/themes/ or in the theme's languages/ folder. Page content created by Prism 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 Prism is updated. Create wp-content/themes/prism-child/style.css:

css
/*
Theme Name: Prism Child
Template: freeframe-prism
*/

Templates, parts, patterns and theme.json in the child theme override the parent's files of the same name. To change the script's data, copy assets/js/main.js to the child theme and load it instead of the parent's:

add_action( 'wp_enqueue_scripts', function () {
	wp_deregister_script( 'freeframe-prism' );
	wp_register_script( 'freeframe-prism', get_stylesheet_directory_uri() . '/assets/js/main.js', array(), '1.0.0', array( 'in_footer' => true, 'strategy' => 'defer' ) );
	wp_enqueue_script( 'freeframe-prism' );
}, 15 );

(Then print the translated strings again, as freeframe_prism_enqueue_assets() in inc/setup.php does, or the script falls back to English.) The parent's stylesheet only contains the classes the parent uses: if your child theme's patterns use 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 playground answers "This demo streams pre-written answers…". Only the three example prompts in PLAYGROUND have answers; their prompt must match the question exactly (letter case doesn't matter).
  • A release or role doesn't appear under its topic. Its ffd-tag--<topic> class must match a filter button's data-filter-value, and it needs ffd-filter-item--changelog (or --roles).
  • The calculator total doesn't match the cards. The calculator uses PRICING; the cards are blocks. Update both.
  • 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 > Prism.
  • The header menu shows other pages, or an old menu. A Navigation block without a chosen menu shows the most recent one. Select it and choose "Main menu" (or yours) in the block settings, in both the desktop and the mobile Navigation block.
  • Prism 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 > Prism pages.
  • The forms don't send anything. They are demo forms; see Forms.
  • The theme can't be installed on an older WordPress. Prism uses the Icon block's icon collections from WordPress 7.1; update WordPress first.

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 illustrations, portraits and logos are included under the same license. Geist, Geist Mono and Instrument Serif are licensed under the SIL Open Font License 1.1 (assets/fonts/*/OFL.txt).

Search documentation

Search the guides and every template's documentation