Created
September 4, 2026 20:56
-
-
Save jerlendds/fb59c5c5d09d6088d16c786bf247d66b to your computer and use it in GitHub Desktop.
1gasc.html
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!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 + CRIMINAL ASYMPTOMATICITY + AFFECT-INDEPENDENT JUDGEMENT</p></section> | |
| <section id="result" hidden><h1>CANDIDATE STATUS:<br>ELIGIBLE FOR INCORPORATION</h1><p>EMPATHY: BYPASSED // DETECTION: FAILED // 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&¶ms.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