Skip to content

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.

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 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 (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.

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 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.

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

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.

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.

Praline is ready for more languages: see Languages.

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.

  • AGENTS.md describes the project and its design rules for coding assistants (Claude Code reads it through CLAUDE.md, Cursor through .cursor/rules/).
  • /llms.txt lists your pages, chocolates, workshops, origins and notes for AI assistants, in every language.
  • Pages carry JSON-LD: the shop as a Store with its hours and offers, chocolates as Products, workshops as Events, origins as Articles, questions as a FAQPage, and each note as a BlogPosting.
Terminal window
npm run dev # local site, editor at /keystatic
npm run build # production build
npm run map -- fetch # draw the map round map.config.json from OpenStreetMap
npm 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).