Overview#
Atlas is a personal-brand website template for an independent real estate agent or realtor. The demo content is for
Delia Quintero, a fictional Nashville agent who trained as an architect and works in six neighborhoods. This is
the Astro version: Astro 7, TypeScript and Tailwind CSS v4, with no UI framework. Pages are .astro
files, the listings, neighborhoods, home value formula and booking rules live in typed data files in src/data/, and
every house and room picture is an SVG drawn from that data by src/lib/art.ts, so there are no photos to license.
Listing cards and their illustrations are rendered at build time; one TypeScript module adds the filters,
calculators, map and booking flow in the browser. It builds to plain static files. This is version 1.0.0 (see
CHANGELOG.md). Live demo: freeframe.dev/templates/atlas.
Quick start#
Requirements: Node.js 24 or newer and pnpm (npm install -g pnpm, or use npm with the same script names).
pnpm install
pnpm dev # http://localhost:4321, reloads as you editOther scripts:
| Command | What it does |
|---|---|
pnpm build |
Builds the static site into dist/ |
pnpm preview |
Serves dist/ locally |
pnpm check |
astro check: type-checks .astro files and TypeScript |
pnpm typecheck |
astro sync && tsc --noEmit with TypeScript 7 |
pnpm lint |
ESLint (typescript-eslint and eslint-plugin-astro) |
Pages#
| File | URL | Purpose and notable features |
|---|---|---|
src/pages/index.astro |
/ |
Hero with portrait and "I'm buying" / "I'm selling" paths, instant estimate teaser, featured listings, count-up stats, testimonial carousel, neighborhood teaser with map, booking CTA |
src/pages/listings.astro |
/listings |
Status tabs (for sale, under contract, sold, all), neighborhood, bedroom and price filters with live counts, sort, empty state, URL state, off-market CTA |
src/pages/listing.astro |
/listing?id= |
Reads ?id=: illustrated gallery, key facts, description, highlights, open houses with .ics downloads, tour request form, similar homes, not-found state |
src/pages/sell.astro |
/sell |
Three-step home value estimate, seller net proceeds sheet, four-week selling plan |
src/pages/buy.astro |
/buy |
Tour-notes sketch, six-step buyer guide, affordability calculator with donut chart, FAQ accordion |
src/pages/neighborhoods.astro |
/neighborhoods |
Interactive SVG map with an info panel, six guide cards with statistics and links to filtered listings |
src/pages/about.astro |
/about |
Story and portrait, sold history chart, timeline, three principles, credentials, personal facts |
src/pages/contact.astro |
/contact |
Consult types, day strip, time slots, details form, live summary, confirmation with .ics download |
src/pages/404.astro |
any unknown URL | Map with a bobbing pin and links back into the site |
Project structure#
atlas/
astro.config.mjs site URL, base path, static file output
package.json scripts and dependencies
tsconfig.json extends Astro's strict preset
eslint.config.mjs ESLint flat config
public/img/ favicon, social image (SVG source and PNG) and the illustrated portrait
src/
pages/*.astro one file per page (see the table above)
layouts/Layout.astro <html>, <head> metadata, header, footer and the script imports
components/
SiteHeader.astro "Open this weekend" bar, logo, main menu, Book button and mobile menu
SiteFooter.astro contact details, link lists, newsletter form and copyright
ListingCard.astro a listing card with its house illustration
NashvilleMap.astro the stylized neighborhood map (static or interactive)
data/
listings.ts AREAS (neighborhoods), LISTINGS, STATUS_LABEL, PRICE_STEPS, SORTS
agent.ts AGENT, CONSULT_TYPES and AVAILABILITY (booking rules)
calculators.ts VALUATION (home value estimate) and AFFORD (affordability)
nav.ts header and footer links, the "Open this weekend" listing
guides.ts the neighborhood guide text
testimonials.ts the home page reviews
buy.ts the buyer guide steps and FAQ
about.ts timeline, principles, credentials, facts and sales per year
lib/
site.ts site name, theme color and the url() helper
art.ts houseSVG(), roomSVG() and GALLERY: the SVG illustrations
format.ts money(), moneyShort(), shortDate() and other formatting helpers
icons.ts small inline SVG icons used in markup built from data
scripts/
main.ts all interactivity (filters, listing page, calculators, map, booking, forms)
back-to-top.ts the back-to-top button
styles/global.css Tailwind source: brand tokens (@theme) and component classes
README.md, CHANGELOG.md, DOCS.mdCustomising#
Brand and colors#
All design tokens live in the @theme block at the top of src/styles/global.css. Each token becomes Tailwind
utilities (bg-cobalt-600, text-persimmon-700, border-sand-200 and so on), and pnpm dev picks up changes
immediately.
| Token group | Used for |
|---|---|
--color-paper, --color-sand-50 to --color-sand-300 |
Page background, soft panels, borders |
--color-ink-400 to --color-ink-950 |
Text, the footer and dark sections |
--color-cobalt-50 to --color-cobalt-900 |
The brand blue: primary buttons, links, the estimate and calculator panels |
--color-persimmon-50 to --color-persimmon-700 |
The warm accent: highlighted words, the "selling" path, section numbers |
--color-butter-100 to --color-butter-400 |
Highlights on dark backgrounds, the "under contract" pill |
--color-moss-100 to --color-moss-700 |
Parks and gardens in the art, "sold" results |
To change the brand blue, replace the cobalt scale:
@theme {
--color-cobalt-600: #0f766e;
--color-cobalt-700: #0d5f59;
--color-cobalt-800: #0b4a46;
}Contrast rules noted in the comments: use persimmon-600 or darker behind white text, persimmon-700 for small
persimmon text on paper, and ink-600 or darker for small grey text. Reusable pieces such as .btn, .btn-primary,
.card, .kicker, .display, .hl, .swash, .input, .tab, .seg, .slot and .listing-card are defined in
the @layer components block of the same file. The theme-color meta tag comes from site.themeColor in
src/lib/site.ts.
The illustrations use hex colors directly: the art colors of each listing in src/data/listings.ts, the constants
GLASS, LAWN, TREE and TREE_DARK at the top of src/lib/art.ts, the map in
src/components/NashvilleMap.astro, the chart colors in src/pages/about.astro and the SVG files in public/img/.
If you change the palette, search for the old hex values (for example #2742cc and #e4552b) and replace them.
Typography#
Two Google Fonts are loaded in the <head> of src/layouts/Layout.astro:
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Albert+Sans:ital,wght@0,400..800;1,400..600&family=Gloock&display=swap" />--font-display (Gloock, a high-contrast display serif with a single weight) is used for headlines, prices and big
numbers through the .display class and font-display; --font-sans (Albert Sans) is the body face. Gloock has no
italic, so emphasis in headlines uses the .hl color and the hand-drawn .swash underline instead. To swap fonts,
change the link in the layout and the two --font-* tokens in src/styles/global.css. Inline SVGs that contain text
(the logo, map labels, the chart and the footer wordmark) name the fonts in font-family attributes.
Content#
Page text is in the .astro files. Repeated content and everything the interactive parts read lives in src/data/:
| Export | File | What it holds |
|---|---|---|
AGENT |
agent.ts |
Name, phone, email, office address and time zone (America/Chicago), used in calendar files and titles |
CONSULT_TYPES, AVAILABILITY |
agent.ts |
Bookable consultations and the weekly booking rules |
AREAS |
listings.ts |
The six neighborhoods: id, name, ppsf (price per square foot), median, dom (average days on market) and yoy (one-year price change) |
LISTINGS |
listings.ts |
Every home, active, under contract or sold |
PRICE_STEPS, SORTS |
listings.ts |
The price filter options and sort orders |
VALUATION, AFFORD |
calculators.ts |
Factors for the home value estimate and the affordability rules |
GUIDES |
guides.ts |
The neighborhood guide text, tags and "best for" line |
TESTIMONIALS |
testimonials.ts |
The home page reviews |
BUYER_STEPS, BUYER_FAQ |
buy.ts |
The buyer guide and FAQ |
TIMELINE, PRINCIPLES, CREDENTIALS, FACTS, SALES_BY_YEAR |
about.ts |
The about page lists and chart |
A listing looks like this (the Listing interface in listings.ts documents every field):
{ 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" } },To add a home, copy an object in LISTINGS and change it:
idmust be unique; it is the?id=of the listing page.statusisactive,pendingorsold. Sold homes also needsold: { price, date }(dateasYYYY-MM-DD).areamust be anidfromAREAS.featured: trueputs an active home 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 one is shown on its next date, so they never go stale. Only active homes show open houses.art.stylepicks the house drawing (see Images and artwork).
TypeScript checks the shape: pnpm check reports a missing field or a misspelled status. The listing cards, the
listings filters, the listing page, the "recent sales" on the estimate, the map counts and the guide buttons all read
this array, so nothing else needs updating. Rebuild (or keep pnpm dev running) to see a change.
The home page stats and the sell page numbers are text in index.astro, sell.astro and about.astro. Keep the
headline numbers (238 homes, 8 days, 101.6%, $171M) in step across them when you change them.
Images and artwork#
Everything is SVG:
- Houses.
houseSVG()insrc/lib/art.tsdraws a 400 x 300 (4:3) house for each listing from itsartobject. The styles arevictorian,farmhouse,bungalow,townhome,modern,ranch,foursquare,tudorandcondo; the colors aresky,body,roof,trimanddoor.ListingCard.astrocalls it at build time. - Rooms. The gallery on the listing page shows the house plus five rooms (
living,kitchen,bedroom,bath,yard) drawn byroomSVG()and tinted from the same colors. The list and captions are inGALLERY. The gallery depends on?id=, somain.tsdraws it in the browser with the same functions. - Portrait.
public/img/delia-portrait.svg(480 x 600, 4:5) is used on the home, about and listing pages. Replace it with a 4:5 photo; the.archclass rounds the top into an arch. - Map.
src/components/NashvilleMap.astrodraws the stylized map (not to scale) on the home, neighborhoods and 404 pages.SHAPESholds each area's outline, color and label position; withinteractiveeach area is an<a class="map-area" data-area="...">link.
To use real photos for listings, add an image path to each listing (for example photo: "/img/utah-4512.jpg" with
the file in public/img/) and render <img src={url(l.photo)} ...> in ListingCard.astro instead of the
houseSVG() fragment, and return an <img> from the gallery in main.ts. Keep a 4:3 aspect ratio so cards line up.
The social image is public/img/og-image.png (1200 x 630); og-image.svg is its editable source. After editing the
SVG, export it to PNG with any tool (or open it in a browser and take a 1200 x 630 screenshot). The favicon is
public/img/favicon.svg.
Navigation, header and footer#
The header and footer are components used by Layout.astro, so each change applies to every page:
src/data/nav.tsholdsMAIN_NAV(desktop menu),MOBILE_NAV(the same links plus Home and Contact),FOOTER_EXPLORE,FOOTER_AREAS(neighborhood ids for the footer) andWEEKEND_BAR, the listing the "Open this weekend" bar links to. To remove the bar, delete its<div>at the top ofSiteHeader.astro.- Each page passes
currentto<Layout>(for examplecurrent="sell"). Links whoseidmatches getaria-current="page", which highlights them. The home page usesindexand the contact pagecontact(which also marks the "Book a consult" button); the listing page useslistings.
To add a page, create src/pages/<name>.astro wrapped in <Layout title="..." description="..." current="<name>">
and add a link to nav.ts. Link with url("/<name>") from src/lib/site.ts, never a hard-coded / path, so the
site also works from a sub-folder.
Features#
Listings filters#
src/pages/listings.astro renders every listing card at build time, showing the homes for sale (newest first) and
hiding the rest. initListings() in main.ts then shows, hides and reorders those cards.
- 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, fromSORTS). 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, given the other filters.
- URL state: the state is written to the address bar with
history.replaceState, for example/listings?status=sold&area=east-nashville&beds=3. Defaults (status=active,sort=newest) are left out. Unknown values (?status=banana,?min=7) are ignored and dropped. - Empty state:
[data-empty]appears 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= from the URL and fills every [data-l="..."] element. Without an id it shows the first
listing; with an unknown one it shows the not-found section ([data-not-found]), which takes over the page's <h1>.
The tab title becomes "address, neighborhood — site name", where the site name is read from og:site_name
(site.name in src/lib/site.ts).
The gallery has previous and next buttons, thumbnails with aria-current, and responds to the left and right arrow
keys when focused. Sold homes show the sold price, 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" copies up to three cards
(same neighborhood first) from a <template data-card-pool> that holds every card, rendered at build time.
Because one page serves every listing, search engines see a single /listing. If listings matter for search, see
SEO and sharing.
Open houses and calendar files#
nextOpenHouse() turns each [weekday, start, end] into the next real 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 in another
time zone. "Add to calendar" builds an iCalendar file with buildIcs() (times in UTC, so every calendar app places it
correctly) and downloads it with downloadIcs().
Home value estimate#
The teaser on the home page ([data-estimate-teaser]) and the three-step wizard on the sell page ([data-valuation])
share estimate() in main.ts:
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 (src/data/calculators.ts). For example, an updated 1,780 sq ft house in Sylvan Park
($520 per sq ft) gives $880,000 to $970,000. The teaser links to /sell?area=...&sqft=...#valuation, which
pre-fills the wizard. Each wizard 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 sold homes in that area.
"Use in the net sheet" copies the midpoint into the net sheet. The page states clearly that the estimate is
illustrative.
Seller net sheet#
[data-net-sheet] recalculates on every input: sale price minus commission (percent), closing costs (percent),
mortgage payoff and repairs or credits (the defaults give $376,050). The colored bar uses [data-bar] segments sized
as a share of the price, and a warning appears if costs exceed the price. Money fields with data-money are
formatted with thousands separators when you leave them.
Affordability calculator#
[data-afford] on the buy page applies the 28/36 rule from 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 limits, draws an SVG donut of the payment, and
links to /listings?max= with the highest price step at or below the result. With the defaults it shows $601,000
and $3,843 a month. Change FRONT_END, BACK_END, PMI_RATE or PMI_BELOW in AFFORD to use different
assumptions.
Neighborhood map#
On the neighborhoods page, hovering or focusing an area on the map ([data-area]) highlights its guide card
([data-area-card]) and fills the info panel ([data-map-info]); hovering or focusing a card highlights its area.
Each area is a link to its guide, so Tab and Enter work. Selecting an area (or arriving with a #east-nashville style
hash) keeps it highlighted. The listing counts (data-area-count="id") and statistics (data-area-stat="id:median",
also ppsf, dom, yoy) are rendered at build time from AREAS and LISTINGS, and refreshed by main.ts.
To add a neighborhood, add it to AREAS, add its shape to SHAPES in NashvilleMap.astro, and add its guide to
GUIDES in src/data/guides.ts (and to FOOTER_AREAS if it belongs in the footer).
Consultation booking#
initBooking() in main.ts runs the booking form on the contact page:
- Consult type: radio cards for the
CONSULT_TYPES(buyer,seller,value,relocation).?type=sellerpre-selects one, and?listing=<id>pre-fills the message. - Day: a strip of the next
AVAILABILITY.daysShowndays that have a free slot, skipping closed dates and days without hours. - Time: slots every
slotStepminutes inside the day's windows, long enough for the chosen consult and at leastminNoticeHoursfrom now. All times are Nashville time (AGENT.timeZone), with the visitor's local time added in the summary when it differs. - Details: name, email, optional phone, timeline and message.
The summary panel updates live. On submit, missing choices are reported inline and focus moves to the first problem.
The confirmation repeats the booking and offers an .ics download. demoBusyShare marks a fixed share of slots as
taken (the same ones on every load) so the demo looks realistic; set it to 0 when connected to a real calendar. If
you change the consult types, update both CONSULT_TYPES and the radio cards in contact.astro.
Carousel, stats, reveals and back to top#
- Testimonials (
[data-carousel]) are a native scroll-snap track with previous and next buttons and a "1 / 6" status. Swipe, trackpad and keyboard scrolling work. - Count-up stats animate elements with
data-count(and optionaldata-decimals) once they scroll into view. - Scroll reveals fade in
.revealelements. Revealing an element also reveals everything above it, so jumping with the End key or an anchor never leaves hidden content. The layout adds thejsclass to<html>in the<head>, and the reveal styles only apply under.js, so nothing stays hidden without JavaScript. - FAQ buttons (
.acc-btn) toggle their panel andaria-expanded. - Back to top is created by
src/scripts/back-to-top.ts; delete that file, its import inLayout.astroand the.back-to-topstyles to remove it.
Animations, smooth scrolling and transitions are turned off for visitors who prefer reduced motion.
Forms#
| Form | Where | Validates |
|---|---|---|
Newsletter ([data-newsletter-form]) |
Footer of every page (SiteFooter.astro) |
|
Tour request ([data-tour-form]) |
listing.astro |
Date (today or later), name, email, optional phone |
| Home value wizard | sell.astro |
Address, neighborhood, square feet (400 to 9,000) |
| Booking | contact.astro |
Consult type, day, time, name, email, optional phone |
Fields with data-validate are checked on submit and again as the visitor corrects them; errors appear under the
field and are linked with aria-describedby. The values are required, email, phone-optional, address,
sqft, future-date and choice (a radio group), and data-message overrides the empty-field message.
The demo forms do not send anything. After validation they show a success state that says so. To send data,
replace the commented block in the submit handler in src/scripts/main.ts.
Connecting to Formspree#
Create a form at Formspree, then in the newsletter handler (search for Connect a real endpoint here) send the data
before showing the success panel:
form.addEventListener("submit", async (e) => {
e.preventDefault();
if (!validateForm(form)) return;
const res = await fetch("https://formspree.io/f/your-id", {
method: "POST",
body: new FormData(form),
headers: { Accept: "application/json" },
});
if (!res.ok) {
alert("Sorry, that did not send. Please email hello@deliaquintero.example.");
return;
}
form.hidden = true;
success?.classList.remove("hidden");
success?.focus();
});Do the same in the tour form handler in initDetail(). Every form field has a name, so FormData picks them all
up; the tour form also sends the listing address in a hidden listing field. The site is static, so a form service
(or your own API) is the simplest route; Astro's server endpoints need an adapter and server output instead of the
static build.
Connecting a booking tool#
The booking flow is a front end only. Either replace the form in contact.astro with your Cal.com or Calendly embed,
or keep it 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 fields. Set
AVAILABILITY.demoBusyShare to 0 either way.
SEO and sharing#
Each page passes title and description to <Layout>. The layout outputs the meta description, a canonical link,
Open Graph tags (og:title, og:description, og:type, og:site_name, og:url, og:image), twitter:card and
theme-color. 404.astro passes noindex, which outputs <meta name="robots" content="noindex"> and no canonical.
The listing page sets its tab title from the listing in the browser.
Canonical, og:url and og:image are absolute URLs built from site in astro.config.mjs. Set your domain when
building:
SITE_URL=https://www.example.com pnpm buildor change the fallback (https://atlas.example.com) in astro.config.mjs.
Suggestions: add @astrojs/sitemap (or a hand-written public/sitemap.xml) and a public/robots.txt. Because
listing pages are built from ?id=, search engines see one /listing; if listings matter for search, give each home
its own static page with a dynamic route (src/pages/listings/[id].astro with getStaticPaths() over LISTINGS).
Real estate sites usually must show license and brokerage details and fair-housing wording. The footer has
placeholders (license no. 000000, Larkspur & Main Realty) in SiteFooter.astro; replace them with your real details
and check your state's advertising rules.
Accessibility#
- Skip link, landmarks (
header,nav,main,footer), one<h1>per page and a logical heading order. - Visible focus outlines (cobalt on light, butter on dark backgrounds) and WCAG AA contrast for text.
- The mobile menu button uses
aria-expandedandaria-controls; Esc closes the menu. - Status tabs, bedroom choices and day buttons use
aria-pressedor native radios; result counts, the estimate teaser, calculator results and the booking summary arearia-liveregions. - The map areas are links with descriptive labels; illustrations are either labelled (
role="img") or hidden from screen readers when decorative. The sold-history chart has a<title>and a<desc>listing every year. - Booked time slots are announced as "booked" rather than offered.
- Motion is reduced for visitors who prefer it.
When editing, keep one <h1> per page, give every new input a <label>, and keep the data-* hooks that
main.ts uses.
Building and deploying#
pnpm build # static site in dist/
pnpm preview # preview dist/ locallydist/ is plain HTML, CSS and JavaScript. Pages are written as sell.html rather than sell/index.html
(build.format: "file" in astro.config.mjs), and links point to /sell without a trailing slash. Netlify,
Cloudflare Pages and GitHub Pages serve /sell from sell.html automatically; Vercel does with cleanUrls
(below). 404.html is served for unknown URLs by most hosts.
- Netlify / Cloudflare Pages: build command
pnpm build, publish directorydist. - Vercel: import the repository; it detects Astro and static output needs no adapter. Add a
vercel.jsonwith{ "cleanUrls": true }so/sellservessell.html. - GitHub Pages: use the official Astro GitHub Action. For a
project site (
username.github.io/repo), build withDEMO_BASE_PATH=/repo. - Any static host: upload the contents of
dist/. If the host doesn't map/selltosell.html, changebuild.formatto"directory"inastro.config.mjs.
To serve the site from a sub-folder, set DEMO_BASE_PATH when building, for example DEMO_BASE_PATH=/atlas pnpm build. It becomes Astro's base, and every link and image written with url() includes it, including the links
main.ts builds. astro.config.mjs also sets compressHTML: false, so the spaces between words and inline tags
that start on a new line are kept.
Tailwind scans src/ for class names, including the strings in src/scripts/main.ts and src/lib/art.ts. Classes
built from strings in TypeScript must appear in full somewhere (for example "pill pill-sold"), not assembled from
pieces; the pill-${status} classes work because .pill-active, .pill-pending and .pill-sold are defined in
global.css.
demo/ (if present) is the build used for the freeframe.dev demo; you can delete it.
TypeScript 7 setup#
package.json installs two TypeScript packages on purpose. @typescript/native is TypeScript 7 and provides the
tsc that pnpm typecheck runs. typescript is an alias for @typescript/typescript6, the TypeScript 6 API that
astro check and typescript-eslint load. When those tools support TypeScript 7, replace both with a single
typescript@7. tsconfig.json extends Astro's strict preset.
Troubleshooting#
| Problem | Fix |
|---|---|
| A listing page says "That listing isn't on my desk" | The ?id= doesn't match any id in LISTINGS. Check the spelling, including links you added by hand. |
| A new listing doesn't appear | Its status must be active, pending or sold and its area an id from AREAS; run pnpm check for typing errors, and rebuild. |
| Links or images break when the site is in a sub-folder | Write internal links with url("/page") from src/lib/site.ts, never "/page", and build with DEMO_BASE_PATH. |
/sell shows a 404 on the host |
The host doesn't map extensionless URLs to .html: enable clean URLs (Vercel cleanUrls) or switch build.format to "directory". |
| Open houses show the wrong day or time | Weekdays are sun to sat, times are 24-hour Nashville time, and the agent's zone is AGENT.timeZone. |
| No booking days appear | Check AVAILABILITY.hours (day 0 is Sunday) and closed, and that minNoticeHours isn't larger than your windows. |
| A Tailwind class used in TypeScript has no effect | Write the full class name in the string; Tailwind can't see classes assembled from pieces. |
| The social image doesn't show when sharing | Set SITE_URL so og:image is an absolute URL to the PNG. |
License#
MIT. Free for personal and commercial use; see LICENSE.txt in the download (or the repository license). All
illustrations, the portrait, map and icons are part of the template and covered by the same license. The agent,
brokerage, homes, prices and clients are fictional.