枠の中でスクロール・クリックして動きを試せます。端末指定は実寸表示です。幅が足りない場合は横にスクロールできます。
再現プロンプト
CAUSTIC STUDIOの作品鑑賞サイト。光の作品を全体表示し、元画像とWebGLの波紋を比較・調整。参考作品と設定を選んで、確認・保存できる相談メモへ引き継ぐ。 配色・構成・動きの数値をまとめた指示書です。 お使いのAIに貼り、素材やお店の情報を伝えて実装してください。仕上がりは利用するAIと素材によって変わります。
Build a complete English website for CAUSTIC STUDIO, a fictional visual studio. Use inline CSS and JavaScript, Three.js for actual image displacement and a prismatic background, and GSAP for the artwork opening transition. Use normal page scrolling. Deliver a coherent artwork-viewing and creative-brief experience, including the full keyboard and touch flow.
## Direction
Use near-black green #0A0F0E, image ground #061111, white #F5F5F5, secondary #B7C2BF and one teal accent #5EEAD4. Use a clean sans, light-to-medium large headings with tight tracking, and tiny monospace image references. Avoid rainbow interface gradients, noisy placeholder artwork, unreadable RGB text splitting, made-up exhibition history, fake social links and badges. Motion belongs to the light and the artwork. The gallery should feel like a carefully paced visual portfolio.
Keep a restrained full-page prismatic light field behind the content, opacity .45. The image and text panels carry the composition. Desktop nav is 76px; a mobile nav uses 108px and exposes all three section links in its second row. The brand, Motion on/off and Start a brief remain in the first row. Native buttons have targets at least 44px. Use a thin top scroll-progress rule. Link to actual Works, Approach and Commissions sections; scrolling transfers focus without another jump. Honour live OS reduced-motion preferences and provide the visible Motion control. An OS preference takes precedence and its reason is exposed accessibly.
Hero is a two-column composition: “Light, / bent on / purpose.” beside Ion Bloom, a real supplied image with a subtle WebGL ripple. Body: “Images with a point of view. Motion with a reason to be there. Explore a small collection of light studies, then find a direction for your own project.” Explore the works scrolls to the gallery. Show the complete square image. Under 900px stack the hero; use about 64px minimum headline type, generous margins and a quiet image caption.
## Artwork and source fidelity
Use these exact four works and their current local image files. Copy the files at their original 800 × 800 resolution when assembling the deliverable; update local asset URLs if needed. Do not generate a random canvas image while waiting for them. The images are AI-generated visual studies, not documentary photographs or production masters. All four total 95,882 bytes; do not claim additional detail from upscaling them.
[{"id":"ion-bloom","name":"Ion Bloom","medium":"Prism / spectrum","image":"/img/demos/warp-cursor/01.webp","width":800,"height":800,"alt":"A triangular glass prism splits a white beam into a fine spectrum on a dark teal surface.","description":"A narrow white beam opens into colour. The prism stays legible while the surrounding light gives the composition its direction.","use":"A title image or a restrained opening for a science, culture or design story."},{"id":"aurora-drift","name":"Aurora Drift","medium":"Water / refraction","image":"/img/demos/warp-cursor/02.webp","width":800,"height":800,"alt":"A vertical white line bends inside a glass of water against a dark background.","description":"One line, interrupted by water. A small displacement makes the familiar glass feel unfamiliar without losing its silhouette.","use":"A quiet editorial image, with a local ripple that invites closer attention."},{"id":"deep-current","name":"Deep Current","medium":"Surface / caustics","image":"/img/demos/warp-cursor/03.webp","width":800,"height":800,"alt":"Teal caustic light forms irregular vertical traces across a dark surface.","description":"Light gathers into threads and drifts across the frame. The soft structure leaves room for an atmospheric transition.","use":"A moving backdrop for a film title, a cultural programme or an immersive page."},{"id":"solar-flare","name":"Solar Flare","medium":"Film / interference","image":"/img/demos/warp-cursor/04.webp","width":800,"height":800,"alt":"Fine bands of orange, violet and teal follow the curved edge of an iridescent liquid film.","description":"A thin edge becomes a field of colour. The intricate surface rewards a full-frame view before any motion is added.","use":"A campaign image or a bold visual accent, with the colour itself carrying the composition."}]
The gallery is a two-column grid, one column on phones, with generous vertical spacing, uncropped square images, title, medium and a visible Explore work action. Its images are native HTML img elements with explicit width/height, descriptive alt, loading=lazy and decoding=async. Open the matching study in a native dialog. A failed gallery image keeps the title and working open action, explaining that the study can be opened to retry. A separate labelled checkbox adds/removes each work as a project reference; never nest that checkbox inside the opening button.
Below the grid, show the actual reference count and a Build a brief button, disabled with no references. Clear references removes all choices without affecting per-work adjustments. Selections are deduplicated, keep catalogue order and last for the current page session. Do not imply account-based or persistent saving.
## Artwork dialog
Use a native modal dialog, labelled by the current work's heading, with internal scrolling, Escape close and a visible Close button. The background is inert while it is open. Remember the exact original opener and restore focus to it when closing, even after Previous/Next work navigation. Focus the work heading on initial open. Keep the nav controls stable while the name and index update politely. Touching the image must never close the dialog.
Lay out a large image stage and a narrower information/control column. The image stage is at most 800px wide, height min(66svh,800px); use contain to preserve the entire image. On mobile stack the columns, reduce the image height to 44–58svh, let the dialog fill the viewport and keep its close/previous/next toolbar sticky. The modal must remain usable in short landscape screens and with enlarged text.
On first opening, animate only the artwork from the source thumbnail rectangle to the final stage rectangle: translate by the rectangle offset, scale by source/final width and height, transform-origin top left, .6 seconds, power3.inOut. Leave dialog text and controls stable. Skip this transition under reduced motion, and cancel an in-progress transform if that preference changes. Closing is immediate and always available. Previous/Next wraps through the four works; it does not reset settings, reference choices or per-work motion time. Restore focus to the original gallery control, not whichever work is currently displayed.
Show the work's medium, name, complete description and suggested direction from the data. “Compare original” switches between the exact original full-frame image and the interactive treatment, without changing the saved settings. Time stops during comparison. “Pause motion” freezes the current treatment and resumes from the same time. With OS/page Motion off, automatic playback is disabled but explicit adjustments remain usable.
Three native range inputs, 0–100 step 1, are Ripple intensity, Centre from left and Centre from top. Defaults are 45%, 50%, 50%. Show the values beside their labels. Pointer movement or a touch tap on the actual image area places the ripple centre; ignore the letterboxed margins. Maintain native vertical scrolling with touch-action:pan-y. Touch scrolling must not turn into a continuous pointer ripple; use pointer-up for touch. The centre sliders provide an equivalent keyboard path. Round centre values to percent and update the draft only when a value changes. Reset response restores the three defaults and leaves original comparison. Disable motion-only controls until an actual first GPU frame succeeds; if the GPU fails, retain the source image, work information and reference/brief actions.
“Use as a project reference” is the same choice as on the gallery. “Build a brief with this work” ensures the current work is included, preserves other selected works and preselects Moving image & interaction. Close the artwork dialog and then open the brief dialog using the original gallery opener for eventual focus restoration. Do not stack two active native dialogs or discard the selection. Saving study notes creates a UTF-8 text file with the work name, medium, description, intended use and current intensity/centre target values. These are creative-reference settings, not a rendered frame, artwork licence or production master. Preserve selectable text if downloading fails; a browser download request is not proof a file reached disk.
## Shader and coordinate contract
Keep the already loaded HTML image visible below the GPU host. Reuse that decoded HTMLImageElement as the Three.js Texture; do not launch a second TextureLoader request. Reconcile images that completed before hydration by checking complete, naturalWidth and naturalHeight. For load failure or a 20-second loading deadline, expose Retry artwork. For context/shader failure expose Retry motion while showing the original image. Retry success is based on an actual rendered frame.
Use PlaneGeometry(2,2), OrthographicCamera(-1,1,1,-1,.1,10) at Z=1 and a ShaderMaterial. Renderer is alpha, antialias=false, SRGB output, pixel ratio capped at 2 (or 1 in an explicitly opted-in TOP preview). The image texture is SRGB with generated mipmaps, LinearMipmapLinearFilter minification, LinearFilter magnification and anisotropy min(8, renderer maximum). Preserve aspect by scaling the plane to [min(1,imageAspect/viewAspect), min(1,viewAspect/imageAspect)]. Sample the full 0–1 UV square; do not use the old reversed cover-UV scale, which squashed images in non-square viewports. Convert pointer positions through the same contained plane: remove its centred letterbox offset and divide by its scaled width/height. Reject coordinates outside the image.
Each work owns a checkpoint {time,x,y,targetX,targetY,energy}, initially {0,.5,.5,.5,.5,0}. While moving, clamp delta to 0–.05 seconds; increment time by delta. Damp position with 1−.88^(delta×60) and energy with .92^(delta×60). Pointer movement adds min(1,energy+distance×6) using normalized artwork coordinates. While stopped, explicit centre edits set current and target coordinates together, so they draw immediately without a running RAF. Never advance time on hidden-tab re-entry or while comparing the original.
Uniforms are uImage=texture, uCentre=[checkpoint.x,1-checkpoint.y], uTime=time, uIntensity=intensity/100, uEnergy=energy and uEffect=0 for original comparison, otherwise 1. Add subtle R/B separation along the analytic radial-wave gradient, at most ±.0015 UV multiplied by intensity and uEffect, using two additional source taps in the same pass. This is artistic image displacement, not a physically accurate refraction simulation. At uEffect=0 the shader samples the unmodified source. Include Three.js colour-space conversion so the original comparison retains the source colour.
Image vertex shader:
varying vec2 vUv;
void main(){vUv=uv;gl_Position=projectionMatrix*modelViewMatrix*vec4(position,1.0);}
Image fragment shader:
uniform sampler2D uImage;
uniform vec2 uCentre;
uniform float uTime;
uniform float uIntensity;
uniform float uEnergy;
uniform float uEffect;
varying vec2 vUv;
void main(){
vec2 uv=vUv;
vec2 diff=uv-uCentre;
float distance=length(diff);
vec2 direction=distance>.0001?diff/distance:vec2(0.0);
float wave=sin(distance*18.0-uTime*6.0)*exp(-distance*6.0);
float strength=uIntensity*(.024+.035*uEnergy)*uEffect;
uv+=direction*wave*strength;
uv.x+=sin(vUv.y*3.0+uTime*.35)*.004*uIntensity*uEffect;
uv.y+=cos(vUv.x*2.4+uTime*.28)*.004*uIntensity*uEffect;
float gradient=(18.0*cos(distance*18.0-uTime*6.0)-6.0*sin(distance*18.0-uTime*6.0))*exp(-distance*6.0);
vec2 dispersion=direction*clamp(gradient/24.0,-1.0,1.0)*0.0015*uIntensity*uEffect;
gl_FragColor=texture2D(uImage,clamp(uv,0.0,1.0));
gl_FragColor.r=texture2D(uImage,clamp(uv+dispersion,0.0,1.0)).r;
gl_FragColor.b=texture2D(uImage,clamp(uv-dispersion,0.0,1.0)).b;
#include <colorspace_fragment>
}
Background uses the same plane/camera but fills the screen. Pixel ratio cap 1.5: retain the lower fill cost for this full-screen procedural backdrop. Decode the legacy display-authored prism colour with sRGBTransferEOTF before colorspace_fragment, preserving its existing dark exposure; then add spatial centred hash dither, peak-to-peak 1/255. No ACES remapping, environment map, bloom or additional render pass. Its frame checkpoint is independent. On moving frames, use scroll progress p clamped to 0–1 to target X=.38+.3p and Y=.65−.3p, then apply the same position damping. Feed uMouse=[x,1−y], uTime=time, uResolution=host width/height and uMotion=1. Freeze time and pose for stopped frames instead of resetting the shader's time multiplier. Use increasing smoothstep edges in the text-safe mask.
Prism vertex shader:
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position.xy, 0.0, 1.0);
}
Prism fragment shader:
precision highp float;
uniform float uTime;
uniform vec2 uResolution;
uniform vec2 uMouse;
uniform float uMotion;
varying vec2 vUv;
float ray(vec2 p, float angle, float width) {
vec2 d = vec2(cos(angle), sin(angle));
float along = dot(p, d);
float across = abs(p.x * d.y - p.y * d.x);
float beam = exp(-pow(across / width, 2.0));
return beam * smoothstep(-0.03, 0.10, along) * exp(-max(along, 0.0) * 0.34);
}
void main() {
vec2 uv = vUv;
vec2 aspect = vec2(uResolution.x / max(uResolution.y, 1.0), 1.0);
vec2 origin = vec2(0.62, 0.43) + (uMouse - 0.5) * vec2(0.10, 0.07);
vec2 p = (uv - origin) * aspect;
float cursorAngle = (uMouse.x - 0.5) * 0.34 + (uMouse.y - 0.5) * -0.18;
float breathe = sin(uTime * 0.19) * 0.025 * uMotion;
float rotation = uTime * 0.018 * uMotion + cursorAngle + breathe;
// 実写プリズムのように、細い白色芯の両縁で波長がほどけて見える間隔。
// 旧値(0.010〜0.023rad)では3色がほぼ完全に重なってティール一色に見えたため、
// 基準角度差とポインターによる開きをともに広げる。
float spread = 0.028 + length(uMouse - 0.5) * 0.030;
vec3 spectrum = vec3(0.0);
for (int i = 0; i < 5; i++) {
float fi = float(i) - 2.0;
float baseAngle = rotation + fi * 0.39;
float width = 0.014 + float(i == 2) * 0.005;
float pulse = 0.78 + 0.22 * sin(uTime * 0.24 + fi * 1.7) * uMotion;
spectrum.r += ray(p, baseAngle - spread, width) * pulse;
spectrum.g += ray(p, baseAngle, width * 0.92) * pulse;
spectrum.b += ray(p, baseAngle + spread, width * 1.08) * pulse;
}
vec3 base = vec3(0.012, 0.027, 0.023);
// 青緑へ寄せすぎず、赤〜紫の縁も判別できる実写寄りの分光。わずかな共通光を
// 混ぜて純RGBの安価なネオン感を抑えつつ、旧出力の約2.5〜3倍の存在感にする。
vec3 prismSpectrum = spectrum * vec3(0.118, 0.104, 0.142);
float sharedLight = dot(prismSpectrum, vec3(0.28, 0.52, 0.20));
prismSpectrum = mix(prismSpectrum, sharedLight * vec3(0.90, 0.98, 1.0), 0.16);
float halo = exp(-length(p) * 2.55) * 0.018;
vec3 color = base + prismSpectrum + halo * vec3(0.34, 0.70, 0.66);
float textSafe = 1.0 - 0.38 * (1.0 - smoothstep(0.12, 0.72, length(uv - vec2(0.42, 0.52))));
float vignette = 1.0 - 0.38 * smoothstep(0.35, 0.82, length(uv - 0.5));
color *= textSafe * vignette;
// This legacy palette was authored in display sRGB. Decode the composed
// colour once so the Three output chunk preserves its dark editorial exposure.
gl_FragColor = sRGBTransferEOTF(vec4(color, 1.0));
#include <colorspace_fragment>
float dither = fract(sin(dot(gl_FragCoord.xy, vec2(127.1, 311.7))) * 43758.5453) - .5;
gl_FragColor.rgb = clamp(gl_FragColor.rgb + dither * 0.00392156862745098, 0.0, 1.0);
}
## Resource ownership and motion
Only the hero and the currently opened artwork need image-displacement renderers; the four gallery images are HTML images. The page background has one independent prism renderer. Allocate each renderer only when its actual host has a nonempty visible intersection and the document is visible. Render a resized first frame before appending the canvas or enabling controls. Catch construction, shader, render and resize errors and context loss. Dispose partially constructed resources on failure and ignore stale callbacks from older scenes.
Opening an artwork or brief releases the page's background/hero GPU resources while preserving their checkpoints; the active artwork is then the only required GPU scene. Offscreen/hidden hosts also release geometry, texture, material and renderer. Recreate the same image and checkpoint on re-entry, at delta 0. Stop all RAF while reduced motion is active, original comparison is selected or explicit Pause is on. Paused input changes redraw once. Disconnect visibility/intersection/resize listeners and clear image deadlines on unmount. Releasing the WebGL texture must not erase the HTML image fallback. Retain a restrained static dark background if the decorative prism cannot render.
## Approach, services and brief
Approach heading: “The image first. / The response second.” Explain three decisions: find the focus, give motion a purpose and keep choices transferable. Do not claim archival prints physically move or invent studio counts.
The two commission cards are Photography & art direction and Moving image & interaction. Each shows scope, what the visitor should bring and what to agree before production. The first covers subject/lighting/selection/retouching; bring identity and usage examples; agree image formats, rights, assets and production scope. The second covers motion tests, an interaction prototype and delivery formats; bring audience/devices/intended action; agree motion alternatives, performance, accessibility and handover. Each “Plan this direction” preselects that exact service and includes all selected works plus their current response settings.
The generic header, shortlist and footer CTAs retain all selected references. Use a dark brief dialog matching #0A0F0E / #F5F5F5 / #5EEAD4, with a legible pale-red validation colour. The shared action specification below supplies the actual required fields, review, edit and save flow. Do not silently send email or imply a confirmed commission. Footer headline: “What should / your image do?” Show Start a project brief and a quiet fictional-studio disclosure. Do not show unlinked social handles or invented exhibition credentials.
## Acceptance
Verify native modal focus containment, Escape, previous/next wrapping, unchanged opener restoration, comparison/pause at the same time, all sliders and letterbox pointer mapping, per-work state through navigation and modal reopening, all 16 reference subsets and both service choices through review/edit/save. Inspect exact source colours in original mode and aspect ratios .5/1/2, 320px, landscape and 200% text. Test cached images, HTTP failure, loading timeout, context loss, retry and hidden-tab return. Confirm the source files are unchanged. Node geometry/uniform/lifecycle tests do not prove real GPU paint, colour fidelity or full browser completion.
## 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": "warp-cursor",
"brand": "CAUSTIC STUDIO",
"accent": "#5eead4",
"title": "A brief for light, bent on purpose",
"intro": "Choose the medium and describe the subject that should remain in focus.",
"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",
"Selected images or moving-image masters, usage scope and a production archive."
]
],
"groups": [
{
"label": "Project focus",
"options": [
{
"title": "Photography & art direction",
"detail": "A subject-led image series, lighting approach and selection for final retouching."
},
{
"title": "Moving image & interaction",
"detail": "A visual study that becomes a film sequence or a responsive web experience."
}
]
}
],
"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."
],
"paper": "#0a0f0e",
"ink": "#f5f5f5",
"colorScheme": "dark"
}
- 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.