Iris
Iris is a site for a local photographer who shoots a bit of everything: weddings, portraits, families, newborns, events, headshots, products and pets, from a small studio or on location. It is built like a darkroom and a light table: at night (the default, following the visitor’s system) a warm near-black room under a red safelight, by day a warm white light table, with fine film grain over both. Headings are set in a high-contrast serif with its italic for marked words, camera data in spaced capitals. The demo is a fictional studio, Nora Vale Photography.
Iris 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) |
| Sessions, journal, 404 | Pages → the page’s own entry |
| About (the approach, a photo; the people come from Team) | Pages → About page |
| Pricing (packages, how booking works, booking, deposits and gift vouchers, questions) | Pages → Pricing page |
| Studio (hours, before you come, map, details, contact form, questions) | Pages → Studio page |
| Sessions (line, summary, how long, price, delivery, included, how it goes, questions, cover, gallery) | Content → Sessions |
| Team (role, what they shoot with, biography, portrait) | Content → Team |
| Journal posts | Content → Journal |
| Name, contact, area covered, navigation, header button, profiles | Pages → Site settings |
| Buttons, labels, lightbox, chat, emails, game texts | Content → Interface texts |
| Privacy, terms, image use | Pages → Legal |
In headlines, words wrapped in *stars* are set in italics and underlined in the accent, as in
“Every kind of *day*”.
Through the lens
Section titled “Through the lens”The home page opens with a viewfinder. The photo sits behind an aperture whose blades open when the page loads; corner brackets, a frame counter and the camera settings sit on top, and focus brackets follow the pointer. Press the shutter button (or wait a few seconds) and the blades snap shut and open on the next photo, while the word in the headline changes with it: “Photographs of your weddings”, then newborns, families… Each photo is a slide in Home → Through the lens with its word, its description and its settings. Only the first photo is part of the first paint; the others load after the page. With reduced motion the blades stay open and photos change only on the button.
Sessions and galleries
Section titled “Sessions and galleries”Every session has a page: the cover across the window, how long it takes, the price and what you deliver, the description and what’s included, how it goes, the gallery and questions. Galleries take photos in any shape and open in a lightbox (previous and next with the buttons, the arrow keys or a swipe; Escape closes it). Each photo is also a plain link to its full-size file, so it works without script. The home page’s film strip lists the sessions on a strip of film that slides as you scroll; its contact sheet takes frames in turn from every gallery, with a loupe under the pointer.
Pricing, booking and payments
Section titled “Pricing, booking and payments”The pricing page holds up to four packages (mark one as the favourite), how booking works in three steps, booking a call with Cal.com, and what you sell with Stripe (the demo: a session deposit, a wedding deposit and a gift voucher), each with a one-time Price id. Without keys, the booking shows a preview of the calendar and the payment buttons explain how to switch them on.
The map
Section titled “The map”The “Find the studio” map is drawn from OpenStreetMap in
your colors: streets, green spaces, water and buildings round the studio, a pin with your name, your
address and directions links. It is part of the page (no embed, no cookies). Put the studio’s
coordinates in map.config.json and run pnpm map -- fetch.
Integrations
Section titled “Integrations”Everything works without keys; each feature switches on with its variables in .env.example.
| Feature | Where | Service |
|---|---|---|
| Book a call | Pricing page, loads on click; a preview of the calendar until connected | Cal.com |
| Deposits and gift vouchers | Pricing page (one-time Price ids) | Stripe |
| Newsletter | Home and journal pages, with a welcome email | Supabase |
| Contact form | Studio page; messages go to the email in Site settings | Resend |
| AI chat | On every page, answers about sessions, prices, booking and the studio; never promises a date | Claude |
| Search | ⌘K or the search button: sessions, people, journal, pricing, studio | built in, or Algolia |
| Analytics | Every page; Google Analytics only after consent | Plausible, Umami, Google Analytics |
Sections
Section titled “Sections”The home page is built from these sections, in any order: Through the lens (the opening), Sessions on a film strip, From shoot to delivery (prints developing in a tray as they scroll in), Contact sheet, Meet the photographer, Numbers, How to find the studio (the map), Journal, Newsletter and Closing call (a wide photo in viewfinder corners).
Colors and brand
Section titled “Colors and brand”src/content/singletons/brand.json (Pages → Brand in Keystatic) holds three colors: the accent
(the safelight: buttons, marks, the record light), the second (film amber: frame numbers and light
fills) and the third (negative cyan: focus brackets). Buttons are darkened until their white text
is readable and every pair is checked; a color that can’t get there stops the build with a message.
Brand also sets the fonts (headings and text) and the corners (square like a print, or rounded).
Languages
Section titled “Languages”Iris is ready for more languages: see Languages.
The 404 page
Section titled “The 404 page”A small game, Rule of thirds: a balloon drifts across a scene. Steer the viewfinder (pointer, drag or the arrow keys) so it sits on one of the four thirds points and press the shutter (a click, Space or the button): up to 100 points by how close it sits, nothing if it’s outside the frame. Five frames, each drifting faster, printed on a little contact strip; the best score is kept in the visitor’s browser.
AI assistants and search engines
Section titled “AI assistants and search engines”AGENTS.mddescribes the project for coding assistants (Claude Code reads it throughCLAUDE.md, Cursor through.cursor/rules/)./llms.txtlists your pages, sessions, people and journal for AI assistants, in every language.- Pages carry JSON-LD: the studio as a
ProfessionalServicewith its packages as offers, sessions asServices with their price, the people asPersons, booking as aHowTo, questions as aFAQPage, and each journal post as aBlogPosting.
Commands
Section titled “Commands”pnpm dev # local site, editor at /keystaticpnpm build # production buildpnpm map -- fetch # draw the map round map.config.json from OpenStreetMappnpm cms sanity schema|seed # 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).