枠の中でスクロール・クリックして動きを試せます。端末指定は実寸表示です。幅が足りない場合は横にスクロールできます。
再現プロンプト
架空のアンビエント/モジュラー・レーベル「CARRIER」の1ページサイト。4つの発振器+2つのLFOで作る控えめなパッド音をAnalyserNode(FFT 1024)でリアルタイム解析し、ミラー型周波数バー+波形線として描画する。自動再生はせずクリックで開始、未再生時・ミュート中も合成アイドル波形で成立し、音量レベルがページ全体のドット・罫線に微連動する。 配色・構成・動きの数値をまとめた指示書です。 お使いのAIに貼り、素材やお店の情報を伝えて実装してください。仕上がりは利用するAIと素材によって変わります。
Build a single self-contained HTML file (inline CSS/JS) that is the complete one-page website for "CARRIER" — a fictional ambient / modular electronic label whose whole page treats "sound made visible" as its one motion language. Use the native Web Audio API and Canvas 2D only — no external libraries for audio/visuals. Use GSAP + ScrollTrigger via CDN (`https://unpkg.com/gsap@3/dist/gsap.min.js`, `https://unpkg.com/gsap@3/dist/ScrollTrigger.min.js`) purely for scroll-triggered entrances. No external audio files, no fetch calls — every sound is synthesized live with oscillators, and every visual is Canvas 2D / inline SVG / CSS, with one bitmap exception: the four circular artist portraits in Section 3 (if no portrait files are available, substitute the CSS placeholder disc described there — never fetch remote images). 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). **Autoplay is strictly forbidden**: no audio may start until the visitor explicitly presses Play.
## Art direction
Near-black background `#05080A`, card surface `#0B1315`, foreground text `#EAF6F8`, muted copy `#7C9296`, hairline borders `rgba(34,211,238,0.16)`, accent cyan `#22D3EE` used for the waveform, transport icons, dots and catalogue numbers. Headings use a geometric sans (Space Grotesk-style, 400–700); catalogue numbers, durations, and the status readout use a monospace (JetBrains Mono-style). Eyebrow labels: 0.7rem, letter-spacing 0.28em, uppercase, muted, weight 600, each prefixed with a small 4-bar "EQ icon" (2px bars, `scaleY` bouncing 0.3↔1 over a 1.1s ease-in-out infinite loop, staggered 0/0.08/0.15/0.3s delays — always running, never audio-linked, purely decorative "the label never sleeps" idle motion). Section padding `clamp(3rem,8vh,6rem) clamp(1.25rem,5vw,3rem)`, 1px hairline dividers between sections. Never use a marquee, a magnetic button, a scroll-triggered count-up stats block, or a plain numbered "01/02/03" service-row list anywhere on this page.
## Signature 1 — the WebAudio pad: 4 oscillators, 2 LFOs, no autoplay
On first Play click, build the graph once (a plain closure/module-level reference, never rebuilt on subsequent toggles): 4 oscillators forming a quiet A-minor pad with an octave shimmer — `110.00Hz` sine (A2, gain 0.05), `130.81Hz` sine (C3, gain 0.04), `164.81Hz` triangle (E3, gain 0.035), `220.00Hz` triangle (A3, gain 0.025) — each with a small `detune` offset (`-6, +4, -3, +5` cents) for chorus width. Each oscillator feeds its own `GainNode` (its listed gain) into a shared bus, which feeds one `BiquadFilterNode` (`type:"lowpass"`, base `frequency:800`, `Q:0.7`). **LFO1** (a 5th oscillator, sine, `0.06Hz` ≈ 16.6s cycle) runs through a `GainNode` of depth `400` straight into `filter.frequency` (an AudioParam), sweeping the cutoff roughly `400Hz–1200Hz`. The filter feeds a master `GainNode` (`gain.value = 0.14` when unmuted, i.e. deliberately quiet — this is a background pad, not a lead). **LFO2** (a 6th oscillator, sine, `0.045Hz` ≈ 22.2s cycle) runs through its own depth-`0.03` `GainNode` directly into the master gain's `.gain` AudioParam, summing with the constant value to create a slow amplitude "breathing" swell. The master gain connects to `audioCtx.destination`. Play/Pause toggles `audioCtx.suspend()`/`.resume()` (never stops or recreates the oscillators). Mute ramps both the master gain and the LFO2 depth gain to `0` (and back) via `gain.setTargetAtTime(target, ctx.currentTime, 0.05)` — a 50ms time-constant ramp so toggling never clicks or pops.
## Signature 2 — AnalyserNode → live waveform + bar graph on canvas
A single `AnalyserNode` (`fftSize:1024` → `frequencyBinCount:512`, `smoothingTimeConstant:0.82`, `minDecibels:-90`, `maxDecibels:-10`) is tapped directly off the filter output — **before** the mute-controlled master gain (this is what makes Signature 4 possible, see below). Every animation frame while playing: call `getByteFrequencyData`/`getByteTimeDomainData`, then on one `<canvas>` (DPR-clamped to 2, resized via `ResizeObserver`): draw a **mirrored bar graph** — average the lowest 192 of the 512 frequency bins (≈0–8.3kHz at 44.1kHz, the pad's actual energy range) into 48 bars (4 bins per bar), each bar's height `= average/255 * canvasHeight * 0.42`, drawn both up and down from the vertical centre with a cyan gradient (`rgba(34,211,238,0.85)` fading to `rgba(34,211,238,0.12)`); then overlay a 1.5px cyan **oscilloscope line** built from the full time-domain buffer (`(sample-128)/128`, scaled to ±26% of canvas height around centre), with an 8px cyan `shadowBlur` glow. The draw loop only runs while the hero section intersects the page's scroll container (`IntersectionObserver`, threshold 0.05) — fully cancelled via `cancelAnimationFrame` off-screen.
## Signature 3 — idle waveform when nothing is playing
Whenever `playing` is false (before the first Play press, or while paused), the same canvas instead draws a **synthetic, audio-free** idle pattern so the visual is never blank: 48 bars with height `= 0.05 + sin(t*0.8 + i*0.4)*0.03 + sin(t*0.31 + i*0.68)*0.015` (two independent slow phases per bar, so bars drift rather than pulse uniformly) at 22% cyan opacity, plus a 1.3px line at 45% cyan opacity following `sin(x*2π*3.6 + t*0.6)*0.1 + sin(x*2π*8.28 + t*0.78)*0.04` (two harmonics, ratio 2.3, secondary amplitude 40% of primary) scaled to canvas height. This idle pattern is purely `Math.sin`-driven — no `AudioContext` exists yet the first time it runs, so there is zero possibility of accidental autoplay.
## Signature 4 — muting never kills the visualization
Because the `AnalyserNode` is tapped before the mute gain, muting the page only silences `audioCtx.destination` — the oscillators, LFOs and filter keep running, the analyser keeps receiving real signal, and the bar graph / waveform line keep animating exactly as they would unmuted. A transport bar (46px circular buttons, 1px hairline border, cyan icon, hover: border turns accent + `scale(1.05)`) holds a Play/Pause toggle and a Mute/Unmute toggle, plus a status readout — a 6px accent dot (`box-shadow` glow) next to the monospace word "STANDBY" or "LIVE" — that also reads the current pause state, independent of mute.
## Page-wide audio-level pulse (dots & hairlines)
Every animation frame (whether playing or idle), compute a single `level` value — while playing, the mean of the same 192 low frequency bins (0–1, i.e. `sum/192/255`); while idle, `0.18 + sin(t*0.5)*0.08`. Smooth it with `smoothed += (level - smoothed) * 0.08` and write it to `--wsx-lvl` as a CSS custom property on the page root every frame. Every accent dot (transport status, footer status) reads `opacity: calc(0.35 + var(--wsx-lvl,0.2)*0.65)` and `transform: scale(calc(1 + var(--wsx-lvl,0.2)*0.6))`; the footer's mailto link underline and each artist "frequency ring" use `calc()`-driven `rgba()` alpha / `brightness()` filters keyed off the same variable — so the whole page visibly, subtly breathes in time with the (silent-safe) signal, whether or not audio is actually audible.
## Section 1: Hero
Two-column grid (stacks on mobile, visual moves above copy). Left: wordmark "CARRIER", eyebrow "SIGNAL ARCHIVE" (with EQ icon), heading "Every silence has a frequency." (`clamp(2.1rem,5vw,3.6rem)`, max-width 14ch), lede ("CARRIER releases ambient and modular work built from a handful of oscillators, patiently detuned. Press play — the visual below is the signal itself, not a decoration."), and the transport bar described above. Right: the bordered canvas panel (`height:min(56cqh,420px)`) described in Signatures 2–3.
## Section 2: Releases
Eyebrow "LATEST TRANSMISSIONS" + EQ icon, heading "Six releases, one carrier signal." A list of six rows (`5rem 1fr minmax(120px,200px) 3.5rem` grid: catalogue / title+artist / waveform fingerprint / duration), 1px hairline top rule per row: CAR-014 "Low Orbit" — Mira Voss — 6:12; CAR-013 "Tape Static" — Kelvin Row — 4:48; CAR-012 "Slow Bloom" — Aki Ferro — 8:03; CAR-011 "Dead Reckoning" — Nils Aurum — 5:37; CAR-010 "Glass Choir" — Mira Voss — 7:21; CAR-009 "Faint Carrier" — Odessa Lune — 3:59. Each row's "waveform fingerprint" is 22 thin cyan bars whose heights are generated once, deterministically, from a seeded PRNG keyed by the catalogue string (FNV-1a hash → mulberry32), each height in `[0.18, 1.0]` of the fingerprint's own 28px height — a static per-release "shape", not live audio. On row hover, the bars animate `scaleY 0.6↔1.15` over a 0.7s ease-in-out alternate loop (a playful "wake up" on touch/hover, satisfying the page's pointer-reactivity requirement).
## Signature 5 (entrance) — "EQ rise" for generated waveforms
Every release row's fingerprint bars start at `scaleY:0` (`transform-origin:bottom`) and, when the row crosses 88% of the viewport height while scrolling, animate to `scaleY:1` over 0.5s `power2.out` with an 0.018s stagger between bars and roughly a 0.0135s stagger between successive rows — reading as an equalizer visibly powering on top-to-bottom, row-by-row. Reverses (`scaleY:0`, 0.25s `power2.in`) on scrolling back above the trigger.
## Section 3: Artists
Eyebrow "ON THE ROSTER" + EQ icon, heading "Four signals, four rooms." A 4-column grid (2 columns below 780px) of artist cards — Mira Voss ("Modular sequences recorded in a single unbroken take."), Kelvin Row ("Tape loops degraded on purpose, then rebuilt."), Aki Ferro ("Field recordings folded into synthesis."), Odessa Lune ("Two oscillators, one room, no plugins.") — each topped by an 88px "frequency ring" badge: an inline SVG of 24 radial cyan tick marks (`stroke-width:1.4`, lengths varying `8–18` per a fixed pseudo-pattern) arranged in a circle, each tick individually pulsing opacity `0.35↔0.95` over a 3.4s ease-in-out infinite loop with a small per-tick delay, **encircling a circular artist portrait** that exactly fills the ring's inner circle (the ticks start at radius 30 of a 100-unit viewBox, so the portrait disc is `inset:20%` = 60% of the badge's diameter, `border-radius:9999px`, `overflow:hidden`). Each portrait is a square, near-monochrome, low-key photo (deep near-black shadows matching `#05080A`, one cool light source, heavy grain, subject anonymized — silhouette / back view / equipment-obscured, one distinct gear motif per artist: a modular wall for Mira Voss, tape reels for Kelvin Row, a wind-jammered field mic outdoors for Aki Ferro, two desktop oscillators in a bare room for Odessa Lune) rendered as a plain `<img loading="lazy" decoding="async">` (never a framework image component), `object-fit:cover`, harmonized into the page's cyan world with `filter:grayscale(0.25) saturate(0.85) contrast(1.06)`, ringed by a 1px cyan hairline (`box-shadow: 0 0 0 1px rgba(34,211,238,0.25)` plus a faint 14px cyan glow), and overlaid (`::after`) with a `radial-gradient` that lifts the centre with 10% cyan and sinks the outer edge toward `rgba(5,8,10,0.42)` so the disc melts into the background. If no portrait files are supplied, keep the identical structure but fill the disc with a dark CSS placeholder (`radial-gradient` from `#0B1315` to `#05080A`). The whole badge carries `filter:brightness(calc(1 + var(--wsx-lvl,0.2)*0.5))` tying ring *and* portrait brightness to the page-wide audio level. On scroll entrance, each ring scales in from `0→1` with fading opacity, `back.out(1.8)`, 0.6s, staggered 0.1s across the four cards ("EQ rise", radial variant).
## Text entrance — "tuning in" (blur → clear, never a y-offset)
Every eyebrow, heading, and paragraph outside the waveform/ring elements starts at `opacity:0, filter:blur(6px)` and, on entering 85% of the viewport (`ScrollTrigger.batch`), animates to `opacity:1, filter:blur(0px)` over 0.7s `power2.out` with an 0.08s stagger — reading like a radio dial finding its station. This is deliberately not a translateY fade: it never moves position, only resolves focus, keeping it distinct from a generic fade-up reveal.
## Section 4: Footer
Eyebrow "STAY TUNED" + EQ icon, heading "Subscribe to the signal.", a monospace mailto link "signal@carrier.fm ↗" (underline opacity tied to `--wsx-lvl`, turns accent cyan on hover), a meta row with the same live status dot+label ("TRANSMITTING"/"STANDBY", mirroring the hero's play state) plus "CARRIER © 2026" and "BANDCAMP — NTS — RA".
## Performance & accessibility
One `AudioContext`, six oscillator nodes, one analyser, one canvas — cheap even on modest hardware; the draw loop is fully cancelled via `IntersectionObserver` when the hero scrolls out of view, and the `AudioContext` is `close()`d on unmount. Read `window.matchMedia("(prefers-reduced-motion: reduce)").matches` on load: if true, skip the blur-in and EQ-rise scroll animations (content appears at rest immediately) and stop the decorative EQ-icon/ring-tick CSS loops — the WebAudio pad and its live visualization are left untouched, since they only ever run in direct response to an explicit Play press, never automatically. Transport buttons are real `<button>`s with `aria-pressed`/`aria-label` reflecting play/mute state. All artists, releases and label copy are original/fictional; no real label, artist or track 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": "wave-synth",
"brand": "CARRIER",
"accent": "#a34635",
"title": "Keep the signal close",
"intro": "Choose the notes you would like to follow, and save a listening guide.",
"facts": [
[
"Release notes",
"New recordings and the ideas behind them"
],
[
"Studio journal",
"The process behind the sound"
],
[
"This instrument",
"The page plays a browser-generated synthesis demonstration."
]
],
"groups": [
{
"label": "Listening notes",
"options": [
{
"title": "New releases",
"detail": "A record of the latest releases and their catalogue references."
},
{
"title": "Studio process",
"detail": "Synthesis, recording and production notes from the studio."
}
]
}
],
"notes": [
"This saves a preference guide. No mailing list registration or music download is created."
]
}
- 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.