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.

The first screen of Forge
Forge

Strength gym

The first screen of Nuit
Nuit

Perfume launch

The first screen of Chez Lina
Chez Lina

Restaurant

The first screen of Terre
Terre

Ceramics shop

The first screen of Relay
Relay

Product page

The first screen of Crush
Crush

Drink brand

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

Open it full screen ↗

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

Open it full screen ↗

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

Open it full screen ↗

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

Open it full screen ↗

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

Open it full screen ↗

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

Open it full screen ↗

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

Open it ↗

Portfolio

Work, a way of working, what clients say, a form.

kaury new my-site --template portfolio

Open it ↗

Blog

Posts written in Markdown, one page each, built at once.

kaury new my-site --template blog

Open it ↗

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.