The flowers package
There is no source image for this site’s flower: no file, database row, or bucket object. The server can regenerate an SVG from a seed; responses may be cached, but the seed remains the source of truth. The detail page keeps that SVG on screen and renders the same specimen in WebGL only when someone requests a PNG. The same seed and package revision produce the same flower again.
That's the premise of @nbot/flowers, a package I built and published on npm. It turns a seed into a botanical model that can be rendered as SVG or WebGL. This site uses its plumeria; the catalog also includes daisies, sunflowers, and passionflowers.
npm install @nbot/flowersimport { cultivar, plumeria } from "@nbot/flowers";
const svg = plumeria({ seed: "2026-06-15" });
const name = cultivar({ seed: "2026-06-15" }); // e.g. "celadine"The SVG path has no DOM or Three.js dependency, so it runs on the server or in the browser. The optional GL entry point uses Three.js and WebGL 2.
The seed becomes a genome
The first thing the package does with a seed string is turn it into a stream of numbers. Not Math.random — that would give different results every time. Instead, it runs the seed through a small deterministic hash (xmur3) to get four 32-bit integers, then feeds those into a generator called sfc32 that produces a reproducible sequence of floats between 0 and 1.
Think of it as a very small slot machine that always lands on the same symbols in the same order, for a given starting position.
Those numbers then build a genome. A plumeria genome has everything: the color of the petals at the base and the tip, the color of the throat, the intensity of the blush on the shoulders, whether there's a flame in the center, how long and curved the petals are, how full or concave the shape is.
Every flower comes from one of eight cultivars, each modelled on a real frangipani variety: Celadine (white with a cadmium yellow center), Rainbow (cream body, orange throat, red rays), Pink Pearl, Sunset, Fuchsia, Gold, Candy Stripe, Carmine. They have different weights, so some appear more often than others. Celadine and Rainbow are three times as common as Candy Stripe.
About one in five flowers hybridises two cultivars. When that happens, all the color properties blend in OKLCH space at a random ratio between 30 and 70 percent, and the name becomes something like "sunset × fuchsia" or "gold × rainbow". The dominant parent comes first.
OKLCH, because RGB lies
All the color work happens in OKLCH, not RGB. OKLCH stands for lightness, chroma, hue, in a color space where equal steps look like equal steps to a human eye. In RGB, moving the same distance toward red and toward green looks very different. In OKLCH, it doesn't.
This matters a lot for hybridisation. When you blend gold (hue around 85) and pink (hue around 355) in HSL, you pass through green. In OKLCH, you pass through orange, because the code finds the short arc between the two hue values. The blends stay harmonious.
It also matters for the final output. Every color in OKLCH might not fit inside the sRGB gamut that screens use. Instead of clipping it (which would make it harsh), the package walks the chroma down gently until the color fits. The result is always printable and never blows out.
The pinwheel problem
A plumeria has five petals arranged in a pinwheel: each one covers its right neighbour and slides under its left neighbour, all the way around the circle. This is a problem.
If you try to draw the petals in any fixed order, the last petal you draw sits on top of everything, breaking the cycle. The first petal should cover the last, but the last was drawn after the first, so it wins. There is no draw order that closes the loop.
The fix is to not fight it. Draw all five petals, accept that the last one wrongly covers the first, and then draw the first petal a second time, clipped to the silhouette of the last. That thin sliver is the only piece of the first petal that was incorrectly hidden. Paste it back on top and the loop closes.
Every petal also casts a contact shadow on the one beneath it: a tight crisp edge that spreads into a soft penumbra, the same way a real petal pressed against another one looks in sunlight.
The geometry
Each petal starts as a straight shape in its own coordinate system, then gets rotated into place. The center line is a parabola, and because parabolas are a special case of Bezier curves, they survive rotation without any distortion.
The sides of the petal follow a Beta distribution kernel, which is a curve that can be wide and symmetric or narrow and offset, depending on two parameters. The two sides of a petal are asymmetric by design: the side that overlaps its neighbour is broader, the side that slides underneath is slimmer. Fullness (concave vs convex) changes the parameters continuously, not in discrete steps.
One subtle thing: every flower that comes out of the math is slightly tilted. Rather than correcting for this at render time, the code measures the lean of the shape using a Fourier integral over the silhouette, then bakes a counter-rotation directly into the petal geometry. The flower emerges already upright.
A garden and a chain
The garden on this site grows a new plumeria every day. Each day's flower is not just seeded by the date. It's seeded by the date, the domain, and the digest of the day before.
genesis (2026-06-15)
sha256(genesis | 2026-06-15 | cultivar name that bloomed)
→ a 12-character digest
2026-06-16 seed: "2026-06-16|nicolabottari.com|<yesterday's digest>"
sha256(yesterday's digest | 2026-06-16 | cultivar name that bloomed)
→ next digest
2026-06-17 seed: "2026-06-17|nicolabottari.com|<yesterday's digest>"
...Each day folds the previous day's result into itself. The seed for June 30th therefore commits to every earlier June bloom through a 12-character fingerprint.
It's not a blockchain. Nothing is distributed, nothing is verified by a network. But it has one property blockchains care about: a flower lifted out of the sequence is an orphan. The chain code and renderer are public, so a bloom’s place can be checked by replaying the sequence from genesis.
A flower that fits the chain has a reproducible place in the garden. Pre-genesis dates and custom seeds still grow flowers, but they do not claim a position in that sequence.
What this looks like in practice
The garden shows every flower since genesis. Each one links to its own page, which shows the full bloom with its name and chain position. The flower on a blog post is the bloom for the day the post was written.
The package is MIT licensed and has no opinion about how you use it. Pass any string as a seed and choose the renderer that fits the surface. The garden keeps SVG on screen; its PNG export lets the package own the temporary WebGL canvas and cleanup.
import { plumeria } from "@nbot/flowers";
import { exportPlumeriaPng } from "@nbot/flowers/gl";
const seed = "your-string-here";
const svg = plumeria({ seed });
const png = await exportPlumeriaPng({ seed }); // 2048 × 2048 BlobThe garden is at /garden. Every flower that has ever bloomed here is there.