This is a submission for Weekend Challenge: Passion Edition
What I Built
An interactive SVG lily for my long distance girlfriend.
I wanted to give her flowers. Not a photo of flowers, not a flower emoji — actual flowers, something she could open on her phone and interact with. She's far away and I wanted her to feel something real when she opened it.
So I built a three-stage interactive lily in vanilla HTML, CSS, and SVG. The flower starts as a bud, evolves through a pre-bloom stage, and finally opens into a full lily — each transition triggered by her tap, each stage hiding clickable memories that open into frosted glass cards with messages and photos.
No frameworks. No libraries. Just code, a browser, and something worth building.
Demo
[Live link → https://deusexspiravit.github.io/lily-intractable/]
How it works:
- The flower opens in three stages — tap "Next Stage" to evolve it
- Each stage transition fires a full-screen flash effect (yes, like a Pokémon evolution)
- The bud, leaves, and petals are all individually clickable
- Each clickable area opens a memory card with a message and a photo
- After one minute, a final message fades in at the bottom
Code
[https://github.com/DeusExspiravit/lily-intractable
How I Built It
I built this over a weekend while learning JavaScript properly for the first time — I mean really learning it, not just copying from Stack Overflow.
I've been doing a structured learning curriculum with Claude, going through JS fundamentals derivation-first: closures, this, async/await, the Shadow DOM, SVG. This project was the practical application of all of it at once.
The SVG flower
The petals are hand-drawn in Figma using bezier curves. I actually learned what a bezier curve is before drawing them — the C cx1 cy1 cx2 cy2 x y path command, control points pulling the line like magnets without touching it. Each petal has three separate path layers with radial gradients to give the 3D depth effect. The veins are separate strokes with reduced opacity layered on top.
I drew all three stages separately in Figma — bud, pre-bloom, and full bloom — then exported each as SVG. The key technical decision was keeping each petal and leaf as a separate <g> element with a .clickable class, so JavaScript could attach independent event listeners to each one after the SVG was fetched and injected into the DOM.
The three-stage state machine
The core JS is a simple state machine — currentStage tracks where we are (1, 2, or 3), and the loadSVG() function fetches and injects the correct SVG file for that stage. The memories object maps stage number to an array of message/photo pairs, so each clickable area at each stage has its own card content.
function transitionToNextStage() {
if (currentStage >= 3) return;
curtain.classList.add('active');
flowerContainer.classList.add('flash');
setTimeout(() => {
currentStage++;
loadSVG(stages[currentStage - 1]);
}, 800);
setTimeout(() => {
curtain.classList.remove('active');
flowerContainer.classList.remove('flash');
}, 1600);
}
The Pokémon evolution transition
This was my favourite part to build. When she taps "Next Stage":
- A full-screen frosted glass curtain fades in (
backdrop-filter: blur(20px)) - A white flash fires on the flower container via a CSS
::afterpseudo-element animation - At peak white (800ms), the SVG swaps underneath — she can't see it happen
- The curtain slides back down, revealing the new stage
The flash is pure CSS — no extra DOM elements needed:
#flower-container::after {
content: '';
position: absolute;
inset: 0;
background: white;
opacity: 0;
pointer-events: none;
}
#flower-container.flash::after {
animation: flashbang 0.8s ease forwards;
}
@keyframes flashbang {
0% { opacity: 0; }
40% { opacity: 1; }
100% { opacity: 0; }
}
The memory cards
Each clickable petal/leaf opens a frosted glass card using backdrop-filter: blur(20px) and a semi-transparent dark background. The card slides up from below on open and fades out on close, using CSS transitions triggered by toggling a .visible class.
The one-minute message
After she's been on the page for a minute — long enough to have explored everything — a final message fades in at the bottom. A small detail, but it felt important.
What I learned
SVG paths are not scary once you understand bezier curves. fetch() returning a Promise that resolves with text you can inject directly as innerHTML is genuinely powerful. CSS pseudo-elements can do things you'd normally reach for JavaScript for. And building something for a specific person, with a specific feeling in mind, is the best reason to learn anything.
I built this while learning. Claude taught me the JS — closures, async/await, the Shadow DOM, fetch — and I applied it. The SVG and the design decisions were mine. The motive was entirely mine.
She's far away. I wanted to give her flowers.
Prize Categories
No specific prize categories — this is a passion project in the truest sense.
Top comments (0)