MOTION PALETTE
← すべてのデモ

Curtain Routes

InteractionGSAP / SVG / Imagery

フルスクリーンで開く ↗

枠の中でスクロール・クリックして動きを試せます。端末指定は実寸表示です。幅が足りない場合は横にスクロールできます。

再現プロンプト

架空の巡回展ギャラリーANTECHAMBERの4つの仮想ページを、5枚の斜めカーテンが遷移中に閉じて開くSPA風ワイプで切り替える1ページサイト。Exhibitionsの展覧会リストは文字だけの索引のまま、行にホバーしたときだけその展覧会のキービジュアル(3:2のローキー写真6点)がカーソル脇に浮かび、コンテンツと同じ左→右のスリバーワイプで現れる。 配色・構成・動きの数値をまとめた指示書です。 お使いのAIに貼り、素材やお店の情報を伝えて実装してください。仕上がりは利用するAIと素材によって変わります。

Build a single self-contained HTML file (inline CSS/JS) for "ANTECHAMBER", a fictional rotating exhibition gallery presented as a single-page app with four virtual rooms: Exhibitions, Artists, Visit, About. Clicking a nav item never scrolls the page — it triggers a five-band diagonal curtain wipe that sweeps closed, swaps the room underneath at full coverage, previews the destination room's title sliding in ahead of the reveal, then sweeps open again. A dedicated "Back" control literally reverses the exact GSAP timelines used for the last forward transition (true .reverse() playback, not a rebuilt mirror animation), and each room owns its own background color and its own internal scroll container. May use a CDN script for gsap. The page scrolls with the normal window inside each virtual room (no outer page-level scroll needed for the standalone output — the four rooms are absolutely stacked and only one is interactive at a time).

## Art direction
- Dark theatrical base: chrome background #0B0C0D, light text #F2F1ED, accent emerald #34D399 used only for eyebrow labels, active nav underline, hover borders, and the curtain bands' thin edge highlight.
- Each of the four rooms has its own solid background hue that fills its full-bleed panel: Exhibitions #0E1F1B (deep teal-black), Artists #1D1622 (plum-black), Visit #241C10 (amber-black), About #15181C (slate-black). Content stays flush ink #0B0C0D/#F2F1ED text pairing is not used inside rooms — rooms use their own tint as background with #F2F1ED text at ~72% opacity for body copy.
- A 1px outer border (rgba(242,241,237,0.14)) frames the whole page. A fixed top nav (64px, translucent chrome background, blurred, 1px bottom hairline) shows a wordmark "ANTECHAMBER" with sub-label "A Rotating Exhibition Series", the four room links (active one underlined in accent), and a "‹ Back" pill button that is invisible/disabled (opacity 0, pointer-events none) until at least one forward transition has happened, then fades to opacity 1. A persistent 40px bottom strip (same translucent chrome treatment) shows "© 2026 ANTECHAMBER" and a contact email — this strip and the nav are the only chrome that survives every transition unchanged.
- Headings use a serif (Georgia, "Times New Roman", serif) weight 700; body copy uses the system sans stack.

## The curtain transition (must match exactly)
Five (CURTAIN_COUNT = 5) absolutely positioned full-height band elements sit in an overlay above all rooms (z-index above room content, below the nav/footer chrome). Each band's CSS width is 100/5% + 6% (CURTAIN_BAND_OVERLAP = 6, extra width so the skewed edges never leave a seam between bands) and each is skewed with transform: skewX(-14deg) (CURTAIN_ANGLE = 14) so its vertical edges read as a diagonal slice; bands sit left-to-right tiling the viewport. At rest every band sits at translateY(110%), fully below the viewport and invisible.

Forward transition (goTo(targetIndex)): build two paused GSAP timelines.
1. coverTl: set all bands to yPercent 110, then tween them to yPercent 0 over 0.5s (CURTAIN_COVER_DURATION) with ease "power3.inOut", staggered 0.055s apart (CURTAIN_STAGGER) so the diagonal sweep visibly travels across the screen band by band. A large centered title element (set to the destination room's name) is tweened in from { yPercent: 30, opacity: 0 } to { yPercent: 0, opacity: 1 } over 0.45s (TITLE_IN_DURATION) with ease "power2.out", starting 0.25s (TITLE_IN_OVERLAP) before the band stagger finishes — so the destination's name is legible on the solid curtain slightly ahead of full coverage, exactly as the room underneath is about to swap.
2. revealTl: tween the title out first — { yPercent: -24, opacity: 0 } over 0.35s (TITLE_OUT_DURATION) with ease "power2.in" — then tween all bands to yPercent -110 over 0.55s (CURTAIN_REVEAL_DURATION), same 0.055s stagger, ease "power3.inOut", starting 0.15s (TITLE_OUT_OVERLAP) before the title-out tween finishes.
coverTl.play(0); its onComplete callback swaps the visible room (toggle a class that sets opacity 1/0 and pointer-events auto/none on the room panels, and update an "inert" attribute on the hidden ones for accessibility) and updates a CSS custom property carrying the new room's background color, then immediately plays revealTl from 0. revealTl's onComplete finalizes the "current room" index and clears an isAnimating guard that blocks re-entrant clicks during a transition. Each transition's {from, to, coverTl, revealTl} is pushed onto a history stack.

Back transition (goBack()): pop the most recent history entry and call entry.revealTl.reverse() — literally reversing the exact same timeline that revealed the current room (bands sweep from open back to fully covering, title reappears mid-reverse since it lives inside that same timeline). Its onReverseComplete callback swaps the room back to entry.from and then calls entry.coverTl.reverse() — reversing the exact timeline that originally covered the screen (bands sweep from covering back down off-screen, title exits the way it entered, uncovering the previous room). Its onReverseComplete restores the "current room" index and clears the animating guard. Because both directions replay the identical timeline objects instead of constructing new ones, the back transition is genuine reverse playback, not a symmetric re-implementation.

## Section: Exhibitions (initial/default room)
Eyebrow "Current & Upcoming", serif H1 "Exhibitions", intro line about six rotations a year, six-week runs. Below it, six plain rows — no numbered prefixes — each showing a show title (serif), the artist or "Group Show", and a date range, separated by 1px hairlines: "Low Tide, High Light" (Reiko Sasaki, 12 Mar – 28 Apr), "Static Bloom" (Group Show, 3 May – 15 Jun), "The Long Room" (Théo Marchetti, 22 Jun – 10 Aug), "Afterimage" (Nadia Okoye, 18 Aug – 21 Sep), "Quiet Machinery" (Group Show, 28 Sep – 9 Nov), "Vantage" (Priya Nair, 16 Nov – 20 Dec).

Row hover — the room's one piece of imagery: the list itself stays pure typography (no thumbnails inline, no layout shift), but hovering a row floats that exhibition's key visual near the cursor. One reusable preview element is mounted once, absolutely positioned over the rooms at a z-index above the room content but *below* the curtain overlay, so a transition wipes over it. It is 320×213px (EX_PREVIEW_WIDTH × EX_PREVIEW_HEIGHT — 3:2, matching the artwork), 1px rgba(52,211,153,0.35) border, ink #0B0C0D backing, shadow "0 18px 44px rgba(0,0,0,0.55)", with a bottom caption bar ("Title — Artist", 0.66rem uppercase, over a transparent→rgba(11,12,13,0.9) upward gradient). Position: reuse the same pointermove listener that drives the spotlight to record the pointer in root-relative pixels; each frame lerp a current position 18% of the way toward it (EX_PREVIEW_POS_LERP), offset by (+36px, -(213+28)px) (EX_PREVIEW_OFFSET_X / EX_PREVIEW_OFFSET_Y) so the card floats above-right of the cursor, then clamp x to 12px (EX_PREVIEW_MARGIN) inside the root and y between the nav's bottom edge and the footer's top edge so it never collides with the persistent chrome. Write that as 'transform: translate(px, py)' on the outer wrapper every frame; GSAP never touches this element. Enter/exit live on a separate inner element and reuse the page's sliver grammar rather than a fade: on pointerenter tween 'clip-path: inset(0 100% 0 0) → inset(0 0% 0 0)' plus opacity 0→1 over 0.45s (EX_PREVIEW_ENTER_DURATION) ease "power3.out"; on pointerleave reverse it over 0.3s (EX_PREVIEW_EXIT_DURATION) ease "power2.in". The hovered row also tints its title to the accent and its bottom hairline to rgba(52,211,153,0.5). Both goTo() and goBack() snap the preview to hidden instantly before starting a transition, so a curtain sweep never carries a stale image. Hidden entirely under 720px and on '(hover: none)' devices — the index stays a plain readable list.

## Section: Artists
Eyebrow "Represented Artists", H1 "Artists", intro line about a small fixed roster plus a rotating cast for group shows. A 2-column grid (1px hairline gaps, collapsing to 1 column under 720px) of four artist cards — Reiko Sasaki (Painting), Théo Marchetti (Sculpture), Nadia Okoye (Photography), Priya Nair (Installation) — each with a one-line practice note.

## Section: Visit
Eyebrow "Plan Your Visit", H1 "Visit". A 2-column layout (stacks under 720px): left column lists Hours, Address, Admission, and Access as label/value rows; right column shows a small inline SVG floor plan (three outlined rooms labeled "Antechamber", "Main Hall", "Study", connected by short doorway-gap lines, stroked in the accent color) — a literal miniature of the gallery's own room-to-room structure.

## Section: About
Eyebrow "The Gallery", H1 "About", two paragraphs on the gallery's 2016 founding as a threshold space between two larger institutions and its policy of never extending a run, and a pull-quote: "A gallery is a curtain you walk through, not a wall you stand in front of." attributed to a 2016 founding note.

## Imagery
Six local WebP key visuals (public/img/demos/curtain-routes/, 640×426, 3:2, each well under 60KB), one per exhibition row in order, named after the show's slug: ex-low-tide-high-light.webp, ex-static-bloom.webp, ex-the-long-room.webp, ex-afterimage.webp, ex-quiet-machinery.webp, ex-vantage.webp. They appear only inside the hover preview — never inline in the list, never as a page background. Generate each with an AI image tool; every prompt ends with the same style block so the six read as one gallery's annual programme: "contemporary art exhibition key visual, moody low-key gallery documentation photograph / abstract artwork reproduction, dark desaturated palette sitting comfortably on a near-black deep-green room (#0E1F1B), muted tones with at most one restrained accent, matte finish, subtle film grain, cinematic quiet atmosphere, no people, no text, no letters, no gallery labels, no watermark, no bright white walls". Subjects, in row order:
1. Low Tide, High Light — abstract painting close-up: a pale horizontal gradient band like a low-tide horizon, ash-mixed matte texture, wet dark sand tones along the bottom, a thin high light only at the top; near-monochrome with faint blue-grey.
2. Static Bloom — an electrostatic flower shape floating in a dark room: fine CRT-noise grain gathering into an opening abstract bloom, a faint incandescent core, scan-line fraying at the edges.
3. The Long Room — a long dim gallery seen head-on down its depth, with a single white plaster cast of a void (a concave "something removed" form) lit weakly from below, throwing a long shadow.
4. Afterimage — an interior photographed the moment after someone left: a slightly dented chair cushion, corridor light spilling through a door left open, a faint long-exposure blur; unmistakably unoccupied.
5. Quiet Machinery — an installation of stopped machines: old machine tools and motors scattered through a dark space under dust, only dull metal reflections catching light; a soundless factory still life.
6. Vantage — looking up at a timber viewing platform / scaffold built in a dark exhibition space, nobody on it, one shaft of spotlight catching only the platform's edge.

The 1536×1024 generations are resized to 640×426 and encoded to WebP q82 before being placed in public/.

## Scroll animations (within each room's own scroller)
Every room mounts its own internal scroll container (overflow-y: auto, container-type: size, cqh section heights) — ScrollTrigger's scroller option is set per-room to that container, never the window. All eyebrow/heading/paragraph/row/card elements use one shared reveal grammar: a thin accent "sliver" wipe — set clip-path: inset(0 100% 0 0) and opacity 0.001, then on ScrollTrigger onEnter (trigger = the element, start "top 90%", toggleActions "play none none reverse") animate to clip-path: inset(0 0% 0 0) and opacity 1 over 0.7s (CONTENT_REVEAL_DURATION) with ease "power3.out", staggered 0.07s (CONTENT_REVEAL_STAGGER) per row/card group. onLeaveBack reverses over 0.3s with ease "power2.in". This left-to-right wipe intentionally echoes the big curtain mechanic at a small scale; no element anywhere uses a plain translateY + opacity fade, and no numbered "01/02/03" row styling appears anywhere.

## Ambient motion and interaction
A soft 640px radial accent glow ("spotlight") sits at low z-index above the room background and below the content, repositioned every animation frame: it lerps 8% of the way (SPOTLIGHT_LERP = 0.08) toward the live cursor position each frame, plus a constant idle Lissajous drift of amplitude 18px (SPOTLIGHT_DRIFT_AMP) at 0.15 rad/s (SPOTLIGHT_DRIFT_SPEED) so it keeps breathing even with no pointer input — the page's one continuous mouse-reactive element, and the reason the room is never a fully static frame.

## Performance & accessibility
Guard all nav/back clicks with an isAnimating ref so transitions can never overlap or be re-entered mid-sweep. The six key visuals are never preloaded: the preview holds one '<img loading="lazy" decoding="async">' with no 'src' until the first hover, and each row's hover assigns its own path once (guarded by a data attribute), so an untouched list downloads no imagery at all. The preview card and its image are decorative — 'aria-hidden' on the wrapper, empty 'alt' — and the exhibition rows remain plain readable text with no hover support. Pause the spotlight's per-frame follow loop via an IntersectionObserver on the page root when off-screen, and resume on re-entry. Give inactive room panels pointer-events: none and an inert attribute so their content and focusable elements are unreachable and hidden from assistive tech while covered by the curtain. Respect prefers-reduced-motion: shrink the cover/reveal tween durations to about 30% of normal, disable inter-band stagger entirely (all bands move together), shorten title tweens to ~0.1–0.15s, and slow the spotlight's idle drift time-scale to 20%. Nav links, the Back button, and all interactive rows/cards have visible hover and active states with sufficient contrast against each room's background. The curtain bands, curtain title, floor-plan SVG, and spotlight are aria-hidden or purely decorative; each room's actual content remains readable as plain static text regardless of animation support.