Freeframe

Documentation

Atlas

v1.0.0Updated Nov 12, 2026A bold, magazine-style personal brand site for a real estate agent, with listings, a home value estimate and calculators.

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 Nuxt version: Nuxt 4, Vue 3 single-file components, TypeScript and Tailwind CSS v4, generated as a static site, 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.

sh
pnpm install
pnpm dev

Open http://localhost:3000. Pages reload as you edit. Other scripts:

Script What it does
pnpm generate Static site in .output/public/
pnpm preview Serve the last build locally
pnpm build Node.js server build in .output/ (pages still prerendered)
pnpm lint ESLint
pnpm typecheck nuxt prepare and vue-tsc

Pages#

Route File Purpose and notable features
/ app/pages/index.vue 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 app/pages/listings.vue 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 app/pages/listing.vue 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 app/pages/sell.vue Three-step home value estimate (#valuation), seller net proceeds sheet (#net-sheet), four-week selling plan
/buy app/pages/buy.vue Tour-notes sketch, six-step buyer guide, affordability calculator (#afford) with donut chart, FAQ accordion
/neighborhoods app/pages/neighborhoods.vue Interactive SVG map with an info panel, six guide cards with statistics and links to filtered listings
/about app/pages/about.vue Story and portrait, sold-history chart, timeline, three principles, credentials, personal facts
/contact app/pages/contact.vue Consult types, day strip, time slots, details form, live summary, confirmation with .ics download
404 app/error.vue Map with a bobbing pin and links back into the site (noindex)

Project structure#

atlas/
  app/
    app.vue                    <NuxtLayout><NuxtPage>, keyed so a link to the page you're on starts it afresh
    error.vue                  404 page (generated as 404.html)
    router.options.ts          query strings written like the HTML version
    layouts/default.vue        skip link, header, page, footer, back to top
    pages/                     one file per page (index, listings, listing, sell, buy, neighborhoods, about, contact)
    components/
      SiteHeader.vue           "Open this weekend" strip, sticky header, current page, mobile menu
      SiteFooter.vue           footer with contact details, links and the Monthly Market Memo signup
      LogoMark.vue             the DQ house monogram
      HouseArt.vue             a listing's house or room illustration
      ListingCard.vue          a listing card
      ListingsBrowser.vue      tabs, filters, sort, results and URL state
      ListingGallery.vue       listing gallery
      TourForm.vue             tour request form
      EstimateTeaser.vue       home page instant estimate
      ReviewCarousel.vue       testimonial carousel
      ValuationWizard.vue      three-step home value estimate
      NetSheet.vue             seller net sheet
      AffordabilityCalculator.vue   affordability calculator
      FaqList.vue              buyer FAQ accordion
      BookingFlow.vue          consultation booking
      FieldError.vue           a field's inline error message
      BackToTop.vue            back-to-top button
    composables/
      useValidation.ts         form validation
      usePageSeo.ts            per-page title, description, canonical and Open Graph
      useCountUp.ts            count-up numbers on the home page
      useAsset.ts              files in public/ with the base path
      useMounted.ts, useYear.ts   browser-only values (clock, URL) without hydration mismatches
    data/
      agent.ts                 AGENT: name, phone, email, office, time zone
      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, MOBILE_NAV, TESTIMONIALS, FAQ
      map.ts                   MAP_SHAPES: the interactive map's neighborhood shapes
    utils/
      art.ts                   house and room drawings (SVG markup from a listing's colors)
      estimate.ts              estimate(): the home value range
      afford.ts                affordability(): the 28/36 rule
      listingState.ts          listings filters, sorting and URL parsing
      time.ts                  Nashville time, next open house, .ics files
      format.ts                money, moneyShort, num, dates, sold percentage
      ref.ts                   makeRef(): confirmation references
    plugins/
      reveal.client.ts         scroll reveals
      same-page.client.ts      links to the page you're on reload it, like the HTML version
      keep-hash.client.ts      keeps the #hash exactly as opened (the map reads it)
      stable-build.server.ts   reproducible builds
    assets/css/main.css        Tailwind CSS v4: fonts, @theme tokens and component classes
    assets/fonts/              Gloock and Albert Sans woff2 files, OFL.txt
  public/img/
    favicon.svg                the DQ house monogram
    delia-portrait.svg         illustrated portrait (480 x 600)
    og-image.svg, og-image.png social sharing image (1200 x 630)
  nuxt.config.ts               base path, head defaults, SITE_URL, prerendered routes, Tailwind
  template.json                gallery metadata

Customizing#

Brand and colors#

All design tokens live in the @theme block at the top of app/assets/css/main.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:

css
@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 gray 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 app/data/listings.ts, the GLASS, LAWN, TREE and TREE_DARK constants in app/utils/art.ts, the map in app/data/map.ts and the page templates and the SVG files in public/img/. If you change the palette, search for the old hex values (for example #2742cc and #e4552b).

Typography#

The fonts are self-hosted from app/assets/fonts/ with @font-face rules at the top of app/assets/css/main.css: 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. The faces use their plain family names, so 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 app/assets/fonts/, update the @font-face rules, and change --font-display or --font-sans in the @theme block. 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 page's template in app/pages/. The data behind the interactive parts lives in app/data/:

Export File What it holds
AGENT agent.ts Name, site name, phone, email, office and time zone (America/Chicago)
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
NAV, MOBILE_NAV, TESTIMONIALS, FAQ content.ts Menus, home page reviews and the buyer FAQ
MAP_SHAPES map.ts The interactive map's neighborhood shapes and labels

A listing looks like this (shortened):

ts
{ 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):

  • id must be unique; it is the ?id= of the listing page.
  • status is active, pending or sold. Sold homes also need sold: { price, date } (date as YYYY-MM-DD).
  • area must be an id from AREAS.
  • featured: true puts a home for sale on the home page (the first three are shown).
  • listed (and sold.date) drive the "Newest" sort.
  • openHouses is a list of [weekday, start, end] in Nashville time, with weekdays sun to sat and 24-hour times. Each one is shown on its next date, so they never go stale. Only homes for sale show open houses.
  • art.style picks 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. HOUSES in app/utils/art.ts draws a 400 x 300 (4:3) house from a listing's art object; <HouseArt> (app/components/HouseArt.vue) wraps it in an <svg>. The styles are victorian, farmhouse, bungalow, townhome, modern, ranch, foursquare, tudor and condo; the colors are sky, body, roof, trim and door. The cards are rendered when the site is generated, so the drawings are in the HTML.
  • Rooms. The listing gallery shows the house plus five rooms (living, kitchen, bedroom, bath, yard) drawn by ROOMS and tinted from the same colors (<HouseArt :kind="...">). The order and captions are in GALLERY in app/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 .arch class rounds the top into an arch.
  • Map. The stylized map (not to scale) is markup in app/pages/neighborhoods.vue, app/pages/index.vue and app/error.vue; the interactive areas on /neighborhoods come from MAP_SHAPES in app/data/map.ts (polygon, fill and label position).

To use real photos for listings, add a photo field to Listing and render an <img> (with useAsset() for files in public/) instead of <HouseArt> in ListingCard.vue and ListingGallery.vue. 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 public/img/favicon.svg.

The desktop menu is NAV and the mobile menu MOBILE_NAV in app/data/content.ts; SiteHeader.vue marks the current page 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.vue. The "Open this weekend" strip is at the top of SiteHeader.vue: change its text and /listing?id= link, or delete that <div>. To add a page, create app/pages/<name>.vue and add its route to routes in nuxt.config.ts.

Features#

Listings filters#

ListingsBrowser.vue runs the listings page; the filter logic is in app/utils/listingState.ts.

  • Status tabs are buttons with data-status (active, pending, sold, all) and aria-pressed.
  • Filters: neighborhood (area), minimum bedrooms (beds: 1 to 4), minimum and maximum price (min, max, from PRICE_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 component reads the URL once the page has loaded, so the generated HTML shows the default view, homes for sale, newest first. A link to /listings from a filtered listings page (the menu, the footer) opens it afresh, like a page load (app/plugins/same-page.client.ts). The result count is an aria-live region.

Listing page#

app/pages/listing.vue 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 site can't know the id at build time. If listings matter for search, consider one prerendered route per home instead (app/pages/listing/[id].vue, with each /listing/<id> added to routes in nuxt.config.ts).

The gallery (ListingGallery.vue) 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 app/utils/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.vue) and the three-step wizard on /sell (ValuationWizard.vue) share estimate() in app/utils/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,000

The 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.vue 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#

AffordabilityCalculator.vue applies the 28/36 rule from AFFORD: housing costs up to 28% of gross monthly income, and housing plus debts up to 36%. affordability() in app/utils/afford.ts 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, app/pages/neighborhoods.vue 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#

BookingFlow.vue on /contact runs the booking form:

  1. Consult type: radio cards for CONSULT_TYPES (buyer, seller, value, relocation). ?type=seller preselects one, and &listing=<id> fills in the message.
  2. Day: a strip of the next AVAILABILITY.daysShown days that have a free slot, skipping closed dates and days without hours.
  3. Time: slots every slotStep minutes inside the day's windows, long enough for the chosen consult and at least minNoticeHours from now, all in Nashville time. The summary adds the visitor's local time when it differs.
  4. 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, shows a short reference (DQ- and four characters, from makeRef() in app/utils/ref.ts) and offers an .ics download that carries the same reference; "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.

  • Testimonials are a native scroll-snap track with previous and next buttons and a "1 / 6" status (ReviewCarousel.vue). Swipe, trackpad and keyboard scrolling work.
  • Count-up stats animate elements with data-count (and optional data-decimals) once they scroll into view (useCountUp() in app/composables/useCountUp.ts).
  • Scroll reveals (app/plugins/reveal.client.ts) fade in .reveal elements. 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 (FaqList.vue).
  • Back to top (BackToTop.vue) appears after a screen of scrolling; remove it from app/layouts/default.vue and delete the .back-to-top styles in main.css to drop it.

Animations, smooth scrolling and transitions are turned off for visitors who prefer reduced motion.

Forms#

Every form validates in the browser and shows an in-world confirmation: the newsletter asks the subscriber to look out for a confirmation email, and the tour request and the booking show a short reference made in the browser (DQ- and four characters, from makeRef() in app/utils/ref.ts; if your form service or booking tool returns its own reference, show that instead). The tour confirmation promises an email "within a few hours" and the booking confirmation "within one business day", matching what the pages already say; change them to match how you actually reply. Nothing is sent or booked until you connect the forms.

Validation is the useValidation() composable in app/composables/useValidation.ts. 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 app/components/SiteFooter.vue Email
Tour request app/components/TourForm.vue Date (today or later), name, email, optional phone
Home value wizard app/components/ValuationWizard.vue Address, neighborhood, square feet
Booking app/components/BookingFlow.vue Consult type, day, time, name, email, optional phone

Connecting to Formspree#

Create a form at Formspree, then in subscribe() in SiteFooter.vue or submit() in TourForm.vue (search for "Connect a real endpoint here") send the data before showing the success state:

ts
async function submit() {
  if (!v.validate(form.value)) return;
  const res = await fetch("https://formspree.io/f/your-id", {
    method: "POST",
    body: new FormData(form.value!),
    headers: { Accept: "application/json" },
  });
  if (!res.ok) return alert("Sorry, that didn’t send. Please email hello@deliaquintero.example.");
  sent.value = 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.

With a server build (pnpm build, see Building and deploying) you can post to your own server route instead: server/api/tour.post.ts with export default defineEventHandler(async (event) => { const body = await readFormData(event); ... }), and fetch("/api/tour", ...) from the form.

Connecting a booking tool#

The booking flow is a front end only. Either replace <BookingFlow /> in app/pages/contact.vue with your Cal.com or Calendly embed, or keep it and send the request in submit() in BookingFlow.vue (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 page calls usePageSeo({ title, description }) (app/composables/usePageSeo.ts), which sets the exact <title> and description copied from the HTML version, a canonical link, Open Graph tags (og:image is public/img/og-image.png) and a Twitter card. The listing page changes its title in the browser; the 404 page passes noindex: true. Social networks need absolute URLs, so set your domain when building; it becomes runtimeConfig.public.siteUrl in nuxt.config.ts:

sh
SITE_URL=https://www.your-domain.com pnpm generate

Consider adding a sitemap.xml and robots.txt to public/, and RealEstateAgent structured data (schema.org JSON-LD, with useHead()) 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-expanded and aria-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 are aria-live regions.
  • The map areas are links with descriptive labels; illustrations are labeled (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#

sh
pnpm generate     # static site in .output/public/
pnpm preview      # serve it locally

nuxt generate prerenders every route listed in routes in nuxt.config.ts as <page>.html (listings.html, linked as /listings), plus 404.html from app/error.vue. Next to each page is a folder with its _payload.json, which Nuxt loads when you move between pages. Upload .output/public/ to any static host (Netlify, Cloudflare Pages, GitHub Pages, S3, Vercel). Hosts that don't map /listings to listings.html on their own need that rewrite (Vercel: "cleanUrls": true); most serve 404.html for unknown URLs.

To serve the site from a sub-folder, set DEMO_BASE_PATH, which becomes Nuxt's app.baseURL:

sh
DEMO_BASE_PATH=/atlas pnpm generate

pnpm build makes a Node.js server build instead (run it with node .output/server/index.mjs); the pages are still prerendered, and you can add server routes. demo/ is the freeframe.dev demo build and can be deleted.

pnpm typecheck runs vue-tsc against the configs Nuxt writes to .nuxt/ (tsconfig.json references them). typescript is aliased to @typescript/typescript6, the TypeScript 6 API that vue-tsc and typescript-eslint load; replace it with typescript@7 once they support 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 SITE_URL set to your domain so og:image is an absolute URL.
A new page shows the 404 page on the live site Add its route to routes in nuxt.config.ts so nuxt generate writes its HTML file.
"Hydration mismatch" warnings in the console after an edit Something reads the clock, the URL or window while rendering. Read it in onMounted() instead, as the listing and booking components do.
Content stays invisible after editing .reveal elements are hidden until app/plugins/reveal.client.ts reveals them; check the browser console for errors.

License#

Freeframe Commercial License: use it in unlimited websites and apps for yourself and your clients; it may not be resold or redistributed as a template. The full terms, with your licensee details, are in LICENSE.txt and at https://freeframe.dev/license#commercial. Third-party components keep their own licenses. Gloock and Albert Sans are under the SIL Open Font License 1.1 (app/assets/fonts/OFL.txt). The agent, brokerage, homes, prices and clients are fictional.

Search documentation

Search the guides and every template's documentation