Synthwave sunset · gpt-oss-120b
Create an animated synthwave sunset: a glowing gradient sun with scanlines setting over an infinite neon grid that scrolls toward the viewer, with a starfield and a reflected horizon.
transcript · 4 messages
Create an animated synthwave sunset: a glowing gradient sun with scanlines setting over an infinite neon grid that scrolls toward the viewer, with a starfield and a reflected horizon.
thinking
We need to create a single-page web app (HTML file) with CSS and JS inline that displays an animated synthwave sunset scene: glowing gradient sun with scanlines, infinite neon grid scrolling toward viewer, starfield, reflected horizon. No external resources. Need to output index.html file.
We'll need Canvas or maybe CSS + keyframes. Canvas would be more flexible. We'll write JS that renders to a canvas each frame using requestAnimationFrame.
Features:
- Starfield: random stars moving slowly (or static) with twinkling.
- Horizon: a reflected horizon line where grid meets water, with gradient.
- Neon grid: perspective grid receding to vanishing point, with lines moving toward viewer, giving illusion of infinite grid.
- Sun: large glow gradient circle with scanlines effect (maybe using semi-transparent horizontal lines across sun).
- Reflection: inverted version of sun on water with slight distortion.
Simplify: draw background gradient (dark night sky to a deep blue). Draw stars as small white points. Draw sun as radial gradient circle near horizon, with horizontal lines overlay (scanlines). Draw grid: perspective lines from horizon: draw vertical lines receding, and horizontal lines moving "forward". We can simulate by incrementing a offset and drawing lines.
We can also draw reflection: draw a mirrored version of sun and grid with a scaling and opacity.
Implement in JS.
Canvas size: full window. On resize, adjust canvas.
Implement an animation loop.
Pseudo steps in draw:
- Clear canvas.
- Set background gradient: top dark (black), middle deep blue, bottom near horizon maybe purple.
But synthwave typical sky is gradient from dark blue to magenta top to orange near horizon. Let's use vertical gradient from #0a0a2a (dark) to #2b0b58 (purple) to #ff6b6b (pinkish) near horizon.
Actually simpler: gradient fillRect.
- Draw stars: generate array of star positions (x,y) randomly, small radius, maybe twinkle by varying opacity.
- Draw sun: position at horizon height (say canvas.height * 0.6). Use radial gradient with colors: bright center (#ffcc33), outer (#ff6600), transparent beyond.
- Draw scanlines: draw many horizontal lines across sun area with slight transparency.
- Draw grid: perspective lines converge at vanishing point (center x, horizon y). We'll draw vertical lines: a set of lines spaced in x, each from vanishing point down to bottom (or to horizon). For animation, we scroll by moving "z" offset for horizontal lines: they appear as receding lines moving forward (like in classic "grid floor").
Simplify approach similar to classic demo: Use a loop that steps over rows (z) and calculate perspective scale: y = horizon + (row * scale) where scale = some factor based on perspective.
Implement as: for each grid line (in both directions) compute its screen y based on depth (z). As z increases each frame, lines move downward.
We can draw grid as using lines: draw vertical lines from vanishing point to bottom at x positions determined by spacing * perspective.
Procedure: define perspective parameters: vanishingPoint = {x: canvas.width/2, y: horizonY}. For each column i from -N to N, compute worldX = i * spacing. Then compute screenX = vanishingPoint.x + worldX * scale where scale = (some factor) maybe (depth / (depth + z)). With a simple linear perspective: scale = near / (z + near). We'll simulate.
Simplify: compute perspective based on a variable offset that increases each frame for grid movement: Let offset be how far we have moved forward. For each line, we compute "z" = i*spacing + offset, then perspective factor = focal / (focal + z). x = vanishingX + z * factor? Actually typical formula: screenX = vanishingX + worldX * (focal/(focal+z)). Similarly for horizontal lines: worldZ changes.
Let's implement: focal = 250. offset increments each frame.
For vertical lines: for i in range -gridCount..gridCount; worldX = i*gridSpacing; screenX = vanishingX + worldX * (focal/(focal + offset)); we draw line from horizonY to bottom, but also need to compute the bottom point using same scaling but with larger z (distance to bottom). Actually vertical lines should converge at vanishing point, so they start at vanishing point and go to bottom (larger y). The x coordinate at bottom can be computed using worldX and a larger z (like offset + depth).
Better to draw them as lines from (vanishingX, horizonY) to (screenX, canvas.height). That's okay for neon grid effect.
For horizontal lines: they are lines parallel to horizon, spaced in depth, moving toward viewer. For each depth step j, compute y = horizonY + (j * spacing) * factor?? Use perspective scaling as: worldZ = j*gridSpacing + offset; screenY = horizonY + (worldZ * (focal/(focal+worldZ))) maybe compute a scaling factor for vertical.
Simplify: we can draw a series of horizontal lines that get closer together as they approach horizon, using simple formula: y = horizonY + (j * spacing) * (1 - (j * spacing)/maxDepth). Might be messy.
Alternative: Use simple approach: draw horizontal lines at y positions using perspective: for each k from 0..numLines, compute depth = k * lineSpacing + offset; perspective = focal/(focal + depth); y = horizonY + depth * perspective; draw line across canvas from leftX to rightX where leftX = vanishingX - (canvas.width) * perspective? Not accurate.
Simplify further: Many synthwave demos use a simple approach: define a "grid" that scrolls vertically. Compute a series of lines where (y) positions are vanishingY + offset/scale. Let's use known formula:
Given a perspective where objects at depth z appear at y = horizonY + z * scale, with scale = focal/(focal + z). So y = horizonY + z * focal/(focal + z). As z increases, y approaches horizonY + focal (asymptote). Actually y approaches horizonY + focal as z->inf? Not correct.
Ok maybe better to use known sample code for synthwave grid using 2D canvas: There is code available but offline.
I'll approximate: Use a loop for depth = offset to offset + maxDepth step lineSpacing; compute y = horizonY + (depth * 200) / (depth + 200) maybe.
Let's pick a simple mapping: y = horizonY + (depth / (depth + 200)) * (canvas.height - horizonY). This will map depth 0 => horizonY, depth large => near bottom.
So for each lineDepth = offset + i*lineSpacing, compute y = horizonY + (lineDepth / (lineDepth + 200)) * (canvas.height - horizonY). Then draw a line from left to right with neon color.
Similarly vertical lines: for each i, compute worldX = i*gridSpacing; compute x = vanishingX + worldX * (depthFactor?), but vertical lines should be drawn from horizon to bottom along that x coordinate as we computed earlier: for each vertical line, we can compute x at bottom using similar mapping: For bottom depth = maxDepth, factor = (maxDepth) / (maxDepth + 200). Actually use same scaling: x = vanishingX + worldX * (depthFactor), where depthFactor = (maxDepth) / (maxDepth+200). So all vertical lines converge near vanishing point.
Simplify: draw vertical lines as vanishingX to bottomX: bottomX = vanishingX + worldX * ( (canvas.height - horizonY) / 200 )? Not.
Given time constraints, maybe produce a decent visual with simple approach: draw a grid of lines as per classic 2D perspective: we can use transform: ctx.setTransform(scale, 0, 0, 1, translateX, translateY). Actually we can use a simple approach: draw a repeating pattern with scale.
Alternative: Use CSS with background and animations? Could use multiple divs with CSS gradients. But more reliable with canvas.
Ok implement a simple version: We'll draw many horizontal lines whose y positions follow a simple formula: y = horizonY + Math.tan(angle) * something. But it's okay.
Let's implement:
- Params:
const horizon = canvas.height * 0.6;
const focal = 250;
const gridSpacing = 30; // world unit
const lineSpacing = 30; // depth step
let offset = 0;
- In draw loop:
offset += 2; // move forward
// Draw vertical lines:
for (let i = -20; i <= 20; i++) {
const worldX = i * gridSpacing;
const scale = focal / (focal + offset);
const x = horizonX + worldX * scale;
ctx.strokeStyle = 'rgba(0,255,255,0.2)';
ctx.beginPath();
ctx.moveTo(x, horizon);
ctx.lineTo(x, canvas.height);
ctx.stroke();
}
// Draw horizontal lines:
const maxDepth = 2000;
for (let d = offset; d < offset + maxDepth; d += lineSpacing) {
const scale = focal / (focal + d);
const y = horizon + d * scale;
const left = horizonX - (canvas.width) * scale;
const right = horizonX + (canvas.width) * scale;
ctx.beginPath();
ctx.moveTo(left, y);
ctx.lineTo(right, y);
ctx.stroke();
}
This should produce perspective lines that get denser near horizon.
Reflection: draw same grid inverse: after drawing grid, we can draw a mirrored copy using ctx.save and apply scaling(1, -1) about horizon line.
But need to offset to horizon to reflect: ctx.save(); ctx.translate(0, horizon*2); ctx.scale(1, -1); draw again with lower opacity.
We'll draw sun reflection similarly: draw sun at same X,y but after scaling -1.
Plan code accordingly.
Now produce index.html with canvas full-screen, resize handler, requestAnimationFrame.
Add CSS for body margin 0, background black.
Now construct final code.
/workspace/index.html (3831 bytes)Successfully wrote 3831 bytes to /workspace/index.html
Implemented a complete, self‑contained synthwave sunset web app: - Single **index.html** with inline CSS and JavaScript. - Features: animated neon grid, glowing gradient sun with scanline effect, starfield, and mirrored horizon reflection. - No external dependencies; runs fully sandboxed. File created: **/workspace/index.html**. Open it in a browser to see the animated scene.
TAKE IT TO CODE
A single self-contained HTML file — copy it and open in any browser, or reuse the prompt with your own pi-agent run.
RUN
EVALUATION
Animated synthwave sun and grid with starfield, but the grid seems clipped/narrow in most frames and no reaction to input.
Vision grade · anthropic/claude-sonnet-5
MEASUREMENTS
Raw readings from the capture, not a verdict — the grade above is the only judgement. Percentages are how much of the screen changed; the measure under-reports sparse motion on a dark background.