Single-page demoscene-style web demo built with Vite + TypeScript, Canvas 2D, and Web Audio API.
- The on-page view counter fetches
/api/viewscontinuously (default every 5 seconds) so audience totals can update live while the page is open.
- Node.js 18+ recommended.
- Place an MP3 at
public/song.mp3(replace the placeholder file). - Edit
public/timeline.release.jsonto change the intro terminal script, section timings, transitions, or text cues. Usemm:ssormm:ss.stime strings for section and cue start/end values (for example,01:44.5). - Sacred anchor policy: before changing timeline timings, read
docs/sacred-musical-anchors.md. The listed musical anchor timestamps are mandatory and must remain locked unless the soundtrack itself is intentionally recut; secondary effect-switch cues in that doc are advisory and must never override the sacred anchors. - Rap lyric cue timing is also locked: the rap starts at 03:25.012 on the word “All”, and the detailed lyric cue anchors in
docs/sacred-musical-anchors.mdmust be preserved when editingtextCues. public/timeline.release.jsonis the canonical timeline and adds per-section era presets plus a curated arc for the graphics-history progression.- The bundled timeline includes lyric-style overlays in
textCues; adjust or replace those cues to change the on-screen callouts synced to the music. - Timeline playback now applies
audio.offset: -0.128inpublic/timeline.release.jsonso the in-app timeline clock matches manual MP3 timing checks (e.g., Audacity) instead of drifting late. - Transition types include
fade,wipe,slide-left,slide-right,slide-up,slide-down,iris,flash,glitch,shatter,signal-collapse,camera-punch-through,bitplane-wipe, andaudio-reactive-particle(vertical VGA-style bands with staggered timing). - Effect sections can include a
paramsobject to tune effect-specific settings such as starfield speed, warp, or turning intensity. - Effects can animate numeric params with an
automationarray on a section or a layer; entries are applied in array order (last wins) and ease over absolute demo time. - Sections can optionally define
layersto mix multiple effects together, withblendmodes likescreenoroverlayand per-layeropacity. - The timeline includes two 3D showcase effects:
proper3d(perspective projection + lighting) andfake3d(2D skew/shading tricks). Thesphere3deffect renders a rotating lit point sphere with orbiting satellites. - The
spherecloudeffect renders a glowing point-cloud sphere with subtle audio-reactive rotation and lighting. - The
infinitycloudeffect renders a glowing point-cloud infinity loop with audio-reactive pulses and lighting. - The
volumetric_cloudseffect renders layered procedural cloud banks with parallax depth and audio-reactive density pulses; tuneparamslikedensity,layers,windSpeed,cloudScale,detail,sunlight,haze, andaudioReact. - The
flyovereffect renders a sky/sea flythrough with distant islands; tuneparamslikespeed,horizon,seaDetail,islandSeed, andpalette. - The
synthwaveSunseteffect renders an outrun sunset with a striped sun, neon sky, and reflective sea; tuneparamslikehorizon,sunRadius,stripeHeight,stripeGap, andseaSpeed. - The
skyboxTransitioneffect renders a panoramic atmospheric backdrop that smoothly evolves from day through sunset into surreal night; tuneparamslikespeed,intensity,horizon,cloudAmount,starAmount,silhouetteAmount,surrealness,audioReactive,loop, andphaseOffset. - The
border_multiplexeffect fakes border-breaking sprites and multiplexed raster reuse; tuneparamslikehwSprites,totalSprites,bandHeight,spriteSize,speed,rasterJitter,borderMaskStrength,audioReact, andseed. - The
raineffect renders layered storm rain with turbulence, optional ground splashes, and low mist bands; tuneparamslikeintensity,wind,speed,streakLength,splash,hue,storm,turbulence,mist, andseed. - The
lightningeffect renders brief flash overlays with optional bolt branches; tuneparamsliketrigger,chancePerSecond,cooldown,flashDuration,bolt,branches, andseed. - The
amiga_showcaseeffect layers copperbars, shadebobs, a twister ribbon, and optional glenz vectors for a 16-bit demo part; tuneparamslikebarCount,barSpeed,barWaveAmp,barWaveFreq,barSaturation,bobCount,bobRadius,bobTrail,bobIntensity,twistWidth,twistAmp,twistSpeed,twistSlices,twistHueSpeed,twistX,glenz, andaudioReact. - The
chesseffect renders a deterministic, self-playing chess match with clearer, silhouette-driven pieces that better read as crowns, crosses, mitres, battlements, and horse heads; tune pacing withparams.speedor anchor withparams.startTime. - The
gl_fractal_tunneleffect renders a WebGL2 raymarched tunnel with audio-reactive pulses and bloom; tuneparamslikequality,warp,hueShift,exposure, andseed. It falls back to thetunneleffect when WebGL2 is unavailable. - The
physics_pileeffect simulates a stack of 2D rigid bodies with independent box rendering (no connector lines) and floor-origin kick pulses that throw boxes upward; tuneparamslikecount,restitution,friction,gravity,kickImpulse/beatImpulse,kickRadius,scatterAngleDeg,scatterJitter,kickUpBias,kickTorque,loosenDuration,loosenFrictionMult,loosenRestitutionAdd,loosenPosCorrMult,loosenExtraSlop,maxLinVel,maxAngVel,kickOrigin,kickOriginY,sepBiasDeg,spawnMode,trail,seed,wreckingCue(swings in a heavy ball), andshatter(freezes and dissolves the stack into particles). - The
gl_impossible_corridoreffect renders a WebGL2 raymarched impossible corridor with bass-driven breathing, beat kicks, and treble shimmer; tuneparamslikequality,warp,hueShift,exposure,seed, andspeed. It falls back to thetunneleffect when WebGL2 is unavailable. - The
neon_alleyeffect renders a WebGL2 raymarched neon alley with audio-reactive shimmer; tuneparamslikequality,speed,exposure,hueShift, andseed. It falls back to theneoneffect when WebGL2 is unavailable. - The
space_hangareffect renders a WebGL2 raymarched sci-fi hangar flythrough with bass-driven camera shake; tuneparamslikequality,speed,exposure,hueShift, andseed. It falls back to thetunneleffect when WebGL2 is unavailable. - The
raymarch_fractaleffect renders a WebGL2 raymarched fractal emerging from a ground plane with audio-reactive palette shifts; tuneparamslikequality,fractal,cameraRadius,cameraHeight,cameraOrbitSpeed,paletteSpeed,audioReact,beatKick, andfractalScale. It falls back to thefractaleffect when WebGL2 is unavailable. - The
explicitpixelseffect can switch between a generated assignment-only static frame (mode: "explicit") and an audio-reactive procedural animation (mode: "procedural"). The checked-in assignment frame is treated as source-of-truth;npm run gen:explicitpixelsonly refreshes metadata and will not regenerate frame bytes. - The
wireframeRideeffect renders a WebGL2 wireframe terrain flythrough with neon gradients, fog, and audio-reactive pulses; tuneparamslikespeed,gridWidth,gridDepth,gridResX,gridResZ,amplitude,noiseFreq,cameraHeight,fov,fog,neon,bassReactive,rmsReactive, andsun. It falls back to theisogrideffect when WebGL2 is unavailable. - The
roadDriveeffect renders a WebGL2 night highway drive with dashed lane markers, guardrail glow, and horizon fog; tuneparamslikespeed,roadWidth,laneDashLength,laneGap,fog,glow,cameraBob,curveStrength,curveFrequency,bassReactive, andrmsReactive. Camera bobbing keeps the road grounded near the lower frame edge to preserve the forward-driving illusion. It falls back to theisogrideffect when WebGL2 is unavailable. - The
prism_bloomeffect renders painterly spectral petals, soft bloom clouds, and drifting dust for a lush AI-art tableau; tuneparamslikebloom,flow,petalCount,smear,prismShift,vignette,audioReact, andseed. - The
velvet_dreamscapeeffect renders flowing silk ribbons, luminous gallery blooms, and subtle film grain for an unabashedly tasteful AI-art hero shot; tuneparamslikebloom,flow,ribbonCount,grain,hueDrift,focus,audioReact, andseed. - The
tetris_matrixeffect renders a self-playing falling-block match that cycles through all 12 pentominoes (five-cell shapes) with clearer side-panel HUD spacing, safe top-out game-over restarts, and visible mid-air spins plus quick-drop/hesitation choices for a monochrome handheld LCD vibe; tuneparamslikespeed,level,glow,contrast,ghost, andseed. - The
introblock controls the terminal presentation fromt=0untilintro.end; the first section must start exactly at the same time so the colour pipeline can take over. Script events are time-coded, so you can align story lines with audio or prior text cue timings. - Visuals include subtle camera zoom and panning that respond to audio energy.
- While the demo is running, tapping/clicking the main canvas injects an extra-strong manual beat pulse so audio-reactive effects (especially
physics_pile) thump like a kick hit. - Post-intro effects render on a 16:9 base canvas; landscape uses letterboxing, while portrait screens scale to fill the height and crop the sides.
- Append
?release=1to the URL to disable the debug overlay/keybinds. - Rendering quality can be tuned via URL query params:
?baseScale=2multiplies the base canvas size (default1, clamped to1-4).?baseW=640&baseH=360overrides the base canvas dimensions (must be 16:9 and between320×180and1920×1080).?quality=0.85scales the effective base resolution for performance (default1.0, clamped to0.65-1.0).?autoQuality=1enables dynamic quality scaling based on frame time (adjusts by0.05at most once per second).- The start overlay includes a render quality selector (
Best performance,Balanced,Maximum) so users can pick sensible defaults without query-string tuning. - Recommended:
baseScale=2for 1080p-class displays,baseScale=3for 1440p+ if your GPU/CPU allows.
Automation example:
{
"effect": "tunnel",
"params": { "speed": 1.15 },
"automation": [
{ "param": "speed", "from": 1.15, "to": 1.55, "t0": 122.0, "t1": 127.0, "ease": "easeInOutQuad" }
]
}Automation supports numeric params only; non-numeric values fall back to the base params.
- The timeline runs in two modes:
introandsections. While the current playback time is beforeintro.end, the app stays inintro; atintro.endand later it switches tosections. - During
intro, the renderer still receives the first section assectioncontext so post-intro visuals can warm up consistently, but transitions and text cues remain inactive. - Section lookup is start-time based: the active section is the last section whose
startis less than or equal to the current time. - Transition blending is evaluated only at section boundaries:
- A transition is active for
section.transition.durationseconds after a section starts. - Transition progress is clamped from
0to1. - The transition type uses the incoming section
transition.in(normalized tofadewhen omitted in JSON). - Supported transition types:
fade,wipe,slide-left,slide-right,slide-up,slide-down,iris,flash,glitch,shatter,signal-collapse,camera-punch-through,bitplane-wipe,audio-reactive-particle,checkerboard-wipe,venetian-blinds,radial-wipe,noise-threshold,portal-zoom,whip-pan,quantum-slice,chromatic-bloom,neural-feedback.
- A transition is active for
textCuesare active only whilestart <= currentTime <= end.- If the final section omits an explicit
end, it is treated as "until audio ends" and is finalized after audio metadata loads. introTimeis exposed in both modes:- In
intro,introTimeequals absolute playback time. - In
sections,introTimeequals elapsed time sinceintro.end.
- In
public/timeline.release.json follows this top-level shape:
{
"audio": { "src": "song.mp3", "offset": 0 },
"intro": {
"mode": "terminal",
"end": "00:54.15",
"theme": {
"bg": "#000000",
"fg": "#d0ffd0",
"accent": "#66ff66",
"dim": "#4c7f4c",
"fontFamily": "'IBM Plex Mono', monospace",
"fontSize": 20,
"lineHeight": 1.4,
"padding": 24,
"window": { "title": "boot", "chrome": true }
},
"script": [
{ "t": "00:00", "type": "prompt", "text": "boot sequence" },
{ "t": "00:02", "type": "type", "text": "loading...", "cps": 28 }
]
},
"sections": [
{
"id": "intro-neon",
"start": "00:54.15",
"end": "01:10.00",
"effect": "neon",
"era": "future",
"transition": { "in": "fade", "out": "flash", "duration": 0.8 },
"fitAlign": "fill",
"params": { "speed": 1.2 },
"automation": [
{ "param": "speed", "from": 0.8, "to": 1.4, "t0": "00:54.15", "t1": "01:10.00", "ease": "linear" }
],
"layers": [
{ "effect": "rain", "opacity": 0.5, "blend": "screen", "fitAlign": "centre", "params": { "intensity": 0.9 } }
]
}
],
"textCues": [
{
"id": "cue-1",
"start": "01:02.0",
"end": "01:06.0",
"text": "HELLO WORLD",
"x": 0.5,
"y": 0.72,
"align": "center",
"size": 42,
"color": "#ffffff",
"effects": { "glitchIn": true, "shadow": true }
}
]
}audio: soundtrack path and optional timeline offset in seconds.intro: terminal intro mode, end timestamp, visual theme, and scripted terminal events (prompt,type,enter,output,ascii,clear).sections: ordered effect schedule with IDs, timing, effect key, optional era preset (8bit,16bit,ps1,pcdemo,future), transitions,fitAlign(top/centre/bottom/fill), parameter overrides, optional automations, and optional layered effects.- In mobile-fit presentation,
fitAlign: top|centre|bottomnow maps each render into its own vertical third so multiple non-filllayers can be shown simultaneously without fullscreen overlap. - To author three simultaneous “main” effects in timeline JSON, set the section’s main
effect+fitAlignto one slot and add additionallayersusingblend: "source-over",opacity: 1, andfitAlignset to the other slots.
- In mobile-fit presentation,
textCues: optional overlay callouts with timing, position, typography, and optional per-cue visual effects (glitch, shadow, scanline mask, and typewriter speed).- Time fields accept either seconds (
number) or timeline strings (mm:ss/mm:ss.s).
Each timeline section effect maps to one of the entries below. Include any of the parameters in a section params object; omit or set to defaults to use the built-in values.
The curated public/timeline.release.json pass now gives every registry effect at least one primary section spotlight while preserving the historical-to-impossible era arc.
| Effect | Parameters | Notes |
|---|---|---|
starfield |
speed, warp, turnRate, turnStrength, drift, sparkle, colorShift |
Warp/turn adjust flight feel; drift/sparkle/colorShift add richer motion and chroma variation. |
plasma |
speed |
|
raster_bars |
orientation, barCount, barThickness, speed, waveAmp, waveFreq, splitStrength, scanlineStep, border, borderSize, palette, audioReact, beatThump |
orientation supports horizontal or vertical; palette supports c64, atari, spectrum, or rainbow. |
kefrens_bars |
barCount, barWidth, amp, freq, speed, phaseOffset, palette |
palette supports rainbow, c64, or amiga. |
copper_gradient_splits |
scanStep, gradientRowStep, barCount, speed, barWobble, barHueStep, hueWobble, saturation, lightnessBase, lightnessPeak, splits, hamish, hamishStrength, paletteClamp, paletteClampSteps, audioReact, beatKick |
Copper bar gradients with optional pseudo-high-colour splits. |
bumpmap_plane |
bufW, bufH, bumpStrength, ambient, diffuseStrength, specStrength, shininess, lightZ, lightSpeed, embossText, embossStrength, animateBumps, waveAmp, waveFreqX, waveFreqY, baseHue, paletteMode, scanlines, audioReact, beatKick, seed |
CPU bump-mapped plane with moving light and optional embossed text. |
vga_fire |
fireW, fireH, stepsPerFrame, baseHeat, sparkChance, decay, wind, windWave, turbulence, gustOnBeat, logoText, logoSize, logoY, audioReact, scanlines, glowStrength |
Classic VGA/DOS fire with optional logo mask. |
tunnel |
speed |
|
dotTunnel |
ringCount, dotsPerRing, fov, speed, twist, palette, glow, seed |
Depth-sorted sprite/ring tunnel; palette selects built-in color ramps. |
moire_grid |
spacing, lineWidth, speed, warp, intensity, palette, audioReact |
Warped interference grid; palette supports cyan, magenta, or amber. |
recursiveFracture |
seed, shapeCount, maxDepth, splitBias, angleJitter, gap, strokeWidth, fillAlpha, lineAlpha, progressSpeed, progressMode, beatPunch, bassInfluence, trebleDetail, paletteMode, minFragmentSize |
Deterministic recursive subdivision panes; progressMode supports outward/inward, and paletteMode supports mono, era, or heat. |
moving_shadow_map |
seed, objectCount, lightCount, lightSpeed, lightHeightMin, lightHeightMax, shadowLength, shadowSoftness, floorGrid, paletteMode, contrast, haze, orbitRadius, colorA, colorB, lightColor |
Faux-3D Canvas2D scene with orbiting lights and projected moving shadows over a ground plane. |
textmode_charset |
cols, rows, glyphSet, mode, speed, palette, scanlines, seed |
Coarse character-grid renderer with glyph ramps ( .:-=+*#%@) and palette-indexed tinting. |
rotozoom |
speed |
|
blobs |
count, radius, orbit, speed, glow |
|
metaballs |
bufW, bufH, count, baseRadius, radiusVar, baseThreshold, edgeSoftness, normalZ, ambient, diffuse, specStrength, shininess, rimStrength, palette, hueSpeed, smoothing, glow, audioReact, beatKick, seed |
Implicit surface metaballs with chrome/neon lighting; palette supports chrome or neon. |
ribbons |
count, speed, amplitude, audioBoost, offset, spacing, thickness |
|
skeletal_ribbon |
boneCount, length, thickness, waveAmp, waveFreq, stiffness, audioInfluence, colorMode, hueShift, glow, debugSkeleton |
Articulated spine/tentacle ribbon driven by chained bone kinematics with beat-reactive pulse thickness. |
lissajous |
points, speed, a, b, radius, lineWidth |
|
marble |
scale, veinScale, contrast, brightness, speed, turbulence, layers |
Animated marble veins using turbulent sine domain warping; audio subtly modulates turbulence, vein scale, and brightness. |
glitch |
sparkles, sparkleSize, sliceCount, sliceBoost, sliceHeight, sliceVariance, offset, shake, maxShake |
|
bokeh |
count, speed, radius, alpha, hueShift |
|
fractal |
iterations, trebleBoost, speed, scale, alpha |
|
feedback |
scale, wobble, rotation, trail, glow |
|
equalizer |
bars, barWidth, height, bassBoost, alpha |
|
spectrum_analyzer |
bands, smoothing, curve, tilt, peakHold, grid, glow |
Parametric-EQ-style spectrum trace with log-spaced bins and peak-hold markers. |
isogrid |
opacity, lineWidth, spacing, wave, speed |
|
kaleidoscope_symmetry |
slices, rotationSpeed, radialZoom, mirror, patternScale, patternWarp, centreX, centreY, colorShift, glow, audioReactive, bassInfluence, trebleInfluence, spinOnBeat, ringDensity |
Mirrored radial wedges driven by a procedural source pattern for mandala/starburst motion. |
neon |
shapes, radius, radiusStep, speed, glow, lineWidth |
|
particles |
trail, burst, burstAudio, force, forceAudio |
|
particleAttractors |
count, seed, attractorCount, strength, swirl, damping, speedLimit, softening, absorbRadius, spawnMode, trailAlpha, particleSize, glow, motion, audioReactive, beatPulse, colorMode, backgroundFade, vignette |
Gravity-well particle flow with swirling paths; spawnMode supports edges, ring, or random, and colorMode supports mono, era, or heat. |
border_multiplex |
hwSprites, totalSprites, bandHeight, spriteSize, speed, rasterJitter, borderMaskStrength, audioReact, seed |
|
fluid |
speed, dissipation, splatCount, splatSize, turbulence, hueShift, seed |
|
reactionDiffusion |
scale, simScale, feed, kill, diffA, diffB, steps, seed, contrast, brightness, invert, paletteMix, audioReactivity, beatPulse, drift, reseedCue |
Gray–Scott style reaction-diffusion simulation with evolving spots/stripes, subtle audio pulse response, and era-aware output shaping. |
smoke_simulation |
density, flowSpeed, turbulence, swirl, diffusion, softness, emission, emitMode, scale, colorMode, hueShift, audioReactive, bassInfluence, midInfluence, trebleInfluence, seed, highlights |
emitMode supports centre, bottom, random; colorMode supports mono or tinted. |
boids_simulation |
count, speed, cohesion, alignment, separation, neighborRadius, separationRadius, trail, size, seed |
Audio-reactive flocking simulation with wraparound space and neon boid trails. |
finale |
trail, starSpeed, starWarp, starTurn, particleCount, particleForce, bars, barHeight |
|
proper3d |
speed |
|
fake3d |
speed |
|
textured_cube |
scale, camDist, focalMul, rotXSpeed, rotYSpeed, rotZSpeed, backfaceCull, perspectiveCorrect, edge, edgeAlpha, shadeStrength, audioReact, beatKick, textureAnim |
Software-textured cube with optional affine/perspective mapping. |
sphere3d |
speed |
|
spherecloud |
speed |
|
infinitycloud |
speed |
|
infinite_zoom_droste |
speed, scaleBase, rotationSpeed, detail, glow, pulse, twist, seed, shape, fitMode |
shape supports portal, rings, or grid; fitMode supports auto or safe. |
infiniteMirror |
depth, scale, rotation, twist, offsetX, offsetY, pulse, glow, softness, vignette, monochrome, mirrorFrames, baseScene, symmetry, strobeOnBeat, feedbackMix |
Self-referential mirror corridor recursion using feedback; baseScene supports grid, rings, checker, bars, void. |
volumetric_clouds |
density, layers, windSpeed, cloudScale, detail, sunlight, haze, audioReact |
Layered procedural cloudscape with parallax and soft haze. |
voronoi_cells |
cellCount, drift, speed, lineWidth, lineAlpha, fillAlpha, contrast, jitter, paletteMode, beatPulse, shade, seed, pixelStep, chromatic |
Animated Voronoi-style cellular mosaic with era-aware palette bias; paletteMode supports mono, neon, heat, or era. |
torus_orbit_3d |
ringCount, pointsPerRing, majorRadius, minorRadius, spinSpeed, wobbleSpeed, depth, glow, palette, audioReact |
Orbiting 3D torus points; palette supports teal, violet, or amber. |
raytrace_spheres |
quality, bufW, bufH, sphereCount, maxDepth, floorReflect, shininess, diffuseStrength, specStrength, ambient, fov, cellSize, adaptive, refineThreshold, refineGrow, aa, aaMode, outputSmoothing, forceAA, audioReact, beatKick, scanlines, seed |
Low-res software raytraced spheres with reflections. |
chess |
speed, showHighlights, startTime |
Deterministic self-playing chess match with clearer silhouette-led pieces, distinctive major-piece markers, and move highlights. |
cloth_sim |
width, height, cols, rows, gravity, damping, stiffness, iterations, wind, flutter, audioReactive, pinMode, driftX, driftY, shading, seamAlpha, paletteMode, backgroundAlpha, mobileQuality, obstacle, obstacleSize |
Canvas2D verlet cloth mesh with pinned anchors, gust-driven folds, beat billows, and era-aware shading that can run as base or composited layer. |
flyover |
speed, horizon, seaDetail, waveSpeed, waveIntensity, islandCount, islandSeed, fog, palette, audioReactive |
palette supports day, sunset, night. |
voxel_landscape |
bufW, bufH, speed, turnRate, turnWobble, camH, heightBob, beatBump, fov, horizon, scale, maxDist, stepBase, stepGrow, fogStrength, audioReact, beatKick, scanlines, seed |
Heightfield voxel landscape flyover with portrait-aware camera framing. |
voxel_world_builder |
buildProgress, cityDensity, glow, cameraLift, seed |
WebGL2 instanced voxel city assembler (64x64 cubes); falls back to Canvas2D isometric voxels when WebGL2 is unavailable. |
gl_fractal_tunnel |
quality, warp, hueShift, exposure, seed |
Falls back to tunnel when WebGL2 is unavailable. |
physics_pile |
count, restitution, friction, gravity, kickImpulse, beatImpulse, kickRadius, scatterAngleDeg, scatterJitter, kickUpBias, kickTorque, loosenDuration, loosenFrictionMult, loosenRestitutionAdd, loosenPosCorrMult, loosenExtraSlop, maxLinVel, maxAngVel, kickOrigin, kickOriginY, sepBiasDeg, spawnMode, trail, seed, wreckingCue, shatter |
spawnMode supports pile or rain; joints stay simulation-only so boxes render without connector lines. |
polar_tunnel |
rotateSpeed, wobbleFrequency, wobbleSpeed, wobbleAmount, radialWobbleFrequency, radialWobbleSpeed, radialWobbleAmount, radialFrequency, angularFrequency, colorCycles, audioReact |
Center-relative polar tunnel with angle/radius wobble and a sine palette for demoscene-style concentric motion. |
gl_impossible_corridor |
quality, warp, hueShift, exposure, seed, speed, internalScale |
Falls back to tunnel when WebGL2 is unavailable. |
neon_alley |
quality, speed, exposure, hueShift, seed |
Falls back to neon when WebGL2 is unavailable. |
space_hangar |
quality, speed, exposure, hueShift, seed |
Falls back to tunnel when WebGL2 is unavailable. |
wireframeRide |
speed, gridWidth, gridDepth, gridResX, gridResZ, amplitude, noiseFreq, cameraHeight, fov, fog, neon, bassReactive, rmsReactive, sun |
Falls back to isogrid when WebGL2 is unavailable. |
roadDrive |
speed, roadWidth, laneDashLength, laneGap, fog, glow, cameraBob, curveStrength, curveFrequency, bassReactive, rmsReactive |
Falls back to isogrid when WebGL2 is unavailable. |
vector3d_balls |
model, pointCount, wireframe, roundDots, baseDotSize, dotDepthScale, lineWidth, camDist, rotXSpeed, rotYSpeed, rotZSpeed, trail, stripeFreq, stripeSpeed, stripeStrength, palette, audioReact, beatKick, seed |
model supports cube, sphere, torus. palette supports c64, spectrum, rainbow. |
envmap_donut |
bufW, bufH, segmentsU, segmentsV, R, r, camDist, focalMul, rotXSpeed, rotYSpeed, rotZSpeed, fresnelStrength, specStrength, shininess, chromeDesat, backfaceCull, scanlines, edge, audioReact, beatKick, seed |
Software environment-mapped chrome torus. |
explosionBurst |
startTime, duration, seed, intensity, radius, particleCount, smokeCount, debrisCount, gravity, drag, turbulence, turbulenceScale, fade, audioReactive |
One-shot seeded explosion burst with flash, fireball, debris streaks, and rolling smoke fade for beat-synced impact moments. |
poly_morph_showcase |
lat, lon, morphSpeed, styleSpeed, style, camDist, focalMul, rotXSpeed, rotYSpeed, rotZSpeed, sat, baseHue, hueSpeed, solidAlpha, glenzAlpha, shadedAlpha, edge, edgeAlpha, sortSolid, sortShaded, sortGlenz, audioReact, beatKick, seed |
style supports auto, solid, glenz, shaded. |
glenz_vectors |
model, instances, camDist, focal, rotXSpeed, rotYSpeed, rotZSpeed, baseHue, hueSpeed, sat, lightness, faceAlpha, edge, edgeAlpha, lineWidth, trailFade, sortFaces, audioReact, beatKick, seed |
model supports cube, octa, icosa; sortFaces supports none or backToFront. |
god_rays |
sourceX, sourceY, rayCount, spread, intensity, haze, occlusion, drift, pulse, warmth, dust, seed, style, sourceDriftX, sourceDriftY, shadowBands |
Atmospheric volumetric-style shafts with drifting haze, procedural occluders, and style variants (sunbreak, window, cathedral). |
synthwaveSunset |
horizon, sunRadius, stripeHeight, stripeGap, seaSpeed, starCount, glow, scanlines, audioReactive |
|
skyboxTransition |
speed, intensity, horizon, cloudAmount, starAmount, silhouetteAmount, surrealness, audioReactive, loop, phaseOffset |
Evolving panoramic skybox backdrop that blends day, sunset, twilight, and surreal night moods. |
taco_meteor_shower |
shellCount, fallSpeed, swirl, burst, stardust, toppingSpread, audioReact, seed |
Luminescent taco shells cascade like meteors, shed sparkling stardust, and splat into avocado/cilantro/salsa confetti. |
rain |
intensity, wind, speed, streakLength, splash, hue, storm, turbulence, mist, seed |
storm controls downpour density/velocity, turbulence adds sideways sway, and mist controls near-ground fog bands. |
water_drops |
dropCount, minRadius, maxRadius, fallSpeed, distortion, trail, audioReact, tint, refraction, microDrops, rivulets, seed |
Layered wet-glass droplets with refractive cores, chromatic rims, sparkling micro-beads, and flowing rivulet streaks. |
fireworks_display |
shellRate, burstSize, glitter, trail, gravity, hueShift, audioReact, launchSpread, seed |
Audio-reactive fireworks with ember launch tails, deterministic shell timing, sparkling burst spokes, and smoky bloom rings. |
cosmic_voyage |
speed, warp, starDensity, galaxyGlow, nebula, asteroidDensity, planetCount, parallax, bloom, seed |
Cinematic deep-space flythrough with layered galaxies, planets, and asteroid belts. |
caustics |
scale, speed, intensity, contrast, warp, detail, background, color, glow, seed, audioReactive, driftX, driftY |
Animated refractive caustic web with converging bright filaments and soft glow bloom. |
prism_bloom |
bloom, flow, petalCount, smear, prismShift, vignette, audioReact, seed |
Painterly prism petals and spectral bloom clouds for a tasteful AI-art showcase beat. |
velvet_dreamscape |
bloom, flow, ribbonCount, grain, hueDrift, focus, audioReact, seed |
Layered silk ribbons, luminous blooms, and gallery grain for a tasteful AI-art statement shot. |
platformerScroll |
speed, seed, tileSize, groundRatio, parallaxFar, parallaxMid, parallaxFront, audioReact, beatKick, platformRate, platformMaxSteps, skyGlow, speedLines, collectibleRate |
Deterministic side-scrolling platformer parallax scene with twinkling sky glow, speed streaks, polished bobbing pickups, and a colorful neon astronaut mascot runner. |
tetris_matrix |
speed, level, glow, contrast, ghost, seed |
Self-playing falling-block match with all 12 pentominoes, clearer side-panel HUD spacing, safe top-out game-over restarts, and visible mid-air spins plus quick-drop/hesitation choices in tricky spots. |
matrix_rain |
speed, density, fontSize, trail, glow, brightness, jitter, audioReact, glyphSet, seed |
Matrix-style falling code rain tuned for slower, smoother descent with smaller glyphs and subtle default jitter. |
tilingMorph |
scale, morphSpeed, morphAmount, rotationSpeed, lineWidth, fillAlpha, paletteShift, audioReactive, cellJitter, roundedness, contrast, seed, backgroundAlpha, mode |
Seam-safe lattice tiling morph that cycles square, diamond, skewed, and rounded-interlocking phases; mode supports mono, palette, neon. |
gameOfLife |
cellSize, stepRate, seed, density, wrap, paletteMode, gridLines, fadeTrails, gliderRate, patternMode, burstOnBeat, survivalTint, safeFit |
Conway-style cellular automata with deterministic seeding, curated inserts, optional wrap edges, and restrained beat-triggered bursts/gliders. |
hexGridPulse |
cellSize, speed, waveScale, rippleStrength, pulseStrength, lineWidth, fillAlpha, glowAlpha, audioReactive, paletteMix, invert |
Hexagonal lattice with travelling waves, radial ripples, and controlled audio-reactive pulse highlights across eras. |
doodle_greetz_wall |
layout, transitionStyle, cycleSeconds, columns, padding, highlightPulse, beatPulseDecay, audioReact, title |
Pulls approved PNG doodles from the doodle API and renders them in grid or carousel layouts. |
lightning |
trigger, chancePerSecond, cooldown, flashDuration, bolt, branches, seed |
trigger supports beat, random, both. |
effect_evolution |
density, motion, warp, trail, seed |
Reinterprets the same lattice across eras. |
amiga_showcase |
barCount, barSpeed, barWaveAmp, barWaveFreq, barSaturation, bobCount, bobRadius, bobTrail, bobIntensity, twistWidth, twistAmp, twistSpeed, twistSlices, twistHueSpeed, twistX, glenz, audioReact |
|
twister |
x, baseWidth, amplitude, turns, speed, sliceH, sat, hueSpeed, minWidthScale, maxWidthScale, minAlpha, maxAlpha, edgeShade, background, trailFade, texture, audioReact, beatKick |
x accepts pixels or normalized 0-1; background supports clear or fade; texture supports solid or pattern. |
sine_scroller_logo |
message, fontSize, speed, waveAmp, waveSpeed, wavePhaseStep, scrollerY, scrollerX, layer2, layer2Speed, layer2FontSize, layer2Y, logoText, logoFontSize, logoY, scanlineStep, logoWaveAmp, logoWaveSpeed, logoWaveFreq, audioReact, beatBoost |
Scroll + sine wave + scanline logo wobble. |
soft_shadows |
count, lightAngle, lightSweep, height, shadowLength, softness, contactHardness, passCount, objectSize, motion, floorGlow, audioReactive, palette |
Layered faux-PCF floor shadows with deterministic contact hardening and smooth penumbra falloff. palette supports studio, sunset, or mono. |
lens_flare |
intensity, sourceX, sourceY, haloRadius, ghostCount, ghostSpread, streakStrength, ringStrength, chromatic, audioReactive, shimmer, bgDim, seed, blendBias |
Cinematic optical flare with centered ghost chains, soft rings, and an optional anamorphic streak. |
lens_wobbler |
bufW, bufH, rotSpeed, baseScale, zoomAmp, zoomSpeed, scrollU, scrollV, lensRadius, lensStrength, invertRing, wobble, wobbleAmp, wobbleFreq, wobbleSpeed, wobbleSlice, audioReact, beatKick, seed, lensPath |
Bubble lens warp with optional jelly wobble. |
shadebobs_bobs |
mode, shadeCount, bobCount, shadeScale, blobRadius, trailFade, blend, hueSpeed, steer, maxSpeed, spriteSize, boingCheckers, bobAlpha, fastBlob, audioReact, beatPulseStrength, dirtyRects, seed |
Amiga-style bobs mixed with shadebobs interference. |
sine_distorter |
mode, amp, freq, speed, slice, phase, sourceScale, edges, source, logoText, audioReact, beatBoost, glow |
Wavy glass distorter (scanline or column sine shifts). |
fractal_zoomer |
setType, zoom, centerX, centerY, iterations, paletteSpeed, audioReact |
setType supports mandelbrot, julia, or burningShip. |
explicitpixels |
mode, speed, audioReact |
mode supports explicit (generated wall of byte assignments) or procedural (loop-driven animation). |
raymarch_fractal |
quality, fractal, cameraRadius, cameraHeight, cameraOrbitSpeed, paletteSpeed, audioReact, beatKick, fractalScale |
fractal supports mandelbulb or mandelbox. |
Use voxel_world_builder for the lyric moment "we can build whole worlds, all we have to do is ask for it" and drive the staged assembly with timeline automation over the section duration.
Recommended section setup:
{
"id": "rap-world-build",
"start": "03:44.4",
"end": "03:49.2",
"effect": "voxel_world_builder",
"params": {
"buildProgress": 0,
"cityDensity": 0.62,
"glow": 0.85,
"cameraLift": 0.15
},
"automation": [
{ "param": "buildProgress", "from": 0.0, "to": 1.0, "t0": "03:44.4", "t1": "03:49.2", "ease": "inOutSine" },
{ "param": "cameraLift", "from": 0.05, "to": 0.3, "t0": "03:44.4", "t1": "03:49.2", "ease": "inOutSine" },
{ "param": "glow", "from": 0.65, "to": 1.05, "t0": "03:47.8", "t1": "03:49.2", "ease": "outQuad" }
]
}Automation guidance by phase:
buildProgress0.0 → 0.4: terrain rises from flat ground.buildProgress0.4 → 0.8: city blocks/buildings extrude.buildProgress0.8 → 1.0: emissive intensity and window flicker become dominant.- Keep
cityDensitymostly static per shot for structural readability (recommended0.5–0.75). - Use a gentle
cameraLiftramp (for example +0.2) to avoid perspective popping at low resolutions (320×180).
Defaults shown are from the built-in effect configuration:
bufW(default240): internal buffer width (lower = faster).bufH(default180): internal buffer height (lower = faster).bumpStrength(default0.035): height gradient scale for normals.ambient(default0.2): base ambient light contribution.diffuseStrength(default1.05): Lambertian diffuse multiplier.specStrength(default0.35): specular highlight multiplier.shininess(default24): specular exponent (higher = tighter highlight).lightZ(default120): light height above the plane.lightSpeed(default1.0): time scale for light motion.embossText(default"BUMP"): text to emboss; set to""to disable.embossStrength(default70): added height for embossed text.animateBumps(defaulttrue): enable animated wave component.waveAmp(default18): animated wave amplitude.waveFreqX(default0.08): animated wave frequency along X.waveFreqY(default0.06): animated wave frequency along Y.baseHue(default200): base hue whenpaletteModeishsl.paletteMode(default"ramp"):ramporhsl.scanlines(defaultfalse): draw scanline overlay.audioReact(default0.7): audio reaction strength.beatKick(default0.7): beat pulse intensity.seed(default0): procedural height map seed.
npm install
npm run devnpm run testnpm run build
npm run preview- Click anywhere or use the stylized Execute control to begin playback (audio + visuals).
- The start overlay and end overlay now pair that demoscene-styled call-to-action with a subtle, bottom-anchored Spread the signal link-style control. On supported devices it opens the native share sheet; otherwise it reveals quick-share links for LinkedIn, X, Facebook, Reddit, email, plus a copy-link fallback.
- The browser tab title now animates with unicode spinner glyphs, a subscript clock, and a binary counter for a glitchy demoscene-style status feed.
Rto restart- At the end screen, use Add a doodle to draw and submit a doodle for moderation; the modal now includes multiple brush colours plus an adjustable brush size slider, and approved doodles only appear in
doodle_greetz_wallafter someone opens the review page and approves them. - At the end screen, use Got an effect idea? Make it real! to open the effect-idea modal, describe a concept, generate TypeScript/runtime code through the OpenAI Codex API, preview it live, and submit it to a moderation queue before it can appear as an effect option.
- Generated runtime code is sandboxed by policy checks, but now intentionally errs on allowing general JavaScript patterns (including dynamic helper patterns such as
eval/Function/import()/require()) unless code attempts side-effects that are unnecessary for visual rendering. - For best generation reliability, favor prompts that imply deterministic helper functions, lookup tables, and explicit state updates.
Fto toggle fullscreen (if supported)Dto toggle the debug overlay (timestamp, skip intro, skip to second half, skip to end, transition selection, effect overrides, monochrome toggle)- The debug overlay shows WebGL status as
OKorFALLBACKwhen available. - The debug overlay now groups controls into
Transport,Effects, andRendersections; on mobile/touch it uses tabs plus an internal scroll region so controls stay reachable without page scrolling. - Selecting an effect in the
Effectssection reveals effect-specific controls (or a note when none are available) and still supports copying timeline-ready JSON. - Easter egg: in devtools, set the debug effect selector value to
__randombefore triggeringchangeto force a random effect pick for quick inspiration. - The same
Effectspanel now includes an Edit param value limits mode that exposesmin / current / maxinputs per numeric parameter, auto-expands draft limits when current values exceed bounds, and saves applied limits to the shared/api/effects?action=paramLimitsdatastore via Apply new limits so clamp thresholds update for all users without editing code. - The serverless view/doodle APIs accept either the legacy
KV_*Upstash variables or the newerDB2_KV_*prefixed variants. If anyDB2_*variable is present, the API locks to the DB2 configuration and ignores legacyKV_*values. Use the REST URL/token variables (*_KV_REST_API_URL,*_KV_REST_API_TOKEN, and optionally*_KV_REST_API_READ_ONLY_TOKEN); copied values are trimmed, and rawredis://URLs are ignored by the REST client. - Doodle submissions now land in a pending moderation queue. The public doodle wall only reads approved doodles, while pending doodles stay hidden until approved via the signed review flow. Set
DOODLE_MODERATION_TOKEN(or legacyDOODLE_ADMIN_TOKEN) to enable the review page at/review.html?id=...&token=..., direct moderation actions through/api/doodles?action=approve|reject&id=...&token=..., and queue inspection through/api/doodles?includePending=1&token=.... - To get fast phone notifications for new doodles, set
DOODLE_MODERATION_BASE_URLto your public site URL and configure eitherDOODLE_MODERATION_WEBHOOK_URLfor a custom JSON webhook payload orDOODLE_MODERATION_NTFY_URL(plus optionalDOODLE_MODERATION_NTFY_TOKEN) to push a message through ntfy. The webhook payload includes the review URL plus direct moderation endpoints, while the ntfy notification sets a default click action that opens the signed doodle review page. - The effect-idea generator requires an OpenAI API key on the server. Set
OPENAI_API_KEYand (optionally)OPENAI_CODEX_MODELif you want to override the default model (gpt-5-codex), then redeploy. - Effect idea moderation uses
EFFECT_MODERATION_TOKEN(falls back toDOODLE_MODERATION_TOKEN/DOODLE_ADMIN_TOKEN). Each submission now gets a signed review page at/effect-review.html?id=...&token=...with live preview + submitted code, approve/reject links still use/api/effects?action=approve|reject&id=...&token=..., and pending queue inspection stays at/api/effects?includePending=1&token=.... - Run
npm run test:integrationin an environment with the DB2 secrets set to verify the live Upstash database exists and can be read/written. If the DB2 URL is malformed, the APIs now fail closed instead of crashing with a 500 during Redis client creation. The serverless API modules also use explicit.jsESM imports so Vercel can resolve the emitted files correctly. - Append
?editor=1in dev builds to open the Scene + Timeline Editor (or toggle "Editor mode" in the debug overlay). The editor shows a live preview, edits hot-apply to the running demo, and changes persist to localStorage. - The timeline editor layout uses a narrow Scenes sidebar, a center workspace with Preview plus a single accordion stack for Basic/Main Slots/Transition, and a right Inspector for scene/layer/cue editing. The Generate Text Cues tool opens from a bottom-left button as a modal.
- The timeline area now dedicates at least half the editor height, renders all scenes on the main track, and adds extra rows for selected-scene layers and scene automation entries.
- Automation clip editing foundations now treat automation as point-driven envelopes (per-segment curve type + tension metadata), with helpers for snapped point add/remove/move, slide-mode temporal shifting, and segment-level curve/tension updates for timeline UI integration.
- In the Scene Automation table, each automation row now includes +Pt to seed/edit point-based clip data from legacy
from/toramps for timeline-envelope workflows. - Automation interaction model mirrors FL-style priorities in code: point hit > segment hit > empty hit, with optional grid-snapped step drawing and axis constraints for pulse-like horizontal/vertical edits.
- Clip-edit operations preserve adjacent segment metadata when adding/removing points so curve/tension edits remain stable during iterative shaping.
- Scene Automation rows now render an inline curve editor: click on empty graph space to add points, drag points to move (hold Shift to slide following points in time), right-click points to delete, and hold Alt while dragging/clicking to temporarily disable snap.
- Timeline automation tracks now render envelope lines directly in-lane (matching inspector clips) and the playlist toolbar includes V+ / V- controls for vertical zoom in addition to horizontal zoom/pan.
- The timeline now shows both horizontal and vertical custom scrollbars; drag the horizontal thumb to pan time, drag its end handles to zoom the visible time window, and use the vertical thumb for tall track stacks.
- Timeline custom scrollbars are anchored inside the timeline panel (not the full page edge) and reserve internal right/bottom padding so bars remain visible on narrow screens.
- The vertical timeline scrollbar thumb is draggable (not just clickable) and the timeline/preview sizing now prioritises keeping the horizontal timeline scrollbar visible at normal 100% browser zoom on 1080p layouts.
- While zoomed in, use Shift + mouse wheel (or horizontal trackpad scroll) to pan left/right across the playlist without resetting zoom.
- Playlist quick controls: mouse wheel = zoom to cursor, Shift+wheel/horizontal scroll = pan timeline, drag empty lane = scrub pan.
- The playlist now includes a dedicated timeline scrollbar under the lanes; the thumb shrinks as you zoom in and can be dragged/clicked for coarse navigation across the full song length.
- Native browser scrollbars for the playlist lane are intentionally hidden so the custom timeline scrollbar is the single visible navigation control.
- In editor mode, scene duration is now derived from the next scene start (the End field is display-only), so looping and active-scene selection match runtime behavior where the newest started scene is the one that renders.
- Scene/layer parameter editing now uses control-aware numeric inputs with clearer
Param/Valuelabels: bounded params get a slider + numeric spinner with +/- nudge buttons, while unbounded numeric params get touch-friendly spinner controls. Focusedinput[type=number]fields now consume the mouse wheel globally (no page scroll) and step by their configured bounds/step values. - Playback now auto-syncs across same-origin tabs/windows via
BroadcastChannel, so play/pause/seek/restart actions in one editor window mirror to the others (handy for running multiple preview sizes side-by-side). - The editor's Text Cues panel now includes a bulk generator: paste words/new lines, set font/colour/size/position/alignment plus start/end timing, and auto-create evenly timed cue sequences (useful for ~100 words over ~30 seconds).
- On touch devices, two floating buttons appear in the lower-right corner:
DBGtoggles the debug overlay and⛶toggles fullscreen.
If you want your phone to buzz the moment someone submits a doodle, the simplest path is to use ntfy. The app is available for Android and iPhone, and this project already knows how to publish moderation messages to an ntfy topic.
-
Pick a long, hard-to-guess topic name, for example
doodle-moderation-a8f4c2e91b7d. -
In your deployment environment, set these variables:
DOODLE_MODERATION_TOKEN=replace-this-with-a-long-random-secret DOODLE_MODERATION_BASE_URL=https://your-public-site.example.com DOODLE_MODERATION_NTFY_URL=https://ntfy.sh/doodle-moderation-a8f4c2e91b7d
Optional if you run a protected/self-hosted ntfy server:
DOODLE_MODERATION_NTFY_TOKEN=your-ntfy-access-token
-
Redeploy the site so the serverless doodle API sees the new environment variables.
-
On your phone, install the official ntfy app:
- Android: Google Play, F-Droid, or the official APK/GitHub release.
- iPhone: App Store.
-
Open the ntfy app, add a subscription, and subscribe to the same topic name you configured above on the
ntfy.shserver (or your self-hosted ntfy server if you are not usingntfy.sh). -
Allow notifications when iOS/Android prompts you. If your phone has per-app notification settings disabled, re-enable them in the system settings before testing.
-
Submit a test doodle from the site. Your phone should receive a notification titled
Doodle awaiting approval. -
Tap the notification itself. ntfy uses its default click action to open the signed
/review.html?...page for that doodle. -
Review the doodle image on that page, then use the Approve or Deny buttons at the bottom to finish moderation in the browser.
Quick verification from a laptop/terminal before testing the site itself:
curl -H "Title: ntfy doodle test" -d "If you can read this on your phone, ntfy is wired up." https://ntfy.sh/doodle-moderation-a8f4c2e91b7dIf that curl command appears on your phone but doodle submissions do not, double-check DOODLE_MODERATION_BASE_URL, DOODLE_MODERATION_TOKEN, and DOODLE_MODERATION_NTFY_URL, then redeploy.
To use Got an effect idea? Make it real! in deployed environments, configure these variables:
OPENAI_API_KEY=sk-...
# Optional override; defaults to gpt-5-codex
OPENAI_CODEX_MODEL=gpt-5-codex
# Required if you want token-protected effect moderation endpoints
EFFECT_MODERATION_TOKEN=replace-with-a-long-random-secret
# Optional effect-specific moderation notifications
EFFECT_MODERATION_BASE_URL=https://your-public-site.example.com
EFFECT_MODERATION_NTFY_URL=https://ntfy.sh/effect-moderation-topic
EFFECT_MODERATION_NTFY_TOKEN=your-ntfy-access-token
# Optional: allow trusted authenticated sessions by cookie/header identity
# Defaults: "__session,next-auth.session-token,__Secure-next-auth.session-token"
EFFECT_GENERATE_SESSION_COOKIE_NAMES=__session,next-auth.session-token,__Secure-next-auth.session-token
# Optional strict allowlists (comma-separated)
EFFECT_GENERATE_ALLOWLIST_IPS=203.0.113.10,198.51.100.17
EFFECT_GENERATE_ALLOWLIST_USERS=alice,moderator-42
# Optional rate limit tuning (defaults: 8 requests / 60 seconds per identity)
EFFECT_GENERATE_RATE_LIMIT_MAX=8
EFFECT_GENERATE_RATE_LIMIT_WINDOW_MS=60000
# Optional global daily cap (defaults to 100 generations/day across all users, UTC day boundary)
EFFECT_GENERATE_DAILY_CAP=100
# Optional generation failure alerting (defaults to moderation ntfy topic if omitted)
EFFECT_GENERATE_ALERT_NTFY_URL=https://ntfy.sh/your-effect-generate-alert-topic
EFFECT_GENERATE_ALERT_NTFY_TOKEN=your-alert-ntfy-access-token
# Optional per-category cooldown for alerts (default 300000ms / 5 minutes)
EFFECT_GENERATE_ALERT_COOLDOWN_MS=300000Optional fallback token behavior:
- If
EFFECT_MODERATION_TOKENis not set,/api/effectsmoderation falls back toDOODLE_MODERATION_TOKEN, thenDOODLE_ADMIN_TOKEN. - Effect moderation notifications can use their own ntfy feed (
EFFECT_MODERATION_NTFY_URL) or fall back to doodle ntfy settings (DOODLE_MODERATION_NTFY_URL). - Generated effects are submitted into a pending queue; they only become selectable after approval via the signed review page (
/effect-review.html?id=...&token=...) or direct API link (/api/effects?action=approve&id=...&token=...). POST /api/effects?action=generateis open by default (no auth friction), but it still recognizes trusted identities in this order for rate-limit keys and audit logs: signed moderation token, session identity, allowlisted user/IP, then fallback public IP.- Generation prompts are trimmed and hard-capped at 3000 characters.
- Generate requests are rate limited per identity (
user/session/IP) via KV-backed sliding window and return429with a friendly wait-time message (for example, “Please wait 5 minutes before trying again.”). - Generate requests also enforce a global daily cap (default
100/day, configurable viaEFFECT_GENERATE_DAILY_CAP) and return429when the day budget is exhausted. - Generate monitoring now persists aggregate counters + recent failure samples in KV so regressions can be tracked over time (status mix, failure categories, and timestamps).
- The generation system prompt is now versioned in KV (
effects:generate:prompt-template) and can self-improve after failures; each failed attempt also logs the exact sent prompt, raw model response, user-facing error message, and failure timestamp (effects:generate:failure-logs). - Self-improvement revisions are now triggered through a dedicated endpoint (
POST /api/effects?action=improvePromptTemplate) between retries, so prompt updates are persisted before the next generate call. - The effect-idea modal now auto-retries after failures (up to 5 self-improvement retries), including runtime-compile failures from generated code, shows active attempt counts, and falls back with an apology message when all retries are exhausted.
- Moderators can inspect those generation diagnostics via
GET /api/effects?action=generateMetrics&token=...(signed moderation token required). - Generation monitoring can also push proactive ntfy alerts on failures (with per-category cooldown) so you do not need to poll logs/metrics manually.
- If generation requests return
503from/api/effects?action=generate, check thatOPENAI_API_KEYis set in your deployed environment and redeploy so the serverless function picks it up. - If generation fails with
Unable to parse generated effect response., the modal now shows the raw model output in the code panel so you can inspect formatting mismatches. - The generator prompt now explicitly asks for
runtimeCodeas plain JavaScript (no TS annotations/import/export). The client still attempts to normalize module-style code (export default function ...) when possible. - Server-side effect moderation/generation endpoints treat submitted/generated code as inert text only (validated/stored/returned), and never execute that code in the server environment. This keeps server secrets (for example
process.envvalues) out of reach of model-generated effects. - Before any generated runtime code is compiled for preview/review, the client applies a safety scanner that rejects primitives unrelated to rendering (for example environment-variable access like
process.env, browser cookies/storage, and network APIs such asfetch/XMLHttpRequest/sendBeacon). - Generated effect payloads now also include optional parameter control metadata (
params) and concise docs (docs). After approval, these generated controls are exposed in the debug panel + editor parameter pickers just like built-in effects. - The effect generator modal now keeps the initial prompt editor focused and roomy, then reveals preview/code/param sections only after a successful generation. While generating, it shows approved community effects in a carousel with previous/next controls (kept available whenever the busy panel is open) plus live param controls for the currently previewed approved effect in a dedicated busy panel beneath the preview (so controls do not overlap the prompt/canvas); those community params now sit behind a compact expandable “Community params” bar so the busy panel stays tidy by default, and the eventual submission uses the current preview param values as defaults automatically (no separate “set defaults” step).
- While effect generation is actively running, backdrop clicks and
Escapeno longer close the generator modal, preventing accidental loss of progress visibility. - Effect generation and moderation preview now use
public/songloop.oggas a dedicated seamless background loop (low volume), while a synthetic preview timeline keeps generated effects animating continuously even as the audio loops. - After approving or denying from
/effect-review.html, the page now offers a Next effect button whenever additional pending effects are still in the moderation queue. - The client also normalizes escaped code payloads (for example strings containing literal
\\n) before compiling preview/runtime effects.
Expected POST /api/effects?action=generate failure codes:
| Status | Cause |
|---|---|
400 |
Missing prompt or prompt exceeds 3000 characters. |
429 |
Rate limit exceeded (either per-identity window or global daily cap). |
503 |
OpenAI unavailable/misconfigured (for example missing OPENAI_API_KEY) or model response parse failure. |