Skip to content

Handy

Handy is a site for a handyman or a small home-repair business: odd-job services, property maintenance, flat-pack assembly and “husband for an hour” services. It is set edge to edge on a pegboard: a pale board with rows of holes and the outlines of tools, hi-vis orange, safety yellow and painter’s tape blue, and chunky headings. On the home page a cutaway of a home runs across the whole window and, as you scroll, the jobs on the to-do list get done one by one: the drip stops, the picture levels, the bulb comes on, the shelf straightens, the door stops creaking, and the to-do note ticks them off. The demo is a fictional business, Sorted Handyman Services.

Handy 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)
Services, jobs, tips, 404 Pages → the page’s own entry
Prices (hourly rate, packages, how a job works, booking, deposits and vouchers, questions) Pages → Prices page
Contact (quote form, areas, map and its ring, hours, details, questions) Pages → Contact page
Services (tool, price from, how long, what’s included, typical jobs, questions, photo) Content → Services
Jobs (where, when, time, cost, before and after photos, story) Content → Jobs
Tips Content → Tips
Name, phone, address, insurance, navigation, header button, profiles Pages → Site settings
Buttons, labels, chat, emails, game texts Content → Interface texts
Privacy, terms, guarantee Pages → Legal

In headlines, words wrapped in *stars* get a strip of blue painter’s tape underneath, as in “Your to-do list, *sorted*”.

The home page’s opening has a to-do list of exactly five jobs, in the order the drawing fixes them: a dripping tap, a crooked picture, a dead bulb, a sagging shelf and a squeaky door. Change the words to your own (keep each to the same kind of job, since the drawing doesn’t change), and the title of the note.

Each job has a before and an after photo of the same view. On the cards (home page, jobs page, a service’s page) the after photo wipes in as the card scrolls past; on the job’s own page a slider lets visitors compare them (drag, or the arrow keys). Take both photos from the same spot.

The prices page holds your hourly rate on a big price tag with its small print (minimum, call-out, materials, waste), up to four packages (mark one as the popular one), how a job works as steps on a strip of painter’s tape (also on the home page), booking a visit with Cal.com, and deposits and gift vouchers you take with Stripe: each one gets 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 is drawn from OpenStreetMap in your colors, with a pin on your base and a dashed ring for the area you cover (Contact page → Map → Radius, in km; 0 hides it). It is part of the page (no embed, no cookies) and follows the theme and your accent. To draw your own area, put your base’s coordinates in map.config.json and run pnpm map -- fetch. detail: "area" (the default in Handy) draws the main roads, railways, water and woods of a district; make size about four times your radius in each direction (24,000 × 12,000 m for 5 km). detail: "streets" draws the streets and buildings round one address instead.

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

Feature Where Service
Book a visit Prices page, loads on click; a preview of the calendar until connected Cal.com
Deposits and gift vouchers Prices page (one-time Price ids) Stripe
Tips by email Home and tips pages, with a welcome email Supabase
Quote form Contact page; messages go to the email in Site settings Resend
AI chat On every page, answers about services, prices, jobs and areas; never gives safety advice on gas or electrics Claude
Search ⌘K or the search button: services, jobs, tips, prices, contact built in, or Algolia
Analytics Every page; Google Analytics only after consent Plausible, Umami, Google Analytics

The home page is built from these sections, in any order: Your to-do list (the opening), Services (cards that swing in on pegboard hooks), Recent jobs (before and after), How a job works (steps on a strip of tape that unrolls), Numbers (on a tape measure that pulls out), The area covered (the map), Tips, Tips by email and Closing call (a safety-yellow band between strips of caution tape). With reduced motion, the home stands fixed and ticked off and nothing moves.

src/content/singletons/brand.json (Pages → Brand in Keystatic) holds three colors: the accent (hi-vis orange: buttons, marks, the ticks), the second (safety yellow: the to-do note, the price tag, the tape measure) and the third (painter’s tape blue). Buttons are darkened until their white text is readable, the second and third are lightened until ink text on them is; a color that can’t get there stops the build with a message. Brand also sets the fonts and the corners. The site opens in the day theme even when the visitor’s system is dark; the toggle switches to the night, a charcoal board, and the browser remembers the choice.

Handy is ready for more languages: see Languages.

A small game, Screw it: drive each screw into a plank until its head sits flush, with circles of your finger or mouse round it or the arrow keys, then press “That’s flush” (or Enter). Stop short and it sticks out; one turn too many and the thread strips. Every screw has its own thread, so watch the head, not the turns. Three planks of five screws; 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, services, jobs and tips for AI assistants, in every language.
  • Pages carry JSON-LD: the business as a HomeAndConstructionBusiness with the areas it serves and its packages, services as Services with their price, jobs as CreativeWorks, how a job works as a HowTo, questions as a FAQPage, and each tip 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).