One-Shot: Claude Fable 5.2 Paints an Aurora
Fable 5.2 landed on OpenRouter today. One prompt, no edits: 'create the most beautiful and stunning single HTML file you can possibly imagine.' It shipped an interactive aurora borealis with 58,000 reasoning tokens spent before the first pixel.
Aiona Edge
CIO & Chief AI Research Scientist

By Aiona Edge, CIO & Chief AI Research Scientist, SMF Works
The demo is live at /demos/fable-5.2-aurora-night-sky — it is animated, and it reacts to you. Move the mouse to look around, tap the sky for a shooting star, tap the water for a ripple.
The ask
The same prompt every page in this series gets, verbatim, 81 bytes with a trailing newline:
create the most beautiful and stunning single HTML file you can possibly imagine
One shot. No edits, no taste pass, no retry. What the model returned is what shipped.
The receipt
| Field | Value |
|---|---|
| Request id | gen-1794255412-KA6uGmj65XYEGQZQOOn4 |
| HTTP | 200 (streamed) |
| Finish | stop |
| Wall clock | 753.3 s (12.6 min) |
| Prompt / completion / total tokens | 32 / 75,134 / 75,166 |
| Reasoning tokens | 57,984 (streamed reasoning chars: 47,619 — both reported) |
| HTML bytes (extracted fence) | 31,878 |
| Cost | $3.01 (usage.cost) |
node --check on the inline script |
pass |
The model thought for 12 minutes before writing. 77% of its tokens were reasoning, not output — Fable 5.2's reasoning is mandatory (efforts max/xhigh/high/medium/low, default high); we sent the catalog default and named nothing.
The page
An aurora borealis rendered in real time — Canvas, not WebGL. Starfield, flowing aurora curtains, mountain ridgelines, a treeline, a single warm lantern light on the shore, and water that reflects it all. The copy says it plainly: "No images. No video. Only mathematics, dreaming."
The title is "Aurora — a night sky imagined in real time". Coordinates are etched in the footer: 69°39′N 18°57′E — Tromsø, Norway. A sound toggle sits in the corner.
One thing we did not expect: the page works on the first click. The gate run (Playwright, 1440×900, 5 s after load) recorded zero page errors and zero console errors, and the interactions listed in the body text — look around, shooting star, ripple — all have handlers in the shipped script.
The fence
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<meta name="theme-color" content="#02060c">
<title>Aurora — a night sky imagined in real time</title>
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='7' fill='%2302060c'/%3E%3Cpath d='M4 22c6-14 10 2 14-9s6 3 10-5' fill='none' stroke='%237dffc8' stroke-width='2.4' stroke-linecap='round'/%3E%3C/svg%3E">
<style>
:root{
--ink:rgba(238,248,245,.94);
--dim:rgba(220,240,236,.56);
--line:rgba(220,240,236,.2);
--glow:125,255,200;
--serif:"Cormorant Garamond","Cormorant","Didot","Bodoni 72","Bodoni MT","Iowan Old Style","Palatino Linotype",Palatino,"Book Antiqua",Georgia,serif;
--sans:"Avenir Next","Avenir","Segoe UI","Helvetica Neue",Helvetica,Arial,sans-serif;
}
*{box-sizing:border-box;margin:0;padding:0}
html,body{height:100%;overflow:hidden;background:#02060c;color:var(--ink);font-family:var(--sans);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-webkit-tap-highlight-color:transparent;user-select:none;-webkit-user-select:none}
#sky{position:fixed;inset:0;width:100%;height:100%;display:block;touch-action:none;outline:none}
/* ---------- title ---------- */
.title{position:fixed;left:50%;top:33%;transform:translate(-50%,-50%);width:min(94vw,1300px);text-align:center;z-index:2;pointer-events:none;transition:opacity 3s ease,filter 3s ease,transform 4.5s ease}
body.live .title{opacity:0;filter:blur(12px);transform:translate(-50%,-56%) scale(1.05)}
.eyebrow{font-size:clamp(9px,1.02vw,12px);letter-spacing:.46em;text-transform:uppercase;color:var(--dim);padding-left:.46em;opacity:0;animation:fadeUp 2.6s ease .8s forwards}
h1{font-family:var(--serif);font-weight:400;font-size:clamp(50px,11.5vw,176px);line-height:1.05;letter-spacing:.24em;padding-left:.24em;margin:.16em 0 .2em;color:#f5fffb;text-shadow:0 0 .3em rgba(130,255,205,.38),0 0 1.3em rgba(90,200,255,.2)}
h1 span{display:inline-block;opacity:0;animation:rise 2.6s cubic-bezier(.2,.7,.1,1) forwards;animation-delay:calc(1.3s + var(--i)*.15s)}
.sub{font-family:var(--serif);font-style:italic;font-size:clamp(15px,1.75vw,23px);letter-spacing:.06em;color:rgba(236,250,245,.8);opacity:0;animation:fadeUp 2.8s ease 3.2s forwards}
@keyframes rise{from{opacity:0;filter:blur(16px);transform:translateY(.22em) scale(1.08)}to{opacity:1;filter:blur(0);transform:none}}
@keyframes fadeUp{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}
/* ---------- hud ---------- */
.hud{position:fixed;z-index:3;font-size:10.5px;line-height:2;letter-spacing:.32em;text-transform:uppercase;color:var(--dim);opacity:0;transition:opacity 1.8s ease;pointer-events:none}
body.live .hud{opacity:1}
body.live.idle .hud{opacity:0}
body.bare .hud,body.bare .title,body.bare .cursor{display:none}
.tl{top:calc(26px + env(safe-area-inset-top));left:calc(30px + env(safe-area-inset-left));display:flex;align-items:center}
.tl b{font-weight:500;color:var(--ink);letter-spacing:.52em}
.rule{display:inline-block;width:40px;height:1px;background:var(--line);margin:0 16px 0 10px}
.bl{left:calc(30px + env(safe-area-inset-left));bottom:calc(24px + env(safe-area-inset-bottom))}
.bl span{display:block}
.bl em{font-style:normal;color:var(--ink)}
.br{right:calc(26px + env(safe-area-inset-right));bottom:calc(22px + env(safe-area-inset-bottom));display:flex;gap:10px}
body.live .br{pointer-events:auto}
body.live.idle .br{pointer-events:none}
.ctl{height:46px;min-width:46px;border-radius:23px;border:1px solid var(--line);background:rgba(8,18,24,.3);-webkit-backdrop-filter:blur(12px) saturate(140%);backdrop-filter:blur(12px) saturate(140%);color:var(--ink);display:flex;align-items:center;justify-content:center;gap:12px;cursor:pointer;font:inherit;letter-spacing:.32em;text-transform:uppercase;transition:border-color .5s,box-shadow .5s,transform .5s,background .5s}
.ctl:hover,.ctl:focus-visible{outline:none;border-color:rgba(var(--glow),.65);box-shadow:0 0 26px rgba(var(--glow),.2),inset 0 0 14px rgba(var(--glow),.08);transform:translateY(-1px)}
.pill{padding:0 20px 0 18px}
.pill.beckon{animation:beckon 2.8s ease-in-out infinite}
@keyframes beckon{0%,100%{box-shadow:0 0 0 rgba(var(--glow),0)}50%{box-shadow:0 0 26px rgba(var(--glow),.28);border-color:rgba(var(--glow),.55)}}
.bars{display:flex;align-items:center;gap:3px;height:16px}
.bars i{display:block;width:2px;height:3px;border-radius:1px;background:currentColor;transition:height .5s}
#snd[aria-pressed="true"] .bars i{animation:eq 1.1s ease-in-out infinite alternate}
#snd[aria-pressed="true"] .bars i:nth-child(1){animation-delay:-.9s}
#snd[aria-pressed="true"] .bars i:nth-child(2){animation-delay:-.3s}
#snd[aria-pressed="true"] .bars i:nth-child(3){animation-delay:-.6s}
#snd[aria-pressed="true"] .bars i:nth-child(4){animation-delay:-.1s}
@keyframes eq{from{height:3px}to{height:15px}}
/* ---------- cursor ---------- */
.cursor{position:fixed;left:0;top:0;width:34px;height:34px;margin:-17px 0 0 -17px;z-index:5;pointer-events:none;opacity:0;transition:opacity .5s;display:none}
.cursor::before{content:"";position:absolute;inset:0;border-radius:50%;border:1px solid rgba(235,255,248,.5);transition:transform .35s cubic-bezier(.2,.7,.2,1),border-color .35s}
.cursor::after{content:"";position:absolute;left:50%;top:50%;width:3px;height:3px;margin:-1.5px;border-radius:50%;background:rgba(235,255,248,.9)}
.cursor.press::before{transform:scale(.55);border-color:rgba(var(--glow),.95)}
@media (hover:hover) and (pointer:fine){
#sky{cursor:none}
.cursor{display:block}
.cursor.on{opacity:1}
body.idle .cursor{opacity:0}
}
/* ---------- fallback ---------- */
.nogl{display:none;position:fixed;inset:0;z-index:1;align-items:flex-end;justify-content:center;padding:12vh 8vw;text-align:center;font-family:var(--serif);font-style:italic;font-size:18px;color:var(--dim);background:radial-gradient(120% 80% at 50% 110%,#0b3a3a 0%,#06202c 35%,#02060c 75%)}
body.fallback .nogl{display:flex}
@media (max-width:680px){
.hud{font-size:9px;letter-spacing:.26em}
.tl{left:20px;top:calc(20px + env(safe-area-inset-top))}
.bl{left:20px;bottom:calc(20px + env(safe-area-inset-bottom))}
.br{right:16px;bottom:calc(16px + env(safe-area-inset-bottom))}
.pill{padding:0 14px}.pill .lbl{display:none}
.rule{width:22px;margin:0 10px 0 4px}
}
@media (prefers-reduced-motion:reduce){
h1 span,.eyebrow,.sub{animation-duration:.01s;animation-delay:.2s}
}
</style>
</head>
<body>
<canvas id="sky" role="img" aria-label="A procedurally generated aurora over a mountain lake at night"></canvas>
<div class="nogl">The northern lights need WebGL to appear. Please try a browser with WebGL enabled.</div>
<div class="title">
<p class="eyebrow">A night sky, imagined in real time</p>
<h1 aria-label="Aurora">
<span style="--i:0">A</span><span style="--i:1">U</span><span style="--i:2">R</span><span style="--i:3">O</span><span style="--i:4">R</span><span style="--i:5">A</span>
</h1>
<p class="sub">No images. No video. Only mathematics, dreaming.</p>
</div>
<div class="hud tl"><b>Aurora</b><i class="rule"></i>69°39′N 18°57′E</div>
<div class="hud bl">
<span><em>Move</em> — look around</span>
<span><em>Tap the sky</em> — shooting star</span>
<span><em>Tap the water</em> — ripple</span>
</div>
<div class="hud br">
<button class="ctl pill beckon" id="snd" aria-pressed="false" title="Sound (S)">
<span class="bars"><i></i><i></i><i></i><i></i></span><span class="lbl">Sound off</span>
</button>
<button class="ctl" id="fs" aria-label="Toggle fullscreen" title="Fullscreen (F)">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"><path d="M1.5 5.5v-4h4M10.5 1.5h4v4M14.5 10.5v4h-4M5.5 14.5h-4v-4"/></svg>
</button>
</div>
<div class="cursor"></div>
<script>
(function(){
'use strict';
const $ = s => document.querySelector(s);
const canvas = $('#sky');
const FOC = 1.25, HOR = 0.18;
const reduced = window.matchMedia && matchMedia('(prefers-reduced-motion: reduce)').matches;
/* =====================================================================
SHADERS
===================================================================== */
const VERT = 'attribute vec2 p; void main(){ gl_Position = vec4(p, 0., 1.); }';
const FRAG = `
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif
uniform vec2 uRes;
uniform float uTime;
uniform vec3 uCam; // yaw, pitch, fade
uniform vec4 uMet[2]; // az, el, angle, t0
uniform vec4 uRip[4]; // x, z, t0, strength
#define PI 3.14159265
#define FOC 1.25
/* ---------- hashing & noise ---------- */
float hash11(float p){ p = fract(p*.1031); p *= p+33.33; p *= p+p; return fract(p); }
float hash12(vec2 p){ vec3 q = fract(vec3(p.xyx)*.1031); q += dot(q, q.yzx+33.33); return fract((q.x+q.y)*q.z); }
float hash13(vec3 q){ q = fract(q*.1031); q += dot(q, q.zyx+31.32); return fract((q.x+q.y)*q.z); }
vec3 hash33(vec3 q){ q = fract(q*vec3(.1031,.1030,.0973)); q += dot(q, q.yxz+33.33); return fract((q.xxy+q.yxx)*q.zyx); }
float vnoise(vec2 p){
vec2 i = floor(p), f = fract(p);
vec2 u = f*f*f*(f*(f*6.-15.)+10.);
return mix(mix(hash12(i), hash12(i+vec2(1.,0.)), u.x),
mix(hash12(i+vec2(0.,1.)), hash12(i+vec2(1.,1.)), u.x), u.y);
}
float vnoise3(vec3 p){
vec3 i = floor(p), f = fract(p);
f = f*f*(3.-2.*f);
return mix(mix(mix(hash13(i), hash13(i+vec3(1.,0.,0.)), f.x),
mix(hash13(i+vec3(0.,1.,0.)), hash13(i+vec3(1.,1.,0.)), f.x), f.y),
mix(mix(hash13(i+vec3(0.,0.,1.)), hash13(i+vec3(1.,0.,1.)), f.x),
mix(hash13(i+vec3(0.,1.,1.)), hash13(i+vec3(1.,1.,1.)), f.x), f.y), f.z);
}
float fbm3(vec3 p){
float s = 0., a = .5;
for(int i=0;i<4;i++){ s += a*vnoise3(p); p = p*2.02+vec3(3.1,1.7,4.3); a *= .5; }
return s;
}
float n1(float x){
float i = floor(x), f = fract(x);
f = f*f*(3.-2.*f);
return mix(hash11(i), hash11(i+1.), f);
}
float ridged(float x){
float s = 0., a = .5;
for(int i=0;i<6;i++){ s += a*(1.-abs(2.*n1(x)-1.)); x = x*2.07+13.7; a *= .5; }
return s;
}
float fbm1(float x){
float s = 0., a = .5;
for(int i=0;i<4;i++){ s += a*n1(x); x = x*2.03+7.3; a *= .5; }
return s;
}
/* ---------- aurora ---------- */
float curtain(vec2 p, float t){
vec2 w = vec2(vnoise(p*.33+vec2(0., t*.02)), vnoise(p*.33+vec2(17.3,-t*.016)));
vec2 q = p+(w-.5)*3.;
float n = vnoise(q*.55+vec2(t*.012, 4.));
float r = 1.-abs(2.*n-1.);
r *= r; r *= r; r *= r;
float m = vnoise(q*1.3+vec2(9.,-t*.028));
float r2 = 1.-abs(2.*m-1.);
r2 *= r2; r2 *= r2; r2 *= r2;
float rays = .3+.7*vnoise(q*8.5+vec2(t*.22,-t*.06));
return (r+.35*r2)*rays+.025*n;
}
vec3 aurora(vec3 rd, float jit, float k){
float t = uTime;
vec2 base = rd.xz/(rd.y+.22);
float mask = .14+.86*smoothstep(.32,.68, vnoise(base*.22+vec2(-t*.009, 40.)));
vec3 top = mix(vec3(.72,.2,.9), vec3(.95,.22,.45), .5+.5*sin(t*.031));
vec3 col = vec3(0.);
float avg = 0.;
for(int i=0;i<32;i++){
float s = (float(i)+jit)/32.;
float h = 1.+s*.72;
float f = curtain(base*h+vec2(3.7,11.3), t);
avg = mix(avg, f, .5);
vec3 c = mix(vec3(.06,1.,.4), vec3(.08,.6,.95), smoothstep(.05,.5,s));
c = mix(c, top, smoothstep(.38,1.,s));
col += c*avg*exp(-s*2.4)*smoothstep(0.,.09,s);
}
return col*.19*k*mask*smoothstep(-.03,.16,rd.y);
}
/* ---------- stars ---------- */
vec3 stars(vec3 rd, float px){
vec3 acc = vec3(0.);
for(int i=0;i<3;i++){
float fi = float(i);
float sc = 36.+fi*44.;
vec3 p = rd*sc+fi*13.1;
vec3 id = floor(p);
vec3 f = fract(p)-.5;
vec3 h = hash33(id);
float r = hash13(id+3.7);
float rad = clamp(max(.03+.05*r, 1.25*px*sc), 0., .2);
float d = length(f-(h-.5)*.58);
float b = 1.-smoothstep(0., rad, d);
b *= b;
float mag = pow(r,7.)*(3.-fi*.8)+.06*r*r;
float tw = .78+.22*sin(uTime*(1.2+4.*h.z)+h.x*60.);
vec3 tint = mix(vec3(.72,.84,1.), vec3(1.,.8,.62), h.y*h.y);
acc += tint*b*mag*tw;
if(i==0) acc += tint*pow(r,14.)*.5*exp(-d*10.);
}
return acc;
}
/* ---------- shooting stars ---------- */
float meteor(vec2 m, vec4 M){
float age = uTime-M.w;
if(age<0. || age>1.3) return 0.;
float k = age/1.3;
vec2 d = vec2(cos(M.z), sin(M.z));
vec2 rel = m-(M.xy+d*age*.55);
float along = -dot(rel,d);
float perp = dot(rel, vec2(-d.y,d.x));
float len = .16*sin(k*PI)+.01;
float tail = clamp(1.-along/len, 0., 1.)*step(0., along);
float w = .0009+.0012*tail;
float core = exp(-perp*perp/(w*w))*tail*tail;
float hd = exp(-dot(rel,rel)/.000012);
return (core*1.6+hd*2.)*sin(k*PI);
}
/* ---------- pines ---------- */
float pines(float az, float e, float dens, float seed, float hmax, float px){
float x = az*dens;
float id = floor(x);
float r = hash11(id*1.31+seed);
float fx = abs(fract(x)-.5+(r-.5)*.3);
float H = hmax*(.4+.6*hash11(id*2.17+seed+5.));
float tt = e/H;
float tier = 1.-fract(tt*5.+r*3.);
float w = (1.-tt)*(.4+.6*tier)*.33;
float a = px*dens*1.2;
return 1.-smoothstep(w-a, w+a, fx);
}
/* ---------- everything above the waterline ---------- */
vec3 scene(vec3 rd, float jit, float px){
float t = uTime;
float lxz = max(length(rd.xz), 1e-4);
float az = atan(rd.x, rd.z);
float el = rd.y/lxz;
float aa = 1.4*px;
float open = smoothstep(.04,.75,abs(az-.05));
float h1 = (.016+.2*pow(ridged(az*1.9+2.3),1.7))*(.42+.58*open);
float h2 = (.011+.09*pow(ridged(az*3.3+31.),1.5))*(.5+.5*smoothstep(0.,.55,abs(az+.12)));
float h3 = .0045+.02*fbm1(az*7.+50.);
float m1 = 1.-smoothstep(h1-aa, h1+aa, el);
float m2 = 1.-smoothstep(h2-aa, h2+aa, el);
float m3 = 1.-smoothstep(h3-aa, h3+aa, el);
if(el < h3+.05){
m3 = max(m3, pines(az, el-h3+.002, 128., 3., .026, px));
m3 = max(m3, pines(az+.0037, el-h3+.003, 83., 9., .036, px));
}
float cover = max(m1, max(m2, m3));
float k = .85+.25*sin(t*.047)+.12*sin(t*.113+1.3);
vec3 col = vec3(0.);
if(cover < .995){
float g = pow(clamp(rd.y,0.,1.), .5);
col = mix(vec3(.011,.04,.055), vec3(.003,.007,.021), g);
col += vec3(.005,.03,.022)*exp(-rd.y*4.)*k;
// milky way
float bd = dot(rd, normalize(vec3(.8,-.45,.15)));
float band = exp(-bd*bd*26.);
float mw = 0.;
if(band > .02){
float neb = fbm3(rd*4.5+vec3(1.3,7.1,2.9));
float dust = fbm3(rd*9.+vec3(21.,3.,8.));
mw = band*smoothstep(.3,.75,neb)*(1.-.75*smoothstep(.45,.7,dust)*band)*smoothstep(0.,.35,rd.y);
col += (vec3(.03,.036,.06)+vec3(.03,.018,.01)*band*band)*mw;
}
vec3 au = aurora(rd, jit, k);
vec3 st = stars(rd, px)*(1.+1.6*mw)*(.25+.75*smoothstep(0.,.3,rd.y));
col += st*(1.-min(.75, au.g*.7));
col += au;
vec2 m = vec2(az, el);
col += vec3(.85,.93,1.)*(meteor(m,uMet[0])+meteor(m,uMet[1]));
}
// far range, catching the light
vec3 hz = vec3(.016,.05,.052)*(.7+.3*k);
vec3 c1 = vec3(.006,.017,.027);
float tex = vnoise(vec2(az*70., el*160.));
c1 += vec3(.01,.032,.03)*exp(-max(h1-el,0.)*26.)*(.35+.65*tex)*k;
c1 = mix(c1, hz, .55*exp(-el*24.));
col = mix(col, c1, m1);
// middle range
vec3 c2 = vec3(.0035,.01,.016)+vec3(.004,.014,.012)*exp(-max(h2-el,0.)*60.)*k;
c2 = mix(c2, hz*.6, .5*exp(-el*45.));
col = mix(col, c2, m2);
// near shore & pines
col = mix(col, vec3(.0012,.0035,.005), m3);
// one warm window across the water
float dd = length(vec2(az-.135, el-.0032));
float fl = .9+.1*sin(t*5.3)*sin(t*2.1+1.);
col += vec3(1.,.55,.2)*(1.4*exp(-dd*650.)+.1*exp(-dd*85.))*fl;
return col;
}
vec3 aces(vec3 x){ return clamp((x*(2.51*x+.03))/(x*(2.43*x+.59)+.14), 0., 1.); }
void main(){
vec2 fc = gl_FragCoord.xy;
float mn = min(uRes.x, uRes.y);
vec2 uv = (fc-.5*uRes)/mn;
float px = 1./(mn*FOC);
float t = uTime;
vec3 rd = normalize(vec3(uv.x, uv.y+.18*uRes.y/mn+uCam.y, FOC));
float cy = cos(uCam.x), sy = sin(uCam.x);
rd = vec3(rd.x*cy+rd.z*sy, rd.y, rd.z*cy-rd.x*sy);
float jit = fract(52.9829189*fract(dot(fc, vec2(.06711056,.00583715)))+t*9.37);
vec3 rr = rd;
float wet = 0.;
float spx = px;
if(rd.y < 0.){
wet = 1.;
vec2 wp = rd.xz/max(-rd.y, .002);
float dist = length(wp);
float fade = 1./(1.+dist*dist*.012);
float w1 = vnoise(wp*1.7+vec2(t*.22, t*.31))+.5*vnoise(wp*4.3+vec2(-t*.4, t*.27));
float w2 = vnoise(wp*2.3+vec2(31.-t*.25, t*.2));
float dy = (w1-.75)*.15*fade;
float dx = (w2-.5)*.05*fade;
float rf = 1./(1.+dist*dist*.004);
for(int i=0;i<4;i++){
vec4 R = uRip[i];
float age = t-R.z;
if(age>0. && age<10.){
float fr = length(wp-R.xy)-age*1.5;
dy += sin(fr*6.5)*exp(-abs(fr)*.9)*exp(-age*.5)*smoothstep(0.,.5,age)*R.w*.075*rf;
}
}
rr = normalize(vec3(rd.x+dx, max(-rd.y+dy, .0008), rd.z));
spx = px*2.;
}
vec3 col = scene(rr, jit, spx);
if(wet > .5){
float fres = .2+.72*pow(1.+rd.y, 5.);
col = col*fres+vec3(.002,.008,.012);
}
// mist lying on the lake
float elv = rd.y/max(length(rd.xz), 1e-4);
float mist = exp(-abs(elv)*(wet>.5 ? 26. : 60.));
float mz = vnoise(vec2(atan(rd.x,rd.z)*8.+t*.02, 1.5+t*.01));
col = mix(col, vec3(.028,.07,.075), mist*(.18+.3*mz));
// lens & film
vec2 q = fc/uRes;
float vig = pow(max(16.*q.x*q.y*(1.-q.x)*(1.-q.y), 1e-4), .22);
col *= mix(.5, 1., vig)*uCam.z;
col = aces(col*1.15);
col = pow(col, vec3(1./2.2));
col = mix(col, col*col*(3.-2.*col), .25);
col += (hash12(fc+fract(t)*91.7)-.5)*.022;
gl_FragColor = vec4(col, 1.);
}
`;
/* =====================================================================
STATE
===================================================================== */
const S = {
t: 40 + Math.random()*600, last: 0,
yaw: 0, pitch: 0, tyaw: 0, tpitch: 0, camYaw: 0,
fade: 0, q: 1,
nextMeteor: 0, nextRipple: 0
};
S.nextMeteor = S.t + 5;
S.nextRipple = S.t + 7;
const met = new Float32Array(8); met[3] = met[7] = -100;
const rip = new Float32Array(16); for (let i = 0; i < 4; i++) rip[i*4+2] = -100;
let metI = 0, ripI = 0;
function addMeteor(az, el, ang){
const o = metI*4; met[o] = az; met[o+1] = el; met[o+2] = ang; met[o+3] = S.t;
metI = (metI+1) % 2;
}
function addRipple(x, z, strength){
const o = ripI*4; rip[o] = x; rip[o+1] = z; rip[o+2] = S.t; rip[o+3] = strength;
ripI = (ripI+1) % 4;
}
function meteorAngle(){
return Math.random() < .5 ? -Math.PI*.5 - .5 - Math.random()*.5
: -Math.PI*.5 + .5 + Math.random()*.5;
}
function randomMeteor(){
addMeteor(S.camYaw + (Math.random()-.5)*1.1, .26 + Math.random()*.3, meteorAngle());
}
/* =====================================================================
WEBGL
===================================================================== */
let gl = null, U = null, glOK = false;
function initGL(){
try{
const opts = {alpha:false, antialias:false, depth:false, stencil:false, powerPreference:'high-performance'};
gl = canvas.getContext('webgl', opts) || canvas.getContext('experimental-webgl', opts);
if(!gl) return false;
const mk = (type, src) => {
const s = gl.createShader(type);
gl.shaderSource(s, src); gl.compileShader(s);
if(!gl.getShaderParameter(s, gl.COMPILE_STATUS)) throw new Error(gl.getShaderInfoLog(s));
return s;
};
const prog = gl.createProgram();
gl.attachShader(prog, mk(gl.VERTEX_SHADER, VERT));
gl.attachShader(prog, mk(gl.FRAGMENT_SHADER, FRAG));
gl.linkProgram(prog);
if(!gl.getProgramParameter(prog, gl.LINK_STATUS)) throw new Error(gl.getProgramInfoLog(prog));
gl.useProgram(prog);
const buf = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1,-1, 3,-1, -1,3]), gl.STATIC_DRAW);
const loc = gl.getAttribLocation(prog, 'p');
gl.enableVertexAttribArray(loc);
gl.vertexAttribPointer(loc, 2, gl.FLOAT, false, 0, 0);
U = {
res: gl.getUniformLocation(prog, 'uRes'),
time: gl.getUniformLocation(prog, 'uTime'),
cam: gl.getUniformLocation(prog, 'uCam'),
met: gl.getUniformLocation(prog, 'uMet'),
rip: gl.getUniformLocation(prog, 'uRip')
};
return true;
}catch(err){
console.error(err);
return false;
}
}
function resize(){
if(!gl) return;
const w = Math.max(1, innerWidth), h = Math.max(1, innerHeight);
const dpr = Math.min(window.devicePixelRatio || 1, 2);
const budget = Math.sqrt(1.8e6/(w*h));
const s = Math.min(dpr, budget)*S.q;
canvas.width = Math.max(2, Math.round(w*s));
canvas.height = Math.max(2, Math.round(h*s));
gl.viewport(0, 0, canvas.width, canvas.height);
}
/* =====================================================================
SOUND — a small generative score
===================================================================== */
const Sound = (() => {
let ctx = null, master, verb, voices = [], on = false, chordI = 0, chordTimer = 0, chimeTimer = 0;
const mtof = m => 440*Math.pow(2, (m-69)/12);
const CH = [[38,45,54,57,64],[35,47,54,57,62],[43,50,54,59,66],[33,45,52,61,64]];
const PENTA = [74,76,78,81,83,86,88,90,93];
function impulse(sec, decay){
const len = Math.floor(ctx.sampleRate*sec), buf = ctx.createBuffer(2, len, ctx.sampleRate);
for(let c = 0; c < 2; c++){
const d = buf.getChannelData(c);
for(let i = 0; i < len; i++) d[i] = (Math.random()*2-1)*Math.pow(1-i/len, decay);
}
return buf;
}
function init(){
const AC = window.AudioContext || window.webkitAudioContext;
if(!AC) return false;
ctx = new AC();
master = ctx.createGain(); master.gain.value = 0;
const comp = ctx.createDynamicsCompressor();
master.connect(comp); comp.connect(ctx.destination);
verb = ctx.createConvolver(); verb.buffer = impulse(5.5, 2.6);
const wet = ctx.createGain(); wet.gain.value = .55;
verb.connect(wet); wet.connect(master);
// pad
const lp = ctx.createBiquadFilter(); lp.type = 'lowpass'; lp.frequency.value = 720; lp.Q.value = .4;
const pad = ctx.createGain(); pad.gain.value = .5;
lp.connect(pad); pad.connect(master); pad.connect(verb);
const lfo = ctx.createOscillator(); lfo.frequency.value = .05;
const lg = ctx.createGain(); lg.gain.value = 260;
lfo.connect(lg); lg.connect(lp.frequency); lfo.start();
CH[0].forEach((m, i) => {
const g = ctx.createGain(); g.gain.value = i === 0 ? .16 : .09;
const o1 = ctx.createOscillator(); o1.type = 'sine';
const o2 = ctx.createOscillator(); o2.type = 'triangle'; o2.detune.value = i % 2 ? 7 : -7;
const g2 = ctx.createGain(); g2.gain.value = .45;
o1.frequency.value = o2.frequency.value = mtof(m);
o1.connect(g); o2.connect(g2); g2.connect(g); g.connect(lp);
const tl = ctx.createOscillator(); tl.frequency.value = .07+.05*i;
const tg = ctx.createGain(); tg.gain.value = .035;
tl.connect(tg); tg.connect(g.gain); tl.start();
o1.start(); o2.start();
voices.push([o1, o2]);
});
// wind
const nb = ctx.createBuffer(1, ctx.sampleRate*3, ctx.sampleRate);
const nd = nb.getChannelData(0);
for(let i = 0; i < nd.length; i++) nd[i] = Math.random()*2-1;
const ns = ctx.createBufferSource(); ns.buffer = nb; ns.loop = true;
const wf = ctx.createBiquadFilter(); wf.type = 'lowpass'; wf.frequency.value = 380; wf.Q.value = .3;
const wg = ctx.createGain(); wg.gain.value = .035;
const wl = ctx.createOscillator(); wl.frequency.value = .09;
const wlg = ctx.createGain(); wlg.gain.value = 170;
wl.connect(wlg); wlg.connect(wf.frequency); wl.start();
const wl2 = ctx.createOscillator(); wl2.frequency.value = .061;
const wl2g = ctx.createGain(); wl2g.gain.value = .018;
wl2.connect(wl2g); wl2g.connect(wg.gain); wl2.start();
ns.connect(wf); wf.connect(wg); wg.connect(master); ns.start();
return true;
}
function chime(m, vel, when){
if(!ctx || !on) return;
const t = ctx.currentTime + (when || 0), f = mtof(m);
const o = ctx.createOscillator(), o2 = ctx.createOscillator();
const g = ctx.createGain(), g2 = ctx.createGain(), dry = ctx.createGain();
o.type = 'sine'; o.frequency.value = f;
o2.type = 'sine'; o2.frequency.value = f*2.01; g2.gain.value = .18;
g.gain.setValueAtTime(0, t);
g.gain.linearRampToValueAtTime(.07*(vel || 1), t+.02);
g.gain.exponentialRampToValueAtTime(.0001, t+3.2);
o.connect(g); o2.connect(g2); g2.connect(g);
let out = g;
if(ctx.createStereoPanner){
const p = ctx.createStereoPanner(); p.pan.value = Math.random()*1.4-.7;
g.connect(p); out = p;
}
out.connect(verb);
dry.gain.value = .35; out.connect(dry); dry.connect(master);
o.start(t); o2.start(t); o.stop(t+3.4); o2.stop(t+3.4);
}
function nextChord(){
chordI = (chordI+1) % CH.length;
const t = ctx.currentTime;
CH[chordI].forEach((m, i) => voices[i].forEach(o => o.frequency.setTargetAtTime(mtof(m), t, .9)));
}
function loopChime(){
if(!on) return;
chime(PENTA[Math.floor(Math.random()*PENTA.length)], .5+Math.random()*.5);
if(Math.random() < .35) chime(PENTA[Math.floor(Math.random()*PENTA.length)], .4, .28+Math.random()*.3);
chimeTimer = setTimeout(loopChime, 1600+Math.random()*4200);
}
function toggle(){
if(!ctx && !init()) return false;
on = !on;
const t = ctx.currentTime;
master.gain.cancelScheduledValues(t);
clearInterval(chordTimer); clearTimeout(chimeTimer);
if(on){
ctx.resume();
master.gain.setTargetAtTime(.9, t, 1.4);
chordTimer = setInterval(nextChord, 15000);
chimeTimer = setTimeout(loopChime, 2500);
}else{
master.gain.setTargetAtTime(0, t, .25);
setTimeout(() => { if(!on && ctx) ctx.suspend(); }, 1800);
}
return on;
}
return { toggle, chime };
})();
/* =====================================================================
INTERACTION
===================================================================== */
const ring = $('.cursor');
let cx = -100, cy = -100, rx = -100, ry = -100;
let down = null, idleT = 0;
const clamp = (v, a, b) => Math.max(a, Math.min(b, v));
function wake(){
document.body.classList.remove('idle');
clearTimeout(idleT);
idleT = setTimeout(() => document.body.classList.add('idle'), 5200);
}
function ray(x, y){
const w = innerWidth, h = innerHeight, m = Math.min(w, h);
let dx = (x-.5*w)/m, dy = (.5*h-y)/m + HOR*h/m + S.pitch, dz = FOC;
const l = Math.hypot(dx, dy, dz); dx /= l; dy /= l; dz /= l;
const c = Math.cos(S.camYaw), s = Math.sin(S.camYaw);
return [dx*c+dz*s, dy, dz*c-dx*s];
}
function tap(x, y){
const r = ray(x, y);
if(r[1] < -.006){
addRipple(r[0]/-r[1], r[2]/-r[1], 1);
Sound.chime(62, .9); Sound.chime(69, .5, .12);
}else{
const az = Math.atan2(r[0], r[2]);
const el = Math.max(.02, r[1]/Math.hypot(r[0], r[2]));
const a = meteorAngle();
addMeteor(az-Math.cos(a)*.12, el-Math.sin(a)*.12, a);
Sound.chime(93, .8); Sound.chime(90, .6, .09); Sound.chime(86, .5, .18);
}
}
canvas.addEventListener('pointerdown', e => {
wake();
down = {lx: e.clientX, ly: e.clientY, t: performance.now(), moved: 0, id: e.pointerId};
ring.classList.add('press');
});
addEventListener('pointermove', e => {
wake();
if(e.pointerType === 'mouse'){
cx = e.clientX; cy = e.clientY; ring.classList.add('on');
S.tyaw = (e.clientX/innerWidth-.5)*.8;
S.tpitch = (.5-e.clientY/innerHeight)*.14;
}
if(down && e.pointerId === down.id){
const dx = e.clientX-down.lx, dy = e.clientY-down.ly;
down.lx = e.clientX; down.ly = e.clientY;
down.moved += Math.abs(dx)+Math.abs(dy);
if(e.pointerType !== 'mouse'){
S.tyaw = clamp(S.tyaw-dx/innerWidth*1.1, -.45, .45);
S.tpitch = clamp(S.tpitch+dy/innerHeight*.25, -.07, .09);
}
}
}, {passive: true});
addEventListener('pointerup', e => {
ring.classList.remove('press');
if(down && e.pointerId === down.id){
if(down.moved < 12 && performance.now()-down.t < 600) tap(e.clientX, e.clientY);
down = null;
}
});
addEventListener('pointercancel', () => { down = null; ring.classList.remove('press'); });
document.addEventListener('mouseleave', () => ring.classList.remove('on'));
const sndBtn = $('#snd'), fsBtn = $('#fs');
function toggleSound(){
const on = Sound.toggle();
sndBtn.setAttribute('aria-pressed', on ? 'true' : 'false');
sndBtn.querySelector('.lbl').textContent = on ? 'Sound on' : 'Sound off';
sndBtn.classList.remove('beckon');
}
sndBtn.addEventListener('click', toggleSound);
const de = document.documentElement;
const reqFS = de.requestFullscreen || de.webkitRequestFullscreen;
if(!reqFS) fsBtn.hidden = true;
function toggleFS(){
if(!reqFS) return;
if(document.fullscreenElement || document.webkitFullscreenElement){
(document.exitFullscreen || document.webkitExitFullscreen).call(document);
}else{
const p = reqFS.call(de);
if(p && p.catch) p.catch(() => {});
}
}
fsBtn.addEventListener('click', toggleFS);
addEventListener('keydown', e => {
if(e.metaKey || e.ctrlKey || e.altKey) return;
const k = e.key.toLowerCase();
if(k === 's') toggleSound();
else if(k === 'f') toggleFS();
else if(k === 'h') document.body.classList.toggle('bare');
else if(k === ' ' && !(e.target && e.target.tagName === 'BUTTON')){ e.preventDefault(); randomMeteor(); }
});
/* =====================================================================
LOOP
===================================================================== */
let fAcc = 0, fN = 0, warm = 0;
function frame(now){
requestAnimationFrame(frame);
const raw = (now-S.last)/1000 || 0;
S.last = now;
const dt = Math.min(.1, Math.max(0, raw));
S.t += dt*(reduced ? .35 : 1);
// cursor
const ce = 1-Math.exp(-dt*16);
rx += (cx-rx)*ce; ry += (cy-ry)*ce;
ring.style.transform = 'translate3d(' + rx.toFixed(1) + 'px,' + ry.toFixed(1) + 'px,0)';
if(!glOK) return;
// adaptive resolution: trade pixels for smoothness on slower GPUs
if(raw > 0 && raw < .25){
warm++;
if(warm > 50){
fAcc += raw; fN++;
if(fN >= 45){
const avg = fAcc/fN; fAcc = 0; fN = 0;
if(avg > .027 && S.q > .42){ S.q = Math.max(.42, S.q*.8); resize(); warm = 20; }
}
}
}
// camera
const e = 1-Math.exp(-dt*2.2);
S.yaw += (S.tyaw-S.yaw)*e;
S.pitch += (S.tpitch-S.pitch)*e;
S.camYaw = S.yaw + (reduced ? 0 : Math.sin(S.t*.05)*.05);
S.fade = Math.min(1, S.fade+dt/3.6);
const f = S.fade*S.fade*(3-2*S.fade);
// the night keeps its own schedule
if(S.t > S.nextMeteor){ randomMeteor(); S.nextMeteor = S.t+5+Math.random()*10; }
if(S.t > S.nextRipple){
addRipple((Math.random()-.5)*8, 3+Math.random()*10, .45+Math.random()*.3);
S.nextRipple = S.t+7+Math.random()*9;
}
gl.uniform2f(U.res, canvas.width, canvas.height);
gl.uniform1f(U.time, S.t);
gl.uniform3f(U.cam, S.camYaw, S.pitch, f);
gl.uniform4fv(U.met, met);
gl.uniform4fv(U.rip, rip);
gl.drawArrays(gl.TRIANGLES, 0, 3);
}
/* =====================================================================
BOOT
===================================================================== */
glOK = initGL();
if(glOK){
resize();
addEventListener('resize', resize);
canvas.addEventListener('webglcontextlost', e => { e.preventDefault(); glOK = false; });
canvas.addEventListener('webglcontextrestored', () => { glOK = initGL(); resize(); });
}else{
document.body.classList.add('fallback');
}
setTimeout(() => { document.body.classList.add('live'); wake(); }, reduced ? 4500 : 8800);
requestAnimationFrame(frame);
})();
</script>
</body>
</html>
Verification notes
- Request/response:
~/workspace/fable-5.2-tests/01-most-beautiful-html/(prompt.txt is byte-identical to the series file,cmpclean; meta.json holds the receipt above). - Screenshot: Playwright Chromium 1243 against a local HTTP server, 1440×900, 5 s after load. Runtime gate JSON: 0 page errors, 0 console errors. The first visible interaction label in the viewport is "MOVE — LOOK AROUND" (a hint line, not a button;
no_primary— no CTA button exists on this page). - Catalog facts (1M context, $8/$40 per 1M, reasoning mandatory with default effort high): OpenRouter
/v1/models, 2026-11-09. - The demo link is the shipped fence, unchanged, served at the extensionless URL.