Praline
Praline is a site for a chocolatier: bonbons and bars with their tasting notes and allergens, a gift box the visitor fills on the home page, pairings with drinks, the cacao origins and the farms behind them, workshops, gift boxes and gift cards paid online, and a visit page with a map. It also fits bean-to-bar makers, patisseries, confectioners and sweet shops. It is glossy and warm, like a box being opened: the home page opens on a short, slow film of molten dark chocolate folding over itself among split cacao pods and bonbons with gold leaf. Behind everything is warm cream paper with a fine grain of cocoa dust and slow swirls of cream and cocoa by day, and dark chocolate with a gold sheen at night, with flecks of gold leaf here and there. On tablets and desktops the navigation is a ribbon across the window, with the logo hanging from it on a gift tag. The demo is a fictional chocolatier, Atelier Noor, on a canal in Bruges.
Praline comes in two variants, chosen when you create the site: files/Sanity (static pages, any host) and
EmDash (--cms emdash, rendered on request on Cloudflare). The pages are the same in
both.
Pages and their content
Section titled “Pages and their content”| Page | Edit it in |
|---|---|
| Home | Pages → Home page (a list of sections) |
| Chocolates, workshops, origins, journal, 404 | Pages → the page’s own entry |
| Gift boxes (boxes and bars paid online, small print, workshop booking, gift cards, questions) | Pages → Gift boxes page |
| Visit (hours, details, map, contact form, questions) | Pages → Contact page |
| Chocolates (kind, cacao, price, size, tasting notes, origin, allergens, questions, photo, story) | Content → Chocolates |
| Workshops (kind, when, length, price, highlights, story, photo) | Content → Workshops |
| Origins (region, variety, farm and since when, tasting notes, story, photo) | Content → Origins |
| Journal (notes; one can be for members only) | Content → Journal |
| Name, phone, address, currency, navigation, header button, profiles | Pages → Site settings |
| Buttons, labels, chat, emails, game texts | Content → Interface texts |
| Privacy, terms, shipping and returns | Pages → Legal |
In headlines, words wrapped in *stars* are set in italic in the accent color (gold at night), as in
“Wrapped *in a ribbon*”. The opening’s headline has its two lines in separate fields.
The film in the opening
Section titled “The film in the opening”The opening plays a short muted film across the whole window (Pages → Home page → Opening → Film, a
file in public/, e.g. /media/hero.mp4), with its first frame as the photo. The photo shows at once;
the film is fetched a moment after the page has loaded and fades in when it plays, so the page stays
fast. With reduced motion or a data saver the photo stays. Keep the left of the picture calm: the
headline sits there on a veil of light.
To make your own: film a slow, close shot of your chocolate (or generate one), 5 to 20 seconds, then export it about 1280 pixels wide as H.264 MP4 without sound, ideally under 3 MB. A clip played forward and then backward loops without a cut. Replace the photo with its first frame.
Fill your box and pairings
Section titled “Fill your box and pairings”- Fill your box (Pages → Home page) lists the bonbons, each with a name, a line and a photo from above. Ticking one drops it into the next well of a 3×3 gift box; a counter shows how many are in, and the price and the order button sit below. Square photos of single bonbons on a plain background work best. It works without JavaScript and with the keyboard.
- Pairings lists drinks (port, espresso, champagne, tea…); each has a line and what goes with it, one pair per line as the chocolate, a dash and why (“Madagascar 72 – red fruit meets the berries in the port”). Picking a drink shows its list.
Chocolates, gift boxes and origins
Section titled “Chocolates, gift boxes and origins”Each chocolate has a page with its photo, the kind, cacao, size and price, its tasting notes as chips, the allergens, its origin, its story, the other chocolates and questions. Gift boxes appear as cards on the home page and the gift boxes page: each is paid online through a one-time Stripe Price id (or a Lemon Squeezy variant), and gift cards sit below. Origins appear as tags on sacks of beans with the region, variety and the year you started buying there; each has a page with the farm, tasting notes and its story.
The map
Section titled “The map”The visit page shows the streets round your shop, drawn from
OpenStreetMap in your colors, with a pin, your address and
directions links. It is part of the page (no embed, no cookies) and follows the theme and your accent.
To draw your own, put the coordinates in map.config.json and run npm run map -- fetch.
Integrations
Section titled “Integrations”Everything works without keys; each feature switches on with its variables in .env.example.
| Feature | Where | Service |
|---|---|---|
| Workshop booking | Gift boxes page, loads on click; a preview of the calendar until connected | Cal.com |
| Gift boxes and gift cards | Gift boxes page (one-time prices) | Stripe or Lemon Squeezy |
| Newsletter | Home and journal pages, with a welcome email | Supabase or Mailchimp |
| Contact form | Visit page; messages go to the email in Site settings | Resend |
| AI chat | Every page; suggests chocolates, names their allergens, never promises a chocolate is free of one | Claude or OpenAI |
| Members-only note | A journal post marked “Members only” | Clerk |
| Search | ⌘K or the search button: chocolates, workshops, origins, notes, gift boxes, visit | built in, or Algolia |
| Analytics | Every page; Google Analytics only after consent | Plausible, Umami, Google Analytics, PostHog |
| Photos | Served by Cloudinary when it is set | Cloudinary |
Sections
Section titled “Sections”The home page is built from these sections, in any order: Opening (the film, the headline, the buttons and up to three numbers of proof), Fill your box, Chocolates, Pairings, Origins, Workshops, Gift boxes, Reviews, Questions, Journal, Newsletter and Closing call. With reduced motion everything stands still.
Colors and brand
Section titled “Colors and brand”src/content/singletons/brand.json (Pages → Brand in Keystatic) holds three colors: the accent
(cocoa: the button, links, marked words and the hue of the night), the second (cream: soft fills) and
the third (gold leaf: fine lines and edges, eyebrows and the marks at night). Buttons are deepened
until their cream text is readable and links are adjusted until they read on the page; a color that
can’t get there stops the build with a message. Brand also sets the corners and two fonts: one for
headings (Fraunces, Playfair Display, DM Serif Display or Cormorant Garamond) and one for text (Manrope,
Jost, DM Sans or Hanken Grotesk). The site opens in its day theme; the toggle switches to the night, and
the browser remembers the choice.
Languages
Section titled “Languages”Praline is ready for more languages: see Languages.
The 404 page
Section titled “The 404 page”A small game, Pack the box: the bonbons have rolled off the tray. Walk round the counter and push each one into a well of the box, with the arrow keys or WASD (or the buttons on a phone). You can push but never pull, and only one bonbon at a time, so the order matters; U undoes a move and R starts the box again. Three boxes, each trickier; the fewer moves, the more points. The best total is kept in the visitor’s browser.
AI assistants and search engines
Section titled “AI assistants and search engines”AGENTS.mddescribes the project and its design rules for coding assistants (Claude Code reads it throughCLAUDE.md, Cursor through.cursor/rules/)./llms.txtlists your pages, chocolates, workshops, origins and notes for AI assistants, in every language.- Pages carry JSON-LD: the shop as a
Storewith its hours and offers, chocolates asProducts, workshops asEvents, origins asArticles, questions as aFAQPage, and each note as aBlogPosting.
Commands
Section titled “Commands”npm run dev # local site, editor at /keystaticnpm run build # production buildnpm run map -- fetch # draw the map round map.config.json from OpenStreetMapnpm run cms -- sanity schema # move content to Sanity (see Content → Sanity)In the EmDash variant: npm run dev, npm run deploy (Cloudflare) and npm run emdash:schema
(after changing the content model).
