Skip to content

Needle

Needle is a site for a tattoo studio: a private studio with resident artists, an independent tattoo artist or a piercing studio. It looks like a parlour after hours: a dark, cinematic photo with drifting smoke and a beam of light, an arched serif logotype with a red word, a rating chip and Book and Call buttons. Beside it a flash design stands as a violet thermal stencil, and as you scroll it gets tattooed: the ink lines follow the stencil, dotwork stipples into the wings and the colour settles in. The demo is a fictional studio, Ironleaf Tattoo Co.

Needle opens in its dark theme; the toggle switches to stencil paper (light), and the browser remembers the choice. It 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)
Artists, flash, aftercare, 404 Pages → the page’s own entry
Book (age line, how it works, rates, consultation booking, deposits and vouchers, questions) Pages → Book page
Visit (map, hours, details, message form, questions) Pages → Visit page
Artists (role, styles, books open / waiting list / closed, price from, rate, portrait, healed work, questions) Content → Artists
Flash (artist, size, placement, price, deposit, claimed, repeatable, picture) Content → Flash
Aftercare guides Content → Aftercare
Name, phone, address, minimum age, licence, navigation, Book button, profiles Pages → Site settings
Buttons, labels, chat, emails, game texts Content → Interface texts
Privacy, terms, consent and age Pages → Legal

In headlines, words wrapped in *stars* are set in the lighter italic, as in “Four hands, *four very different lines*”.

The first home section holds the logotype in three parts (an arched word, a big word in your accent and a small word between rules), the rating chip (leave its value empty to hide it), one line about what and where, the main button, the Call button (it rings the phone number from Site settings), the small print (the address follows) and a wide, dark photo; the right side of the photo shows best. The scene stays pinned for about half a screen of scrolling while the flash design is tattooed, then scrolls away. With reduced motion the design shows finished and nothing moves.

Each artist has a page with their portrait (it morphs from their card), styles, whether their books are open, their minimum and rate, the bio, their healed work and their flash. With a waiting list the button asks visitors to join it through the message form; with closed books there is no button.

Flash designs are pinned to the wall, a little crooked, with their price. Each can be claimed with a deposit: give the design a one-time Stripe Price id (or a Lemon Squeezy variant id) in “Deposit price id”. A claimed design gets a red stamp and can’t be paid for; set it back to available, or untick “Repeatable” for one-of-a-kind pieces.

The book page starts with the minimum age (Site settings → Minimum age), then the rates, booking a free consultation with Cal.com, how it works as steps joined by a needle line, deposits and gift vouchers with Stripe (one-time Price ids) and the questions on age, ID, pricing and touch-ups. Without keys, the booking shows a preview of the calendar and the payment buttons explain how to switch them on.

The visit page (and the home section “Find the studio”) shows the streets round the studio, drawn from OpenStreetMap in your colours, with a pin and directions links. It is part of the page (no embed, no cookies). To draw your own, put the studio’s coordinates in map.config.json and run pnpm map -- fetch. No street names are shown.

Everything works without keys; each feature switches on with its variables in .env.example.

Feature Where Service
Book a consultation Book page, loads on click; a preview of the calendar until connected Cal.com
Flash deposits, session deposits, gift vouchers Flash pages and the book page (one-time prices) Stripe or Lemon Squeezy
Flash drops by email Home and aftercare pages, with a welcome email Supabase or Mailchimp
Message form Visit page; messages go to the email in Site settings Resend
AI chat Every page: artists, flash, prices, booking, hours. It always states the minimum age and never gives medical advice beyond the written aftercare Claude or OpenAI
Members-only aftercare A guide marked “Members only” is read after signing in Clerk
Search ⌘K or the search button: artists, flash, aftercare, booking, visit built in, or Algolia
Analytics Every page; Google Analytics only after consent Plausible, Umami, PostHog, Google Analytics
Photos Delivered by Cloudinary when set Cloudinary

The home page is built from these sections, in any order: Opening (the photo, the logotype and the stencil that gets inked), Styles (a marquee), Resident artists (portraits that give way to healed work), Flash sheet (designs pinned to a strip of wall), How it works (steps joined by a needle line that is tattooed across), Numbers, Find the studio (the map), Latest aftercare, Flash drops by email and Closing call.

src/content/singletons/brand.json (Pages → Brand in Keystatic) holds three colors: the accent (tattoo red: buttons, the big word of the logotype, marks), the second (stencil violet: the stencil lines and small marks) and the third (flash gold: badges and the rating stars). Buttons are darkened until their white text is readable, marks are kept visible on both walls, the gold is lightened until dark text on it reads; a color that can’t get there stops the build with a message. Brand also sets the fonts (a high-contrast serif with its italic for headings, a sans for text) and the corners.

Needle is ready for more languages: see Languages.

A small game, Line work: ink a flash design’s whole stencil in one line, never lifting the needle and never going over a line twice. Tap a dot to start, then a dot joined to it (or Tab and Enter, then the numbers 1–4 shown beside the next dots). Stuck on a dot with lines left, the needle lifts and the design starts over. Some designs can only be drawn from one of two dots. Three designs, more lines each; the best score is kept in the visitor’s browser.

  • AGENTS.md describes the project for coding assistants (Claude Code reads it through CLAUDE.md, Cursor through .cursor/rules/).
  • /llms.txt lists your pages, artists, flash and aftercare for AI assistants, in every language.
  • Pages carry JSON-LD: the studio as a TattooParlor, artists as Persons, flash as Products and an OfferCatalog, how it works as a HowTo, questions as a FAQPage, and each guide as a BlogPosting.
Terminal window
pnpm dev # local site, editor at /keystatic
pnpm build # production build
pnpm map -- fetch # draw the map round map.config.json from OpenStreetMap
pnpm 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).