枠の中でスクロール・クリックして動きを試せます。端末指定は実寸表示です。幅が足りない場合は横にスクロールできます。
再現プロンプト
架空の小規模コーヒー焙煎所KILNの1ページ。ヒーローに暗い焙煎ドラムのループ映像を敷き、半透明radial-gradientと湯気・火の粉で可読性と暖色を制御。焙煎5段階の写真カード積層からフッターの折り畳みまで、物理的な紙のモーション言語で貫く。 配色・構成・動きの数値をまとめた指示書です。 お使いのAIに貼り、素材やお店の情報を伝えて実装してください。仕上がりは利用するAIと素材によって変わります。
Build a single self-contained HTML file (inline CSS and inline JS in one `<script>` tag) for a complete, long-form one-page site for a fictional small-batch coffee roastery called **KILN**. Use a local muted looping MP4 of a dark roasting drum as the full-bleed hero background, with the existing radial gradient over it at high translucent opacity so the central white headline remains dominant. Load GSAP 3 and ScrollTrigger from CDN only. The motion language for the entire page is **physical paper and stacked layers**: sections behave like sheets, cards like notes, and the footer like a package that folds shut.
## Art direction
Palette: background/near-black ember `#1A100C`, deep ember `#3A241A`/`#5C3423`, mid ember `#6B3320`/`#9B5A34`, bright accent `#FF8A65`, pale highlight `#FFC29E`/`#FFF5EE`, foreground `#F5F5F5`, muted `#ABABAB`. Headings are bold sans-serif (weight 700–800) at fluid sizes via `clamp()`; eyebrows are 0.72rem uppercase with 0.28–0.32em letter-spacing in the accent color. Corner radii: 24px for hero-scale cards, 18px for the testimonial deck, 9999px for the pill CTA button. Layer every section over a fixed, page-wide film-grain overlay (a 180×180px tiled data-URI SVG built from a single `<filter><feTurbulence type="fractalNoise" baseFrequency="0.9" numOctaves="2" stitchTiles="stitch"/></filter>`, opacity 0.05, `mix-blend-mode:overlay`, `pointer-events:none`, fixed over the viewport) so the whole page always reads as textured paper. Additionally, every section below the hero gets a thin cast-shadow strip along its top edge (`height:22px`, `linear-gradient(to bottom, rgba(0,0,0,0.38), transparent)`) to read as one paper sheet overlapping the sheet above it.
## Section 1: Hero
A fixed brand mark sits top-left across the whole page (`position:fixed; left:20px; top:20px; z-index:40`): a 14×14px inline flame SVG (a single tapered teardrop path filled `#FF8A65`) next to the wordmark "KILN" (0.78rem, weight 700, letter-spacing 0.22em, color `#FFF5EE`). The hero itself is a full-viewport-height flex column, centered, background `radial-gradient(ellipse at 50% 25%, #3A241A 0%, #1A100C 70%)`: eyebrow "KILN ROASTERY", an H1 "Five stages of heat, / one cup of clarity." (`clamp(1.8rem,5vw,3.2rem)`, weight 700, color `#F5F5F5`), a sub-line about small-batch drum roasting, and a "↓ Scroll to begin" hint that bounces `translateY(0→6px)` with opacity `0.6→1` over 2.2s ease-in-out infinite. All four elements share the page's "paper reveal" entrance (see Scroll animations).
Between the radial hero background and the copy, add two plain lazy-loaded transparent 1536×1024 WebP plates: `ambient-hero-steam.webp` (four sparse warm steam curls) and `ambient-hero-embers.webp` (small amber sparks, screen blend). Generate both on perfectly flat pure-magenta `#FF00FF`, chroma-key locally to a soft alpha matte with despill, and render full-bleed at restrained opacity. Animate them with separate GSAP transform+opacity loops that rise, drift, and breathe; use a ScrollTrigger visibility gate to play both only while the hero intersects the viewport and pause both off screen. Keep them static under reduced motion and preserve a calm central copy area.
## Section 2: The Roast — five stacked cards
Directly below the hero, a framing block (eyebrow "THE ROAST", H2 "Five layers, one drum.", one line of copy) uses the same paper-reveal entrance. Then five card wrappers follow, each a plain block `position:relative; height:100vh; width:100%` with increasing `z-index` (index+1). Inside each wrapper sits a sticky layer (`position:sticky; width:100%; height:82vh`) whose `top` stair-steps per card: `top = calc(6vh + index*3vh)` (6vh→18vh across index 0–4; 18+82=100 so the last card's sticky box still fits one viewport exactly), leaving a thin edge of each earlier card peeking out behind the next. The card is `width:min(880px,92%); height:100%; border-radius:24px; padding:2.5rem`, flex column `justify-content:space-between`, `box-shadow:0 30px 60px -20px rgba(0,0,0,0.55)`, `transform-origin:50% 60%`.
Each card's face is a full-bleed local roast-stage photograph (`stage-01.webp` … `stage-05.webp`, see "## Imagery"): an absolutely-positioned `<img>` covering the card (`inset:0; width/height:100%; object-fit:cover; pointer-events:none`, empty `alt`, first card `loading="eager"`, the rest `lazy`). Beneath the photo, keep a pre-load fallback face that shifts hue across the 5 cards: bases `#1A100C → #3A1F14 → #6B3320 → #B14D2A → #FF8A65`, highlights `#3A241A → #5C3423 → #9B5A34 → #E08654 → #FFC29E`, each `radial-gradient(120% 130% at 30% 20%, <highlight>, <base>)` as the card background plus a centered abstract SVG motif per stage (Ember: concentric rings rotating 46s linear infinite; Kindle: diagonal-stripe shimmer, `background-position` sweep over 8s linear infinite; Smolder: 6×6 dot grid with a twinkling glow, 4.5s ease-in-out infinite alternate; Blaze: 3 stacked wave paths bobbing `translateY(0→-8px)` over 5s ease-in-out infinite alternate; Radiance: glowing center + 16 radiating lines rotating 30s linear infinite reverse) — the opaque photo simply covers both once decoded. Above the photo sit, in order: a lower-left readability scrim (`radial-gradient(120% 90% at 0% 100%, rgba(12,7,4,0.55) 0%, rgba(12,7,4,0.26) 36%, transparent 64%)`, `pointer-events:none`) reinforcing the photograph's built-in dark corner under the text, a giant watermark number 01–05 (`clamp(5rem,18vw,10rem); color:rgba(255,255,255,0.12)`, top-center where the photos stay quiet), and left-aligned kicker/title/copy text (z-order bottom→top: gradient+motif fallback → photo → scrim/number → copy). The five roast stages: **Ember**, **Kindle**, **Smolder**, **Blaze**, **Radiance**.
Internal parallax: number speed 0.55, motif speed 1 (skipped), text speed 1.25 — for each non-1 speed element, `gsap.to(el,{yPercent:(speed-1)*-24, ease:"none", scrollTrigger:{trigger:wrapper, start:"top bottom", end:"bottom top", scrub:1}})`. Stack-cover on the first 4 cards: `gsap.to(card,{scale:0.92, y:-28, filter:"brightness(0.6)", ease:"none", scrollTrigger:{trigger:wrapper, start:"top top", end:"bottom top", scrub:1, invalidateOnRefresh:true}})`. A vertical rail of 5 dots fixed at `right:20px; top:50%` highlights the centered card (`background:#FF8A65; transform:scale(1.7); box-shadow:0 0 10px 2px rgba(255,138,101,0.6)`) via `ScrollTrigger.create({trigger:wrapper, start:"top center", end:"bottom center", onToggle:...})`.
## Section 3: Materials — chips fall and stack
Eyebrow "MATERIALS & PROCESS" and H2 "Eight notes, one drum." use the paper-reveal entrance. Below them, a `flex-wrap` field (`max-width:900px`, gap `14px 18px`) holds 8 chip pills, each a torn-note shape via `clip-path:polygon(0 6px, 6px 0, calc(100% - 6px) 0, 100% 6px, 100% 100%, 0 100%)`, background `#2A1A12`, 1px border `rgba(255,138,101,0.25)`, padding `0.85rem 1.4rem`, shadow `0 14px 22px -14px rgba(0,0,0,0.6)`: "Ethiopia Yirgacheffe Lot", "Colombia Huila Reserve", "Brazil Cerrado Natural", "12kg Cast-Iron Drum", "First-Crack Watch", "48-Hour Degas Rest", "Hand-Sorted Green Beans", "Slow-Cooled Cupping Batch". When the section enters the viewport (`start:"top 65%"`, plays once, no reverse), a single `gsap.timeline` drops each chip in sequence, 0.09s apart: each chip tweens from `{y:-340, rotation:<random start angle -34..40deg>, opacity:0}` to `{y:0, rotation:<final resting angle -5..5deg>, opacity:1}` over 0.5s `power2.in`, then squashes to `{scaleX:1.16, scaleY:0.76}` over 0.09s `power1.out` on impact, then bounces back to `{scaleX:1, scaleY:1}` over 0.5s `elastic.out(1,0.35)`.
## Section 4: Brand story
A section with base color `#150C09`, min-height 90vh, centered content max-width 46rem. Behind the text sits an overlay div covering the whole section: `linear-gradient(120deg, rgba(255,138,101,0) 0%, rgba(255,138,101,0.45) 35%, rgba(255,90,40,0.28) 60%, rgba(255,138,101,0) 100%)` at `background-size:220% 220%`. A 5-line quote plus an attribution line, each starting at `opacity:0.2`, `clip-path:inset(0% 100% 0% 0%)` (fully clipped from the right, like an unopened paper strip) and `rotateX(-14deg)` with `transform-origin:0% 100%` and `perspective:800px`. A `gsap.timeline({scrollTrigger:{trigger:section, start:"top 80%", end:"bottom 55%", scrub:0.6}})` tweens each line to `clip-path:inset(0% 0% 0% 0%)`, `rotateX(0)`, `opacity:1` (duration 1, positioned sequentially at integer timeline offsets 0..5), so each line wipes open left-to-right and flattens as if unrolling, one at a time. Independently, the background overlay animates `opacity:0→0.9` and `background-position:"0% 50%"→"100% 50%"` scrubbed 1:1 against `start:"top bottom", end:"bottom top"` on the same section.
## Section 5: Reviews — testimonial deck shuffle
Eyebrow "REVIEWS" and H2 "Trusted by slow mornings everywhere." use the paper-reveal entrance. Below, a `min(440px,92%)`×220px deck holds 4 absolutely-positioned testimonial cards (radius 18px, background `#2A1A12`, border `rgba(255,138,101,0.2)`, shadow `0 26px 44px -20px rgba(0,0,0,0.6)`), stacked with depth index 0 (front) to 3 (back): `translateY(depth*16px) scale(1-depth*0.05) rotate(<fixed per-depth angle: 0, -6, 5, -8 deg>)`, `z-index:4-depth`. Quotes: "The first bag ruined every other coffee I own." (Noah P., Subscriber since 2023); "You can taste the batch number. That's rare." (Rin T., Café owner, Kobe); "Consistently the freshest pour-over I've made at home." (Delphine M., Subscriber since 2022); "Five stages, and you can taste every one of them." (Marcus O., Guest roaster review). On scroll-enter (`start:"top 70%"`, plays once), each card is dealt in from `y:-240`, a large random start rotation (-48/34/-30/52deg per card) and `opacity:0` to its resting stack transform over 0.65s `back.out(1.6)`, staggered 0.13s **back-card-first** (deepest card lands first, as if dealt from the bottom of the deck).
The deck is clickable/tappable (`role="button"`, keyboard-operable via Enter/Space) and shows a caption below it: "Tap the stack to shuffle ↻" (pulsing opacity 0.4↔0.9 over 2.6s). On click, the front card (depth 0) is thrown away: `gsap.to(card, {x:480, rotation:-24, opacity:0, duration:0.45, ease:"power2.in"})`. On completion, every other card's depth decreases by 1 (the thrown card wraps to depth 3 and is instantly reset there with `gsap.set`, invisible mid-pile). Every remaining card then animates to its new depth's transform: the card newly promoted to depth 0 gets `duration:0.6, ease:"elastic.out(1,0.5)"` (an elastic settle into the front slot), all others get `duration:0.4, ease:"power3.out"`. This produces an infinite, clickable card-shuffle: the top card is swiped away with a rotateZ+x throw and the next card beneath elastically pops into place.
## Section 6: Footer — the package folds shut
Background `radial-gradient(ellipse at 50% 75%, #5C3423 0%, #1A100C 75%)`, `perspective:1300px` on the section. Two full-width flap divs (`height:20vh` each, background `linear-gradient(180deg,#2A1A12,#1A100C)`, `transform-style:preserve-3d`) sit at the very top and bottom edges of the footer, framing — never covering — the centered content (eyebrow "READY TO BREW", H2 "Your next cup is still green.", copy about the 250g bag, a CTA button, fine print). The top flap has `transform-origin:top center` and a `2px` accent border along its hinge edge (bottom); the bottom flap mirrors this at `transform-origin:bottom center`. Both start folded open/back (`rotateX(110deg)` top, `rotateX(-110deg)` bottom — effectively invisible, folded away from the viewer) and scrub to `rotateX(0)` (lying flat, sealing the top and bottom edges of the page) via `gsap.to(flap, {rotationX:0, ease:"none", scrollTrigger:{trigger:footer, start:"center center", end:"bottom bottom", scrub:1}})`, so the two paper flaps visibly close in as the user finishes scrolling — the packaging sealing shut as the page's final act, while the CTA in the untouched middle band stays fully visible and clickable throughout.
The CTA button ("Start Your Subscription", background `#FF8A65`, color `#1A100C`, weight 800, padding `1rem 2.2rem`, radius 9999px, shadow `0 20px 40px -16px rgba(255,138,101,0.6)`) has a simple paper-lift hover (no pointer-follow/magnetic behavior): `transform:translateY(-4px) rotate(-1deg)` with a deeper glow shadow, via a 0.3s `cubic-bezier(0.22,1,0.36,1)` CSS transition.
## Imagery
Five local WebP photographs (`public/img/demos/stack-cards/stage-01.webp` … `stage-05.webp`, 1536×1024, each under 200KB), one per roast card in order. Generate each with an AI image tool; every prompt below shares the same style block — *"moody macro photography inside a small-batch coffee roastery, cinematic warm chiaroscuro lighting, deep charcoal shadows with ember-orange rim light, shallow depth of field, fine film grain, low-key exposure with the lower-left third falling into near-black shadow, upper-center kept quiet for an overlaid number, no people, no hands, no text, no logos, no watermarks"* — and the five dominant colors must track the card bases `#1A100C → #3A1F14 → #6B3320 → #B14D2A → #FF8A65` (a monotonic dark-to-bright, black-brown-to-apricot slope; no blues/greens except the pale green of raw beans), so the series reads as one shoot and every frame survives the stack's `brightness(0.6)` darken with its subject silhouette intact:
1. **stage-01.webp — Ember**: pale-green raw beans pouring into the dark mouth of a cast-iron drum, a faint red glow of first heat deep inside; near-black exposure, dominant `#1A100C`.
2. **stage-02.webp — Kindle**: inside the turning drum — beans just taking color against the arc of a metal fin, heat shimmer, one or two beans showing their first crack in sharp focus; scorched red-brown `#3A1F14`–`#5C3423`.
3. **stage-03.webp — Smolder**: the development stage — a band of medium-roast beans with thin threads of smoke rising; terracotta-to-cinnamon `#6B3320`–`#9B5A34`.
4. **stage-04.webp — Blaze**: macro of dark-roast beans glistening with surface oils under a strong orange rim light; `#B14D2A`–`#E08654`.
5. **stage-05.webp — Radiance**: the cooling tray — a stirring arm sweeping through spread beans, steam rising, bright top light; the highest-key frame, `#FF8A65`–`#FFC29E`, but the lower-left third stays in shadow like the rest of the series.
## Scroll animations
Register ScrollTrigger once; build every tween inside one `gsap.context()` scoped to the root so it reverts cleanly on teardown. A single reusable "paper reveal" entrance drives every heading/copy/eyebrow across the hero, roast heading, materials heading, reviews heading and footer: for every marked element inside a section, `gsap.set({opacity:0, clipPath:"inset(0% 0% 46% 0%)", rotateX:-12deg, transformPerspective:900, transformOrigin:"50% 100%"})`, then `ScrollTrigger.create({trigger:section, start:"top 78%", toggleActions:"play none none reverse"})` plays `gsap.to({opacity:1, clipPath:"inset(0% 0% 0% 0%)", rotateX:0, duration:<0.8-0.9>, ease:"power3.out", stagger:<0.1-0.12>})` on enter and reverses to the initial clipped/tilted state over 0.4s `power2.in` on leave-back — a sheet wiping open and un-tilting from a backward lean, not a plain y+opacity fade.
## Performance & accessibility
Every scroll-linked and idle animation only touches `transform`, `opacity`, `filter` and `clip-path` (GPU-compositable), never layout properties, with `will-change:transform` on chips, deck cards and stack cards. Element counts stay small and fixed (8 chips, 4 deck cards, 5 stack cards, 36 dots, 16 radiating lines) — plain DOM/SVG/CSS, no canvas needed. The five roast photographs are decorative (`aria-hidden`, empty `alt`), loaded eager on the first card and lazy on the rest, and each stays under 200KB. Attach a `ResizeObserver` to the scroll container (or, in this standalone build, to `document.body`) that calls `ScrollTrigger.refresh()` on every size change. Respect `prefers-reduced-motion:reduce` globally: render the stack cards as a static vertical list (no shrink/darken/parallax), skip the chip-drop timeline (chips render directly at their resting rotation/opacity), skip the deck's entrance and set every paper-reveal target straight to its resolved state, skip the story's scrub reveal (lines fully visible, background overlay fixed at 0.54 opacity), and fold the footer flaps to `rotateX(0)` immediately with no scrub. The deck shuffle still works under reduced motion but snaps positions instantly via `gsap.set` instead of tweening. Disable the motif keyframes, chevron bounce and shuffle-hint pulse via a matching `@media (prefers-reduced-motion:reduce)` rule, and remove the CTA's hover lift. On teardown, call `ctx.revert()` (removing every ScrollTrigger/tween created inside the context) and disconnect the `ResizeObserver`.
## Current video loading contract
This supersedes earlier blanket autoplay/preload instructions. Keep a real still frame from each background clip as its loading, failure and reduced-motion poster. Do not attach the MP4 URL or call play until the actual picture/scene is visible and needed. Use muted, loop, playsInline and initial preload=none; pause offscreen and in hidden tabs, and remove the source on teardown. A late play promise must not restart a stopped clip or pause a newer owner of the element. After rejection, keep the poster and retry on re-entry instead of retrying each animation frame.
## 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": "stack-cards",
"brand": "KILN",
"accent": "#884b29",
"title": "Your next roast, considered",
"intro": "Choose the amount you can enjoy while it is fresh. Compare the cadence and flavour before saving a subscription plan.",
"facts": [
[
"Freshness",
"250 g bags · roasted to order"
],
[
"Dispatch",
"Within 48 hours of the selected delivery date"
],
[
"Delivery",
"$5 per shipment · free over $35"
],
[
"Flexibility",
"Pause or cancel before the next roast date"
]
],
"groups": [
{
"label": "Delivery plan",
"options": [
{
"title": "One bag · $18 every 4 weeks",
"detail": "250 g for occasional brewing. Example delivered total: $23 per shipment."
},
{
"title": "Two bags · $36 every 4 weeks",
"detail": "500 g for a daily ritual. Example delivered total: $36 with free shipping."
}
]
},
{
"label": "Roast preference",
"options": [
{
"title": "Filter",
"detail": "Lighter roast; a cleaner cup and brighter fruit notes."
},
{
"title": "Espresso",
"detail": "More developed roast; fuller body and a rounder finish."
}
]
}
],
"notes": [
"Whole beans. Illustrative prices in USD; taxes depend on destination. No subscription or recurring charge is created in this sample."
]
}
- 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.