Overview#
Gather is a website for a local church, built for Riverbend Church, a fictional non-denominational
church in Lawrence, Kansas. It has a home page with this Sunday's times and a countdown to the next
service, a plan-your-visit page, a sermon archive and sermon pages with an audio player, an events
calendar with calendar downloads, a small-group finder and ministries, a giving page, an about page with
the staff team, contact and prayer requests, and a 404 page. This is the Next.js version: Next.js 16 (App
Router, static export), React 19, TypeScript and Tailwind CSS v4. Sermons, events, groups, giving and
staff live in typed data files in src/data/. This guide covers version 1.0.0. See it live at
freeframe.dev/templates/gather.
Quick start#
Requires Node.js 24 and pnpm.
pnpm install
pnpm dev # http://localhost:3000pnpm build writes the static site to out/, and pnpm start serves it. pnpm lint and
pnpm typecheck check the code.
Pages#
| Route | File | Purpose and notable features |
|---|---|---|
/ |
src/app/page.tsx |
Hero with river illustration, "This Sunday" card with times and the next-service countdown, three-step "I'm new" path, latest sermon with player, next four events (with the event dialog), ministries, scripture and figures band, newsletter sign-up |
/visit |
src/app/visit/page.tsx |
What to expect, service times, kids' check-in steps and rooms, accessibility, SVG parking map, FAQ accordion, plan-your-visit form with live summary |
/sermons |
src/app/sermons/page.tsx |
"Watch live" panel with countdown and the latest message, series cards, search and series/speaker/book filters with URL state, sermon cards |
/sermon |
src/app/sermon/page.tsx |
One sermon (/sermon?id=room-at-the-table): series cover, audio player, key verse, notes, discussion questions with copy button, speaker card, the rest of the series, related sermons |
/events |
src/app/events/page.tsx |
Category filter, month calendar and list views, selected-day agenda, event dialog with "Add to calendar" (.ics) |
/groups |
src/app/groups/page.tsx |
Small-group finder (day, area, season of life, childcare), ministries for kids, youth, care and music (#kids, #youth, #care, #music) |
/give |
src/app/give/page.tsx |
Frequency, amount chips and custom amount, fund, cover-the-fee option, live total, budget bar, funds, FAQ |
/about |
src/app/about/page.tsx |
Story timeline, beliefs (#beliefs), leadership, staff grid with SVG portraits |
/contact |
src/app/contact/page.tsx |
Office details, urgent pastoral contact, contact form, prayer request form (#prayer) |
| 404 | src/app/not-found.tsx |
Signpost illustration and links back (exported as 404.html) |
Project structure#
gather/
src/app/
layout.tsx <html>, skip link, header, <main>, footer, scroll reveal, back to top, default metadata
fonts.ts Nunito Sans and Urbanist with next/font/local
globals.css Tailwind entry: brand tokens (@theme) and component classes
icon.svg favicon (the Riverbend logo)
page.tsx … one folder per route (see Pages), not-found.tsx for the 404 page
src/components/
SiteHeader.tsx top bar, sticky header, main and mobile menus
ServiceStatus.tsx "Next gathering … / Live now" line in the top bar
Countdown.tsx countdown to the next service / "Live now" card
ThisSunday.tsx the date of this Sunday
AudioPlayer.tsx the sermon player
SermonArchive.tsx series cards, filters and sermon list (/sermons)
SermonDetail.tsx everything on /sermon
SermonCard.tsx a sermon in a grid
LatestSermon.tsx, LatestPlayer.tsx, HomeEvents.tsx home and sermons page pieces
EventsCalendar.tsx month grid, day agenda and list view (/events)
EventCard.tsx an event date as a card button
EventDialog.tsx the event detail dialog and .ics download
GroupFinder.tsx group filters and cards (/groups)
forms/ validation.tsx (shared), NewsletterForm, VisitForm, GiveForm, ContactForm, PrayerForm
art/Avatar.tsx SVG staff portraits
art/SeriesArt.tsx SVG series covers
WithUrl.tsx reads the URL for widgets after the page loads
Icons.tsx, Logo.tsx, SiteFooter.tsx, Year.tsx, Reveal.tsx, BackToTop.tsx
src/data/
church.ts CHURCH, SERVICES, KEEP_DEMO_CURRENT, DEMO_ANCHOR
people.ts PEOPLE, STAFF
sermons.ts SERIES, BOOKS, SERMONS, PROGRESS_KEY
events.ts EVENT_CATS, EVENTS
groups.ts DAYS, AREAS, STAGES, GROUPS
giving.ts GIVING
visit.ts VISIT
site.ts NAV, MOBILE_NAV
src/lib/
dates.ts day numbers, formatting and the church's time zone
demo.ts calendar(): shifted sermon and event dates, every event occurrence
services.ts serviceState(): the next or current service
ics.ts buildIcs() and downloadIcs()
progress.ts saved listening positions
url.ts, storage.ts, mounted.ts, tick.ts, metadata.ts, asset.ts
src/fonts/ the woff2 files and their licences
public/img/ og-image.png (social image) and og-image.svg (its source)
next.config.ts, eslint.config.mjs, tsconfig.json, postcss.config.mjs, package.jsonCustomising#
Brand and colours#
The design tokens are in the @theme block of src/app/globals.css. Every utility such as bg-plum or
text-muted comes from them.
| Token | Default | Used for |
|---|---|---|
--color-paper |
#fbf6ee |
Page background |
--color-shell |
#fffdf9 |
Cards and form fields |
--color-sand |
#f4eadb |
Soft panels, filter bars |
--color-line |
#e5d8c5 |
Hairlines and card rings |
--color-ink |
#2b1b36 |
Text, the footer, dark bands and the audio player |
--color-muted |
#5f5468 |
Secondary text |
--color-plum / --color-plum-dark |
#5b2a6e / #431d52 |
Brand colour, primary buttons, links |
--color-lilac |
#efe5f5 |
Selected states, worship events |
--color-apricot / -light / -dark |
#f6a96b / #fde3cc / #9a4716 |
Warm accent (backgrounds), tint, accent text |
--color-sage / -light / -dark |
#c9dcc0 / #e6efe0 / #33603b |
Hills, care ministry, serve events, success |
--color-river / -light / -dark |
#bcd9e8 / #e3f0f6 / #1f5577 |
River, kids' ministry and events |
--color-rose |
#b3364a |
Errors, "Live now", classes events |
To change the brand colour, edit the tokens; the dev server picks the change up at once:
@theme {
--color-plum: #1f5c7a;
--color-plum-dark: #164459;
}Use apricot only as a background: for text on light backgrounds use apricot-dark, which passes WCAG
AA. The SVG artwork (hero, map, portraits, series covers, logo) uses literal hex values; search src/ for
#5b2a6e and #f6a96b to recolour it. Also change themeColor in src/app/layout.tsx if you change
the page background.
Component classes in globals.css: .btn-primary, .btn-warm, .btn-outline, .btn-light, .btn-sm,
.card, .card-hover, .eyebrow, .h-display, .h-section, .tag, .chip-dot, .link, .field,
.field-label, .field-error, .option and .pill (radio and checkbox cards that highlight when
checked), .check, .faq (the accordion) and .scrub (the player's slider).
Typography#
Urbanist (headings, font-display) and Nunito Sans (body text, font-sans) are self-hosted
with next/font/local in src/app/fonts.ts, from the variable Latin woff2 files in src/fonts/ (the
same files Google Fonts serves, SIL Open Font License). They keep their real family names, so
globals.css and the SVG text (fontFamily="Urbanist, sans-serif") use them directly:
--font-sans: "Nunito Sans", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
--font-display: "Urbanist", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;To swap a font, put its woff2 files in src/fonts/, point the src and the font-family declaration in
fonts.ts at them, and change the names in @theme. You can also use next/font/google instead.
Content#
Page copy is in the page files in src/app/. Everything that is listed, filtered or calculated comes from
src/data/:
| Export | File | What it holds |
|---|---|---|
CHURCH |
church.ts |
Name, timeZone (IANA name, e.g. America/Chicago), timeZoneLabel, full address for calendar files, liveUrl, uidDomain |
SERVICES |
church.ts |
The weekly schedule: day (0 = Sunday), start ("09:00"), minutes, name |
KEEP_DEMO_CURRENT, DEMO_ANCHOR |
church.ts |
Moves demo dates forward (see Demo dates) |
PEOPLE, STAFF |
people.ts |
Staff and speakers with role, email, bio and avatar; STAFF is the order on the about page |
SERIES, BOOKS, SERMONS |
sermons.ts |
Sermon series (title, subtitle, art: table, sun, wheat or wave, blurb), book labels, every sermon |
EVENT_CATS, EVENTS |
events.ts |
Event categories with their colour classes, and events |
DAYS, AREAS, STAGES, GROUPS |
groups.ts |
Small-group filter labels and groups |
GIVING |
giving.ts |
Amount chips, default amount, min and max, card fee, funds |
VISIT |
visit.ts |
Number of Sundays offered, maximum party size, kids' rooms by age |
NAV, MOBILE_NAV |
site.ts |
Menu items |
Add a sermon to SERMONS (order doesn't matter; lists sort by date). TypeScript checks the keys:
{
id: "a-new-sermon", title: "A New Sermon", series: "open-table", part: 6, speaker: "dana",
date: "2026-10-18", book: "luke", passage: "Luke 10:38–42", duration: 2100, audio: "",
summary: "One or two sentences for cards and the page description.",
verse: { text: "Quoted text.", ref: "Luke 10:41 (KJV)" },
notes: [["Heading", "One short paragraph."], ["Heading", "Paragraph."]],
questions: ["First question?", "Second question?"],
},speaker is a PersonId (a key of PEOPLE), series a SeriesId, book a key of BOOKS, and
duration is in seconds. A new person or series also needs its id added to the PersonId or SeriesId
type. The demo quotes the King James Version, which is in the public domain; check the licence of any
other translation before quoting it.
Add an event to EVENTS. Times are 24-hour, in the church's time zone; add weekly to repeat it every
week until that date:
{ id: "choir-night", title: "Choir Night", cat: "worship", date: "2026-11-05", weekly: "2026-12-17",
start: "19:00", end: "20:30", place: "Main auditorium", summary: "Open rehearsal for anyone who sings." },Add a group to GROUPS; day, area and stage must be keys of DAYS, AREAS and STAGES, and
spots: 0 shows "Full: join the waitlist":
{ id: "lunch-break", name: "Lunch Break", day: "wed", time: "12:15", area: "downtown", stage: "everyone",
host: "Ana Ruiz", where: "Riverbend, Room 210", spots: 6, childcare: false, blurb: "A 45-minute study over lunch." },Demo dates#
The demo is written for the week of DEMO_ANCHOR ("2026-10-11"). While KEEP_DEMO_CURRENT is true,
every sermon and event date moves forward by whole weeks so that the latest sermon is always last Sunday
and events are always upcoming. Weekdays never change. Set KEEP_DEMO_CURRENT = false in
src/data/church.ts once you enter real dates, or your dates will be shifted too.
The shift is worked out by calendar() in src/lib/demo.ts, in the browser, from today's date in the
church's time zone. Anything that depends on the clock (dates, the countdown, "Live now", the calendar,
sermon lists) is rendered after the page loads, so the static HTML never holds a stale date and React
never sees a hydration mismatch. The useMounted() hook in src/lib/mounted.ts is how components wait
for that.
Images and artwork#
There are no photos. All artwork is SVG:
| Artwork | Where |
|---|---|
| Hero landscape (church, river, hills) | Inline in src/app/page.tsx, 600×540 |
| Welcome tent, small-group table, 404 signpost | Inline in the visit, groups and not-found pages |
| Parking map | Inline in src/app/visit/page.tsx, 640×420, with a <title> and <desc> |
| Staff portraits | <Avatar id="dana" /> (src/components/art/Avatar.tsx), drawn from each person's avatar settings |
| Series covers | <SeriesArt series="ruth" v={2} /> (src/components/art/SeriesArt.tsx), 320×200, varied by v (the sermon's part) |
| Logo | src/components/Logo.tsx, and src/app/icon.svg (favicon) |
A portrait's avatar object takes skin, hair, style (short, long, bun, curly, coily,
waves or bald), bg, shirt, and optional glasses and beard. <Avatar> is decorative unless
you pass a label, which becomes its alt text.
To use photos instead, replace <Avatar> or <SeriesArt> with <img> tags (files in public/, URLs
through asset() from src/lib/asset.ts): portraits are square (1:1), series covers 16:10, and the hero
frame about 10:9. Give every image meaningful alt text.
The social image is public/img/og-image.png (1200×630), rendered from og-image.svg. Edit the SVG,
then export it to PNG at 1200×630.
Navigation, header and footer#
The header and its menus are src/components/SiteHeader.tsx, built from NAV and MOBILE_NAV in
src/data/site.ts; the footer is src/components/SiteFooter.tsx. To add a page, create
src/app/<route>/page.tsx and add it to NAV (and to the footer lists if you like). The current page's
link gets aria-current="page" automatically; /sermon counts as Sermons.
Features#
Service status and countdown#
<ServiceStatus> in the top bar shows "Next gathering: Sunday at 9:00 am", or "Live now" with a "Watch"
link while a service is running. <Countdown> (home page and the sermons page; dark for the plum panel)
shows days, hours, minutes and seconds to the next service, and switches to a "Live now" card with a
"Watch live" button linking to CHURCH.liveUrl. <ThisSunday> shows the date of the next Sunday with a
service still to come.
Everything comes from serviceState() in src/lib/services.ts, which reads SERVICES in
CHURCH.timeZone, so a visitor in another time zone sees the right countdown for your church. Add a
service to SERVICES:
{ day: 3, start: "18:30", minutes: 60, name: "Midweek Worship" },To preview the live state at any time, add ?live=1 to a URL, for example /?live=1. The ticking
numbers are hidden from screen readers; a visually hidden sentence ("Starts in 5 days, 18 hours and 53
minutes.") gives the same information without announcing every second. Before the page has loaded in the
browser, these show the plain service times.
Audio player#
<AudioPlayer sermon={s} /> (src/components/AudioPlayer.tsx) appears on the sermon page, on the home
page (latest sermon) and in the "Watch live" panel on /sermons.
- With a real file: set the sermon's
audioto a URL ("/audio/room-at-the-table.mp3"for a file inpublic/audio/, or a full URL from your podcast host). The player then drives a real<audio>element: its duration comes from the file, and speed, seeking and progress use the media element. - Without a file (
audio: "", the demo): playback is simulated over the sermon'sdurationwith a timer, so every control works and a note under the player says so.
Controls: play/pause, a slider for scrubbing (5-second steps), back and forward 15 seconds, and a speed
button cycling through SPEEDS (1×, 1.25×, 1.5×, 1.75×, 2×, 0.75×). Only one player plays at a time.
Keyboard, while focus is inside the player: Space (when not on a button) or K play/pause, J and L
back and forward 15 seconds, arrow keys on the slider move 5 seconds, Home/End jump to the start or
end. The slider has an aria-valuetext such as "12 minutes 5 seconds of 38 minutes 42 seconds", the play
button's label switches between "Play" and "Pause", and speed changes are announced through a polite live
region.
The listening position is saved (see Saved data); returning to a sermon shows "Resuming at 12:04" and sermon cards show "Resume at 12:04".
Sermon archive#
<SermonArchive> fills the series, speaker and book selects from the data and filters on every change.
Search (after a short pause in typing, or on Enter) matches every word against the title, passage,
summary, series and speaker. The filters are kept in the URL, so links can be shared:
/sermons?series=open-table&speaker=dana&book=luke&q=table (names are case-insensitive; unknown values
are ignored). Series cards toggle the series filter. The result count is a live region; with no results an
empty state offers to clear the filters.
Sermon page#
/sermon?id=<id> (<SermonDetail>) shows one sermon; an unknown or missing id shows the latest sermon,
with a notice for an unknown one. The page title and meta description follow the sermon. "Copy questions"
copies the title, passage and numbered questions to the clipboard (it needs https or localhost;
elsewhere it says copying failed). Related sermons are the rest of the series, then other sermons by the
same speaker.
Events calendar#
<EventsCalendar> shows every occurrence of EVENTS (weekly events are expanded by calendar()). The
month view is a table with one button per day; choosing a day shows its events in the "Selected day"
panel, with the next three events after it. On wide screens each day lists up to two events and a "+n
more" button. The list view shows weekly events once in an "Every week" box, then upcoming events by
month.
Category pills filter both views. State is kept in the URL: /events?view=list&cat=kids,serve&month=2026-11.
Opening an event adds event=<id>-<date>, so a link opens straight to that event.
The detail is <EventDialog>, a native <dialog> (focus stays inside it and Esc closes it; focus
returns to the button that opened it). It opens for a click on any element with
data-open-event="<key>", which is how <EventCard> works. "Add to calendar" downloads an .ics file
built by buildIcs() in src/lib/ics.ts, with start and end times converted from the church's time zone
to UTC, so it imports correctly into Google Calendar, Apple Calendar and Outlook wherever the visitor is.
Groups finder#
<GroupFinder> renders filter pills for day, area and season of life from DAYS, AREAS and STAGES,
each with a live count; options that would give no results are disabled. "Childcare provided" is a
checkbox. State is in the URL: /groups?day=tue,thu&area=west&stage=families&childcare=1. "Ask to join"
links to /contact?topic=groups&group=<id>, which pre-fills the contact form.
Plan your visit#
<VisitForm> offers the next VISIT.sundays Sundays (computed in the church's time zone; today is
skipped once the last Sunday service has started) and one radio card per Sunday service. Adults and
children use steppers (typed numbers are clamped when the field changes); each child gets an age select,
and the summary shows which room they go to from VISIT.rooms. Children in the last room (youth) count as
an extra seat.
Giving#
<GiveForm> builds the amount chips from GIVING.amounts and the fund select from GIVING.funds.
Typing your own amount clears the chips. "Cover the card fee" adds what is needed so that, after the fee
in GIVING.fee (2.2% + $0.30 by default), the church receives the whole gift (feeFor()); the summary
shows the total and what the church receives either way. /give?fund=pantry pre-selects a fund.
No payment is taken. See Connecting a payment provider.
Scroll reveal, header and back to top#
Elements with .reveal fade in as they scroll into view (src/components/Reveal.tsx); everything above
the current element is revealed too, so jumping to an anchor never leaves gaps. With reduced motion,
nothing is hidden or animated. The header gets a border and shadow once the page scrolls. The mobile menu
(#menu-toggle) closes with Esc, after following a link and when the window grows to desktop width.
To remove the back-to-top button, delete <BackToTop /> from layout.tsx and the .back-to-top styles
in globals.css.
Forms#
All forms use useValidation() from src/components/forms/validation.tsx: errors appear on blur and
submit and update as you type, the first invalid field is focused, each error is linked with
aria-describedby, and a status line says "Please fix the highlighted fields."
| Attribute | Rule |
|---|---|
required |
Must not be empty (checkboxes must be ticked, selects must have a value) |
type="email", type="tel" |
Must look like an email address / phone number |
minLength |
Minimum length |
data-label |
Name used in the error message |
data-novalidate |
Field is skipped (used by filters) |
| Form | Component | Behaviour |
|---|---|---|
Newsletter (data-form="newsletter") |
NewsletterForm.tsx (home) |
Replaced with a thank-you message |
Plan your visit (#visit-form) |
VisitForm.tsx |
Validates contact details and each child's age; confirms the Sunday, time and seats |
Giving (#give-form) |
GiveForm.tsx |
Validates the amount (min and max from GIVING), name and email; explains nothing was charged |
Contact (#contact-form) |
ContactForm.tsx |
Pre-filled from ?topic= and ?group=; thank-you message |
Prayer (#prayer-form) |
PrayerForm.tsx |
Request required; email becomes required when "I'd like a pastor to get in touch" is ticked; the message reflects "Keep this private" |
Demo forms don't send anything. Each form's onValid callback (the function passed to
useValidation) is where to send it. For example, with Formspree in ContactForm.tsx:
const v = useValidation(async (form) => {
const res = await fetch("https://formspree.io/f/your-id", {
method: "POST",
body: new FormData(form),
headers: { Accept: "application/json" },
});
if (res.ok) setDone((form.querySelector<HTMLInputElement>("#c-name")?.value ?? "").trim().split(" ")[0]);
});Every field has a name, so FormData carries all the values; keep a name on any field you add.
Prayer requests can be sensitive: send them to a service and inbox only your pastoral team can read, and
honour the private field.
If you remove static export (see Building and deploying), you can post to a
Route Handler instead, for example src/app/api/contact/route.ts:
export async function POST(request: Request) {
const data = await request.formData();
// Send data.get("email"), data.get("message")… to your inbox or CRM here.
return Response.json({ ok: true });
}Connecting a payment provider#
The giving form never collects card details. To take real gifts, change the onValid callback in
GiveForm.tsx to call your server, which creates a checkout session with your provider (Stripe Checkout,
PayPal, or a church platform such as Tithe.ly or Planning Center Giving) and returns its URL:
const res = await fetch("/api/create-gift", { method: "POST", body: new FormData(form) });
const { url } = await res.json();
window.location.href = url;That endpoint needs a server (a Route Handler without static export, or a function on your host).
Recurring gifts (frequency is weekly or monthly) map to subscriptions in most providers. Many church
platforms also offer an embeddable giving form you can use instead of this one.
Saved data (localStorage)#
| Key | Shape | Used for |
|---|---|---|
riverbend-sermon-progress (PROGRESS_KEY) |
{ "room-at-the-table": 724 } (seconds) |
Resume position for each sermon |
readProgress() in src/lib/progress.ts keeps only ids that exist in SERMONS and positions between 5
seconds and 5 seconds before the end; anything else (broken JSON, arrays, unknown ids) is dropped. A
position is removed when a sermon finishes. If storage is blocked, the player works without saving. To
reset, run localStorage.removeItem("riverbend-sermon-progress") in the browser console, or change
PROGRESS_KEY to stop using old values.
SEO and sharing#
Each page exports metadata built by pageMeta(title, description) (src/lib/metadata.ts), which sets
the title, description and Open Graph tags; the defaults and the Twitter card are in
src/app/layout.tsx. The sermon page also updates its title and description from the sermon shown.
Social sites need absolute image URLs: set your address when you build,
NEXT_PUBLIC_SITE_URL=https://www.yourchurch.org pnpm buildand metadataBase turns /img/og-image.png into a full URL. Add a src/app/sitemap.ts and
src/app/robots.ts (both work with static export) to list the pages. For search, consider Church
structured data (a schema.org type) with your address and service times in a
<script type="application/ld+json"> on the home page.
Accessibility#
- Skip link, landmarks, one
<h1>per page and a logical heading order. aria-current="page"on the current navigation item; the mobile menu usesaria-expanded, closes withEscand when the window grows to desktop width.- Visible focus outlines on every control; radio and checkbox cards show focus on the card.
- The audio player is fully keyboard operable, with labelled buttons and a native range slider with spoken time.
- The calendar is a table with a caption and column headers; day buttons have full-date labels with the
number of events; the event detail is a modal
<dialog>. - Filter results and counts are announced with
role="status"; form errors are linked to their fields and the thank-you message is focused. - Illustrations have
<title>text (the map also has a<desc>); decorative SVG is hidden from screen readers. - Colour contrast meets WCAG AA;
prefers-reduced-motionturns off reveals, the live pulse and the river shimmer.
When editing, keep one <h1> per page, give every new input a <label>, and don't use apricot for text.
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=/church pnpm build); links,
scripts and asset() 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), 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#
- The countdown is an hour off. Check
CHURCH.timeZoneis your church's IANA time zone (for exampleAmerica/New_York), not a city name or an offset. - My sermons or events have the wrong dates.
KEEP_DEMO_CURRENTis stilltrue, so dates are moved forward. Set it tofalseinsrc/data/church.ts. - Dates and lists appear a moment after the page. That's by design: they depend on the visitor's clock and are rendered in the browser (see Demo dates).
- The player says playback is simulated. The sermon's
audiois empty. Add a URL to an audio file; if it is on another domain, that server must allow it to be played (most podcast hosts do). - A new sermon, event or group doesn't type-check. Its
series,speakerorcatmust be one of the ids inSeriesId,PersonIdorEventCategoryId; add new ids to those types. - "Copy questions" fails. The clipboard needs
httpsorlocalhost. - A class has no effect. Tailwind only sees class names written out in full; build them with complete
names (
"bg-plum"), not by joining fragments. - Images or links break in a sub-folder. Build with
DEMO_BASE_PATH, usenext/linkfor pages andasset()for files inpublic/.
License#
MIT: free for personal and commercial use. See LICENSE.txt in the download. The SVG artwork, logo,
portraits and social image are part of the template and covered by the same license. The fonts in
src/fonts/ are under the SIL Open Font License (see the *-OFL.txt files). Scripture quotations in the
demo are from the King James Version, which is in the public domain.