diff --git a/src/scripts/easter-eggs.ts b/src/scripts/easter-eggs.ts index bb084ee..e575c24 100644 --- a/src/scripts/easter-eggs.ts +++ b/src/scripts/easter-eggs.ts @@ -1225,6 +1225,227 @@ function finalInscription() { }, 5000) } +// ================================================================ +// Owl of Athena โ accessible discovery UI +// A blinking owl in the corner opens a panel listing all easter eggs +// with one-click invoke buttons. No console required. +// ================================================================ + +interface EggMeta { + id: string + name: string + desc: string + icon: string + trigger: () => void + command: string + category: 'Interactive' | 'Visual' | 'Mode' +} + +const EGG_CATALOG: EggMeta[] = [ + { id: 'eklesia', name: 'Interactive Ekklesia', desc: 'Propose a decree and put it to the vote of 60 citizens.', icon: 'โ๏ธ', trigger: interactiveAssembly, command: 'assembly()', category: 'Interactive' }, + { id: 'boule', name: 'Council of 500', desc: 'The ten tribes each cast their block votes.', icon: '๐๏ธ', trigger: bouleVote, command: 'boule()', category: 'Interactive' }, + { id: 'decree', name: 'Decree of the Day', desc: 'A randomly generated Athenian decree.', icon: '๐', trigger: decreeGenerator, command: 'decree()', category: 'Interactive' }, + { id: 'scribe', name: 'Chisel Mode', desc: 'Transform the page into marble inscription.', icon: 'โ๏ธ', trigger: scribeMode, command: 'scribe()', category: 'Mode' }, + { id: 'sacredfire', name: 'Sacred Fire', desc: 'Light the eternal flame of Hestia.', icon: '๐ฅ', trigger: sacredFire, command: 'sacredfire()', category: 'Mode' }, + { id: 'stoichedon', name: 'Stoichedon Cascade', desc: 'Six Greek letters ฮฮฮฮฮฮ fall and chisel into a grid.', icon: 'ฮฮ', trigger: stoichedonCascade, command: 'type "edoxen"', category: 'Visual' }, + { id: 'assembly', name: 'The Assembly Convokes', desc: 'Stele silhouettes march across the screen.', icon: '๐ฟ', trigger: assemblyMarch, command: 'โโโโโโโโBA', category: 'Visual' }, + { id: 'gavel', name: 'Gavel Bang', desc: 'A parliamentary gavel demands ORDER!', icon: '๐จ', trigger: gavelBang, command: 'gavel()', category: 'Visual' }, + { id: 'resolve', name: 'Resolution Stamp', desc: 'A wax-seal stamp slams down: แผฮดฮฟฮพฮตฮฝ.', icon: '๐', trigger: resolutionStamp, command: 'resolve()', category: 'Visual' }, + { id: 'lottery', name: 'Sortition', desc: 'Athenian random selection by lot.', icon: '๐ฒ', trigger: sortition, command: 'lottery()', category: 'Visual' }, + { id: 'ostracize', name: 'Ostracism', desc: 'Banish someone by pottery shard!', icon: '๐บ', trigger: ostracism, command: 'ostracize()', category: 'Visual' }, + { id: 'marathon', name: 'Marathon Runner', desc: 'Pheidippides brings news of victory.', icon: '๐', trigger: marathonRunner, command: 'marathon()', category: 'Visual' }, + { id: 'symposium', name: "Philosopher's Quote", desc: 'Wisdom from Socrates, Aristotle, and the ancients.', icon: '๐', trigger: symposiumQuote, command: 'symposium()', category: 'Visual' }, + { id: 'prophecy', name: 'Oracle of Delphi', desc: 'The Pythia speaks โ enigmatically and smokily.', icon: '๐ฎ', trigger: oracleProphecy, command: 'prophecy()', category: 'Visual' }, +] + +let owlEl: HTMLButtonElement | null = null +let panelEl: HTMLDivElement | null = null + +function createOwlButton() { + if (owlEl) return + + owlEl = document.createElement('button') + owlEl.setAttribute('aria-label', 'Open Hidden Inscriptions โ discover easter eggs') + owlEl.title = 'The Owl of Athena' + owlEl.style.cssText = ` + position:fixed;bottom:20px;left:20px;z-index:99998; + width:52px;height:60px;border:none;background:transparent; + cursor:pointer;padding:0;opacity:0.7; + transition:opacity 0.3s,transform 0.3s; + filter:drop-shadow(0 2px 8px rgba(0,0,0,0.3)); + ` + owlEl.innerHTML = `` + + document.body.appendChild(owlEl) + + owlEl.onmouseenter = () => { owlEl!.style.opacity = '1'; owlEl!.style.transform = 'translateY(-2px)' } + owlEl.onmouseleave = () => { owlEl!.style.opacity = '0.7'; owlEl!.style.transform = '' } + owlEl.onclick = openDiscoveryPanel + + const eyes = [owlEl.querySelectorAll('ellipse')[2], owlEl.querySelectorAll('ellipse')[3]] + function blink() { + if (!owlEl || document.hidden) return + eyes.forEach(eye => { + if (eye) eye.animate([ + { transform: 'scaleY(1)' }, + { transform: 'scaleY(0.1)' }, + { transform: 'scaleY(1)' }, + ], { duration: 150, easing: 'ease-in-out' }) + }) + setTimeout(blink, 3000 + Math.random() * 4000) + } + setTimeout(blink, 2000 + Math.random() * 3000) + + owlEl.animate([ + { opacity: 0, transform: 'translateY(20px) scale(0.5)' }, + { opacity: 0.7, transform: 'translateY(0) scale(1)' }, + ], { duration: 600, delay: 1500, easing: 'cubic-bezier(0.34,1.56,0.64,1)', fill: 'forwards' }) +} + +function openDiscoveryPanel() { + if (panelEl) { closeDiscoveryPanel(); return } + + panelEl = document.createElement('div') + panelEl.setAttribute('role', 'dialog') + panelEl.setAttribute('aria-modal', 'true') + panelEl.setAttribute('aria-labelledby', 'owl-panel-title') + panelEl.style.cssText = `position:fixed;inset:0;z-index:100000;background:rgba(8,12,20,0.85);backdrop-filter:blur(6px);display:flex;align-items:center;justify-content:center;padding:1.5rem;overflow-y:auto;` + document.body.appendChild(panelEl) + + const modal = document.createElement('div') + modal.style.cssText = ` + max-width:580px;width:100%;max-height:85vh;overflow-y:auto; + background:linear-gradient(135deg,#1a1f2e 0%,#141b26 100%); + border:1px solid rgba(224,176,74,0.3); + border-radius:16px;padding:2rem; + box-shadow:0 20px 60px rgba(0,0,0,0.5),0 0 80px rgba(224,176,74,0.08); + font-family:'IBM Plex Sans',sans-serif;color:#e0b04a; + ` + panelEl.appendChild(modal) + + const close = document.createElement('button') + close.innerHTML = 'ร' + close.setAttribute('aria-label', 'Close') + close.style.cssText = `position:absolute;top:0.5rem;right:1rem;background:none;border:none;color:rgba(224,176,74,0.5);font-size:1.8rem;cursor:pointer;line-height:1;` + close.onclick = closeDiscoveryPanel + modal.appendChild(close) + + const header = document.createElement('div') + header.innerHTML = ` +
help() for command access.
+