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 = ` +
๐Ÿฆ‰ Hidden Inscriptions
+
The Owl of Athena guards these secrets
+ ` + modal.appendChild(header) + + const progress = document.createElement('div') + const pct = Math.round((discovered.size / ALL_EGGS.length) * 100) + progress.innerHTML = ` +
+ Discovered${discovered.size}/${ALL_EGGS.length} +
+
+
+
+ ` + modal.appendChild(progress) + + const categories: EggMeta['category'][] = ['Interactive', 'Mode', 'Visual'] + categories.forEach(cat => { + const eggs = EGG_CATALOG.filter(e => e.category === cat) + if (eggs.length === 0) return + + const catHeader = document.createElement('div') + catHeader.textContent = cat + catHeader.style.cssText = `font-family:'JetBrains Mono',monospace;font-size:0.7rem;letter-spacing:0.15em;text-transform:uppercase;color:rgba(224,176,74,0.35);margin:1.5rem 0 0.5rem;` + modal.appendChild(catHeader) + + eggs.forEach(egg => { + const isFound = discovered.has(egg.id) + const card = document.createElement('div') + card.style.cssText = ` + display:flex;align-items:center;gap:0.75rem; + padding:0.75rem;margin-bottom:0.5rem; + background:rgba(0,0,0,${isFound ? '0.2' : '0.15'}); + border:1px solid rgba(224,176,74,${isFound ? '0.15' : '0.06'}); + border-radius:10px;transition:border-color 0.2s; + ` + + const icon = document.createElement('div') + icon.textContent = egg.icon + icon.style.cssText = `font-size:1.5rem;width:2rem;text-align:center;flex-shrink:0;font-family:'Fraunces',Georgia,serif;` + card.appendChild(icon) + + const info = document.createElement('div') + info.style.cssText = `flex:1;min-width:0;` + info.innerHTML = ` +
+ ${egg.name}${isFound ? ' โœ“' : ''} +
+
${egg.desc}
+
${egg.command}
+ ` + card.appendChild(info) + + const btn = document.createElement('button') + btn.textContent = 'โœฆ' + btn.setAttribute('aria-label', `Invoke ${egg.name}`) + btn.style.cssText = ` + flex-shrink:0;width:36px;height:36px; + background:rgba(224,176,74,0.1);border:1px solid rgba(224,176,74,0.2); + border-radius:8px;color:#e0b04a;font-size:1rem;cursor:pointer; + transition:all 0.2s; + ` + btn.onmouseenter = () => { btn.style.background = 'rgba(224,176,74,0.25)'; btn.style.borderColor = 'rgba(224,176,74,0.5)'; card.style.borderColor = 'rgba(224,176,74,0.25)' } + btn.onmouseleave = () => { btn.style.background = 'rgba(224,176,74,0.1)'; btn.style.borderColor = 'rgba(224,176,74,0.2)'; card.style.borderColor = 'rgba(224,176,74,0.06)' } + btn.onclick = () => { + closeDiscoveryPanel() + setTimeout(() => egg.trigger(), 300) + } + card.appendChild(btn) + + modal.appendChild(card) + }) + }) + + const tips = document.createElement('div') + tips.innerHTML = ` +
Also try
+
+ Click the nav logo 5ร— for a surprise.
+ Open browser console (F12) and type help() for command access. +
+ ` + modal.appendChild(tips) + + panelEl.onclick = (e) => { if (e.target === panelEl) closeDiscoveryPanel() } + const escHandler = (e: KeyboardEvent) => { if (e.key === 'Escape') { closeDiscoveryPanel(); document.removeEventListener('keydown', escHandler) } } + document.addEventListener('keydown', escHandler) + + panelEl.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 200, fill: 'forwards' }) + modal.animate([{ transform: 'scale(0.9) translateY(20px)', opacity: 0 }, { transform: 'scale(1) translateY(0)', opacity: 1 }], { duration: 300, easing: 'cubic-bezier(0.34,1.56,0.64,1)', fill: 'forwards' }) +} + +function closeDiscoveryPanel() { + if (!panelEl) return + const p = panelEl + panelEl = null + p.animate([{ opacity: 1 }, { opacity: 0 }], { duration: 200, fill: 'forwards' }) + setTimeout(() => p.remove(), 250) +} + // ================================================================ // Console: help() โ€” lists all easter eggs // ================================================================ @@ -1321,8 +1542,8 @@ function setupLogoClick() { function printTeaser() { const style = 'font-size:14px;font-weight:bold;color:#e0b04a;' const sub = 'font-size:11px;color:#6b7280;' - console.log('%c๐Ÿ›๏ธ Edoxen', style) - console.log('%cType help() to discover hidden inscriptions.', sub) + console.log('%c๐Ÿฆ™ Edoxen', style) + console.log('%cClick the owl in the corner (or type help()) to discover hidden inscriptions.', sub) } export function initEasterEggs() { @@ -1330,5 +1551,6 @@ export function initEasterEggs() { setupKeyboard() setupConsole() setupLogoClick() + createOwlButton() printTeaser() }