枠の中でスクロール・クリックして動きを試せます。端末指定は実寸表示です。幅が足りない場合は横にスクロールできます。
再現プロンプト
架空の香水アトリエ「Terra & Bloom」の縦長1ページサイト。カーソルの軌跡に生成花が咲いて減衰消滅し、速度で開花サイズが変わり、クリックで一斉開花のバースト、スクロールでも縦の軌跡が残る——粒子プールで駆動する1本のcanvasループ。 配色・構成・動きの数値をまとめた指示書です。 お使いのAIに貼り、素材やお店の情報を伝えて実装してください。仕上がりは利用するAIと素材によって変わります。
Build a single self-contained HTML file (inline CSS + inline vanilla JS, no framework; CDN allowed only for GSAP + its ScrollTrigger plugin) for "Terra & Bloom" — a fictional botanical perfume atelier. This is a complete, vertically scrolling one-page site (window scroll is fine; a container-scroll constraint only applies to the embedded-iframe version of this demo). Four sections, one motion language throughout — THE CURSOR'S TRAIL BLOOMS: Hero → Collection → Atelier → Footer.
## Art direction
A warm, light editorial palette — a departure from typical dark UI demos, chosen because ink blooming on pale paper is the core metaphor. Background #FBF3EF (warm paper), primary text #2B1A1E (deep wine-charcoal), muted text rgba(43,26,30,0.6–0.7), accent #FB7185 (rose) with a darker rose #C6425A for eyebrows/links. Headings use an elegant serif (Cormorant Garamond weights 400/600/700, italic available) with fluid clamp() sizing (hero h1 clamp(2.3rem,6.6vw,4.8rem); section h2 clamp(1.7rem,4vw,2.5rem)); body copy uses a clean geometric sans (Work Sans 300–600). Cards are #FFFCFA with a 1px rgba(43,26,30,0.08) border, 20px radius, soft shadow "0 16px 36px rgba(43,26,30,0.06)". Buttons are pill-shaped, solid rose fill #FB7185 with white-rose text #FFF7F4 and a soft rose glow shadow, or a ghost variant (transparent, 1px rgba(43,26,30,0.2) border) for secondary actions — hover only scales the button 1.035× and deepens its shadow (CSS transition 220ms ease). Do NOT implement magnetic/pointer-follow buttons anywhere on this page — that idiom belongs exclusively to a different page in this system; every button here uses plain CSS hover only.
## The bloom engine (canvas, shared by every signature below)
A full-viewport '<canvas>' sits as a fixed, 'pointer-events:none' overlay above all scrolling content (so it never blocks clicks on real buttons/links beneath it), sized to the viewport at 'min(devicePixelRatio,2)', resized via ResizeObserver. It is driven by an object pool of at most 220 "bloom" particles ('MAX_BLOOMS = 220') — spawning never allocates unboundedly; new blooms simply overwrite the oldest pool slot round-robin, giving an automatic hard cap with O(1) spawn cost. Each bloom has a random seed-derived flower shape: 'petalCount' 5–9, 'hue' = 351° (matching the #FB7185 accent) ± 46° of variance, 'saturation' 62–88%, 'lightness' 55–74%, and a random 'rotation'. Drawing a bloom at scale 's' and opacity 'o': translate to its (x,y), rotate by its rotation, then for each petal at angle 'i/petalCount·2π' draw a filled ellipse offset '-0.55·radius' along that rotated axis (rx = 0.34·radius, ry = 0.58·radius, fill 'hsla(hue, sat%, light%, o·0.86)'), and finish with a small center circle (radius 0.22·radius, hue+20°, darker/more saturated, alpha o). A bloom's lifecycle runs on three coefficients: it opens over the first 22% of its lifespan ('GROW_FRACTION = 0.22') with an easeOutBack curve ('1 + 2.70158·(x−1)³ + 1.70158·(x−1)²', giving a slight "pop" overshoot past full size before settling), holds fully open until 55% of its lifespan ('FADE_START_FRACTION = 0.55'), then fades linearly to opacity 0 over the remaining 45%. Lifespan itself is randomized 900–1700ms per bloom ('BLOOM_LIFESPAN_MIN_MS'/'MAX_MS'). One requestAnimationFrame loop clears the canvas and redraws every alive pool slot each frame, removing any bloom whose age exceeds its lifespan.
## Interaction signatures
(1) **Trail**: pointermove (mouse or touch) spawns blooms along the cursor's path. Track the last spawn point; once the pointer has moved at least 10px ('TRAIL_SPAWN_DIST_PX') from it, spawn a bloom (interpolating up to 6 evenly-spaced blooms along the segment for fast swipes, 'TRAIL_SPAWN_MAX_STEPS'), then update the last point. (2) **Speed → size**: compute pointer speed in px/ms between spawn checks; bloom radius = 'clamp(10 + speed·15, 10, 46)' ('RADIUS_BASE_PX', 'SPEED_RADIUS_FACTOR', 'RADIUS_MAX_PX') — a slow drift opens tight 10px buds, a fast swipe opens blossoms up to 46px. (3) **Click burst**: a click spawns 18 blooms at once ('CLICK_BURST_COUNT'), evenly distributed around a full circle with small angular jitter (±0.4rad), each launched with an outward velocity of '5.5 · (0.6 + random·0.8)' px/frame ('CLICK_BURST_SPEED_PX') that decays by a friction factor of 0.9 every frame ('BURST_FRICTION') — unlike trail blooms (which spawn static), burst blooms visibly drift outward before settling and fading, using the same grow/hold/fade envelope. (4) **Scroll trail**: every animation frame, read the scroll container's scrollTop delta; accumulate its absolute value, and every time the accumulator crosses 14px ('SCROLL_TRAIL_SPAWN_DIST_PX', capped at 3 spawns/frame via 'SCROLL_TRAIL_MAX_PER_FRAME') spawn a bloom at the last known pointer x (± 24px jitter) and a y-position that steps by 14px in the scroll direction and wraps modulo the viewport height — so scrolling alone, even with a motionless pointer, leaves its own continuous vertical trail of blooms down the page. Scroll-trail bloom radius uses the same formula with a gentler factor: 'clamp(10 + |scrollDelta|·0.6, 10, 46)' ('SCROLL_RADIUS_FACTOR').
## Section 1: Hero
Full-viewport, centered, text-center. Eyebrow "Terra & Bloom — Botanical Perfume Atelier, Est. 2019". Serif H1 "Every step you take, something blooms." Copy: "Move your cursor, scroll, or simply linger — the trail behind you opens into procedurally grown flowers, each one unique, each one gone within seconds." A plain (non-magnetic) pill CTA "Explore the Collection →" that smooth-scrolls to Collection. An italic hint below: "Try clicking — a whole bed of flowers opens at once." Entrance ("bud → bloom", this page's own reveal language, used for every section's entrance): each element starts at opacity 0, scaleY 0.42, scaleX 0.86, y +22px, transform-origin 50% 100% (as if pushing up out of soil, folded shut), animating to identity over 1.0s with 'back.out(1.4)' easing and 0.12s stagger — a visible "unfurl" rather than a plain fade.
## Section 2: Collection
Eyebrow "The Collection", H2 "Six scents, drawn like ink on water.", intro copy: "Each bottle carries its own generated bloom — the same seeded logic that grows behind your cursor, frozen here into a single mark." A responsive grid ('repeat(auto-fit, minmax(min(240px,100%),1fr))', gap 22px) of 6 cards. Each card leads with a 218px-tall bottle of that scent standing on the card with no plinth or frame — clear glass, softly tinted fragrance liquid, a simple cap, and a **blank cream label plate** with nothing printed on it (in this standalone build draw the bottle as a simple CSS/SVG silhouette rather than loading an image — no external images allowed). The card's identity mark is then **layered on top of that blank label in the DOM, never baked into the bottle art**: a static SVG "flower medallion" generated by the exact same petal-placement formula as the canvas engine but with a fixed numeric seed per card (seeds 11, 29, 47, 63, 81, 97) run through a deterministic hash ('sin(seed·12.9898)·43758.5453', fractional part), so each card always renders the same flower. Absolutely position the medallion at the label plate's center, size it to ~62% of the label's width, and set 'mix-blend-mode: multiply' at 0.92 opacity so it reads as ink sunk into the paper rather than a sticker floating above it. Below the bottle: a name, a one-line notes list, and a plain text "View →" CTA (no magnetism). The six liquids stay off pure magenta and track the accent family — pale cold pink, amber, peony pink, orchid pink, deep orchid, and a night-violet. The six scents: "Névé" — cold violet, wet stone, white musk; "Ember Rose" — smoked rose, amber resin, clove; "Salt Peony" — sea salt, peony, driftwood; "Wild Fig" — fig leaf, green tea, cedar; "Honeyed Iris" — iris root, wild honey, vetiver; "Midnight Jasmine" — night jasmine, tuberose, dark musk. On scroll-in (section top crosses 78% of viewport), cards reveal from opacity 0 / scale 0.5 / rotate -10deg (transform-origin 50% 100%) to identity, 0.8s 'back.out(1.5)', 0.09s stagger — the same bud-opening language as the hero, reversing 0.3s power2.in on scroll-back.
## Section 3: Atelier
Two-column layout (stacks to one column under 760px), border-top 1px rgba(43,26,30,0.1). Left: eyebrow "Inside The Atelier", H2 "We grow every formula the way a flower opens — slowly, then all at once.", two paragraphs of copy about an 18-month formulation process and letting scents "bloom generously, then let go" (0.98rem, line-height 1.7, muted), each fading in (opacity 0→1, y 20px→0, 0.8s power3.out, 0.1s stagger) at the section's top-75%-of-viewport line. Right: a photographic still life of the atelier's perfume organ — apothecary bottles in a wooden rack, glass pipettes, blotter strips, a marble mortar, brass scales and pressed flowers on a cream bench — in a 20px-radius frame (max-width 460px) with a soft "0 22px 48px rgba(43,26,30,0.14)" shadow (in this standalone build substitute a plain cream block — no external images allowed). Overlapping that frame's bottom-right corner (right -13%, bottom -19%, width 40% of the frame, with a soft drop-shadow) sits a single large flower medallion (seed 7, same generator) that is NOT just decorative — this is the hero's core technique reappearing below the fold. Its wrapper is scrubbed directly to scroll position across the section (from "top 85%" to "bottom 40%" of viewport, scrub 0.6, no fixed duration): opacity 0.35→1, scale 0.35→1, rotate -55deg→0deg — the flower visibly opens as the visitor scrolls through the section, exactly mirroring the grow-envelope feel of a single canvas bloom but stretched across the whole scroll range instead of a lifespan in milliseconds.
## Section 4: Footer
Centered, min-height ~70vh, border-top 1px rgba(43,26,30,0.1). Serif H2 "Come wander through the garden." Copy: "Visit the atelier, or write to us — we reply personally, usually with a pressed petal." A ghost-style pill CTA "Write to us ↗" opening the local planning dialog (plain hover only, no magnetism). Footer meta row: "© 2026 Terra & Bloom" · "Grown in — a quiet courtyard" · "@terraandbloom" (0.8rem, rgba(43,26,30,0.45), centered, 1.5rem gaps). All footer elements use the same bud-to-bloom entrance as the hero (scaleY 0.5→1, y 16px→0, opacity 0→1, 0.9s 'back.out(1.3)', 0.1s stagger), triggered at the section's top-80%-of-viewport line.
## Performance & accessibility
The bloom pool is hard-capped at 220 concurrent particles with O(1) round-robin spawn/recycle — never grow an array unbounded. Canvas drawing only (no layout-affecting DOM writes) runs inside the loop; the canvas itself is 'pointer-events:none' so it never intercepts clicks meant for real buttons/links, and the outer page listens for pointermove/click/scroll to feed the pool. Respect 'prefers-reduced-motion: reduce' globally: skip the entire bloom engine (no canvas drawing, no trail/burst/scroll spawning), and make every section's bud-to-bloom entrance apply its final state instantly with no animated transition. Cancel the requestAnimationFrame handle, revert the GSAP context and ScrollTrigger instances, and disconnect the ResizeObserver on teardown. All interactive elements (CTA buttons, links) are real, labeled, keyboard-focusable controls with visible focus outlines (2px #C6425A, offset 2–3px); the canvas and flower medallions are decorative and 'aria-hidden'.
## Action flow and content
Implement the following local planning experience for the primary CTA, in the visual language of the page. Use a real button opening a labelled modal dialog. Choosing a pricing card must preselect that exact plan. A navigation link may move to another section; a planning CTA must lead to its review flow.
Use this content for the dialog, including the option details and notes:
{
"slug": "trail-bloom",
"brand": "TERRA & BLOOM",
"accent": "#825b45",
"title": "Find your accord",
"intro": "A slowly composed fragrance, with its notes, format and wearing character laid out before you choose.",
"facts": [
[
"Concentration",
"Eau de parfum · 18% fragrance blend"
],
[
"Format",
"Refillable glass bottle · 50 ml"
],
[
"First impression",
"The opening changes as the fragrance warms on skin"
]
],
"groups": [
{
"label": "Format",
"options": [
{
"title": "Discovery vial · €8",
"detail": "2 ml spray. Explore the fragrance on skin before choosing a full bottle."
},
{
"title": "Eau de parfum · €120",
"detail": "50 ml refillable bottle in a paper presentation box."
}
]
}
],
"notes": [
"Fictional fragrance and prices; scent notes describe the concept. Ingredients, allergens, delivery and returns must be supplied for any real product."
]
}
- Explain before input that this is a fictional brand sample: choices can be reviewed and saved, with no external submission, booking, payment or registration. Use each brand's language. Do not launch a mail client or present an installer for a fictional product.
- Give every option group a legend, inputs explicit labels and errors linked descriptions. Validate options against the allowed values, requested dates as actual dates from today onward, and free text as 10–2,000 trimmed characters. Blank spaces alone must fail. Tracking accepts the sample reference TG-2048 only.
- Focus the first invalid field and preserve every entered value. On success, show the selected option WITH its full detail, requested date or brief, relevant facts and notes. An edit action returns to the preserved form. Do not claim a real reservation, purchase or account has succeeded.
- Save the complete review as UTF-8 text. A browser download request is not proof of a file being written. On download failure, retain the full text in a labelled, selectable textarea and offer retry. Do not show a false success state.
- Constrain modal content to the viewport with internal scrolling. Support keyboard and touch, trap focus while open, close on Escape and restore focus to the exact initiating control. Keep these operations available with motion disabled.
- If a separate project-contact CTA is present, let it collect a project brief and review it locally; a product selection CTA must retain the product choice. Verify both flows independently.
Each scent’s View button uses that scent’s own name, notes and bottle image. Keep the concentration/format comparison. The separate project CTA collects a project brief.