Overview#
Atlas is a personal-brand website for an independent real estate agent, built as a WordPress block
theme. The fictional brand is Delia Quintero, a Nashville agent who trained as an architect and works
in six neighborhoods. The theme has a home page with her portrait, "I'm buying" and "I'm selling"
paths, an instant home value estimate, the homes she is listing now, her record in four count-up
numbers, a review carousel, a neighborhood teaser and a booking call to action; a listings page with
For sale, Under contract and Sold tabs, filters, sorting and shareable links; a listing page driven
by ?id= with an illustrated room gallery, open houses you can add to a calendar and a tour request;
a selling page with a three-step home value estimate and a seller net sheet; a buying page with a
six-step guide, an affordability calculator and an FAQ; an interactive neighborhood map with six
guides; an about page with a sales chart and a timeline; a consultation booking page; 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/atlas.
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 parts drawn from data (the
listing cards, the listing page, the house and room illustrations) and the interactive parts (the
filters, the gallery, the calculators, the map highlight, the booking flow and the forms) work as in
the HTML version: they are empty blocks or small Custom HTML blocks filled and driven by
assets/js/main.js. Colors 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
atlas.zipfrom freeframe.dev/templates/atlas (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 Atlas.
- Go to Appearance > Atlas setup and click Create the demo pages. This creates the Home, Listings, Listing, Sell, Buy, Neighborhoods, About and Book a consult 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 > Atlas pages) made of section patterns. Every section is also offered on its own in the block inserter under Patterns > Atlas.
| Page | Slug | Page pattern | Sections (pattern files in patterns/) |
|---|---|---|---|
| Home | / |
page-home.php |
home-hero (portrait, buyer and seller paths, rating), estimate-teaser (instant estimate), featured-listings, record (count-up numbers), reviews (carousel), neighborhoods-teaser (links and map), home-cta |
| Listings | /listings/ |
page-listings.php |
listings-hero, listings-results (status tabs, filters, sort, results, empty state), off-market |
| Listing | /listing/?id=… |
page-listing.php |
listing-detail (gallery, facts, description, highlights, open houses, tour request, similar homes), listing-not-found |
| Sell | /sell/ |
page-sell.php |
sell-hero (numbers and what every listing includes), valuation (home value estimate), net-sheet, sell-process |
| Buy | /buy/ |
page-buy.php |
buy-hero (tour sketch), buyer-steps, affordability (calculator), buyer-faq |
| Neighborhoods | /neighborhoods/ |
page-neighborhoods.php |
neighborhoods-hero, neighborhood-map (map and info panel), neighborhood-guides (six guides and a call to action) |
| About | /about/ |
page-about.php |
about-hero (portrait and story), sold-history (numbers and chart), timeline, promises, credentials |
| Book a consult | /contact/ |
page-contact.php |
contact-hero, booking (consult types, day strip, times, details, summary, confirmation) |
| 404 | any missing URL | templates/404.html |
404 |
The Listing page shows one home from the data in assets/js/main.js, picked by ?id=
(/listing/?id=utah-4512); without an id it shows the first listing. It has no menu link: the
listing cards link to it.
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
(a privacy policy, for example) choose the Page with title template.
Project structure#
freeframe-atlas/
style.css theme header (name, version, requirements, license); no styles
theme.json design tokens: palette, fonts, spacing, layout widths, button
functions.php loads the files in inc/
inc/setup.php stylesheet and script (with the site title, its strings and the pages'
URLs), editor styles, the `js` class script, theme color 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 > Atlas 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 listings, neighborhoods, illustrations, filters, calculators, booking...
assets/icons/ SVG icons for the Icon block
assets/img/ portrait, logo, favicon, social image
assets/fonts/ Albert Sans and Gloock, 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-atlas.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 colors#
The colors are in theme.json (settings.color.palette) and in Appearance > Editor > Styles >
Colors > Palette:
| Color | Default | Used for |
|---|---|---|
| Paper, Sand 50 to 300 | #f6f2ea, #f1ece2 to #c9bba2 |
the page background, soft panels and borders |
| Ink 400 to 950 | #8a8d9a to #0e1430 |
text (800), headings (950), the top bar, dark sections and the footer |
| Cobalt 50 to 900 | #eef1fd to #18245f |
the brand blue: buttons and links (600, 700), the estimate and calculator boxes (800) |
| Persimmon 50 to 700 | #fff1eb to #a13517 |
the warm accent: highlighted words, section numbers, the selling path |
| Butter 100 to 400 | #fbf1cf to #e8b93b |
highlights on cobalt and ink, the under-contract pill |
| Moss 100 to 700 | #e3ead9 to #435a39 |
parks in the art and "sold" results |
| White | #ffffff |
cards and your own blocks |
The theme's stylesheet reads these colors from the palette (bg-cobalt-600, text-persimmon-700...),
so changing Cobalt 600 in Styles recolors every primary button at once. To move to another brand
color, change the whole scale together and keep the 600 shade dark enough for white button text
(4.5:1); use Persimmon 700 or darker for small persimmon text on paper, and Ink 600 or darker for
small grey text. The illustrations carry their own hex colors: each listing's art colors and the
GLASS, LAWN, TREE and TREE_DARK constants in assets/js/main.js, the maps, sketch and chart
in Custom HTML blocks, and the SVG files in assets/img/. Search for the old values (for example
#2742cc and #e4552b) to change them too. The browser UI color (theme-color) is set in
freeframe_atlas_head_meta() in inc/setup.php.
Component classes from the HTML template work on blocks too: add them in Block settings >
Advanced > Additional CSS class(es). card gives a group the white card look, panel the sand
panel, cobalt-block and ink-block the dark gradient boxes (add on-dark so text and focus
outlines suit them), kicker is the small numbered label above a heading (its number is a
kicker-num span), display the headline style, lede the intro paragraph, hl (on a span in a
heading) the persimmon highlighted words and swash their hand-drawn underline, tag a small sand
pill and link-arrow the underlined link with an arrow.
Typography#
Two fonts are bundled in assets/fonts/ and declared in theme.json
(settings.typography.fontFamilies), so no request goes to Google: Gloock (one weight; preset
display) for headlines, prices and big numbers, and Albert Sans (variable, 400 to 800, with
italics; preset sans) for text. Gloock has no italic, so emphasis in headlines uses the hl color
and the swash underline. 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--display and --sans), so keep those slugs. SVGs
with text (the maps, the sketch, the chart and the footer wordmark) name the fonts in font-family
attributes.
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 > Atlas.
What the script fills or drives, and pictures drawn in SVG, are empty blocks or Custom HTML
blocks: edit Custom HTML in the block's HTML view, and keep their ids and data- attributes.
| Pattern | Custom HTML | What it is |
|---|---|---|
home-hero |
the house in the "Just sold" card | an SVG picture |
estimate-teaser |
the neighborhood and size form | see Home value estimate |
featured-listings, listings-results, listing-detail |
the "turn on JavaScript" notes; in the listings: the status tabs, sort menu, filters, result count and the empty state's picture and buttons; on the listing page: the breadcrumb, the status line, the gallery, the facts and the tour form | see Listings and filters and Listing page |
reviews |
the Previous and Next buttons and the "1 / 6" status | see Carousel, numbers and motion |
neighborhoods-teaser, 404 |
the maps (and the 404's bobbing pin) | SVG pictures |
valuation, net-sheet, affordability |
the step indicator and the forms with their results | see Features |
buy-hero, sold-history |
the tour sketch and the sales chart | SVG pictures (labelled for screen readers) |
neighborhood-map |
the map and the info panel | see Neighborhood map |
booking |
the form, the confirmation and the summary | see Consultation booking |
header, footer |
the menu button; the newsletter form and 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 tabindex, so the script turns classes
starting with ffd- into attributes when the page loads, ff-hidden into hidden and
ff-tabindex-0 into tabindex="0": ffd-featured-listings on the home page's empty list becomes
data-featured-listings, and ffd-area-card--germantown on a guide becomes
data-area-card="germantown".
revealfades a block in as it scrolls into view;reveal-delay-1toreveal-delay-3stagger it.link-cardon a group makes the whole group clickable: the first link inside it stretches over the group (the buying and selling paths on the home page, the logo).
The data that drives the interactive parts lives in constants at the top of the second part of
assets/js/main.js, as in the HTML version:
| Constant | What it holds |
|---|---|
AGENT |
name, phone, email, office address and time zone (America/Chicago), used in calendar files |
AREAS |
the six neighborhoods: id, name, ppsf (price per square foot), median, dom (average days on market) and yoy (one-year price change) |
LISTINGS |
every home, for sale, under contract or sold |
VALUATION |
factors and rounding for the home value estimate |
CONSULT_TYPES |
bookable consultations: label, length in minutes and where they happen |
AVAILABILITY |
weekly hours, slot spacing, notice period, days shown, closed dates and the demo "booked" share |
PRICE_STEPS, SORTS, AFFORD |
the listings' price filter steps and sort options; the affordability rules |
Adding a listing
Copy an object in LISTINGS (in a child theme, see Child themes) and change it:
{ id: 'utah-4512', status: 'active', featured: true, address: '4512 Utah Ave', area: 'sylvan-park', zip: '37209',
price: 925000, beds: 3, baths: 2, sqft: 1780, lot: '0.21 acres', year: 1928, type: 'Single-family', style: 'Craftsman bungalow', dom: 3, listed: '2026-10-12',
headline: 'A Sylvan Park bungalow with the original built-ins',
description: ['First paragraph.', 'Second paragraph.'],
features: ['Original built-ins and fireplace', 'Deep back deck'],
openHouses: [['sat', '13:00', '15:00']],
art: { style: 'bungalow', sky: '#e3ead9', body: '#8fa97a', roof: '#435a39', trim: '#f6f2ea', door: '#a13517' } }idmust be unique; it is the?id=of the listing page (/listing/?id=utah-4512).statusisactive,pendingorsold. Sold homes also needsold: { price, date }(dateasYYYY-MM-DD).areamust be anidfromAREAS.featured: trueputs a home for sale on the home page (the first three are shown).listed(andsold.date) drive the "Newest" sort.openHousesis a list of[weekday, start, end]in Nashville time, with weekdayssuntosatand 24-hour times. Each is shown on its next date, so they never go stale. Only homes for sale show open houses.art.stylepicks the house drawing (see Images and artwork).
The cards, the listing page, the "recent sales" in the estimate, the map counts and the guide buttons all read this array. The listings data, the neighborhood names and the sentences built from them stay in English in the script; the labels around them are translatable.
The text that is page content is not in the data: the "Open this weekend" bar in the header, the "Just sold" card on the home page, the record numbers (238 homes, 8 days, 101.6%, $171M, also on the selling and about pages), the reviews, the guides' text and the chart. Keep them in step when you change the data.
Images and artwork#
- Houses and rooms.
houseSVG()inassets/js/main.jsdraws a 400 × 300 (4:3) house for each listing from itsartobject:victorian,farmhouse,bungalow,townhome,modern,ranch,foursquare,tudororcondo, colored bysky,body,roof,trimanddoor. The gallery on the listing page adds five rooms (ROOMS: living, kitchen, bedroom, bath, yard), listed with their captions inGALLERY. To use photos, add aphotoURL to each listing and return an<img>instead ofhouseSVG(l, ...)inlistingCard()androomSVG(); keep a 4:3 crop. - Portrait.
assets/img/delia-portrait.svg(480 × 600, 4:5) is an Image block on the home, about and listing pages: select it and Replace it with a 4:5 photo. The[&_img]:archclass rounds the top into an arch. - Maps, sketch and chart are inline SVG in Custom HTML blocks (
neighborhoods-teaser,neighborhood-map,404,buy-hero,sold-history); replace any picture with an Image block if you prefer. - The logo is
assets/img/logo.svgin an Image block next to the name in the header and footer patterns. The social image isassets/img/og-image.png(1200 × 630, sourceog-image.svg); the faviconassets/img/favicon.svgis used until you set a Site Icon in Settings > General.
Icons are Icon blocks: select one and pick another from the Atlas 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 top bar with this weekend's open house is the first group of the header: change its link
(/listing/?id=fatherland-1418) and text, or delete the group.
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, Listings, Buy, Sell, Neighborhoods, About, Contact). Its first link (Home) and
last link (Contact) are hidden in the desktop header, which has the logo and its own "Book a
consult" button; keep them first and last, or remove the .site-nav … :first-child and
:last-child rules 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 in the menu; on the listing page the
script marks Listings.
The footer's Explore and Neighborhoods menus are List blocks: click a link to change it, press Enter
to add one. The phone number, email and address also appear in the header, on the listing and
booking pages and in AGENT; search for them when you change them. The license line is placeholder
text (license no. 000000, Larkspur & Main Realty): replace it with your real details and check your
state's advertising and fair-housing rules.
Features#
Listings and filters#
initListings() fills the listings page ([data-listings]) with cards in [data-results].
- Status tabs are buttons with
data-status(active,pending,sold,all) andaria-pressed. - Filters: neighborhood (
area), minimum bedrooms (beds, 1 to 4), minimum and maximum price (min,max, fromPRICE_STEPS) and sort (newest,price-asc,price-desc,size). Sold homes are filtered and sorted by their sold price. - Live counts: each tab and each neighborhood option shows how many homes you would see if you picked it.
- Shareable links: the state is written to the address bar, for example
/listings/?status=sold&area=east-nashville&beds=3; defaults are left out and unknown values are ignored. Link to a filtered list from any button (the guides link to/listings/?area=…). - Empty state: shown when nothing matches, with a clear-filters button.
The result count is an aria-live region, so screen readers hear "3 homes for sale" after each
change.
Listing page#
initDetail() reads ?id=, fills every [data-l="…"] block (address, area, price, headline,
description, facts, highlights...) and sets the tab title to "address, neighborhood — site title"
(the site title from Settings > General). An unknown id shows the listing-not-found section,
whose heading becomes the page's only h1. The gallery has previous and next buttons, thumbnails
with aria-current, and answers the left and right arrow keys when focused. Sold homes show the
sold price and the percentage of asking, and a "Want one like this?" panel instead of the tour form;
homes under contract say so in the open-house box. "You might also like" shows homes from the same
neighborhood first.
Open houses and calendar files#
nextOpenHouse() turns each [weekday, start, end] into its next date in AGENT.timeZone, skipping
one that has already ended today. Times are shown in Central Time, with the visitor's own time added
when they are elsewhere. "Add to calendar" downloads an iCalendar (.ics) file built by buildIcs()
(times in UTC, so every calendar app places it correctly).
Home value estimate#
The teaser on the home page ([data-estimate-teaser]) and the three-step estimate on the selling
page ([data-valuation]) share estimate():
value = sqft x area ppsf x type factor x condition factor x (1 + bed and bath adjustment) x (1 + extras)
range = value plus or minus the condition's spread, rounded to $5,000The factors are in VALUATION. The teaser links to /sell/?area=…&sqft=…#valuation, which
prefills the estimate. Each step validates before moving on and moves focus to the next step's
heading. The result shows where the estimate sits against the neighborhood median and lists the
agent's sales there; "Use in the net sheet" copies the midpoint into the net sheet. The page says
clearly that it is illustrative, not an appraisal.
Seller net sheet#
[data-net-sheet] recalculates on every input: sale price minus commission and closing costs (in
percent), mortgage payoff and repairs or credits. The bar's [data-bar] segments are sized as a
share of the price, and a warning appears when costs exceed the price. Fields with data-money get
thousands separators when you leave them.
Affordability calculator#
[data-afford] on the buying page applies the 28/36 rule in AFFORD: housing costs up to 28% of
gross monthly income, and housing plus debts up to 36%. It finds the highest price whose monthly
cost (principal and interest, property tax, insurance, mortgage insurance below 20% down, HOA dues)
fits both, draws a donut of the payment and links to /listings/?max= with the highest price step at
or below the result. Change FRONT_END, BACK_END, PMI_RATE or PMI_BELOW for other
assumptions.
Neighborhood map#
Hovering or focusing an area on the map ([data-area], a link to its guide) highlights its guide
card ([data-area-card], the ffd-area-card--<id> class on the guide's group) and fills the info
panel; hovering or focusing a card highlights its area. Selecting an area, or arriving with a hash
such as #east-nashville, keeps it highlighted. Blocks with ffd-area-stat--<id>:median (also
ppsf, dom, yoy) show statistics from AREAS, and spans with data-area-count="<id>" in the
guide buttons show the number of homes for sale. To add a neighborhood, add it to AREAS, a polygon
to the maps and a guide (duplicate one in List View, then change its HTML anchor and classes).
Consultation booking#
initBooking() runs the booking page:
- Consult type: radio cards matching
CONSULT_TYPES(buyer,seller,value,relocation)./contact/?type=sellerpreselects one and?listing=<id>prefills the message. - Day: the next
AVAILABILITY.daysShowndays with a free time, skippingcloseddates and days without hours. - Time: slots every
slotStepminutes inside the day's hours, long enough for the consult and at leastminNoticeHoursfrom now, in Nashville time. - Details: name, email, optional phone, timeline and message.
The summary updates live; on submit, missing choices are reported inline and focus moves to the
first problem. The confirmation offers an .ics download. demoBusyShare marks a fixed share of
times as booked so the demo looks realistic; set it to 0 once connected.
Carousel, numbers and motion#
- Reviews (
ffd-carousel) are a scroll-snap track with Previous and Next buttons and a "1 / 6" status; swipe, trackpad and keyboard scrolling work, and it never moves on its own. - Count-up numbers animate spans with
data-count(anddata-decimals) in the record section. - Scroll reveals fade in
revealblocks; revealing one reveals everything above it. - FAQ: the buyer questions are an Accordion block (WordPress's own script).
- Back to top is created by the last block in
assets/js/main.js.
Animations, smooth scrolling and transitions are off for visitors who prefer reduced motion.
Block styles#
Select a Button block and choose a style in the block settings: the default (cobalt), Persimmon,
Ink, Butter, Outline and Outline (on dark). Add is-large or is-small in
Additional CSS class(es) for the large and small sizes.
Forms#
Every form is a demo: it validates in the browser and shows a success state, but nothing is sent or booked until you connect it. The confirmations say so.
| Form | Where | Validates |
|---|---|---|
Newsletter ([data-newsletter-form]) |
footer | |
Tour request ([data-tour-form]) |
listing page | date (today or later), name, email, optional phone |
| Home value estimate | selling page | address, neighborhood, square feet (400 to 9,000) |
| Booking | booking page | consult type, day, time, name, email, optional phone |
Fields with data-validate (required, email, phone-optional, address, sqft, future-date,
choice) are checked on submit and again as the visitor corrects them; data-message overrides the
empty-field message. Errors appear under the field, are linked with aria-describedby and focus
moves to the first problem.
Connecting a form#
Use a form plugin (replace the form's Custom HTML block with the plugin's block; add the classes
label, input and btn btn-primary to its labels, fields and button), or send the form from the
script (in a child theme). In the newsletter handler in assets/js/main.js (search for
Connect a real endpoint here), send it before showing the success panel, for example with
Formspree:
form.addEventListener('submit', function (e) {
e.preventDefault();
if (!validateForm(form)) return;
fetch('https://formspree.io/f/your-id', { method: 'POST', body: new FormData(form), headers: { Accept: 'application/json' } })
.then(function (res) {
if (!res.ok) throw new Error('Send failed');
form.hidden = true;
success.classList.remove('hidden');
success.focus();
})
.catch(function () { alert('Sorry, that did not send. Please email hello@deliaquintero.example.'); });
});Do the same in the tour form handler in initDetail(); it also sends the listing address in a
hidden listing field.
Connecting a booking tool#
Either replace the booking section with your Cal.com or Calendly embed (a Custom HTML block with
its embed code), or keep this flow and post the request to your form service in the submit handler
in initBooking() (the block marked "Connect a real booking tool"), sending state.type,
state.slot.start.toISOString() and the details. Set AVAILABILITY.demoBusyShare to 0 either way.
SEO and sharing#
Page titles come from WordPress: the page title, an em dash and the site name (for example "Listings — Delia Quintero Real Estate"); the home page shows the site name and tagline (**Settings
General**), and the listing page sets its own title from the listing. 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. Because listing pages are built from?id=, search engines see one Listing page; if listings matter for search, give each home a page of its own.RealEstateAgentstructured data (schema.org) with your real details can be added by most SEO plugins; the theme doesn't include it because it would describe a fictional agent.
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 (the not-found state takes it over),
labelled sections, visible focus outlines (cobalt on light, butter on dark), the menu button with
aria-expanded and a label that changes with its state (Escape closes the menu), status tabs and day
buttons with aria-pressed, radios for the other choices, aria-live result counts, estimate,
calculator results and booking summary, a keyboard-operable gallery and map, booked times announced
as booked, form errors linked with aria-describedby and aria-invalid, alt text, role="img"
labels or aria-hidden on every picture, and reduced-motion support. The demo passes axe (WCAG 2.1
A and AA).
When you edit: keep one Heading 1 per page and don't skip heading levels, give images alt text (or
mark them decorative), keep link text meaningful, check contrast when you change colors, label any
field you add to a form and add data-validate when it needs checking.
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-atlas, 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-atlas. All text in patterns,
templates and PHP goes through WordPress's translation functions; the script's interface strings
(labels, validation messages, open-house and booking lines) are translated in PHP
(freeframe_atlas_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-atlas wp-content/themes/freeframe-atlas/languages/freeframe-atlas.potSave freeframe-atlas-es_ES.mo (for example) in wp-content/languages/themes/ or in the theme's
languages/ folder. Page content created by Atlas setup is copied into your pages in the site's
language at that moment; translate it in the editor like any other content. Dates and prices are
formatted in US English by the script (toLocaleDateString and toLocaleString with en-US).
Child themes#
Use a child theme for changes you want to keep when Atlas is updated (your listings in particular).
Create wp-content/themes/atlas-child/style.css:
/*
Theme Name: Atlas Child
Template: freeframe-atlas
*/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-atlas) and enqueue yours in the child's functions.php, keeping the inline
window.freeframeAtlas data (or copy it). 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 > Atlas.
- A listing page says "That listing isn't on my desk". The
?id=doesn't match anidinLISTINGS. Check the spelling in the link (the header's open-house link included). - Listing links go to the wrong page. The script finds the Listing, Listings and Sell pages by
their slugs (
listing,listings,sell; seefreeframe_atlas_page_urls()ininc/setup.php). Keep those slugs, or change the list there. - Open houses show the wrong day or time. Weekdays are
suntosat, times are 24-hour Nashville time, and the zone isAGENT.timeZone. - No booking days appear. Check
AVAILABILITY.hours(day 0 is Sunday) andclosed, and thatminNoticeHoursisn't larger than your hours. - Home or Contact is missing from the desktop menu. The desktop header hides the menu's first and last links. See Navigation, header and footer.
- Atlas 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 > Atlas 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 portrait, house and room illustrations, maps,
chart and logo are included under the same license. Albert Sans and Gloock are licensed under the
SIL Open Font License 1.1 (OFL.txt in each folder of assets/fonts/). The agent, brokerage, homes,
prices and clients are fictional.