Showcase
Real sites.
One file each.
Each site below is one .kaury file and a folder of pictures. It is running in the page: scroll inside, click, send its form. Then read its code.
Strength gym · dark, loud
Forge
A headline over a shaded photograph, a ticker, pictures in black and white, prices that follow a choice.
shade
grayscale
ticker
components
232 lines of Kaury
Read the code of Forge
// Forge: the home page of a strength gym.
// Read as: a landing page for people choosing where to train, led by oversized type
// on a dark lifting floor, in off-black, bone and one acid green. Variance 8, motion 7, density 4.
site "Forge"
colors accent #C8F03C, night #0C0D0A, coal #171915, bone #ECEAE2
fonts "General Sans", "Tanker"
lang "en"
url "https://forge.example"
style background night, color bone, radius 0, max-width 1320
seo "Forge, strength gym in Lausanne", "A lifting floor with twelve racks, small coached groups and open gym from six in the morning. Your first session is free."
state yearly = false
state sent = false
let words = ["Strength", "Conditioning", "Weightlifting", "Open gym", "Mobility", "Sleds and carries"]
let training = [
{ n: "01", name: "Strength", text: "Squat, press, deadlift. Eight people per coach and a written programme, twelve weeks at a time.", when: "Mon, Wed, Fri\n06:30 and 18:30" },
{ n: "02", name: "Conditioning", text: "Sleds, rowers and loaded carries. Forty minutes that leave nothing in the tank.", when: "Tue, Thu\n12:15 and 19:00" },
{ n: "03", name: "Weightlifting", text: "Snatch, clean and jerk on four competition platforms, coached lift by lift.", when: "Tue, Thu 18:00\nSat 10:00" },
{ n: "04", name: "Open gym", text: "Your programme, our floor. Chalk is allowed, dropping the bar too.", when: "Every day\n06:00 to 22:00" }
]
let plans = [
{ name: "Drop in", month: 25, year: 25, per: "per visit", text: "The whole floor for the day. No contract, no card on file.", points: ["Open gym, 06:00 to 22:00", "Showers and towels", "Chalk on the house"], main: false },
{ name: "Unlimited", month: 119, year: 99, per: "per month", text: "Every class and open gym, seven days a week.", points: ["All coached classes", "Open gym, 06:00 to 22:00", "A programme review every twelve weeks"], main: true },
{ name: "Coached", month: 189, year: 159, per: "per month", text: "Unlimited, plus one hour alone with your coach each week.", points: ["Everything in Unlimited", "Weekly one to one", "Your own programme"], main: false }
]
style title
weight 400, uppercase
style emphasis
upright, color accent
style logo
font "titles", size 30, weight 400, uppercase, tracking 1
style display
font "titles", size 216, line-height 0.86, tracking 0
tablet size 132
mobile size 76
style heading
font "titles", size 112, line-height 0.9, tracking 0
tablet size 84
mobile size 56
style lead
size 20, line-height 1.5, width 540
mobile size 18
style label
uppercase, tracking 2, size 12, weight 600, color muted
style quiet
background transparent, color bone, border 2 bone
hover background bone, color night
style dark
background night, color bone
hover background coal
style tab
background transparent, color bone, border 2 line, padding 9 20, size 15, weight 600
selected background bone, color night, border 2 bone
style line
padding 30 0, border-top 1 line, gap 24
hover move 10 0
style shot
grayscale
hover grayscale 0
style plan
background coal, padding 32, gap 14
mobile padding 24
style best
background accent, padding 32, gap 14
mobile padding 24
animation rise, 0.9s, smooth
from move 0 32
to move 0 0
animation slide, 32s, loop, linear
from move 0 0
to move -50% 0
animation widen, scroll
from grow 0.92, opacity 0.4
to grow 1, opacity 1
component Coach name role photo about
column gap 6
image photo, about, cover, height 500, shot
mobile height 420
spacer 8
subtitle name, level 3, font "titles", size 38, weight 400, uppercase, line-height 1
text role, label
page "/"
section header, sticky, padding 18 48
mobile padding 14 20
logo "Forge"
links Training, Coaches, Prices
button "Book a trial", small, to "#trial"
section hero, background "hall.jpg", shade 0.94 bottom, color bone, padding 0 48 56
mobile padding 0 20 40, shade 0.72
spacer 250
column gap 30, rise
title "Strong is\n*built* here.", display
row gap 32
text "Twelve racks, small coached groups and a floor that opens at six. No mirrors, no machines lined up in a row.", lead
row gap 12, margin 0 0 0 auto
mobile margin 0
button "Book a trial", large, to "#trial"
button "See the prices", large, quiet, to "#prices"
section band, background accent, padding 16 0, clip
row no-wrap, slide, full-width, gap 40
for w in words
text w, font "titles", size 30, uppercase, line-height 1
text "/", font "titles", size 30, line-height 1, opacity 0.45
for w in words
text w, font "titles", size 30, uppercase, line-height 1
text "/", font "titles", size 30, line-height 1, opacity 0.45
section training, padding 110 48 96
mobile padding 64 20 56
grid columns 1.4 1, gap 48
tablet columns 1, gap 20
title "Four ways\nunder the *bar*.", heading, level 2
text "Pick one or mix them. Every class is capped, so a coach sees every lift you make.", lead, middle
spacer 28
column gap 0
for t in training
grid columns 0.25 1.1 1.7 0.8, line
mobile columns 1, gap 8
text t.n, color accent, weight 600
subtitle t.name, level 3, font "titles", size 46, weight 400, uppercase, line-height 1
text t.text, color muted
text t.when, label, align right
mobile align left
section floor, padding 0 48 110
mobile padding 0 20 64
grid columns 1.7 1 1, gap 12
tablet columns 1
image "rack.jpg", "A power rack with a loaded barbell on the lifting floor", cover, height 460, shot, widen
mobile height 280
image "plate.jpg", "Close view of a forty-five pound plate on a barbell", cover, height 460, shot
mobile height 280
column gap 14, background coal, padding 32
title "620 square metres of iron.", level 2, font "titles", size 54, line-height 0.95
text "Twelve racks, four platforms, thirty metres of turf. One sauna, because you will want it.", color muted
section coaches, padding 0 48 110
mobile padding 0 20 64
title "Coaches who\nstill *compete*.", heading, level 2
spacer 24
grid 3 columns, gap 20
tablet columns 1, gap 36
Coach "Ines Moreau", "Head coach, strength", "ines.jpg", "Ines Moreau, in profile on the lifting floor"
column padding 72 0 0 0
tablet padding 0
Coach "Tom Keller", "Conditioning", "tom.jpg", "Tom Keller with a towel around his neck after a session"
Coach "Karim Haddad", "Weightlifting", "karim.jpg", "Karim Haddad sitting between the machines"
section prices, padding 0 48 110
mobile padding 0 20 64
row gap 20
title "Simple *prices*.", heading, level 2
row gap 8, margin 0 0 0 auto
mobile margin 0
button "Monthly", tab, selected (not yearly) -> yearly = false
button "Yearly", tab, selected yearly -> yearly = true
spacer 24
grid columns 1 1.25 1, gap 16
tablet columns 1
for p in plans
column plan, best p.main
text p.name, uppercase, tracking 2, size 13, weight 600
row gap 10
text "{if yearly then p.year else p.month}", font "titles", size 104, line-height 0.9
text "CHF\n{p.per}", size 14, line-height 1.3, margin auto 0 6 0
text p.text
column gap 0
for point in p.points
text point, padding 10 0, border-top 1 line, size 15
if p.main
button "Book a trial", dark, to "#trial", margin auto 0 0 0
else
button "Book a trial", quiet, to "#trial", margin auto 0 0 0
text "Yearly prices are per month, paid once a year. Students and under 25: 20 percent off.", color muted, size 15
section trial, background accent, padding 96 48
mobile padding 56 20
grid columns 1.15 1, gap 72
tablet columns 1, gap 32
column gap 20
title "Your first\nsession is\non us.", heading, level 2
text "Come an hour before a class. A coach shows you the floor, checks how you move and lifts with you.", lead
column gap 16, middle
if sent
title "See you on the floor, {name}.", level 3, font "titles", size 48, line-height 0.95
text "We write back today with a time."
else
form mail "hello@forge.example", subject "Trial session" -> sent = true
field name "Ada Lovelace", required, label "Name"
field email "ada@example.com", type email, required, label "Email"
select goal "Get stronger", "Get fitter", "Learn the lifts", "Come back from an injury", label "What brings you in"
button "Book a trial", large, dark
footer padding 56 48 40, gap 32, direction column
mobile padding 40 20 32
grid 3 columns, gap 32
tablet columns 1, gap 20
column gap 4
text "Forge", font "titles", size 30, uppercase, line-height 1
text "Strength gym, Lausanne", color muted
column gap 4
text "Rue de Genève 88, 1004 Lausanne"
text "Every day, 06:00 to 22:00", color muted
column gap 4
link "hello@forge.example" "mailto:hello@forge.example"
link "021 555 01 88" "tel:+41215550188"
divider
text "Made with Kaury", color muted, size 14
```
Perfume launch · cold luxury
Nuit
One object in three dimensions that follows the mouse, notes that change the picture when you point at them.
3D object
state
form by e-mail
146 lines of Kaury
Read the code of Nuit
// Nuit: the launch page of a niche perfume.
// Read as: a launch page for people who collect small-batch perfume, led by one object
// in three dimensions, in near-black, smoke and silver. Variance 7, motion 7, density 2.
site "Nuit"
colors accent #D5DBE0, night #0A0B0D, smoke #15171B, silver #D5DBE0
fonts "Switzer", "Melodrama"
lang "en"
url "https://nuit.example"
style background night, color silver, radius 2, max-width 1280
seo "Nuit Blanche, eau de parfum", "Iris, incense and cedar, made in Grasse in a single batch of five hundred numbered bottles. Released on 21 November."
state note = "iris"
state sent = false
let notes = [
{ id: "iris", name: "Iris", place: "The opening", text: "Cold and powdery, like silk taken out of a drawer. Pressed from roots that waited six years in Tuscany.", photo: "iris.jpg", about: "The petal of an iris seen very close, violet in the dark" },
{ id: "incense", name: "Incense", place: "The heart", text: "Olibanum from Oman, burnt slowly. The smoke of a church after everyone has left.", photo: "smoke.jpg", about: "A thin line of white smoke rising in the dark" },
{ id: "cedar", name: "Cedar", place: "What stays", text: "Dry Atlas cedar and a little vetiver. It is still on your coat the next evening.", photo: "cedar.jpg", about: "Fir trees fading into green fog" }
]
let shown = notes.find(n -> n.id == note)
let facts = [
{ name: "Concentration", value: "Eau de parfum, 22 percent" },
{ name: "Bottle", value: "50 ml, hand-cut glass" },
{ name: "Batch", value: "500 bottles, numbered 001 to 500" },
{ name: "Made in", value: "Grasse, by Hélène Aubry" },
{ name: "Price", value: "CHF 240" }
]
style title
weight 300
style emphasis
italic
style logo
font "titles", size 36, weight 400
style display
font "titles", size 176, line-height 0.86, tracking -3
tablet size 120
mobile size 76, tracking -1
style heading
font "titles", size 84, line-height 0.95, tracking -2
tablet size 64
mobile size 44, tracking -1
style lead
size 19, line-height 1.6, width 440
style label
uppercase, tracking 3, size 11, weight 500, color muted
style quiet
background transparent, color silver, border 1 silver
hover background silver, color night
style name
background transparent, color silver, font "titles", size 104, weight 300, line-height 1, padding 0, opacity 0.45
hover opacity 0.75
selected opacity 1
tablet size 72
mobile size 52
// the first words of the page move in but do not fade in: what fades in is read late
animation rise, 1.1s, smooth
from move 0 36
to move 0 0
page "/"
section header, padding 22 48
mobile padding 16 20
logo "Nuit"
links Notes, Bottle
button "Join the list", small, quiet, to "#list"
scene hero, height 86vh, particles dust, background night
light dramatic
object "gem", color silver, glass, size 1.45, position 1.7 0.1 0, alt "The bottle of Nuit Blanche, cut like a dark stone", fallback "bottle.jpg"
spin slow
follows mouse, smooth
column gap 26, rise, width 100%, max-width 1312, padding 0 16, align left
mobile padding 0
text "Eau de parfum, N°1", label
title "Nuit\n*Blanche*.", display
text "Iris, incense and cedar, for the hours when the city finally goes quiet.", lead
row gap 20
button "Join the list", large, to "#list"
text "Released 21 November.\n500 numbered bottles.", size 14, line-height 1.5, color muted
section notes, padding 120 48
mobile padding 72 20
grid columns 1 1, gap 72
tablet columns 1, gap 36
column gap 8, middle
text "Three notes, in the order you meet them", label
spacer 12
for n in notes
button n.name, name, selected (note == n.id) -> note = n.id
on hover -> note = n.id
spacer 20
text shown.place, label
text shown.text, lead
image shown.photo, shown.about, cover, height 640
mobile height 420
section batch, background "light.jpg", shade 0.45, padding 170 48, center
mobile padding 96 20
title "One batch. When it is gone, it is *gone*.", heading, level 2, width 900
text "We mix once a year, in November, and we do not restock.", lead, color silver
section bottle, padding 120 48
mobile padding 72 20
grid columns 0.8 1, gap 88
tablet columns 1, gap 40
image "bottle.jpg", "A dark glass bottle standing on a round table, lit from behind", cover, height 680
mobile height 440
column gap 28, middle
title "Fifty millilitres,\ncut by *hand*.", heading, level 2
text "The glass is blown in the Jura and cut so that no two bottles catch the light the same way. Yours carries its number under the base.", lead
column gap 0
for f in facts
row gap 16, padding 16 0, border-top 1 line
text f.name, label
text f.value, margin 0 0 0 auto
section list, background smoke, padding 130 48, center
mobile padding 80 20
if sent
title "You are on the list.", heading, level 2
text "We write once, on 21 November at nine in the morning.", lead
else
title "Be told *first*.", heading, level 2
text "The list opens the sale one hour before everyone else. One e-mail, then silence.", lead
spacer 8
form mail "list@nuit.example", subject "The list" -> sent = true
row gap 12, center
field email "ada@example.com", type email, required, label "Email", width 320
button "Join the list", large, margin auto 0 0 0
footer padding 40 48, gap 24
mobile padding 32 20
text "Nuit, Grasse"
link "Instagram" "https://www.instagram.com/"
text "Made with Kaury", color muted, margin 0 0 0 auto
```
Restaurant · light, warm
Chez Lina
Led by photography: a picture pinned over another, tabs that filter a menu, a card that stays in view.
pin
tabs
sticky
form by e-mail
221 lines of Kaury
Read the code of Chez Lina
// Chez Lina: the home page of a neighbourhood restaurant.
// Read as: a landing page for locals choosing where to eat tonight, led by photography,
// in olive, brick and paper. Variance 7, motion 5, density 3.
site "Chez Lina"
colors accent #A63C25, paper #EEF0E7, leaf #DCE3CD, ink #1C2118
fonts "Satoshi", "Cabinet Grotesk"
lang "en"
url "https://chezlina.example"
style background paper, color ink, radius 22, max-width 1240
seo "Chez Lina, Mediterranean kitchen in Vevey", "Seasonal plates to share and natural wines, on a stone terrace two steps from the lake. Open Tuesday to Sunday."
state service = "lunch"
state sent = false
let dishes = [
{ name: "Burrata, peach, basil", note: "From Puglia, with the first peaches of the valley", price: 18, when: "lunch" },
{ name: "Grilled octopus, chickpeas, lemon", note: "Cooked over vine cuttings", price: 26, when: "lunch" },
{ name: "Tagliolini, bottarga, zest", note: "Rolled every morning", price: 24, when: "lunch" },
{ name: "Sea bream, fennel, olive oil", note: "Whole, for one", price: 34, when: "lunch" },
{ name: "Artichokes, anchovy, breadcrumbs", note: "To share while you choose", price: 19, when: "dinner" },
{ name: "Lamb shoulder, harissa, mint", note: "For two, six hours in the oven", price: 76, when: "dinner" },
{ name: "Turbot on the bone, capers", note: "For two or three, carved at the table", price: 92, when: "dinner" },
{ name: "Olive oil cake, apricot", note: "Still warm", price: 11, when: "dinner" }
]
let shown = dishes.filter(d -> d.when == service)
let wines = [
{ name: "Chasselas sur lie", from: "Lavaux", glass: 9 },
{ name: "Gamay des collines", from: "Geneva", glass: 10 },
{ name: "Orange muscat", from: "Valais", glass: 12 }
]
let hours = [
{ days: "Tuesday to Saturday", time: "12:00 to 14:00, then 18:30 to 22:00" },
{ days: "Sunday", time: "12:00 to 15:00" },
{ days: "Monday", time: "Closed" }
]
style emphasis
italic, color accent
style display
font "titles", size 92, line-height 0.98, tracking -3, weight 800
tablet size 64
mobile size 46, tracking -1
style heading
font "titles", size 52, line-height 1.02, tracking -2, weight 800
mobile size 36
style lead
size 20, line-height 1.5, width 470
mobile size 18
style label
uppercase, tracking 2, size 12, weight 700, color accent
style quiet
background transparent, color ink, border 2 ink
hover background ink, color paper
style tab
background transparent, color ink, border 2 ink, padding 9 20, size 15, weight 600
selected background ink, color paper
style dish
padding 22 0, border-top 1 line, gap 24
title size 23
style tile
background white, padding 36, gap 12, radius 22
mobile padding 26
style tinted
background leaf, padding 36, gap 12, radius 22
mobile padding 26
style brick
background accent, padding 36, gap 12, radius 22
mobile padding 26
style wordmark
font "titles", size 230, line-height 0.82, tracking -9, weight 800, color leaf
tablet size 140
mobile size 76, tracking -3
animation rise, 0.9s, smooth
from move 0 28
to move 0 0
animation settle, scroll
from grow 0.94, opacity 0.5
to grow 1, opacity 1
page "/"
section header, sticky, padding 18 48
mobile padding 14 20
logo "Chez Lina"
links Menu, Kitchen, Find
button "Book a table", small, to "#book"
section hero, padding 40 48 96
mobile padding 24 20 64
grid columns 1 0.92, gap 72
tablet columns 1, gap 40
column gap 26, rise, middle
text "Mediterranean kitchen in Vevey", label
title "Long lunches,\nby the *lake*.", display
text "Seasonal plates to share and natural wines, on a stone terrace two steps from the water.", lead
row gap 12
button "Book a table", large, to "#book"
button "See the menu", large, quiet, to "#menu"
box front
image "terrace.jpg", "The stone terrace of Chez Lina at dusk, tables set under the lamps", cover, height 640
tablet height 460
mobile height 380
image "burrata.jpg", "Burrata with peaches and figs on a white plate", cover, width 210, height 210, pin bottom -34 left -46, border 8 paper, shadow strong
tablet hidden
mobile hidden
section menu, padding 0 48 96
mobile padding 0 20 64
grid columns 1.7 1, gap 56
tablet columns 1, gap 32
column gap 0
row gap 16
title "The menu", heading, level 2
row gap 8, margin 0 0 0 auto
button "Lunch", tab, selected (service == "lunch") -> service = "lunch"
button "Dinner", tab, selected (service == "dinner") -> service = "dinner"
spacer 28
for d in shown
row dish
column gap 4
subtitle d.name, level 3, size 23
text d.note, color muted
text "{d.price}", margin 0 0 0 auto, size 23, weight 600, font "titles"
text "Prices in Swiss francs. Tell us about allergies when you book.", color muted, size 15, padding 22 0 0 0, border-top 1 line
column
column tinted, sticky 96
text "Lina's table", label
title "Five plates, chosen by the kitchen.", size 30, line-height 1.1, font "titles", weight 800, level 3
text "What the market had this morning, served to the whole table. Tell us what you do not eat and leave the rest to us."
text "68 per person", size 23, weight 700, font "titles"
button "Book a table", to "#book"
section kitchen, padding 0 48 96
mobile padding 0 20 64
grid columns 1.35 1, gap 18
tablet columns 1
image "kitchen.jpg", "Bowls being plated on the pass of the kitchen", cover, height 560, settle
mobile height 300
column gap 18
image "table.jpg", "A table set outside with burrata, tomatoes and green glasses", cover, height 290
mobile height 240
column brick
title "Everything is made here, every morning.", size 30, line-height 1.1, font "titles", weight 800, level 2
text "The pasta, the bread, the harissa. Lina buys the fish at the port of Ouchy before seven."
section wine, padding 0 48 96
mobile padding 0 20 64
grid columns 0.8 1, gap 72
tablet columns 1, gap 32
image "glasses.jpg", "Two wine glasses on a wooden table in the afternoon sun", cover, height 520
mobile height 320
column gap 22, middle
title "Wines with nothing *added*.", heading, level 2
text "Forty bottles from growers we have met, most of them within a day's drive. Half are poured by the glass, so you can follow the meal instead of the list.", lead
column gap 0
for w in wines
row gap 16, padding 14 0, border-top 1 line
text w.name, weight 700
text w.from, color muted
text "{w.glass} a glass", margin 0 0 0 auto
section find, padding 0 48 96
mobile padding 0 20 64
grid columns 1.3 1, gap 18
tablet columns 1
column tinted
text "Find us", label
title "Quai Perdonnet 14,\n1800 Vevey", heading, level 2
text "On the lake side of the old town, five minutes on foot from the station. The terrace is the one with the vines.", width 430
link "021 555 01 42" "tel:+41215550142", size 20, weight 700
column tile
text "Open", label
for h in hours
column gap 2, padding 12 0, border-top 1 line
text h.days, weight 700
text h.time, color muted
section book, padding 0 48 110
mobile padding 0 20 72
grid columns 1 1.1, gap 72
tablet columns 1, gap 32
column gap 18
title "Come *hungry*.", heading, level 2
text "Tables for two to eight. For more, write to us and we keep the terrace for you.", lead
image "pour.jpg", "Wine poured into a glass in front of the trees", cover, height 300
tablet hidden
mobile hidden
column gap 16
if sent
title "See you soon, {name}.", size 34, font "titles", weight 800, level 3
text "We confirm your table by e-mail within the hour."
else
form mail "table@chezlina.example", subject "Table request" -> sent = true
field name "Ada Lovelace", required, label "Name"
field email "ada@example.com", type email, required, label "Email"
select guests "2 people", "3 to 4 people", "5 to 8 people", label "Table for"
select moment "Lunch", "Dinner", label "Service"
textarea message "A birthday, an allergy, a favourite table", label "A word for the kitchen"
button "Book a table", large
footer padding 56 48 0, gap 28, direction column
mobile padding 40 20 0
row gap 32
text "Quai Perdonnet 14, 1800 Vevey"
link "table@chezlina.example" "mailto:table@chezlina.example"
text "Made with Kaury", margin 0 0 0 auto, color muted
text "Chez Lina", wordmark, attr "aria-hidden" "true"
```
Ceramics shop · quiet gallery
Terre
A shop: pieces filtered by kind, a page for each one, a basket kept in the browser.
pages from a list
persist
functions
tabs
218 lines of Kaury
Read the code of Terre
// Terre: the shop of a small ceramics studio.
// Read as: a shop for people who buy handmade things, in a quiet gallery language:
// paper, ink and one cobalt blue. Variance 6, motion 4, density 4.
site "Terre"
colors accent #1F3FBF, paper #F6F4EF, mist #E9E6DC, ink #14161C
fonts "Supreme", "Chillax"
lang "en"
url "https://terre.example"
style background paper, color ink, radius 18, max-width 1280
seo "Terre, ceramics thrown in Vevey", "Cups, bowls and vases thrown by hand in a small studio in Vevey, fired twice and sent in three days. Each piece is made once."
state kind = "all"
state cart = []
state ordered = false
let total = sum cart, p -> p.price
persist "cart", cart
let pieces = [
{ id: "stack", name: "Stacking cups", kind: "cups", price: 48, photo: "stack.jpg", about: "Three fluted cups, stacked, in pink and white", note: "Set of three, fluted, 180 ml each" },
{ id: "indigo", name: "Indigo mug", kind: "cups", price: 38, photo: "indigo.jpg", about: "Two deep blue mugs on a white table", note: "Cobalt glaze, 300 ml" },
{ id: "bowl", name: "Cream bowl", kind: "bowls", price: 44, photo: "bowl.jpg", about: "A cream bowl turned over with a pear on top, against a mustard wall", note: "Matt glaze, 16 cm across" },
{ id: "lilac", name: "Lilac tumbler", kind: "cups", price: 32, photo: "lilac.jpg", about: "A lilac tumbler on a slice of wood", note: "No handle, 220 ml" },
{ id: "vases", name: "Trio of vases", kind: "vases", price: 140, photo: "vases.jpg", about: "Three vases in black, blue and rust on a ribbed table", note: "Sold together, 18 to 32 cm tall" },
{ id: "sprout", name: "Seedling cup", kind: "vases", price: 26, photo: "sprout.jpg", about: "A small cream cup with a seedling growing out of it", note: "For a cutting, or an espresso" }
]
let shown = pieces.filter(p -> kind == "all" or p.kind == kind)
let kinds = [
{ id: "all", name: "Everything" },
{ id: "cups", name: "Cups" },
{ id: "bowls", name: "Bowls" },
{ id: "vases", name: "Vases" }
]
let facts = [
{ name: "Clay", value: "Stoneware from Burgundy" },
{ name: "Glazes", value: "Mixed here, lead-free" },
{ name: "Dishwasher", value: "Yes, every piece" },
{ name: "Sent", value: "In three days, in straw" }
]
function add piece
cart.add piece
ordered = false
function held piece
cart.count(c -> c.id == piece.id)
style title
weight 600
style emphasis
upright, color accent
style display
font "titles", size 112, line-height 0.9, tracking -4
tablet size 88
mobile size 56, tracking -2
style heading
font "titles", size 60, line-height 0.98, tracking -2
mobile size 38, tracking -1
style lead
size 20, line-height 1.5, max-width 440
mobile size 18
style label
uppercase, tracking 2, size 12, weight 700
style quiet
background transparent, color ink, border 2 ink
hover background ink, color paper
style tab
background transparent, color ink, border 2 mist, padding 8 18, size 15, weight 600
hover border 2 ink
selected background ink, color paper, border 2 ink
style piece
gap 10
image hover grow 1.02
style frame
clip, radius 18
style tinted
background mist, padding 32, gap 0, radius 18
mobile padding 24
style blue
background accent, padding 32, gap 12, radius 18
mobile padding 24
style basket-row
gap 16, padding 14 0, border-top 1 line
animation rise, 0.9s, smooth
from move 0 28
to move 0 0
component Top
section header, sticky, padding 18 48
mobile padding 14 20
logo "Terre"
links Shop, Studio
button "Basket ({cart.length})", small, to "/#basket"
page "/"
Top
section hero, padding 56 48 40
mobile padding 28 20 24
grid columns 2.4 1, gap 56
tablet columns 1, gap 20
title "Made once.\nKept for *years*.", display, rise
column gap 16, middle
text "Cups, bowls and vases thrown by hand in Vevey. No two are the same, and none is made twice.", lead
text "Sent in three days", label
section shop, padding 0 48 96
mobile padding 0 20 64
row gap 8
for k in kinds
button k.name, tab, selected (kind == k.id) -> kind = k.id
text "{shown.length} pieces", color muted, margin 0 0 0 auto
mobile hidden
spacer 6
grid 3 columns, gap 24 20
tablet 2 columns
mobile columns 1
for p in shown
column piece
box frame
image p.photo, p.about, cover, height 380
mobile height 320
row gap 12
link p.name, "/piece/{p.id}", plain, weight 600, size 19
text price(p.price), margin 0 0 0 auto, weight 600
text p.note, color muted, size 15
button (if held(p) > 0 then "In the basket ({held(p)})" else "Add to the basket"), small, quiet -> add p
section studio, padding 0 48 96
mobile padding 0 20 64
grid columns 1.35 1, gap 20
tablet columns 1
image "wheel.jpg", "Two hands shaping a pot on a turning wheel", cover, height 560
mobile height 300
column gap 20
column blue
title "Thrown on the wheel, fired twice.", heading, level 2
text "One potter, one kiln, forty pieces a week. When a glaze runs out, the color is gone."
column tinted
for f in facts
row gap 16, padding 14 0, border-bottom 1 line
text f.name, label
text f.value, margin 0 0 0 auto
section basket, padding 0 48 110
mobile padding 0 20 72
grid columns 1 1.4, gap 56
tablet columns 1, gap 20
column gap 14
title "Your *basket*.", heading, level 2
text "It stays here when you come back: the basket is kept in your browser.", lead
column gap 0
if cart.length == 0
text "Nothing in it yet. Every piece above has a button.", color muted, padding 14 0, border-top 1 line
else
for item in cart
row basket-row
image item.photo, item.about, cover, width 64, height 64
text item.name, weight 600
text price(item.price), margin 0 0 0 auto
button "Remove", small, quiet -> cart.remove item
row basket-row
text "Total", label
text price(total), margin 0 0 0 auto, font "titles", size 30, weight 600
spacer 16
if ordered
text "Thank you. This is a demonstration: nothing was charged and nothing will be sent.", weight 600
else
button "Order", large -> ordered = true
footer padding 40 48, gap 24
mobile padding 32 20
text "Terre, Rue du Lac 12, 1800 Vevey"
link "hello@terre.example" "mailto:hello@terre.example"
text "Made with Kaury", color muted, margin 0 0 0 auto
page "/piece/{p.id}" for p in pieces
seo "{p.name}, Terre", "{p.name}: {p.note}. Thrown by hand in Vevey, fired twice, sent in three days."
Top
section piece, padding 40 48 110
mobile padding 20 20 72
grid columns 1.1 1, gap 64
tablet columns 1, gap 28
box frame
image p.photo, p.about, cover, height 640
mobile height 380
column gap 18, middle
link "All the pieces" "/#shop", label, plain
title p.name, heading
text p.note, lead
text price(p.price), font "titles", size 44, weight 600
row gap 12
button (if held(p) > 0 then "In the basket ({held(p)})" else "Add to the basket"), large -> add p
button "See the basket", large, quiet, to "/#basket"
column tinted
for f in facts
row gap 16, padding 12 0, border-bottom 1 line
text f.name, label
text f.value, margin 0 0 0 auto
footer padding 40 48, gap 24
mobile padding 32 20
text "Terre, Rue du Lac 12, 1800 Vevey"
link "hello@terre.example" "mailto:hello@terre.example"
text "Made with Kaury", color muted, margin 0 0 0 auto
```
Product page · clear, exact
Relay
No photograph: the picture is the product, drawn in Kaury and alive. Figures and bars follow the period you choose.
live chart
state
data
details
259 lines of Kaury
Read the code of Relay
// Relay: the landing page of a delivery tool for bike couriers.
// Read as: a product page for people who run a small delivery team, in a clear, exact language:
// paper, ink and one green. The picture of the page is the product itself, drawn in Kaury and alive.
// Variance 6, motion 5, density 5.
site "Relay"
colors accent #0B7A5B, paper #F4F5F1, panel #FFFFFF, ink #101412, soft #E4E7E0
fonts "General Sans", "Clash Grotesk"
lang "en"
url "https://relay.example"
style background paper, color ink, radius 16, max-width 1280
seo "Relay, deliveries on time for bike couriers", "Relay plans the routes of a team of bike couriers, tells each customer when the parcel is close, and shows what was late and why. Free for three riders."
state range = "week"
state team = "small"
state sent = false
let periods = [
{ id: "today", name: "Today" },
{ id: "week", name: "This week" },
{ id: "month", name: "This month" }
]
let numbers = {
today: { parcels: "184", ontime: "97%", minutes: "21", riders: [
{ name: "Ines", done: 52, bar: "100%" },
{ name: "Marc", done: 47, bar: "90%" },
{ name: "Sana", done: 44, bar: "85%" },
{ name: "Théo", done: 41, bar: "79%" }
] },
week: { parcels: "1,236", ontime: "96%", minutes: "23", riders: [
{ name: "Sana", done: 331, bar: "100%" },
{ name: "Ines", done: 318, bar: "96%" },
{ name: "Marc", done: 302, bar: "91%" },
{ name: "Théo", done: 285, bar: "86%" }
] },
month: { parcels: "5,410", ontime: "95%", minutes: "24", riders: [
{ name: "Ines", done: 1422, bar: "100%" },
{ name: "Sana", done: 1390, bar: "98%" },
{ name: "Théo", done: 1311, bar: "92%" },
{ name: "Marc", done: 1287, bar: "91%" }
] }
}
let seen = numbers[range]
let stops = [
{ place: "Rue du Lac 12", time: "10:42", state: "Delivered" },
{ place: "Avenue de la Gare 3", time: "10:58", state: "Delivered" },
{ place: "Quai Perdonnet 14", time: "11:10", state: "Two stops away" },
{ place: "Rue des Moulins 7", time: "11:25", state: "Planned" }
]
let teams = [
{ id: "small", name: "1 to 3 riders", price: "0", per: "for ever", note: "Everything, for a team that starts." },
{ id: "medium", name: "4 to 10 riders", price: "49", per: "per month", note: "Routes for the whole team, and the weekly report." },
{ id: "large", name: "11 riders and more", price: "129", per: "per month", note: "Several depots, and someone to call." }
]
let plan = teams.find(t -> t.id == team)
let questions = [
{ q: "Do my riders need a new phone?", a: "No. Relay runs in the browser of any phone from the last six years. Nothing to install." },
{ q: "What does the customer receive?", a: "One text when the parcel leaves, one when the rider is two stops away, with a link to follow it." },
{ q: "Can I bring my addresses?", a: "Yes. Paste a list or drop a spreadsheet: Relay reads the addresses and orders the stops." },
{ q: "What happens after three riders?", a: "Nothing changes for them. You choose a plan when the fourth one joins." }
]
style title
weight 600
style emphasis
upright, color accent
style display
font "titles", size 92, line-height 0.94, tracking -3
tablet size 76
mobile size 50, tracking -1.5
style heading
font "titles", size 56, line-height 1, tracking -1.5
mobile size 36, tracking -1
style lead
size 20, line-height 1.5, max-width 460
mobile size 18
style label
font "mono", uppercase, tracking 1, size 12
style quiet
background transparent, color ink, border 2 ink
hover background ink, color paper
style tab
background transparent, color ink, border 1 soft, padding 7 14, size 14, weight 500
hover border 1 ink
selected background ink, color paper, border 1 ink
style panel
background panel, radius 20, padding 22, gap 18, border 1 soft, shadow soft
mobile padding 16
style tile
background paper, radius 14, padding 16, gap 4
mobile padding 10
style figure
font "titles", size 40, weight 600, line-height 1, tracking -1
mobile size 24
style track
background soft, radius 999, height 10, clip
style fill
background accent, radius 999, height 10
style sheet
background panel, radius 20, padding 28, gap 14, border 1 soft
mobile padding 22
style green
background accent, radius 20, padding 28, gap 14
mobile padding 22
style stop
gap 12, padding 12 0, border-top 1 soft
style chip
font "mono", size 11, padding 4 9, radius 999, background soft
style bubble
background soft, radius 16, padding 14 16, size 15, line-height 1.45, max-width 300
// the first words of the page move in but do not fade in: what fades in is read late
animation rise, 0.9s, smooth
from move 0 28
to move 0 0
animation grow, 0.9s, smooth
from opacity 0, move -40 0
to opacity 1, move 0 0
page "/"
section header, sticky, padding 16 48
mobile padding 12 20
logo "Relay"
links Product, Prices, Questions
button "Start free", small, to "#start"
section hero, padding 48 48 96
mobile padding 24 20 64
grid columns 1.15 1, gap 56
tablet columns 1, gap 36
column gap 24, middle, rise
text "For bike couriers", label
title "Every parcel,\n*on time*.", display
text "Relay plans the routes, tells each customer when the parcel is close, and shows what was late and why.", lead
row gap 12
button "Start free", large, to "#start"
button "Prices", large, quiet, to "#prices"
column panel
row gap 8
for p in periods
button p.name, tab, selected (range == p.id) -> range = p.id
grid 3 columns, gap 10
mobile columns 3, gap 6
column tile
text "Parcels", label
text seen.parcels, figure
column tile
text "On time", label
text seen.ontime, figure
column tile
text "Minutes each", label
text seen.minutes, figure
column gap 0
for r in seen.riders
grid columns 1 4 0.8, gap 12, padding 10 0, border-top 1 soft
mobile columns 1.2 3 0.9
text r.name, weight 500
box track, middle
box fill, grow, width r.bar
text "{r.done}", label, align right, middle
text "Live: change the period above.", label, color muted, margin auto 0 0 0
section product, padding 0 48 96
mobile padding 0 20 64
grid columns 1.3 1, gap 40
tablet columns 1, gap 12
title "Three things, *done well*.", heading, level 2
text "No map to stare at all day, no board to keep up to date. Relay does the dull part and leaves you the calls.", lead, middle
spacer 20
grid columns 1.2 1 1, gap 16
tablet columns 1
column sheet
text "Routes", label
subtitle "The order of the stops fixes itself.", level 3, size 26, line-height 1.1
column gap 0
for s in stops
row stop
text s.time, label
text s.place, weight 500
text s.state, chip, margin 0 0 0 auto
column sheet
text "Customers", label
subtitle "A text when it is close.", level 3, size 26, line-height 1.1
text "Your parcel is two stops away. Sana will ring in about eight minutes.", bubble
text "Fewer missed doors, fewer second trips.", color muted
column green
text "Reports", label
text "23", font "titles", size 120, weight 600, line-height 0.9, tracking -4
text "minutes per parcel this week, one less than last week. You see why on Monday morning."
section prices, padding 0 48 96
mobile padding 0 20 64
grid columns 1 1.2, gap 64
tablet columns 1, gap 24
column gap 16
title "One price,\nby the *team*.", heading, level 2
text "Every plan has everything. You pay for the number of riders, not for features.", lead
column sheet
row gap 8
for t in teams
button t.name, tab, selected (team == t.id) -> team = t.id
row gap 12
text "CHF {plan.price}", font "titles", size 88, weight 600, line-height 0.9, tracking -3
text plan.per, label, margin auto 0 10 0
text plan.note, size 18
button "Start free", large, to "#start", margin 6 0 0 0
section questions, padding 0 48 96
mobile padding 0 20 64
grid columns 1 1.7, gap 56
tablet columns 1, gap 12
title "Before you *ask*.", heading, level 2
column gap 4
for f in questions
details f.q
text f.a, color muted
section start, padding 0 48 110
mobile padding 0 20 72
column green
grid columns 1.2 1, gap 48
tablet columns 1, gap 20
column gap 12
title "Three riders, free, for ever.", heading, level 2
text "Leave an address: you get a link, and your first route is planned in ten minutes."
column gap 12, middle
if sent
text "Check your inbox: the link is on its way.", weight 600, size 20
else
form mail "hello@relay.example", subject "A new team" -> sent = true
field email "you@yourteam.ch", type email, required, label "Email"
button "Send me the link", large, background ink, color paper
footer padding 40 48, gap 24
mobile padding 32 20
text "Relay, Vevey"
link "hello@relay.example" "mailto:hello@relay.example"
text "Made with Kaury", color muted, margin 0 0 0 auto
```
Drink brand · pop, immersive
Crush
Three pages, a scene in 3D with a mascot, a cart kept in the browser.
scene
character
persist
pages
123 lines of Kaury
Read the code of Crush
// Crush — canned mocktails. A complete immersive site, in a single file.
site "Crush"
colors accent #EA4374, night #05060F, crush-blue #23347A, cream #FDF7F2, ember #3E0A1C
font "Outfit"
lang "en"
url "https://crush.example"
style background night, color cream
transition fade
// ---- data ----
let flavors = [
{ name: "Berry Crush", image: "berry.webp", note: "Strawberry, raspberry, a hint of basil", price: 3.9 },
{ name: "Green Crush", image: "green.webp", note: "Cucumber, lime, fresh mint", price: 3.9 },
{ name: "Sunset Crush", image: "sunset.webp", note: "Peach, apricot, orange blossom", price: 4.2 },
{ name: "Tropical Crush", image: "tropical.webp", note: "Mango, passion fruit, roasted pineapple", price: 4.2 }
]
state cart = []
let total = sum cart, f -> f.price
let items = length cart
persist "cart", cart
function add-to-cart flavor
cart.add flavor
confetti()
component FlavorCard flavor
card
image flavor.image, flavor.name, cover
subtitle flavor.name, size 26
text flavor.note, opacity 0.85
row gap 12
text price(flavor.price), bold
button "Add", small -> add-to-cart flavor
enters from bottom
style background ember, border 1 #FFFFFF22, radius 22, hover lift 8, shadow strong
// ---- pages ----
page "/"
seo "Crush — canned mocktails", "Four alcohol-free recipes, pressed in Switzerland. Taste the difference."
section header, sticky, glass
logo "wordmark.svg"
links Flavors, Story, Cart
button "Cart ({items})", small, outline, to "/cart"
scene home, fullscreen, particles bubbles
light sunset
camera follows mouse
character mascot "mascot.glb", size 1.1, position 2.2 -0.3 0, alt "Kaury, the Crush mascot"
enters from right
says "Hi! Click me."
on click -> jump
title "Taste the difference", size 96, text-gradient accent #FF9CC0
enters from bottom
text "Mocktails pressed in Switzerland. Zero alcohol, zero compromise.", size 20, opacity 0.85
row gap 12, center
button "See the flavors", large -> scroll-to "flavors"
button "Our story", large, outline, to "/story"
section flavors
subtitle "Four flavors. No regrets.", size 56
enters from left
grid 4 columns, gap 24
tablet 2 columns
for flavor in flavors
FlavorCard flavor
section numbers, center
style gradient ember night 180
grid 3 columns, gap 32
for n in [{ n: "0 %", t: "alcohol" }, { n: "100 %", t: "pressed fruit" }, { n: "33 cl", t: "of freshness" }]
column center
title n.n, size 72, text-gradient accent #FF9CC0, level 2
text n.t, uppercase, opacity 0.8
enters from bottom
footer
text "© Crush — a fictional drink, example site written in Kaury"
links Flavors, Story
page "/story"
seo "Our story", "How Crush was born in Vevey, between two markets."
section header, sticky, glass
logo "wordmark.svg"
links Home, Cart
section fullscreen
style max-width 760
title "One can, one story.", size 72
enters from bottom
text "It all starts at a market in Vevey: a stand, four fruits, and the will to prove that an alcohol-free drink can have character."
text "Today, Crush is found in the bars that care about their sober guests as much as the others."
object "mark.svg", size 0.5
float
follows mouse, smooth
button "Discover the flavors", to "/#flavors"
page "/cart"
seo "Your cart", "The drinks you picked."
section header, sticky, glass
logo "wordmark.svg"
links Home, Story
section
style max-width 760
title "Your cart", size 64
if cart.length == 0
text "Your cart is empty for now."
button "Pick a flavor", to "/#flavors"
else
for item, i in cart
row gap 16
image item.image, item.name, width 64, radius 12
text item.name, bold
text price(item.price)
button "Remove", small, ghost -> cart.remove item
divider
row gap 16
title "Total: {price(total)}", size 32, level 2
button "Order", large -> ordered = true
if ordered
text "Thank you! This is a demo: nothing was charged.", color accent
```
Starters
Or start from one command.
What kaury new gives you, before you change a word. The blocks follow your colors and your fonts.
Landing
A product page made of ready-made blocks: hero, features, prices, questions, contact.
kaury new my-site --template landing
Portfolio
Work, a way of working, what clients say, a form.
kaury new my-site --template portfolio
Blog
Posts written in Markdown, one page each, built at once.
kaury new my-site --template blog
Start from one of them.
Every site here is in the repository, in examples/. Copy the folder, change the words and the pictures, and it is yours. Or take one of the six homepages of the library.





