类型:Site模板 · 收录:2026-11-03 21:25:00 · 源码:会员可见
Create ONE standalone file `index.html` (all HTML, CSS and JS inline; no frameworks, no images, no video, nothing external except Google Fonts). It is a cinematic dark landing page for "Helion Dispatch — Autumn '26" (a fictional AI platform that runs clean-energy grids). Every background is generated live in WebGL; every UI element is HTML/CSS/SVG. Follow every number exactly. It must be fully mobile responsive (breakpoints 1100px, 860px, 480px) with no horizontal scroll.
══════════════════════════════════════
0. PAGE MAP (top → bottom)
══════════════════════════════════════
Fixed: WebGL canvas · film grain · header · bottom pill nav
1. HERO (270vh, sticky): blue night particle scene + rotating 3D text ring "Limitless" + bottom row
2. AUTOPILOT (heading only) over an amber "hyperspace streak" shader, reached by a zoom-warp
3. ORCHESTRATE: pure black page; a light dashboard + 3 cards inside see-through windows that show the amber shader
4. AGENT chapter (heading only) over a rose-gold aurora shader
5. BUILD: pure black page; 6 cards inside windows that show the aurora + a feature grid. No footer.
══════════════════════════════════════
1. FONTS, TOKENS, BASE
══════════════════════════════════════
<link href="https://fonts.googleapis.com/css2?family=Inter+Tight:wght@200;300;400;500;600&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
:root{--bg:#03050d;--ink:#f5f1ea;--muted:rgba(245,241,234,.62);--faint:rgba(245,241,234,.38);--line:rgba(255,226,196,.13);--line-2:rgba(255,226,196,.22);
--amber:#f2933c;--gold:#ffd08a;--ice:#a9c8ff;--good:#7ee2a8;--radius:0px;--gutter:clamp(16px,3.2vw,44px);
--sans:"Inter Tight",ui-sans-serif,system-ui,sans-serif;--mono:"JetBrains Mono",ui-monospace,monospace;
--ui:#fff;--ui-ink:#1c1511;--ui-mut:#8a7c71;--ui-faint:#b8aca2;--ui-soft:#f8f5f1;--ui-acc:#ef7a2a}
*{box-sizing:border-box;margin:0;padding:0} html,body{background:#03050d} body{color:var(--ink);font-family:var(--sans);font-size:15px;line-height:1.5;overflow-x:hidden;-webkit-font-smoothing:antialiased}
a{color:inherit;text-decoration:none} button{font:inherit;color:inherit;background:none;border:0;cursor:pointer} ::selection{background:#f2933c;color:#1a0b03}
@keyframes pulse{50%{opacity:.35}} @keyframes spin{to{transform:rotate(360deg)}} @keyframes blink{50%{opacity:0}}
WHITE-UI HOUSE STYLE (applies to every light card on the page):
opaque #fff panels · NO borders, outlines or divider lines anywhere · parts are separated only by slightly darker tints rgba(60,30,10,.03–.06) · 4px corner radius on every panel, button, chip, tile and badge (only true circles stay round: avatars, status dots, the + button, the send button, toggles) · type 10–13px, tabular numbers · panel shadow 0 20px 40px -22px rgba(30,8,0,.55) · status chips: green #e5f6ec/#17803f, warm #fff1e3/#c4621f · badges: .b-ok #dcf5e5/#17703d, .b-warn #ffecd3/#9a560e, .b-dev #e7efff/#2f5bd3, .b-stg #dcf5e5/#17703d, .b-pro #ffeedd/#b4531c (padding 1px 7px, 10px, 500).
SEE-THROUGH WINDOW FRAMES (.frame): position:relative; overflow:hidden; border:0; border-radius:0; no background at all (the WebGL shader shows through them, see §5). Never put opacity, transform or filter on their ancestors.
══════════════════════════════════════
2. FIXED LAYERS
══════════════════════════════════════
<canvas id="gl">: fixed, inset 0, 100%×100%, z-index 0, fallback background radial-gradient(120% 90% at 60% 30%,#1b3a78,#0a1638 45%,#03050d).
.grain: fixed, inset -50%, z-index 1, pointer-events none, opacity .07, mix-blend-mode overlay, background = SVG data-URI 160×160 <filter><feTurbulence type="fractalNoise" baseFrequency=".9" numOctaves="2" stitchTiles="stitch"/></filter>, animated 1.2s steps(6) infinite, translating between (0,0),(-3%,2%),(2%,-3%),(-2%,-1%),(3%,3%).
<main> position relative, z-index 2.
HEADER (fixed top, z 50, flex, align center, gap 28px, padding 14px var(--gutter), background linear-gradient(180deg,rgba(3,5,13,.55),rgba(3,5,13,0))):
• brand (500 15px, gap 10px): 22px SVG [circle cx12 cy12 r11 stroke #f5f1ea at 35% opacity; path "M5 14a7 7 0 0 1 14 0" stroke #f5f1ea width 1.6 round; circle cx12 cy14 r2.6 fill #ffd08a] + "Helion Dispatch" + span.vol "Vol. 07 · Autumn ’26" (--faint, 400).
• nav (gap 26px, 14px, --muted → ink on hover): "Dispatches" + 10px chevron, "Search" + 13px magnifier.
• flex spacer · link "helion.energy" (14px, --muted) · button .btn "Get early access" (height 40px, padding 0 18px, radius 999px, bg #f5f1ea, color #120a05, 500 14px; hover translateY(-1px), bg #fff, box-shadow 0 8px 30px -8px rgba(255,208,138,.55)). Both the button and the docs links point to #top.
• ≤860px hide nav and domain link; ≤480px hide .vol, button height 36px, padding 0 14px, 13px.
BOTTOM PILL NAV (#pill): fixed, left 50%, bottom 18px, z 60; hidden (opacity 0, translate(-50%,30px)) until hero progress > .35, then translate(-50%,0) with .5s cubic-bezier(.2,.8,.2,1).
Button: 236×46, radius 999px, bg rgba(255,248,240,.95), color #1d0f06, 15px, padding 0 8px 0 18px, shadow 0 18px 40px -12px rgba(0,0,0,.55); left a 20px-tall label window whose text slides up and out, then in from below, when the section changes; right a 32px hamburger (three 14px×1.5px bars) that morphs into an X when open.
Menu (opens above, bottom 54px): rounded 18px card, same cream, padding 8px, scale .94 → 1 + fade .3s; links (9px 12px, 14px) with mono 10px indices: Dispatch 07 00, Autopilot 01, Orchestrate 02, Agent 03, Build 04; the current one gets a rgba(29,15,6,.07) background. Close on outside click.
Current section = the last of [#hero, #autopilot, #orchestrate, #agent, #build] whose top < 50% of the viewport.
══════════════════════════════════════
3. HERO
══════════════════════════════════════
<section class="hero" id="hero" style="height:270vh;position:relative"><div class="hero-sticky" style="position:sticky;top:0;height:100svh;overflow:hidden"> ring stage + bottom row </div></section>
3a. ROTATING 3D TEXT RING
Geometry: a vertical cylinder of letters seen slightly from below. The near side reads normally, left to right; the far side shows through mirrored and sits lower.
.ring-stage{position:absolute;left:50%;top:44%;width:0;height:0;perspective:1100px;perspective-origin:50% 40%;pointer-events:none;font-size:clamp(58px,11vw,190px)} (≤860px: top:40%)
.ring{position:absolute;left:0;top:0;transform-style:preserve-3d;will-change:transform}
.ring span{position:absolute;left:0;top:0;transform-origin:0 0;font-weight:300;letter-spacing:-.03em;line-height:1;white-space:pre;color:#f4f7ff;text-shadow:0 0 28px rgba(150,190,255,.35);backface-visibility:visible}
Text 'Limitless · Limitless · ' (24 chars incl. spaces and middle dots), one span each.
Layout (once, again after document.fonts.ready, and on resize): ctx.font='300 100px "Inter Tight", sans-serif'; ws[i]=measureText(ch).width/100; total=Σws; R=total/(2π)×1.3 (em);
acc=0; angle_i=(acc+ws[i]/2)/total×360; acc+=ws[i]; span.style.transform=`rotateY(angleideg)translateZ({R}em) translate(-50%,-55%)`.
Every frame: ringRot += dt×9 (0 if reduced motion); rot = ringRot + scrollY×0.12; tilt = 16 + (mouseY−.5)×8 (16 before any pointer; mouseY 0..1 with bottom = 0);
heroP = clamp(scrollY/(hero.offsetHeight−innerHeight),0,1); fly = smoothstep(.15,.60,heroP);
ring.style.transform=`translateZ(fly*900px)rotateX({tilt}deg) rotateY(${-rot}deg)` ← translateZ outermost: the whole tilted, spinning ring flies straight at the camera, about 5.5× bigger at fly=1 (perspective 1100).
ringStage.opacity = 1 − smoothstep(.20,.55,heroP); each letter's opacity = .16 + .84×((cos((angle_i−rot)·π/180)+1)/2)^1.6.
smoothstep(a,b,x){t=clamp((x−a)/(b−a),0,1);return t*t*(3−2t)}
3b. HERO BOTTOM ROW (#heroBottom): absolute, bottom 0, padding 0 var(--gutter) clamp(22px,4vh,44px), grid 1.1fr | auto | 1.1fr, align end, gap 24px.
Left: eyebrow (mono 11px, .14em, uppercase, --ice, opacity .85, gap 10px) with a 6px glowing dot pulsing 2s: "DISPATCH 07 — AUTUMN 2026"; then p (clamp(17px,1.5vw,21px), line-height 1.3, -.015em, max-width 360px, text-shadow 0 2px 20px rgba(0,0,0,.4)): "120+ releases to generate, store and trade clean energy — everywhere the sun reaches."
Centre: "Scroll to ignite" (12px, --muted) above a 1px×46px line (gradient ink → transparent, opacity .8) with a white 40%-tall highlight sliding top→bottom (1.8s cubic-bezier(.6,0,.3,1) infinite).
Right: grid 2 columns auto (gap 6px 40px, justify-self end), links (15px, gap 10px) "<small mono 10px faint>NN</small><span>Label</span>", the span underline grows 0→100% on hover (.35s): 01 Autopilot, 05 Markets, 02 Forecast, 06 Fleet, 03 Storage, 07 Developer, 04 Trading, 08 Console.
JS: opacity = 1 − smoothstep(.02,.22,heroP); pointer-events none when heroP > .15.
≤860px: one column (gap 22px), links grid repeat(2,1fr) full width (gap 4px 28px), scroll cue hidden.
3c. PAINTED SCENE (offscreen canvas; source for particle colours and the soft background)
PRNG: function rng(seed){return function(){seed|=0;seed=seed+0x6D2B79F5|0;let t=Math.imul(seed^seed>>>15,1|seed);t=t+Math.imul(t^t>>>7,61|t)^t;return((t^t>>>14)>>>0)/4294967296}}
Canvas: sw = 440 (300 mobile), sh = round(sw×H/W). Paint EXACTLY:
function paintScene(cv,w,h){
cv.width=w;cv.height=h;const g=cv.getContext('2d');const R=rng(7);
let gr=g.createLinearGradient(0,0,0,h);
gr.addColorStop(0,'#02041a');gr.addColorStop(.32,'#0a1a4a');gr.addColorStop(.56,'#24509c');gr.addColorStop(.64,'#3d77c6');gr.addColorStop(.7,'#14306a');gr.addColorStop(1,'#030a22');
g.fillStyle=gr;g.fillRect(0,0,w,h);
let rg=g.createRadialGradient(w*.66,h*.3,0,w*.66,h*.3,Math.max(w,h)*.5);
rg.addColorStop(0,'rgba(236,242,255,.95)');rg.addColorStop(.12,'rgba(190,212,255,.65)');rg.addColorStop(.4,'rgba(90,130,230,.22)');rg.addColorStop(1,'rgba(20,40,120,0)');
g.fillStyle=rg;g.fillRect(0,0,w,h);
rg=g.createRadialGradient(w*.18,h*.22,0,w*.18,h*.22,w*.35);rg.addColorStop(0,'rgba(120,150,255,.35)');rg.addColorStop(1,'rgba(40,60,160,0)');g.fillStyle=rg;g.fillRect(0,0,w,h);
const cols=['rgba(110,180,255,.55)','rgba(200,225,255,.5)','rgba(80,120,255,.45)','rgba(160,200,255,.4)','rgba(220,235,255,.35)'];
g.globalCompositeOperation='lighter';
for(let k=0;k<5;k++){g.beginPath();const y0=h*(.12+k*.07);
for(let i=0;i<=40;i++){const x=i/40*w;const y=y0+Math.sin(i*.28+k*1.7)*h*.05+Math.sin(i*.09+k)*h*.06;i?g.lineTo(x,y):g.moveTo(x,y)}
g.strokeStyle=cols[k];g.lineWidth=h*(.025+R()*.03);g.filter=`blur(${Math.round(h*.02)}px)`;g.stroke();}
g.filter='none';g.globalCompositeOperation='source-over';
g.beginPath();g.moveTo(0,h);
for(let i=0;i<=60;i++){const x=i/60*w;const y=h*.66-Math.abs(Math.sin(i*.37)*Math.sin(i*.11+1))*h*.11-R()*h*.012;g.lineTo(x,y)}
g.lineTo(w,h);g.closePath();gr=g.createLinearGradient(0,h*.52,0,h*.8);gr.addColorStop(0,'#1b3a7a');gr.addColorStop(1,'#071636');g.fillStyle=gr;g.fill();
const gauss=()=>(R()+R()+R()-1.5)/1.5, lerp=(a,b,t)=>a+(b-a)*t;
const tree=(fx,base,top,wd,trunkCol,rim,leafCols,scale)=>{
const x0=w*fx,yb=h*base,yt=h*top,lean=(R()-.5)*w*.035,ph=R()*9;
const X=t=>x0+lean*t*t+Math.sin(t*4+ph)*w*.004;const L=[],Rt=[];
for(let i=0;i<=24;i++){const t=i/24,y=lerp(yb,yt,t),half=w*wd*(1-t*.75)*.5;L.push([X(t)-half,y]);Rt.push([X(t)+half,y])}
g.beginPath();L.forEach(([x,y],i)=>i?g.lineTo(x,y):g.moveTo(x,y));Rt.reverse().forEach(([x,y])=>g.lineTo(x,y));g.closePath();g.fillStyle=trunkCol;g.fill();
g.beginPath();Rt.reverse().forEach(([x,y],i)=>i?g.lineTo(x,y):g.moveTo(x,y));g.strokeStyle=rim;g.lineWidth=Math.max(1,w*.0016);g.stroke();
for(let b=0;b<5;b++){const t=.5+R()*.42,sx=X(t),sy=lerp(yb,yt,t),dir=R()<.5?-1:1,len=w*(.025+R()*.04)*scale;
g.beginPath();g.moveTo(sx,sy);g.quadraticCurveTo(sx+dir*len*.5,sy-len*.15,sx+dir*len,sy-len*.55);g.strokeStyle=trunkCol;g.lineWidth=Math.max(1,w*.0014);g.stroke()}
const clumps=5+(R()*4|0);
for(let c=0;c<clumps;c++){const t=.5+R()*.55,cx=X(Math.min(t,1))+gauss()*w*.03*scale,cy=lerp(yb,yt,Math.min(t,1.04)),sp=w*(.02+R()*.03)*scale;
for(let k=0;k<90*scale;k++){const dx=gauss()*sp,dy=gauss()*sp*.7,lit=(dx>0?.6:0)+R()*.6;
g.fillStyle=leafCols[Math.min(leafCols.length-1,(lit*leafCols.length*.7)|0)];g.beginPath();g.arc(cx+dx,cy+dy,w*(.0025+R()*.006)*scale,0,7);g.fill()}}};
const farLeaf=['rgba(40,80,160,.55)','rgba(80,130,210,.5)','rgba(150,190,245,.45)','rgba(210,228,255,.45)'];
[[.08,.68,.2],[.33,.7,.24],[.61,.69,.18],[.86,.68,.22]].forEach(([fx,b,t])=>tree(fx,b,t,.006,'rgba(16,36,86,.9)','rgba(150,185,240,.35)',farLeaf,.6));
gr=g.createLinearGradient(0,h*.6,0,h*.78);gr.addColorStop(0,'rgba(190,215,255,0)');gr.addColorStop(.5,'rgba(190,215,255,.32)');gr.addColorStop(1,'rgba(190,215,255,0)');g.fillStyle=gr;g.fillRect(0,h*.6,w,h*.18);
const hill=(yb,amp,ph,c1,c2)=>{g.beginPath();g.moveTo(0,h);for(let i=0;i<=50;i++){const x=i/50*w;g.lineTo(x,h*yb-Math.sin(i*.12+ph)*h*amp-Math.sin(i*.31+ph*2)*h*amp*.3)}g.lineTo(w,h);g.closePath();
const gg=g.createLinearGradient(0,h*(yb-amp),0,h);gg.addColorStop(0,c1);gg.addColorStop(1,c2);g.fillStyle=gg;g.fill()};
hill(.8,.05,.4,'#2c64b4','#08193d');
const nearLeaf=['rgba(6,18,48,.9)','rgba(18,46,104,.85)','rgba(46,96,184,.8)','rgba(120,170,240,.75)','rgba(220,234,255,.8)'];
[[.16,.9,.04,.009],[.43,.92,.02,.011],[.53,.91,.12,.007],[.78,.9,.06,.01],[.95,.93,.16,.008]].forEach(([fx,b,t,wd])=>tree(fx,b,t,wd*1.3,'#030a20','rgba(190,215,255,.55)',nearLeaf,1));
hill(.88,.06,2.2,'#1c4a92','#040d26');
for(let i=0;i<900;i++){const x=R()*w,y=h*(.76+R()*.24);g.fillStyle=`rgba(190+R()*65|0,{215+R()*40|0},255,${R()*.35})`;g.fillRect(x,y,1+R()*2,1+R()*2)}
rg=g.createRadialGradient(w/2,h*.45,Math.min(w,h)*.3,w/2,h*.5,Math.max(w,h)*.8);rg.addColorStop(0,'rgba(0,0,0,0)');rg.addColorStop(1,'rgba(0,2,12,.6)');g.fillStyle=rg;g.fillRect(0,0,w,h);
}
Soft texture: draw it onto a 340px-wide canvas with ctx.filter='blur(2px)'; upload it (UNPACK_FLIP_Y true, LINEAR, CLAMP_TO_EDGE).
3d. PARTICLES (built with R = rng(42))
count = clamp(W×H/(mobile?11:13), 20000, mobile?40000:110000).
Density: lum=.3r+.59g+.11b; edge=|central-difference gradient|; dens=.34+min(1,(5×5 box-sum of edge)/25×14). Rejection-sample (x,y) until R() ≤ dens (max 40 tries).
Colour = the painted pixel at a random ±4px offset × (.68+R()×.5); q=R(): q<.025 silver (s=.8+R()×.2 → s×(.9,.95,1)); q<.11 cyan (.3+R()×.25, .55+R()×.3, 1); q<.15 lavender (.55+R()×.2, .6+R()×.15, .95); q<.2 navy (×(.35,.4,.6)).
Attributes (9 floats): a_pos=(x×2−1,(1−y)×2−1), a_col, a_rnd=4×R().
Cursor: mT=[x/W, 1−y/H] from pointermove/touchstart/touchmove; mouse=lerp(mouse,mT,.16) per frame.
Trail: when the smoothed mouse moves >.012 from the last stored point, push {x,y,vx=Δx,vy=Δy,t:now}, keeping the newest 10; strength k = a<1 ? min(1,a×7)×(1−a)^2.2 : 0 with a=age/1400ms → u_trail[i]=(x,y,k,0), u_tvel[i]=(vx,vy).
══════════════════════════════════════
4. WEBGL (one canvas, one loop)
══════════════════════════════════════
gl=getContext('webgl',{antialias:false,alpha:false,premultipliedAlpha:false}). W=canvas.clientWidth, H=canvas.clientHeight (NOT innerWidth); DPR=min(dpr, mobile?1.5:1.75); canvas.width=W×DPR. Rebuild the scene and particles only if the width changes >2px or the height >25%. isMobile = (max-width:860px) or (pointer:coarse).
Each frame: background quad (blend off) then GL_POINTS (blendFunc SRC_ALPHA, ONE_MINUS_SRC_ALPHA). u_size = mobile?1.5:1.8, u_pr = DPR, u_aspect = W/H.
SHARED MASK GLSL (inject into BOTH fragment shaders):
uniform vec4 u_rects[12];uniform float u_ra[12];uniform float u_nr,u_cut,u_c2t,u_c2b,u_rad,u_hpx,u_wpx,u_arch;
float rbox(vec2 p,vec4 r){vec2 c=r.xy+r.zw*.5;vec2 q=abs(p-c)-r.zw*.5+u_rad;return length(max(q,0.))+min(max(q.x,q.y),0.)-u_rad;}
float winMask(vec2 p){float m=0.;for(int i=0;i<12;i++){if(float(i)>=u_nr)break;m=max(m,(1.-smoothstep(-1.,1.,rbox(p,u_rects[i])))*u_ra[i]);}return m;}
vec4 region(vec2 p){float xn=p.x/u_wpx;float e=(2.*xn-1.)*(2.*xn-1.);
float bow=u_arch*(1.-e);float c1=u_cut+bow;float t2=u_c2t+bow;float b2=u_c2b+bow;
float belowC1=smoothstep(c1-1.5,c1+1.5,p.y);
float inB=smoothstep(t2-1.5,t2+1.5,p.y)*(1.-smoothstep(b2-1.5,b2+1.5,p.y));
float belowB=smoothstep(b2-1.5,b2+1.5,p.y);
return vec4(1.-belowC1,inB,max(belowC1-inB,0.),belowB);}
Uniforms each frame (device px, top-origin): u_cut = #orchestrate top; u_c2t/u_c2b = #agent top/bottom; u_rad = 0; u_arch = 0 (edges perfectly straight); u_hpx/u_wpx = canvas size; u_rects = up to 12 on-screen .frame rects [left,top,width,height]×DPR; u_ra = each frame's alpha, lerped .08/frame toward 1 once it has the class .in (0 before); u_nr = count.
BACKGROUND FRAGMENT SHADER (OCT = 5 desktop, 4 mobile):
precision highp float; varying vec2 v; uniform sampler2D u_tex; uniform vec2 u_mouse,u_vp; uniform float u_time,u_warp,u_aspect;
float hash(vec3 p){p=fract(p*.3183099+.1);p*=17.;return fract(p.x*p.y*p.z*(p.x+p.y+p.z));}
float noise(vec3 x){vec3 i=floor(x),f=fract(x);f=f*f*(3.-2.*f);return mix(mix(mix(hash(i),hash(i+vec3(1,0,0)),f.x),mix(hash(i+vec3(0,1,0)),hash(i+vec3(1,1,0)),f.x),f.y),mix(mix(hash(i+vec3(0,0,1)),hash(i+vec3(1,0,1)),f.x),mix(hash(i+vec3(0,1,1)),hash(i+vec3(1,1,1)),f.x),f.y),f.z);}
float fbm(vec3 p){float s=0.,a=.5;for(int i=0;i<OCT;i++){s+=a*noise(p);p=p*2.03+vec3(1.7,9.2,3.1);a*=.5;}return s;}
[MASK GLSL HERE]
vec3 heroCol(vec2 uv,vec2 vpu){vec3 c=vec3(0.);vec2 d=uv-vpu;float amt=u_warp*.75;
for(int i=0;i<10;i++){float s=1.-amt*float(i)/10.;c+=texture2D(u_tex,vpu+d*s).rgb;}c/=10.;
vec2 m=(uv-u_mouse)*vec2(u_aspect,1.);c+=vec3(.25,.4,.8)*exp(-dot(m,m)*14.)*.35;return c*.82;}
vec3 streaks(vec2 uv,vec2 vpu){vec2 p=(uv-vpu)*vec2(u_aspect,1.);vec2 m=(uv-u_mouse)*vec2(u_aspect,1.);float md=exp(-dot(m,m)*6.);
float r=length(p);float a=atan(p.y,p.x);vec2 cs=vec2(cos(a),sin(a));float lr=log(r+.03);float t=u_time;
float n1=fbm(vec3(cs*2.1,lr*.8-t*.16));float n2=fbm(vec3(cs*5.5+4.,lr*1.5-t*.3+md*.4));float n3=fbm(vec3(cs*13.+9.,lr*2.4-t*.48));
float s=n1*.6+n2*.35+n3*.15;float rad=smoothstep(.0,.75,r);
vec3 deep=vec3(.03,.014,.01),umber=vec3(.2,.085,.035),copper=vec3(.7,.31,.12),amber=vec3(1.,.58,.22),cream=vec3(1.,.9,.74);
vec3 c=mix(deep,umber,smoothstep(.28,.55,s)*(.4+rad));c=mix(c,copper,smoothstep(.46,.72,s)*rad);
c=mix(c,amber,smoothstep(.58,.82,n2*.6+n1*.5)*rad*(.75+md*.6));c+=cream*pow(smoothstep(.6,.95,n3*.45+n1*.62),3.)*rad*(.8+md);
c+=vec3(.09,.035,.1)*(1.-s)*rad*.7;c+=vec3(.55,.75,1.)*pow(smoothstep(.72,.98,n3),5.)*rad*.35;c+=amber*md*.12;return c;}
vec3 aurora(vec2 uv){vec2 p=uv*vec2(u_aspect,1.);vec2 m=(uv-u_mouse)*vec2(u_aspect,1.);float md=exp(-dot(m,m)*5.);
float t=u_time*.07;float bend=fbm(vec3(p.x*.7,p.y*.5,t))*1.4;float x=p.x*1.3+bend+m.x*md*.6;
float cur=fbm(vec3(x*2.6,p.y*.35-t*.6,t*.8));float ray=fbm(vec3(x*11.,p.y*.12,t*1.3));
float hgt=smoothstep(.02,.5,uv.y)*smoothstep(1.08,.55,uv.y+cur*.25);float k=smoothstep(.36,.76,cur)*hgt*(1.+md*.5);
float r=pow(smoothstep(.42,.85,ray),2.)*k;
vec3 c=mix(vec3(.02,.01,.015),vec3(.12,.04,.075),smoothstep(0.,1.,uv.y));
c+=vec3(.6,.19,.22)*k*.9;c+=vec3(1.,.55,.42)*r*1.15;c+=vec3(1.,.88,.74)*r*r*1.3;
c+=vec3(.36,.12,.32)*smoothstep(.3,.9,fbm(vec3(p*1.5,t)))*(1.-k)*.4;
float s=hash(vec3(floor(gl_FragCoord.xy/3.),1.));c+=vec3(1.,.93,.86)*step(.9965,s)*(.55+.45*sin(u_time*3.+s*60.));
c+=vec3(1.,.7,.55)*md*.15*(.4+k);return c;}
void main(){vec2 uv=v;vec2 vpu=u_vp*.5+.5;vec2 px=vec2(gl_FragCoord.x,u_hpx-gl_FragCoord.y);
vec4 rg=region(px);float win=rg.z>.001?winMask(px)*rg.z:0.;
if(rg.x<.001&&rg.y<.001&&win<.001){gl_FragColor=vec4(0.,0.,0.,1.);return;}
float wA=rg.x+win*(1.-rg.w),wB=rg.y+win*rg.w;vec3 col=vec3(0.);
if(wA>.001){vec3 a=vec3(0.);if(u_warp<.999)a=heroCol(uv,vpu);if(u_warp>.001)a=mix(a,streaks(uv,vpu),smoothstep(0.,1.,u_warp));vec2 q=uv-.5;a*=1.-dot(q,q)*.9;col+=a*wA;}
if(wB>.001)col+=aurora(uv)*wB;
col+=(hash(vec3(gl_FragCoord.xy,u_time*60.))-.5)*.035*min(1.,wA+wB);gl_FragColor=vec4(col,1.);}
PARTICLE VERTEX SHADER:
attribute vec2 a_pos;attribute vec3 a_col;attribute vec4 a_rnd;
uniform float u_time,u_warp,u_aspect,u_pr,u_size;uniform vec2 u_vp;uniform vec4 u_trail[10];uniform vec2 u_tvel[10];
varying vec3 v_col;varying float v_a;
void main(){vec2 p=a_pos;float t=u_time;float hgt=clamp((p.y+1.)*.5,0.,1.);
p.x+=sin(t*.7+p.y*3.+a_rnd.x*.6)*.007*hgt*hgt;
p+=vec2(sin(t*.9+a_rnd.x*6.283),cos(t*.8+a_rnd.y*6.283))*.0045*(.3+a_rnd.z);
bool pol=a_rnd.w>.13&&a_rnd.w<.24;float kk=0.;
if(pol){kk=fract(a_rnd.y+t*(.012+a_rnd.z*.022));p.y=mix(-1.05,1.05,kk);p.x+=sin(t*.5+a_rnd.x*20.)*.03;}
vec2 off=vec2(0.);float fs=0.;
for(int i=0;i<10;i++){vec4 tp=u_trail[i];if(tp.z<=0.)continue;vec2 d=p-(tp.xy*2.-1.);d.x*=u_aspect;float dist=length(d);float f=exp(-dist*dist*70.)*tp.z;
vec2 dir=d/(dist+1e-4);vec2 perp=vec2(-dir.y,dir.x);off+=(dir*(.045+.05*a_rnd.z)+perp*.04*(a_rnd.w-.5))*f;off+=u_tvel[i]*f*(1.4+a_rnd.z*2.)*vec2(u_aspect,1.);fs+=f;}
off/=max(1.,fs);off.x/=u_aspect;p+=off;
bool dust=a_rnd.w<.13;float w=u_warp;vec2 hp=u_vp+(p-u_vp)*(1.+w*w*(1.2+a_rnd.z*7.));
float ang=a_rnd.x*6.2831;float rr=fract(a_rnd.y+t*(.025+a_rnd.z*.05));vec2 dp=u_vp+vec2(cos(ang)/u_aspect,sin(ang))*(rr*rr*2.3)+off*.6;
float dw=smoothstep(.35,.9,w);float aHero=1.-smoothstep(.15,.8,w);float sz=(1.1+a_rnd.z*1.7)*u_size*(1.+w*3.*a_rnd.z);vec3 c=a_col;
if(dust){p=mix(hp,dp,dw);v_a=mix(aHero,.95*smoothstep(.12,.42,rr),dw);sz=mix(sz,(.7+rr*2.4)*u_size*.9,dw);c=mix(c,mix(vec3(1.,.96,.9),vec3(1.,.72,.42),a_rnd.z),dw);}
else{p=hp;v_a=aHero;if(pol){v_a*=sin(kk*3.14159);c=mix(c,vec3(.82,.9,1.),.65);sz*=.8;}}
v_col=c;gl_PointSize=sz*u_pr;gl_Position=vec4(p,0.,1.);}
PARTICLE FRAGMENT SHADER:
precision highp float;varying vec3 v_col;varying float v_a; [MASK GLSL HERE]
void main(){vec2 px=vec2(gl_FragCoord.x,u_hpx-gl_FragCoord.y);vec4 rg=region(px);float vis=rg.x+rg.y;if(rg.z>.001)vis+=winMask(px)*rg.z;
float d=length(gl_PointCoord-.5);float a=smoothstep(.5,.18,d)*v_a*min(vis,1.);if(a<.01)discard;gl_FragColor=vec4(v_col,a);}
══════════════════════════════════════
5. ZOOM-WARP TIMELINE (hero → Autopilot)
══════════════════════════════════════
warpTarget = smoothstep(.22,.88,heroP); warp = lerp(warp,warpTarget,.09) per frame.
vp = lerp(vp, [(mouseX−.5)×2×.18×warp, (mouseY−.5)×2×.12×warp], .05).
0–.22: the ring spins faster with scroll and the bottom row fades. .15–.60: the ring flies at the camera (translateZ 0→900px) and fades (.20–.55). .22–.88: the background zoom-blurs toward the vanishing point (10 taps, warp×.75) and crossfades into amber streaks; particles burst outward (×1+warp²×1.2…8.2), grow up to 4× and fade by .8; 13% become warm "dust" streaming radially outward forever. Reversible on scroll up.
══════════════════════════════════════
6. SECTION: AUTOPILOT
══════════════════════════════════════
<section class="s2" id="autopilot"><h2 class="rv">Autopilot</h2></section>
.s2{position:relative;min-height:115vh;margin-top:-40vh;display:flex;flex-direction:column;justify-content:center;padding:18vh var(--gutter) 10vh}
.s2 h2{font-weight:300;font-size:clamp(68px,12.5vw,220px);line-height:.88;letter-spacing:-.055em;background:linear-gradient(180deg,#fff 30%,#ffe2bd 100%);-webkit-background-clip:text;background-clip:text;color:transparent}
Only the heading. Background = amber streaks (scene A).
══════════════════════════════════════
7. SECTION: ORCHESTRATE (#orchestrate, black page)
══════════════════════════════════════
.sec{position:relative;padding:12vh var(--gutter) 6vh}
.sec-head{display:grid;grid-template-columns:1.3fr 1fr;gap:40px;align-items:start;margin-bottom:clamp(28px,4vw,48px)}
.sec-head h3{font-weight:400;font-size:clamp(32px,4.2vw,62px);line-height:1.02;letter-spacing:-.04em;max-width:720px} em{font-style:normal;color:#ffd08a}
.sec-head .side{justify-self:end;max-width:400px;font-size:14px;color:var(--muted);padding-top:8px} link: inline-flex, gap 8px, margin-top 14px, ink, 500, 1px bottom border (line-2 → gold on hover, gap grows to 12px).
H3: "Your energy, <em>orchestrated</em> from sunrise to settlement". Side: "One console for generation, storage and market positions. Autopilot balances all three in real time and explains every move it makes." + link "Explore the console →".
7a. MAIN FRAME (#dashFrame.frame): height clamp(580px,86vh,880px); display grid; place-items center; perspective 1800px. The app inside tilts toward the cursor: rotateY(x×4deg) rotateX(−y×3deg) (x,y = −.5….5 in the frame), .5s ease, reset on leave (not on mobile).
.app{width:min(1120px,88%);height:85%;background:#fff;color:#1c1511;border-radius:4px;display:grid;grid-template-columns:196px 1fr 268px;overflow:hidden;box-shadow:0 60px 120px -30px rgba(30,8,0,.7);font-size:12.5px}
SIDEBAR (#f6f4f1, no divider, padding 14px 10px 14px 0, gap 1px):
logo row (padding 2px 14px 14px, 600 14px): 20px SVG (ink arc "M5 14a7 7 0 0 1 14 0" stroke 2 + orange #ef7a2a dot r2.8) "Helion".
items (padding 5px 10px 5px 16px, gap 9px, radius 0 4px 4px 0, 12.5px, #5d5249, 14px stroke icons #a0948a; hover bg rgba(60,30,10,.035)): Home (SELECTED: bg #fff, ink, 500, orange icon, ::before 3px #ef7a2a bar on the left edge), Sites, Forecast, Storage, Markets (right: grey "3"), Reports;
separator (1px rgba(60,30,10,.07), margin 12px 10px 12px 16px);
Playbooks (right tag "New": padding 1px 7px, radius 4px, #ffeedd/#c4621f, 10px 500), Guardrails;
account card (margin-top auto, margin-left 12px, radius 4px, padding 12px, white text, background linear-gradient(150deg,#2e1b12 0%,#9c4c25 58%,#f2a65e 100%) plus an ::after radial #ffdcae glow 110px at the top-right corner, opacity .55): 28px white avatar "MV" (#9c4c25 text) + "Mara Vidal" (500 12px) / "Fleet plan · 38 sites" (10.5px, 75%); full-width white button "Connect a new site" (28px, radius 4px, #2e1b12 text, 500 11.5px).
MAIN (background radial-gradient(55% 45% at 50% 36%,#fff3e6 0%,#fff 72%)):
top bar (padding 14px 18px): "Iberia portfolio" (ink 500) + " / Tonight" (faint); right two pills (padding 4px 10px, radius 4px, bg rgba(60,30,10,.045), 11px #62554c): "● Autopilot on" (green dot pulsing 1.6s), "☀ 19° · Clear" (hidden ≤860px).
centre column (flex 1, centred, gap 6px):
AI ORB (66px circle, margin-bottom 16px, overflow hidden, bg #2e1b12, shadow 0 16px 34px -12px rgba(156,76,37,.75), breathing 4.8s: scale 1↔1.06 with the shadow → 0 18px 44px -10px rgba(242,166,94,.8)):
::before inset −35%, conic-gradient(from 0deg,#2e1b12,#9c4c25,#f2a65e,#ffdcae,#c4622d,#5a2c17,#2e1b12), blur 9px, spin 7s linear;
3 blurred blobs (mix-blend screen; the 3rd multiply): 70% #ffdcae radial at left −10% top −5% (5.5s, translate(35%,30%) scale 1.2 at 50%); 65% #f2a65e at right −15% bottom −10% (6.5s, translate(−40%,−25%) scale .85); 55% #9c4c25 at 25%/30% (4.5s, path −30%,−25% → 20%,−35%);
::after gloss: radial rgba(255,244,228,.75) at 32% 24% (to 32%) + a dark base radial at 50% 120%, inset 1px rgba(255,230,200,.18) ring.
"Good evening, Mara" (600 26px, −.035em) · "The grid is calm. What should Autopilot handle tonight?" (13px, #8a7c71) · stat line (margin-top 22px, gap 18px, 11.5px muted, bold ink numbers counting up): "412.8 MWh today", "86% fleet charge", "€128,402 revenue".
prompt box (margin 0 18px 16px, radius 4px, bg rgba(60,30,10,.04), padding 12px 12px 10px 14px): placeholder line types these in turn (42ms per char, hold 1.8s, delete 2 chars every 18ms, 400ms gap) with a 1.5px orange caret: "Shift tonight’s surplus into the 19:00 peak…", "Hold 20% reserve while the storm warning is active…", "Sell anything above €85 on intraday…", "Why did Sevilla-03 underperform yesterday?"; row: chips "+ Site", "⚡ Playbooks", "⚙ Options" (padding 5px 10px, radius 4px, bg rgba(60,30,10,.05), 11px) + a 30px round ink send button (white up-arrow, scale 1.08 on hover).
RIGHT PANEL (#faf9f7, no divider, padding 16px 14px, gap 12px): header "Autopilot log" (600 15px) + 28px tinted "⋯" button; tinted search (32px) "Search actions…"; list (gap 14px; grid 16px | 1fr | auto):
• "Evening peak shift" / now: spinning orange ring check (border-top transparent, .9s); "Moving 42 MWh from midday surplus into the 19:00 ramp."; a progress bar (4px, tinted track, orange fill) that climbs 3–9% every 500ms and loops.
• "Storm stow" / 12m: green ✓ check; "Trackers stowed ahead of 62 km/h gusts at Tarifa."; a 96px tile linear-gradient(160deg,#ffe6c8,#f39a52 58%,#b24d1d) with "Output · Tarifa" (white 600) and "+18% vs. plan", plus a live white sparkline + 18% white area (24 points random-walking every 900ms).
• "Intraday bid batch" / 34m: "24 bids placed at €81–86 / MWh." + four overlapping 20px squares (#ffb45e, #2f5bd3, #2fbf71, #e0702c; 2px #faf9f7 border, −6px overlap).
• "Forecast refresh" / 1h: "Sevilla-03 raised by 6% after cloud break."
Button "New playbook" (36px, radius 4px, ink bg, white 500).
7b. THREE CARDS (.cards grid 3 columns, gap 16px, margin-top 16px; 1 column ≤860px). Each: .frame (aspect-ratio 4/3, container-type inline-size) + h5 (margin-top 14px, 500 15px) + p (13.5px, --muted, max-width 370px). Panels centred in the frame (display grid, place-items center). @container (max-width:360px): panel width 90%, padding 11px, hide secondary lines (pc-n small, note skeleton, step trash buttons), smaller text.
CARD 1 "Forecasts that see 72 hours out" / "Satellite cloud tracking, wind models and on-site sensors stream into one forecast per panel string.":
white card (width 86%, padding 14px, grid gap 6px): header "Forecast inputs" (600 13px) + "Sevilla-03" (10.5px muted, right). Two rows (padding 8px, radius 4px, bg rgba(60,30,10,.035), gap 10px):
34px thumbnail (radius 4px), cloud imagery = radial white blobs over linear-gradient(140deg,#17366e,#4f86d6) · "Meteosat-12" (500 12px) / "Cloud imagery · every 5 min" (10.5px muted);
solar-panel thumbnail = 8px white 28% grid lines over linear-gradient(160deg,#f2a65e 0%,#9c4c25 35%,#1f2f55 36%,#14213d) · "Tarifa irradiance" / "On-site sensors · every second";
each with a button (27px, padding 0 10px, radius 4px, ink bg, white 500 11px) "🔒 Connect" (11px lock SVG).
Animated cursor: a 22px black arrow SVG with a white stroke and a black pill "You" (white text, 1.5px white ring, shadow) at offset 18px/20px. Loop: rest near the card's bottom-right (1.8s) → glide to button 1 (transform transition 1.1s cubic-bezier(.6,0,.25,1)), wait 1.3s, press (scale .93 for 170ms), the button becomes a green chip "● Live" (pulsing dot) → 900ms → the same for button 2 → back to rest, wait 3.2s, reset both and repeat.
CARD 2 "Storage that charges itself" / "Chain a trigger and a few actions into a playbook once. Batteries fill when power is cheap and green, then release it into the peak.":
No panel. A centred column of 3 white step cards (width 76%, padding 8px 8px 8px 9px, radius 4px, 500 12px, shadow 0 16px 30px -18px rgba(30,8,0,.65)), each: a bare 15px coloured icon (no tile), label, status, 24px tinted trash button:
orange trend-down icon "Price drops below €40" · blue battery icon "Charge Tarifa BESS to 90%" · green shield icon "Hold 20% storm reserve".
Between and after the steps: a 2px × 16px dashed connector (linear-gradient(rgba(255,240,225,.85) 50%,transparent 50%) 0 0/2px 6px, background-position animated 0→6px every .6s so it flows down), ending in a 34px round ink "+" button (white plus, shadow).
Loop: wait 1.2s; each step → "● Running" (orange #ef7a2a, pulsing dot, card translateX(4px)) for 1.5s → "● Done" (green #17803f) → 250ms; then after 1.4s the + presses (scale .88, 180ms); 1.6s later reset and repeat.
CARD 3 "Trade every kilowatt" / "Bids land on day-ahead and intraday markets automatically, inside limits you set once and audit any time.":
white note (width 86%, padding 14px 14px 12px): header "Bid rationale" (600 13px) + tag "19:00" (radius 4px, rgba(60,30,10,.06), 10px) + right chip "Filled ✓" (green, with a 12px green circle check).
text (11.5px, line-height 1.6, color #4f433b; override any generic `.card p` colour): "Prices spike at 19:00 as solar fades. Selling 12 MWh from Tarifa <span class="sel">at €84.20 beats holding by €1,140, while the storm reserve</span> stays intact for tomorrow."
.sel: ink text, background linear-gradient(rgba(239,122,42,.2),…) no-repeat 0 0/0% 100%, box-decoration-break clone, animated 7s: 0–12% 0%, 30–82% 100%, 92–100% 0%.
toolbar (absolute, left 50%, top 64%, white, padding 4px, radius 4px, shadow 0 12px 26px -10px rgba(30,8,0,.45) + 1px rgba(60,30,10,.06) ring), 7s in sync: hidden until 30%, shown 36–80% (rises 6px), fades by 88%. Four 24×22 icon buttons: star (orange, no background), comment, copy, flag.
two placeholder bars (7px tall, radius 4px, rgba(60,30,10,.06); second 45% wide), margin 12px 0; tags "Intraday" (tinted) and "Peak shave" (#fff1e3/#c4621f).
7c. NOTES ROW: grid 3 columns, gap 16px, margin-top clamp(40px,7vh,72px), padding-top 22px, border-top 1px var(--line); h6 500 14px + p 13px muted: "Grid-aware APIs" / "Query live frequency, congestion and nodal prices for 40+ markets from a single endpoint." · "Fleet telemetry at 1-second resolution" / "Inverters, trackers and BMS data stream in, deduplicated and time-aligned by default." · "Explainable decisions" / "Every Autopilot action ships with a plain-language rationale and a one-click rollback."
══════════════════════════════════════
8. CHAPTER: AGENT (#agent)
══════════════════════════════════════
<section class="s2 ch2" id="agent"><h2 class="rv">Agent</h2></section> .ch2{margin-top:8vh;min-height:125vh;padding:24vh var(--gutter) 22vh} h2 gradient linear-gradient(180deg,#fff 30%,#ffd3c2 100%). Only the heading; background = aurora (scene B) with straight top and bottom edges into black.
══════════════════════════════════════
9. SECTION: BUILD (#build, black page, padding-bottom 20vh)
══════════════════════════════════════
Head: H3 "Energy skills for your <em>favorite agent</em>"; side: "Build automations, dashboards and market adapters from your terminal or coding agent — with typed SDKs, sandbox grids and replayable telemetry." + link "Read the dev docs →".
Grid: 6 columns, gap 16px. .big = 1/-1 (frame height clamp(440px,66vh,640px)); .half = span 3 (aspect 16/10); .third = span 2 (aspect 4/3). Caption under each frame: margin-top 12px, h5 500 14.5px, p 13px muted max 520px. ≤860px all cards full width.
Shared panel .kp: white, radius 4px, padding 14px, 11px, panel shadow, flex column, gap 10px. Header .kp-h: title (600 13px; file names in mono 500 12px) + muted subtitle + chip on the right (padding 2px 8px, radius 4px, 10.5px 500, dot 6px). Code/terminal blocks .kp-pre: padding 12px, radius 4px, bg rgba(60,30,10,.035), mono 11px, line-height 1.7, #3b2c22, pre-wrap; syntax .s #b4531c, .p #2f5bd3, .n #17803f, .c #a99a8e, .k #c4622d, .g #17803f, .m #a39486.
BIG "Agent-native console" / "Describe a playbook in plain language; your agent writes, tests and deploys it against a sandbox replica of your fleet.":
.kp-duo absolute inset 7% 5%, grid 1.25fr 1fr, gap 14px (≤860px: static, one column, the frame gets padding 16px 12px, agent panel 300px tall, console sidebar hidden).
Console panel (row layout, no padding): sidebar 132px (#f6f4f1, padding 14px 8px 14px 0; logo "Console"; items Overview (selected, same edge-bar style as the dashboard), Sites, Batteries, Bids, Playbooks, Logs at 11.5px); main (padding 14px): header "Tarifa Wind & Solar" · "Iberia" · green "● Live"; 3 tinted stat tiles (Output now 64.2 MW · Availability 99.3% · Today €41.7k; label 10px muted, value 600 14px); table with alternating tinted rows (rounded ends): Inverter block A Healthy 21.4 MW · Inverter block B Healthy 19.8 MW · Tracker row 14 Stowed(warn) 0.0 MW · Turbine T-07 Healthy 23.0 MW.
Agent panel: header "Agent session" · "storm_reserve.ts" · warm "Sandbox"; .kp-pre (flex 1) types this script (lines with class k/g appear whole; others type 2 chars per 22ms; pause 520ms after k/g lines, 700ms after others; a blank line after lines 2 and 7; blinking 7×13px orange caret; restart 3.8s after the end):
[m] "› helion agent" · [] "I want batteries in Iberia to hold 20% reserve whenever a storm warning is active." · [k] "● Reading fleet config… 38 sites, 412 MWh storage" · [k] "● Writing playbook storm_reserve.ts" · [k] "● Replaying against sandbox: Jan 2026 storm" · [g] "✓ Reserve held at 20.4% across all packs" · [g] "✓ Revenue impact: −€1,140 vs. +€9,800 avoided penalties" · [] "Ready to deploy to iberia-staging. Run `helion deploy storm_reserve`?"
then a prompt bar (padding 7px 7px 7px 11px, radius 4px, tinted, muted) "Ask your agent to change the playbook…" + a 24px round ink send button.
HALF "Real-time grid events" / "React to frequency dips, price spikes and curtailment orders within milliseconds.": centred panel (84% wide): header "events.ts" (mono) · "Grid events" · green chip "● Event received · 14 ms" that re-pops every 3s (scale .85→1, .45s bouncy) with a random 8–21 ms; code:
// subscribe to grid events
helion.events.on("frequency.dip", {
region: "ES-AND",
threshold: 49.85,
action: "discharge.fast"
})
HALF "Bulk operations from the CLI" / "Stage and submit hundreds of bids at once, with dry-runs and automatic rollback.": header "Terminal" (mono) · "helion-cli" · warm pulsing "● Running"; code:
$ helion bids bulk --market intraday
--sites "tag:iberia" --strategy peak-shave
✓ 38 sites validated
✓ 214 bids staged
then a row "Submitting bids" (muted) + bold % on the right + a 4px bar (tinted track, orange fill); it climbs 4–11% every 260ms, shows "done ✓" at 100%, then restarts.
THIRD "Every environment in one place" / "Sandbox, staging and production fleets, side by side.": NO panel. A centred column (80% wide) of 3 separate white rows (padding 8px, radius 4px, 500 12px, shadow 0 16px 30px -18px rgba(30,8,0,.65)) joined by the same flowing dashed connector (14px): 24px gradient square + name/sub + badge: sandbox-grid "Replay · Jan 2026 storm" Dev (gradient #a9c8ff→#4a6fb8) · iberia-staging "38 sites mirrored" Staging (#7ee2a8→#2f8a5c) · iberia-prod "412 MW live" Prod (#ffd08a→#c4622d).
THIRD "Playbook logs" / "Trace every run, input and market response.": panel header "Playbook runs" · "Last 24h" · green "48 ok"; a 58px tinted bar area with 24 orange bars (every 3rd at 55% opacity) whose heights re-randomise every 1.8s (.8s ease); two mono log rows (alternating tint): "peak_shave · 2m ago · ok", "storm_stow · 14m ago · ok" (ok in green).
THIRD "Localized console" / "Operate in 14 languages, with local units and market rules.": panel header "Status" · "14 languages"; a tinted row with a 28px green check circle + title/subtitle that cycles every 2.6s (fade/slide out .35s, swap, back in): "Grid healthy / All 38 sites reporting" → "Netz stabil / Alle 38 Standorte melden" → "Réseau stable / Les 38 sites répondent" → "電力網は正常 / 38拠点すべて稼働中" → "Red estable / Los 38 sitios reportan"; below, chips EN DE FR JA ES (padding 3px 8px, radius 4px, tinted, muted) where the active language goes ink with white text, in sync.
FEATURE GRID: 3 columns (gap 0 28px, margin-top clamp(40px,7vh,72px)); each item padding 18px 0 22px, border-top 1px var(--line), h6 500 14px, p 13px muted max 340px: Typed SDKs / "TypeScript, Python and Go clients generated from one schema, versioned together." · Sandbox grids / "Replay any historic day against your playbooks before they touch real hardware." · Webhook retries / "Exponential backoff and dead-letter queues for every event subscription." · Market adapters / "OMIE, EPEX, Nord Pool and ERCOT connectors with unified bid formats." · Role-based guardrails / "Cap exposure per trader, per site, per agent — enforced at the API." · Carbon ledger / "Hourly-matched certificates exported straight to your ESG reports."
No closing CTA and no footer.
══════════════════════════════════════
10. MOTION, RESPONSIVE & LOOP
══════════════════════════════════════
Reveal: .rv{opacity:0;transform:translateY(28px);transition:opacity 1s,transform 1s cubic-bezier(.2,.7,.2,1)} .rv.in{opacity:1;transform:none}; delays .d1 .08s, .d2 .16s, .d3 .24s; an IntersectionObserver (threshold .18, rootMargin 0 0 −5% 0) adds .in once and starts [data-count] counters (ease-out quartic over 1.6s; data-dec, data-prefix, data-suffix; en-US grouping). Apply .rv to headings, captions, notes and every .frame.
≤1100px: dashboard grid 172px | 1fr (right panel hidden).
≤860px: section heads stack (gap 14px, side left-aligned); all grids one column; dashboard frame height auto with padding 26px 0; app width 91%, height auto, one column, sidebar hidden, hero padding 22px 16px 20px, greeting 22px, stat line wraps centred; prompt margin 0 12px 12px; Build big frame as above; half/third frames aspect auto with min-height 260px.
≤480px: body 14px; hide the third prompt chip.
Main loop (requestAnimationFrame): dt=min(.05,Δt); update the mouse smoothing, trail, ring, warp (lerp .09), vp, window rects and section cut lines; draw the background, then the particles. Passive scroll listener for heroP, the pill label and visibility. Debounced 120ms resize. Respect prefers-reduced-motion (no ring spin, near-zero CSS animation durations). Use unique JS variable names (no duplicate `const` in the one script).[/hidecontent]