Skip to the card
Mireille
Book a table
Open until 17:00

The system

The parts, unassembled

Every token, primitive and section this theme ships

Sections
23
Primitives
27
Pages
13
Tokens
84

× 2 modes × 3 presets

The tokens

The sheet is painted from the live custom properties, so it cannot go stale: change theme.css and it changes with it.

token-sheet

src/components/token-sheet.tsx

The tokens

84 tokens, two modes, three presets

Surfaces

  • Page--background
  • Card stock--card
  • Tab at rest--muted
  • Secondary--secondary
  • Hairline--border

Ink and brand

  • Ink--foreground
  • Quiet ink--muted-foreground
  • Stamp red--primary
  • Served-now chip--accent
  • Link--link
  • Focus--ring

The slip's own material

These five do not invert.

  • Slip--slip
  • Slip ink--slip-ink
  • Stamp--slip-stamp
  • Slip edge--slip-edge
  • Clip--clip

Status

  • Positive--positive-fill
  • Warning--warning-fill
  • Critical--critical-fill

The type ladder

Ratio 1.250, anchored at 1.5rem. Brygada 1918 sets the card and the headings, Radio Canada the chrome, Sometype Mono the slip.

  • Lunch, until half past twod4 · 2.93rem, fluid
  • Lunch, until half past twod3 · 2.344rem, fluid
  • Lunch, until half past twod2 · 1.875rem
  • Lunch, until half past twod1 · 1.5rem
  • Lunch, until half past twolead · 1.125rem
  • Lunch, until half past twobody · 1rem
  • Lunch, until half past twosmall · 0.875rem
  • Lunch, until half past twocaption · 0.75rem

Geometry and stroke

Paper has square corners. The card, the slip and every photograph are radius zero; controls take a quarter-rem; the served-now chip is the one full-radius object in the theme.

  • Card and slip--radius-card · 0
  • Controls--radius · 0.25rem
  • Served-now chip--radius-chip · full
  • Icons--icon-stroke 1.5 · size-4 / 5 / 6

The presets, three by two

Zinc, Cresson and Faïence are a token swap on one attribute — set data-preset on the root element. A preset repaints the room and the ink; it never repaints the paper, and it never moves anything.

Zinclight

Le midi

11:30 – 14:30 · Paris-style bistro

Get directionsServed now

Zincdark

Le midi

11:30 – 14:30 · Paris-style bistro

Get directionsServed now

Cressonlight

Le midi

11:30 – 14:30 · Morning and brunch

Get directionsServed now

Cressondark

Le midi

11:30 – 14:30 · Morning and brunch

Get directionsServed now

Faïencelight

Le midi

11:30 – 14:30 · Tiled bakery-café

Get directionsServed now

Faïencedark

Le midi

11:30 – 14:30 · Tiled bakery-café

Get directionsServed now

The shell

Two sections every route carries. The header derives whether the café is open from the hours record and the clock; nobody writes that sentence.

site-header

src/components/site-header.tsx

Skip to the card
Mireille
Book a table
Open until 17:00

site-footer

src/components/site-footer.tsx

Home

Four bands. The photograph is the daypart being served at the fixture instant, and the card under it carries the day's slips.

hero-served

src/components/hero-served.tsx

Full-bleed plate with the daypart clause over it. Square at 375, the plate's native 16:10 through the tablet widths, a measured 600px band from lg.

A bowl of soup, a croque cut in half and a lentil salad on the zinc, cutlery mid-use
Served now · Wednesday 16 September

Lunch, until half past two.

Walk in and order at the counter: 9 dishes, 2 changes.

Walk in, no booking: 34 seats inside and 12 on the terrace.

served-card

src/components/served-card.tsx

The menu-layout axis in its switcher state, with type-only rows: folder tabs over one card, opening on the service being served.

On the card

The card, and today’s changes

The menu is set once a season; today goes on the slip.

Brunch and souper are not served today.

See the whole card

Le midi

Tuesday to Friday · 11:30 – 14:30

Marks: V Vegetarian · GF No gluten · N Contains nuts

Today

Wed 16 Sep

  • Soupe: courge rôtie et pomme, graines grillées · VG$9

Posted 07:52 by Élise

Instead

Wed 16 Sep

  • Tomates du marché Jean-Talon instead, last of the season$15

Posted 07:52 by Élise

Œufs & tartines

  • Tartine aux champignonsVOyster mushrooms, crème fraîche, chives, ryeslip · 2$15

Soupes & salades

  • Soupe du jourcourge rôtie et pomme, graines grillées · VG$9
  • Salade de lentillesVGFNPuy lentils, roast beets, chèvre, walnuts$16

Sandwichs

  • Croque MireilleHam, Comté, béchamel, house sourdough$17
  • Jambon-beurreParis ham, salted butter, cornichons, baguette$13

Assiettes

  • Poulet rôti, demiGFHalf a chicken, its jus, frites and a green salad$26
  • Céleri-rave rôtiVNWhole-roast celeriac, brown butter, hazelnuts, farro$18
  • Truite fumée sur röstiGFSmoked trout from Saint-Alexis, dill crème fraîche$21

Desserts

  • Tarte TatinVApple, caramel, crème fraîche$9
Allergies
Tell us about an allergy before you order and we will tell you what is in the dish. One kitchen, shared pans, no separate fryer.
Prices
Taxes (TPS/TVQ) added at the till

on-the-grinder

src/components/on-the-grinder.tsx

The espresso grinder at the end of the counter, hopper full, portafilter loaded

On the grinder

The grinder changes when a bag runs out, not on a schedule

Espresso

Brûlerie des Carrières · Sidamo, Ethiopia · washed

On since Tue 8 Sep

Filter

Brûlerie des Carrières · Huila, Colombia · natural

On since Tue 1 Sep

Out of the oven

Kouign-amann
07:30 and 14:30usually gone by 11:00
Croissant au beurre
07:00usually gone by 10:30

visit-strip

src/components/visit-strip.tsx

Getting here

6340 rue Beaubien Est

Montréal, Québec H1M 1A9

Step-free from the pavement, and the 18 stops at the corner

Transit
The 18 Beaubien bus stops at the corner
Access
Step-free entrance on Beaubien; one accessible washroom
Laptops
Laptops away until 14:30
Dogs
Terrace only, and there is a water bowl by the door

Today

  • Wednesday07:30 – 17:00
  • Terraceslip · 11 May to 15 October, weather allowing

Takeaway

Ordered on our own page, so the whole price stays in the room.

Collection at the counter, and we do not deliver.

Order for collectioncommande.mireille.example

Book a table

Bookings: Souper. Everything else is walk-in.

Book a table

The card

Fifty-two items, five services, nine all-day drinks. The menu-layout axis in its stacked state, with thumbnail rows.

menu-index

src/components/menu-index.tsx

The whole card

Five services on one card, and the day's changes clipped on top

52 dishes · 10 sections · 5 times of day

Services

  1. Le petit-déjeunerBreakfastTuesday to Friday · 07:30 – 11:0012 dishes · $4.25 – $14Served today
  2. Le brunchSaturday to Sunday · 09:00 – 15:0014 dishes · $4.25 – $21Not today
  3. Le midiLunchTuesday to Friday · 11:30 – 14:309 dishes · $9 – $26Served now
  4. Le goûterTuesday to Sunday · 14:30 – 17:009 dishes · $4 – $9Served today
  5. Le souperThursday to Saturday · 17:30 – 22:00 · last order 21:1511 dishes · $9 – $34Not today

Café & boissons · 9 drinks, printed once at the end

Allergies
Tell us about an allergy before you order and we will tell you what is in the dish. One kitchen, shared pans, no separate fryer.
Prices
Taxes (TPS/TVQ) added at the till

menu-dayparts

src/components/menu-dayparts.tsx

Every service down the page, each opening on its counter band. Forty of the fifty-two items have no photograph and render the type-only row — never an empty square.

Croissants on a wire rack, two café crèmes and a folded newspaper on the zinc, in low morning light

Le petit-déjeuner

Breakfast · Tuesday to Friday · 07:30 – 11:00

Marks: V Vegetarian · VG Vegan · N Contains nuts

Viennoiseries

  • Croissant au beurreVLaminated overnight, baked at seven$4.25
  • Pain au chocolatVTwo batons of Valrhona, same dough as the croissant$4.75
  • Chausson aux pommesVCortland apples from Rougemont, barely sweetened$4.50
  • Kouign-amannVButter, sugar, and forty minutes of patience. Two batches a day$5.50

Œufs & tartines

  • Œufs brouillés, tartineVSlow-scrambled eggs, cultured butter, sourdoughAdd: Egg $2.50$14
  • Tartine à l'avocatVGAvocado, lemon, chilli, ryeAdd: Egg $2.50$13
  • Baguette, beurre et confitureVHalf a baguette, salted butter, this month's jamAdd: Egg $2.50$7
  • Œuf à la coque, mouillettesVSix-minute egg, soldiers of buttered sourdough$9

Bols

  • Bol de granola maisonVNOats and hazelnuts, yoghurt, whatever fruit is in$11
  • Bircher aux pommesVOats soaked overnight, grated apple, toasted seeds$10

Desserts

  • Tarte au citronVSharp lemon curd, no meringue, a thin shell$7
  • Cookie au chocolat noirV70% chocolate, rye flour, flaky salt$4.25
A buckwheat crêpe, smoked-trout rösti and orange juice at a crowded end of the counter

Le brunch

Saturday to Sunday · 09:00 – 15:00

Not today

Marks: V Vegetarian · GF No gluten · VG Vegan · DF No dairy

Viennoiseries

  • Kouign-amannVButter, sugar, and forty minutes of patience. Two batches a day$5.50

Œufs & tartines

  • Baguette, beurre et confitureVHalf a baguette, salted butter, this month's jamAdd: Egg $2.50$7
  • Œuf à la coque, mouillettesVSix-minute egg, soldiers of buttered sourdough$9

Bols

  • Bol de yaourt, compote de rhubarbeVGFSheep's yoghurt, rhubarb stewed with orange$10
  • Salade de fruits d'automneVGGFPear, grape, late melon, mint$9
  • Bircher aux pommesVOats soaked overnight, grated apple, toasted seeds$10

Assiettes

  • Crêpe sarrasin, œuf et jambonBuckwheat galette, Comté, ham from Charlevoix$17
  • Pain doré à la briocheVThick brioche, maple from Lanaudière, crème fraîche$16
  • Œufs bénédictine au saumon fuméCold-smoked salmon, hollandaise, muffin anglais$19
  • Saucisses de Toulouse, pommes rissoléesGFDFCoarse pork sausage, potatoes fried in duck fat$18
  • Truite fumée sur röstiGFSmoked trout from Saint-Alexis, dill crème fraîche$21

Desserts

  • Gaufre de Liège, crème fouettéeVPearl-sugar waffle, whipped cream, cocoa$12
  • Tarte au citronVSharp lemon curd, no meringue, a thin shell$7
  • Cookie au chocolat noirV70% chocolate, rye flour, flaky salt$4.25
A bowl of soup, a croque cut in half and a lentil salad on the zinc, cutlery mid-use

Le midi

Lunch · Tuesday to Friday · 11:30 – 14:30

Served now

Marks: V Vegetarian · GF No gluten · N Contains nuts

Today

Wed 16 Sep

  • Soupe: courge rôtie et pomme, graines grillées · VG$9

Posted 07:52 by Élise

Instead

Wed 16 Sep

  • Tomates du marché Jean-Talon instead, last of the season$15

Posted 07:52 by Élise

Œufs & tartines

  • Tartine aux champignonsVOyster mushrooms, crème fraîche, chives, ryeslip · 2$15

Soupes & salades

  • Soupe du jourcourge rôtie et pomme, graines grillées · VG$9
  • Salade de lentillesVGFNPuy lentils, roast beets, chèvre, walnuts$16

Sandwichs

  • Croque MireilleHam, Comté, béchamel, house sourdough$17
  • Jambon-beurreParis ham, salted butter, cornichons, baguette$13

Assiettes

  • Poulet rôti, demiGFHalf a chicken, its jus, frites and a green salad$26
  • Céleri-rave rôtiVNWhole-roast celeriac, brown butter, hazelnuts, farro$18
  • Truite fumée sur röstiGFSmoked trout from Saint-Alexis, dill crème fraîche$21

Desserts

  • Tarte TatinVApple, caramel, crème fraîche$9
A kouign-amann on a plate beside a slice of tarte Tatin and a cortado, in long afternoon light

Le goûter

Tuesday to Sunday · 14:30 – 17:00

Marks: V Vegetarian · N Contains nuts · GF No gluten

Note

Wed 16 Sep

  • Kouign-amann: second batch out at 14:30

Posted 07:05 by Élise

Viennoiseries

  • Madeleines, les troisVBaked to order, brown butter and lemon zest$6
  • Cake aux olives et romarinVA savoury loaf, cut thick, olive oil on the side$6.50
  • Palmier feuilletéVThe croissant dough's offcuts, rolled in sugar$4
  • Kouign-amannVButter, sugar, and forty minutes of patience. Two batches a day$5.50

Desserts

  • Financier à la pistacheVNGFPistachio and beurre noisette, nothing else$5
  • Cannelé de BordeauxVRum and vanilla, a dark shell and a soft middle$4.50
  • Tarte au citronVSharp lemon curd, no meringue, a thin shell$7
  • Cookie au chocolat noirV70% chocolate, rye flour, flaky salt$4.25
  • Tarte TatinVApple, caramel, crème fraîche$9
Half a roast chicken, a carafe of red and a candle in a glass, with blue window light behind

Le souper

Thursday to Saturday · 17:30 – 22:00 · last order 21:15

Not today

Marks: V Vegetarian · GF No gluten · N Contains nuts · DF No dairy · VG Vegan

Souper — entrées

  • Soupe à l'oignon gratinéeVOnions cooked down for three hours, Comté, crouton$14
  • Betteraves, chèvre frais, noisettesVGFNRoast beets, fresh goat's cheese, toasted hazelnuts$15
  • Rillettes de porc, cornichonsDFPotted pork, mustard, a pile of cornichons, toast$14

Souper — plats

  • Moules marinière, fritesDFMussels from Îles-de-la-Madeleine, white wine, shallot$27
  • Truite amandineGFNWhole trout, brown butter, almonds, capers$29
  • Steak frites, beurre maître d'hôtelGFBavette from Charlevoix, parsley butter, frites$32
  • Magret de canard, chou rouge braisé et poires pochées au cidreGFDFDuck breast rested pink, red cabbage, cider-poached pear$34

Desserts

  • Crème brûlée à la vanilleVGFCracked at the table, nothing underneath it$11
  • Île flottanteVGFPoached meringue, crème anglaise, caramel$10
  • Tarte TatinVApple, caramel, crème fraîche$9

Café & boissons

  • Carafe de vin rouge, 500 mlVGGamay from the Loire, poured cool$26

menu-drinks

src/components/menu-drinks.tsx

Café & boissons

Served at every hour, on every card

  • EspressoVGGFToday's espresso, on the grinder since TuesdayAdd: Oat milk $0.75 · Extra shot $1.25$3.25
  • AllongéVGGFA long black, pulled short and lengthenedAdd: Oat milk $0.75 · Extra shot $1.25$3.50
  • Café crèmeVGFEspresso and steamed milk, in the big cupAdd: Oat milk $0.75 · Extra shot $1.25$4.75
  • CappuccinoVGFSix ounces, no syrup, no dustingAdd: Oat milk $0.75 · Extra shot $1.25$5
  • Filtre du jourVGGFBatch filter, refilled on the half hourAdd: Oat milk $0.75 · Extra shot $1.25$4
  • Chocolat chaudVGFMelted 64%, milk, no powderAdd: Oat milk $0.75$5.25
  • Thé en feuillesVGGFFour leaf teas, in a pot, timed at the counterAdd: Oat milk $0.75$4
  • Jus d'orange presséVGGFPressed when you order it, not beforeAdd: Oat milk $0.75$5.50
  • Limonade maisonVGFLemon, a little honey, soda from the tapAdd: Oat milk $0.75$5

Walk in, order at the counter

Visit

One hours record drives the header, the footer, the card and this page. The dated exceptions are slips clipped to it, not a paragraph.

hours-card

src/components/hours-card.tsx

Hours

This week, and what changed

One record for the week, with the dated exceptions clipped on.

Mireille's corner shopfront with the terrace set out under a plain dark awning

Hours

Tuesday to Sunday · 34 seats inside, 12 on the terrace

One row is superseded by a slip this week.

Hours

Mon 14 Sep

  • Terrace closes for the season after Sunday 27 September

Posted 08:12 by Élise

  • MondayClosed
  • Tuesday07:30 – 17:00
  • Wednesday07:30 – 17:00
  • Thursday07:30 – 22:00
  • Friday07:30 – 22:00
  • Saturday09:00 – 22:00
  • Sunday09:00 – 17:00

Last order 21:15 at souper.

Today

Wednesday, 07:30 – 17:00

Lunch is being served until 14:30.

  • Terraceslip · 11 May to 15 October, weather allowing

Twelve seats under the awning, through the same level door

See the whole card

Between services 11:00 to 11:30, Tuesday to Friday.

house-rules

src/components/house-rules.tsx

House rules

Laptops, dogs, the terrace

The laptop answer changes with the service, so it is a table.

Dogs
Terrace only, and there is a water bowl by the door
Terrace
Twelve seats, first come, no bookings. In rain the chairs come in and the room takes everybody.
Children
High chairs, two of them, and a changing table in the washroom
Larger groups
Parties of 7 or more by enquiry. Book a table

Laptops

Laptops away until 14:30

  • Breakfast07:30 – 11:00Laptops welcome
  • Brunch09:00 – 15:00Laptops away
  • Lunch11:30 – 14:30Laptops away
  • Goûter14:30 – 17:00Laptops welcome
  • Souper17:30 – 22:00Laptops away

Why the rule changes with the hour

Thirty-four seats and two rushes a day. Between 11:30 and 14:30, and again from 17:30, a table with a laptop on it is a table nobody can eat at. Outside those hours the wifi is free, the plugs are along the banquette, and nobody will hurry you.

find-us

src/components/find-us.tsx

Getting in

6340 rue Beaubien Est

Montréal, Québec H1M 1A9

The corner door on Beaubien, level with the pavement, no bell.

Bus
The 18 Beaubien stops at 6e Avenue, thirty metres from the door
Metro
Beaubien, orange line, is a twelve-minute walk or two stops west
Parking
Free on 6e Avenue; the Beaubien side is metered until 21:00
Bikes
Two racks under the awning and a Bixi stand at 8e Avenue
Door
One door on Beaubien, 86cm wide, level with the pavement
Washroom
Ground floor, 80cm door, grab rail, no step from the room
Room
Tables at 74cm with room to take a chair away at two of them
The dining room seen from behind the counter before service, chairs at bare tables

Address

Mireille6340 rue Beaubien EstMontréal, Québec H1M 1A9

The coordinates, for a map app that wants numbers

45.5761, -73.5556

notices

src/components/notices.tsx

The one route a buyer edits rather than deletes. The six fields /book collects, what each is for and how long it is kept, then five closed native disclosures. Every answer is written against THIS form and THIS build: change a field, add a third-party script, or turn the booking widget on, and the page is wrong until site.privacy and this section are edited.

Notices

Personal information, and what we do with it

What the enquiry form collects, and who reads it.

No third-party script runs here, so there is nothing to consent to.

Updated 2026-09-16

The enquiry form

6 fields · kept for 12 months

  • What is this aboutWhich of the two enquiries this is, so the right person reads it
  • Your nameSo the reply is addressed to somebody
  • EmailThe only way we answer. Nothing else is ever sent to it
  • How many peopleWhether the room can take the party, before we say yes
  • Preferred dateOptional. It tells us which service you mean
  • Anything we should knowWhatever you choose to tell us. Please leave out health details

Who to ask

Responsible
Élise Tremblay. Owner-chef, and the person responsible for personal information.
Retention
12 months, then deleted

What the café does with what you send

Élise reads the enquiry and answers by email, usually the same day. It is used for that and for nothing else: no newsletter, no list, no advertising. Nothing is kept at the counter except the booking itself, written in the book, without your address.

Seeing it, correcting it, or having it deleted

You may ask to see what we hold, have it corrected, have it deleted, and withdraw your consent at any time. One email does all four: vieprivee@mireille.example.

Who else sees it

Nobody. The enquiry reaches the café's own mailbox and is not passed on, sold, or used for marketing. The site runs no analytics, no advertising and no third-party script.

Cookies, and why there is no banner

The site keeps one thing in your browser: your choice of the light or the dark mode. It stays on your device, we never read it, and it is shared with nobody. There is nothing else to consent to.

Allergies, and what we can promise

Tell us about an allergy before you order and we will tell you what is in the dish. One kitchen, shared pans, no separate fryer.

This page ships as a working shape, filled in for a fictional café. Replace every name, address and answer with your client's own, and have somebody qualified read it before launch. It is not legal advice.

Book

Two services take a table and the rest of the day is walk-in. The provider seam ships in all three of its modes; the demo runs on link.

booking-policy

src/components/booking-policy.tsx

Booking

What takes a booking, and what does not

Two services take a table; the rest of the day is walk-in.

Walk in for everything else, every day we are open.

Hours, access and the terrace slip

Book a table

5 services · 2 take a booking

Bookings: souper. Everything else is walk-in.

Booking

Mon 14 Sep

  • Weekend brunch is walk-in only until 27 September; terrace tables go first

Posted 08:20 by Élise

  • Le petit-déjeuner07:30 – 11:00Walk in, order at the counter
  • Le brunch09:00 – 15:00slip · 1Book online, up to 6
  • Le midi11:30 – 14:30Walk in, order at the counter
  • Le goûter14:30 – 17:00Walk in, order at the counter
  • Le souper17:30 – 22:00Book online, up to 6; last order 21:15

Before you book

Tables of up to 6
Larger than that and the partner cannot seat it in one go.
Larger groups
7 or more by enquiry. Send the enquiry
Late
A booked table is held for 15 minutes.

booking-seam

src/components/booking-seam.tsx

site.booking.mode picks the branch: provider embeds the partner, link sends the reader out, none says walk-in only. The link renders under the container in every case.

Booking a table

Our reservation partner takes the table; we keep the record.

Book with our partnerwww.opentable.example

We show no live availability; the partner's page has the times.

A booked table is held for 15 minutes. After that the table goes to whoever is at the door.

A two-top laid for supper with a carafe and a lit candle

Larger parties, and vouchers

One note for a larger party or a voucher, answered by email.

7 or more for a group

Or write to groupes@mireille.example

What you send reaches the café by email and is kept nowhere else. Nothing is held until we answer.

Larger groups
7 or more eat from a shorter card, which comes back with the answer. Tables of 6 and under go through the partner.
Gift vouchers
Written by hand at the counter, in any amount, no expiry.

enquiry-form

src/components/enquiry-form.tsx

The theme's only form. Empty submit gives a role=alert summary that takes focus and links to each control; one bad field gives no summary and focus to the control itself. Values survive a rejected submit.

7 or more for a group

Or write to groupes@mireille.example

What you send reaches the café by email and is kept nowhere else. Nothing is held until we answer.

Handover

The editing guide, which is deleted before launch. It maps every region of the site to the field behind it and to the WordPress screen it becomes.

handover-map

src/components/handover-map.tsx

Framed with bands={false}: on its own route each key is printed over the live band it names, and those six sections are already framed above.

The home page

Where each region comes from

Every band is stamped, then rendered underneath its own stamp.

Every band below is the section itself, rendered at the instant the card was.

The header bar

The wordmark, the three routes, and whether the café is open right now.

In this build

  • src/fixtures/mireille.ts

    site.name · site.nav[].label, .href

  • src/components/site-header.tsx

    the bar's structure and the quiet action

  • src/fixtures/derive.ts

    openStatus(), derived from the dayparts and never typed

In WordPress

  • parts/header.html

    Site Editor › Patterns › Template parts › Header

  • Settings › General

    Site title, and the Site Logo block

  • Site Editor › Navigation

    the three routes

  • carafe/open-status

    a dynamic block; change the hours, not the sentence

The photograph and its words

The counter at whatever hour it is, with the daypart's own clause over it.

In this build

  • mireille.ts › dayparts[i].image

    key and alt: the plate for this service

  • mireille.ts › site.hero

    the fallback plate, used when nothing is being served

  • src/assets/index.ts

    focal: the measured crop, and the one the hero uses

  • not authored

    the heading is built from dayparts[i].label, .start and .end

In WordPress

  • carafe/hero-served-now

    core/cover with its native focal point

  • Settings › Café › Dayparts

    Photograph and focal, per service. This is where the hero changes

  • Site Editor › Templates › Front page

    the Cover block, for the fallback plate

  • carafe/served-heading

    the derived H1; it has no text to lose on import

ManualThe overlay's contrast is measured against the crop, not guessed. Replace a counter plate with a lighter one and the white type over it stops passing AA; WordPress has no equivalent of src/assets/measure-overlay.ts, so the check stays a person's job at the port.

The card, and today's slips

The service being served, its dishes, and the dated paper clipped over it.

In this build

  • mireille.ts › menuItems[]

    name, description, priceCents, dietary, dayparts, section, addOns

  • mireille.ts › menuSections[]

    label and order: the order the card prints in

  • mireille.ts › slips[]

    kind, target, replaces, validFrom, validTo, lines, author

In WordPress

  • carafe/card-served-now

    the carafe/card block, target daypart:now

  • Menu items › the dish

    Title, Excerpt, Dish details, Menu sections

  • Menu sections › the term

    Order

  • Day slips › Add new

    Kind, Target, Valid from and to, Lines

On the grinder

Today's espresso and filter, and what came out of the oven when.

In this build

  • mireille.ts › site.coffee

    espresso and filter: roaster, origin, process, since

  • mireille.ts › site.bakes[]

    item, batches[], usuallyGone

  • src/assets/index.ts

    place/grinder, the plate beside it

In WordPress

  • carafe/on-the-grinder

    core/image plus carafe/coffee and carafe/bakes

  • Settings › Café › Coffee

    the two bags on today

  • Settings › Café › Bakes

    the batches and the hour each is usually gone

Getting here

The address, today's hours, the house rules, and the terrace exception.

In this build

  • mireille.ts › site

    address, geo, transit, access, phone, seats, terrace

  • mireille.ts › site.houseRules

    dogs, and laptops per daypart

  • mireille.ts › hoursRows[]

    key, label, value. The one hours record

  • src/components/ui/directions.ts

    the directions link, built from site.geo

In WordPress

  • carafe/visit-strip

    carafe/hours-today and the directions button

  • Settings › Café

    Address, Geo, Transit, Access, Phone, Terrace, House rules

  • Settings › Café › Dayparts

    the hours themselves; every surface reads them

The footer

The week, the freshness stamp, the address, and the two demo links.

In this build

  • mireille.ts › hoursRows[]

    the week, minus the terrace row

  • derive.ts › cardChangedAt()

    the newest menuItems[].modified, derived and never typed

  • mireille.ts › site.taxNote

    the line under the prices

  • mireille.ts › site.demoNav

    this page and /components. Delete both before launch

In WordPress

  • parts/footer.html

    Site Editor › Patterns › Template parts › Footer

  • carafe/card-changed

    the newest post_modified over menu_item

  • Settings › Café

    Address, Phone, Email, Tax note

The menu card

One row, part by part

The same row every service on the card is built from.

A bowl of soup, a croque cut in half and a lentil salad on the zinc, cutlery mid-use
Le midi11:30 – 14:30

Soupes & salades

  • Salade de lentillesVGFNPuy lentils, roast beets, chèvre, walnutsGreen lentils with roast beetroot, crumbled goat's cheese and walnuts$16
  • Soupe du jourcourge rôtie et pomme, graines grillées · VG$9
  • Tartine aux champignonsVOyster mushrooms, crème fraîche, chives, ryeslip · 2$15

Marks: V Vegetarian · GF No gluten · N Contains nuts

Taxes (TPS/TVQ) added at the till

Drawn from mireille.ts at the fixture instant. The stamped number on the struck row is the one today’s card prints.

The daypart band

Each service opens on its own counter photograph, with its French name.

In this build

  • mireille.ts › dayparts[i]

    cardLabel, days, start, end, lastOrder, image

In WordPress

  • Settings › Café › Dayparts

    the whole row, including the photograph

The section heading

The card's own divisions, in the order the kitchen reads them.

In this build

  • mireille.ts › menuSections[]

    label, order

  • mireille.ts › menuItems[i].section

    which division a dish prints under

In WordPress

  • Menu sections

    a non-hierarchical taxonomy; term title and Order

  • Menu items › the dish

    the Menu sections box

A dish row

The name, the line under it, the diet marks, the price, and the plate.

In this build

  • mireille.ts › menuItems[i]

    name, description, dietary[], priceCents, addOns[]

  • menuItems[i].image.key

    the plate, by key and with no extension

  • src/assets/index.ts

    focal: where the 96px thumbnail crops to

In WordPress

  • Menu items › the dish

    Title, Excerpt, Featured image

  • Dish details

    Price, Dietary, Dayparts, Add-ons, Focal point

A row the slip writes

Soupe du jour ships with no description; the day's special supplies it.

In this build

  • menuItems[i].description

    null. A printed card cannot know today's soup

  • slips[] › kind: special

    lines[].text, lines[].priceCents

In WordPress

  • Menu items › the dish

    leave Excerpt empty

  • Day slips › Add new

    Kind: Today. Target: the daypart

A struck row

A replaces slip strikes the row, stamps its number, and hides its plate.

In this build

  • slips[] › kind: replaces

    replaces: the menuItems id whose row it crosses out

  • nothing on the item changes

    delete the slip or let it expire and the row is back

In WordPress

  • Day slips › Add new

    Kind: Instead. Target: the daypart. Replaces: the dish

  • Day slips › the slip

    Trash it, or move Valid to into the past

The card foot

Every mark used on this card, expanded once, and where tax is added.

In this build

  • derived from the rows

    the key lists only the marks this card prints

  • strings.ts › dietary

    V, VG, GF, DF, N. The future carafe text domain

  • mireille.ts › site.taxNote

    the tax line

In WordPress

  • carafe/dietary-key

    built from the rows the card printed

  • Settings › Café

    Tax note

handover-tasks

src/components/handover-tasks.tsx

What a client does

The four editing tasks

The four the buyer research puts in front of an operator.

Left is this repository. Right is the block theme and its companion plugin.

Task 1 of 4

Update a dish

Its name, the line under it, its diet marks and which services it is on.

In this build

  1. 1Find the item by its id.src/fixtures/mireille.ts › menuItems[]
  2. 2Edit the French name and the English line under it.name · description
  3. 3Set the marks and the division it prints under.dietary[] · section
  4. 4List every service it is served at.dayparts[]
  5. 5Move the date, so the footer's freshness stamp moves with it.modified
  6. 6Run the fixture check; it refuses a section or daypart that does not exist.bun themes/carafe/src/fixtures/check.ts

In WordPress

  1. 1Open the dish.Menu items › the dish
  2. 2The title is the French name; the excerpt is the line under it.Title · Excerpt
  3. 3Set the marks and the services.Dish details › Dietary, Dayparts
  4. 4Pick one term.Menu sections
  5. 5Update. The stamp reads post_modified, so it moves on its own.Update

Task 2 of 4

Change a price

For good, or for one service only, without touching the printed card.

In this build

  1. 1Edit the price in cents; 1700 prints as $17 and 425 as $4.25.menuItems[i].priceCents
  2. 2Add-ons carry their own.menuItems[i].addOns[j].priceCents
  3. 3For today only, add a slip instead and leave the item alone.slips[] › kind: "replaces"
  4. 4Put the new price on the slip's line and close the window.lines[].priceCents · validFrom · validTo

In WordPress

  1. 1Open the dish and set the price.Menu items › the dish › Dish details › Price
  2. 2Add-ons are rows in the same panel.Dish details › Add-ons
  3. 3For today only, post a slip instead.Day slips › Add new › Kind: Instead
  4. 4Give the line its own price and its valid window.Lines · Valid from · Valid to

Task 3 of 4

Replace an image

A new plate, and the point in it the crop has to keep.

In this build

  1. 1Put the file beside the others.src/assets/dishes/
  2. 2Add its static import and its row to the register.src/assets/index.ts › PLATES
  3. 3Record the point the crop must hold; it is measured, not guessed.PLATES[key].focal
  4. 4Point the item at the key, with no file extension on it.menuItems[i].image.key
  5. 5For a counter plate, re-measure the overlay before it carries type.bun themes/carafe/src/assets/measure-overlay.ts

In WordPress

  1. 1Set the dish's photograph.Menu items › the dish › Featured image
  2. 2Drag the crop point onto the food.Dish details › Focal point
  3. 3A service's counter plate is set with the service.Settings › Café › Dayparts › Photograph
  4. 4The hero fallback is the Cover block's own image and focal point.Site Editor › Templates › Front page

ManualThe hero has no scrim: its white type is legible because every counter plate was measured dark where the words land. Core has no equivalent check, so a replacement counter plate is somebody's eye at the port. A dish thumbnail is unaffected; it never carries type.

Task 4 of 4

Point a visitor at a reservation

Which services take a booking, and where the button sends them.

In this build

  1. 1Set the mode; the demo ships "link".mireille.ts › site.booking.mode
  2. 2Give it the café's own booking page.site.booking.url
  3. 3Mark which services take a booking at all.dayparts[i].bookable
  4. 4Set where one table ends and a group enquiry begins.site.booking.partyMax · groupMin · groupEmail
  5. 5A live policy slip removes a service from the booking line without editing any of this.slips[] › kind: "policy"

In WordPress

  1. 1Set the mode, the provider and the URL.Settings › Café › Booking
  2. 2Set the party limits and the group address.Booking › Party max, Group min, Group email
  3. 3Mark the bookable services.Settings › Café › Dayparts › Bookable
  4. 4The header's action is a button in the header part.Site Editor › Patterns › Template parts › Header

UnverifiedOpenTable is the one documented adapter and its current embed requirements have not been confirmed. Until they are, the demo ships the link mode, the provider path is a documented seam, and no listing copy promises compatibility.

handover-slips

src/components/handover-slips.tsx

Every morning

Posting today’s change

Five kinds of slip, and what each one does to the card.

Every slip carries a window. Outside it the card underneath is the truth again.

Today

Wed 16 Sep

  • Soupe: courge rôtie et pomme, graines grillées · VG$9

Posted 07:52 by Élise

special

Post today's soup

Adds a line to the card for its window, and fills the row whose description was left empty.

In this build

  • kind: "special"

    target: { type: "daypart", id }, the card it clips to

  • lines[]

    text, and priceCents when the line carries a price

In WordPress

  • Day slips › Add new

    Kind: Today

  • Target

    Daypart, then the service

Instead

Wed 16 Sep

  • Tomates du marché Jean-Talon instead, last of the season$15

Posted 07:52 by Élise

replaces

Strike a dish, or re-price it for one day

Crosses the row out, stamps its number in the price column, and hides that dish's photograph.

In this build

  • kind: "replaces"

    replaces: the menuItems id whose row it strikes

  • lines[].priceCents

    a price here is today's price; the item's own does not move

In WordPress

  • Day slips › Add new

    Kind: Instead

  • Target · Replaces

    the service, then the dish. The list is filtered to it

Hours

Mon 14 Sep

  • Terrace closes for the season after Sunday 27 September

Posted 08:12 by Élise

hours

Close the terrace for the season

Strikes one row of the hours record, on the home page and on the hours card.

In this build

  • kind: "hours"

    target: { type: "hours", id } and rowKey

  • rowKey

    mon to sun, or terrace. A key the hours record holds

In WordPress

  • Day slips › Add new

    Kind: Hours

  • Target

    Hours, then the row

Booking

Mon 14 Sep

  • Weekend brunch is walk-in only until 27 September; terrace tables go first

Posted 08:20 by Élise

policy

Make a service walk-in only

Strikes its booking row, and the home page's booking line stops naming it.

In this build

  • kind: "policy"

    target: { type: "booking", id } and rowKey

  • rowKey

    a bookable daypart; the build refuses one that takes no bookings

In WordPress

  • Day slips › Add new

    Kind: Booking

  • Target

    Booking, then the service

Note

Wed 16 Sep

  • Kouign-amann: second batch out at 14:30

Posted 07:05 by Élise

notice

Say something that changes no row

A line on the card, and nothing struck. The second batch, the last of the season.

In this build

  • kind: "notice"

    target: the card it belongs on

  • lines[]

    text; no price, no replaces, no rowKey

In WordPress

  • Day slips › Add new

    Kind: Note

  • Target

    Daypart, then the service

What the build refuses to draw

5 checks

Every slip in the fixture is validated once, when the module is imported, so a slip that points at nothing cannot reach a page: the page never renders. The port runs the same checks in a rest_pre_insert_day_slip filter, so the editor refuses the row instead.

  1. 1A target that does not exist. A slip renders only on its target's card, so an orphan is a change nobody can read.
  2. 2A replaces slip whose dish is not served in the target service. The row it crosses out is not on that card.
  3. 3An hours slip naming a row the hours record does not have.
  4. 4A policy slip naming a service the café takes no bookings for.
  5. 5A window that ends before it starts, which is a change nobody made.

handover-limits

src/components/handover-limits.tsx

Before you promise it

What the port does not do

8 boundaries, stated before a client is told otherwise.

Without the companion plugin, there is no card

Plugin
The dishes, the sections and the slips are custom post types, a taxonomy and their meta, and those are plugin territory rather than theme territory. The block theme ships the templates, the parts, the patterns, theme.json and the style variations; carafe-menu ships the content model, the options screen, the dynamic blocks and the slip validation. Deactivate the plugin and the theme degrades to core-block patterns, a Group with a paragraph and a core Table for the card, so a client who stops paying still has a site. They do not still have slips.

Dark mode is not a core feature

Static CSS
Core has no dark mode, so the port carries one static stylesheet enqueued front end and editor, which remaps every --wp--preset--color--* to its dark value under prefers-color-scheme and again under [data-theme="dark"], and a toggle that sets the attribute. It is not editable in Styles and it is not a setting a client can break. A new colour added in Styles will not have a dark value until somebody adds one to that file.

Both languages ship; the document head is still yours

en-CA · fr-CA
Every string a visitor meets is authored in both languages — the chrome in src/fixtures/strings.ts, which becomes the carafe text domain with a .pot, and the content as { fr, en } pairs in src/fixtures/mireille.ts. site.locale picks the half a build prints, every route’s root element carries it as lang, and French runs inside an English page are marked so a screen reader does not read “Œufs” with English phonemes. English is the default and French is prefixed, and the switch across is in the header on every page. All five public routes are built in both languages and every internal link resolves into the one being read, so a French reader never lands on the English card. What is not here is the <link rel="alternate"> pair: in React the document head belongs to whoever mounts the theme, so the hreflang lines go in your own metadata export and README § Two languages has them verbatim. Name only a page that exists — the French entry’s routes list is what the switch and the alternates both read, and it is the field to edit when a route joins or leaves one language. The HTML edition is single-locale English by the same ruling. At the WordPress port the pairs become one multilingual plugin’s translated records, chosen and tested there.

The booking provider is a seam, not an integration

Unverified
One adapter is documented, OpenTable, as an embed container rendered only when the mode is provider. Its current embed requirements have not been confirmed, so the demo ships mode: "link" and sends a visitor to the café’s existing booking page. The theme never shows live availability and never confirms a table: a website that says a table is held, when nothing held it, is the one failure a restaurant cannot absorb.

Flipping that field changes what the site is. The provider branch mounts a third-party loader, which is a script, which sets cookies. Today this build runs no third-party script at all, which is why /notices can say there is nothing to consent to and why there is no cookie banner. Turn the widget on and that sentence stops being true: under Law 25 the consent obligations attach, and the notice has to be rewritten before the site goes live, not after.

The order link is a seam, and there is still no cart

site.order
site.order.mode has the same three branches as the booking seam: direct for the café’s own ordering page, partner for a named marketplace, and none, which draws nothing. That is the whole of it. There is no basket, no quantity on a dish row, no checkout, no payment and no delivery, and the theme claims no preparation time and no queue because it cannot know either. If a client wants ordering rather than a link to ordering, that is a commerce plugin and a different quote.

/notices is edited before launch, never deleted

Law 25
It is the one route here that is not a demo. Québec’s Law 25 has required a published, plain-language privacy notice and a named, reachable person responsible of any business collecting personal information through a website since 22 September 2023, with no size floor, and /book collects six fields. The page is written against this form and this build: change the fields, add an analytics script, or turn the booking widget on, and the notice is wrong until somebody edits site.privacy and the answers on src/components/notices.tsx. It is not legal advice and it is not a substitute for a client’s own; it is the shape, filled in.

This page and /components do not become templates

Deleted
Both are demo routes. Delete src/app/handover.tsx, src/app/components.tsx and the two site.demoNav rows before a launch, and nothing links to either. Their content does not vanish at the port: this map becomes the plugin’s Appearance › Café guide screen, and every pattern region carries a metadata.name matching the label used here, so the Site Editor’s List View names regions exactly as this page does.

A cached page cannot freeze what is being served

Interactivity API
The card prints the day’s schedule and every slip’s window into data-wp-context, and a view store re-derives the served tab, the chip and slip visibility on load. So a page held in a cache for six hours still shows the right service. With JavaScript off the answer is the full-day card, which is also what this build renders when no clock is passed and what the not-found page shows.

handover-annotation

src/components/handover-annotation.tsx

The four parts that route is drawn with. The stamp is the slip's own stamp: one marking language on the site, not two.

Today’s card

The service being served, with the day’s slips clipped on.

In this build

  • src/fixtures/mireille.ts

    MENU_ITEMS[].name, .priceCents

In WordPress

  • Menu items → Edit

    Title, Price

PortThe card’s order is the service’s order, which WordPress keeps as menu order rather than as a date.

Not found

The error page keeps its job: the closed counter, every service and its hours, and the way back to the card.

not-found

src/components/not-found.tsx

Rendered clock-free (now = null): a page built once and cached cannot claim an hour, so it prints the whole day instead of one service.

The counter wiped down at closing with the chairs upended behind it

Page not found

That page is not on the card

Here is what we serve, and how to find us

52 dishes across 5 times of day, 34 seats inside.

What we serve

52 dishes · 10 sections

  • Le petit-déjeuner07:30 – 11:00

    Tuesday to Friday · 12 dishes

  • Le brunch09:00 – 15:00

    Saturday to Sunday · 14 dishes

  • Le midi11:30 – 14:30

    Tuesday to Friday · 9 dishes

  • Le goûter14:30 – 17:00

    Tuesday to Sunday · 9 dishes

  • Le souper17:30 – 22:00

    Thursday to Saturday · 10 dishes

Getting here

Transit
The 18 Beaubien bus stops at the corner
Access
Step-free entrance on Beaubien; one accessible washroom
Laptops
Laptops welcome outside the lunch and evening rush
Dogs
Terrace only, and there is a water bowl by the door

The signature

A dated sheet clipped over a card, and the row underneath it that the sheet supersedes. Five kinds, one of which strikes a row out.

ui/slip

src/components/ui/slip.tsx

All five kinds, at the fixture instant. An expired slip and a future one are in the record and neither renders — s-06 lapsed on 15 September, s-07 starts on 12 October.

special

Today

Wed 16 Sep

  • Soupe: courge rôtie et pomme, graines grillées · VG$9

Posted 07:52 by Élise

replaces

Instead

Wed 16 Sep

  • Tomates du marché Jean-Talon instead, last of the season$15

Posted 07:52 by Élise

notice

Note

Wed 16 Sep

  • Kouign-amann: second batch out at 14:30

Posted 07:05 by Élise

hours

Hours

Mon 14 Sep

  • Terrace closes for the season after Sunday 27 September

Posted 08:12 by Élise

policy

Booking

Mon 14 Sep

  • Weekend brunch is walk-in only until 27 September; terrace tables go first

Posted 08:20 by Élise

ui/clipped-card

src/components/ui/clipped-card.tsx

The whole device: a card, its slip lane, and a row struck out by one of the sheets in that lane. Three sheets is the density the lane was measured at.

Le midi

Wednesday 16 September · 11:30 – 14:30

Marks: V Vegetarian · VG Vegan · GF No gluten

Instead

Wed 16 Sep

  • Tomates du marché Jean-Talon instead, last of the season$15

Posted 07:52 by Élise

  • Soupe du jourCourge rôtie et pomme, graines grillées$9
  • Croque MireilleHam, Comté, béchamel, house sourdough$17
  • Tartine aux champignonsVOyster mushrooms, crème fraîche, chives, ryeslip · 2$15

Le midi

Wednesday 16 September · 11:30 – 14:30

Today

Wed 16 Sep

  • Soupe: courge rôtie et pomme, graines grillées · VG$9

Posted 07:52 by Élise

Instead

Wed 16 Sep

  • Tomates du marché Jean-Talon instead, last of the season$15

Posted 07:52 by Élise

Note

Wed 16 Sep

  • Kouign-amann: second batch out at 14:30

Posted 07:05 by Élise

  • Croque MireilleHam, Comté, béchamel, house sourdough$17
  • Salade de lentillesVGFNPuy lentils, roast beets, chèvre, walnuts$16

The variant axes

Two axes, both states of each. The fiction picks one per route; the buyer is buying both, so both are shown and both are captured.

ui/item-row

src/components/ui/item-row.tsx

The item-rows axis. Type-only is the default on /, thumbnails on /menu. An item with no photograph renders the type-only row on either, and a superseded row hides its plate.

type-only · default on /

  • Croque MireilleHam, Comté, béchamel, house sourdough$17
  • Salade de lentillesVGFNPuy lentils, roast beets, chèvre, walnuts$16
  • Magret de canard, chou rouge braisé et poires pochées au cidreGFDFDuck breast rested pink, red cabbage, cider-poached pear$34

with thumbnail · default on /menu

  • Croque MireilleHam, Comté, béchamel, house sourdough$17
  • Salade de lentillesVGFNPuy lentils, roast beets, chèvre, walnuts$16
  • Magret de canard, chou rouge braisé et poires pochées au cidreGFDFDuck breast rested pink, red cabbage, cider-poached pear$34

superseded · struck, stamped, and the plate hidden

  • Tartine aux champignonsVOyster mushrooms, crème fraîche, chives, ryeAdd: Egg $2.50$15
  • Tartine aux champignonsVOyster mushrooms, crème fraîche, chives, ryeslip · 2$15

menu layout

The second axis is composition, so both of its states are sections: switcher is served-card, one card with folder tabs opening on the service being served, and stacked is menu-dayparts, every service down the page on its own counter band. Both are framed above.

Primitives

27 parts, each installable on its own. Seven are tt-ui 1.5.1 vendored into the theme and restyled; the rest are the theme's own.

ui

src/components/ui/

The chrome, the actions, the card's marks and the controls, at the sizes they ship at.

chrome · logo, nav, mode toggle, status

MireilleOpen until 17:00Closed · opens Tuesday at 07:30

actions · filled, quiet, ghost, disabled, pending

Get directionsBook a tableSee the whole cardServed now · Wednesday 16 September

the card's marks · dietary letters, the key, the slip stamp

VGF

Marks: V Vegetarian · VG Vegan · GF No gluten

slip · 2the mark a superseded row carries

controls · text, number, select, multi-line

Up to six at one table

ui/copy-line · press to copy, select-and-say-so when the clipboard is refused

6340 rue Beaubien Est
Montréal

ui/card-panel

src/components/ui/card-panel.tsx

Le midi

Tuesday to Friday · 11:30 – 14:30

  • Tartine aux champignonsVOyster mushrooms, crème fraîche, chives, ryeAdd: Egg $2.50$15
  • Soupe du jour$9
  • Salade de lentillesVGFNPuy lentils, roast beets, chèvre, walnuts$16

ui/hours-row

src/components/ui/hours-row.tsx

Today is marked, and a row an hours slip supersedes is struck with the sheet's own number in the value column.

  • MondayClosed
  • Tuesday07:30 – 17:00
  • Wednesday07:30 – 17:00
  • Thursday07:30 – 22:00
  • Terraceslip · 11 May to 15 October, weather allowing

ui/tabs

src/components/ui/tabs.tsx

Arrow keys move the selection and the panel follows. Souper runs Thursday to Saturday, so on the fixture's Wednesday its tab is present and not selectable — a state of this switcher, not a specimen.

Le midi, Tuesday to Friday, 11:30 to 14:30.

Le souper is not on tonight.

ui/accordion-disclosure

src/components/ui/accordion-disclosure.tsx

Enter or Space opens the panel the summary names; the heading stays a heading, so a screen reader's heading list is still the page's outline.

Allergens and substitutions

5 marks
Every dish carries its marks on the card. Tell whoever takes your order and the kitchen will say what it can change; the fryer is shared, so nothing is guaranteed free of gluten.

Tables of seven or more

By email
Write to the café with the date, the time and how many of you there are. Groups eat from a shorter card, which is sent back with the confirmation.

ui/field

src/components/ui/field.tsx

Every state the control ships in. The error is bound to the input, so it is announced rather than merely printed near it.

at rest

Up to six at one table

invalid · the message is bound to the control

Tables here seat six; write to us for seven or more

valid

We will write back the same day

read-only · focusable, copyable, not editable

Souper is Thursday to Saturday

disabled · out of the tab order

Closed for the season after 27 September

ui/form-error-summary

src/components/ui/form-error-summary.tsx

role=alert, focused after a rejected submit, one link per failing control. It appears only when more than one field failed; a single failure sends focus straight to that control.

ui/sheet-drawer

src/components/ui/sheet-drawer.tsx · src/components/ui/mobile-nav.tsx

The theme's only modal layer, and the narrow-width navigation built on it. Enter opens it, focus lands inside and is trapped, Escape closes it and returns focus to the trigger; the page behind it does not scroll.

the real trigger

ui/ground

src/components/ui/ground.tsx · src/assets/drawings/

The two ground drawings, on the page they are drawn for. Each is a grayscale luminance mask over a --texture-ink layer, so the file carries no colour at all and one asset serves both modes and all three presets. Switch the mode above and the ink inverts with the token; the drawing does not change.

counter-rings · behind the served card on /

maple-drift · behind the visit strip on / and the house rules on /visit

the arithmetic

The alpha is a token, per mode, and it is measured rather than chosen: 6.5% on the light page for 3.4 to 4.5 L* of relief, 12.8% on the dark page for 8.3 to 10.4, because a near-black ground is compressive and cannot be darkened. Four other surfaces were argued to zero — every band that carries a photograph, the whole of /menu, the booking form, and the footer, which appears on all seven routes and would have made this wallpaper.

ui/dropdown-menu

src/components/ui/dropdown-menu.tsx · src/components/ui/locale-switch.tsx

The theme's only menu, and the one thing it opens: the language switch in the header. Arrows move between the rows, Home and End jump to the ends, a letter jumps to the row starting with it, Enter chooses, Tab and Escape close and put focus back on the trigger. Each row is a real link to the same page in the other language, so it works with JavaScript off.

the real trigger