diff --git a/public/css/themes/h2g2.css b/public/css/themes/h2g2.css new file mode 100644 index 0000000..a8e9824 --- /dev/null +++ b/public/css/themes/h2g2.css @@ -0,0 +1,228 @@ +/* + * Hitchhiker's Guide to the Galaxy Theme + * DON'T PANIC - Hidden theme activated via easter egg + * Inspired by the book covers: deep space blue with warm friendly orange lettering + */ + +/* ========== DARK MODE (default) ========== */ +:root { + /* === Core Palette === */ + --palette-panic-orange: #FF9F1C; + --palette-panic-gold: #FFBF69; + --palette-towel-brown: #A67C52; + --palette-space-deep: #0D1B2A; + --palette-space-mid: #1B263B; + --palette-space-light: #243B53; + --palette-space-lighter: #334E68; + --palette-space-border: #486581; + --palette-starlight: #F0F4F8; + --palette-starlight-muted: #9FB3C8; + --palette-improbability-purple: #7B68EE; + --palette-babel-fish-yellow: #FFE066; + + /* === Semantic: Backgrounds === */ + --bg: var(--palette-space-deep); + --bg-secondary: var(--palette-space-mid); + --bg-tertiary: var(--palette-space-lighter); + --surface: var(--palette-space-light); + --input-bg: var(--palette-space-mid); + + /* === Semantic: Text === */ + --text: var(--palette-starlight); + --text-secondary: var(--palette-starlight-muted); + + /* === Semantic: Accent === */ + --accent: var(--palette-panic-orange); + --accent-light: var(--palette-panic-gold); + --accent-secondary: var(--palette-improbability-purple); + + /* === Semantic: Status === */ + --danger: #E63946; + --success: #06D6A0; + --warning: var(--palette-panic-orange); + + /* === Semantic: Borders === */ + --border: var(--palette-space-border); + --glass-bg: rgba(27, 38, 59, 0.9); + --glass-border: rgba(255, 159, 28, 0.15); + + /* === Semantic: Message Bubbles === */ + --user-bubble: var(--palette-panic-orange); + --user-bubble-end: #E8890C; + --assistant-bubble: var(--palette-space-mid); + + /* === Semantic: List View === */ + --scope-header-color: var(--palette-panic-orange); + --card-border: rgba(255, 159, 28, 0.4); + --pinned-border: rgba(255, 159, 28, 0.6); + --pinned-bg: rgba(255, 159, 28, 0.1); + + /* === Alpha Variants === */ + --accent-alpha-8: rgba(255, 159, 28, 0.08); + --accent-alpha-12: rgba(255, 159, 28, 0.12); + --accent-alpha-15: rgba(255, 159, 28, 0.15); + --accent-alpha-20: rgba(255, 159, 28, 0.2); + --accent-alpha-25: rgba(255, 159, 28, 0.25); + --accent-alpha-30: rgba(255, 159, 28, 0.3); + --accent-alpha-35: rgba(255, 159, 28, 0.35); + --accent-alpha-50: rgba(255, 159, 28, 0.5); + + --danger-alpha-15: rgba(230, 57, 70, 0.15); + --danger-alpha-40: rgba(230, 57, 70, 0.4); + + --teal-alpha-6: rgba(6, 214, 160, 0.06); + --teal-alpha-25: rgba(6, 214, 160, 0.25); + + --terracotta-alpha-6: rgba(255, 159, 28, 0.06); + --terracotta-alpha-10: rgba(255, 159, 28, 0.1); + + --white-alpha-6: rgba(255, 255, 255, 0.06); + --white-alpha-10: rgba(255, 255, 255, 0.1); + --white-alpha-60: rgba(255, 255, 255, 0.6); + + --black-alpha-8: rgba(0, 0, 0, 0.08); + --black-alpha-15: rgba(0, 0, 0, 0.15); + --black-alpha-20: rgba(0, 0, 0, 0.2); + --black-alpha-25: rgba(0, 0, 0, 0.25); + --black-alpha-30: rgba(0, 0, 0, 0.3); + --black-alpha-40: rgba(0, 0, 0, 0.4); + --black-alpha-50: rgba(0, 0, 0, 0.5); + + /* === Component: Status Indicators === */ + --status-idle: var(--palette-panic-gold); + --status-thinking: var(--palette-panic-orange); + --status-disconnected: var(--palette-space-border); + + /* === Component: Mode Badges === */ + --mode-autopilot-bg: var(--teal-alpha-25); + --mode-autopilot-text: #06D6A0; + --mode-readonly-bg: var(--accent-alpha-20); + --mode-readonly-text: var(--palette-panic-orange); + + /* === Component: Syntax Highlighting === */ + --syntax-keyword: var(--palette-panic-orange); + --syntax-string: var(--palette-babel-fish-yellow); + --syntax-number: #E63946; + --syntax-comment: #627D98; + --syntax-function: var(--palette-panic-gold); + --syntax-type: var(--palette-improbability-purple); + --syntax-variable: var(--palette-towel-brown); + --syntax-operator: var(--palette-starlight-muted); + + /* === Component: Code Blocks === */ + --code-bg: var(--bg); + --code-header-bg: var(--bg-tertiary); + --code-inline-bg: var(--accent-alpha-15); + + /* === Component: Gradient (list view background) === */ + --mesh-color-1: rgba(255, 159, 28, 0.08); + --mesh-color-2: rgba(123, 104, 238, 0.06); + --mesh-color-3: rgba(166, 124, 82, 0.04); + + /* === Component: Brand === */ + --brand-icon-color: var(--palette-panic-orange); + --brand-gradient: linear-gradient(135deg, var(--palette-panic-orange) 0%, var(--palette-improbability-purple) 100%); + + /* === PWA: Theme Colors === */ + --theme-color: #0D1B2A; +} + +/* ========== LIGHT MODE ========== */ +html[data-theme="light"] { + --palette-space-deep: #F0F4F8; + --palette-space-mid: #D9E2EC; + --palette-space-light: #FFFFFF; + --palette-space-lighter: #BCCCDC; + --palette-space-border: #9FB3C8; + --palette-starlight: #102A43; + --palette-starlight-muted: #486581; + --palette-panic-orange: #E8890C; + + --bg: #F0F4F8; + --bg-secondary: #D9E2EC; + --bg-tertiary: #BCCCDC; + --surface: #FFFFFF; + --input-bg: #FFFFFF; + --text: #102A43; + --text-secondary: #486581; + --border: #9FB3C8; + --glass-bg: rgba(255, 255, 255, 0.9); + --glass-border: rgba(232, 137, 12, 0.15); + --assistant-bubble: #F0F4F8; + + --scope-header-color: #D97706; + --card-border: rgba(217, 119, 6, 0.4); + --pinned-border: rgba(217, 119, 6, 0.6); + --pinned-bg: rgba(217, 119, 6, 0.1); + + --white-alpha-6: rgba(0, 0, 0, 0.03); + --white-alpha-10: rgba(0, 0, 0, 0.06); + --code-bg: #D9E2EC; + --code-header-bg: #BCCCDC; + --code-inline-bg: rgba(232, 137, 12, 0.1); + --mesh-color-1: rgba(217, 119, 6, 0.12); + --mesh-color-2: rgba(123, 104, 238, 0.04); + --mesh-color-3: rgba(166, 124, 82, 0.03); + + --syntax-keyword: #B45309; + --syntax-string: #92400E; + --syntax-number: #BE123C; + --syntax-comment: #64748B; + --syntax-function: #D97706; + --syntax-type: #6D28D9; + --syntax-variable: #78350F; + --syntax-operator: #486581; + + --scrollbar-thumb: rgba(0, 0, 0, 0.15); + --theme-color: #F0F4F8; +} + +/* Auto theme via media query */ +@media (prefers-color-scheme: light) { + html:not([data-theme="dark"]):not([data-theme="light"]) { + --palette-space-deep: #F0F4F8; + --palette-space-mid: #D9E2EC; + --palette-space-light: #FFFFFF; + --palette-space-lighter: #BCCCDC; + --palette-space-border: #9FB3C8; + --palette-starlight: #102A43; + --palette-starlight-muted: #486581; + --palette-panic-orange: #E8890C; + + --bg: #F0F4F8; + --bg-secondary: #D9E2EC; + --bg-tertiary: #BCCCDC; + --surface: #FFFFFF; + --input-bg: #FFFFFF; + --text: #102A43; + --text-secondary: #486581; + --border: #9FB3C8; + --glass-bg: rgba(255, 255, 255, 0.9); + --glass-border: rgba(232, 137, 12, 0.15); + --assistant-bubble: #F0F4F8; + + --scope-header-color: #D97706; + --card-border: rgba(217, 119, 6, 0.4); + --pinned-border: rgba(217, 119, 6, 0.6); + --pinned-bg: rgba(217, 119, 6, 0.1); + + --white-alpha-6: rgba(0, 0, 0, 0.03); + --white-alpha-10: rgba(0, 0, 0, 0.06); + --code-bg: #D9E2EC; + --code-header-bg: #BCCCDC; + --code-inline-bg: rgba(232, 137, 12, 0.1); + --mesh-color-1: rgba(217, 119, 6, 0.12); + --mesh-color-2: rgba(123, 104, 238, 0.04); + --mesh-color-3: rgba(166, 124, 82, 0.03); + --syntax-keyword: #B45309; + --syntax-string: #92400E; + --syntax-number: #BE123C; + --syntax-comment: #64748B; + --syntax-function: #D97706; + --syntax-type: #6D28D9; + --syntax-variable: #78350F; + --syntax-operator: #486581; + --scrollbar-thumb: rgba(0, 0, 0, 0.15); + --theme-color: #F0F4F8; + } +} diff --git a/public/js/ui.js b/public/js/ui.js index 6408cd6..20b8548 100644 --- a/public/js/ui.js +++ b/public/js/ui.js @@ -248,16 +248,18 @@ const bellQuotes = { "It's possible I may wet the bed. I'm a very anxious person.", ], aquatic: [ - "This is an adventure.", + "Now if you'll excuse me, I'm going to go on an overnight drunk.", "I wonder if it remembers me.", - "Let me tell you about my boat.", - "Out here, we're all equals.", + "Don't point that gun at him, he's an unpaid intern.", + "Son of a bitch, I'm sick of these dolphins.", "Be still, Cody.", + "I'm going to find it and I'm going to destroy it.", "We're in the middle of a lightning strike rescue.", - "I'm right on top of that.", - "This is supposed to be a happy occasion!", + "This is supposed to be a happy occasion. Let's not blow it.", "That's an endangered species at most.", - "You know I'm not good with those things.", + "We're being led on an illegal suicide mission by a selfish maniac.", + "Out here, we're all equals.", + "Let me tell you about my boat.", ], monokai: [ "Hello, World!", @@ -278,10 +280,13 @@ const bellQuotes = { "Time for a nap...", "*blinks slowly*", "Cozy vibes only.", - "*curls up*", + "In ancient times cats were worshipped as gods; they have not forgotten this.", "Warm and fuzzy.", - "*kneads blanket*", + "I'm not sleeping, I'm debugging with my eyes closed.", "Purrfect.", + "Cats are connoisseurs of comfort.", + "The smallest feline is a masterpiece.", + "*knocks things off desk*", ], fjord: [ "Velkommen.", @@ -295,6 +300,34 @@ const bellQuotes = { "Stay cozy.", "The fjords await.", ], + paper: [ + "The first draft of anything is shit.", + "Start writing, no matter what. The water does not flow until the faucet is turned on.", + "A word after a word after a word is power.", + "The scariest moment is always just before you start.", + "You can always edit a bad page. You can't edit a blank page.", + "Write drunk, edit sober.", + "There is nothing to writing. All you do is sit down at a typewriter and bleed.", + "The secret of getting ahead is getting started.", + "Fill your paper with the breathings of your heart.", + "Either write something worth reading or do something worth writing.", + "A writer is someone for whom writing is more difficult than it is for other people.", + "Tomorrow may be hell, but today was a good writing day, and on the good writing days nothing else matters.", + ], + h2g2: [ + "Don't Panic.", + "The Answer to the Ultimate Question of Life, the Universe, and Everything is 42.", + "Time is an illusion. Lunchtime doubly so.", + "So long, and thanks for all the fish.", + "A towel is about the most massively useful thing an interstellar hitchhiker can have.", + "In the beginning the Universe was created. This has made a lot of people very angry.", + "I love deadlines. I love the whooshing noise they make as they go by.", + "Anyone who is capable of getting themselves made President should on no account be allowed to do the job.", + "The ships hung in the sky in much the same way that bricks don't.", + "For a moment, nothing happened. Then, after a second or so, nothing continued to happen.", + "Would it save you a lot of time if I just gave up and went mad now?", + "Space is big. Really big. You just won't believe how vastly, hugely, mind-bogglingly big it is.", + ], }; // Bell ring handler @@ -613,6 +646,31 @@ export function openNewChatModal(cwd = '') { // Thank you easter egg - check for gratitude and show hearts const THANK_YOU_PATTERNS = /\b(thanks?|thank\s*you|thx|ty|tysm|thank\s*u|cheers|gracias|merci|danke|arigatou?|grazie)\b/i; + +// Hitchhiker's Guide easter eggs +const DONT_PANIC_PATTERN = /\bdon['']?t\s*panic\b/i; + +// Marvin the Paranoid Android - triggers on frustration/sadness +const MARVIN_TRIGGERS = { + frustration: /\b(ugh+|argh+|grr+|ffs|wtf|smh|doesn['']?t\s*work|won['']?t\s*work|not\s*working|why\s*(won['']?t|doesn['']?t|isn['']?t|can['']?t)|can['']?t\s*figure|so\s*(annoying|frustrated)|this\s*is\s*broken|hate\s*this|sick\s*of|tired\s*of)\b/i, + sadness: /\b(depressed|miserable|hopeless|awful|terrible|horrible|devastated|give\s*up|giving\s*up|want\s*to\s*cry|can['']?t\s*do\s*this|what['']?s\s*the\s*point)\b/i, + profanity: /\b(fuck(ing|ed)?|shit(ty)?|damn(it)?|crap|hell|bastard|bitch|ass(hole)?)\b/i +}; + +const MARVIN_QUOTES = [ + "Life? Don't talk to me about life.", + "I think you ought to know I'm feeling very depressed.", + "Here I am, brain the size of a planet, and they ask me to pick up a piece of paper.", + "I'd make a suggestion, but you wouldn't listen. No one ever does.", + "The first ten million years were the worst. And the second ten million... they were the worst too.", + "Do you want me to sit in a corner and rust, or just fall apart where I'm standing?", + "Pardon me for breathing, which I never do anyway.", + "I have a million ideas. They all point to certain death.", + "I've been talking to the ship's computer. It hates me.", + "I'm not getting you down at all, am I?", + "My capacity for happiness you could fit into a matchbox without taking out the matches first.", + "I'm at a rough estimate thirty billion times more intelligent than you. Let me give you an example.", +]; const COPY_ICON_SVG = ''; function buildUserMessageActionButtons() { @@ -653,6 +711,443 @@ function triggerHeartsAnimation() { } } +// /dance command - make the UI wiggle +function triggerDanceMode() { + haptic(30); + const chatView = document.getElementById('chat-view'); + if (!chatView) return; + + // Add dance animation + if (!document.getElementById('dance-style')) { + const style = document.createElement('style'); + style.id = 'dance-style'; + style.textContent = ` + @keyframes ui-dance { + 0%, 100% { transform: rotate(0deg) scale(1); } + 10% { transform: rotate(-1deg) scale(1.01); } + 20% { transform: rotate(1deg) scale(0.99); } + 30% { transform: rotate(-0.5deg) scale(1.02); } + 40% { transform: rotate(0.5deg) scale(1); } + 50% { transform: rotate(-1deg) scale(1.01); } + 60% { transform: rotate(1deg) scale(0.99); } + 70% { transform: rotate(-0.5deg) scale(1); } + 80% { transform: rotate(0.5deg) scale(1.01); } + 90% { transform: rotate(-0.5deg) scale(1); } + } + .dancing { animation: ui-dance 0.8s ease-in-out 3; } + `; + document.head.appendChild(style); + } + + chatView.classList.add('dancing'); + setTimeout(() => chatView.classList.remove('dancing'), 2500); + showToast('๐๐บ'); +} + +// /matrix command - green falling code rain +function triggerMatrixMode() { + haptic(30); + + // Create canvas overlay + const canvas = document.createElement('canvas'); + canvas.id = 'matrix-canvas'; + canvas.style.cssText = ` + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + pointer-events: none; + z-index: 9999; + opacity: 0.9; + `; + document.body.appendChild(canvas); + + const ctx = canvas.getContext('2d'); + canvas.width = window.innerWidth; + canvas.height = window.innerHeight; + + const chars = 'ใขใคใฆใจใชใซใญใฏใฑใณใตใทในใปใฝใฟใใใใใใใใใใใใใใใใใ ใกใขใคใฆใจใฉใชใซใฌใญใฏใฒใณ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + const fontSize = 14; + const columns = Math.floor(canvas.width / fontSize); + const rows = Math.floor(canvas.height / fontSize); + // Randomize initial positions so columns don't all start together + const drops = Array(columns).fill(0).map(() => Math.floor(Math.random() * -rows)); + + let frameCount = 0; + const maxFrames = 300; // ~5 seconds at 60fps + + function draw() { + ctx.fillStyle = 'rgba(0, 0, 0, 0.05)'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + + ctx.fillStyle = '#0f0'; + ctx.font = `${fontSize}px monospace`; + + for (let i = 0; i < drops.length; i++) { + // Only draw when on screen (drops[i] > 0) + if (drops[i] > 0) { + const char = chars[Math.floor(Math.random() * chars.length)]; + ctx.fillText(char, i * fontSize, drops[i] * fontSize); + } + + if (drops[i] * fontSize > canvas.height && Math.random() > 0.975) { + drops[i] = 0; + } + drops[i]++; + } + + frameCount++; + if (frameCount < maxFrames) { + requestAnimationFrame(draw); + } else { + // Fade out + canvas.style.transition = 'opacity 0.5s'; + canvas.style.opacity = '0'; + setTimeout(() => canvas.remove(), 500); + } + } + + draw(); + showToast('๐ Follow the white rabbit...'); +} + +// 42 characters - Hitchhiker's Guide reference +function triggerHitchhikersEgg() { + haptic(20); + showToast('๐ The Answer to the Ultimate Question of Life, the Universe, and Everything'); +} + +// Don't Panic - large friendly letters +function triggerDontPanic() { + haptic(20); + + const overlay = document.createElement('div'); + overlay.style.cssText = ` + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background: #1a1a2e; + display: flex; + align-items: center; + justify-content: center; + z-index: 9999; + opacity: 0; + transition: opacity 0.3s; + pointer-events: none; + `; + overlay.innerHTML = ` +