Start
Tutorial — a real site in 10 steps
Build a complete little shop site, from an empty file to a 3D hero, a cart and a contact form.
We build Bloom, a fictional flower shop: a home page with a 3D hero, a list of bouquets, a cart that survives a reload, and a contact page. Every step adds a few lines to the same site.kaury.
1. The site
site "Bloom"
colors accent #E93D82, cream #FFF6EE
font "Satoshi"
lang "en"
url "https://bloom.example"
colors gives names to colors, usable everywhere (background cream). The font is downloaded at build time and served by your site: no request to Google.
2. A page
page "/"
seo "Bloom — flowers in Vevey", "Fresh bouquets, delivered the same day."
style background cream
section fullscreen, center
title "Flowers that speak", size 80
text "Fresh bouquets, delivered the same day."
seo writes the <title>, the description, Open Graph and the canonical link.
3. A header
section header, sticky, glass
logo "Bloom"
links Bouquets, Contact
links builds a menu. Bouquets points to #bouquets if a section has that name, otherwise to the page /bouquets. On phones, the menu folds behind a button by itself.
4. Data
let bouquets = [
{ name: "Peonies", price: 45, image: "peonies.jpg" },
{ name: "Tulips", price: 30, image: "tulips.jpg" },
{ name: "Wild", price: 38, image: "wild.jpg" }
]
Images go in public/ and are written without /.
5. A loop and a component
component Bouquet b
card b.name, b.image
text price(b.price), bold
style hover lift 6, shadow soft
page "/"
section bouquets
subtitle "Our bouquets"
grid 3 columns, gap 24
for b in bouquets
Bouquet b
A component name starts with a capital letter. price 45 gives CHF 45.00 (or price 45, "EUR").
6. State: a cart
state cart = []
let total = sum cart, b -> b.price
persist "cart", cart
total reads a state, so it recomputes itself. persist keeps the cart in the browser across reloads. In the component, add:
button "Add" -> cart.add b
And anywhere in the page:
text "Cart: {cart.length} bouquet(s), {price(total)}"
7. Conditions
if cart.length == 0
text "Your cart is empty."
else
button "Order ({price(total)})", large -> ordered = true
if ordered
text "Thank you!", bold, pink
ordered = true creates the state on the fly.
8. The 3D hero
Put a flower.glb in public/, then replace the first section:
scene fullscreen, particles dust
light soft
object "flower.glb", size 1.4, alt "A peony"
spin slow
follows mouse, smooth
title "Flowers that speak", size 80
That's it. Three.js loads only on this page, after the page is displayed and the visitor's first gesture, so Lighthouse stays at 100.
9. A second page and a form
page "/contact"
seo "Contact", "Order a bouquet or ask a question."
section
title "Contact"
form -> sent = true
field email "Your email", type email, required, label "Email"
textarea message "Your message", label "Message"
button "Send"
if sent
text "Thanks, we answer within the day."
10. Build and publish
kaury build
kaury deploy --netlify
You now have a multi-page site with server rendering, a 3D hero, a persistent cart, a form, a sitemap, llms.txt and structured data — in about 70 lines.
Next: the language in depth · every element