MOTION PALETTE
← すべてのデモ

Line Draw

HeroSVG / GSAP / ScrollTrigger

フルスクリーンで開く ↗

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

再現プロンプト

架空のフィールド調査スタジオ「Studio Periplus」の完成された1ページサイト。ページ全長を縦断する1本のインクの route 線(実測 DOM 座標から生成し、コンテンツの左右を交互に縫う)が scrub で描かれ続け、ヒーローの同心軌道・星座・稜線、断面図の等高線、フッターの一筆書きワードマークまでを物理的に繋ぐ。リンクの下線は clip-path でホバー時に「描かれ」、本文の入場もすべて同じインクの言語(clip-path のリビール)で統一されている。 配色・構成・動きの数値をまとめた指示書です。 お使いのAIに貼り、素材やお店の情報を伝えて実装してください。仕上がりは利用するAIと素材によって変わります。

Build a single self-contained HTML file (inline CSS and inline JS) reproducing "Studio Periplus" — a fictional field-survey / cartography studio's complete one-page site whose entire motion language is "a single line of ink travels the length of the page." It is a tall, normal window-scrolling editorial page (no inner scroll container needed). The only allowed external dependency is GSAP core + the ScrollTrigger plugin from a CDN (e.g. `https://cdn.jsdelivr.net/npm/gsap@3.15/dist/gsap.min.js` and `.../ScrollTrigger.min.js`); no other CDN, image, font file or fetch call. All visuals are pure SVG/DOM/CSS. Headings use Fraunces (weights 300/400/500/600, normal+italic); everything else uses Manrope (weights 400/500/600/700) — self-hosted Google Fonts or system-serif/sans fallback.

## Art direction
Palette everywhere: background near-black `#0F0E0B`, cream ink `#EAD9A0`, white `#FFFFFF`, muted body `rgba(245,241,230,0.62)`, faint caption `rgba(245,241,230,0.4)`. Eyebrow labels: Manrope, `0.7rem`, `letter-spacing:0.32em`, weight 600, uppercase, cream. Body copy: `clamp(0.9rem,1.4vw,1.05rem)`, `line-height:1.6`, muted. Every single-property or two-property reveal in this page must use the SAME idiom — a left-to-right ink sweep implemented with `clip-path`, never `translateY`/opacity-only fades and never `scaleX`. Define two clip-path constants: hidden `inset(0% 100% 0% 0%)` (nothing visible, as if clipped from the right edge inward) and shown `inset(0% 0% 0% 0%)`. GSAP can tween between them directly because both are the same `inset()` function with the same number of values. A persistent top-left brand mark link "Studio Periplus" (Fraunces italic, `0.95rem`, cream, `position:fixed; top:clamp(1rem,3vw,1.6rem); left:clamp(1rem,4vw,2.2rem); z-index:20`) scrolls the page to top on click and demonstrates the hover-ink-underline idiom from the first viewport.

**Hover ink-underline** (used on the brand mark, every survey row's "Open field note →" button, and the footer's "Read the field philosophy" link): each is a bare-styled `<a>`/`<button>` (`display:inline-flex; position:relative; color:inherit`) with a `::after` pseudo-element (`position:absolute; left:0; right:0; bottom:-0.22em; height:0.07em; background-image:repeating-linear-gradient(90deg, currentColor 0 7px, transparent 7px 11px)` — a dashed, hand-inked texture, not a solid bar). Default state `clip-path: inset(0 100% 0 0)`; on `:hover`/`:focus-visible`, `clip-path: inset(0 0 0 0)`, `transition: clip-path 0.55s cubic-bezier(0.65,0,0.35,1)`. This is pure CSS, no JS needed per instance, and is explicitly NOT a `scaleX` transform.

## The spine: one route line down the entire page
Absolutely position a full-bleed `<div>` (`position:absolute; inset:0; z-index:6; pointer-events:none`) as a child of the page's main content wrapper (which itself is `position:relative`, auto height) containing one `<svg>` with **no `viewBox`** (so 1 SVG user unit = 1 real CSS px, sized `width:100%; height:100%; overflow:visible`) holding a single `<path>` (`stroke:#EAD9A0; stroke-width:1.3; stroke-linecap:round; stroke-opacity:0.68; fill:none`). Because the wrapper is absolutely positioned inside an auto-height parent, its box resolves to exactly the parent's content height (auto-height parents compute their height from in-flow children only, so there's no circular dependency) — this lets the SVG's raw pixel coordinate system align 1:1 with real measured DOM positions.

On mount and on every `ResizeObserver` firing (observe both the scroll container and the content wrapper — layout is fluid/`clamp()`-based so heights change with viewport), measure `content.clientWidth` as `W` and each of the 5 section elements' `offsetTop`/`offsetHeight` relative to the content wrapper (hero, surveys, cross-section, manifesto, footer). Compute `left = W*0.07`, `right = W*0.93`, `mid = W*0.5`, then build this ordered list of 16 waypoints (weaving left↔right through each section in turn, always ending exactly `4px` above the footer's top edge so the line and the footer wordmark visually meet): `(left+W*0.03, 6)`; hero: `(right-W*0.05, heroTop+heroH*0.22)`, `(left+W*0.02, heroTop+heroH*0.5)`, `(right-W*0.08, heroTop+heroH*0.82)`; surveys: `(left, surveysTop+surveysH*0.12)`, `(right-W*0.04, surveysTop+surveysH*0.38)`, `(left+W*0.03, surveysTop+surveysH*0.64)`, `(right-W*0.06, surveysTop+surveysH*0.9)`; cross-section: `(mid-W*0.16, topoTop+topoH*0.1)`, `(mid+W*0.18, topoTop+topoH*0.4)`, `(mid-W*0.12, topoTop+topoH*0.68)`, `(mid+W*0.08, topoTop+topoH*0.92)`; manifesto: `(right-W*0.1, manifestoTop+manifestoH*0.18)`, `(left+W*0.06, manifestoTop+manifestoH*0.5)`, `(mid, manifestoTop+manifestoH*0.86)`; final: `(mid, footerTop-4)`. Convert this point list into a single smooth path with a deterministic "quadratic through midpoints" helper (no external curve library): start with `M p0`; for every interior point `i` (not first/last) emit `Q pi.x,pi.y midpoint(pi,pi+1).x,midpoint(pi,pi+1).y`; finish with `L` to the exact last point. Set `path.d`, measure `getTotalLength()`, set `stroke-dasharray` to that length. Drive `stroke-dashoffset` from `length*(1-progress)` inside one `ScrollTrigger.create({trigger:content, start:"top top", end:"bottom bottom", scrub:0.5, onUpdate:...})` covering the whole document — this is the page's scroll-progress indicator AND its connective tissue at once (there is no separate progress bar). Layer one independent idle tween on top so the drawn portion is never static: `opacity → 0.68*0.65`, duration 3.2s, `sine.inOut`, yoyo, repeat -1.

## Section 1: Hero (preserve exactly — the page's signature artwork)
Full first viewport (`min-height:100vh`), background `radial-gradient(120% 120% at 78% 38%, #17150F 0%, #0F0E0B 62%)`, flex row (column under 720px). Left column (`width:min(420px,42%)`): eyebrow "Field Notes — 07"; 2-line Fraunces headline `clamp(2rem,5.2vw,3.8rem)` "A MAP OF" / "QUIET ORBITS" (ORBITS in cream italic `.em`); body "Constellations, ridgelines and orbital paths redraw themselves, stroke by stroke, like ink finding its own way home."; caption "Fig. 07 — redrawn every 12s"; a bobbing scroll cue "↓ Scroll to begin the survey" (CSS keyframe, disabled under reduced motion). All six text nodes start at clip-path hidden and sweep to shown, duration 0.8s, `power3.out`, stagger 0.12s, starting at `t=0` inside a `gsap.timeline({delay:0.1})`.

Right column holds one `<svg viewBox="0 0 800 600">`: 4 orbit rings (radii 50/95/145/200, two-arc circle paths, cream, width 1.5) around center `(540,258)`; a sun (r=6) and 3 planets (r=4.5) on orbits 0–2 at angles -35°/205°/100°; a 7-point/6-edge constellation graph (white, width 1, r=2.5 dots); 4 cubic-bezier ridgeline paths (cream, width 1, resting opacity 0.45/0.6/0.8/1). All 14 paths + 11 node circles: measure lengths, dash to full, tween `stroke-dashoffset→0` at `t=0.2`, duration 1.3s, `power2.inOut`, stagger 0.18s; nodes grow `r:0→resting` at `t=0.7`, duration 0.55s, `back.out(1.8)`, stagger 0.09s (intro completes ≈3.94s). Breathing loop from intro-end: path opacity → `resting*0.75`, 2.6s, sine yoyo, repeat -1, random stagger 0.15; node r → `base*1.4`, 2.2s, same pattern. Every 12s (start-to-start, rescheduled at cycle start): 3 random paths erase (dashoffset→full, opacity→0, 0.6s power2.in, stagger 0.08) then redraw (1.2s power2.inOut, stagger 0.08, +0.3s gap). Pointer: convert `pointermove` to SVG space via `getScreenCTM().inverse()`; highlight the nearest orbit ring (width 2.5, white) within 260px of center, reset on leave.

## Section 2: Surveys
Eyebrow "Studio Periplus — Surveys" + heading "Five expeditions, / still being drawn." A bulletless, bordered list of 5 rows with exact copy: **01 Ridgeline Atlas** / **02 Coastal Drift** / **03 Night Sky Index** / **04 Underground Rivers** / **05 Silence Survey**, each with its one-sentence description (same text as before). Each row's chapter number (this hand-drawn decorative numbering is the one page-specific exception to any "numbered list" rule) is rotated by a fixed per-row angle `[-3, 2, -2.5, 3, -1.5]` degrees via inline `transform: rotate()`, breaking mechanical alignment. Below the description, a small uppercase "Open field note →" ink-underline button. A 1px rule below each row starts `clip-path: inset(0 100% 0 0)` and sweeps to shown (0.9s, `power3.out`) together with the title/description/number/button (`clip-path` sweep, 0.7s, `power3.out`, stagger 0.08s), triggered per-row at `start:"top 82%", toggleActions:"play none none reverse"`. Ambient: once drawn, rules shimmer — opacity → 0.6, 3s sine yoyo, repeat -1, random stagger 0.3, delay 1s.

## Section 3: Cross-Section (unnamed range)
A pinned `min-height:240vh` section (`position:sticky; top:0; height:100vh` inner viewport) so extra scroll drives the drawing. Left: elevation index 01–06 listing `2,140/1,780/1,420/1,060/700/340` meters, each row revealed via clip-path sweep (not x-offset), duration 0.4s, staggered 0.5 "virtual seconds" inside the scrubbed timeline. Right: `<svg viewBox="0 0 960 620">` with 6 nested closed contour loops (6-point loops at radii 40/85/130/178/228/280, shared angle-modifier fingerprint `[1.15,0.9,1.05,0.85,1.2,0.95]`, smoothed via the closed-loop "midpoint" technique, opacity 1→0.4 inward-brightest), a north indicator (line + 2 ticks), a peak marker circle, and two uppercase labels "PEAK · 2,140M" / "SURVEY BASELINE · 0M" that fade in via opacity only (no clip-path, no offset — SVG `<text>` elements get a minimal single-property fade). One `gsap.timeline({scrollTrigger:{trigger:section, start:"top top", end:"bottom bottom", scrub:0.6}})` sequences: rings' dashoffset→0 at `t=0` (dur 1, stagger 0.5) with index clip-path sweep; north lines at `t=3` (dur 0.6, stagger 0.15); peak dot `r:0→4` at `t=3.6`; labels' opacity at `t=3.8`. All eases `none`. Idle: wrap the 6 rings in one `<g>`, rotate ±1.6°, 9s sine yoyo, repeat -1.

## Section 4: Manifesto
Centered, `max-width:56ch`, eyebrow "Field Philosophy". Six Fraunces lines (`clamp(1.3rem,3.4vw,2.05rem)`), exact copy with the noted word in cream italic `.em`: "Periplus does not draw what is already known." / "We walk the *unmapped* edge until it agrees to be seen." / "Every line on the page is a question asked of the ground." / "What answers *quietly*, we call a finding." / "What answers loudly, we call weather, and we wait." / "This is fieldwork as *devotion*, not conquest." All six start clip-path hidden; ScrollTrigger `start:"top 70%", toggleActions:"play none none reverse"` sweeps them to shown, 0.8s, `power3.out`, stagger 0.14s (reverse sweep back to hidden, 0.3s `power2.in`, on leave-back).

## Section 5: Footer — the spine becomes the wordmark
Centered column. A wordmark region (`width:min(360px,78%); margin:0 auto`) holds `<svg viewBox="0 0 440 130">` with ONE continuous path (stroke `#EAD9A0`, width 1.6, round caps/joins) — the spine's final flourish, drawn with the exact same "quadratic through midpoints" helper fed by this 35-point sequence (an entry flourish that visually continues the spine, then loose cursive-gesture strokes evoking p-e-r-i-p-l-u-s, then a trailing underline swash): `(14,6) (30,34) (40,58) (54,92) (34,86) (46,54) (72,32) (94,58) (76,62) (104,40) (124,26) (138,56) (154,30) (168,58) (184,94) (164,88) (176,54) (200,8) (214,60) (232,38) (246,70) (262,38) (276,60) (298,42) (306,66) (324,48) (340,62) (366,56) (398,40) (420,66) (404,96) (300,110) (180,106) (70,112) (38,90)`. A small circle (r target 3.2, fill cream) sits at the final point `(38,90)` as a "wet ink" dot; once drawn it pulses continuously via CSS keyframes (`scale 1→1.4→1, opacity 0.85→1→0.85, 2.4s ease-in-out infinite`, disabled under reduced motion). Measure the wordmark path's length once; drive its `stroke-dashoffset` (and the dot's `r = 3.2*progress`) from a `ScrollTrigger.create({trigger:footer, start:"top 92%", end:"top 32%", scrub:0.4, onUpdate...})` — i.e. it draws itself in as the footer enters the viewport, picking up exactly where the page-long spine left off.

Below the mark: Fraunces heading "Begin your own survey."; a pill CTA "Begin a Survey" (`padding:0.9rem 2.1rem; border:1px solid rgba(245,241,230,0.4); border-radius:999px`, hover border/color → cream); the "Read the field philosophy" ink-underline link (scrolls to the manifesto section); a faint meta line "Studio Periplus — Field Notes 01–07 — Cartography for Quiet Places". These three text elements start clip-path hidden and sweep to shown together, 0.7s `power3.out`, stagger 0.1s, via `ScrollTrigger` `start:"top 60%", toggleActions:"play none none reverse"`.

## Performance & accessibility
Only `stroke-dashoffset`, `opacity`, `stroke`/`stroke-width`, `r`, `clip-path` and `rotation` are animated — never layout-affecting properties. Cache every `getTotalLength()` result on mount/resize, never inside a scroll or animation loop; rebuild the spine's `d` attribute only from a `ResizeObserver` callback, not per-frame. Respect `prefers-reduced-motion: reduce`: skip the hero draw-on timeline, the 12s erase/redraw cycle, the spine's scrub draw, the cross-section's scrub timeline and idle rotation, and the footer wordmark's scrub draw — render every line fully drawn immediately instead; keep breathing/pulse loops but shrink amplitude and shorten reveal durations to ~0.3s. On unmount: revert the GSAP context (killing tweens/ScrollTriggers created inside it), explicitly kill the rescheduled erase/redraw delayed call, remove the `pointermove`/`pointerleave` listeners, and disconnect the `ResizeObserver`.

## 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": "line-draw",
  "brand": "STUDIO PERIPLUS",
  "accent": "#766334",
  "title": "Begin your own survey",
  "intro": "A good map begins with a question. Describe the place and the people who need to understand it, then keep a brief for the first conversation.",
  "facts": [
    [
      "First conversation",
      "A 30-minute scope review"
    ],
    [
      "Working stages",
      "Research → field observation → draft atlas → handover"
    ],
    [
      "Deliverables",
      "A digital atlas, annotated source notes and a print-ready map"
    ]
  ],
  "groups": [
    {
      "label": "Survey format",
      "options": [
        {
          "title": "Field note · 2–3 weeks",
          "detail": "One place and one question; a concise illustrated record."
        },
        {
          "title": "Place atlas · 6–8 weeks",
          "detail": "Multiple routes, an editorial narrative and a coordinated map series."
        }
      ]
    }
  ],
  "brief": "Describe the place, what you need to understand, and how the map will be used.",
  "notes": [
    "Timings are illustrative and start after scope agreement. Travel, survey permissions and source licensing would be scoped separately."
  ]
}

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


Each field-note row opens its own title, description and corresponding content below, with a guide download and no project-inquiry form:
[
  {
    "facts": [
      [
        "Duration",
        "11 days"
      ],
      [
        "Method",
        "Walking transects, ridge sketches and elevation annotation"
      ],
      [
        "Output",
        "A folded ridge atlas with five annotated sections"
      ]
    ],
    "notes": [
      "The study followed the changing edge between exposed rock and low cloud. Repeated sketches from fixed stopping points made visibility part of the map.",
      "The final atlas pairs contour drawings with weather notes, preserving the difference between what was measured and what was inferred. Fictional field study; not a navigation chart."
    ]
  },
  {
    "facts": [
      [
        "Cadence",
        "An observation at each equinox"
      ],
      [
        "Method",
        "Repeated shore profiles and fixed-point photography"
      ],
      [
        "Output",
        "An overlay atlas of changing tidelines"
      ]
    ],
    "notes": [
      "Each visit returned to the same reference marks. The drawings keep date and tide conditions beside every observation so that changes can be interpreted in context.",
      "The study uses transparency to show change without pretending the shoreline has a single permanent edge. Fictional study; erosion data is illustrative."
    ]
  },
  {
    "facts": [
      [
        "Setting",
        "Unlit observation ground"
      ],
      [
        "Method",
        "Hand-drawn sky charts with time and visibility logs"
      ],
      [
        "Output",
        "A seasonal index of observed constellations"
      ]
    ],
    "notes": [
      "The chart records the horizon as carefully as the stars. Trees, ridges and cloud cover explain the gaps in each observation.",
      "The index separates a sighting from a catalogue reference. Empty areas remain empty rather than being filled for visual balance. Fictional observational record."
    ]
  },
  {
    "facts": [
      [
        "Medium",
        "Subsurface water routes"
      ],
      [
        "Method",
        "Interpretation of sample sonar traces"
      ],
      [
        "Output",
        "A sectional map with confidence annotations"
      ]
    ],
    "notes": [
      "Different line weights mark different levels of confidence. An inferred connection stays visibly different from a recorded trace.",
      "The atlas keeps the instrument log beside the interpretation, allowing a reader to understand how the drawing was made. Fictional technical study, not surveyed ground data."
    ]
  },
  {
    "facts": [
      [
        "Extent",
        "A 100 km coastal study concept"
      ],
      [
        "Method",
        "Timed listening stations and environmental notes"
      ],
      [
        "Output",
        "A soundscape atlas with an observation diary"
      ]
    ],
    "notes": [
      "Quiet was recorded as a changing condition rather than a score. Wind, distance from roads and the time of day accompany each station.",
      "The finished map invites comparison between visits, with no claim that one isolated reading describes a place forever. Fictional study; no acoustic measurements are presented as real."
    ]
  }
]