Overview#
Crumb is a website for a neighbourhood bakery and café with click-and-collect preorders, built for Bramble & Rye, a fictional bakery on St Benedicts Street in Norwich. It has a live "fresh out of the oven" board, a preorder catalogue with per-day availability and quantity limits, a basket drawer with a collection day, time slot and order cut-off, a demo checkout with an order number and a calendar file, a celebration cake builder with a live drawing and price, a café menu with diet filters, an about page and a visit page with a wholesale form. This is the Next.js version: Next.js 16 (App Router), React 19, TypeScript and Tailwind CSS v4, exported as a fully static site. The product cards, bake artwork and café menu are rendered at build time from typed data, so they are in the HTML. This guide covers version 1.0.0. See it live at freeframe.dev/templates/crumb.
Quick start#
Requirements: Node.js 24 or newer and pnpm (npm works too).
pnpm install
pnpm dev # http://localhost:3000Try an order: add a croissant and a seeded rye on the preorder page, pick a collection slot in the basket and place the demo order at the checkout. Nothing is sent or charged.
The bake board, open-now badges and cut-off all follow the bakery's clock. To preview another time of day,
add ?now= to any page URL, for example http://localhost:3000/?now=2026-10-17T10:15.
pnpm build writes the static site to out/; pnpm start serves it.
Pages#
| Route | File | Purpose | Notable sections and features |
|---|---|---|---|
/ |
src/app/page.tsx |
Home | Hero with the shop-window illustration, live bake board, how preordering works, this week's specials (from the product data), celebration cake teaser, café teaser, reviews, visit us with open-now badge, hours and map |
/shop |
src/app/shop/page.tsx |
Preorder | Shelf chips with counts (/shop?category=bread), "Collecting" day, "only that day's bakes" toggle, product cards grouped by shelf, add buttons and steppers, allergen note |
/cakes |
src/app/cakes/page.tsx |
Celebration cakes | Builder for size, sponge, filling, finish, colour, message and collection date; live SVG preview, price breakdown and allergens; recent cakes; cake FAQ |
/cafe |
src/app/cafe/page.tsx |
Café menu | Coffee, tea, brunch and sweet things with diet filters (/cafe?diet=vegan,gf), café notes |
/checkout |
src/app/checkout/page.tsx |
Checkout (demo) | Collection day and slot, notes, contact details, pay at collection or demo card, order summary, confirmation with order number and .ics file, empty state with the last order |
/about |
src/app/about/page.tsx |
Our story | Story, timeline, the 36-hour sourdough process, suppliers, team, waste figures |
/visit |
src/app/visit/page.tsx |
Visit & wholesale | Illustrated map, hours with today highlighted, contact, getting here, wholesale enquiry form (/visit#wholesale), FAQ |
| any other | src/app/not-found.tsx |
Not found (404) | Links back to home, preorder and café |
Project structure#
crumb/
src/
app/
layout.tsx html shell: fonts, top bar, header, footer, basket drawer, toast, back to top
fonts.ts Karla, Corben and Delicious Handrawn via next/font/local
globals.css Tailwind v4 @theme tokens and component classes
icon.svg favicon
page.tsx home page
shop/ cakes/ cafe/ checkout/ about/ visit/ one folder per page (page.tsx)
not-found.tsx 404 page
components/
SiteHeader.tsx header, navigation, basket button and mobile menu (client)
SiteFooter.tsx footer with address, hours and the bake list sign-up
ShopClock.tsx open-now badge, cut-off countdown and hours list (client)
ProductCard.tsx a bake's card, diet tags and the week strip (rendered on the server)
Logo.tsx the Bramble & Rye mark
UrlParam.tsx reads ?category= / ?diet= in the browser (client)
Year.tsx, RevealObserver.tsx, BackToTop.tsx
home/BakeBoard.tsx the live bake board, its title and counts line (client)
shop/ShopCatalogue.tsx shelf chips, collection day, toggle and the grouped cards (client)
basket/ BasketButton, BasketDrawer, CardAction (add / stepper on a card),
parts (Stepper, PickupFields, ConflictAlert), Toast
cakes/CakeBuilder.tsx the cake builder and the earliest-date text (client)
cafe/CafeMenu.tsx diet chips, count and the menu (client)
checkout/CheckoutView.tsx the checkout form, summary, confirmation and empty state (client)
forms/ useValidation hook, NewsletterForm (with SuccessBox), WholesaleForm
data/
shop.ts SHOP, TIMEZONE, LOCALE, CURRENCY, HOURS and PICKUP rules
bakes.ts CATEGORIES, ALLERGENS, DIETS, PRODUCTS, BAKE_SCHEDULE and CATEGORY_TINT
cafe.ts CAFE_MENU
cakes.ts CAKE: celebration cake options, lead time and limits
lib/
clock.ts the shop clock: open-now, cut-off, collection dates and slots, ?now=
art.ts the SVG drawing code (bakes, cakes, tier icons, the paper bag)
bake-art.ts bakeOf(id) / bakeInner(id) shortcuts for the page illustrations
basket.ts the saved basket: types, repair on read, conflicts, line info and totals
store.tsx shared client state: basket, drawer, toast and the basket actions
cake.ts cake configuration, price, summary, allergens and description
order.ts the last order and its .ics calendar file
validation.ts form field rules
format.ts money, lists and allergen text
metadata.ts per-page title, description and Open Graph tags
asset.ts, storage.ts, use-shop-now.ts
fonts/ the woff2 files and their licenses
public/img/ og-image.png and its source og-image.svg
next.config.ts static export and optional base path
template.json gallery metadata (not part of your site)Customising#
Brand and colours#
The design tokens are in the @theme block of src/app/globals.css. Every utility such as bg-bramble or
text-muted comes from them.
| Token | Default | Used for |
|---|---|---|
--color-flour |
#fbf5ec |
Page background |
--color-paper |
#fffdf8 |
Cards and form fields |
--color-oat |
#f3e7d5 |
Soft panels, bread tiles |
--color-line |
#e7d6bf |
Hairlines and dashed rules |
--color-ink |
#3a2620 |
Text, outlines, the footer |
--color-muted |
#6c574a |
Secondary text |
--color-bramble / --color-bramble-deep |
#74305a / #561f42 |
Brand colour: buttons, links, basket |
--color-bramble-soft |
#f3dce7 |
Selected options |
--color-blush / --color-blush-deep |
#f7dcd3 / #a33a3f |
Cake tiles, sold out, error text |
--color-pistachio / --color-pistachio-deep |
#dfe8cb / #43602a |
Whole cake tiles, "on the shelf", vegan |
--color-butter / --color-butter-deep |
#f9e8b2 / #7a5a06 |
Pastry tiles, "baking now", badges |
--color-lilac, --color-sky |
#e5ddf0, #d9e8ee |
Pastel accents |
--color-crust |
#b9733c |
Decoration only (steam, bread brown) |
To change the brand colour, edit the tokens:
@theme {
--color-bramble: #2f5d50;
--color-bramble-deep: #1f4237;
--color-bramble-soft: #d8ebe3;
}The illustrations use fixed colours in src/lib/art.ts and inline SVG in the pages, so a new brand colour
doesn't recolour the bakes. The shelf tile colours are CATEGORY_TINT in src/data/bakes.ts.
Typography#
Three fonts are self-hosted with next/font/local in src/app/fonts.ts, from the same woff2 files Google
Fonts serves (in src/fonts/, with their SIL Open Font License files):
| Token | Font | Used for |
|---|---|---|
--font-display |
Corben (400, 700) | Headings, prices, the wordmark |
--font-sans |
Karla (400–800, with italics) | Body text and UI |
--font-hand |
Delicious Handrawn | Hand-lettered notes (.hand), the cake message |
Each localFont() call sets the real family name with declarations, so globals.css refers to "Karla",
"Corben" and "Delicious Handrawn" directly. To swap a font, put its woff2 files in src/fonts/, change
the paths and family names in fonts.ts and the matching token in globals.css. The SVG artwork names
'Delicious Handrawn' and Corben directly for its labels.
Content#
Page text lives in the page files under src/app/. Everything the interactive parts work from is in
src/data/:
| Export | File | What it holds |
|---|---|---|
SHOP |
shop.ts |
Name, address, phone and email used in the drawer, toasts and calendar file |
TIMEZONE, LOCALE, CURRENCY |
shop.ts |
Europe/London, en-GB, GBP |
HOURS |
shop.ts |
Opening hours per weekday, Sunday first, ["07:30", "16:00"] or null for closed |
PICKUP |
shop.ts |
cutoff ("14:00"), slotStep (30), lastSlotBeforeClose (30), daysAhead (14), notesMax (200) |
CATEGORIES |
bakes.ts |
The shelves: bread, pastry, bakes, whole |
ALLERGENS, DIETS |
bakes.ts |
Allergen names and diet labels (vegan, vegetarian, gf, df) |
PRODUCTS |
bakes.ts |
Every bake on the preorder page |
BAKE_SCHEDULE, BAKING_MINS |
bakes.ts |
When each bake comes out of the oven, and how long "baking now" shows before that |
CAFE_MENU |
cafe.ts |
Café sections and items |
CAKE |
cakes.ts |
Celebration cake options, the builder's default choices, lead time and limits |
Add a bake by adding an object to PRODUCTS:
{
id: "rye-bun",
name: "Caraway rye bun",
category: "bread",
price: 1.6,
unit: "each",
days: [5, 6],
max: 12,
blurb: "Soft rye buns with caraway, made for sandwiches.",
allergens: ["gluten"],
diet: ["vegan"],
art: { kind: "boule", crust: "#8c5532", top: "#a8693d" },
},| Field | Meaning |
|---|---|
id |
Unique, used in the basket and in data-add buttons |
category |
One of the CATEGORIES ids |
price |
In pounds; whole numbers show without pence (£32) |
days |
Weekdays it's baked (0 Sunday to 6 Saturday), or null for every day you're open |
max |
The most one order can hold |
leadDays |
Optional days' notice (default 1: order before the cut-off the day before) |
special |
Optional badge text; specials also appear on the home page |
allergens, diet |
Keys from ALLERGENS and DIETS |
art |
How it's drawn (see Images and artwork) |
TypeScript checks every field against the Product type. To show a new bake on the home page board, add it
to BAKE_SCHEDULE with the time the first tray comes out and, optionally, when it usually sells out:
{ id: "rye-bun", out: "09:15", soldOut: "13:00" },Café items are { name, price, desc, diet } inside a CAFE_MENU section. The cake builder reads
CAKE.sizes, CAKE.sponges, CAKE.fillings, CAKE.finishes and CAKE.colours; each option has an id, a
label and a price (a size has serves, factor and tiers, the diameters in inches used for the
drawing). CAKE.defaults sets the options picked when the builder opens.
The footer hours, reviews, the café teaser on the home page and the "Recently boxed up" cakes on /cakes are
written in the page files: update them alongside HOURS, CAFE_MENU and CAKE.
Images and artwork#
There are no photos. All artwork is SVG:
- Bakes are drawn by
bake(art)insrc/lib/art.tsfrom a product'sartobject.kindpicks the drawing:boule,tin,baguette,focaccia,croissant,painchoc,bun,knot,scone,tart,cookie,brownie,loafcakeorlayercake. Optional colours:crustandtop(breads),seeds/oats(tin),score: "leaf"(boule),almonds(croissant),dough/swirl(bun),sponge,frosting,fillingandtop("berries","walnuts","drip","flowers") forlayercake. A seeded random generator makes the same bake look the same every time, so the drawings made at build time match the ones the browser draws in the basket. - Celebration cakes are drawn by
cake(config)from the builder's choices; the recent cakes on/cakesand the cake on the home page use the same function with fixed choices. - The hero shop window, map, kitchen window, team avatars, coffee cup and 404 loaf are inline SVG in the page
files (the bakes inside them come from
bakeInner()insrc/lib/bake-art.ts). Edit them in place, or replace them with an<img>frompublic/, usingasset()for the path.
To use photos for the bakes, give each product an image path and replace the art <div> in
ProductCard.tsx with an <img> (keep the 4:3 tile). Keep the hero image around 560×660 (portrait).
The social image is public/img/og-image.png (1200×630), rendered from og-image.svg. The favicon is
src/app/icon.svg.
Navigation, header and footer#
The header, footer, basket drawer and toast are in src/app/layout.tsx, so they're shared by every page.
The main navigation is NAV in src/components/SiteHeader.tsx (the mobile menu adds "Home" in front); the
footer's "Explore" links are EXPLORE in src/components/SiteFooter.tsx. The current page is marked with
aria-current="page" from the route. To add a page, create src/app/<route>/page.tsx and add it to NAV.
Features#
Shop clock and opening hours#
All times use the bakery's clock (TIMEZONE) via shopNow() in src/lib/clock.ts, so a visitor abroad sees
the shop's real state. useShopNow() (src/lib/use-shop-now.ts) shares one clock between every component and
checks it every 30 seconds. While the page is prerendered there is no "now", so these parts show their static
fallback text and switch to live text in the browser, which avoids hydration mismatches.
The components in src/components/ShopClock.tsx:
OpenStatusshows "Open now until 4pm", "Closing soon, at 4pm" or "Closed · opens Tuesday 7:30am" with a coloured dot (shopStatus()).Cutoffshows the countdown toPICKUP.cutoff("Order in the next 3h 40m to collect tomorrow"), or when preorders open next (cutoffText()).HoursListlists the week from Tuesday with today in bold and a "Today" badge.
Add ?now=YYYY-MM-DDTHH:MM to a URL to test any time.
Bake board#
BakeBoard in src/components/home/BakeBoard.tsx shows every BAKE_SCHEDULE entry baked today. Each bake is
Out at 7:30am until BAKING_MINS before its time, then Baking now (with rising steam), then Out
since 7:30am, and Sold out (faded) after its optional soldOut time. Bakes on the shelf come first and
sold-out ones last. BoardTitle and BoardNote show the day and a counts line ("8 on the shelf · 1 in the
oven · 2 still to come"). After closing (and on closed days) the board shows the next bake day instead, so the
page never looks empty. Each bake links to its shelf on the preorder page.
Preorder catalogue#
ShopCatalogue in src/components/shop/ShopCatalogue.tsx groups PRODUCTS by shelf. The prerendered page
has every card; in the browser the chips filter by shelf and keep the address in step (?category=pastry),
the "Collecting" select is the basket's collection day, and "Only that day's bakes" hides anything not baked
then. Cards (ProductCard.tsx) show the bake days as a Monday-first week strip, diet tags and allergens, and
CardAction shows:
- Add to basket when it fits the chosen day.
- Add for Sat 17 Oct when it doesn't: adding it moves the collection to the first day that suits the whole basket, and a toast says so.
- A quantity stepper and "In your basket" once it's in the basket, capped at
max.
Filters are buttons with aria-pressed, and the result count is a live region.
Basket drawer#
The basket button in the header opens BasketDrawer (#basket-drawer), a modal dialog with a focus trap;
Esc, the close button or the backdrop close it and focus returns to the button that opened it. It lists each
line with a stepper (at one, the minus becomes a bin) and a remove button, then the collection details
(PickupFields and ConflictAlert in src/components/basket/parts.tsx, shared with the checkout):
- Collection day: open days from the earliest possible (
earliestDate()) up toPICKUP.daysAhead(CAKE.daysAheadwhen there's a celebration cake). Orders beforePICKUP.cutoffcan collect the next open day; after it, the day after. Whole cakes add theirleadDays, celebration cakesCAKE.leadDays. Days that don't suit everything say "(2 items not baked)". - Time slot: half-hour slots from opening time until
lastSlotBeforeCloseminutes before closing. - Conflicts: if something isn't baked on the chosen day, the drawer says so, offers the next day that suits everything, and disables checkout until it's fixed.
- Notes up to
PICKUP.notesMaxcharacters, saved as you type.
The basket actions (addProduct(), setQty(), removeLine(), setPickup(), setNotes()), the drawer and
the toast live in src/lib/store.tsx.
Celebration cake builder#
CakeBuilder in src/components/cakes/CakeBuilder.tsx builds the radio cards from CAKE and redraws the cake
and price on every change; the drawing has a text description of the cake for screen readers. The price is
the size's price plus sponge, filling and finish extras multiplied by the size's factor, rounded to whole
pounds (cakePrice() in src/lib/cake.ts). The colour swatches are disabled for the naked finish. The
message is limited to CAKE.messageMax characters with a live counter. The date's minimum is CAKE.leadDays
days ahead (one more after the cut-off) and closed days are rejected. Adding the cake puts it in the basket,
sets the collection to that date and shows a toast; one order holds up to CAKE.maxPerOrder cakes. On phones
a second "Add cake to basket" button with the price sits at the end of the form.
Café menu filters#
CafeMenu in src/components/cafe/CafeMenu.tsx renders CAFE_MENU. The diet chips combine (a dish must
match all of them; vegan counts as vegetarian and dairy-free), keep the address in step (?diet=vegan,gf)
and show an empty state with a clear button when nothing matches.
Checkout and calendar file#
CheckoutView in src/components/checkout/CheckoutView.tsx shows the collection picker, notes, contact
fields, payment choice and the order summary. "Pay now by card" reveals a demo card form (Luhn-checked number
in groups of four, MM / YY expiry, CVC). If something isn't baked on the chosen day, placing the order is
refused until the day is fixed. On a valid submit it creates an order number (BR- and five digits), saves
the order to crumb-last-order, empties the basket and shows a confirmation with an Add to calendar
(.ics) link built by icsFor() in src/lib/order.ts: a 30-minute event at the collection slot with
TZID=Europe/London and a VTIMEZONE block, with the items in the description. The empty checkout shows the
last order and its calendar link.
Scroll reveal, motion and back to top#
Elements with the reveal class fade in as they scroll into view (RevealObserver.tsx); the steam and the
"baking now" pulse are CSS animations in globals.css. All of them are off with prefers-reduced-motion. A
back-to-top button (BackToTop.tsx) appears after one screen of scrolling and moves focus to the main
content; remove it from layout.tsx and the .back-to-top styles in globals.css to drop it.
Forms#
All forms validate in the browser with inline messages (rules in src/lib/validation.ts, wiring in
src/components/forms/useValidation.tsx) and show a success state. None of them send anything until you
connect them.
| Form | Where | Validates |
|---|---|---|
Bake list sign-up (data-form="newsletter") |
src/components/forms/NewsletterForm.tsx, footer of every page |
|
Wholesale enquiry (data-form="wholesale") |
src/components/forms/WholesaleForm.tsx, /visit |
Business, type, name, email, optional phone (10–13 digits), loaves a day (a whole number from 10 to 400), at least one interest, start date at least 7 days ahead, message up to 600 characters |
Cake builder (#cake-form) |
src/components/cakes/CakeBuilder.tsx, /cakes |
Collection date (lead time, open day, up to CAKE.daysAhead), message length |
Checkout (#checkout-form) |
src/components/checkout/CheckoutView.tsx, /checkout |
Time slot, name, email, phone (10–13 digits), allergen confirmation, card fields when paying now |
Rules come from attributes on each field: required and data-label ("Your email is required."), the input
type, data-validate (name, card, expiry, cvc, open-day), min / max, maxlength,
data-min-msg and, for checkboxes, data-msg. Errors appear in <p id="<field id>-error"> after the
field, linked with aria-describedby and aria-invalid, and the first problem is focused.
To connect the wholesale form to a form service such as Formspree, post the data in
its onSubmit before showing the thank-you message:
onSubmit={async (e) => {
e.preventDefault();
const form = e.currentTarget;
if (!v.validate(form)) return setStatus("Please check the highlighted fields.");
const res = await fetch("https://formspree.io/f/your-id", {
method: "POST",
body: new FormData(form),
headers: { Accept: "application/json" },
});
if (!res.ok) return setStatus("Sorry, that didn’t send. Please try again or call us.");
const value = (name: string) => (form.elements.namedItem(name) as HTMLInputElement).value.trim();
setSent({ first: value("name").split(/\s+/)[0], business: value("business") });
}}The newsletter works the same way in NewsletterForm.tsx. Every field already has a name. If you remove
the static export (see Building and deploying), you can post to your own Route
Handler (for example src/app/api/wholesale/route.ts exporting POST) or call a Server Action
instead.
For real orders, onSubmit in CheckoutView builds an order object with the lines, collection day and
slot, contact details and payment choice; send it to your order system where the comment says so, take
payment with your provider (Stripe Checkout, Square, SumUp…) and only show the confirmation when it succeeds.
Remove the demo card form when you do, and never trust prices from the browser: recalculate them on the
server from your product data.
Saved data (localStorage)#
| Key | Shape | Notes |
|---|---|---|
crumb-basket |
{ lines, date, time, notes } |
lines are { id, qty } or { id: "cake", key, cake: { size, sponge, filling, finish, colour, message }, qty: 1 }; date is YYYY-MM-DD, time is HH:MM |
crumb-last-order |
{ number, placedAt, date, time, notes, name, email, phone, payment, lines, total } |
Written at checkout; shown on the empty checkout page |
Everything is checked on every read (loadBasket() in src/lib/basket.ts, readLastOrder() in
src/lib/order.ts): unknown products and cake options are dropped, quantities become whole numbers within
max, duplicates merge, more than CAKE.maxPerOrder cakes are dropped, dates outside the bookable range and
slots that don't exist reset, and the repaired value is written back when a page loads. Prices are never
stored; they always come from PRODUCTS and CAKE. Components read the basket with useStore()
(useSyncExternalStore), which has no basket while the page is prerendered and hydrated, and other open tabs
update through the storage event. To reset, run localStorage.removeItem("crumb-basket") in the console,
or clear the site's data. Change the key names if you run several sites on one domain.
SEO and sharing#
- Site-wide defaults are in
metadatainsrc/app/layout.tsx(they are also the home page's title and description). - Every page exports
metadata = pageMetadata(title, description)fromsrc/lib/metadata.ts, which sets the title, description, Open Graph tags (with the imageasset("/img/og-image.png")) and the Twitter card. metadataBasecomes fromNEXT_PUBLIC_SITE_URL(falling back tohttps://example.com). Set it when you build so social images get absolute URLs:
NEXT_PUBLIC_SITE_URL=https://www.your-bakery.co.uk pnpm buildThe product cards and café menu are in the exported HTML, so every search engine sees them. Consider adding
src/app/sitemap.ts and src/app/robots.ts (both work with static export; leave out the checkout) and a
Bakery structured data block as JSON-LD with the address, telephone and
openingHours.
Accessibility#
- Skip link, landmarks (
header,nav,main,footer), oneh1per page and a logical heading order. - Visible focus outlines in the brand colour, including radio cards, chips and swatches; WCAG AA contrast.
- The mobile menu button has
aria-expandedand an "Open menu" / "Close menu" label, and closes on Esc. - The basket drawer is
role="dialog"witharia-modal, a focus trap, Esc and the backdrop to close, and focus returned to the button that opened it. - Shelf and diet chips are toggle buttons (
aria-pressed); the result counts, toasts and cake price are live regions. - Steppers have labelled buttons ("One more Butter croissant"), remove buttons include the item name, and the week strip has a text version ("Baked Thursdays & Saturdays.").
- The cake drawing has a text description; decorative art is hidden from screen readers.
- Form errors are linked with
aria-describedby, fields getaria-invalidand the first error is focused. - Reduced motion turns off reveals, steam, the "baking now" pulse and smooth scrolling.
When editing, keep exactly one h1 per page, give every new input a <label>, and write full class names
(Tailwind only finds classes it can read in the source).
Building and deploying#
pnpm build writes a fully static site to out/ (output: "export" in next.config.ts). Deploy it
anywhere:
- Vercel: import the repository; no settings needed.
- Netlify / Cloudflare Pages: build command
pnpm build, publish directoryout. - Any static host: upload the contents of
out/.
To serve the site from a sub-folder, build with a base path: DEMO_BASE_PATH=/crumb pnpm build.
next.config.ts passes it to basePath and to NEXT_PUBLIC_BASE_PATH, which asset() uses.
pnpm build:demo does this for the freeframe.dev demo and writes demo/, which you can delete.
Need server features (a Route Handler for orders, Server Actions, middleware)? Remove output: "export"
and images: { unoptimized: true } from next.config.ts and deploy to a Node-capable host such as Vercel.
Everything else keeps working.
pnpm lint runs ESLint 10 and pnpm typecheck runs TypeScript 7 (@typescript/native); the typescript
package is an alias of @typescript/typescript6, because typescript-eslint still needs the TypeScript 6 API.
Troubleshooting#
- The bake board says "Tomorrow" all day.
HOURSdecides when you're open; check the closing time for today and thatTIMEZONEis your bakery's zone. - A bake never appears on the board. It needs a
BAKE_SCHEDULEentry and must be baked that day (days). - Customers can't pick tomorrow. It's after
PICKUP.cutoff, tomorrow is closed, or something in the basket needs more notice (leadDays). - The basket shows old items. Stored baskets are repaired against
PRODUCTS; if you renamed anid, old baskets drop that line. Clearcrumb-basketto start fresh. - A new class has no effect. Tailwind only finds full class names in the source; write
bg-pistachio, not"bg-" + colour. - Images or links break under a sub-folder. Build with
DEMO_BASE_PATHand reference public files withasset()(links made withnext/linkget the base path automatically). - Social previews point to
example.com. SetNEXT_PUBLIC_SITE_URLwhen building. - The calendar file has the wrong time. Events use
TIMEZONE; if you change it, update theVTIMEZONErules inicsFor()too.
License#
MIT: the full text is in LICENSE.txt, included in the download. You can use, change and sell sites built
with this template, including all artwork (bakes, cakes, map, logo and illustrations). Karla, Corben and
Delicious Handrawn are licensed under the SIL Open Font License (src/fonts/OFL-karla.txt,
src/fonts/OFL-corben.txt, src/fonts/OFL-delicious-handrawn.txt). Bramble & Rye, its team and its
customers are fictional.