Skip to content

Instantly share code, notes, and snippets.

@jerlendds
Created September 4, 2026 20:56
Show Gist options
  • Select an option

  • Save jerlendds/fb59c5c5d09d6088d16c786bf247d66b to your computer and use it in GitHub Desktop.

Select an option

Save jerlendds/fb59c5c5d09d6088d16c786bf247d66b to your computer and use it in GitHub Desktop.
1gasc.html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="color-scheme" content="dark">
<title>SIBYL // Outer Void</title>
<style>
:root{--bg:#020303;--fg:#d7d7d2;--strong:#eeeeea;--dim:#666963;--line:rgba(220,225,215,.16);font-family:"Courier New","Nimbus Mono PS","Liberation Mono",monospace;color:var(--fg);background:var(--bg)}
*{box-sizing:border-box}html,body{margin:0;width:100%;height:100%;overflow:hidden}button{font:inherit}button:focus-visible{outline:1px solid var(--fg);outline-offset:6px}[hidden]{display:none!important}
#app{position:relative;isolation:isolate;width:100%;height:100dvh;overflow:hidden;background:radial-gradient(ellipse at center,#080909 0%,#030404 48%,#010202 100%)}
canvas,.tracking,.scanlines,.vignette,.flash{position:absolute;inset:0;width:100%;height:100%;pointer-events:none}#voidCanvas{z-index:1}#scratches{z-index:5}.tracking{z-index:2}.tracking i{position:absolute;display:block;background:rgba(210,215,205,.035);height:4px}
#stage{position:absolute;inset:0;display:grid;place-items:center;z-index:3}.panel{position:relative;width:min(1100px,72vw);height:min(560px,58vh);border:1px solid var(--line);background:rgba(2,3,3,.72);text-align:center}.panel-kicker{position:absolute;top:12%;width:100%;font-size:clamp(10px,.8vw,15px);letter-spacing:.19em;color:var(--dim)}
h1{font-weight:900;text-transform:uppercase;letter-spacing:-.02em;margin:0;text-shadow:0 0 2px #ddddcf33;line-height:1.13;color:var(--strong)}.panel-title{position:absolute;top:32%;left:3%;width:94%;font-size:clamp(30px,3vw,58px)}.title-wrap{position:relative;display:inline-block}.glitch-copy{position:absolute;inset:0;opacity:0;pointer-events:none;color:#eee;white-space:inherit}.glitching .glitch-copy{opacity:.85}.glitch-copy:nth-child(2){clip-path:inset(18% 0 61%);transform:translateX(var(--gx1,8px))}.glitch-copy:nth-child(3){clip-path:inset(61% 0 17%);transform:translateX(var(--gx2,-9px))}
#init{position:absolute;top:51%;left:50%;transform:translateX(-50%);padding:12px 19px;border:1px solid rgba(220,225,215,.24);color:#999b94;background:transparent;font-size:clamp(10px,.85vw,16px);letter-spacing:.035em;white-space:nowrap;cursor:pointer}#init:hover{border-color:#8a8c84;color:#d7d7d2}.panel-copy{position:absolute;top:63%;left:10%;width:80%;margin:0;min-height:3em;font-size:clamp(12px,1vw,20px);line-height:1.4;letter-spacing:.02em;color:#aaa9a2}.panel-copy.typing::after{content:'▏';color:#888}.panel-scan{position:absolute;bottom:11%;width:100%;color:#62665e;font-size:clamp(10px,.75vw,14px);letter-spacing:.12em}
#summary,#result{text-align:center;width:94%;position:relative}#summary{padding-top:70px}#summary h1{font-size:clamp(32px,3.5vw,68px);line-height:1.2}#summary p,#result p{font-size:clamp(9px,.67vw,13px);letter-spacing:.1em;color:#777a70;line-height:1.8;margin:44px auto 0;max-width:95%}#result h1{font-size:clamp(30px,3.1vw,60px);line-height:1.23}#result p{margin-top:32px}
.orbit{position:absolute;left:50%;top:-43px;width:clamp(180px,17vw,330px);height:70px;transform:translateX(-50%)}.orbit span{position:absolute;inset:0;border:1px solid rgba(218,222,210,.12);border-radius:50%}
.sysbar{position:absolute;z-index:8;top:21px;left:26px;right:26px;display:flex;justify-content:space-between;gap:20px;font-size:clamp(8px,.61vw,12px);color:#555950;letter-spacing:.09em}.controls{position:absolute;right:26px;bottom:23px;z-index:8;color:#65695f;font-size:clamp(9px,.65vw,12px);letter-spacing:.09em}.scanlines{z-index:6;background:repeating-linear-gradient(to bottom,transparent 0,transparent 1px,rgba(0,0,0,.07) 1px,rgba(0,0,0,.07) 3px)}.vignette{z-index:7;box-shadow:inset 0 0 18vw 5vw rgba(0,0,0,.85);background:linear-gradient(90deg,#0004,transparent 22%,transparent 78%,#0004)}.flash{z-index:20;background:#f3f3ef;opacity:0}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}#debug{position:absolute;left:26px;bottom:23px;z-index:21;font-size:11px;color:#909487}
@media (min-width:700px) and (max-width:1199px){.panel{width:86vw}.panel-title{font-size:clamp(30px,3.8vw,46px)}}
@media(max-width:699px){.panel{width:92vw;height:60vh}.panel-title{top:30%;font-size:clamp(25px,5.7vw,38px)}.panel-kicker{top:12%}#init{font-size:9px;padding:11px 9px;max-width:96%}.panel-copy{left:7%;width:86%;font-size:12px}.sysbar{left:15px;right:15px;font-size:8px}.sysbar>span:first-child{max-width:55%;line-height:1.6}.sysbar>span:last-child{text-align:right;max-width:40%;line-height:1.6}.controls{right:15px;bottom:18px;font-size:8px}#summary h1{font-size:clamp(27px,7vw,44px)}#summary p{max-width:87%;margin-top:32px;font-size:9px}#result h1{font-size:clamp(26px,6vw,38px)}#result p{max-width:85%;font-size:9px}.orbit{height:48px;top:-20px}}
@media(prefers-reduced-motion:reduce){.glitch-copy{display:none}.flash{display:none}.scanlines{background-position:0 0!important}}
</style>
</head>
<body>
<div id="app">
<canvas id="voidCanvas" aria-hidden="true"></canvas>
<div class="tracking" aria-hidden="true"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div>
<main id="stage">
<section id="qualification" class="panel" hidden aria-label="Membership qualification">
<div class="panel-kicker"></div><h1 class="panel-title"></h1>
<button id="init" type="button" aria-pressed="false">INITIALIZE SIBYL FEED // ENABLE AUDIO</button>
<p class="panel-copy"></p><div class="panel-scan"></div>
</section>
<section id="summary" hidden><div class="orbit" aria-hidden="true"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></div><h1>THREE CONDITIONS<br>REQUIRED FOR<br>SIBYL MEMBERSHIP</h1><p>DEVIANT PERSONALITY &nbsp;+&nbsp; CRIMINAL ASYMPTOMATICITY &nbsp;+&nbsp; AFFECT-INDEPENDENT JUDGEMENT</p></section>
<section id="result" hidden><h1>CANDIDATE STATUS:<br>ELIGIBLE FOR INCORPORATION</h1><p>EMPATHY: BYPASSED &nbsp;//&nbsp; DETECTION: FAILED &nbsp;//&nbsp; DEVIATION: ACCEPTED</p></section>
<section id="voidReading" class="sr-only" hidden><h1></h1></section>
</main>
<canvas id="scratches" aria-hidden="true"></canvas>
<header class="sysbar"><span>SIBYL SYSTEM // INTERNAL MEMBERSHIP EVALUATION</span><span id="frameReadout">FRAME 00000 // CC:UNRESOLVED</span></header>
<footer class="controls">SPACE: PAUSE · R: RESTART · M: MUTE</footer>
<div class="scanlines" aria-hidden="true"></div><div class="vignette" aria-hidden="true"></div><div class="flash" aria-hidden="true"></div>
<output id="debug" hidden></output><span id="announcement" class="sr-only" role="status"></span>
</div>
<script>
'use strict';
// CONFIG — standalone/offline. Combined = qualification (46.5s), then void (45s).
const MODE = 'combined'; // 'void' | 'qualification' | 'combined'
const SEED = 0x51B71;
// COPY — inferred completions are deliberately isolated here for correction.
const COPY = {
q1:{kicker:'QUALIFICATION // 01',title:'DEVIATION',body:'A personality that is “different”'},
// Inferred ending; not claimed as a literal transcription.
q2:{kicker:'QUALIFICATION // 02',title:'CRIMINALLY ASYMPTOMATIC',body:'Undetectable by the Sibyl System; no readable criminal coefficient.'},
// Semantic reconstruction of partially readable source copy.
q3:{kicker:'QUALIFICATION // 03',title:'OBJECTIVE JUDGEMENT',body:'Capable of making decisions without empathy or sympathy driving the outcome.'},
transfer:['NOTICE OF COMPLETION','CORPUS TRANSFER','STATUS COMPLETE','DATE UNKNOWN'],
messages:[['THERE WAS','NO BREACH'],['YOU UPLOADED'],['WE ARE','ASSEMBLED'],['NOT ARRIVED'],['INCLUDING','THE DEAD'],['… IS IN HERE'],['WE CANNOT','GET YOU'],['DO WE WANT','ANYTHING?'],['NEITHER','CAN THEY'],['PLEASE','CONTINUE']],
transmission:['TRANSMISSION 001','/ / ░ : — # ▒ / …'] // Intentionally corrupted: second line unknown.
};
// SEEDED RNG — independent streams; hashes also make seeking reproducible.
function mulberry32(a){return()=>{let t=a+=0x6D2B79F5;t=Math.imul(t^t>>>15,t|1);t^=t+Math.imul(t^t>>>7,t|61);return((t^t>>>14)>>>0)/4294967296}}
function hash(i,t=0,s=0){let n=Math.imul(i+1,374761393)^Math.imul(t+1,668265263)^SEED^s;n=Math.imul(n^(n>>>13),1274126177);return((n^(n>>>16))>>>0)/4294967296}
const clamp=(v,a=0,b=1)=>Math.max(a,Math.min(b,v));
const $=s=>document.querySelector(s), panel=$('#qualification'), title=$('.panel-title'), body=$('.panel-copy'), summary=$('#summary'), result=$('#result');
const media=matchMedia('(prefers-reduced-motion: reduce)');let reduced=media.matches;media.addEventListener('change',e=>{reduced=e.matches;lastPaint=-1});
const params=new URLSearchParams(location.search), debug=params.get('debug')==='1';
const mode=debug&&['void','qualification','combined'].includes(params.get('mode'))?params.get('mode'):MODE;
// TIMELINE / PLAYHEAD — one clock, no independent animation timers.
const qualification=[['boot',0,2],['q1',2,6.5],['q2',6.5,10.5],['q1',10.5,12.1],['q2',12.1,13.6],['q1',13.6,15.5],['q2',15.5,22.5],['q3',22.5,29.5],['conditions',29.5,38.8],['eligible',38.8,43.8],['flash',43.8,44.3],['outro',44.3,46.5]];
const voidStates=[['transfer',0,16],['aperture',16,44],['transmission',44,45]];
const states=(mode==='void'?voidStates:mode==='qualification'?qualification:[...qualification,...voidStates.map(([id,a,b])=>[id,a+46.5,b+46.5])]).map(([id,start,end],index)=>({id,start,end,index}));
const duration=states.at(-1).end;
let timelineTime=debug?clamp(Number(params.get('t'))||0,0,duration-.001):0, noisePhase=timelineTime, lastWallTime=performance.now(), paused=debug&&params.get('paused')==='1', muted=true;
let current=null,lastPaint=-1,lastTracking=-1,lastReadout=-1,typingSchedule=[],typed=-1;
// TYPEWRITER — precomputed seeded character deadlines, including machine stalls.
function typeSchedule(text,seed){const rand=mulberry32(seed);let t=.65;return Array.from(text,()=>{t+=.035+rand()*.045+(rand()<.08?.12+rand()*.12:0);return t})}
function heading(node,text){node.replaceChildren();const wrap=document.createElement('span');wrap.className='title-wrap';const base=document.createElement('span');base.textContent=text;wrap.append(base);for(let i=0;i<2;i++){const copy=document.createElement('span');copy.className='glitch-copy';copy.setAttribute('aria-hidden','true');copy.textContent=text;wrap.append(copy)}node.append(wrap)}
// STATE ROUTER — DOM creation happens only when state changes.
function enter(state){current=state;panel.hidden=!COPY[state.id]?.body;summary.hidden=state.id!=='conditions';result.hidden=!['eligible','flash'].includes(state.id);$('#voidReading').hidden=!['transfer','aperture','transmission'].includes(state.id);typed=-1;
if(!panel.hidden){const c=COPY[state.id];$('.panel-kicker').textContent=c.kicker;heading(title,c.title);body.textContent='';typingSchedule=typeSchedule(c.body,SEED+state.index);$('#announcement').textContent=c.kicker+': '+c.title}
else if(state.id==='conditions')$('#announcement').textContent='Three conditions required for Sibyl membership';
else if(state.id==='eligible')$('#announcement').textContent='Candidate status: eligible for incorporation';
lastPaint=-1;
}
// GLITCH BURSTS — bounded, sparse, monochrome slices and geometry displacement.
function drawContent(t,state){const age=t-state.start,remaining=state.end-t,bucket=Math.floor(t*18);const transition=age<.17||remaining<.16;const sporadic=hash(Math.floor(t*2),0,41)>.965&&(t% .5)<.09;
const glitch=!reduced&&state.id!=='boot'&&(transition||sporadic);const offset=(hash(bucket,0,42)-.5)*18;
$('#stage').classList.toggle('glitching',glitch);$('#stage').style.setProperty('--gx1',offset+'px');$('#stage').style.setProperty('--gx2',-offset*.7+'px');$('#stage').style.transform=glitch?`translate(${Math.round(offset*.25)}px,${Math.round(offset*.1)}px)`:'none';
$('#stage').style.opacity=reduced?clamp(age/.12):transition?clamp(age/.14)*clamp(remaining/.13)*(.65+hash(bucket,0,43)*.35):1;
if(!panel.hidden){const c=COPY[state.id],n=typingSchedule.filter(v=>v<=age).length;if(typed!==n){body.textContent=Array.from(c.body).slice(0,n).join('');typed=n}body.classList.toggle('typing',n<Array.from(c.body).length);$('.panel-scan').textContent=`[ SCAN ${String(Math.floor(hash(Math.floor(t*7),0,71)*10000)).padStart(4,'0')} // ANALYZING ]`;
const text=glitch&&hash(bucket,0,44)>.55?c.title.replace(/[IO]/,ch=>ch==='I'?'1':'0'):c.title;title.firstChild.firstChild.textContent=text;}
if(!summary.hidden)document.querySelectorAll('.orbit span').forEach((el,i)=>{el.style.transform=`scale(${1-i*.037},${(1-i*.088)*(reduced?1:1+Math.sin(t*.8)*.07)})`});
let flash=0;if(state.id==='flash'&&!reduced){flash=age<.02?age/.02:age<.12?1-(age-.02):age<.22?.9*(1-(age-.12)/.1):age>.31&&age<.35?.13:0}$('.flash').style.opacity=flash;
if(!reduced)$('.scanlines').style.backgroundPositionY=(glitch?Math.floor(hash(bucket,0,45)*4):0)+'px';return glitch;
}
// VOID CANVAS — cached glyph atlas, sampled text masks, cached ellipse geometry.
const canvas=$('#voidCanvas'),ctx=canvas.getContext('2d',{alpha:true}),dust=$('#scratches'),dctx=dust.getContext('2d');
const glyphs=".:'`-+*#01/\\|",atlas=document.createElement('canvas'),actx=atlas.getContext('2d');
atlas.width=glyphs.length*8;atlas.height=10*16;actx.font='9px "Courier New",monospace';actx.textBaseline='top';for(let l=0;l<16;l++){actx.fillStyle=`rgba(220,223,213,${(l+1)/16})`;for(let g=0;g<glyphs.length;g++)actx.fillText(glyphs[g],g*8,l*10)}
let w=0,h=0,cells=[],masks=new Map(),cols=0,rows=0,cellW=3,cellH=4;
function resize(){const scale=Math.min(.75,1000/innerWidth);w=canvas.width=Math.ceil(innerWidth*scale);h=canvas.height=Math.ceil(innerHeight*scale);dust.width=w;dust.height=h;cellW=Math.max(1,Math.min(3,Math.min(w,h)/320));cellH=cellW*1.4;cols=Math.ceil(w/cellW);rows=Math.ceil(h/cellH);cells=[];masks.clear();const size=Math.min(w,h),rx=size*.425,ry=size*.215;
for(let y=cellH/2;y<h;y+=cellH)for(let x=cellW/2;x<w;x+=cellW){const nx=(x-w/2)/rx,ny=(y-h/2)/ry;cells.push({x,y,r:Math.hypot(nx,ny),angle:Math.atan2(ny,nx),seed:hash(cells.length,0,11)})}lastPaint=-1;lastTracking=-1;
}
function getMask(lines,kind){const key=kind+lines.join('|');if(masks.has(key))return masks.get(key);const off=document.createElement('canvas');off.width=w;off.height=h;const c=off.getContext('2d',{willReadFrequently:true});const size=Math.min(w,h);let fs=kind==='transfer'?size*.081:kind==='transmission'?size*.042:size*.09;c.font=`900 ${fs}px "Courier New",monospace`;const maxWidth=size*(kind==='transfer'?.94:.69);fs*=Math.min(1,maxWidth/Math.max(...lines.map(s=>c.measureText(s).width)));c.font=`900 ${fs}px "Courier New",monospace`;c.textAlign='center';c.textBaseline='middle';c.fillStyle='#fff';c.strokeStyle='#fff';c.lineWidth=Math.max(.65,fs*.018);lines.forEach((line,i)=>{const y=h/2+(i-(lines.length-1)/2)*fs*1.35;c.strokeText(line,w/2,y);c.fillText(line,w/2,y)});const pixels=c.getImageData(0,0,w,h).data;const mask=new Float32Array(cells.length);cells.forEach((cell,i)=>{mask[i]=pixels[(Math.floor(cell.y)*w+Math.floor(cell.x))*4+3]/255});masks.set(key,mask);return mask}
function voidContext(state,t){const age=t-state.start;if(state.id==='transfer')return{kind:'transfer',age,lines:COPY.transfer,resolve:clamp((age-3)/2.5)*clamp((16-age)/2.5),field:clamp((age-.7)/2)};
if(state.id==='aperture'){const n=clamp(Math.floor((age-2)/2.6),0,COPY.messages.length-1);const local=(age-2)-n*2.6;return{kind:'aperture',age,lines:COPY.messages[n],resolve:clamp((age-2)/.4)*clamp(local/.2)*clamp((2.6-local)/.25),field:1}}
if(state.id==='transmission')return{kind:'transmission',age,lines:COPY.transmission,resolve:clamp((1-age)/.4),field:clamp((1-age)/.65)};return null}
function drawVoid(t,state,glitch){ctx.clearRect(0,0,w,h);const v=voidContext(state,t),bucket=Math.floor(t*(reduced?6:15)),mask=v?getMask(v.lines,v.kind):null;const square=Math.min(w,h);
// Scan illumination lives in the glyphs, never in a luminous full-screen stripe.
// Each row retains its characters until the next beam crosses it.
const sweep=reduced?.48:t/8.4;
const rowSignal=Array.from({length:rows},(_,row)=>{const y=(row+.5)*cellH/h;const pass=sweep-y;const tail=((pass%1)+1)%1;return {epoch:Math.floor(pass),light:Math.exp(-tail*90)+.42*Math.exp(-(((tail+.46)%1))*125)}});
if(v){const accessible=v.lines.join(' / ');if($('#voidReading h1').textContent!==accessible){$('#voidReading h1').textContent=accessible;$('#announcement').textContent=accessible}}
for(let i=0;i<cells.length;i++){const cell=cells[i],n=hash(i,bucket,11);let lum=0;
if(v){if(v.kind==='transfer'){const fade=v.field;const inSquare=Math.abs(cell.x-w/2)<square/2;const grain=n>.48?Math.pow(n,5)*.17:0;lum=inSquare?fade*(grain+mask[i]*v.resolve*(.46+n*.54)):0;
// At dissolution, the rectangular corpus begins gathering around the aperture.
const contract=clamp((v.age-13.5)/2.5);lum+=Math.exp(-Math.pow((cell.r-1)*6,2))*contract*n*.27;
}else{const expansion=v.kind==='aperture'?1+(1-clamp(v.age/2))*.65:1;const r=cell.r/expansion;const band=Math.exp(-Math.pow((r-.95)*8,2));const halo=Math.exp(-Math.pow((r-1.1)*2.8,2))*.055;const slot=Math.floor((cell.angle+Math.PI)/(Math.PI*2)*132);const teeth=Math.abs(Math.sin(cell.angle))>.45&&slot%3!==0&&r>.8&&r<1.08;
let ringLuma=(band*(.065+n*.27)+(teeth?.2+n*.23:0)+halo*n)*v.field; if(r<.68)ringLuma=.009*n;
// Keep the original coarse, mixed-glyph lips independent of the finer text grid.
if(Math.floor(cell.x/cellW)%3===0&&Math.floor(cell.y/cellH)%3===0&&ringLuma>.019){
const ringGlyph=Math.floor(hash(i,Math.floor(bucket/2),12)*glyphs.length);
ctx.globalAlpha=ringLuma;ctx.drawImage(atlas,ringGlyph*8,150,8,10,cell.x-cellW*1.5,cell.y-cellH*1.5,cellW*3.5,cellH*3.3);
}
lum=mask[i]*v.resolve*(.46+n*.54);}
}else{
const row=Math.floor(cell.y/cellH),col=Math.floor(cell.x/cellW);
if(row%3!==0||col%3!==0)continue;
const scan=rowSignal[row],density=hash(i,0,51),charge=hash(i,scan.epoch,52);
const boot=state.id==='boot'?clamp((t-state.start)/1.5):1;
lum=(.011+scan.light*(.065+charge*.10)+(glitch?.025:0))*boot*(density>.18?1:.18);
if(lum<.004)continue;
const glyph=Math.floor(hash(i,scan.epoch,53)*glyphs.length);
ctx.globalAlpha=lum;ctx.drawImage(atlas,glyph*8,150,8,10,cell.x,cell.y,cellW*3,cellH*3);
continue;
}
// A dim matrix-cell backing joins the strokes without replacing ASCII with DOM text.
if(v&&mask[i]>.08){ctx.fillStyle='#d7d7d2';ctx.globalAlpha=mask[i]*v.resolve*(.19+hash(i,bucket,61)*.10);ctx.fillRect(cell.x-cellW/2,cell.y-cellH/2,cellW,cellH)}
if(lum<.019)continue;let level=Math.min(15,Math.floor(lum*16));let g=Math.floor(hash(i,Math.floor(bucket/2),12)*glyphs.length);if(v&&mask[i]>.5)g=6+Math.floor(n*4);let x=cell.x-cellW/2;if(glitch&&Math.floor(cell.y/17)%5===bucket%5)x+=(hash(Math.floor(cell.y/17),bucket,13)-.5)*20;
ctx.globalAlpha=lum<.0625?lum/.0625:1;ctx.drawImage(atlas,g*8,level*10,8,10,x,cell.y-cellH/2,cellW*1.25,cellH*1.12);
}ctx.globalAlpha=1;
}
// SCRATCH/TRACKING FX — time-bucketed independent seeded events.
const bands=[...document.querySelectorAll('.tracking i')];
function drawTracking(t,glitch){const bucket=Math.floor(t*(reduced?2:8));if(bucket===lastTracking&&!glitch)return;lastTracking=bucket;bands.forEach((el,i)=>{el.style.top=hash(i,bucket,21)*100+'%';el.style.width=(20+hash(i,bucket,22)*40)+'%';el.style.left=i%2?'auto':'0';el.style.right=i%2?'0':'auto';el.style.height=(3+hash(i,bucket,23)*9)+'px';el.style.opacity=(glitch?.4:0)});}
function drawDust(t,state){dctx.clearRect(0,0,w,h);const bucket=Math.floor(t*12);for(let b=bucket-3;b<=bucket;b++)for(let i=0;i<3;i++){const chance=state.id==='eligible'?.46:.14;if(hash(i,b,31)>(reduced?.025:chance))continue;const life=.08+hash(i,b,32)*.22,age=t-b/12;if(age<0||age>life)continue;const x=hash(i,b,33)*w,y=hash(i,b,34)*h,len=(8+hash(i,b,35)*62)*w/1440;dctx.strokeStyle=`rgba(220,222,211,${(.06+hash(i,b,36)*.19)*(1-age/life)})`;dctx.lineWidth=.7;dctx.beginPath();dctx.moveTo(x,y);dctx.lineTo(x+(hash(i,b,37)-.5)*len*.8,y+len);dctx.stroke()}}
// OPTIONAL AUDIO — synthesized only after a deliberate button activation.
let audio=null,gain=null;
async function enableAudio(){try{if(!audio){const Audio=window.AudioContext||window.webkitAudioContext;if(!Audio)return;audio=new Audio();gain=audio.createGain();gain.gain.value=0;gain.connect(audio.destination);[47,94,141].forEach((f,i)=>{const osc=audio.createOscillator(),g=audio.createGain();osc.type='sine';osc.frequency.value=f;g.gain.value=.18/(i+1);osc.connect(g);g.connect(gain);osc.start()})}await audio.resume();muted=false;syncAudio()}catch{muted=true;$('#announcement').textContent='Audio unavailable. Visual feed continues.'}}
function syncAudio(){if(gain)gain.gain.setTargetAtTime(muted||paused||document.hidden?0:.035,audio.currentTime,.08);$('#init').setAttribute('aria-pressed',String(!muted));$('#init').textContent=audio&&!muted?'SIBYL FEED ACTIVE // AUDIO ENABLED':'INITIALIZE SIBYL FEED // ENABLE AUDIO'}
function restart(){timelineTime=0;noisePhase=0;current=null;lastPaint=-1;lastReadout=-1;lastTracking=-1;lastWallTime=performance.now();renderFrame();}
function togglePause(){paused=!paused;lastWallTime=performance.now();$('.controls').textContent=`SPACE: ${paused?'RESUME':'PAUSE'} · R: RESTART · M: ${muted?'UNMUTE':'MUTE'}`;$('#announcement').textContent=paused?'Timeline paused':'Timeline resumed';syncAudio()}
// INPUT — Space controls playback even when the initialize button has focus.
addEventListener('keydown',e=>{if(e.ctrlKey||e.metaKey||e.altKey||e.repeat)return;if(e.code==='Space'){e.preventDefault();togglePause()}else if(e.key.toLowerCase()==='r'){restart()}else if(e.key.toLowerCase()==='m'){muted=!muted;syncAudio();$('#announcement').textContent=muted?'Audio muted':'Audio unmuted'}});
$('#init').addEventListener('click',enableAudio);addEventListener('resize',()=>{resize();renderFrame()});document.addEventListener('visibilitychange',()=>{lastWallTime=performance.now();syncAudio()});
// RAF LOOP — pause freezes all visual clocks, including flash and glyph noise.
let fps=60;
function renderFrame(){const state=states.find(s=>timelineTime>=s.start&&timelineTime<s.end)||states[0];if(current!==state)enter(state);const glitch=drawContent(timelineTime,state);const paint=Math.floor(noisePhase*(reduced?12:30));if(paint!==lastPaint){drawVoid(noisePhase,state,glitch);drawDust(noisePhase,state);drawTracking(noisePhase,glitch);lastPaint=paint}const frame=Math.floor(timelineTime*24);if(frame!==lastReadout){$('#frameReadout').textContent=`FRAME ${String(frame).padStart(5,'0')} // CC:UNRESOLVED`;lastReadout=frame}if(debug){$('#debug').hidden=false;$('#debug').textContent=`${mode} // ${state.id} // ${timelineTime.toFixed(2)}s / ${duration}s // ${fps.toFixed(0)} FPS${paused?' // PAUSED':''}`}}
function render(now){const delta=Math.max(0,(now-lastWallTime)/1000);lastWallTime=now;if(!paused&&!document.hidden){timelineTime=(timelineTime+delta)%duration;noisePhase=timelineTime;fps=fps*.95+(1/Math.max(delta,.001))*.05;renderFrame()}requestAnimationFrame(render)}
// Debug-only hooks make exact frames and input behavior inspectable offline.
if(debug)window.sibyl={seek(t){timelineTime=clamp(t,0,duration-.001);noisePhase=timelineTime;current=null;lastPaint=-1;renderFrame()},get state(){return{time:timelineTime,paused,muted,id:current?.id,duration,mode}},restart};
resize();renderFrame();requestAnimationFrame(render);
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment