Overview#
Compass is a website template for coaches and consultants. The demo content is for Bearing, the fictional
leadership coaching practice of Nadia Whitcombe in Pittsburgh, who works with new managers, founders and senior
leaders through 1:1 coaching, a group cohort and team workshops. The design is warm and editorial: a serif display
face with italic accents, ivory and linen backgrounds, a dusk-indigo brand colour, marigold highlights and cut-paper
SVG illustrations. This is the Astro version: Astro 7, TypeScript and Tailwind CSS v4, with no UI framework, built to
plain static files. It has the same pages, design, copy and interactions as the HTML version. This guide covers
version 1.0.0 (see template.json). Live demo:
freeframe.dev/templates/compass.
Quick start#
Requirements: Node.js 24 or newer and pnpm (npm works too).
pnpm install
pnpm dev # http://localhost:4321With npm: npm install, then npm run dev.
| Command | What it does |
|---|---|
pnpm dev |
Development server at http://localhost:4321 with hot reload |
pnpm build |
Static site in dist/ |
pnpm preview |
Serve dist/ locally after a build |
pnpm check |
astro check: type-checks .astro files and TypeScript |
pnpm typecheck |
Generate Astro's types, then type-check with tsc (TypeScript 7) |
pnpm lint |
ESLint (TypeScript and Astro rules) |
pnpm build:demo |
Build for /demos/astro/compass into demo/ (only used for the demo on freeframe.dev) |
Pages#
| Route | File | Purpose | Notable sections and features |
|---|---|---|---|
/ |
src/pages/index.astro |
Home | Hero with portrait and intro-call CTA, press wordmarks, "who it's for" cards, count-up outcomes, programs preview, the four method points, testimonial carousel, free guide sign-up, final CTA |
/programs |
src/pages/programs.astro |
Programs and pricing | Foundations and Executive 1:1 packages, strategy session, First 90 Days cohort, team workshops, comparison table, FAQ accordion, "Recommended by your self-check" badges |
/method |
src/pages/method.astro |
The Bearing Method | Interactive compass diagram with tabs, 60-minute session structure, six-month engagement timeline |
/results |
src/pages/results.astro |
Client results | Summary stats, eight client stories with before/after metrics, filters by client type, pull quote |
/about |
src/pages/about.astro |
About the coach | Story, studio illustration, career path, credentials, values, personal notes |
/resources |
src/pages/resources.astro |
Resources | Leadership self-check quiz, filterable library of articles, worksheets and episodes, podcast, guide sign-up |
/article |
src/pages/article.astro |
Sample article | Long-form layout with drop cap, illustration, pull quote, "try this week" box, author card, related links |
/book |
src/pages/book.astro |
Book a call | Four-step booking flow with a live summary sidebar |
/contact |
src/pages/contact.astro |
Contact | SVG map, studio address, contact cards, validated contact form, other ways to work together |
/404 |
src/pages/404.astro |
Not found | Wandering compass needle and links back into the site |
Project structure#
compass/
astro.config.mjs site URL, base path, static output (pages built as about.html)
package.json scripts and dependencies
pnpm-workspace.yaml marks the folder as a standalone pnpm project
tsconfig.json extends Astro's strict preset
eslint.config.mjs ESLint flat config (typescript-eslint + eslint-plugin-astro)
public/img/
favicon.svg browser tab icon
og-image.svg social image source (1200 x 630)
og-image.png social image used in og:image
nadia-portrait.svg arch-framed portrait illustration (480 x 600)
studio.svg studio interior illustration (800 x 560)
guide-cover.svg cover of the free guide (360 x 460)
src/
pages/*.astro one file per page (see Pages)
layouts/Layout.astro <html>, <head> metadata, skip link, header, footer and the script imports
components/
SiteHeader.astro sticky header, desktop and mobile menus
SiteFooter.astro footer columns, social links, studio details, wordmark and "Back to top" link
data/
nav.ts header, mobile menu and footer links
home.ts "who it's for" cards, outcome stats, method points, testimonials
programs.ts the four programs shown by the quiz result (name, price, link, summary)
program-details.ts package inclusions, cohort weeks and facts, team formats, workshop topics, FAQ
method.ts the four compass points, session steps, engagement timeline, "your part"
stories.ts client stories and the results filter chips
resources.ts the resource library and its filter chips
about.ts career path, credentials, values
quiz.ts self-check questions, result types and the storage key
booking.ts call types, availability rules and the call-type cards
lib/
site.ts site name, email, phone, theme colour and the url() helper
icons.ts line icons as SVG strings, and the logo mark
scripts/
main.ts all interactive behaviour (menu, reveals, carousel, tabs, filters, forms, quiz, booking)
back-to-top.ts the back-to-top button
styles/global.css Tailwind CSS v4: brand tokens and component classes
template.json gallery metadata for freeframe.dev
README.md, DOCS.md, CHANGELOG.mdCustomising#
Brand and colours#
All design tokens live in the @theme block at the top of src/styles/global.css. Each token becomes Tailwind
utilities (bg-dusk-800, text-marigold-700, border-linen-200 and so on).
| Token group | Used for |
|---|---|
--color-ivory, --color-linen-50 to --color-linen-300 |
Page background, soft section backgrounds, borders |
--color-stone-500 to --color-stone-800 |
Body copy and muted text |
--color-dusk-50 to --color-dusk-950 |
The brand indigo: buttons, links, dark sections, the footer |
--color-marigold-100 to --color-marigold-700 |
The warm accent: highlights, stars, accent buttons on dark backgrounds |
--color-clay-100 to --color-clay-700 |
Secondary accent for tags and illustrations |
To change the brand colour, replace the dusk scale; Astro rebuilds the CSS on the next pnpm dev change or
pnpm build:
@theme {
--color-dusk-700: #2f5d50;
--color-dusk-800: #244a40;
--color-dusk-900: #1a3730;
--color-dusk-950: #12261f;
}Keep the contrast rules noted in the comments: use marigold-700 (not 400/500) for marigold text on light
backgrounds, and stone-600 or darker for small text. Reusable pieces such as .btn, .btn-primary, .card,
.chip, .input, .eyebrow and .display 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 (public/img/*.svg), the logo mark in src/lib/icons.ts and the inline SVGs in the pages use hex
colours directly. If you change the palette, search for the old hex values (for example #2a3259 and #f0c464) and
replace them.
Typography#
Two Google Fonts are loaded by one <link> in src/layouts/Layout.astro:
- Source Serif 4 (300–600, roman and italic, optical sizes) for headings, numbers and quotes:
--font-serif, used through the.displayclass andfont-serif. - Instrument Sans (400–700) for body text and the interface:
--font-sans.
Each token has a system fallback stack, so the site still reads well if the fonts don't load. To swap a font, change
the Google Fonts URL in the layout and the matching token in @theme:
--font-serif: "Fraunces", "Iowan Old Style", Georgia, ui-serif, serif;Inline SVGs that contain text (press wordmarks, the method compass, the podcast cover, the map and the footer
wordmark) name the fonts in font-family attributes; quote family names that contain digits, as in
font-family="'Source Serif 4', Georgia, serif".
Content#
Page copy that appears once (headings, intros, the article) is written in the page's .astro file. Anything that
repeats is typed data in src/data/ and rendered with .map(), so adding an item is one entry.
Site details. The site name, email and phone number used in the header, footer, contact page and meta tags come
from site in src/lib/site.ts:
export const site = {
name: "Bearing Leadership Coaching",
themeColor: "#fbf8f3",
email: "hello@bearing.example",
phone: "(412) 555-0143",
phoneHref: "tel:+14125550143",
};Client stories. STORIES in src/data/stories.ts. Each story has filter tags, tag pills, the program, a title,
a paragraph, two or three before/after metrics and the client. To add one:
{
id: "maya",
tags: "founders",
labels: [{ tone: "tag-marigold", text: "Founder" }],
program: "Executive 1:1 · 6 months",
title: "Your headline",
text: "What changed, in two or three sentences.",
cols: "sm:grid-cols-2",
metrics: [
{ label: "Meetings per week", before: "30", after: "18", bars: [100, 60] },
{ label: "Team engagement score", before: "58", after: "81", bars: [72, 100], up: true },
],
person: { initials: "MS", tone: "bg-dusk-100 text-dusk-800", name: "Maya Singh", role: "CEO, Example Co" },
},id becomes the card's id="story-maya". bars are the two bar widths in percent (the larger value is 100); up
colours the "after" bar dusk for numbers that should rise, otherwise it is marigold. Valid tags are the chip values in
STORY_FILTERS: managers, founders, executives, teams and career; a story can have several, separated by
spaces ("executives teams"). Chip counts are computed from the data at build time and recounted by the script.
Resources. RESOURCES in src/data/resources.ts. Each entry has a kind ("Article", "Worksheet" or
"Podcast"), topics (the chip values in RESOURCE_FILTERS: delegation, feedback, confidence, strategy,
career), a header tone, an eyebrow, title, text and meta (reading time or format). The kind sets the icon and
the link in RESOURCE_KINDS: articles go to /article, worksheets to the guide sign-up (#guide) and episodes to
the podcast section (#podcast).
{ kind: "Article", topics: "feedback", tone: "bg-dusk-50 text-dusk-700", eyebrow: "Feedback", title: "Your title", text: "One-line summary.", meta: "6 min read" },Home page. AUDIENCES, STATS, METHOD_POINTS and TESTIMONIALS in src/data/home.ts. The first four
testimonials also supply the hero's initials avatars. Each stat's value is both the count-up target and the number
shown without JavaScript.
Programs. The programs page reads FOUNDATIONS_INCLUDES, EXECUTIVE_INCLUDES, COHORT_WEEKS,
COHORT_DETAILS, TEAM_FORMATS, WORKSHOP_TOPICS and FAQ from src/data/program-details.ts; prices, the
comparison table and the strategy-session panel are written in src/pages/programs.astro. PROGRAMS in
src/data/programs.ts holds the name, price, link and summary that the quiz result shows. Prices also appear in the
home page preview; keep all of them in step when you change a price.
Cohort dates. First 90 Days cohorts start on the second Wednesday of January, April, July and October.
COHORT_DATES in src/data/programs.ts is null in the demo, which shows rolling dates: the first start at least
COHORT_MIN_WEEKS (6) weeks away and the one after it, so the demo never advertises a cohort that has started. Set
{ next: "2027-01-13", after: "2027-04-14" } for your real dates. The script (src/lib/cohort.ts) fills in every
element with data-cohort="next" or "after" in the format of data-cohort-format: full ("Wednesday, January
13, 2027"), long ("January 13, 2027"), month ("April 2027") or season ("Winter 2027"). The dates written in
the pages are shown without JavaScript; keep them in step once you set real dates.
Method and about. COMPASS_POINTS, SESSION_STEPS, TIMELINE and BRING in src/data/method.ts; PATH,
CREDENTIALS and VALUES in src/data/about.ts.
Quiz and booking. See Leadership self-check and Booking flow.
Images and artwork#
All artwork is SVG, so there are no photos to license. Files in public/img/ are copied to the build as they are and
referenced with url("/img/..."). To use real photos instead:
- Portrait (
nadia-portrait.svg, used on home, about, article and book pages): replace with a 4:5 photo. To keep the arch shape, addclass="arch object-cover"to the<img>;.archrounds the top into a full arch. - Studio (
studio.svgon about): any 10:7 landscape image. - Guide cover (
guide-cover.svg, home and resources): a 360 x 460 (roughly 4:5) cover of your own lead magnet. - The method compass, press wordmarks, map, podcast cover, article illustration and 404 compass are inline SVG in
their pages. Replace the press wordmarks in
index.astrowith your own logos, and only list publications that have really featured you.
The social image is public/img/og-image.png (1200 x 630), rendered from og-image.svg. Edit the SVG, then export a
new PNG with any vector editor or a headless browser. The favicon is public/img/favicon.svg. Icons are SVG strings
in src/lib/icons.ts, rendered with <Fragment set:html={icon("arrow", "size-5")} />; add a path to paths to add
an icon.
Navigation, header and footer#
The header and footer are components, so there is one place to change them:
- Links:
src/data/nav.ts.mainNavis the desktop menu,mobileNavthe mobile menu (Home, the main links and "Book a free intro call"), andfooterNavthe footer's Coaching and Practice columns. Write paths without.html("/programs#faq"); the components pass them throughurl(). - Current page: each page passes
currentto<Layout>(current="programs"), which setsaria-current="page"on the matching links; the underline and mobile highlight come from thearia-[current=page]styles on.nav-linkand.mobile-link. The article passescurrent="resources". The "Book a free call" button in the header is shown from thesmbreakpoint up and is marked current on the booking page. The desktop menu appears from thelgbreakpoint (1024 px); below that the menu button shows the mobile menu. - Footer: social links, the studio address and the large "Bearing" wordmark (an
aria-hiddeninline SVG) are inSiteFooter.astro.
To add a page, create src/pages/<name>.astro with <Layout title=... description=... current="<name>">, put the
content in <main id="main" tabindex="-1">, and add it to nav.ts.
Features#
All behaviour is in src/scripts/main.ts (and back-to-top.ts), imported once from the layout. Each feature looks
for its markup and does nothing on pages without it.
Booking flow#
/book contains a four-step flow inside the [data-booking] element, driven by initBooking() in main.ts:
-
Call type: radio cards named
call-type, rendered fromCALL_TYPE_CARDSwith the length and price fromCALL_TYPES(both insrc/data/booking.ts). Link to/book?type=strategy(orteam) to preselect a type. -
Date and time: a month calendar (Monday first) and a list of time slots. Slots are generated from
AVAILABILITY:tsexport const AVAILABILITY = { timeZone: 'America/New_York', zoneName: 'Eastern Time', hours: { 1: [['09:00', '12:00'], ['13:30', '17:00']], /* ... */ 5: [['09:00', '12:30']] }, slotStep: 30, minNoticeHours: 24, daysAhead: 45, closed: ['2026-11-26', '2026-12-25' /* ... */], demoBusyShare: 0.35, };hoursis keyed by weekday (0 is Sunday) in studio time; a slot fits when its whole length (minutesof the call type) ends before the window closes.demoBusySharehides a stable share of slots so the demo looks realistic; set it to0once you connect real availability. Times are shown in the visitor's time zone (detected withIntl), with the studio time underneath and a "Show times in" selector to switch to studio time. Slots that cross midnight are labelled(+1 day). -
Details: a validated form (
[data-booking-form]) with name, email, role, optional company, a message of at least 20 characters and a consent checkbox. -
Confirm: a summary of the request, a clear "this is a demo" notice, a Save a calendar hold (.ics) button that downloads an event file built in the browser, and Book another call.
The sidebar ([data-booking-aside]) updates as choices are made. To add a call type, add a key to CallTypeKey,
CALL_TYPES and CALL_TYPE_CARDS.
Accessibility: each step has a focusable heading that receives focus when the step changes; the step list marks the
current step with aria-current="step". Calendar days are buttons with full-date aria-labels that include the
number of open times; only one day is in the tab order, and the arrow keys move by day and week, Home/End go to the
start/end of the week and Page Up/Page Down move four weeks. Unavailable days use aria-disabled="true". Time slots
are native radio buttons inside a fieldset, and a missing date or time shows an inline role="alert" message.
Leadership self-check#
The quiz on /resources (#quiz, element [data-quiz]) has three views: intro, questions and result. Questions
are rendered from QUIZ_QUESTIONS in src/data/quiz.ts as a fieldset of radio buttons, one question at a time with
a progress bar and Back/Next buttons. Each answer is tagged with a result type; the type with the most answers wins,
and ties go to the type listed first in QUIZ_RESULTS. The result shows the type, three steps to try, a bar for each
type and the recommended program from PROGRAMS.
To change the quiz, edit the arrays: every answer is [text, resultType], and each result's program must be a key
of PROGRAMS (TypeScript checks both). Link to /resources#quiz from anywhere to send people to it. The legend of
each new question receives focus so screen reader users hear it.
Recommended program badge#
On /programs, elements with data-recommended (hidden by default) are shown inside the element whose
data-program matches the saved quiz result: foundations, executive, cohort or teams.
Method compass#
On /method, the four tabs ([data-compass-tabs], standard role="tablist" with arrow-key, Home and End support)
show one panel each; both are rendered from COMPASS_POINTS. The compass SVG ([data-compass]) turns its .needle
group to the selected point by the shortest route and highlights the matching [data-point] circle; clicking a point
in the drawing selects its tab. The drawing is decorative for assistive technology, which uses the tabs.
Filters#
Any element with data-filter-root gets a filter: chip buttons with data-filter (with all meaning everything),
cards with data-tags, an optional [data-filter-count] live region and an optional [data-filter-empty] message.
The selected filter is written to the URL (?type= on results, ?topic= on resources, set with
data-filter-param), so filtered views can be shared and linked, for example /results?type=founders. Chips use
aria-pressed. To add a filter value, add a chip to STORY_FILTERS or RESOURCE_FILTERS and use the value in some
items' tags.
Testimonial carousel#
The home page carousel ([data-carousel]) is a horizontal scroll-snap list, so swiping, trackpads and the arrow
keys (once the list is focused) all work. The Previous/Next buttons scroll by one card and disable at the ends; the
"1 / 6" counter is visual only. There is no autoplay. Slides and their "n of 6" labels come from TESTIMONIALS; the
counter's starting text is in index.astro.
FAQ accordion#
Each question on /programs is a .acc-btn button with aria-expanded and aria-controls pointing to its panel,
rendered from FAQ. The first item starts open; change the i === 0 checks in programs.astro to open another.
Answers are HTML strings, so they can contain links (build the link with url(), as the last answer does).
Count-up stats, scroll reveals and motion#
Numbers with data-count (and optional data-decimals) count up once when they scroll into view; the final value
is already in the HTML. Elements with .reveal fade in on scroll (add .reveal-delay-1 to -3 to stagger), and
everything above a revealed element is revealed too. The 404 needle wanders with a CSS animation. All motion is
switched off when the visitor prefers reduced motion.
Header, mobile menu and back to top#
The header gains a border and shadow after scrolling. Below 1024 px the menu button toggles #mobile-menu, updates
aria-expanded and its label, and closes on Escape or when a link is chosen. A back-to-top button appears after
about one screen of scrolling and moves focus to the main content; to remove it, delete
src/scripts/back-to-top.ts, its import in Layout.astro and the .back-to-top styles in global.css.
Forms#
There are four forms. None of them sends anything until you connect it, and each success state says so.
| Form | Where | Validates |
|---|---|---|
Guide sign-up ([data-guide-form]) |
/, /resources |
First name required, email format |
Contact ([data-contact-form]) |
/contact |
Name, email, optional phone (7+ digits if filled), topic, message of 20+ characters |
Booking details ([data-booking-form]) |
/book |
Name, email, role, message of 20+ characters, consent checkbox |
Quiz ([data-quiz-form]) |
/resources |
An answer is chosen before moving on |
Validation is driven by data-validate attributes (required, email, phone-optional, message, consent) and
the messages in fieldMessage() in main.ts. Errors appear under the field, are linked with aria-describedby,
set aria-invalid="true" and clear as the visitor fixes them; the first invalid field receives focus.
Connecting to Formspree#
Create a form at Formspree and copy its endpoint. Then, in main.ts, find the comment Connect a real endpoint here
in the guide/contact form handler and send the data before showing the success panel:
form.addEventListener('submit', async (e) => {
e.preventDefault();
if (!validateForm(form)) return;
try {
const res = await fetch('https://formspree.io/f/your-id', {
method: 'POST',
body: new FormData(form),
headers: { Accept: 'application/json' },
});
if (!res.ok) throw new Error('Send failed');
} catch {
alert('Sorry, something went wrong. Please email hello@bearing.example.');
return;
}
form.hidden = true;
if (success) { success.classList.remove('hidden'); success.focus(); }
});Then update the success text in the page (remove the "This is a demo" notice). For the guide sign-up you can instead post to your email tool (Kit, Mailchimp, Buttondown) using the form action it gives you.
The site is static, so there is no server code. If you need one (to email a request or save it to a CRM), add an Astro adapter and an API endpoint, or use your host's functions (Netlify Functions, Vercel Functions, Cloudflare Workers) and post to that URL instead of Formspree.
Booking flow#
You have three options:
- Replace it with an embed. Remove the
[data-booking]section inbook.astroand paste the inline embed code from Cal.com or Calendly. Keep the page header; the booking code inmain.tsonly runs when[data-booking]exists. - Keep the flow and send a request. In the
[data-booking-form]submit handler ininitBooking(), post the details plus the chosen slot (state.type,state.slot.at.toISOString()) to a form service before callinggoTo(4), then confirm each request by hand. Change the step 4 notice to say a confirmation will follow. - Use a scheduling API. Load real free slots from the Cal.com or Calendly API on your own server and replace
slotsFor()with them; never put API keys in client-side code.
Saved data (localStorage)#
| Key | Shape | Notes |
|---|---|---|
compass-quiz-v1 |
{ "answers": [0, 2, 1, 0, 3, 2], "date": "2026-10-12" } |
One answer index per question |
readQuiz() in main.ts ignores the value unless answers is an array with exactly one whole-number index per
question, each in range. The result type is always recalculated from the answers, so editing QUIZ_RESULTS never
leaves a stale result. Retake the self-check removes the key. To clear it manually, run
localStorage.removeItem('compass-quiz-v1') in the browser console. If storage is blocked, the quiz still works; it
just isn't remembered. If you change the questions, bump QUIZ_KEY in src/data/quiz.ts (for example to
compass-quiz-v2).
SEO and sharing#
Each page passes title and description to <Layout>; the layout writes the <title>, meta description,
canonical URL, og:* tags and twitter:card, plus theme-color and the SVG favicon. Canonical and og:url use the
clean URL (/programs, not /programs.html), and og:image is the absolute URL of /img/og-image.png. Both are
built from site in astro.config.mjs, which reads SITE_URL:
SITE_URL=https://www.your-domain.com pnpm buildor change the fallback (https://compass.example.com) in astro.config.mjs. The 404 page passes noindex, which
outputs <meta name="robots" content="noindex"> and no canonical.
For a sitemap, add the official @astrojs/sitemap integration, and add a public/robots.txt. Consider
ProfessionalService or Person structured data (JSON-LD) with your name, address and services; add it as a
<script type="application/ld+json"> in the layout's <head>.
Accessibility#
Built in:
- Skip link,
header/nav/main/footerlandmarks, one<h1>per page and a logical heading order. - Visible focus rings (
:focus-visibleinglobal.css, marigold on dark sections), including on radio cards, quiz answers and time slots. - Mobile menu with
aria-expanded,aria-controlsand Escape to close. - Filter chips with
aria-pressedand a live result count; accessible tabs and accordion; a keyboard-operable calendar; a carousel that never moves on its own. - Labelled form fields, inline errors linked with
aria-describedby, and focus moved to the first error. alttext oraria-hiddenon every image and SVG.- Reduced motion turns off reveals, count-ups, the needle animations and smooth scrolling.
- Text colours meet WCAG AA contrast.
When editing, keep one <h1> per page, give every new input a <label>, keep the aria-controls ids of tabs and
accordion buttons in step with their panels, keep alt text meaningful when you swap illustrations for photos, and
check new colour pairs for contrast (small text on ivory should be stone-600 or darker).
Building and deploying#
pnpm build # static site in dist/
pnpm preview # preview dist/ locallydist/ is plain HTML, CSS and JavaScript. Pages are written as programs.html rather than
programs/index.html (build.format: "file" in astro.config.mjs), and links point to /programs without a
trailing slash. Netlify, Cloudflare Pages and GitHub Pages serve /programs from programs.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/programsservesprograms.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/programstoprograms.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=/compass pnpm build. It becomes Astro's base, and every link and image written with url()
includes it (including the program links in the quiz result). astro.config.mjs also sets compressHTML: false, so
the spaces between words and inline tags that start on a new line are kept.
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 link or image 404s after deploying to a sub-folder | Write it with url("/path") rather than a hard-coded "/path", and build with DEMO_BASE_PATH set to the folder. |
/programs 404s but /programs.html works |
Your host doesn't map clean URLs. On Vercel add { "cleanUrls": true }; elsewhere set build.format: "directory". |
| No time slots on any day | Check AVAILABILITY.hours in src/data/booking.ts uses weekday numbers 0 to 6 and "HH:MM" strings, the call length fits inside each window, and daysAhead is larger than minNoticeHours / 24. |
| Times look an hour off around daylight saving changes | Make sure AVAILABILITY.timeZone is an IANA name such as America/New_York, not a fixed offset. |
| The quiz always shows an old result | It is saved in localStorage; click Retake the self-check or remove compass-quiz-v1, and bump QUIZ_KEY when you change the questions. |
| A filter chip shows 0 or the wrong cards | The chip's value must appear exactly (lowercase, no spaces) in the items' tags or topics. |
| SVG text renders in the wrong font | Quote font names that contain digits in font-family attributes, for example 'Source Serif 4'. |
| Content stays invisible after editing | Elements with .reveal are hidden until main.ts runs and they scroll into view; check the browser console for errors. |
License#
MIT. Free for personal and commercial use, including client projects. The license text is in LICENSE.txt in the
download. All artwork (SVG illustrations, icons, favicon and social image) is included under the same license.
Bearing, Nadia Whitcombe and every client, company and publication named in the template are fictional.