枠の中でスクロール・クリックして動きを試せます。端末指定は実寸表示です。幅が足りない場合は横にスクロールできます。
再現プロンプト
架空のパリの香水メゾン「TOURNELLE」の1ページサイト。12枚の香りカードによる3Dリングカルーセルを核に、マレ地区の工房を写したローキーな額装写真でメゾンの背景にも厚みを持たせる。リングは慣性ドラッグとスクロールで回り、reduced-motion時は静的グリッドに畳まれる。 配色・構成・動きの数値をまとめた指示書です。 お使いのAIに貼り、素材やお店の情報を伝えて実装してください。仕上がりは利用するAIと素材によって変わります。
Build a single self-contained HTML file (inline CSS/JS) that is the complete one-page website for "TOURNELLE" — a fictional Paris perfume house whose entire identity is "la roue des parfums" (the wheel of scents). The signature is a rotating 3D ring carousel: 12 scent cards arranged as a true cylinder in CSS 3D, turned BOTH by pointer drag (with inertia and snap) and by scroll inside a pinned section. Use CSS 3D transforms for all geometry and GSAP + ScrollTrigger via CDN (`https://unpkg.com/gsap@3/dist/gsap.min.js`, `https://unpkg.com/gsap@3/dist/ScrollTrigger.min.js`) for tweens, pinning and scrubbing — no WebGL. Cards, wheels and swatches remain CSS gradients + typography; one local low-key atelier photograph is used only in Section 4. The page scrolls normally with the window scroller (build one long document-flow page; do not add an internal scroll container).
## Art direction
Background `#141117` (aubergine-tinted ink), card surface `#1D1922`, foreground ivory `#F1EAE0`, muted copy `#9A9086`, accent champagne gold `#C8A45D`, hairline `rgba(200,164,93,0.18)`. Headings and scent names use a high-contrast display serif with a true italic (Cormorant Garamond-style, weights 500–700); UI/body text uses a clean geometric sans (Jost-style, 400–600). Eyebrows: 0.7rem, letter-spacing 0.28em, uppercase, muted, weight 600. Headings mix roman + one italic accent word tinted `#C8A45D` (e.g. "Every family, *thirty degrees* apart."). Section padding `clamp(3rem,8vh,6rem) clamp(1.25rem,5vw,3rem)`, 1px hairline top border between sections. Exactly two easings sitewide: `power3.out` for every entrance/snap, `power2.out` for reversals and the scroll-snap. Never use a marquee, magnetic buttons, count-up stats, or a numbered 01/02/03 service list.
## The scent catalogue (data)
12 fictional eaux de parfum = the 12 families of a fragrance wheel, 30° apart. Each entry: number, name, family (FR/EN), a notes line, a `wheel` hex (used in the conic wheel graphics) and a `from`/`to` gradient pair (card backdrop). Data (no / name / family / notes / wheel / from / to):
- 01 / Fleur de Papier / Florale · Floral / "orange blossom, white musk, rice paper" / #D9A6AC / #A96A73 / #4A2830
- 02 / Iris Poudré / Poudrée · Powdery / "iris butter, violet, cool talc" / #B9B1CC / #756C92 / #322C46
- 03 / Rose Fumée / Florale fumée · Smoky floral / "damask rose, cade smoke, black plum" / #C2848E / #8A4E58 / #3A2026
- 04 / Ambre Orage / Ambrée · Amber / "amber resin, storm air, benzoin" / #DBA057 / #9A6224 / #46290C
- 05 / Épice Double / Épicée · Spicy / "cinnamon bark, pink pepper, clove" / #C97F4F / #97522B / #431F0F
- 06 / Cuir Volant / Cuir · Leather / "suede, saffron, birch tar" / #AD7C55 / #7A4E2E / #331D10
- 07 / Cèdre Minuit / Boisée · Woody / "atlas cedar, black tea, vetiver root" / #7C9B84 / #44614C / #16241B
- 08 / Vétiver Gris / Boisée sèche · Dry woods / "vetiver, flint, dry papyrus" / #93A896 / #5A6F5E / #232E26
- 09 / Mousse Ancienne / Chyprée · Chypre / "oakmoss, bergamot, patchouli" / #8FA06B / #5A6C3B / #242D14
- 10 / Figue Verte / Verte · Green / "fig leaf, crushed stems, coconut wood" / #A3B984 / #66794A / #29321C
- 11 / Agrume Premier / Hespéridée · Citrus / "yuzu zest, petitgrain, neroli" / #DCC077 / #9E8340 / #443716
- 12 / Brume Marine / Aquatique · Aquatic / "salt fog, ambergris, driftwood" / #9FB4BE / #5F7883 / #26343B
The recurring "scent wheel" graphic (hero + footer, both `aria-hidden`) is a pure-CSS annulus: `conic-gradient(from -15deg, wheel1 0deg 30deg, wheel2 30deg 60deg, … wheel12 330deg 360deg)` with a spoke overlay `repeating-conic-gradient(from -15deg, rgba(20,17,23,0.9) 0deg 0.5deg, transparent 0.5deg 30deg)` layered on top, a centre hole (`inset:19%`, background `#141117`, 1px hairline border, border-radius 50%) and `border-radius:50%` — always rotating (`@keyframes` to `rotate(360deg)`, linear, infinite).
## Section 1: Hero
Topbar: serif wordmark "TOURNELLE" (accent gold, letter-spacing 0.14em) + eyebrow "MAISON DE PARFUM — PARIS · MMXIX". Centre: two-line serif H1 — "Twelve scents," (roman ivory) / "one turning wheel." (italic, accent gold) at `clamp(2.4rem,7vw,4.6rem)`; lede "A fragrance house built on a single instrument: the wheel. Twelve eaux de parfum, thirty degrees apart, blended where the families meet."; two pill CTAs — "Turn the wheel" (filled accent, smooth-scrolls to the ring section) and "Visit the atelier" (hairline outline). Behind the text, the scent-wheel annulus at `clamp(480px,88vw,860px)` diameter sits centred at `top:62vh` (its lower half bleeding off-screen; hero has `overflow:hidden`), opacity 0.85, soft glow `box-shadow: 0 0 120px rgba(200,164,93,0.12)`, rotating one full turn every **90s linear**. Cursor parallax: pointermove over the hero maps the pointer to `[-1,1]` per axis and pulls the wheel's wrapper by **±14px** on x/y through `gsap.quickTo(…, {duration:0.6, ease:"power3"})`; pointerleave returns it to 0. Hero entrance (mount, once): the two H1 lines + lede + CTA row start at `rotate:-4deg, y:30, opacity:0, transformOrigin:"0% 100%"` and settle to zero over **0.9s power3.out, stagger 0.12, delay 0.1** — the page's "turn into place" language, never a plain fade-up.
## Section 2: La Roue — the pinned 3D ring carousel (the signature)
Full-viewport section (eyebrow "LA COLLECTION", heading "Every family, *thirty degrees* apart."), pinned by ScrollTrigger for **+280% of viewport height** (`start:"top top"`, `end:"+=" + innerHeight*2.8`, `pin:true, scrub:true, anticipatePin:1, invalidateOnRefresh:true`).
### Ring geometry (fixed, named constants)
- 12 cards, `STEP = 30°`. Card size **180×260px**, border-radius 14px, 1px border `rgba(241,234,224,0.14)`.
- Ring radius from the regular-polygon circumradius formula: `R = round((CARD_W × 1.28) / (2 × tan(π/12))) = 430px` (1.28 is the circumferential spacing factor).
- Stage: a 0×0 anchor centred in the section with `perspective:1400px`; inside it a `transform-style:preserve-3d` tilt wrapper idles between `rotateX(-2.5deg)` and `rotateX(-5.5deg)` (9s ease-in-out alternate infinite — the ring is never static); inside that, the rotating ring container (`preserve-3d`, one `rotateY(θ)` write per frame).
- Each card is placed ONCE: `translate(-50%,-50%) rotateY(i×30deg) translateZ(430px)` (+ a per-frame `scale(s)` appended, see fog). Cards are `preserve-3d` with two faces, both `backface-visibility:hidden`: the front face carries the scent content over its gradient backdrop; the back face (`rotateY(180deg)`) shows the house monogram — an italic serif "T" in accent gold over `#1D1922` with horizontal hairline pinstripes (`repeating-linear-gradient`, 1px lines every 12px, `rgba(200,164,93,0.07)`) and a "TOURNELLE — PARIS" microlabel — so the far side of the ring reads as the backs of a physical card deck.
- Card front content (all real DOM text, centred, ivory): top row "N° 04" / family EN (0.55rem, ls 0.14em); middle "TOURNELLE" microlabel (ls 0.3em), serif name (1.18rem, weight 600), a 26×1px rule, the notes line (0.6rem); bottom row "EAU DE PARFUM" / "50 mL" (0.5rem). Backdrop: `radial-gradient(130% 80% at 50% -12%, rgba(255,255,255,0.16), transparent 55%), linear-gradient(160deg, from 0%, to 100%)` on a layer that is **130% of the card width, offset left:-15%** (parallax headroom), clipped by the face's `overflow:hidden`.
### Rotation model — two composed sources
One scalar per source, composed every frame: `θ = scrollRotation + dragRotation`.
- **Scroll**: the pin's `onUpdate` only writes a target — `scrollTarget = progress × 360` (exactly one full revolution across the pin). A rAF loop lerps `scrollCurrent += (scrollTarget − scrollCurrent) × 0.12` per frame (snapping equal when within 0.01°) so scrub motion is always paced to the display, then applies the frame.
- **Drag**: pointerdown on the ring area (`cursor:grab`, `touch-action:pan-y` so vertical page scroll still works on touch, `user-select:none`, pointer capture) kills any running tween; each pointermove adds `dx × 0.25deg` to `dragRotation` (**0.25 deg/px — a 120px drag moves exactly one card**) and folds the instantaneous speed into a smoothed velocity `v = v×0.8 + (dx×0.25/dtMs)×0.2` (deg/ms). On release: if the pointer travelled under 6px and went down on a card, treat it as a tap and tween that card to the front by the shortest path (0.55s power3.out); else if `|v| > 0.005` start inertia — per frame `dragRotation += v×dtMs; v ×= 0.94^(dtMs/16.7)` until `|v| ≤ 0.005 deg/ms` — then **snap**: tween `dragRotation` so the composed total `scrollTarget + dragRotation` lands on the nearest multiple of 30°, over **0.55s power3.out**.
- **Scroll snap**: the ScrollTrigger `snap.snapTo` is a function, not `1/12` — `snapTo(value) = clamp01((round((value×360 + dragRotation)/30)×30 − dragRotation)/360)` — so scroll always comes to rest with a card dead-centre even after the user has dragged an arbitrary offset. `duration:{min:0.25,max:0.6}, ease:"power2.out", delay:0.1`.
- **Keyboard**: the ring area is focusable (`tabIndex=0`, `role="group"`, aria-label explaining drag + arrow keys); ArrowLeft/ArrowRight tween the composed total one 30° step (0.55s power3.out).
### Depth fog, active card, parallax (per frame, per card)
For card i compute `Δ = ((i×30 + θ) normalized to [-180,180))` and `t = (cos Δ + 1)/2` (1 = facing the camera, 0 = facing away):
- `opacity = 0.22 + 0.78 × t^1.5` — back-of-ring cards dim to 0.22, showing ghostly monogram backs.
- `filter: brightness(0.5 + 0.5×t)` — depth cue on top of natural perspective foreshortening.
- Front emphasis: `scale = 1 + 0.07 × max(0, (cosΔ − cos30°)/(1 − cos30°))` — only the card within ±30° of centre ramps up to **1.07×**; appended to the card's static transform.
- **Counter-rotation parallax**: the card's backdrop layer shifts `translate3d(−clamp(Δ, −60°, +60°) × 0.45px, 0, 0)` — up to ±27px, exactly the 15% overhang, so the gradient inside each card slides against the ring's turn like light moving across a bottle.
Per-frame DOM writes: 1 ring transform + 12×(transform/opacity/filter) + 12 backdrop transforms ≈ **49 style writes** — no framework re-render in the hot path; the loop is started/stopped by an IntersectionObserver on the section (threshold 0.05).
### Readout
Below the ring, a live readout for the front card — `front = ((round(−θ/30) mod 12)+12) mod 12`, updated only when the index actually changes: accent line "N° 04 / 12", serif name, "Ambrée · Amber — amber resin, storm air, benzoin", plus the hint "Drag the deck, click a card, or keep scrolling — the wheel turns either way." The whole 3D stage sits in a wrapper that gets a uniform `scale(clamp(width/860, 0.52, 1))` from a ResizeObserver (applied to the wrapper only, never to per-frame-animated elements, so the geometry math never changes on mobile).
## Section 3: L'Anatomie — three turns of one scent
Eyebrow "L'ANATOMIE", heading "Three turns of *one* scent.", lede "Every Tournelle eau is built like the wheel it came from — it keeps turning after you wear it." Three hairline-separated rows (max-width 640px): "La tête / The first turn · 0–20 min", "Le cœur / The second turn · 20 min–4 h", "Le fond / The last turn · 4–12 h", each with 2–3 sentences of copy (rim = citrus/spice, spokes = florals, hub = woods/amber). Each row's swatch is a 96px CSS donut — `conic-gradient` of three family colours in 120° thirds (tête #DCC077/#C97F4F/#A3B984, cœur #C2848E/#B9B1CC/#D9A6AC, fond #7C9B84/#DBA057/#AD7C55) with a centre hole (`inset:26%`, bg colour, hairline border). On scroll entry (trigger "top 85%", reversible) the donut spins **from rotate(-120deg) + opacity 0 to rest over 0.9s power3.out** — a quarter-turn of the wheel, echoing the carousel.
## Section 4: La Maison — the atelier
Eyebrow "LA MAISON", heading "One atelier, *twelve* spokes." Use a two-column body (stack to one column below 720px). Left: frame the local 1024px-square image "/img/demos/spin-deck/atelier-still.webp" in a 4:3 crop with object-fit cover and lazy loading. It shows an unlabeled perfume bottle and radial brass maturation apparatus in a low-key Marais workshop. Tone it into the page with saturate(0.72), contrast(1.06), brightness(0.72), an aubergine diagonal veil, double champagne/ivory hairlines and a deep shadow. Overlay at the lower right a compact circular-text emblem — an SVG (`viewBox="0 0 300 300"`) with the text "TOURNELLE · PARIS · LA ROUE DES PARFUMS · MMXIX ·" on a circular path (r=110, uppercase geometric sans 17px, ls 0.14em, muted), rotating **40s linear infinite**, with a static italic serif "T" monogram over a translucent aubergine disc. Right: two paragraphs — the house blends in a converted carousel workshop in the Marais; twelve maturation racks radiate like spokes and each turns a full revolution every four minutes; new scents are found "by standing between two racks and noticing what the air does where the families overlap." Do not alter the 12-card ring carousel to accommodate this image.
## Section 5: Footer
Centred: eyebrow "LE CERCLE", heading "Come *full circle*.", mailto link "hello@tournelle-parfums.com ↗" (hairline underline → accent on hover), meta row "TOURNELLE © 2026" / "LE MARAIS, PARIS — BY APPOINTMENT ONLY". Behind everything, the same scent-wheel annulus at `clamp(420px,70vw,720px)`, opacity 0.14, rotating **120s linear** — the wheel completing itself, still turning when nothing else moves.
## Shared reveal language
Every eyebrow, heading, lede, row and footer element (not the ring cards) enters by "turning into place": initial `rotate:-4deg, y:24, opacity:0, transformOrigin:"0% 100%"`, animating to zero over **0.7s power3.out** via `ScrollTrigger.batch` at `start:"top 82%"` with **stagger 0.08**; scrolling back up reverses to the initial state over 0.3s power2.out. No element anywhere uses a plain y-only fade-up.
## Performance & accessibility
Respect `prefers-reduced-motion`: collapse the ring into a **static grid** of the 12 card fronts (`repeat(auto-fit, minmax(150px,1fr))`, no pin, no readout), disable the ambient wheel/emblem/tilt animations via the media query, and skip all entrance offsets (content renders at rest). The rAF loop runs only while the ring section intersects the viewport (IntersectionObserver, threshold 0.05) and is fully cancelled — along with the ScrollTrigger pin, ResizeObservers, tweens and pointer/key listeners — on teardown. Animate only transform/opacity/filter; animation state lives in plain variables (never per-frame framework state; the only state write is the readout's index change). The drag surface keeps `touch-action:pan-y` so the carousel never traps vertical scrolling on touch devices, and the ring is fully operable by keyboard. All copy, names and the brand are original and fictional; no real perfume house, product or address is referenced.
## 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": "spin-deck",
"brand": "TOURNELLE",
"accent": "#78553f",
"title": "Compose your scent visit",
"intro": "Choose a starting family, then keep a note of what you would like to explore on skin.",
"facts": [
[
"Collection",
"Twelve scents arranged by family"
],
[
"Exploration",
"Compare the first impression, heart and dry-down."
],
[
"Format",
"A shortlist for a consultation; no purchase is made here."
]
],
"groups": [
{
"label": "Starting family",
"options": [
{
"title": "Floral & powdery",
"detail": "Begin with Fleur de Papier, Iris Poudré or Rose Fumée."
},
{
"title": "Woods & amber",
"detail": "Begin with Cèdre Minuit, Vétiver Gris or Ambre Orage."
},
{
"title": "Green & fresh",
"detail": "Begin with Figue Verte, Agrume Premier or Brume Marine."
}
]
}
],
"brief": "Name any scent that caught your attention and the character you are looking for.",
"notes": [
"Scent notes describe fictional concepts. Availability and ingredients would be confirmed before a real purchase."
]
}
- 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.