Overview#
Atlas is a bold, magazine-style personal-brand website 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. It has filterable active and sold listings, listing pages with illustrated room galleries and open houses
you can add to a calendar, a three-step home value estimate, a seller net sheet, a buyer affordability calculator, an
interactive neighborhood map and an online consultation booking flow. Gloock headlines and a cobalt, persimmon and
butter palette give it an editorial look, and every house and room picture is an SVG drawn from data, so there are no
photos to license. This is the Next.js version: Next.js 16 (App Router, static export), React 19, TypeScript and
Tailwind CSS v4, with no other dependencies. This guide covers version 1.0.0 (see template.json and
CHANGELOG.md). Live demo: freeframe.dev/templates/atlas.
Quick start#
Requirements: Node.js 24 and pnpm.
pnpm install
pnpm devOpen http://localhost:3000. Pages reload as you edit. Other scripts:
| Script | What it does |
|---|---|
pnpm build |
Static export to out/ |
pnpm start |
Serve out/ locally |
pnpm lint |
ESLint |
pnpm typecheck |
Generate route types and run TypeScript |
Pages#
| Route | File | Purpose and notable features |
|---|---|---|
/ |
src/app/page.tsx |
Hero with portrait and "I'm buying" / "I'm selling" paths, instant estimate teaser, three featured listings, count-up stats, testimonial carousel, neighborhood teaser with map, booking call to action |
/listings |
src/app/listings/page.tsx |
Status tabs (for sale, under contract, sold, all), neighborhood, bedroom and price filters with live counts, sort, empty state, URL state, off-market call to action |
/listing |
src/app/listing/page.tsx |
One page for every home, chosen by ?id=: illustrated gallery, key facts, description, highlights, open houses with .ics downloads, tour request form, similar homes, not-found state |
/sell |
src/app/sell/page.tsx |
Three-step home value estimate (#valuation), seller net proceeds sheet (#net-sheet), four-week selling plan |
/buy |
src/app/buy/page.tsx |
Tour-notes sketch, six-step buyer guide, affordability calculator (#afford) with donut chart, FAQ accordion |
/neighborhoods |
src/app/neighborhoods/page.tsx |
Interactive SVG map with an info panel, six guide cards with statistics and links to filtered listings |
/about |
src/app/about/page.tsx |
Story and portrait, sold-history chart, timeline, three principles, credentials, personal facts |
/contact |
src/app/contact/page.tsx |
Consult types, day strip, time slots, details form, live summary, confirmation with .ics download |
| 404 | src/app/not-found.tsx |
Map with a bobbing pin and links back into the site (noindex) |
Project structure#
atlas/
src/
app/
layout.tsx <html>, fonts, default metadata, top bar, header, footer, reveals, back to top
fonts.ts Gloock and Albert Sans with next/font/local
globals.css Tailwind CSS v4: @theme tokens and component classes
icon.svg favicon (the DQ house monogram)
page.tsx home page
listings/page.tsx listings (filters read from the URL)
listing/page.tsx listing detail (?id=)
sell/, buy/, neighborhoods/, about/, contact/ the other routes
not-found.tsx 404 page
components/
TopBar.tsx "Open this weekend" strip above the header
SiteHeader.tsx sticky header, current page, mobile menu (client)
SiteFooter.tsx footer with contact details and links
Newsletter.tsx Monthly Market Memo signup (client)
Logo.tsx, Icons.tsx logo mark and line icons
Art.tsx <HouseSvg> and <RoomSvg> wrappers for the illustrations
MapArt.tsx the neighborhood map: shapes, base layers, static map
ListingCard.tsx a listing card (server-rendered)
ListingsBrowser.tsx tabs, filters, sort, results and URL state (client)
ListingDetail.tsx listing page and not-found state (client)
Gallery.tsx listing gallery (client)
TourForm.tsx tour request form (client)
EstimateTeaser.tsx home page instant estimate (client)
HomeEffects.tsx count-up numbers and the testimonial carousel (client)
Valuation.tsx three-step home value estimate (client)
NetSheet.tsx seller net sheet (client)
Affordability.tsx affordability calculator (client)
Faq.tsx buyer FAQ accordion (client)
AreaExplorer.tsx interactive map and info panel (client)
Booking.tsx consultation booking (client)
useValidation.tsx form validation hook
RevealObserver.tsx scroll reveals (client)
BackToTop.tsx back-to-top button (client)
Year.tsx current year in the footer
data/
agent.ts AGENT: name, phone, email, office, time zone, site URL
areas.ts AREAS: the six neighborhoods, their statistics and guide text
listings.ts LISTINGS, STATUS_LABEL, PRICE_STEPS, SORTS, GALLERY
valuation.ts VALUATION (home value factors) and AFFORD (28/36 rule)
booking.ts CONSULT_TYPES and AVAILABILITY
content.ts NAV, TESTIMONIALS, BUYER_STEPS, FAQ, SELL_STEPS, TIMELINE, SALES_BY_YEAR, CREDENTIALS
lib/
art.ts house and room drawings (SVG markup from a listing's colors)
estimate.ts estimate(): the home value range
listings.ts listings filters, sorting and URL parsing
time.ts Nashville time, next open house, .ics files
format.ts money, moneyShort, num, dates, sold percentage
metadata.ts pageMeta(): per-page title, description and Open Graph
asset.ts asset(): prefixes files in public/ with the base path
fonts/ Gloock and Albert Sans woff2 files, OFL.txt
public/img/
delia-portrait.svg illustrated portrait (480 x 600)
og-image.svg, og-image.png social sharing image (1200 x 630)
next.config.ts static export and base path
template.json gallery metadataCustomising#
Brand and colors#
All design tokens live in the @theme block at the top of src/app/globals.css. Each token becomes Tailwind
utilities (bg-cobalt-600, text-persimmon-700, border-sand-200 and so on).
| 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;
}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 classes such as .btn, .btn-primary, .card, .kicker, .display, .hl,
.swash, .input, .tab, .seg, .slot and .listing-card are in the @layer components block of the same file.
The illustrations use hex colors directly: each listing's art colors in src/data/listings.ts, the GLASS,
LAWN, TREE and TREE_DARK constants in src/lib/art.ts, the map in src/components/MapArt.tsx and the SVG files
in public/img/. If you change the palette, search for the old hex values (for example #2742cc and #e4552b).
Typography#
src/app/fonts.ts loads two fonts with next/font/local from src/fonts/: the exact woff2 files Google Fonts serves
to the HTML version, so text sets identically. Gloock (--font-display, one weight) is used for headlines,
prices and big numbers through .display and font-display; Albert Sans (--font-sans, variable, 400 to 800) is
the body face. Each family has a latin and a latin-ext file split by unicode range. Every face is registered under its
plain family name, which is why globals.css and the SVG text in the logo, map, chart and footer wordmark can name
"Gloock" and "Albert Sans" directly.
To swap a font, put its woff2 files in src/fonts/, update the src and family names in fonts.ts, and change
--font-display or --font-sans in globals.css. Gloock has no italic, so emphasis in headlines uses the .hl
color and the hand-drawn .swash underline instead.
Content#
Page copy is in each route's page.tsx. The data behind the interactive parts lives in src/data/:
| Export | File | What it holds |
|---|---|---|
AGENT |
agent.ts |
Name, site name, phone, email, office, time zone (America/Chicago) and site URL |
AREAS |
areas.ts |
The six neighborhoods: id, name, ppsf (price per square foot), median, dom (days on market), yoy (one-year change), and the guide card text |
LISTINGS |
listings.ts |
Every home, for sale, under contract or sold |
VALUATION, AFFORD |
valuation.ts |
Home value factors and the affordability rule |
CONSULT_TYPES, AVAILABILITY |
booking.ts |
Bookable consultations, weekly hours, notice, closed dates |
TESTIMONIALS, BUYER_STEPS, FAQ, SELL_STEPS, TIMELINE, SALES_BY_YEAR, CREDENTIALS |
content.ts |
Reviews, guides, the about page chart and timeline |
A listing looks like this (shortened):
{ 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 (TypeScript checks the shape):
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 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 one 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).
Listing cards, listing pages, the comparable sales in the estimate, the map counts and the guide buttons all read this array, so nothing else needs updating. The headline numbers (238 homes, 8 days, 101.6%, $171M) appear on the home, sell and about pages; keep them in step when you change them.
Images and artwork#
Everything is SVG:
- Houses.
HOUSESinsrc/lib/art.tsdraws a 400 x 300 (4:3) house from a listing'sartobject;<HouseSvg>insrc/components/Art.tsxwraps it in an<svg>. The styles arevictorian,farmhouse,bungalow,townhome,modern,ranch,foursquare,tudorandcondo; the colors aresky,body,roof,trimanddoor. The cards are rendered on the server, so the drawings are in the exported HTML. - Rooms. The listing gallery shows the house plus five rooms (
living,kitchen,bedroom,bath,yard) drawn byROOMSand tinted from the same colors. The order and captions are inGALLERYinsrc/data/listings.ts. - 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/MapArt.tsxholds the stylized map (not to scale):MAP_SHAPESlists each neighborhood's polygon, fill and label position,MapBasedraws the parks, roads, river and downtown.
To use real photos for listings, add a photo field to Listing and return an <img> (with asset() for files in
public/) instead of <HouseSvg> in ListingCard.tsx and of <RoomSvg> in Gallery.tsx. 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. Export the SVG to
PNG after editing it. The favicon is src/app/icon.svg.
Navigation, header and footer#
The desktop menu is NAV and the mobile menu MOBILE_NAV in src/data/content.ts; SiteHeader.tsx marks the
current route with aria-current="page" (a listing page counts as Listings). The footer's link lists are EXPLORE
and AREA_LINKS at the top of SiteFooter.tsx. The "Open this weekend" strip is TopBar.tsx: change its text and
/listing?id= link, or remove <TopBar /> from layout.tsx.
Features#
Listings filters#
ListingsBrowser.tsx runs the listings page; the filter logic is in src/lib/listings.ts.
- 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. Picking a minimum above the maximum clears the other one. - Live counts: each tab and each neighborhood option shows how many homes you would see if you picked it.
- 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 and unknown values (?status=banana,?min=7) are dropped (parseListingState()). - Empty state (
[data-empty]) appears when nothing matches, with a Clear filters button that keeps the status and sort and moves focus to the neighborhood filter.
The page reads the URL in the browser (inside a <Suspense> boundary), so the exported HTML shows the default view,
homes for sale, newest first. The result count is an aria-live region.
Listing page#
ListingDetail.tsx reads ?id=. Without an id it shows the first listing; with an unknown one it shows "That
listing isn't on my desk" with its own <h1>. The tab title becomes "address, neighborhood — Delia Quintero Real
Estate", set in the browser because a static export can't know the id at build time. If listings matter for search,
consider generating one static route per home instead (src/app/listing/[id]/page.tsx with generateStaticParams).
The gallery (Gallery.tsx) 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.
Open houses and calendar files#
nextOpenHouse() in src/lib/time.ts turns each [weekday, start, end] into its 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. Because this depends on the clock, open houses are filled in by the
browser after the page loads. "Add to calendar" builds an iCalendar file with buildIcs() (times in UTC, so every
calendar app places them correctly; 11:00 Central on a Saturday in October is DTSTART:...T160000Z) and downloads
it with downloadIcs().
Home value estimate#
The home page teaser (EstimateTeaser.tsx) and the three-step wizard on /sell (Valuation.tsx) share
estimate() in src/lib/estimate.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. For example, an updated 1,780 sq ft house in Sylvan Park comes out at $880,000 to
$970,000. The teaser links to /sell?area=...&sqft=...#valuation, which fills in the wizard. 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 sold homes in that area. "Use in the net sheet" copies the midpoint into
the net sheet and scrolls to it.
Seller net sheet#
NetSheet.tsx recalculates on every keystroke: sale price minus commission (percent), closing costs (percent),
mortgage payoff and repairs or credits (the defaults net $376,050). The colored bar shows each part as a share of the
price, and a warning appears if costs exceed the price. Money fields (data-money) get thousands separators when you
leave them.
Affordability calculator#
Affordability.tsx applies the 28/36 rule from AFFORD: housing costs up to 28% of gross monthly income, and housing
plus debts up to 36%. affordability() finds the highest price whose monthly cost (principal and interest, property
tax, insurance, mortgage insurance below 20% down, HOA dues) fits both limits, the component 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 that is
$601,000 at $3,843 a month, linking to homes under $600K. Change FRONT_END, BACK_END, PMI_RATE or PMI_BELOW to
use other assumptions.
Neighborhood map#
On /neighborhoods, AreaExplorer.tsx draws the map and the info panel. Hovering or focusing an area highlights its
guide card ([data-area-card]) and fills the panel; 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 hash such as #east-nashville) keeps it
highlighted. Counts of homes for sale come from LISTINGS; the statistics and guide text from AREAS.
To add a neighborhood, add it to AREAS (the guide card appears automatically) and add its shape to MAP_SHAPES.
Consultation booking#
Booking.tsx on /contact runs the booking form:
- Consult type: radio cards for
CONSULT_TYPES(buyer,seller,value,relocation).?type=sellerpreselects one, and&listing=<id>fills in the message. - Day: a strip of the next
AVAILABILITY.daysShowndays that have a free slot, skippingcloseddates and days withouthours. - Time: slots every
slotStepminutes inside the day's windows, long enough for the chosen consult and at leastminNoticeHoursfrom now, all in Nashville time. The summary adds the visitor's local time 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 day strip when no day is picked). The confirmation repeats the booking and offers an .ics download; "Book
another" starts again. 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. The days and times depend on the clock, so they are
filled in by the browser.
Carousel, stats, reveals and back to top#
- Testimonials are a native scroll-snap track with previous and next buttons and a "1 / 6" status
(
HomeEffects.tsx). Swipe, trackpad and keyboard scrolling work. - Count-up stats animate elements with
data-count(and optionaldata-decimals) once they scroll into view. - Scroll reveals (
RevealObserver.tsx) fade in.revealelements. Revealing an element also reveals everything above it, so jumping with the End key or an anchor never leaves hidden content. - FAQ buttons toggle their panel and
aria-expanded(Faq.tsx). - Back to top (
BackToTop.tsx) appears after a screen of scrolling; remove it fromlayout.tsxand delete the.back-to-topstyles inglobals.cssto drop it.
Animations, smooth scrolling and transitions are turned off for visitors who prefer reduced motion.
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 success messages say so.
Validation is the useValidation() hook in src/components/useValidation.tsx. A field is checked when it has
data-validate; data-message replaces the "empty" message and the wording is in fieldMessage():
data-validate |
Rule |
|---|---|
required |
Must be filled |
email |
A valid email address (required when the field has required) |
phone-optional |
Blank, or at least 7 digits |
address |
At least 5 characters |
sqft |
Between VALUATION.minSqft and VALUATION.maxSqft square feet |
future-date |
Today or later |
choice |
One radio of the group is chosen (on the group's first radio) |
Errors appear under the field (or the group), set aria-invalid, are linked with aria-describedby, focus the first
problem and clear as the visitor fixes them.
| Form | File | Validates |
|---|---|---|
| Newsletter | src/components/Newsletter.tsx |
|
| Tour request | src/components/TourForm.tsx |
Date (today or later), name, email, optional phone |
| Home value wizard | src/components/Valuation.tsx |
Address, neighborhood, square feet |
| Booking | src/components/Booking.tsx |
Consult type, day, time, name, email, optional phone |
Connecting to Formspree#
Create a form at Formspree, then in onSubmit() in Newsletter.tsx or TourForm.tsx (search for "Connect a real
endpoint here") send the data before showing the success state:
async function onSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
const form = e.currentTarget;
if (!validate(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) return alert("Sorry, that didn’t send. Please email hello@deliaquintero.example.");
setDone(true);
}Every field has a name, so FormData picks them all up; the tour form also sends the listing address in a hidden
listing field. Then remove the "Demo only" sentence from the success panel.
If you remove the static export (see Building and deploying), you can post to a Route
Handler instead (src/app/api/tour/route.ts with export async function POST(request: Request)) or call a Server
Action from onSubmit.
Connecting a booking tool#
The booking flow is a front end only. Either replace <Booking /> in src/app/contact/page.tsx with your Cal.com or
Calendly embed, or keep it and send the request in onSubmit() in Booking.tsx (the block marked "Connect a real
booking tool"): post type, slot.start.toISOString() and the details fields to your form service or server. Set
AVAILABILITY.demoBusyShare to 0 either way, and load real busy times from your calendar if you keep the flow.
SEO and sharing#
Each route exports metadata built with pageMeta(title, description) from src/lib/metadata.ts: the exact
<title>, the description, Open Graph tags with public/img/og-image.png, and a Twitter card. The listing page sets
its title in the browser. The 404 page adds robots: { index: false }. Social networks need absolute image URLs, so
set your domain when building; it becomes metadataBase in layout.tsx:
NEXT_PUBLIC_SITE_URL=https://www.your-domain.com pnpm buildConsider adding src/app/sitemap.ts and src/app/robots.ts, and RealEstateAgent structured data (schema.org
JSON-LD) with your real name, office and phone. Real estate sites usually must show license and brokerage details and
fair-housing wording: the footer has placeholders (license no. 000000, Larkspur & Main Realty); replace them with your
own and check your state's advertising rules.
Accessibility#
Built in:
- A skip link, landmarks (
header,nav,main,footer), one<h1>per page (the listing not-found state has its own) 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; Escape closes the menu and returns focus. - Status tabs and day buttons use
aria-pressed, bedroom and consult choices are 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 labelled (
role="img") or hidden from screen readers when decorative. Booked time slots are announced as "booked" rather than offered. - Motion is reduced for visitors who prefer it.
When you edit, keep one <h1> per page, give every new input a <label>, and keep the data-* attributes the
components use for validation and state.
Building and deploying#
pnpm build # static site in out/
pnpm start # preview out/next.config.ts sets output: "export", images.unoptimized and basePath from DEMO_BASE_PATH. To host the site in
a sub-folder, build with that folder (DEMO_BASE_PATH=/atlas pnpm build); links, scripts and asset() image URLs all
pick it up. Deploy out/ to any static host (Netlify, Cloudflare Pages, GitHub Pages, S3) or import the repository
into Vercel. Most hosts serve 404.html for missing pages automatically. demo/ is the freeframe.dev demo build and
can be deleted.
To use server features (Route Handlers, Server Actions, one static page per listing with fresh data), remove
output: "export" from next.config.ts and deploy to Vercel or any Node.js host with next build and next start.
@typescript/native provides TypeScript 7's tsc for pnpm typecheck; typescript is aliased to
@typescript/typescript6 because typescript-eslint needs the TypeScript 6 API. Replace both with typescript@7 once
typescript-eslint supports it.
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. |
| A new listing doesn't appear | Its status must be active, pending or sold and its area an id in AREAS; pnpm typecheck reports shape errors. Homes for sale only show on the default tab. |
| Open houses show the wrong day or time | Weekdays are sun to sat, times are 24-hour Nashville time, and the zone is AGENT.timeZone (an IANA name such as America/Chicago). |
| No booking days appear | Check AVAILABILITY.hours (day 0 is Sunday) and closed, and that minNoticeHours isn't larger than your windows. |
| A filtered listings link opens unfiltered | The values must be valid: an AREAS id for area, a PRICE_STEPS value for min and max, 1 to 4 for beds. |
| The social preview has no image | Build with NEXT_PUBLIC_SITE_URL set to your domain so og:image is an absolute URL. |
| Images or styles are missing under a sub-folder | Build with DEMO_BASE_PATH set to that folder and use asset() for files in public/. |
| Content stays invisible after editing | .reveal elements are hidden until RevealObserver reveals them; check the browser console for errors. |
License#
MIT, see the repository license (included as LICENSE.txt in the download). All illustrations (houses, rooms, the
portrait, map, icons and social image) are original to this template and covered by the same license. Gloock and
Albert Sans are under the SIL Open Font License 1.1 (src/fonts/OFL.txt). The agent, brokerage, homes, prices and
clients are fictional.