枠の中でスクロール・クリックして動きを試せます。端末指定は実寸表示です。幅が足りない場合は横にスクロールできます。
再現プロンプト
架空のデザインスタジオ「ARRAY」のポートフォリオサイト。同じ12件の作品タイルが、ピン留めされた1つのセクション内でスクロールに応じてIndex(一覧)⇄Grid(均等グリッド)⇄Stack(傾いた積み木)⇄Poster(1枚の構成)へと、getBoundingClientRectの差分に基づく自前FLIP実装(First/Last/Invert/Play)で物理的に再配置される。name/year/hueの並び替えキーも同じFLIPで再生され、キャプションは常に打ち消しスケールで可読性を保つ。 配色・構成・動きの数値をまとめた指示書です。 お使いのAIに貼り、素材やお店の情報を伝えて実装してください。仕上がりは利用するAIと素材によって変わります。
Build a single self-contained HTML file (inline CSS/JS; you may load GSAP + ScrollTrigger from a CDN — `https://unpkg.com/gsap@3/dist/gsap.min.js`, `https://unpkg.com/gsap@3/dist/ScrollTrigger.min.js` — no other runtime dependencies, no external images or fetch calls) for "ARRAY" — a fictional design studio ("Design Studio — Toronto / Seoul"). The entire page's motion language is "layout is not fixed": one set of twelve project tiles physically relocates between four states using a hand-written FLIP implementation (First/Last/Invert/Play on `getBoundingClientRect()` deltas — do not use a pre-built FLIP plugin; measure and invert the transforms yourself, then hand the resulting tween to GSAP). The page scrolls normally with the window (the inner-scroll-container constraint described below is specific to the MOTION PALETTE embed and not needed standalone — build one long document-flow page and drive ScrollTrigger off the default window scroller).
## Art direction
Background `#121113`, card surface `#1C1B1F`, hairline border `rgba(245,244,247,0.09)`, foreground `#F3F1F5`, muted `#9B96A3`, fixed brand accent `#AB47BC` used only for CTAs, the active sort pill, the active stage dot and the heading's small square icon — never for tile artwork. Headings use a bold geometric sans (Space Grotesk-style, weight 700); body copy uses a plain grotesque (Inter, 400–600); every label, sort control and eyebrow uses a monospace face (Space Mono). The hero, capabilities section and footer all share a faint 44px×44px graph-paper grid background (`linear-gradient` hairlines both axes, `rgba(245,244,247,0.055)`) that echoes the tile grid even where no tiles are present. Never use a marquee, a magnetic button, a scroll-triggered count-up stats block, or a generic numbered "01/02/03" service-row list anywhere on the page, and never reveal content with a plain y+opacity fade-up.
## The FLIP mechanism (build this once, drive everything with it)
Keep one persistent DOM node per work, keyed by a stable id, in a `Map<id, element>`. Before any state change that will move or resize tiles (a layout-mode switch or a re-sort), loop over every tile and store its **First** rect (`el.getBoundingClientRect()`) — read this while the tiles still show their *current*, possibly-transformed, on-screen position. Apply the state change (swap a CSS mode class on the container and/or re-order the underlying array so the DOM nodes are re-parented in a new sequence via matching keys). In a layout-effect that runs after the browser has committed the new layout: first read each tile's **current rotation** via `gsap.getProperty(el,"rotation")` into a map, then *neutralize* every tile's transform (`gsap.set(el,{x:0,y:0,scaleX:1,scaleY:1,rotation:0})`) — this is mandatory before measuring **Last**, otherwise a lingering transform from the previous animation corrupts the new reading. Now measure each tile's **Last** rect. For every tile compute `dx = first.left-last.left`, `dy = first.top-last.top`, `sx = first.width/last.width`, `sy = first.height/last.height`, and the tile's *target* rotation for the new mode (`0deg` in Index/Grid/Poster; a per-tile tilt in Stack, see below). **Invert+Play**: `gsap.fromTo(el, {x:dx,y:dy,scaleX:sx,scaleY:sy,rotation:prevRotation}, {x:0,y:0,scaleX:1,scaleY:1,rotation:nextRotation, duration:0.65, ease:"power3.inOut", delay: index*0.025})` — a `0.65s` interpolation of position, size AND rotation together, staggered `0.025s` per tile (a `0.275s` wave across all twelve). Readability during the flight is the third required signature: give every tile a nested caption wrapper that receives its own **counter-scale** tween in parallel — `gsap.fromTo(label, {scaleX:1/sx, scaleY:1/sy}, {scaleX:1, scaleY:1, duration:0.65, ease:"power3.inOut", delay: index*0.025})` — so while the outer tile box freely stretches or squashes between two very different aspect ratios, the name/year/hue caption inside it never visibly distorts. On first mount, skip the tween entirely and just set every tile to its resting transform.
## Signature 1 — Selected Works: one pinned section, four re-flowed states
Twelve fictional projects with `id/name/year/hue`: Anchor Type (2023, 262°), Basalt (2019, 206°), Civic Index (2024, 151°), Drift Atlas (2021, 18°), Echo Press (2018, 322°), Fathom (2022, 191°), Glasshouse (2020, 42°), Harbor Light (2025, 284°), Isotope (2017, 101°), Juniper Field (2023, 58°), Kite & Rope (2021, 339°), Lucid Grain (2019, 128°). Each tile's artwork must read as a flat poster/packaging-style thumbnail whose **dominant color is the work's own hue** (the reference implementation uses locally-hosted square WebP images with the dominant hue matched to each work's hue value, drawn with `object-fit: cover` over a same-hue CSS-gradient underlay that shows until the image loads — cover-cropping is what lets one square artwork survive every shape from the 46px list thumb to the 4:1 poster band, and the hue-matched dominant color is what makes the HUE sort read as a walk around the color wheel). In this self-contained file, with no external images allowed, build the artwork from that underlay alone: a base of `hsl(H 30% 12%)` plus four gradient recipes rotated by `index % 4` (two off-center radial-gradient blends, a linear diagonal, and a conic-gradient wheel), each dominated by the tile's own hue so the sorted color-wheel order stays legible. Pin the "Selected Works" section (`ScrollTrigger`, `start:"top top"`, `end:"+="+scrollerHeight*3.2`, `pin:true`, `anticipatePin:1`) and, on `onUpdate`, bucket `self.progress` into four equal quarters mapped to `["list","grid","stack","poster"]`; call the FLIP-wrapped state setter only when the bucket index actually changes. **Index** (list): tiles are `46px`-tall rows in a vertical flex stack, a `46×46` thumbnail on the left, the caption beside it — reads like an alphabetized ledger. **Grid**: a plain `4-column`, `10px`-gap, `aspect-ratio:1` grid, everything the same size. **Stack**: a `4-column` grid with `96px` auto-rows, `12px` gap and `grid-auto-flow:dense`; every 4th tile (by its current sorted index) spans 2 columns as a wider "block"; every tile also receives a small permanent tilt cycling through `[-5deg,-2deg,3deg,-3deg,5deg]` by index — never more than 5°, so captions stay legible — giving a "piled like proofs on a desk" read. **Poster**: a fixed `4-column × 5-row` grid (max-width `640px`) with one hand-authored composition covering exactly the 12 tiles with zero gaps: tile 0 spans columns 1–2/rows 1–2 (the hero block), tile 1 spans columns 3–4/row 1, tiles 2–3 sit singly in row 2 (columns 3 and 4), tiles 4–5 sit singly in row 3 (columns 1–2), tile 6 spans columns 3–4/row 3, tiles 7–10 sit one-per-column across row 4, and tile 11 spans the full width of row 5 as a closing band — rotation resets to `0deg` here, since Poster is the page's point of visual rest. A stage heading (Index/Grid/Stack/Poster) and one-line description crossfade with the mode, four small square progress dots track which quarter is active, and this is also where the sort control lives.
## Signature 2 — the sort key, replayed through the same FLIP
A persistent "SORT — NAME / YEAR / HUE" control sits under the stage heading. Clicking an inactive key re-sorts the same twelve-item array (`localeCompare` for name, numeric ascending for year and hue) and runs it through the identical FLIP request used for stage changes — tiles physically slide to their new slot inside whichever layout mode is currently showing, with the same `0.65s power3.inOut`, `0.025s`-per-tile stagger and counter-scaled captions. The active sort pill is filled with the accent and gets a soft `4px` accent glow ring.
## Section: Hero
Top bar: wordmark "ARRAY" and a nav (Capabilities / Studio / Work). A five-word headline, "EVERY LAYOUT IS A DRAFT.", rendered as five separate inline-block spans in correct reading order in the DOM at all times (never scrambled, for accessibility) — on mount only, each word starts offset by its own fixed scatter vector (roughly `±20–50px` x/y and `±5–10deg` rotation, a different vector per word) and opacity 0, then animates to `x:0,y:0,rotation:0,opacity:1` over `0.7s power3.out`, staggered `0.05s`, starting `0.3s` after mount — a small preview, in the hero itself, of "elements settling into their layout." Below it: one sub-paragraph and two CTAs ("See the work" filled accent, "Our approach" outline).
## Section: Capabilities
Heading "Six disciplines, one method." Six plain cells — Identity Systems, Editorial Design, Type Design, Packaging, Wayfinding, Digital Products — each `scale(0.85)`+`opacity:0` at rest, materializing to `scale:1, opacity:1` over `0.5s back.out(1.6)`, staggered `0.05s`, on `ScrollTrigger.batch` (`start:"top 90%"`).
## Section: Studio
Heading "Structure first, style second." with a lede paragraph on process, plus three pill words — LEGIBLE, FLEXIBLE, HONEST — using the same materialize treatment as the capability cells.
## Section: Footer
Heading "Let's structure something.", a mailto CTA "Start a project ↗", and a meta row ("ARRAY STUDIO © 2026" / "TORONTO — SEOUL — REMOTE"), on the same graph-paper background as the hero.
## Scroll animations (shared heading treatment)
Every non-hero section heading gets: a tiny 4-square icon (a 2×2 CSS grid of accent squares) scaling in from `0.4` with a `45deg→0deg` un-rotate (`0.5s back.out(2)`), an eyebrow label sliding in on x (`-10px→0`, `0.5s power2.out`, `0.05s` delay), and the title itself wiping open via `clip-path: inset(0 100% 0 0) → inset(0 0% 0 0)` (`0.6s power3.out`, `0.1s` delay), on `ScrollTrigger` `start:"top 85%"`, `toggleActions:"play none none reverse"`. Supporting copy (ledes, hero sub-text, footer meta) uses a distinct secondary treatment — `opacity:0+blur(4px) → opacity:1+blur(0)`, `0.6s power2.out`, `0.06s` stagger via `ScrollTrigger.batch` — kept visually different from both the heading wipe and the tile FLIP.
## Performance & accessibility
Tile transforms are written only during an active FLIP tween (never per-frame outside one), and every tween's `delay` is a simple `index*0.025`, not a per-frame recomputation. Respect `prefers-reduced-motion`: skip the FLIP invert/play tween entirely (snap tiles straight to their resting position/rotation with no animation), skip the hero word-scatter entrance (render words already in place), and skip the pinned scroll-driven stage switching (the section still renders, simply without the pin/scrub). Sort buttons and nav links are real `<button>`/anchor elements; the tile grid carries a live `aria-label` stating the current arrangement (e.g. "12 selected works, currently arranged as grid"). All studio, project and process copy is original and fictional; no real studio, client or brand 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": "grid-morph",
"brand": "ARRAY",
"accent": "#793b88",
"title": "A structure for your next project",
"intro": "Start with the information people need to find, and the identity that should hold it together.",
"facts": [
[
"Starting point",
"Choose a focus, describe the audience and keep a scope brief."
],
[
"Review",
"Scope, supplied material, timing and the intended use."
],
[
"Handover to discuss",
"Design system, agreed publication files or interface source, and a handover guide."
]
],
"groups": [
{
"label": "Project focus",
"options": [
{
"title": "Identity & editorial",
"detail": "A visual language, type hierarchy and a flexible publication grid."
},
{
"title": "Digital experience",
"detail": "An information structure and responsive interface, with working key journeys."
}
]
}
],
"brief": "Describe the audience, the intended result, the material you already have and any timing constraints.",
"notes": [
"This is a local project brief. Scope, availability and any fee would be agreed in a separate conversation."
]
}
- 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.