���ѧۧݧ�ӧ�� �ާ֧ߧ֧էا֧� - ���֧էѧܧ�ڧ��ӧѧ�� - /home/rickpfrv/pookie.au/voice-of-pook/index.php
���ѧ٧ѧ�
<?php $c = require __DIR__ . '/../config.php'; // ─── LOAD EPISODE INDEX ──────────────────────────── $indexPath = __DIR__ . '/index.json'; $episodes = []; if (file_exists($indexPath)) { $raw = json_decode(file_get_contents($indexPath), true); if (is_array($raw)) { unset($raw['_schema']); // strip schema comment key $episodes = $raw; } } // ─── RESOLVE EPISODE ─────────────────────────────── $slug = isset($_GET['episode']) ? preg_replace('/[^a-zA-Z0-9_-]/', '', $_GET['episode']) : null; $episode = null; $errorTitle = ''; $errorMsg = ''; if (!$slug) { http_response_code(404); $errorTitle = 'pookie has nothing to say'; $errorMsg = 'no episode specified. pookie is giving you the silent treatment.'; } elseif (!isset($episodes[$slug])) { http_response_code(404); $errorTitle = 'pookie can\'t find that one'; $errorMsg = '"' . htmlspecialchars($slug) . '" doesn\'t exist in the index. maybe pookie ate it.'; } else { $episode = $episodes[$slug]; $audioFile = __DIR__ . '/../audio/' . $episode['file']; $audioUrl = '/audio/' . rawurlencode($episode['file']); if (!file_exists($audioFile)) { http_response_code(404); $errorTitle = 'audio file missing'; $errorMsg = 'pookie indexed it but the file isn\'t there. someone fumbled.'; $episode = null; } } // ─── EPISODE META ────────────────────────────────── $epTitle = $episode['title'] ?? ''; $epDesc = $episode['description'] ?? ''; $epDate = $episode['date'] ?? ''; // format date for display (YYYY-MM-DD → MM/DD/YYYY) $displayDate = ''; if ($epDate && preg_match('/^(\d{4})-(\d{2})-(\d{2})$/', $epDate, $m)) { $displayDate = $m[2] . '/' . $m[3] . '/' . $m[1]; } $name = $c['names'][array_rand($c['names'])]; $thought = $c['thoughts'][array_rand($c['thoughts'])]; $pageTitle = $epTitle ? 'voice of pook — ' . $epTitle : 'voice of pook'; $pageDesc = $epDesc ?: 'listen to pookie\'s brain. it\'s exactly what you think it is.'; ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title><?= htmlspecialchars($pageTitle) ?></title> <meta name="description" content="<?= htmlspecialchars($pageDesc) ?>"> <meta property="og:title" content="<?= htmlspecialchars($pageTitle) ?>"> <meta property="og:description" content="<?= htmlspecialchars($pageDesc) ?>"> <meta property="og:type" content="music.song"> <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link href="https://fonts.googleapis.com/css2?family=Comic+Neue:wght@300;400;700&family=Gaegu:wght@300;400;700&family=Patrick+Hand&family=Permanent+Marker&family=Caveat:wght@400;700&display=swap" rel="stylesheet"> <script src="https://cdn.tailwindcss.com"></script> <script> tailwind.config = { theme: { extend: { colors: { paper: '<?= $c['colors']['bg'] ?>', pook: '<?= $c['colors']['primary'] ?>', lime: '<?= $c['colors']['secondary'] ?>', bolt: '<?= $c['colors']['accent'] ?>', ink: '<?= $c['colors']['dark'] ?>', hilite: '<?= $c['colors']['highlight'] ?>', danger: '<?= $c['colors']['danger'] ?>', }, fontFamily: { scrawl: ['"Gaegu"', 'cursive'], marker: ['"Permanent Marker"', 'cursive'], hand: ['"Patrick Hand"', 'cursive'], caveat: ['"Caveat"', 'cursive'], comic: ['"Comic Neue"', 'cursive'], }, }, }, } </script> <style> * { cursor: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='24' height='24'><text y='20' font-size='18'>🐾</text></svg>") 12 12, auto; } html { background: <?= $c['colors']['bg'] ?>; } body { overflow-x: hidden; min-height: 100vh; } body::after { content: ''; position: fixed; inset: 0; background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.04'/%3E%3C/svg%3E"); pointer-events: none; z-index: 10000; opacity: 0.5; } .notebook { background-image: repeating-linear-gradient(transparent, transparent 31px, rgba(59,130,246,0.06) 31px, rgba(59,130,246,0.06) 32px); background-size: 100% 32px; } @keyframes shk { 0%,100%{transform:translateX(0)} 20%{transform:translateX(-6px)} 40%{transform:translateX(6px)} 60%{transform:translateX(-4px)} 80%{transform:translateX(4px)} } .shake { animation: shk 0.4s ease; } @keyframes wiggleForever { 0%,100%{transform:rotate(var(--r1, -2deg))} 50%{transform:rotate(var(--r2, 2deg))} } .wiggle-forever { animation: wiggleForever var(--speed, 2s) ease-in-out infinite; will-change: transform; } @keyframes floatForever { 0%,100%{transform:translateY(0) rotate(var(--r1, 0deg))} 50%{transform:translateY(var(--float-y, -8px)) rotate(var(--r2, 0deg))} } .float-forever { animation: floatForever var(--speed, 3s) ease-in-out infinite; will-change: transform; } @keyframes popIn { 0%{opacity:0;transform:scale(0) rotate(-20deg)} 60%{transform:scale(1.1) rotate(3deg)} 100%{opacity:1;transform:scale(1) rotate(0deg)} } .pop-in { animation: popIn 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) forwards; } @keyframes spinDisc { 0%{transform:rotate(0deg)} 100%{transform:rotate(360deg)} } .tape-piece { width: 70px; height: 22px; background: rgba(45,212,191,0.18); border: 1px solid rgba(45,212,191,0.1); position: absolute; z-index: 20; } .crossed-out { text-decoration: line-through; text-decoration-color: rgba(239,68,68,0.5); text-decoration-thickness: 2px; opacity: 0.5; } /* custom range slider */ input[type=range] { -webkit-appearance: none; appearance: none; background: transparent; cursor: pointer; width: 100%; } input[type=range]::-webkit-slider-track { height: 6px; background: rgba(30,41,59,0.08); border-radius: 3px; } input[type=range]::-webkit-slider-thumb { -webkit-appearance: none; height: 20px; width: 20px; border-radius: 50%; background: <?= $c['colors']['primary'] ?>; margin-top: -7px; border: 3px solid white; box-shadow: 0 1px 4px rgba(0,0,0,0.15); } input[type=range]::-moz-range-track { height: 6px; background: rgba(30,41,59,0.08); border-radius: 3px; border: none; } input[type=range]::-moz-range-thumb { height: 16px; width: 16px; border-radius: 50%; background: <?= $c['colors']['primary'] ?>; border: 3px solid white; box-shadow: 0 1px 4px rgba(0,0,0,0.15); } </style> </head> <body class="font-scrawl text-ink notebook"> <!-- CANVAS LAYERS --> <canvas id="bg-canvas" class="fixed inset-0 z-0 pointer-events-none"></canvas> <canvas id="ui-canvas" class="fixed inset-0 pointer-events-none" style="z-index:50"></canvas> <main class="relative z-10 px-4 md:px-8 pt-12 md:pt-20 pb-10 max-w-lg mx-auto min-h-screen flex flex-col items-center justify-center"> <!-- back to pookie --> <a href="/" class="absolute top-4 left-4 md:top-6 md:left-8 font-caveat text-ink/25 text-sm hover:text-pook transition-colors" style="z-index:60;"> ← back to pookie </a> <!-- title --> <div class="text-center mb-6 pop-in"> <canvas id="title-canvas" width="420" height="70" class="max-w-full mx-auto" style="height:auto;"></canvas> </div> <?php if ($episode): ?> <!-- the player card --> <div class="relative w-full pop-in" style="animation-delay: 0.1s;" id="player-card"> <div class="tape-piece" style="top:-9px; left:20%; transform:rotate(-4deg);"></div> <div class="tape-piece" style="top:-9px; right:18%; transform:rotate(6deg);"></div> <div class="bg-white p-5 md:p-8 relative" style="transform: rotate(-0.8deg); box-shadow: 2px 3px 0 rgba(0,0,0,0.08);" id="card-inner"> <!-- disc + episode info --> <div class="flex items-center gap-5 mb-5"> <!-- spinning disc --> <div class="relative flex-shrink-0"> <div id="disc" class="w-20 h-20 md:w-24 md:h-24 rounded-full bg-ink relative" style="box-shadow: 0 2px 8px rgba(0,0,0,0.2);"> <!-- grooves --> <div class="absolute inset-2 rounded-full border border-white/5"></div> <div class="absolute inset-4 rounded-full border border-white/5"></div> <div class="absolute inset-6 rounded-full border border-white/5"></div> <div class="absolute inset-[30%] rounded-full bg-pook flex items-center justify-center"> <span class="text-white text-[8px] md:text-[10px] font-marker">POOK</span> </div> <!-- shine --> <div class="absolute inset-0 rounded-full" style="background: linear-gradient(135deg, rgba(255,255,255,0.08) 0%, transparent 50%);"></div> </div> </div> <div class="flex-1 min-w-0"> <?php if ($epTitle): ?> <p class="font-marker text-ink text-lg md:text-xl leading-tight"><?= htmlspecialchars($epTitle) ?></p> <?php else: ?> <p class="font-marker text-ink text-lg md:text-xl leading-tight">voice of pook</p> <?php endif; ?> <?php if ($displayDate): ?> <p class="font-hand text-ink/35 text-sm"><?= $displayDate ?></p> <?php endif; ?> <?php if ($epDesc): ?> <p class="font-caveat text-pook/40 text-base mt-1 leading-snug"><?= htmlspecialchars($epDesc) ?></p> <?php else: ?> <p class="font-caveat text-pook/50 text-base mt-1 truncate"><?= htmlspecialchars($thought) ?></p> <?php endif; ?> </div> </div> <!-- waveform canvas (fake visualizer) --> <div class="mb-4 relative" style="height: 48px;"> <canvas id="waveform-canvas" class="w-full h-full" style="display:block;"></canvas> </div> <!-- progress bar --> <div class="mb-3"> <input type="range" id="seek-bar" min="0" max="100" value="0" step="0.1"> <div class="flex justify-between mt-1"> <span class="font-hand text-ink/25 text-base" id="time-current">0:00</span> <span class="font-hand text-ink/25 text-base" id="time-total">0:00</span> </div> </div> <!-- controls --> <div class="flex items-center justify-center gap-4"> <!-- rewind 10s --> <button onclick="skip(-10)" class="group relative p-2 hover:scale-110 transition-transform" title="back 10s"> <svg class="w-6 h-6 text-ink/25 group-hover:text-pook transition-colors" fill="currentColor" viewBox="0 0 24 24"> <path d="M12.5 3c-4.65 0-8.58 3.03-9.96 7.22L1.5 9.5l1.55 4.07 4.07-1.55-1.09-.42A7.499 7.499 0 0112.5 5c4.14 0 7.5 3.36 7.5 7.5S16.64 20 12.5 20c-1.96 0-3.73-.75-5.07-1.97l-1.42 1.42A9.466 9.466 0 0012.5 22c5.24 0 9.5-4.26 9.5-9.5S17.74 3 12.5 3z"/> <text x="9" y="15.5" font-size="7" font-family="sans-serif" font-weight="bold" fill="currentColor">10</text> </svg> </button> <!-- play/pause --> <button id="play-btn" onclick="togglePlay()" class="relative w-14 h-14 md:w-16 md:h-16 flex items-center justify-center hover:scale-105 transition-transform"> <canvas id="play-btn-canvas" class="absolute inset-0 w-full h-full pointer-events-none"></canvas> <span class="relative z-10 text-white text-xl md:text-2xl" id="play-icon">▶</span> </button> <!-- forward 10s --> <button onclick="skip(10)" class="group relative p-2 hover:scale-110 transition-transform" title="forward 10s"> <svg class="w-6 h-6 text-ink/25 group-hover:text-pook transition-colors" fill="currentColor" viewBox="0 0 24 24"> <path d="M11.5 3c4.65 0 8.58 3.03 9.96 7.22l1.04-.72-1.55 4.07-4.07-1.55 1.09-.42A7.499 7.499 0 0011.5 5C7.36 5 4 8.36 4 12.5S7.36 20 11.5 20c1.96 0 3.73-.75 5.07-1.97l1.42 1.42A9.466 9.466 0 0111.5 22C6.26 22 2 17.74 2 12.5S6.26 3 11.5 3z"/> <text x="8" y="15.5" font-size="7" font-family="sans-serif" font-weight="bold" fill="currentColor">10</text> </svg> </button> </div> <!-- volume --> <div class="flex items-center justify-center gap-2 mt-4"> <span class="text-ink/20 text-base">🔈</span> <input type="range" id="volume-bar" min="0" max="100" value="80" class="w-24" style="height:4px;"> <span class="text-ink/20 text-base">🔊</span> </div> <!-- pookie commentary --> <div class="mt-5 text-center"> <p class="font-caveat text-ink/20 text-base" id="commentary">press play. pookie has things to say.</p> </div> </div> </div> <!-- speed control (below card) --> <div class="mt-4 flex items-center justify-center gap-2 pop-in" style="animation-delay:0.2s;"> <span class="font-hand text-ink/20 text-base">speed:</span> <?php foreach ([0.5, 1, 1.5, 2] as $s): ?> <button onclick="setSpeed(<?= $s ?>)" class="speed-btn font-hand text-base px-2 py-0.5 rounded transition-all <?= $s === 1 ? 'bg-pook/15 text-pook' : 'text-ink/20 hover:text-pook' ?>" data-speed="<?= $s ?>"> <?= $s ?>x </button> <?php endforeach; ?> </div> <?php else: ?> <!-- error state --> <div class="text-center pop-in"> <p class="text-5xl mb-4">🐾</p> <p class="font-marker text-ink text-xl mb-2"><?= $errorTitle ?></p> <p class="font-hand text-ink/40 text-sm mb-6"><?= $errorMsg ?></p> <a href="/" class="font-marker text-pook hover:text-pook/70 text-sm transition-colors">← back to pookie's world</a> </div> <?php endif; ?> <!-- footer --> <div class="mt-8 text-center"> <p class="font-scrawl text-ink/10 text-[10px]">a <?= htmlspecialchars($name) ?> production</p> </div> </main> <script> const C = { col: <?= json_encode($c['colors']) ?>, names: <?= json_encode($c['names']) ?>, }; <?php if ($episode): ?> // ═══════════════════════════════════════════════════ // AUDIO PLAYER // ═══════════════════════════════════════════════════ const audio = new Audio('<?= $audioUrl ?>'); audio.volume = 0.8; audio.preload = 'metadata'; let isPlaying = false; let animFrame = 0; const seekBar = document.getElementById('seek-bar'); const timeCur = document.getElementById('time-current'); const timeTotal = document.getElementById('time-total'); const playIcon = document.getElementById('play-icon'); const disc = document.getElementById('disc'); const commentary = document.getElementById('commentary'); const playComments = [ 'pookie is speaking. silence please.', 'the voice of a generation (pookie\'s words not ours)', 'shh. genius is talking.', 'pookie didn\'t rehearse this. raw talent.', 'this is what perfection sounds like.', 'pookie recorded this in one take. obviously.', ]; const pauseComments = [ 'pookie waits. pookie is patient. (press play.)', 'taking a break? pookie judges silently.', 'paused? the audacity.', 'pookie\'s voice lingers in your mind anyway.', ]; const endComments = [ 'that was art and pookie knows it.', 'you\'re welcome. — pookie', 'replay it. you know you want to.', 'pookie has spoken. the session is complete.', ]; function fmt(s){ if(isNaN(s)) return '0:00'; const m=Math.floor(s/60), sec=Math.floor(s%60); return m+':'+String(sec).padStart(2,'0'); } audio.addEventListener('loadedmetadata', () => { timeTotal.textContent = fmt(audio.duration); seekBar.max = audio.duration; }); audio.addEventListener('timeupdate', () => { if(!seekBar._dragging){ seekBar.value = audio.currentTime; timeCur.textContent = fmt(audio.currentTime); } }); audio.addEventListener('ended', () => { isPlaying = false; playIcon.innerHTML = '▶'; disc.style.animation = 'none'; commentary.textContent = endComments[Math.floor(Math.random()*endComments.length)]; }); seekBar.addEventListener('mousedown', () => { seekBar._dragging = true; }); seekBar.addEventListener('touchstart', () => { seekBar._dragging = true; }); seekBar.addEventListener('input', () => { timeCur.textContent = fmt(seekBar.value); }); seekBar.addEventListener('change', () => { audio.currentTime = seekBar.value; seekBar._dragging = false; }); document.getElementById('volume-bar').addEventListener('input', function(){ audio.volume = this.value/100; }); function togglePlay(){ if(isPlaying){ audio.pause(); isPlaying = false; playIcon.innerHTML = '▶'; disc.style.animation = 'none'; commentary.textContent = pauseComments[Math.floor(Math.random()*pauseComments.length)]; } else { audio.play(); isPlaying = true; playIcon.innerHTML = '⏸'; disc.style.animation = 'spinDisc 3s linear infinite'; commentary.textContent = playComments[Math.floor(Math.random()*playComments.length)]; } } function skip(s){ audio.currentTime = Math.max(0, Math.min(audio.duration||0, audio.currentTime + s)); } function setSpeed(s){ audio.playbackRate = s; document.querySelectorAll('.speed-btn').forEach(b => { b.classList.toggle('bg-pook/15', parseFloat(b.dataset.speed)===s); b.classList.toggle('text-pook', parseFloat(b.dataset.speed)===s); b.classList.toggle('text-ink/20', parseFloat(b.dataset.speed)!==s); }); } // keyboard controls document.addEventListener('keydown', e => { if(e.code==='Space'){e.preventDefault();togglePlay();} if(e.code==='ArrowLeft') skip(-5); if(e.code==='ArrowRight') skip(5); }); <?php endif; ?> // ═══════════════════════════════════════════════════ // CANVAS DRAWING (shared) // ═══════════════════════════════════════════════════ let frame = 0; function wb(x,y,a){return[x+(Math.random()-.5)*a,y+(Math.random()-.5)*a]} // background doodles const bgC = document.getElementById('bg-canvas'); const bg = bgC.getContext('2d'); function drawStar(ctx,x,y,s,rot){ctx.beginPath();for(let i=0;i<5;i++){const a=rot+(i*Math.PI*2/5)-Math.PI/2;const ia=rot+((i+.5)*Math.PI*2/5)-Math.PI/2;const[ox,oy]=wb(x+Math.cos(a)*s,y+Math.sin(a)*s,s*.2);const[ix,iy]=wb(x+Math.cos(ia)*s*.35,y+Math.sin(ia)*s*.35,s*.2);i===0?ctx.moveTo(ox,oy):ctx.lineTo(ox,oy);ctx.lineTo(ix,iy);}ctx.closePath();ctx.stroke();} function drawHeart(ctx,x,y,s,rot){ctx.save();ctx.translate(x,y);ctx.rotate(rot);ctx.beginPath();ctx.moveTo(0,s*.25);ctx.bezierCurveTo(-s*.05,-s*.35,-s*.8,-s*.35,0,s*.75);ctx.moveTo(0,s*.25);ctx.bezierCurveTo(s*.05,-s*.35,s*.8,-s*.35,0,s*.75);ctx.stroke();ctx.restore();} function drawNote(ctx,x,y,s,rot){ctx.save();ctx.translate(x,y);ctx.rotate(rot);ctx.beginPath();ctx.arc(-s*.3,0,s*.3,0,Math.PI*2);ctx.fill();ctx.moveTo(-s*.3+s*.3,0);ctx.lineTo(-s*.3+s*.3,-s);ctx.moveTo(-s*.3+s*.3,-s);ctx.lineTo(-s*.3+s*.3+s*.3,-s+s*.15);ctx.stroke();ctx.restore();} function drawSquig(ctx,x,y,s,rot){ctx.save();ctx.translate(x,y);ctx.rotate(rot);ctx.beginPath();ctx.moveTo(-s,0);for(let i=0;i<=8;i++)ctx.lineTo(-s+(s*2*i/8),Math.sin(i*1.3)*s*.5+(Math.random()-.5)*3);ctx.stroke();ctx.restore();} function drawSpiral(ctx,x,y,s,rot){ctx.save();ctx.translate(x,y);ctx.rotate(rot);ctx.beginPath();for(let i=0;i<=40;i++){const a=(i/40)*Math.PI*4;const r=(i/40)*s;ctx.lineTo(Math.cos(a)*r+(Math.random()-.5)*1.5,Math.sin(a)*r+(Math.random()-.5)*1.5);}ctx.stroke();ctx.restore();} const doodleFns = [drawStar, drawHeart, drawNote, drawSquig, drawSpiral]; let doodles = []; function genDoodles(){ doodles = []; const cols = [C.col.primary, C.col.secondary, C.col.accent, C.col.highlight]; const count = Math.min(Math.floor((bgC.width*bgC.height)/25000), 150); for(let i=0; i<count; i++){ doodles.push({ x: Math.random()*bgC.width, y: Math.random()*bgC.height, fn: doodleFns[Math.floor(Math.random()*doodleFns.length)], s: 5+Math.random()*15, rot: Math.random()*Math.PI*2, op: 0.02+Math.random()*0.04, wobOff: Math.random()*Math.PI*2, col: cols[Math.floor(Math.random()*cols.length)], }); } } function resizeBg(){ bgC.width=window.innerWidth; bgC.height=window.innerHeight; genDoodles(); } window.addEventListener('resize', resizeBg); resizeBg(); function drawBg(){ bg.clearRect(0,0,bgC.width,bgC.height); for(const d of doodles){ bg.save(); bg.globalAlpha = d.op + Math.sin(frame*.0015+d.wobOff)*.01; bg.strokeStyle = d.col; bg.fillStyle = d.col; bg.lineWidth = 1.2; bg.lineCap = 'round'; bg.lineJoin = 'round'; const wobY = Math.sin(frame*.0012+d.wobOff)*4; d.fn(bg, d.x, d.y+wobY, d.s, d.rot+Math.sin(frame*.0006+d.wobOff)*.04); bg.restore(); } } // UI canvas — wobbly border around the player card const uiC = document.getElementById('ui-canvas'); const ui = uiC.getContext('2d'); function resizeUi(){ uiC.width=window.innerWidth; uiC.height=window.innerHeight; } window.addEventListener('resize', resizeUi); resizeUi(); function wobbyRect(ctx,x,y,w,h,lw){ ctx.lineWidth=lw||2.5; ctx.lineCap='round'; ctx.lineJoin='round'; ctx.beginPath(); const s=36; for(let i=0;i<=s;i++){ const t=i/s; let px,py; if(t<.25){px=x+w*(t/.25);py=y;} else if(t<.5){px=x+w;py=y+h*((t-.25)/.25);} else if(t<.75){px=x+w-w*((t-.5)/.25);py=y+h;} else{px=x;py=y+h-h*((t-.75)/.25);} px+=Math.sin(t*Math.PI*6+frame*.004)*3.5; py+=Math.cos(t*Math.PI*4+frame*.004)*3.5; i===0?ctx.moveTo(px,py):ctx.lineTo(px,py); } ctx.closePath(); ctx.stroke(); } function drawUi(){ ui.clearRect(0,0,uiC.width,uiC.height); const card = document.getElementById('card-inner'); if(card){ const r = card.getBoundingClientRect(); ui.globalAlpha = .25; ui.strokeStyle = C.col.primary; wobbyRect(ui, r.left-6, r.top-6, r.width+12, r.height+12, 3); ui.globalAlpha = .1; ui.strokeStyle = C.col.secondary; wobbyRect(ui, r.left-14, r.top-14, r.width+28, r.height+28, 2); } ui.globalAlpha = 1; } // play button canvas — wobbly filled circle <?php if ($episode): ?> const pbC = document.getElementById('play-btn-canvas'); const pb = pbC.getContext('2d'); function drawPlayBtn(){ const el = document.getElementById('play-btn'); if(!el||!pbC) return; const sz = el.offsetWidth + 8; pbC.width = sz; pbC.height = sz; pbC.style.left = '-4px'; pbC.style.top = '-4px'; pbC.style.width = sz+'px'; pbC.style.height = sz+'px'; pb.clearRect(0,0,sz,sz); const cx=sz/2, cy=sz/2, r=sz/2-4; pb.beginPath(); for(let i=0;i<=32;i++){ const a=(i/32)*Math.PI*2; const pr=r+Math.sin(a*5+frame*.005)*2.5+(Math.random()-.5)*1; const px=cx+Math.cos(a)*pr, py=cy+Math.sin(a)*pr; i===0?pb.moveTo(px,py):pb.lineTo(px,py); } pb.closePath(); pb.fillStyle = isPlaying ? '#14b8a6' : C.col.primary; pb.fill(); pb.strokeStyle = isPlaying ? '#0d9488' : '#0f766e'; pb.lineWidth = 2.5; pb.stroke(); } // waveform visualizer const wfC = document.getElementById('waveform-canvas'); const wf = wfC.getContext('2d'); const barCount = 48; let wfBars = Array.from({length:barCount}, ()=>Math.random()*0.3+0.1); function drawWaveform(){ const w = wfC.parentElement.offsetWidth; const h = 48; wfC.width = w; wfC.height = h; wf.clearRect(0,0,w,h); const bw = w/barCount; const progress = audio.duration ? audio.currentTime/audio.duration : 0; for(let i=0;i<barCount;i++){ const active = i/barCount <= progress; // bars bounce when playing let barH; if(isPlaying){ barH = (wfBars[i] + Math.sin(frame*.03+i*.5)*0.15 + Math.sin(frame*.017+i*.3)*0.1) * h; } else { barH = wfBars[i] * h * 0.6; } barH = Math.max(4, Math.min(h-4, barH)); const x = i*bw + bw*0.15; const bWidth = bw*0.7; const y = (h-barH)/2; wf.fillStyle = active ? C.col.primary : 'rgba(30,41,59,0.08)'; wf.globalAlpha = active ? (isPlaying ? 0.7 : 0.5) : 0.6; // draw wobbly bar wf.beginPath(); wf.moveTo(x+(Math.random()-.5)*0.5, y); wf.lineTo(x+bWidth+(Math.random()-.5)*0.5, y+(Math.random()-.5)*0.5); wf.lineTo(x+bWidth+(Math.random()-.5)*0.5, y+barH+(Math.random()-.5)*0.5); wf.lineTo(x+(Math.random()-.5)*0.5, y+barH); wf.closePath(); wf.fill(); } wf.globalAlpha = 1; } <?php endif; ?> // title canvas const titleC = document.getElementById('title-canvas'); const titleCtx = titleC.getContext('2d'); function drawTitle(){ const w=titleC.width, h=titleC.height; titleCtx.clearRect(0,0,w,h); const text = "voice of pook"; titleCtx.font = '700 40px "Comic Neue", "Comic Sans MS", cursive'; titleCtx.textAlign = 'center'; let x = w/2 - titleCtx.measureText(text).width/2; for(let i=0;i<text.length;i++){ const ch = text[i]; const cw = titleCtx.measureText(ch).width; titleCtx.save(); titleCtx.translate(x+cw/2, 45); titleCtx.rotate(Math.sin(frame*.003+i*1.2)*.03 + (i%2===0?-.015:.015)); titleCtx.translate(0, Math.sin(frame*.004+i*.7)*2); // shadow titleCtx.fillStyle = 'rgba(45,212,191,0.12)'; titleCtx.fillText(ch, 2, 2); // main titleCtx.fillStyle = C.col.primary; titleCtx.fillText(ch, 0, 0); titleCtx.restore(); x += cw - 1; } // underline const tw = titleCtx.measureText(text).width; titleCtx.strokeStyle = C.col.secondary; titleCtx.lineWidth = 2.5; titleCtx.lineCap = 'round'; titleCtx.globalAlpha = .5; titleCtx.beginPath(); for(let i=0;i<=16;i++){ const t=i/16; const px = w/2-tw/2-5 + (tw+10)*t; const py = 54 + Math.sin(t*Math.PI*2+frame*.004)*3 + (Math.random()-.5)*1; i===0?titleCtx.moveTo(px,py):titleCtx.lineTo(px,py); } titleCtx.stroke(); titleCtx.globalAlpha = 1; } // ═══════════════════════════════════════════════════ // MAIN LOOP // ═══════════════════════════════════════════════════ let tabVisible = true; document.addEventListener('visibilitychange', () => { tabVisible = !document.hidden; }); function loop(){ if(!tabVisible){requestAnimationFrame(loop);return;} frame++; if(frame%3===0){ drawBg(); drawUi(); } if(frame%4===0) drawTitle(); <?php if ($episode): ?> drawPlayBtn(); drawWaveform(); <?php endif; ?> requestAnimationFrame(loop); } loop(); </script> </body> </html>
| ver. 1.4 |
Github
|
.
| PHP 7.4.33 | ���֧ߧ֧�ѧ�ڧ� ����ѧߧڧ��: 0 |
proxy
|
phpinfo
|
���ѧ����ۧܧ�