MOTION PALETTE
← すべてのデモ

Text Ring

TypographyCSS 3D / GSAP / ScrollTrigger

フルスクリーンで開く ↗

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

再現プロンプト

架空の音楽フェス「ORBIT FESTIVAL」の1ページサイト。CSS 3D(rotateY+translateZ)で組んだ3層のテキスト円筒が常時逆回転しながらスクロール速度で加速・半径を変え、外層のヘッドライナー名はホバーで正面に止まってリンク化する。ラインナップの円弧配置・放射状タイムテーブル・見出しの「リング断片」入場まで一貫してリング/円弧の言語で統一する。 配色・構成・動きの数値をまとめた指示書です。 お使いのAIに貼り、素材やお店の情報を伝えて実装してください。仕上がりは利用するAIと素材によって変わります。

Build a single self-contained HTML file (inline CSS/JS) that is the complete one-page website for "ORBIT FESTIVAL" — a fictional music festival whose entire motion language is "everything turns". Use CSS 3D transforms as the core mechanism and GSAP + ScrollTrigger via CDN (`https://unpkg.com/gsap@3/dist/gsap.min.js`, `https://unpkg.com/gsap@3/dist/ScrollTrigger.min.js`) for tweening and scroll triggers — no WebGL, no external libraries, images, or fetch calls. The page scrolls normally with `window`/`document.documentElement` scroll (the internal-scroll-container constraint described below is specific to the MOTION PALETTE embed and not needed standalone — build one long scrolling page in document flow and drive every ScrollTrigger `scroller` off the default window scroller instead).

## Art direction

Warm near-black background `#0E0C08`, card surface `#1A160D`, foreground `#F5F1E6`, muted copy `#9C9482`, hairline `rgba(250,204,21,0.16)`, accent yellow `#FACC15`. Headings use a bold condensed display sans (Anton-style, single weight, uppercase, tight letter-spacing); UI/body text uses a clean geometric sans (Manrope-style, 400–700). Eyebrows: 0.7rem, letter-spacing 0.28em, uppercase, muted, weight 700. Section padding `clamp(3rem,8vh,6rem) clamp(1.25rem,5vw,3rem)`, 1px hairline top-border between sections. 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 this page.

## Signature 1 & 2 — the hero: a 3-layer CSS-3D text cylinder, permanently spinning, alternating direction per layer

The hero stage is one `perspective:1100px` container holding a zero-size 3D anchor (`position:absolute; top:50%; left:50%; width:0; height:0; transform-style:preserve-3d`) with three ring layers as its children, each also `transform-style:preserve-3d`. Each ring layer contains N items positioned **once, at render time, and never touched again per-item** — every item's own inline `transform` is a static `translate(-50%,-50%) rotateY(angleDeg) translateZ(radiusPx)`, where `angleDeg = index * (360/N)`. Only the three ring-layer *containers* receive a fresh `transform` every animation frame (`rotateY(currentRotation) scale3d(s,s,s)`) — three style writes per frame total, not per character. **Layer 1** (outer, radius `260px`): 8 headliner names as real `<a>` elements, one per `45°` slot (`360/8`), base angular speed `+6deg/s` (clockwise). **Layer 2** (radius `170px`): 45 individual characters of the repeating string `"ORBIT FESTIVAL • SEPT 18–20 • "` placed every `8°`, base speed `-9deg/s` (counter-clockwise — opposite layer 1). **Layer 3** (radius `108px`, innermost): 30 alternating `✦`/`•` glyphs placed every `12°`, base speed `+14deg/s` (clockwise again — opposite layer 2, so the three layers alternate CW/CCW/CW). All ring text/glyph elements have `backface-visibility:hidden` so a mirrored copy never shows through the back of the cylinder; the natural CSS perspective foreshortening (larger/sharper near the camera, smaller toward the back) is the only depth cue needed — no manual per-item opacity or scale math.

Both scroll-linked behaviours from the same rAF loop: (a) **radius** — one `ScrollTrigger` on the hero (`start:"top top", end:"bottom top", scrub:0.5`) drives a shared `radiusScale = 1 - progress*0.28`, applied as the `scale3d` on all three layer containers (radius shrinks up to 28% as the hero scrolls past); (b) **speed** — track `|scrollTop delta|` per scroll event, accumulate a decaying `velocity` (`gain 0.05`, cap `1.4`, decays ×0.9/frame), and each frame add `velocity * 30`(deg/s) on top of each layer's own base speed, signed to match that layer's existing direction (so faster scrolling always reinforces each ring's own spin, never fights it). The cylinder never stops on its own — it is always turning, even at rest.

## Signature 3 — hovering a headliner snaps it to the front and turns it into a link

Every layer-1 word is already a real `<a href="#lineup">`; a click is prevented unless the word is currently the "active" (front-facing) one, so the ring must be interacted with before it's followable — this reinforces "it has to face you to be clicked". On `pointerenter`/`focus` of a word at slot index `i`: pause the whole cylinder (layer-1's own base-speed increment stops immediately; layers 2 & 3 also stop advancing, so the whole assembly visibly halts to present the word), then compute the nearest rotation `target` such that `(i*45 + target) mod 360 == 0` (i.e. that slot faces the camera at `rotateY(0)`) by solving `target = -i*45 + round((current-(-i*45))/360)*360` — always the shortest path from wherever the ring currently is — and tween the shared rotation value there over `0.6s power3.out`. While active, the word gets a filled accent pill (`background:#FACC15, color:#0E0C08`) and scales to `1.12×`. On `pointerleave`/`blur`, un-pause and ramp back up to full angular speed over `0.8s` (a `spinRamp` multiplier climbing `0→1`) rather than jumping straight back to full speed, so the restart always feels smooth.

## Signature 4 — section headings "unwrap" from a scattered ring fragment

Every heading is split into one `<span>` per character. For a heading of `n` characters, character `i`'s **initial** (pre-reveal) state is computed from `angleDeg = (i - (n-1)/2) * 10` (a 10°-per-character arc centred on the heading): `x = sin(angleRad)*60px`, `y = -(1-cos(angleRad))*60px`, `rotate = angleDeg*0.55`, `opacity:0` — i.e. every character starts displaced along a 60px-radius arc above and to the side of its resting position, as though it were still riding the rim of a ring. On scroll entry (`ScrollTrigger`, `start:"top 85%"`, `toggleActions` reversible) the whole set animates to `x:0, y:0, rotate:0, opacity:1` over `0.6s power3.out` with a `0.02s` per-character stagger — reading as the ring literally unrolling into a straight line of text. The hero's own two-line H1 ("THREE DAYS." / "ONE ORBIT.") uses the identical per-character math but fires once on mount (0.15s delay) instead of on a ScrollTrigger, since it is already on-screen at load.

## Section 1: Hero

Top bar: wordmark "ORBIT" (accent yellow) and eyebrow "SEPT 18–20, 2026". Centre-stage, layered above/through the spinning cylinder (`pointer-events:none` on the wrapper, re-enabled per child): the two-line ring-fragment heading, a lede ("A festival built around a single stage that never stops turning. Eight headliners, three sound systems, one continuous loop."), and two pill CTAs — "See the lineup" (filled accent, smooth-scrolls to the lineup section) and "Get passes" (outline, scrolls to passes).

## Section 2: The Bill (id="lineup")

Eyebrow "THE BILL", ring-fragment heading "Bowed, not boxed." Two concentric arcs built with the classic `rotate(θ) translateY(-R) rotate(-θ) translateX(-50%)` placement (per item: `translateX(-50%)` first centres the element on its anchor, then the rotate/translate/rotate trio swings it out to radius `R` at angle `θ` while keeping its own text upright): an outer arc of the same 8 headliner names at `R=420px`, angles stepping `7.2°` apart (±25.2° total spread, large Anton type), and an inner arc of 10 support acts at `R=210px`, angles stepping `4.4°` apart, smaller muted type. Support acts: Drift Colony, Pale Circuit, Annex Radio, Loose Orbit, Field Tapes, Cold Relay, Satellite Hymn, Quiet Machine, Amber Static, Afterglow Unit.

## Section 3: The Loop (radial schedule)

Eyebrow "THE LOOP", ring-fragment heading "Every set has an angle.", lede "Three concentric tracks, one continuous window: 14:00 to 02:00. Hover a mark." One SVG (`viewBox="0 0 340 340"`, centre `170,170`) draws three concentric hairline-stroke rings for Main Stage (`r=150`), Grove Stage (`r=115`) and Late Signal (`r=80`), 12 clock-face tick marks around the rim, and one accent dot per scheduled set positioned via `angle = ((totalMinutesSinceWindowStart)/720)*360 - 90` (a 12-hour/720-minute window starting 14:00, mapped to the full 360°, offset so 14:00 sits at 12 o'clock) at `x = cx + cos(angle)*trackRadius, y = cy + sin(angle)*trackRadius`. Main Stage: 16:00 Drift Colony, 18:00 Sienna Reef, 20:00 Magnet Valley, 22:00 The Long Now, 00:00 Null Compass. Grove Stage: 15:00 Field Tapes, 17:00 Annex Radio, 19:00 Orla Keene, 21:00 Ghost Meter, 23:00 Vesta Grove. Late Signal: 22:30 Quiet Machine, 00:30 Aurelia Wolf, 01:30 Amber Static. Hovering/focusing a dot enlarges it (`r 5→7`) and writes its time/act/stage into a live text readout below the clock (defaulting to "— hover a mark —").

## Section 4: Get In (id="passes")

Eyebrow "GET IN", ring-fragment heading "Three ways in." Three circular badge tiles (`220px` diameter, full `border-radius:50%`, 1px hairline border) in a row: "DAY PASS — $89 — One day, any stage."; "WEEKEND PASS — $199 — All three days, full orbit."; "ORBIT PASS — $349 — Weekend + backstage viewing deck, early entry, poster." On hover, a badge lifts `translateY(-6px) rotate(-3deg)` over `0.35s cubic-bezier(0.22,1,0.36,1)` and its border turns accent yellow.

## Section 5: Footer

A large (`60cqh`) dashed accent-tinted circle sits centred behind the content, rotating one full turn every `48s` linear infinite — the page's closing "orbit", always turning even when nothing else is happening. Eyebrow "STAY IN ORBIT", ring-fragment heading "See you on the loop.", mailto link "hello@orbitfestival.com ↗" (hairline underline, turns accent on hover), meta row "ORBIT FESTIVAL © 2026" / "INSTAGRAM — RA — DICE".

## Secondary entrance — arc fade for supporting copy

Ledes, CTAs and meta rows that are not headings or ring elements use a distinct "arc fade": `opacity:0, filter:blur(4px)` → `opacity:1, filter:blur(0px)`, `0.6s power2.out`, batched with a `0.06s` stagger via `ScrollTrigger.batch` at `"top 88%"` — deliberately not a y-translate fade, kept visually distinct from both the ring-fragment heading language and the hero's own reveal.

## Performance & accessibility

The rotating cylinder writes exactly three `transform` strings per frame (one per ring-layer container) regardless of how many words/characters/glyphs it holds; the loop is fully cancelled via `IntersectionObserver` (threshold 0.05) whenever the hero scrolls out of the viewport. Read `window.matchMedia("(prefers-reduced-motion: reduce)").matches` on load: if true, stop all ambient auto-rotation (rings hold a static pose) and the footer ring's spin animation, skip the ring-fragment scatter (headings render at rest, opacity 1), and shorten the hover-to-front snap to `0.25s` (kept, since it is a direct response to user interaction, not ambient motion). Ring words and clock dots are real interactive elements (`<a>`, focusable `<circle>` with `tabIndex`) reachable and operable via keyboard focus, not just mouse hover. On viewports below 780px the ring-cylinder's 3D anchor is uniformly scaled down (`0.62×`, applied to the anchor wrapper, never to the per-frame-animated layer containers, so rotation keeps working) and the two lineup arcs shrink their type and radius to remain legible in a single column. All artists, acts and copy are original/fictional; no real festival, artist 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": "text-ring",
  "brand": "ORBIT FESTIVAL",
  "accent": "#835133",
  "title": "Find your festival pass",
  "intro": "Compare access and choose a pass before keeping a plan for your weekend.",
  "facts": [
    [
      "Stages",
      "Main Stage, Grove Stage and Late Signal"
    ],
    [
      "Planning",
      "Programme and access arrangements would be confirmed before purchase."
    ],
    [
      "Status",
      "Fictional festival and programme."
    ]
  ],
  "groups": [
    {
      "label": "Pass",
      "options": [
        {
          "title": "DAY PASS",
          "detail": "One day, any stage · $89."
        },
        {
          "title": "WEEKEND PASS",
          "detail": "All three days · $199."
        },
        {
          "title": "ORBIT PASS",
          "detail": "Weekend, backstage viewing deck, early entry and poster · $349."
        }
      ]
    }
  ],
  "notes": [
    "Prices are examples. Saving this plan does not hold a ticket or confirm access."
  ]
}

- 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.