diff --git a/web-app/assets/banners/2048-game.jpg b/web-app/assets/banners/2048-game.jpg index 6adeb2e..f4e7851 100644 Binary files a/web-app/assets/banners/2048-game.jpg and b/web-app/assets/banners/2048-game.jpg differ diff --git a/web-app/assets/banners/armstrong.jpg b/web-app/assets/banners/armstrong.jpg index 4467d66..bbef83d 100644 Binary files a/web-app/assets/banners/armstrong.jpg and b/web-app/assets/banners/armstrong.jpg differ diff --git a/web-app/assets/banners/blackjack21.jpg b/web-app/assets/banners/blackjack21.jpg index ab7cc6e..62bd6cd 100644 Binary files a/web-app/assets/banners/blackjack21.jpg and b/web-app/assets/banners/blackjack21.jpg differ diff --git a/web-app/assets/banners/calculator.jpg b/web-app/assets/banners/calculator.jpg index ebe9a41..d89984c 100644 Binary files a/web-app/assets/banners/calculator.jpg and b/web-app/assets/banners/calculator.jpg differ diff --git a/web-app/assets/banners/coin-flip.jpg b/web-app/assets/banners/coin-flip.jpg index 492c1e2..d5e558d 100644 Binary files a/web-app/assets/banners/coin-flip.jpg and b/web-app/assets/banners/coin-flip.jpg differ diff --git a/web-app/assets/banners/collatz.jpg b/web-app/assets/banners/collatz.jpg index ff1e020..35c2b77 100644 Binary files a/web-app/assets/banners/collatz.jpg and b/web-app/assets/banners/collatz.jpg differ diff --git a/web-app/assets/banners/color-palette.jpg b/web-app/assets/banners/color-palette.jpg index b8d0433..2c7ffd3 100644 Binary files a/web-app/assets/banners/color-palette.jpg and b/web-app/assets/banners/color-palette.jpg differ diff --git a/web-app/assets/banners/coordinate-polar-transform.jpg b/web-app/assets/banners/coordinate-polar-transform.jpg index f35db26..11088c1 100644 Binary files a/web-app/assets/banners/coordinate-polar-transform.jpg and b/web-app/assets/banners/coordinate-polar-transform.jpg differ diff --git a/web-app/assets/banners/derivative-calculator.jpg b/web-app/assets/banners/derivative-calculator.jpg index 5d7bf68..5093053 100644 Binary files a/web-app/assets/banners/derivative-calculator.jpg and b/web-app/assets/banners/derivative-calculator.jpg differ diff --git a/web-app/assets/banners/dice-rolling.jpg b/web-app/assets/banners/dice-rolling.jpg index 428acf4..0bf8922 100644 Binary files a/web-app/assets/banners/dice-rolling.jpg and b/web-app/assets/banners/dice-rolling.jpg differ diff --git a/web-app/assets/banners/dots-boxes.jpg b/web-app/assets/banners/dots-boxes.jpg index 370f5ba..4263cbf 100644 Binary files a/web-app/assets/banners/dots-boxes.jpg and b/web-app/assets/banners/dots-boxes.jpg differ diff --git a/web-app/assets/banners/emoji-memory.jpg b/web-app/assets/banners/emoji-memory.jpg index 0fe7c4b..3ffa9fc 100644 Binary files a/web-app/assets/banners/emoji-memory.jpg and b/web-app/assets/banners/emoji-memory.jpg differ diff --git a/web-app/assets/banners/fibonacci.jpg b/web-app/assets/banners/fibonacci.jpg index 4b87552..939f108 100644 Binary files a/web-app/assets/banners/fibonacci.jpg and b/web-app/assets/banners/fibonacci.jpg differ diff --git a/web-app/assets/banners/flames.jpg b/web-app/assets/banners/flames.jpg index b1c6275..e01dcfe 100644 Binary files a/web-app/assets/banners/flames.jpg and b/web-app/assets/banners/flames.jpg differ diff --git a/web-app/assets/banners/flappy-game.jpg b/web-app/assets/banners/flappy-game.jpg index bc7731c..823725e 100644 Binary files a/web-app/assets/banners/flappy-game.jpg and b/web-app/assets/banners/flappy-game.jpg differ diff --git a/web-app/assets/banners/hangman.jpg b/web-app/assets/banners/hangman.jpg index 5c9469b..ef82af8 100644 Binary files a/web-app/assets/banners/hangman.jpg and b/web-app/assets/banners/hangman.jpg differ diff --git a/web-app/assets/banners/math-quiz.jpg b/web-app/assets/banners/math-quiz.jpg index e0e7bf8..1a2fa27 100644 Binary files a/web-app/assets/banners/math-quiz.jpg and b/web-app/assets/banners/math-quiz.jpg differ diff --git a/web-app/assets/banners/morse-code.jpg b/web-app/assets/banners/morse-code.jpg index d316211..11e58c0 100644 Binary files a/web-app/assets/banners/morse-code.jpg and b/web-app/assets/banners/morse-code.jpg differ diff --git a/web-app/assets/banners/number-converter.jpg b/web-app/assets/banners/number-converter.jpg index d69cf76..305181d 100644 Binary files a/web-app/assets/banners/number-converter.jpg and b/web-app/assets/banners/number-converter.jpg differ diff --git a/web-app/assets/banners/number-guessing.jpg b/web-app/assets/banners/number-guessing.jpg index a3e29e9..e69d8bb 100644 Binary files a/web-app/assets/banners/number-guessing.jpg and b/web-app/assets/banners/number-guessing.jpg differ diff --git a/web-app/assets/banners/pascal-triangle.jpg b/web-app/assets/banners/pascal-triangle.jpg index a8cacf5..fe25b2c 100644 Binary files a/web-app/assets/banners/pascal-triangle.jpg and b/web-app/assets/banners/pascal-triangle.jpg differ diff --git a/web-app/assets/banners/password-forge.jpg b/web-app/assets/banners/password-forge.jpg index 1929a70..f74f2bd 100644 Binary files a/web-app/assets/banners/password-forge.jpg and b/web-app/assets/banners/password-forge.jpg differ diff --git a/web-app/assets/banners/prime-analyzer.jpg b/web-app/assets/banners/prime-analyzer.jpg index daecbee..a8ec209 100644 Binary files a/web-app/assets/banners/prime-analyzer.jpg and b/web-app/assets/banners/prime-analyzer.jpg differ diff --git a/web-app/assets/banners/productive-pet.jpg b/web-app/assets/banners/productive-pet.jpg new file mode 100644 index 0000000..a8e726b Binary files /dev/null and b/web-app/assets/banners/productive-pet.jpg differ diff --git a/web-app/assets/banners/progression-recognizer.jpg b/web-app/assets/banners/progression-recognizer.jpg index e0027da..858e217 100644 Binary files a/web-app/assets/banners/progression-recognizer.jpg and b/web-app/assets/banners/progression-recognizer.jpg differ diff --git a/web-app/assets/banners/projectile-motion.jpg b/web-app/assets/banners/projectile-motion.jpg index 2ca8787..83f1c06 100644 Binary files a/web-app/assets/banners/projectile-motion.jpg and b/web-app/assets/banners/projectile-motion.jpg differ diff --git a/web-app/assets/banners/rock-paper-scissor.jpg b/web-app/assets/banners/rock-paper-scissor.jpg index 0f13b07..e719f91 100644 Binary files a/web-app/assets/banners/rock-paper-scissor.jpg and b/web-app/assets/banners/rock-paper-scissor.jpg differ diff --git a/web-app/assets/banners/snake-game.jpg b/web-app/assets/banners/snake-game.jpg index b437e4c..7b18632 100644 Binary files a/web-app/assets/banners/snake-game.jpg and b/web-app/assets/banners/snake-game.jpg differ diff --git a/web-app/assets/banners/tic-tac-toe.jpg b/web-app/assets/banners/tic-tac-toe.jpg index 781d9af..0a1dd61 100644 Binary files a/web-app/assets/banners/tic-tac-toe.jpg and b/web-app/assets/banners/tic-tac-toe.jpg differ diff --git a/web-app/assets/banners/tower-of-hanoi.jpg b/web-app/assets/banners/tower-of-hanoi.jpg index e6533e9..ffb6c64 100644 Binary files a/web-app/assets/banners/tower-of-hanoi.jpg and b/web-app/assets/banners/tower-of-hanoi.jpg differ diff --git a/web-app/assets/banners/typing-speed-tester.jpg b/web-app/assets/banners/typing-speed-tester.jpg index 91f6346..8bd7336 100644 Binary files a/web-app/assets/banners/typing-speed-tester.jpg and b/web-app/assets/banners/typing-speed-tester.jpg differ diff --git a/web-app/assets/banners/whack-a-mole.jpg b/web-app/assets/banners/whack-a-mole.jpg index 81e36e7..28456e0 100644 Binary files a/web-app/assets/banners/whack-a-mole.jpg and b/web-app/assets/banners/whack-a-mole.jpg differ diff --git a/web-app/assets/banners/word-scramble.jpg b/web-app/assets/banners/word-scramble.jpg index 53b6458..211bddd 100644 Binary files a/web-app/assets/banners/word-scramble.jpg and b/web-app/assets/banners/word-scramble.jpg differ diff --git a/web-app/css/styles.css b/web-app/css/styles.css index 0d4bdac..292e3cd 100644 --- a/web-app/css/styles.css +++ b/web-app/css/styles.css @@ -1,5106 +1,3098 @@ -/* ── Google Fonts ── */ -@import url('https://fonts.googleapis.com/css2?family=Fredoka:wght@400;500;600;700&family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;0,9..40,600;1,9..40,400&display=swap'); +/* ═══════════════════════════════════════════════════════════════ + PYTHON PROJECTS GALLERY — PREMIUM DESIGN SYSTEM + Typography: Inter (UI) + JetBrains Mono (Tech) + Palette: Deep slates, soft glows, glassmorphism + Motion: transform/opacity only, cubic-bezier(0.16,1,0.3,1) + ═══════════════════════════════════════════════════════════════ */ -/* ═══════════════════════════════════════ - CSS VARIABLES -═══════════════════════════════════════ */ +@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&display=swap'); + +/* ── Design Tokens ──────────────────────────────────────────── */ :root { - --accent-color: #22c55e; - --primary-color: var(--accent-color); - --secondary-color: #06b6d4; - --success-color: #10b981; - --danger-color: #ef4444; - --warning-color: #f59e0b; - --bg-color: #081120; - --surface-color: #111827; - --panel-color: #0f172a; - --control-color: #172033; - --text-color: #e5e7eb; - --text-secondary: #94a3b8; - --border-color: #1f2937; - --accent-soft: rgba(34, 197, 94, 0.14); - --accent-border: rgba(34, 197, 94, 0.35); - --overlay-color: rgba(8, 17, 32, 0.78); - --on-accent: #ffffff; - --footer-card-color: #e9830f; - --footer-card-light: rgba(252, 176, 91, 0.08); - --footer-card-border: rgba(252, 176, 91, 0.25); - --shadow: 0 10px 30px rgba(0, 0, 0, 0.3); - --shadow-modal: 0 25px 50px -12px rgba(0, 0, 0, 0.45); - --transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - --transition-fast: 0.15s cubic-bezier(0.4, 0, 0.2, 1); - --motion-duration: 0.3s; - - --hero-green-200: #bbf7d0; - --hero-green-300: #86efac; - --hero-green-400: #4ade80; - --hero-green-500: #22c55e; - --hero-green-600: #05431c; - --hero-green-nav-bg: #0d2118; - --hero-green-border: rgba(74, 222, 128, 0.35); - --hero-green-glow: rgba(34, 197, 94, 0.28); - --hero-font-display: 'Fredoka', 'Segoe UI', sans-serif; - --hero-font-body: 'DM Sans', 'Segoe UI', sans-serif; -} - -[data-theme="light"] { - --primary-color: #16a34a; - --secondary-color: #22c55e; - --bg-color: #f0fdf4; - --surface-color: #ffffff; - --border-color: #bbf7d0; - --shadow: 0 20px 60px rgba(34, 197, 94, 0.22); - --shadow-modal: 0 35px 80px -12px rgba(22, 163, 74, 0.35); - --text-color: #0f172a; - --text-secondary: #64748b; - --footer-card-light: rgba(252, 176, 91, 0.06); - --footer-card-border: rgba(252, 176, 91, 0.18); - - --hero-green-nav-bg: #e8faf0; - --hero-green-border: rgba(22, 163, 74, 0.30); - --hero-green-glow: rgba(22, 163, 74, 0.15); -} - -/* ═══════════════════════════════════════ - RESET & BASE -═══════════════════════════════════════ */ + --font-sans: 'Space Grotesk', system-ui, -apple-system, sans-serif; + --font-mono: 'JetBrains Mono', 'SF Mono', 'Fira Code', monospace; + + --accent: #22c55e; + --accent-soft: rgba(34, 197, 94, 0.12); + --accent-glow: rgba(34, 197, 94, 0.25); + --danger: #ef4444; + --warning: #f59e0b; + --success: #10b981; + + --bg: #0c0f1a; + --bg-elevated: rgba(18, 22, 36, 0.85); + --bg-glass: rgba(255, 255, 255, 0.04); + --surface: rgba(18, 22, 36, 0.92); + --surface-hover: rgba(30, 35, 52, 0.95); + --border: rgba(255, 255, 255, 0.06); + --border-accent: rgba(34, 197, 94, 0.2); + --text: #f0f2f5; + --text-secondary: #8892a4; + --text-tertiary: #4a5368; + --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.3); + --shadow-md: 0 4px 20px rgba(0, 0, 0, 0.4); + --shadow-lg: 0 8px 40px rgba(0, 0, 0, 0.5); + --shadow-glow: 0 0 30px var(--accent-glow); + + --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1); + --duration-fast: 200ms; + --duration-base: 300ms; + --duration-slow: 500ms; + + --nav-height: 72px; +} + +[data-theme='light'] { + --bg: #f4f6f9; + --bg-elevated: rgba(255, 255, 255, 0.85); + --bg-glass: rgba(0, 0, 0, 0.02); + --surface: rgba(255, 255, 255, 0.92); + --surface-hover: #ffffff; + --border: rgba(0, 0, 0, 0.06); + --border-accent: rgba(34, 197, 94, 0.25); + --text: #0f172a; + --text-secondary: #64748b; + --text-tertiary: #94a3b8; + --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.06); + --shadow-md: 0 4px 20px rgba(0, 0, 0, 0.08); + --shadow-lg: 0 8px 40px rgba(0, 0, 0, 0.1); + --shadow-glow: 0 0 30px rgba(34, 197, 94, 0.15); +} + +/* ── Reset & Base ──────────────────────────────────────────── */ *, *::before, *::after { - margin: 0; - padding: 0; - box-sizing: border-box; + margin: 0; + padding: 0; + box-sizing: border-box; } html { - scroll-behavior: smooth; + scroll-behavior: smooth; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } -[data-theme="light"] { - --bg-color: #f8fafc; - --surface-color: #ffffff; - --panel-color: #f1f5f9; - --control-color: #ffffff; - --text-color: #1f2937; - --text-secondary: #6b7280; - --border-color: #d8dee8; - --accent-soft: rgba(34, 197, 94, 0.1); - --accent-border: rgba(34, 197, 94, 0.26); - --overlay-color: rgba(15, 23, 42, 0.46); - --shadow: 0 10px 30px rgba(0, 0, 0, 0.1); - --shadow-modal: 0 25px 50px -12px rgba(15, 23, 42, 0.18); +body { + font-family: var(--font-sans); + background: var(--bg); + color: var(--text); + line-height: 1.6; + transition: background var(--duration-base) ease, color var(--duration-base) ease; + overflow-x: hidden; } -* { - margin: 0; - padding: 0; - box-sizing: border-box; +::selection { + background: var(--accent); + color: #fff; } -a, -button, -input, -select, -textarea, -.project-card, -.whack-hole { - touch-action: manipulation; - /* Removes 300ms mobile click delay globally */ +.container { + max-width: 1280px; + margin: 0 auto; + padding: 0 24px; } -body { - font-family: 'Space Grotesk', 'Segoe UI', sans-serif; - background: var(--bg-color); - color: var(--text-color); - line-height: 1.6; - transition: background-color var(--motion-duration) ease, color var(--motion-duration) ease; +a { + color: var(--accent); + text-decoration: none; } -#main-content { - scroll-margin-top: 5.5rem; +button { + font-family: inherit; + cursor: pointer; + border: none; + background: none; + color: inherit; } -.container { - margin: 0 auto; - padding: 0 20px; +img { + max-width: 100%; + display: block; } -/* ═══════════════════════════════════════ - NAVBAR -═══════════════════════════════════════ */ -.navbar { - background: color-mix(in srgb, var(--surface-color) 86%, transparent); - backdrop-filter: blur(8px); - padding: 1.25rem 0; - position: sticky; - top: 0; - z-index: 1000; - border-bottom: 1px solid var(--border-color); +/* ── Scrollbar ─────────────────────────────────────────────── */ +::-webkit-scrollbar { + width: 6px; } - -.navbar .container { - display: flex; - justify-content:space-around; - align-items: center; +::-webkit-scrollbar-track { + background: transparent; } -.navbar-brand { - display: flex; - align-items: baseline; - gap: 1.5rem; +::-webkit-scrollbar-thumb { + background: var(--text-tertiary); + border-radius: 3px; } - -.logo { - display: flex; - align-items: center; +::-webkit-scrollbar-thumb:hover { + background: var(--text-secondary); } -.navbar-tagline { - font-size: 0.875rem; - color: var(--text-secondary); - font-family: 'Space Grotesk', sans-serif; - font-weight: 500; - letter-spacing: 0.025em; - opacity: 0.85; - white-space: nowrap; +/* ── Visually Hidden ───────────────────────────────────────── */ +.visually-hidden { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; } -.logo h1 { - font-size: 1.5rem; - color: var(--text-color); - font-family: 'IBM Plex Mono', monospace; - font-weight: 600; - letter-spacing: 0.5px; - display: flex; - align-items: baseline; - gap: 0; - margin: 0; +.skip-link { + position: absolute; + top: -100px; + left: 50%; + transform: translateX(-50%); + background: var(--accent); + color: #fff; + padding: 12px 24px; + border-radius: 8px; + font-weight: 600; + z-index: 5000; + transition: top var(--duration-fast) ease; } - -.logo-py { - font-family: 'IBM Plex Mono', monospace; - font-weight: 400; - font-size: 1.1rem; - color: var(--text-secondary); - letter-spacing: 0.02em; +.skip-link:focus { + top: 16px; } -.logo-mini { - font-family: 'Space Grotesk', sans-serif; - font-weight: 700; - font-size: 1.3rem; - color: var(--text-color); - margin: 0 0.15rem; +/* ── Cursor Glow ───────────────────────────────────────────── */ +.cursor-glow { + position: fixed; + width: 320px; + height: 320px; + border-radius: 50%; + pointer-events: none; + z-index: 0; + background: radial-gradient(circle, var(--accent-glow) 0%, transparent 70%); + transform: translate3d(-50%, -50%, 0); + opacity: 0; + transition: opacity 0.4s ease; + will-change: transform; +} + +[data-theme='light'] .cursor-glow { + opacity: 0 !important; +} + +/* ═══════════════════════════════════════════════════════════════ + NAVIGATION — Floating Glass Dock + ═══════════════════════════════════════════════════════════════ */ +/* ── Primary Left Sidebar Dock ────────────────────────────── */ +.sidebar-dock { + position: fixed; + top: 16px; + left: 16px; + bottom: 16px; + width: 280px; + background: var(--bg-glass); + backdrop-filter: blur(24px) saturate(1.4); + -webkit-backdrop-filter: blur(24px) saturate(1.4); + border: 1px solid var(--border); + border-radius: 24px; + padding: 24px; + display: flex; + flex-direction: column; + gap: 28px; + box-shadow: var(--shadow-lg); + z-index: 1000; + transition: transform var(--duration-slow) var(--ease-out-expo), + opacity var(--duration-slow) var(--ease-out-expo), + left var(--duration-slow) var(--ease-out-expo); + transform: translateX(-120%); + opacity: 0; + pointer-events: none; } -.logo-parens { - font-family: 'IBM Plex Mono', monospace; - font-weight: 600; - color: var(--accent-color); - font-size: 1.1rem; - margin-left: 0.1rem; +body.sidebar-active .sidebar-dock { + transform: translateX(0); + opacity: 1; + pointer-events: auto; } -.logo-icon { - width: 42px; - height: 42px; - color: var(--hero-green-400) !important; - stroke-width: 2; - filter: drop-shadow(0 0 6px var(--hero-green-glow)); - flex-shrink: 0; +[data-theme='light'] .sidebar-dock { + background: rgba(255, 255, 255, 0.7); + border-color: rgba(0, 0, 0, 0.06); } -.nav-controls { - display: flex; - gap: 1rem; - align-items: center; +.sidebar-brand { + display: flex; + flex-direction: column; + gap: 4px; } -.theme-toggle, -.sound-toggle { - background: var(--accent-color); - border: none; - color: var(--on-accent); - width: 40px; - height: 40px; - border-radius: 50%; - cursor: pointer; - font-size: 1.2rem; - transition: var(--transition); - display: flex; - align-items: center; - justify-content: center; +.sidebar-search { + width: 100%; } -.theme-toggle:hover, -.sound-toggle:hover { - background: rgba(74, 222, 128, 0.28); - box-shadow: 0 0 14px var(--hero-green-glow); - color: var(--hero-green-200); - transform: scale(1.1); - box-shadow: 0 0 15px rgba(34, 197, 94, 0.4); +.sidebar-search .search-box { + width: 100%; + max-width: none; } -.theme-toggle:active, -.sound-toggle:active { - transform: scale(0.95); +.sidebar-search .search-dropdown { + width: 280px; + left: calc(100% + 12px); + top: 0; } -/* ═══════════════════════════════════════ - HERO SECTION -═══════════════════════════════════════ */ -.hero-section { - position: relative; - padding: 7.5rem 0 3.5rem; - min-height: 520px; - text-align: center; - overflow: hidden; - background-color: var(--bg-color); - transition: background-color 0.5s ease; +.sidebar-menu-wrapper { + display: flex; + flex-direction: column; + gap: 12px; + flex: 1; + overflow-y: auto; + scrollbar-width: none; /* Hide scrollbar for clean vertical list */ } - -/* Canvas fills the full hero */ -#boardCanvas { - position: absolute; - inset: 0; - width: 100% !important; - height: 100% !important; - z-index: 0; - pointer-events: none; - opacity: 0.82; - display: block; +.sidebar-menu-wrapper::-webkit-scrollbar { + display: none; } -/* Stunning Premium Geometric Grid Background */ -.hero-background { - position: absolute; - inset: 0; - overflow: hidden; - z-index: 0; - pointer-events: none; - background-color: transparent; - background-image: - linear-gradient(to right, var(--border-color) 1px, transparent 1px), - linear-gradient(to bottom, var(--border-color) 1px, transparent 1px); - background-size: 36px 36px; - opacity: 0.25; - mask-image: radial-gradient(ellipse at top, black 40%, transparent 80%); - -webkit-mask-image: radial-gradient(ellipse at top, black 40%, transparent 80%); +.sidebar-menu-title { + font-family: var(--font-mono); + font-size: 0.72rem; + font-weight: 600; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--text-tertiary); + padding-left: 6px; } -.hero-background::before { - content: ''; - position: absolute; - top: 0; - left: 50%; - transform: translateX(-50%); - width: 100vw; - height: 100%; - max-width: 1200px; - background: radial-gradient(circle at top, var(--accent-soft) 0%, transparent 70%); - z-index: -1; +.sidebar-menu { + display: flex; + flex-direction: column; + gap: 6px; + padding: 2px 6px; /* Horizontal safe gutter prevents box-shadow clipping and boundary overlap */ +} + +/* Sidebar menu tab colors */ +.sidebar-tab[data-category='all'] { + --tab-color: var(--accent); + --tab-color-glow: rgba(16, 185, 129, 0.25); + --tab-color-soft: rgba(16, 185, 129, 0.08); + --tab-color-soft-hover: rgba(16, 185, 129, 0.03); +} +.sidebar-tab[data-category='games'] { + --tab-color: #ec4899; /* Neon Pink */ + --tab-color-glow: rgba(236, 72, 153, 0.25); + --tab-color-soft: rgba(236, 72, 153, 0.08); + --tab-color-soft-hover: rgba(236, 72, 153, 0.03); +} +.sidebar-tab[data-category='math'] { + --tab-color: #6366f1; /* Cosmic Indigo/Blue */ + --tab-color-glow: rgba(99, 102, 241, 0.25); + --tab-color-soft: rgba(99, 102, 241, 0.08); + --tab-color-soft-hover: rgba(99, 102, 241, 0.03); +} +.sidebar-tab[data-category='utilities'] { + --tab-color: #10b981; /* Mint Green */ + --tab-color-glow: rgba(16, 185, 129, 0.25); + --tab-color-soft: rgba(16, 185, 129, 0.08); + --tab-color-soft-hover: rgba(16, 185, 129, 0.03); +} +.sidebar-tab[data-category='favorites'] { + --tab-color: #f59e0b; /* Amber Gold */ + --tab-color-glow: rgba(245, 158, 11, 0.25); + --tab-color-soft: rgba(245, 158, 11, 0.08); + --tab-color-soft-hover: rgba(245, 158, 11, 0.03); +} +.sidebar-tab[data-category='playground'] { + --tab-color: #a78bfa; /* Electric Purple */ + --tab-color-glow: rgba(167, 139, 250, 0.25); + --tab-color-soft: rgba(167, 139, 250, 0.08); + --tab-color-soft-hover: rgba(167, 139, 250, 0.03); +} + +.sidebar-menu .sidebar-tab { + display: flex; + align-items: center; + width: 100%; + text-align: left; + gap: 12px; + padding: 10px 14px; + border-radius: 12px; + font-family: var(--font-sans); + font-size: 0.88rem; + font-weight: 500; + color: var(--text-secondary); + background: transparent; + border: 1px solid transparent; + transition: all var(--duration-fast) ease; + cursor: pointer; + position: relative; /* Added relative context for active line */ +} + +/* Active sidebar category glow line */ +.sidebar-menu .sidebar-tab::before { + content: ''; + position: absolute; + left: 4px; + top: 20%; + height: 60%; + width: 3px; + background: var(--tab-color); + border-radius: 99px; + opacity: 0; + transform: scaleY(0); + transition: transform var(--duration-base) var(--ease-out-expo), opacity var(--duration-base) ease; } -[data-theme="light"] .hero-background { - opacity: 0.6; +.sidebar-menu .sidebar-tab.active::before { + opacity: 1; + transform: scaleY(1); } -/* Vignette fade at bottom of hero */ -.hero-section::after { - content: ''; - position: absolute; - bottom: 0; - left: 0; - right: 0; - bottom: 0; - pointer-events: none; - z-index: 0; +.sidebar-menu .sidebar-tab:hover { + color: var(--text); + background: var(--tab-color-soft-hover); + border-color: rgba(255, 255, 255, 0.03); + padding-left: 16px; /* Stable horizontal content shift */ } -.hero-code-snippets { - position: absolute; - inset: 0; - z-index: 1; - overflow: hidden; - pointer-events: none; +[data-theme='light'] .sidebar-menu .sidebar-tab:hover { + background: rgba(0, 0, 0, 0.015); + border-color: rgba(0, 0, 0, 0.02); } -.code-snippet { - position: absolute; - color: color-mix(in srgb, var(--text-secondary) 74%, var(--accent-color)); - font-family: 'IBM Plex Mono', monospace; - font-size: clamp(0.72rem, 1.2vw, 0.92rem); - letter-spacing: 0.02em; - opacity: 0.16; - text-shadow: 0 0 20px rgba(34, 197, 94, 0.24); - white-space: nowrap; - transform-origin: center; +.sidebar-menu .sidebar-tab.active { + color: var(--text); + background: var(--tab-color-soft); + border-color: var(--tab-color-glow); + box-shadow: 0 0 16px var(--tab-color-glow); + padding-left: 18px; /* Stable horizontal active content shift */ } -.snippet-one { - top: 15%; - left: 10%; - animation: codeFloatOne 18s ease-in-out infinite; +/* Sidebar Search Box Focus Glow */ +.sidebar-search .search-box input:focus { + border-color: var(--accent) !important; + box-shadow: 0 0 16px rgba(16, 185, 129, 0.15) !important; + background: rgba(255, 255, 255, 0.04) !important; } -.snippet-two { - top: 25%; - right: 10%; - opacity: 0.18; - animation: codeFloatTwo 22s ease-in-out infinite; +/* Sidebar Controls bounce */ +.sidebar-controls button { + position: relative; + transition: all var(--duration-fast) ease; } - -.snippet-three { - top: 50%; - left: 12%; - opacity: 0.22; - animation: codeFloatThree 20s ease-in-out infinite; +.sidebar-controls button i { + transition: transform var(--duration-fast) cubic-bezier(0.34, 1.56, 0.64, 1); } - -.snippet-four { - top: 60%; - right: 8%; - animation: codeFloatTwo 24s ease-in-out infinite reverse; +.sidebar-controls button:hover i { + transform: scale(1.18) rotate(8deg); } -.snippet-five { - top: 75%; - left: 15%; - animation: codeFloatOne 21s ease-in-out infinite reverse; +.sidebar-menu .sidebar-tab .sidebar-icon { + display: flex; + align-items: center; + justify-content: center; + width: 20px; + height: 20px; + color: var(--text-tertiary); + transition: all var(--duration-fast) ease; } -.snippet-six { - top: 20%; - right: 25%; - opacity: 0.15; - animation: codeFloatThree 26s ease-in-out infinite; +.sidebar-menu .sidebar-tab:hover .sidebar-icon, +.sidebar-menu .sidebar-tab.active .sidebar-icon { + color: var(--tab-color); } -.snippet-seven { - top: 80%; - right: 18%; - opacity: 0.15; - animation: codeFloatTwo 28s ease-in-out infinite; +.sidebar-menu .sidebar-tab.active .sidebar-icon { + transform: scale(1.15); } -[data-theme="light"] .code-snippet { - color: color-mix(in srgb, var(--text-secondary) 70%, var(--accent-color)); - opacity: 0.15; - text-shadow: none; +.sidebar-footer { + display: flex; + align-items: center; + justify-content: space-between; + padding-top: 16px; + border-top: 1px solid var(--border); } -@keyframes codeFloatOne { - - 0%, - 100% { - transform: translate3d(0, 0, 0) rotate(4deg); - } - - 50% { - transform: translate3d(-12px, 9px, 0) rotate(-2deg); - } +.sidebar-controls { + display: flex; + align-items: center; + gap: 8px; + width: 100%; } -@keyframes codeFloatTwo { +.sidebar-controls .sound-toggle, +.sidebar-controls .theme-toggle { + flex: 1; + height: 40px; + border-radius: 10px; + background: var(--bg-glass); + border: 1px solid var(--border); + color: var(--text-secondary); + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: all var(--duration-fast) ease; +} - 0%, - 100% { - transform: translate3d(0, 0, 0) rotate(-5deg); - } +.sidebar-controls .sound-toggle:hover, +.sidebar-controls .theme-toggle:hover { + background: var(--accent-soft); + color: var(--accent); + border-color: var(--border-accent); +} - 50% { - transform: translate3d(10px, -8px, 0) rotate(3deg); - } +/* Mobile Toggles and Layout Adjustments */ +.mobile-sidebar-toggle { + display: none; + position: fixed; + top: 16px; + left: 16px; + width: 44px; + height: 44px; + border-radius: 12px; + border: 1px solid var(--border); + background: var(--bg-glass); + backdrop-filter: blur(16px); + -webkit-backdrop-filter: blur(16px); + color: var(--text); + align-items: center; + justify-content: center; + z-index: 1001; + cursor: pointer; + transition: all var(--duration-fast) ease; + box-shadow: var(--shadow-md); } -@keyframes codeFloatThree { +.mobile-sidebar-toggle:hover { + background: var(--accent-soft); + color: var(--accent); + border-color: var(--border-accent); +} - 0%, - 100% { - transform: translate3d(0, 0, 0) rotate(-3deg); - } +/* ── Floating Hero Zone Controls ───────────────────────────── */ - 50% { - transform: translate3d(14px, 7px, 0) rotate(2deg); - } +.hero-controls { + position: fixed; + top: 20px; + right: 24px; + display: flex; + align-items: center; + gap: 12px; + z-index: 1001; + opacity: 1; + transition: opacity var(--duration-base) ease; } -.hero-background.color-bends-container { - position: absolute; +.hero-control-btn { + width: 44px; + height: 44px; + border-radius: 12px; + border: 1px solid var(--border); + background: var(--bg-glass); + backdrop-filter: blur(12px) saturate(1.4); + -webkit-backdrop-filter: blur(12px) saturate(1.4); + color: var(--text-secondary); + display: inline-flex; + align-items: center; + justify-content: center; + cursor: pointer; + box-shadow: var(--shadow-sm); + transition: all var(--duration-fast) ease; } -.color-bends-container { - position: relative; - width: 100%; - height: 100%; - overflow: hidden; - isolation: isolate; - background: - radial-gradient(circle at top left, rgba(34, 197, 94, 0.12), transparent 40%), - #081120; +[data-theme='light'] .hero-control-btn { + background: rgba(255, 255, 255, 0.7); + border-color: rgba(0, 0, 0, 0.06); } -[data-theme="light"] .color-bends-container { - background: - radial-gradient(circle at top left, rgba(34, 197, 94, 0.08), transparent 42%), - #f8fafc; +.hero-control-btn:hover { + transform: translateY(-1px); + color: var(--text); + background: var(--accent-soft); + border-color: var(--border-accent); } -.color-bends-container::before, -.color-bends-container::after { - content: ''; - position: absolute; - pointer-events: none; +.hero-control-btn i { + transition: transform var(--duration-fast) cubic-bezier(0.34, 1.56, 0.64, 1); } - -.color-bends-container::before { - inset: 0; - background: - linear-gradient(rgba(148, 163, 184, 0.035) 1px, transparent 1px), - linear-gradient(90deg, rgba(148, 163, 184, 0.025) 1px, transparent 1px); - background-size: 36px 36px; - mask-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.72), transparent 78%); - opacity: 0.65; +.hero-control-btn:hover i { + transform: scale(1.18) rotate(8deg); } -.color-bends-container::after { - inset: 0; - background: linear-gradient(to bottom, transparent 0%, rgba(8, 17, 32, 0.72) 100%); - opacity: 0.78; +/* Hide controls when sidebar is visible */ +body.sidebar-active .hero-controls { + opacity: 0; + pointer-events: none; } -[data-theme="light"] .color-bends-container::before { - opacity: 0.35; -} +/* Desktop padding adjustment */ +@media (min-width: 1100px) { + body { + padding-left: 0 !important; + } + + .hero-section, + .hero-timeline-section { + padding-left: 0 !important; + margin-left: auto !important; + margin-right: auto !important; + width: 100% !important; + } -[data-theme="light"] .color-bends-container::after { - background: linear-gradient(to bottom, transparent 0%, rgba(248, 250, 252, 0.72) 100%); - opacity: 0.72; + .projects-section, + #playgroundSection, + .footer { + padding-left: 312px !important; + transition: padding-left var(--duration-slow) var(--ease-out-expo); + } } -.hero-content { - position: relative; - z-index: 2; - max-width: 680px; - margin-left: calc(50% - 350px); - margin-right: auto; - padding-top: 15px; - text-align: center; +/* Responsive/Mobile Sidebar behaviors */ +@media (max-width: 1100px) { + .sidebar-dock { + transform: translateX(-120%); + left: -280px; + opacity: 0; + } + + .sidebar-dock.open { + transform: translateX(0); + left: 16px; + opacity: 1; + pointer-events: auto; + } + + .mobile-sidebar-toggle { + display: none; + } + + body.sidebar-active .mobile-sidebar-toggle { display: flex; - flex-direction: column; - align-items: center; + } } -.hero-kicker { - color: var(--accent-color); - font-family: 'IBM Plex Mono', monospace; - font-size: 0.78rem; - font-weight: 600; - letter-spacing: 0.12em; - margin-bottom: 1rem; - text-transform: uppercase; - animation: fadeInUp 0.7s ease; +/* ── Logo ──────────────────────────────────────────────────── */ +.logo-wrap { + display: flex; + align-items: center; + gap: 10px; + text-decoration: none; + flex-shrink: 0; } -.hero-title { - font-size: clamp(2.45rem, 7vw, 4.75rem); - font-weight: 700; - margin-bottom: 1rem; - color: var(--text-color); - animation: fadeInUp 0.7s ease; - line-height: 1.1; - max-width: none; +.logo-top-row { + display: flex; + align-items: center; + gap: 10px; } -.hero-title-wrapper { - position: relative; - display: inline-block; +.sidebar-dock .logo-wrap { + flex-direction: column; + align-items: flex-start; + gap: 8px; } -.python-orbit { - position: absolute; - width: 400px; - height: 400px; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - border: 2px solid rgba(34, 197, 94, 0.15); - border-radius: 50%; - animation: revolve 20s linear infinite; - pointer-events: none; - z-index: 1; -} - -.python-orbiter { - position: absolute; - width: 50px; - height: 50px; - top: -25px; - left: 50%; - transform: translateX(-50%); - display: flex; - align-items: center; - justify-content: center; - animation: rotate 4s linear infinite; +.sidebar-dock .logo-tagline { + display: inline-flex !important; + margin-left: 0; } -.python-logo { - font-size: 2.5rem; - filter: drop-shadow(0 4px 12px rgba(34, 197, 94, 0.4)); - animation: spinPython 3s linear infinite; +.navbar-logo { + width: 38px; + height: 38px; + object-fit: contain; + filter: drop-shadow(0 0 10px var(--accent-glow)); } -@keyframes revolve { - from { - transform: translate(-50%, -50%) rotate(0deg); - } - to { - transform: translate(-50%, -50%) rotate(360deg); - } +.logo-text { + display: flex; + align-items: center; + font-family: var(--font-sans); + font-size: 1.35rem; + font-weight: 700; + letter-spacing: -0.03em; } -@keyframes rotate { - from { - transform: translateX(-50%) rotate(0deg); - } - to { - transform: translateX(-50%) rotate(360deg); - } +.logo-py { + color: var(--text-secondary); + font-weight: 500; } -@keyframes spinPython { - from { - transform: rotateY(0deg) scale(1); - } - 50% { - transform: rotateY(180deg) scale(1.1); - } - to { - transform: rotateY(360deg) scale(1); - } +.logo-mini { + color: var(--text); } -[data-theme="light"] .python-orbit { - border-color: rgba(22, 163, 74, 0.2); +.logo-parens { + color: var(--accent); + font-weight: 600; } -[data-theme="light"] .python-logo { - filter: drop-shadow(0 4px 12px rgba(22, 163, 74, 0.3)); +.logo-tagline { + display: none; + align-items: center; + gap: 6px; + padding: 4px 12px; + border-radius: 999px; + font-size: 0.72rem; + font-weight: 500; + color: var(--text-secondary); + background: var(--bg-glass); + border: 1px solid var(--border); + letter-spacing: 0.02em; + white-space: nowrap; } - -.hero-subtitle { - font-size: 1.05rem; - color: var(--text-secondary); - margin-bottom: 1rem; - animation: fadeInUp 0.7s ease 0.2s both; - font-weight: 500; - max-width: 560px; +.logo-tagline::before { + content: ''; + width: 5px; + height: 5px; + border-radius: 50%; + background: var(--accent); + box-shadow: 0 0 8px var(--accent-glow); } -.hero-meta { - display: flex; - justify-content: center; - flex-wrap: wrap; - gap: 0.5rem 1rem; - color: var(--text-secondary); - font-family: 'IBM Plex Mono', monospace; - font-size: 0.78rem; - margin-bottom: 5rem; - opacity: 0.82; - animation: fadeInUp 0.7s ease 0.3s both; +@media (min-width: 900px) { + .logo-tagline { + display: inline-flex; + } } -.hero-meta span { - display: inline-flex; - align-items: center; - gap: 0.45rem; +/* ── Search ────────────────────────────────────────────────── */ +/* Glowing Sound and Theme Toggle Icons */ +.sound-toggle i { + color: #14b8a6; /* Neon Teal */ + filter: drop-shadow(0 0 6px rgba(20, 184, 166, 0.45)); + transition: all var(--duration-fast) ease; } -.hero-meta span::before { - content: ''; - width: 4px; - height: 4px; - border-radius: 50%; - background: var(--accent-color); - opacity: 0.72; +.sound-toggle.muted i, +.sound-toggle i.fa-volume-mute { + color: #ef4444 !important; /* Muted Red */ + filter: drop-shadow(0 0 6px rgba(239, 68, 68, 0.5)) !important; } -[data-theme="light"] .hero-subtitle { - color: #14532d; +.theme-toggle i.fa-moon { + color: #8b5cf6; /* Cosmic Purple */ + filter: drop-shadow(0 0 8px rgba(139, 92, 246, 0.55)); } -.hero-features { - display: flex; - gap: 1.6rem; - justify-content: center; - align-items: center; - flex-wrap: wrap; - /* margin-bottom: 1.1rem; */ - animation: fadeInUp 0.7s ease 0.4s both; +.theme-toggle i.fa-sun { + color: #f59e0b; /* Golden Sun */ + filter: drop-shadow(0 0 8px rgba(245, 158, 11, 0.55)); } -.hero-content .btn-random-project { - animation: fadeInUp 0.7s ease 0.6s both; +.search-box { + flex: 1; + max-width: 400px; + height: 42px; + display: flex; + align-items: center; + gap: 10px; + padding: 0 14px; + border-radius: 12px; + border: 1px solid var(--border); + background: var(--bg-glass); + transition: border-color var(--duration-fast) ease, + background var(--duration-fast) ease, + box-shadow var(--duration-fast) ease; } -/* ═══════════════════════════════════════ - FEATURE BADGES (hero category tabs) -═══════════════════════════════════════ */ -.feature-badge { - position: relative; - display: inline-flex; - align-items: center; - gap: 0.38rem; - background: transparent; - padding: 0.25rem 0 0.35rem; - border: 0; - border-radius: 0; - transition: color 0.2s ease; - cursor: pointer; - font-size: 0.82rem; - font-family: 'IBM Plex Mono', monospace; - color: var(--text-secondary); - text-transform: lowercase; +.search-box i { + color: var(--text-tertiary); + font-size: 0.85rem; + flex-shrink: 0; } -.feature-badge.active { - background: transparent; - border-color: transparent; - box-shadow: none; - color: var(--text-color); +.search-box input { + flex: 1; + background: transparent; + border: none; + outline: none; + color: var(--text); + font-size: 0.9rem; + font-family: var(--font-sans); + min-width: 0; } -.feature-badge.active .feature-label { - color: var(--text-color); +.search-box input::placeholder { + color: var(--text-tertiary); } -.feature-badge:hover { - transform: none; - box-shadow: none; - border-color: transparent; - color: var(--text-color); +.search-box:focus-within { + border-color: var(--accent); + background: var(--bg-elevated); + box-shadow: var(--shadow-glow); } -.feature-badge::before { - content: ''; - width: 5px; - height: 5px; - border-radius: 50%; - background: var(--border-color); - transition: background-color 0.2s ease, box-shadow 0.2s ease; +.search-box:focus-within i { + color: var(--accent); } -.feature-badge::after { - content: ''; - position: absolute; - left: 0; - right: 0; - bottom: 0; - height: 1px; - background: var(--accent-color); - opacity: 0; - transform: scaleX(0.4); - transform-origin: left; - transition: opacity 0.2s ease, transform 0.2s ease; +/* ── Nav Controls ──────────────────────────────────────────── */ +.nav-controls { + display: flex; + align-items: center; + gap: 8px; + flex-shrink: 0; +} + +.sound-toggle, +.theme-toggle { + width: 38px; + height: 38px; + border-radius: 10px; + border: 1px solid var(--border); + background: var(--bg-glass); + color: var(--text-secondary); + font-size: 0.9rem; + display: flex; + align-items: center; + justify-content: center; + transition: all var(--duration-fast) ease; } -.feature-badge:hover::after, -.feature-badge.active::after { - opacity: 1; - transform: scaleX(1); +.sound-toggle:hover, +.theme-toggle:hover { + background: var(--accent-soft); + color: var(--accent); + border-color: var(--border-accent); + transform: translateY(-1px); } -.feature-badge.active::before { - background: var(--accent-color); - box-shadow: 0 0 10px rgba(34, 197, 94, 0.55); +.mobile-menu-toggle { + display: none; + width: 38px; + height: 38px; + border-radius: 10px; + border: 1px solid var(--border); + background: var(--bg-glass); + color: var(--text); + font-size: 0.9rem; + align-items: center; + justify-content: center; + flex-shrink: 0; } -.feature-icon { - display: none; +/* ═══════════════════════════════════════════════════════════════ + HERO SECTION — Cinematic + ═══════════════════════════════════════════════════════════════ */ +.hero-section { + position: relative; + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + padding: calc(var(--nav-height) + 40px) 24px 60px; + overflow: hidden; } -.feature-label { - font-weight: 600; - color: currentColor; +#boardCanvas, +#timelineCanvas { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + z-index: 0; + pointer-events: none; + opacity: 0.5; } -.btn-explore { - background: linear-gradient(135deg, #6abf8d, #7fcfbd); - color: var(--on-accent); - border: none; - padding: 1rem 2.5rem; - font-size: 1.1rem; - font-weight: 700; - border-radius: 50px; - cursor: pointer; - transition: transform 0.18s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.18s ease; - box-shadow: 0 8px 0 rgba(0, 0, 0, 0.1), 0 12px 30px rgba(34, 197, 94, 0.3); - animation: fadeInUp 0.7s ease 0.6s both; - display: inline-block; - position: relative; - top: 0; +.hero-background { + position: absolute; + inset: 0; + z-index: 0; + overflow: hidden; + pointer-events: none; } -.btn-explore:hover { - transform: translateY(-6px); - box-shadow: 0 12px 0 rgba(0, 0, 0, 0.08), 0 20px 45px rgba(34, 197, 94, 0.4); +.hero-background::before { + content: ''; + position: absolute; + inset: 0; + background-image: + linear-gradient(to right, var(--border) 1px, transparent 1px), + linear-gradient(to bottom, var(--border) 1px, transparent 1px); + background-size: 48px 48px; + opacity: 0.3; + mask-image: radial-gradient(ellipse at center, black 30%, transparent 70%); + -webkit-mask-image: radial-gradient(ellipse at center, black 30%, transparent 70%); } -.btn-explore:active { - transform: translateY(4px); - box-shadow: 0 4px 0 rgba(0, 0, 0, 0.08), 0 6px 12px rgba(34, 197, 94, 0.2); - top: 2px; - filter: brightness(0.95); +.hero-background::after { + content: ''; + position: absolute; + top: 0; + left: 50%; + transform: translateX(-50%); + width: 800px; + height: 600px; + background: radial-gradient(circle, var(--accent-soft) 0%, transparent 60%); + opacity: 0.6; + pointer-events: none; } -.badge { - background: var(--surface-color); - padding: 0.5rem 1.5rem; - border-radius: 50px; - font-size: 1rem; - border: 1px solid var(--border-color); - transition: var(--transition); - display: inline-flex; - align-items: center; - gap: 0.5rem; - white-space: nowrap; - box-shadow: 0 4px 18px var(--hero-green-glow); +.hero-orb { + position: absolute; + border-radius: 50%; + filter: blur(80px); + opacity: 0.3; + pointer-events: none; + z-index: 0; } -.btn-random-project:hover { - background: linear-gradient(135deg, var(--hero-green-500), var(--hero-green-400)); - border-color: var(--hero-green-400); - transform: translateY(-2px); - box-shadow: 0 8px 28px rgba(74, 222, 128, 0.45); +.hero-orb-one { + width: 400px; + height: 400px; + left: -100px; + top: 10%; + background: radial-gradient(circle, rgba(34, 197, 94, 0.3), transparent 70%); + animation: orbFloat 20s ease-in-out infinite; } -.btn-random-project:active { - transform: translateY(0); +.hero-orb-two { + width: 500px; + height: 500px; + right: -150px; + top: 20%; + background: radial-gradient(circle, rgba(99, 102, 241, 0.2), transparent 70%); + animation: orbFloat 25s ease-in-out infinite reverse; } -.btn-random-project i { - font-size: 1.1rem; +.hero-orb-three { + width: 300px; + height: 300px; + left: 40%; + bottom: -50px; + background: radial-gradient(circle, rgba(34, 197, 94, 0.15), transparent 70%); + animation: orbFloat 18s ease-in-out infinite 5s; } -.btn-random-project.shuffle { - animation: shuffle 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55); +@keyframes orbFloat { + 0%, 100% { transform: translate(0, 0) scale(1); } + 33% { transform: translate(30px, -40px) scale(1.05); } + 66% { transform: translate(-20px, 20px) scale(0.95); } } -/* ═══════════════════════════════════════ - TABS SECTION (search + filter bar) -═══════════════════════════════════════ */ -.tabs-section { - padding: 3rem 0 2rem; - background: var(--surface-color); - position: relative; - overflow: hidden; +/* ── Hero Content ──────────────────────────────────────────── */ +.hero-shell { + position: relative; + z-index: 2; + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + max-width: 800px; + width: 100%; } -.tabs-section::before { - content: ''; - position: absolute; - top: -50%; - right: -20%; - width: 400px; - height: 400px; - background: radial-gradient(circle, rgba(34, 197, 94, 0.05), transparent); - border-radius: 50%; - pointer-events: none; - animation: float 6s ease-in-out infinite; +.hero-badge-row { + display: flex; + align-items: center; + gap: 12px; + margin-bottom: 32px; + flex-wrap: wrap; + justify-content: center; } -.tabs-section .container { - display: flex; - flex-direction: column; - gap: 2rem; - position: relative; - z-index: 1; +/* ── Hero Big Logo & Brand Title ────────────────────────────── */ +.hero-logo-header { + display: flex; + align-items: center; + justify-content: center; + gap: clamp(12px, 3vw, 20px); + margin-bottom: 24px; + animation: fadeInUp var(--duration-slow) var(--ease-out-expo) both; } -/* ===== THEMED COMPONENTS ===== */ -.project-content, -.ui-panel { - background: var(--surface-color); - border: 1px solid var(--border-color); - border-radius: 16px; - color: var(--text-color); - box-shadow: var(--shadow); +.hero-logo-image { + width: clamp(48px, 8vw, 72px); + height: clamp(48px, 8vw, 72px); + object-fit: contain; + filter: drop-shadow(0 0 20px var(--accent-glow)); + transition: transform var(--duration-base) var(--ease-out-expo); } -.project-content { - padding: 1.5rem; +.hero-logo-header:hover .hero-logo-image { + transform: scale(1.12) rotate(12deg); } -.ui-subpanel { - background: var(--panel-color); - border: 1px solid var(--border-color); - border-radius: 12px; - color: var(--text-color); +.hero-logo-text { + font-family: var(--font-sans); + font-size: clamp(2.4rem, 6vw, 4.2rem); + font-weight: 800; + letter-spacing: -0.04em; + display: flex; + align-items: center; + line-height: 1; } -.ui-stat, -.score-box, -.score-card { - background: var(--accent-soft); - border: 1px solid var(--accent-border); - border-radius: 10px; - color: var(--text-color); +.hero-logo-py { + color: var(--text-secondary); + font-weight: 500; } -.ui-button, -.game-btn, -.control-btn, -.btn-primary { - background: var(--accent-color); - border: 1px solid var(--accent-color); - border-radius: 10px; - color: var(--on-accent); - cursor: pointer; - font-family: inherit; - font-weight: 700; - transition: var(--transition); +.hero-logo-mini { + color: var(--text); + background: linear-gradient(135deg, var(--text), var(--text-secondary)); + -webkit-background-clip: text; + background-clip: text; } -.ui-button:hover, -.game-btn:hover, -.control-btn:hover, -.btn-primary:hover { - box-shadow: 0 8px 24px rgba(34, 197, 94, 0.28); - transform: translateY(-2px); +.hero-logo-parens { + background: linear-gradient(135deg, var(--accent), #06b6d4); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + font-weight: 800; } -.ui-input { - background: var(--control-color); - border: 1px solid var(--border-color); - border-radius: 10px; - color: var(--text-color); - font-family: inherit; +[data-theme='light'] .hero-logo-py { + color: rgba(0, 0, 0, 0.45); } -.ui-canvas-frame { - background: var(--panel-color); - border: 2px solid var(--accent-border); - border-radius: 12px; - box-shadow: var(--shadow); +[data-theme='light'] .hero-logo-mini { + color: #000; } -/* ===== MODERN SEARCH BAR ===== */ -.search-section { - position: relative; - margin-bottom: 2rem; +/* ── Hero Badge Row (Glassified Kicker & Status Button) ──────── */ +.hero-kicker, +.hero-status { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 8px 16px; + border-radius: 999px; + font-family: var(--font-mono); + font-size: 0.72rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--text-secondary); + background: rgba(255, 255, 255, 0.05); + backdrop-filter: blur(12px) saturate(1.4); + -webkit-backdrop-filter: blur(12px) saturate(1.4); + border: 1px solid rgba(255, 255, 255, 0.12); + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.15); + transition: transform var(--duration-fast) var(--ease-out-expo), + background var(--duration-fast) ease, + border-color var(--duration-fast) ease, + box-shadow var(--duration-fast) ease; + position: relative; + overflow: hidden; + cursor: pointer; + text-decoration: none; } -.search-wrapper { - position: relative; - display: flex; - align-items: center; - justify-content: space-between; - max-width: 600px; - margin: 0 auto; - width: 100%; - height: 56px; +[data-theme='light'] .hero-kicker, +[data-theme='light'] .hero-status { + background: rgba(0, 0, 0, 0.04); + border-color: rgba(0, 0, 0, 0.08); + color: var(--text); +} - /* Glassmorphism: Blur effect with fallback */ - background: color-mix(in srgb, var(--control-color) 88%, transparent); - backdrop-filter: blur(20px) saturate(180%); - -webkit-backdrop-filter: blur(20px) saturate(180%); - - /* Styling - More prominent border and glow */ - border: 2px solid var(--accent-border); - border-radius: 50px; - padding: 0 1.5rem; - - /* Shadows and glow - More visible */ - box-shadow: - inset 0 1px 2px 0 color-mix(in srgb, var(--surface-color) 70%, transparent), - 0 10px 30px -14px rgba(99, 102, 241, 0.45); - transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); -} - -.search-wrapper:hover { - border-color: var(--accent-color); - background: var(--control-color); - box-shadow: - 0 12px 34px -16px rgba(99, 102, 241, 0.5); -} - -.search-wrapper:focus-within { - border-color: var(--accent-color); - background: var(--control-color); - box-shadow: - 0 0 0 3px var(--accent-soft), - 0 20px 46px -24px rgba(99, 102, 241, 0.6); - animation: searchGlow 3s ease-in-out infinite; -} - -@keyframes searchGlow { - - 0%, - 100% { - box-shadow: - inset 0 1px 3px 0 rgba(99, 102, 241, 0.15), - inset 0 -1px 2px 0 rgba(0, 0, 0, 0.5), - 0 0 0 2px rgba(99, 102, 241, 0.4), - 0 20px 60px -10px rgba(99, 102, 241, 0.5), - 0 0 50px -5px rgba(99, 102, 241, 0.35); - } - - 50% { - box-shadow: - inset 0 1px 3px 0 rgba(139, 92, 246, 0.2), - inset 0 -1px 2px 0 rgba(0, 0, 0, 0.5), - 0 0 0 2px rgba(139, 92, 246, 0.5), - 0 25px 70px -10px rgba(99, 102, 241, 0.6), - 0 0 60px -5px rgba(139, 92, 246, 0.4); - } -} - -.search-icon-wrapper { - display: flex; - align-items: center; - justify-content: center; - margin-right: 0.75rem; - color: var(--accent-color); - flex-shrink: 0; +.hero-kicker::before, +.hero-status::before { + content: ''; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.18), transparent); + transition: none; } -.search-icon { - font-size: 1.2rem; - transition: all 0.3s ease; +.hero-kicker:hover, +.hero-status:hover { + transform: translateY(-2px); + background: rgba(255, 255, 255, 0.15); + border-color: var(--accent); + box-shadow: 0 6px 20px var(--accent-glow); } -.search-wrapper:focus-within .search-icon { - color: var(--accent-color); - transform: scale(1.1); +[data-theme='light'] .hero-kicker:hover, +[data-theme='light'] .hero-status:hover { + background: rgba(0, 0, 0, 0.07); + border-color: var(--accent); } -.search-input { - flex: 1; - background: transparent; - border: none; - color: var(--text-color); - font-size: 1.05rem; - font-weight: 500; - outline: none; - padding: 0.8rem 0.5rem; - font-family: inherit; - min-width: 0; - height: 100%; +.hero-kicker:hover::before, +.hero-status:hover::before { + left: 100%; + transition: left 0.75s ease-in-out; } -.search-input::placeholder { - color: var(--text-secondary); - font-weight: 400; +/* Dots Inside Hero Badges */ +.hero-kicker-dot { + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--accent); + box-shadow: 0 0 8px var(--accent-glow); + animation: pulseKicker 2s ease-in-out infinite; } -.search-input:-webkit-autofill, -.search-input:-webkit-autofill:hover, -.search-input:-webkit-autofill:focus, -.search-input:-webkit-autofill:active { - -webkit-box-shadow: 0 0 0 1000px transparent inset; - -webkit-text-fill-color: var(--text-color); +@keyframes pulseKicker { + 0%, 100% { + opacity: 1; + transform: scale(1.1); + } + 50% { + opacity: 0.4; + transform: scale(0.85); + } } -.search-shortcut { - display: flex; - align-items: center; - margin-left: 0.5rem; - opacity: 0.7; - transition: all 0.3s ease; - font-size: 0.8rem; - white-space: nowrap; +.hero-status-dot { + width: 6px; + height: 6px; + border-radius: 50%; + background: #ef4444; /* Blinking Red Dot */ + box-shadow: 0 0 10px rgba(239, 68, 68, 0.8), 0 0 20px rgba(239, 68, 68, 0.4); + animation: blinkRed 1.2s ease-in-out infinite; } -.search-wrapper:focus-within .search-shortcut { - opacity: 0; - display: none; +@keyframes blinkRed { + 0%, 100% { + opacity: 1; + transform: scale(1.1); + box-shadow: 0 0 10px rgba(239, 68, 68, 0.8), 0 0 20px rgba(239, 68, 68, 0.4); + } + 50% { + opacity: 0.35; + transform: scale(0.85); + box-shadow: 0 0 4px rgba(239, 68, 68, 0.2); + } } -.search-shortcut kbd { - background: var(--accent-soft); - border: 1px solid var(--accent-border); - border-radius: 6px; - padding: 0.35rem 0.6rem; - font-family: 'Courier New', monospace; - font-size: 0.8rem; - font-weight: 600; - color: var(--accent-color); - box-shadow: 0 2px 0 rgba(0, 0, 0, 0.1); +.hero-title-wrapper { + margin-bottom: 20px; } -.search-loader { - display: flex; - align-items: center; - justify-content: center; - margin: 0 0.5rem; - flex-shrink: 0; +.hero-title { + font-family: var(--font-sans); + font-size: clamp(3rem, 8vw, 5.5rem); + font-weight: 800; + line-height: 1.05; + letter-spacing: -0.04em; + color: var(--text); } -.spinner { - width: 18px; - height: 18px; - border: 2.5px solid rgba(99, 102, 241, 0.2); - border-top-color: rgba(99, 102, 241, 0.9); - border-radius: 50%; - animation: spin 1s linear infinite; +.hero-title-line { + display: block; } -@keyframes spin { - to { - transform: rotate(360deg); - } -} - -.search-clear-btn { - background: none; - border: none; - color: rgba(99, 102, 241, 0.7); - cursor: pointer; - font-size: 1.2rem; - padding: 0.4rem 0.6rem; - margin-left: 0.25rem; - transition: all 0.2s ease; - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - width: 40px; - height: 40px; - flex-shrink: 0; +.hero-title-accent { + background: linear-gradient(135deg, var(--accent), #06b6d4); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; } -.search-clear-btn:hover { - background: rgba(99, 102, 241, 0.15); - color: rgba(99, 102, 241, 1); - transform: rotate(90deg) scale(1.1); +.hero-subtitle { + font-size: clamp(1rem, 2vw, 1.15rem); + color: var(--text-secondary); + max-width: 560px; + margin: 0 auto 28px; + line-height: 1.7; } -.search-clear-btn:focus { - outline: 2px solid rgba(99, 102, 241, 0.6); - outline-offset: 2px; +.hero-cta-row { + display: flex; + align-items: center; + gap: 12px; + flex-wrap: wrap; + justify-content: center; + margin-bottom: 32px; } -.search-clear-btn:active { - transform: rotate(90deg) scale(0.95); +.btn-primary-hero, +.btn-contribution-hero { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 14px 28px; + border-radius: 12px; + font-family: var(--font-sans); + font-size: 0.95rem; + font-weight: 600; + color: #fff; + background: rgba(255, 255, 255, 0.05); + backdrop-filter: blur(12px) saturate(1.4); + -webkit-backdrop-filter: blur(12px) saturate(1.4); + border: 1px solid rgba(255, 255, 255, 0.12); + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2); + transition: transform var(--duration-fast) var(--ease-out-expo), + background var(--duration-fast) ease, + border-color var(--duration-fast) ease, + box-shadow var(--duration-fast) ease; + position: relative; + overflow: hidden; + text-decoration: none; } -/* ===== AUTOCOMPLETE DROPDOWN ===== */ -.search-dropdown { - position: absolute; - top: 100%; - left: 50%; - transform: translateX(-50%); - width: calc(100% + 4rem); - max-width: 600px; - margin-top: 0.75rem; - background: color-mix(in srgb, var(--surface-color) 94%, transparent); - backdrop-filter: blur(15px); - -webkit-backdrop-filter: blur(15px); - border: 2px solid var(--accent-border); - border-radius: 20px; - box-shadow: - var(--shadow-modal), - 0 0 36px rgba(99, 102, 241, 0.08); - z-index: 1000; - animation: dropdownSlideIn 0.3s cubic-bezier(0.4, 0, 0.2, 1); -} - -@keyframes dropdownSlideIn { - from { - opacity: 0; - transform: translateX(-50%) translateY(-10px); - } - - to { - opacity: 1; - transform: translateX(-50%) translateY(0); - } +[data-theme='light'] .btn-primary-hero, +[data-theme='light'] .btn-contribution-hero { + background: rgba(0, 0, 0, 0.04); + border-color: rgba(0, 0, 0, 0.08); + color: var(--text); } -.dropdown-content { - max-height: 400px; - overflow-y: auto; - padding: 0; +.btn-primary-hero::before, +.btn-contribution-hero::before { + content: ''; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.18), transparent); + transition: none; } -.dropdown-section { - padding: 0.75rem 0; - border-bottom: 1px solid rgba(99, 102, 241, 0.1); +.btn-primary-hero:hover, +.btn-contribution-hero:hover { + transform: translateY(-2px); + background: rgba(255, 255, 255, 0.15); + border-color: var(--accent); + box-shadow: 0 8px 30px var(--accent-glow); } -.dropdown-section:last-child { - border-bottom: none; +[data-theme='light'] .btn-primary-hero:hover, +[data-theme='light'] .btn-contribution-hero:hover { + background: rgba(0, 0, 0, 0.07); + border-color: var(--accent); } -.dropdown-section-title { - padding: 0.5rem 1.5rem; - font-size: 0.8rem; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0.5px; - color: var(--text-secondary); - display: flex; - align-items: center; - gap: 0.5rem; - opacity: 0.7; +.btn-primary-hero:hover::before, +.btn-contribution-hero:hover::before { + left: 100%; + transition: left 0.75s ease-in-out; } -.dropdown-section-title i { - font-size: 0.75rem; +.btn-primary-hero:active, +.btn-contribution-hero:active { + transform: translateY(0); } -.dropdown-list { - display: flex; - flex-direction: column; +.btn-secondary-hero { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 14px 24px; + border-radius: 12px; + font-family: var(--font-sans); + font-size: 0.95rem; + font-weight: 600; + color: var(--text); + background: var(--bg-glass); + border: 1px solid var(--border); + transition: all var(--duration-fast) ease; } -.dropdown-item { - padding: 0.75rem 1.5rem; - cursor: pointer; - transition: all 0.2s ease; - display: flex; - align-items: center; - gap: 0.75rem; - color: var(--text-secondary); - border-left: 3px solid transparent; +.btn-secondary-hero:hover { + background: var(--bg-elevated); + border-color: var(--text-tertiary); + transform: translateY(-2px); } -.dropdown-item:hover, -.dropdown-item.selected { - background: rgba(34, 197, 94, 0.08); - color: var(--accent-color); - border-left-color: var(--accent-color); +/* ── Hero Meta ─────────────────────────────────────────────── */ +.hero-meta { + display: flex; + gap: 24px; + flex-wrap: wrap; + justify-content: center; + font-family: var(--font-mono); + font-size: 0.78rem; + color: var(--text-secondary); } -.dropdown-item:active { - background: rgba(34, 197, 94, 0.15); +.hero-meta span { + display: inline-flex; + align-items: center; + gap: 8px; } -.dropdown-item-icon { - font-size: 0.9rem; - opacity: 0.7; +.hero-meta strong { + color: var(--text); + font-weight: 600; } -.dropdown-item-text { - flex: 1; - font-weight: 500; +.hero-meta span::before { + content: ''; + width: 4px; + height: 4px; + border-radius: 50%; + background: var(--accent); + opacity: 0.6; } -.dropdown-item-tag { - display: inline-block; - background: rgba(34, 197, 94, 0.1); - color: var(--accent-color); - padding: 0.15rem 0.5rem; - border-radius: 4px; - font-size: 0.75rem; - font-weight: 600; +/* ── Hero Timeline ─────────────────────────────────────────── */ +.hero-timeline-section { + position: relative; + padding: 80px 0; + overflow: hidden; } -.dropdown-recent-item { - display: flex; - align-items: center; - justify-content: space-between; - padding: 0.5rem 1.5rem; +.timeline-container { + position: relative; + max-width: 1280px; + margin: 0 auto; + padding: 0 24px; } -.dropdown-recent-text { - display: flex; - align-items: center; - gap: 0.5rem; - flex: 1; + +.timeline-grid { + display: flex; + flex-direction: column; + gap: 80px; + position: relative; + z-index: 1; } -.dropdown-recent-remove { - background: none; - border: none; - color: var(--text-secondary); - cursor: pointer; - opacity: 0; - transition: opacity 0.2s ease; - padding: 0.25rem; - font-size: 0.9rem; +.timeline-item { + display: grid; + grid-template-columns: 1fr 40px 1fr; + gap: 32px; + align-items: center; + opacity: 0; + transform: translateY(40px); + transition: opacity 0.6s var(--ease-out-expo), transform 0.6s var(--ease-out-expo); } -.dropdown-recent-item:hover .dropdown-recent-remove { - opacity: 1; - color: var(--danger-color); +.timeline-item.visible { + opacity: 1; + transform: translateY(0); } -.dropdown-tips { - padding: 1rem 1.5rem; - font-size: 0.85rem; - color: var(--text-secondary); +.timeline-item:nth-child(odd) .timeline-content { + grid-column: 1; + text-align: right; } -.dropdown-tips p { - margin: 0 0 0.5rem 0; - font-weight: 600; - color: var(--text-color); +.timeline-item:nth-child(even) .timeline-content { + grid-column: 3; + text-align: left; } -.dropdown-tips ul { - list-style: none; - padding: 0; - margin: 0; +.timeline-node { + grid-column: 2; + display: flex; + align-items: center; + justify-content: center; + position: relative; + z-index: 2; } -.dropdown-tips li { - padding: 0.25rem 0; - opacity: 0.8; +.timeline-dot { + width: 14px; + height: 14px; + border-radius: 50%; + background: var(--bg); + border: 2px solid var(--text-tertiary); + transition: all var(--duration-base) var(--ease-out-expo); + position: relative; } -.dropdown-tips code { - background: rgba(34, 197, 94, 0.1); - color: var(--accent-color); - padding: 0.1rem 0.3rem; - border-radius: 3px; - font-family: 'Monaco', 'Courier New', monospace; - font-size: 0.8rem; - font-weight: 600; +.timeline-item.visible .timeline-dot { + border-color: var(--accent); + background: var(--accent); + box-shadow: 0 0 20px var(--accent-glow); + transform: scale(1.2); } -.dropdown-content::-webkit-scrollbar { - width: 6px; +.timeline-item.active .timeline-dot { + transform: scale(1.6); + box-shadow: 0 0 30px var(--accent-glow), 0 0 60px rgba(34, 197, 94, 0.15); } -.dropdown-content::-webkit-scrollbar-track { - background: transparent; +.timeline-dot::after { + content: ''; + position: absolute; + inset: -4px; + border-radius: 50%; + border: 2px solid var(--accent); + opacity: 0; + transform: scale(0.8); + transition: all 0.6s var(--ease-out-expo); } -.dropdown-content::-webkit-scrollbar-thumb { - background: rgba(99, 102, 241, 0.3); - border-radius: 3px; +.timeline-item.active .timeline-dot::after { + opacity: 1; + transform: scale(1); + animation: timelinePulse 2s ease-in-out infinite; } -.dropdown-content::-webkit-scrollbar-thumb:hover { - background: rgba(99, 102, 241, 0.5); +@keyframes timelinePulse { + 0%, 100% { transform: scale(1); opacity: 0.6; } + 50% { transform: scale(1.5); opacity: 0; } } -/* ===== EMPTY STATE ===== */ -.empty-state { - padding: 3rem 2rem; - text-align: center; - background: rgba(99, 102, 241, 0.05); - border: 2px dashed rgba(99, 102, 241, 0.2); - border-radius: 20px; - margin-top: 2rem; - animation: fadeIn 0.4s ease; +.timeline-item.active .timeline-content { + border-color: var(--accent); + box-shadow: 0 0 30px var(--accent-glow), var(--shadow-lg); + transform: translateY(-4px) scale(1.02); + background: var(--surface-hover); } -.empty-state p { - margin: 0.5rem 0; - color: var(--text-secondary); +.timeline-item.active .timeline-title { + background: linear-gradient(135deg, var(--accent), #22c55e); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; } -.empty-state p:first-child { - font-size: 1.5rem; - color: var(--text-color); - font-weight: 600; - margin-bottom: 0.5rem; +.timeline-content { + display: flex; + align-items: center; + gap: 16px; + padding: 24px; + border-radius: 16px; + background: var(--surface); + border: 1px solid var(--border); + backdrop-filter: blur(12px); + transition: all var(--duration-base) ease; } -.empty-state-hint { - font-size: 0.9rem !important; - color: var(--text-secondary) !important; - opacity: 0.8; +.timeline-item:nth-child(odd) .timeline-content { + flex-direction: row-reverse; } -.tabs { - display: flex; - gap: 1rem; - justify-content: center; - flex-wrap: wrap; - margin-top: 1.5rem; +.timeline-item:nth-child(even) .timeline-content { + flex-direction: row; } -.search-container { - max-width: 600px; - margin: 0 auto; +.timeline-icon { + flex-shrink: 0; + width: 28px; + height: 28px; + color: var(--accent); + transition: transform var(--duration-base) var(--ease-out-expo); } -.no-results { - text-align: center; - padding: 4rem 2rem; - animation: fadeIn 0.6s ease; + +.timeline-item:nth-child(odd) .timeline-text { + padding-right: 12px; + border-right: 2px solid var(--accent); + border-right-color: rgba(34, 197, 94, 0.5); } -.no-results-icon { - font-size: 4rem; - margin-bottom: 1rem; - opacity: 0.5; +.timeline-item:nth-child(even) .timeline-text { + padding-left: 12px; + border-left: 2px solid var(--accent); + border-left-color: rgba(34, 197, 94, 0.5); } -.no-results h3 { - font-size: 1.5rem; - margin-bottom: 0.5rem; +.timeline-item.active .timeline-icon { + transform: scale(1.2); } -.no-results p { - color: var(--text-secondary); +.timeline-text { + flex: 1; } -.tab { - background: transparent; - border: 1px solid var(--border-color); - color: var(--text-color); - padding: 0.75rem 1.5rem; - border-radius: 50px; - cursor: pointer; - font-size: 1rem; - transition: var(--transition); +.timeline-item:nth-child(odd) .timeline-text { + text-align: right; } -.tab:hover { - border-color: var(--accent-color); - transform: translateY(-2px); +.timeline-item:nth-child(even) .timeline-text { + text-align: left; } -.tab.active { - background: var(--accent-color); - border-color: var(--accent-color); - color: var(--on-accent); - box-shadow: 0 4px 14px rgba(34, 197, 94, 0.35); +.timeline-item.visible .timeline-content { + border-color: var(--border-accent); + box-shadow: var(--shadow-md); } -.tab:active { - transform: scale(0.98); +.timeline-step { + font-family: var(--font-mono); + font-size: 0.7rem; + font-weight: 600; + color: var(--accent); + letter-spacing: 0.08em; + text-transform: uppercase; + margin-bottom: 6px; + display: block; } -.hero-features .feature-badge, -.hero-features .feature-badge.tab { - background: transparent; - border: 0; - border-radius: 0; - box-shadow: none; - color: var(--text-secondary); - padding: 0.15rem 0 0.35rem; - transform: none; +.timeline-title { + font-family: var(--font-sans); + font-size: 1.15rem; + font-weight: 700; + margin-bottom: 6px; + letter-spacing: -0.02em; } -.hero-features .feature-badge.active, -.hero-features .feature-badge.tab.active { - background: transparent; - border-color: transparent; - box-shadow: none; - color: var(--text-color); +.timeline-desc { + font-size: 0.9rem; + color: var(--text-secondary); + line-height: 1.6; } -.hero-features .feature-badge:hover { - color: var(--text-color); - transform: none; +/* ── Timeline Route Badge ────────────────────────────────── */ +.timeline-route-badge { + position: absolute; + top: -55px; /* Offset upward to sit beautifully above the first timeline grid row */ + right: 24px; + font-family: var(--font-sans); + font-size: clamp(1.5rem, 4vw, 2.75rem); /* Mathematically half of the hero title clamp(3rem, 8vw, 5.5rem) */ + font-weight: 800; + line-height: 1.05; + letter-spacing: -0.04em; + background: linear-gradient(135deg, var(--accent), #06b6d4); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + z-index: 10; + pointer-events: none; + text-transform: uppercase; } -/* Tabs Wrapper */ -.tabs-wrapper { - display: flex; - align-items: center; - justify-content: space-between; - gap: 2rem; - flex-wrap: wrap; +.timeline-route-badge::after { + content: ''; + position: absolute; + bottom: -6px; /* Position beneath the text */ + left: 0; + width: 100%; + height: 4px; + background: var(--accent); + box-shadow: 0 0 10px var(--accent-glow); + border-radius: 2px; } -.tabs-wrapper .tabs { - flex: 1; - min-width: 300px; +/* ── Timeline Section Background Grid & Orbs (Matching Hero) ── */ +.timeline-background { + position: absolute; + inset: 0; + z-index: 0; + overflow: hidden; + pointer-events: none; } -/* Random Project Button */ -.btn-random-project { - background: transparent; - border: none; - color: var(--accent-color); - padding: 0.25rem 0; - border-radius: 0; - cursor: pointer; - font-family: 'IBM Plex Mono', monospace; - font-size: 0.84rem; - font-weight: 700; - letter-spacing: 0.01em; - transition: color 0.2s ease, transform 0.2s ease; - display: inline-flex; - align-items: center; - gap: 0.35rem; - white-space: nowrap; - box-shadow: none; +.timeline-background::before { + content: ''; + position: absolute; + inset: 0; + background-image: + linear-gradient(to right, var(--border) 1px, transparent 1px), + linear-gradient(to bottom, var(--border) 1px, transparent 1px); + background-size: 48px 48px; + opacity: 0.3; + mask-image: radial-gradient(ellipse at center, black 30%, transparent 70%); + -webkit-mask-image: radial-gradient(ellipse at center, black 30%, transparent 70%); } -.btn-random-project:hover { - background: transparent; - border-color: transparent; - color: var(--text-color); - transform: translateX(3px); - box-shadow: none; +.timeline-background::after { + content: ''; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 800px; + height: 800px; + background: radial-gradient(circle, var(--accent-soft) 0%, transparent 60%); + opacity: 0.5; + pointer-events: none; } -.dropdown-item:hover, -.dropdown-item.selected { - background: rgba(34, 197, 94, 0.12); - color: var(--primary-color); - border-left-color: var(--primary-color); +.timeline-orb { + position: absolute; + border-radius: 50%; + filter: blur(80px); + opacity: 0.25; + pointer-events: none; + z-index: 0; } -.dropdown-item:active { - background: rgba(34, 197, 94, 0.22); +.timeline-orb-one { + width: 400px; + height: 400px; + right: -100px; + top: 20%; + background: radial-gradient(circle, rgba(34, 197, 94, 0.25), transparent 70%); + animation: orbFloat 20s ease-in-out infinite; } -.dropdown-item-icon { - font-size: 0.9rem; - opacity: 0.7; +.timeline-orb-two { + width: 450px; + height: 450px; + left: -150px; + bottom: 15%; + background: radial-gradient(circle, rgba(6, 182, 212, 0.15), transparent 70%); + animation: orbFloat 25s ease-in-out infinite reverse; } -.dropdown-item-text { - flex: 1; - font-weight: 500; + + + +/* ── Dynamic Timeline SVG Track ──────────────────────────── */ +.timeline-svg { + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + pointer-events: none; + z-index: 2; /* Sits over the background overlays but behind content */ } -.dropdown-item-tag { - display: inline-block; - background: rgba(34, 197, 94, 0.18); - color: var(--primary-color); - padding: 0.15rem 0.5rem; - border-radius: 4px; - font-size: 0.75rem; - font-weight: 600; +.timeline-svg-track { + stroke: var(--border); + stroke-width: 6px; + stroke-linecap: round; + stroke-dasharray: 0 16; + opacity: 0.8; + transition: stroke var(--duration-base) ease; } -.dropdown-recent-item { - display: flex; - align-items: center; - justify-content: space-between; - padding: 0.5rem 1.5rem; +.timeline-svg-fill { + stroke: var(--accent); + stroke-width: 6px; + stroke-linecap: round; + stroke-dasharray: 0 16; + transition: stroke var(--duration-base) ease; + filter: drop-shadow(0 0 8px var(--accent)); } -.dropdown-recent-text { - display: flex; - align-items: center; - gap: 0.5rem; - flex: 1; +/* Ensure timeline grid elements overlap properly */ +.timeline-grid { + position: relative; + z-index: 3; /* Above timeline SVG */ } -.dropdown-recent-remove { - background: none; - border: none; - color: var(--text-secondary); - cursor: pointer; - opacity: 0; - transition: opacity 0.2s ease; - padding: 0.25rem; - font-size: 0.9rem; +.timeline-item { + position: relative; + z-index: 4; /* Card layering above grid root */ } -.dropdown-recent-item:hover .dropdown-recent-remove { - opacity: 1; - color: var(--danger-color); +.timeline-node { + position: relative; + z-index: 5; /* Step nodes sit on top of everything */ } -.dropdown-tips { - padding: 1rem 1.5rem; - font-size: 0.85rem; - color: var(--text-secondary); + +/* ═══════════════════════════════════════════════════════════════ + FILTER SIDEBAR + ═══════════════════════════════════════════════════════════════ */ +.filter-sidebar { + display: flex; + align-items: center; + gap: 4px; + padding: 6px; + background: var(--surface); + border: 1px solid var(--border); + border-radius: 14px; + margin: 0 auto 48px; + max-width: 100%; + overflow-x: auto; + scrollbar-width: none; + -ms-overflow-style: none; +} + +.filter-sidebar::-webkit-scrollbar { + display: none; } -.dropdown-tips p { - margin: 0 0 0.5rem; - font-weight: 600; - color: var(--text-color); +.sidebar-header, +.sidebar-divider, +.sidebar-random-btn, +.sidebar-badge { + display: none; } -.dropdown-tips ul { - list-style: none; - padding: 0; - margin: 0; +.sidebar-tab { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 8px 14px; + border-radius: 10px; + font-family: var(--font-sans); + font-size: 0.8rem; + font-weight: 500; + color: var(--text-secondary); + background: transparent; + border: 1px solid transparent; + white-space: nowrap; + transition: all var(--duration-fast) ease; + cursor: pointer; } -.dropdown-tips li { - padding: 0.25rem 0; - opacity: 0.8; +.sidebar-tab:hover { + color: var(--text); + background: var(--tab-color-soft-hover); + border-color: rgba(255, 255, 255, 0.03); } -.dropdown-tips code { - background: rgba(34, 197, 94, 0.12); - color: var(--primary-color); - padding: 0.1rem 0.3rem; - border-radius: 3px; - font-family: 'Monaco', 'Courier New', monospace; - font-size: 0.8rem; - font-weight: 600; +[data-theme='light'] .sidebar-tab:hover { + background: rgba(0, 0, 0, 0.015); + border-color: rgba(0, 0, 0, 0.02); } -.dropdown-content::-webkit-scrollbar { - width: 6px; +.sidebar-tab.active { + color: var(--text); + background: var(--tab-color-soft); + border-color: var(--tab-color-glow); + box-shadow: 0 0 12px var(--tab-color-glow); } -.dropdown-content::-webkit-scrollbar-track { - background: transparent; +.sidebar-icon { + display: flex; + align-items: center; + justify-content: center; + width: 18px; + height: 18px; + flex-shrink: 0; + color: var(--text-tertiary); + transition: all var(--duration-fast) ease; } -.dropdown-content::-webkit-scrollbar-thumb { - background: rgba(34, 197, 94, 0.3); - border-radius: 3px; +.sidebar-tab:hover .sidebar-icon, +.sidebar-tab.active .sidebar-icon { + color: var(--tab-color); } -.dropdown-content::-webkit-scrollbar-thumb:hover { - background: rgba(34, 197, 94, 0.5); +.sidebar-icon svg { + width: 16px; + height: 16px; + stroke-width: 2; } -/* ═══════════════════════════════════════ - EMPTY / NO-RESULTS STATES -═══════════════════════════════════════ */ -.empty-state { - padding: 3rem 2rem; - text-align: center; - background: rgba(34, 197, 94, 0.04); - border: 2px dashed rgba(34, 197, 94, 0.18); - border-radius: 20px; - margin-top: 2rem; - animation: fadeIn 0.4s ease; +.sidebar-label { + flex-shrink: 0; } -.empty-state p { - margin: 0.5rem 0; - color: var(--text-secondary); +/* ═══════════════════════════════════════════════════════════════ + PROJECTS SECTION — Bento Grid + ═══════════════════════════════════════════════════════════════ */ +.projects-section { + padding: 40px 0 80px; + position: relative; + z-index: 1; } -.empty-state p:first-child { - font-size: 1.5rem; - color: var(--text-color); - font-weight: 600; - margin-bottom: 0.5rem; +.section-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 40px; + gap: 16px; + flex-wrap: wrap; } -.empty-state-hint { - font-size: 0.9rem !important; - opacity: 0.8; +.section-heading { + font-family: var(--font-sans); + font-size: clamp(1.5rem, 3vw, 2rem); + font-weight: 700; + letter-spacing: -0.03em; } -.no-results { - text-align: center; - padding: 4rem 2rem; - animation: fadeIn 0.6s ease; +.section-heading span { + color: var(--text-secondary); + font-weight: 400; } -.no-results-icon { - font-size: 4rem; - margin-bottom: 1rem; - opacity: 0.5; +.btn-random-project { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 10px 20px; + border-radius: 10px; + font-family: var(--font-sans); + font-size: 0.85rem; + font-weight: 600; + color: var(--text); + background: var(--bg-glass); + border: 1px solid var(--border); + transition: all var(--duration-fast) ease; + white-space: nowrap; } -.no-results h3 { - font-size: 1.5rem; - margin-bottom: 0.5rem; +.btn-random-project:hover { + background: var(--accent-soft); + border-color: var(--border-accent); + color: var(--accent); + transform: translateY(-1px); } -.no-results p { - color: var(--text-secondary); +.btn-random-project.shuffle i { + animation: spin 0.4s ease; } -/* ═══════════════════════════════════════ - PROJECT CARDS - IMPROVED VISIBILITY -═══════════════════════════════════════ */ -.projects-section { - padding: 4rem 0; +@keyframes spin { + to { transform: rotate(360deg); } } -.faq-section { - padding: 4rem 0; +/* ── Bento Grid ────────────────────────────────────────────── */ +.projects-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 16px; } -/* Main card style – solid background, clear borders */ +/* Project Card */ .project-card { - position: relative; - overflow: hidden; - padding: 1.75rem 1.5rem 1.5rem; - display: flex; - flex-direction: column; - border-radius: 28px; - background: var(--surface-color); - border: 3px solid var(--accent-border); - box-shadow: 0 8px 0 rgba(106, 191, 141, 0.18), var(--shadow); - transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.2s ease, border-color 0.2s ease; - backdrop-filter: none; - -webkit-backdrop-filter: none; + position: relative; + display: flex; + flex-direction: column; + padding: 24px; + border-radius: 20px; + background: var(--surface); + border: 1px solid var(--border); + cursor: pointer; + transition: transform var(--duration-base) var(--ease-out-expo), + border-color var(--duration-fast) ease, + box-shadow var(--duration-base) ease, + background var(--duration-fast) ease; + overflow: hidden; + -webkit-user-select: none; + user-select: none; } .project-card:hover { - transform: translateY(-8px) scale(1.02); - border-color: var(--accent-color); - box-shadow: 0 12px 0 rgba(106, 191, 141, 0.15), 0 24px 40px rgba(0, 0, 0, 0.12); -} - -.project-card:active { - transform: translateY(-2px) scale(0.98); - box-shadow: 0 4px 0 rgba(106, 191, 141, 0.1), 0 8px 20px rgba(0, 0, 0, 0.08); + transform: translateY(-4px); + border-color: var(--border-accent); + box-shadow: 0 12px 40px rgba(0, 0, 0, 0.3); + background: var(--surface-hover); } -/* Light mode specific card background */ -[data-theme="light"] .project-card { - background: #ffffff; - border-color: rgba(106, 191, 141, 0.4); +[data-theme='light'] .project-card:hover { + box-shadow: 0 12px 40px rgba(0, 0, 0, 0.08); } -[data-theme="dark"] .project-card { - background: #1e1a18; - border-color: rgba(159, 221, 181, 0.3); +.project-card:active { + transform: translateY(-1px); } -/* Prominent button styling */ -.btn-play { - background: var(--accent-color); - color: var(--on-accent); - border: none; - padding: 0.7rem 1.4rem; - border-radius: 40px; - font-weight: 700; - font-size: 0.9rem; - cursor: pointer; - transition: transform 0.15s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.15s ease, background 0.15s ease; - margin-top: 0.75rem; - width: fit-content; - box-shadow: 0 6px 0 rgba(0, 0, 0, 0.12), 0 6px 16px rgba(0, 0, 0, 0.1); - position: relative; - top: 0; +/* ── Card Banner ──────────────────────────────────────────── */ +.card-banner { + width: calc(100% + 48px); + max-width: none; + margin: -24px -24px 16px; + height: 160px; + object-fit: cover; + display: block; + border-radius: 20px 20px 0 0; + flex-shrink: 0; + transition: transform 0.4s var(--ease-out-expo), filter 0.4s ease; + will-change: transform; + background: var(--bg-glass); } -.btn-play:hover { - transform: translateY(-4px); - background: var(--accent-color); - filter: brightness(1.1); - box-shadow: 0 10px 0 rgba(0, 0, 0, 0.1), 0 12px 28px rgba(106, 191, 141, 0.35); +.project-card:hover .card-banner { + transform: scale(1.03); + filter: brightness(1.08) saturate(1.05); } -.btn-play:active { - transform: translateY(4px); - box-shadow: 0 2px 0 rgba(0, 0, 0, 0.1), 0 4px 8px rgba(0, 0, 0, 0.08); - top: 2px; - filter: brightness(0.95); +.project-card .card-banner + .card-category { + margin-top: 0; } -.faq-trigger:hover, -.faq-trigger:focus-visible { - background: rgba(99, 102, 241, 0.08); - outline: none; +/* Gradient overlay on banner for depth */ +.card-banner-wrap { + position: relative; + width: calc(100% + 48px); + margin: -24px -24px 0; + overflow: hidden; + flex-shrink: 0; } -.faq-trigger:focus-visible { - box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.18); +.card-banner-wrap .card-banner { + width: 100%; + margin: 0; + height: 160px; + border-radius: 0; } -.faq-label { - display: inline-flex; - align-items: center; - gap: 0.85rem; +.card-banner-wrap::after { + content: ''; + position: absolute; + inset: 0; + background: linear-gradient(to bottom, transparent 50%, var(--surface) 100%); + pointer-events: none; } -.faq-label i { - color: var(--primary-color); - min-width: 1.25rem; - font-size: 1.05rem; +/* Banner-less cards get a subtle gradient header */ +.card-gradient-head { + width: calc(100% + 48px); + margin: -24px -24px 16px; + height: 80px; + border-radius: 20px 20px 0 0; + flex-shrink: 0; + background: linear-gradient(135deg, var(--accent-soft), transparent 60%); + opacity: 0.5; } -.faq-icon { - display: inline-flex; - align-items: center; - justify-content: center; - transition: transform 0.25s ease; +/* Card glow border that tracks cursor */ +.project-card::before { + content: ''; + position: absolute; + inset: -1px; + border-radius: 20px; + background: radial-gradient(400px circle at var(--mouse-x, 50%) var(--mouse-y, 50%), var(--accent-glow), transparent 40%); + opacity: 0; + transition: opacity 0.3s ease; + pointer-events: none; + z-index: 0; } -.faq-card.open .faq-icon { - transform: rotate(180deg); +.project-card:hover::before { + opacity: 1; } -.faq-panel { - max-height: 0; - overflow: hidden; - padding: 0 1.5rem; - transition: max-height 0.32s ease, padding 0.32s ease; +.project-card > * { + position: relative; + z-index: 1; } -.faq-card.open .faq-panel { - max-height: 1000px; - padding-bottom: 1.25rem; +/* Card Layout */ +.card-category { + display: inline-flex; + align-items: center; + gap: 4px; + width: fit-content; + padding: 4px 10px; + border-radius: 6px; + font-family: var(--font-mono); + font-size: 0.65rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--accent); + background: var(--accent-soft); + border: 1px solid var(--border-accent); + margin-bottom: 12px; } -.faq-panel p { - margin: 0; - color: var(--text-secondary); - line-height: 1.8; - font-size: 0.98rem; +.card-banner + .card-category, +.card-banner-wrap + .card-category { + margin-top: 0; } -.faq-panel ul { - margin: 0.75rem 0 0 1.1rem; - color: var(--text-secondary); - line-height: 1.8; +.card-category .cat-icon { + width: 12px; + height: 12px; } -.faq-panel ul li { - margin-bottom: 0.55rem; +.card-actions { + display: flex; + align-items: center; + gap: 6px; + margin-bottom: 12px; + flex-wrap: wrap; } -.faq-panel pre { - margin: 0.75rem 0 0; - padding: 1rem; - background: rgba(99, 102, 241, 0.08); - border: 1px solid var(--border-color); - border-radius: 0.85rem; - overflow-x: auto; - color: var(--text-color); - font-family: 'Courier New', monospace; - font-size: 0.92rem; - line-height: 1.6; +.btn-play { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 6px 14px; + border-radius: 8px; + font-family: var(--font-sans); + font-size: 0.78rem; + font-weight: 600; + color: var(--text); + background: var(--bg-glass); + border: 1px solid var(--border); + transition: all var(--duration-fast) ease; } -.faq-panel code { - white-space: pre-wrap; +.btn-play:hover { + background: var(--accent-soft); + border-color: var(--border-accent); + color: var(--accent); + transform: scale(1.02); } -@media (max-width: 860px) { - .faq-grid { - grid-template-columns: 1fr; - } +.btn-favorite, +.btn-share { + width: 30px; + height: 30px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 8px; + font-size: 0.8rem; + color: var(--text-tertiary); + background: transparent; + border: 1px solid transparent; + transition: all var(--duration-fast) ease; + flex-shrink: 0; } -@media (max-width: 640px) { - .faq-section { - padding: 3rem 0; - } - - .faq-trigger { - padding: 1rem 1.25rem; - } - - .faq-panel { - padding: 0 1.25rem; - } +.btn-favorite:hover, +.btn-share:hover { + background: var(--bg-glass); + border-color: var(--border); + color: var(--text-secondary); + transform: scale(1.1); } -.projects-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); - gap: 1.2rem; - margin-top: 2rem; - align-items: stretch; +.btn-favorite.active { + color: var(--warning); } -/* Remove any older conflicting .project-card rules that might still exist */ -.project-card:not([data-category]) { - /* fallback, but our main rule above already covers all */ +.btn-share svg { + width: 13px; + height: 13px; } - .project-card h3 { - font-size: 1.15rem; - line-height: 1.3; - margin-bottom: 0.35rem; - color: var(--text-color); - font-weight: 700; - width: 100%; + font-family: var(--font-sans); + font-size: 1.05rem; + font-weight: 700; + letter-spacing: -0.02em; + margin-bottom: 6px; + line-height: 1.3; } .project-card p { - color: var(--text-secondary); - margin-bottom: 0; - font-size: 0.85rem; - line-height: 1.45; - width: 100%; + font-size: 0.85rem; + color: var(--text-secondary); + line-height: 1.5; + margin-bottom: 0; + flex: 1; } -.btn-generate { - background: var(--accent-color); - color: var(--on-accent); - border: none; - padding: 0.75rem 2rem; - border-radius: 50px; - cursor: pointer; - font-size: 1rem; - font-weight: 600; - transition: var(--transition); +.card-metrics { + display: flex; + align-items: center; + gap: 16px; + margin-top: 16px; + padding-top: 16px; + border-top: 1px solid var(--border); + font-family: var(--font-mono); + font-size: 0.7rem; + color: var(--text-tertiary); } -.btn-generate:hover { - transform: scale(1.05); - box-shadow: 0 5px 20px rgba(34, 197, 94, 0.4); +.card-metrics span { + display: inline-flex; + align-items: center; + gap: 4px; } -/* Icon colour classes */ -.game-icon-rps { - color: #999fee; +.project-card-badge { + display: none; } -.game-icon-blackjack { - color: #8b5cf6; +.project-card-meta { + display: none; } -.game-icon-dice { - color: #e04f4f; +/* ── Bento Grid Sizing ─────────────────────────────────────── */ +.project-card[data-category='games'] { + grid-column: span 1; } -.game-icon-coin { - color: #eab308; +.project-card[data-category='math'] { + grid-column: span 1; } -.game-icon-target { - color: #5bc085; +.project-card[data-category='utilities'] { + grid-column: span 1; } -.game-icon-hangman { - color: #b99b48; +/* ── Search Dropdown ───────────────────────────────────────── */ +.search-dropdown { + position: absolute; + top: calc(100% + 8px); + left: 0; + right: 0; + background: var(--surface); + border: 1px solid var(--border); + border-radius: 14px; + box-shadow: var(--shadow-lg); + z-index: 100; + display: none; + overflow: hidden; + backdrop-filter: blur(20px); } -.game-icon-word { - color: #8a99ee; +.search-dropdown.active { + display: block; + animation: dropdownIn 0.2s var(--ease-out-expo); } -.game-icon-love { - color: #ec4899; +@keyframes dropdownIn { + from { + opacity: 0; + transform: translateY(-8px); + } + to { + opacity: 1; + transform: translateY(0); + } } -.game-icon-grid { - color: #5cf6a1; +.dropdown-content { + max-height: 360px; + overflow-y: auto; } -.game-icon-lock { - color: #06b6d4; +.dropdown-section { + padding: 8px 0; } -.game-icon-brain { - color: #f59e0b; +.dropdown-section-title { + padding: 8px 16px; + font-size: 0.7rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--text-tertiary); } -.game-icon-snake { - color: #22c55e; +.dropdown-list { + display: flex; + flex-direction: column; } -.game-icon-memory { - color: rgb(199, 199, 0); +.dropdown-item { + display: flex; + align-items: center; + gap: 10px; + padding: 10px 16px; + cursor: pointer; + transition: background var(--duration-fast) ease; + color: var(--text-secondary); } -.game-icon-hammer { - color: #8a5834; +.dropdown-item:hover, +.dropdown-item.selected { + background: var(--accent-soft); + color: var(--text); } -.game-icon-puzzle { - color: #3b82f6; +.dropdown-item-icon { + width: 24px; + height: 24px; + border-radius: 6px; + overflow: hidden; + flex-shrink: 0; } -.game-icon-bird { - color: #38bdf8; +.dropdown-item-icon img { + width: 100%; + height: 100%; + object-fit: cover; } -.math-icon-fibonacci { - color: #10b981; +.dropdown-item-text { + flex: 1; + font-size: 0.85rem; + font-weight: 500; } -.math-icon-progress { - color: #06b6d4; +.dropdown-item-tag { + font-family: var(--font-mono); + font-size: 0.65rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.04em; + color: var(--accent); + background: var(--accent-soft); + padding: 2px 8px; + border-radius: 4px; } -.math-icon-triangle { - color: #f59e0b; +.recent-searches-section, +.tips-section, +.no-results-message { + padding: 16px; } -.math-icon-gem { - color: #f65c71; +.dropdown-recent-item { + display: flex; + align-items: center; + justify-content: space-between; + padding: 8px 16px; } -.math-icon-calc { - color: #06b6d4; +.dropdown-recent-text { + display: flex; + align-items: center; + gap: 10px; + flex: 1; + cursor: pointer; + color: var(--text-secondary); + font-size: 0.85rem; } -.math-icon-number { - color: #84cc16; +.dropdown-recent-text:hover { + color: var(--text); } -.math-icon-prime { - color: rgb(247, 79, 107); +.dropdown-recent-remove { + background: none; + border: none; + color: var(--text-tertiary); + cursor: pointer; + padding: 4px; + font-size: 0.8rem; + opacity: 0; + transition: opacity var(--duration-fast) ease; } -.math-icon-rocket { - color: #f97316; +.dropdown-recent-item:hover .dropdown-recent-remove { + opacity: 1; } -.math-icon-compass { - color: #14b8a6; +.tips-section p { + font-size: 0.85rem; + color: var(--text-secondary); + margin-bottom: 8px; } -.math-icon-derivative { - color: #ec4899; +.tips-section ul { + list-style: none; + padding: 0; } -.utility-icon-radio { - color: #3b82f6; +.tips-section li { + padding: 4px 0; + font-size: 0.8rem; + color: var(--text-tertiary); } -.utility-icon-tower { - color: #f97316; +.tips-section code { + font-family: var(--font-mono); + font-size: 0.75rem; + color: var(--accent); + background: var(--accent-soft); + padding: 1px 6px; + border-radius: 4px; } -.utility-icon-convert { - color: #22c55e; +.no-results-message { + text-align: center; + padding: 24px 16px; + color: var(--text-tertiary); + font-size: 0.9rem; } -.utility-icon-typing { - color: #5cdff6; +/* Search loader */ +.search-loader { + display: flex; + align-items: center; + justify-content: center; + padding: 8px; } -/* ═══════════════════════════════════════ - MODAL -═══════════════════════════════════════ */ -.modal { - display: none; - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: var(--overlay-color); - backdrop-filter: blur(6px); - -webkit-backdrop-filter: blur(6px); - z-index: 2000; - animation: fadeIn 0.3s ease; +.spinner { + width: 16px; + height: 16px; + border: 2px solid var(--border); + border-top-color: var(--accent); + border-radius: 50%; + animation: spin 0.6s linear infinite; } -.modal.active { - display: flex; - align-items: center; - justify-content: center; +/* ── Empty State ───────────────────────────────────────────── */ +.empty-state { + text-align: center; + padding: 60px 24px; + grid-column: 1 / -1; } -.modal-content { - background: var(--surface-color); - padding: 2rem; - border-radius: 20px; - max-width: 900px; - width: 90%; - max-height: 85vh; - display: flex; - flex-direction: column; - position: relative; - animation: slideUp 0.3s ease; - box-shadow: var(--shadow-modal); - border: 1px solid var(--border-color); - margin: auto; - /* overflow removed to stop clipping */ +.empty-state-title { + font-size: 1.25rem; + font-weight: 600; + margin-bottom: 8px; + color: var(--text); } -.modal-close { - position: absolute; - top: 1.5rem; - right: 1.5rem; - background: var(--danger-color); - color: var(--on-accent); - border: none; - width: 36px; - height: 36px; - border-radius: 50%; - font-size: 1.5rem; - cursor: pointer; - transition: var(--transition); - z-index: 100; /* Forces button above inner content */ - display: flex; - align-items: center; - justify-content: center; - line-height: 1; - box-shadow: 0 2px 8px rgba(0,0,0,0.2); +.empty-state-hint { + font-size: 0.9rem; + color: var(--text-secondary); } -.modal-close:hover { - transform: scale(1.1) rotate(90deg); +/* ═══════════════════════════════════════════════════════════════ + STICKY FILTER BAR + ═══════════════════════════════════════════════════════════════ */ +.sticky-filter-bar { + position: fixed; + top: calc(var(--nav-height) + 24px); + left: 50%; + transform: translateX(-50%); + z-index: 999; + opacity: 0; + pointer-events: none; + transition: opacity var(--duration-base) ease, transform var(--duration-base) ease; + width: calc(100% - 32px); + max-width: 600px; } -/* ── reused section placeholder ── */ - -/* Animations */ -@keyframes fadeIn { - from { - opacity: 0; - } - to { - opacity: 1; - } +.sticky-filter-bar.visible { + opacity: 1; + pointer-events: auto; } -@keyframes fadeInUp { - from { - opacity: 0; - transform: translateY(30px); - } - - to { - opacity: 1; - transform: translateY(0); - } +.sticky-filter-inner { + display: flex; + align-items: center; + justify-content: center; + gap: 4px; + padding: 6px; + background: var(--surface); + border: 1px solid var(--border); + border-radius: 14px; + backdrop-filter: blur(20px); + overflow-x: auto; + scrollbar-width: none; } -@keyframes slideUp { - from { - transform: translateY(50px); - opacity: 0; - } - - to { - transform: translateY(0); - opacity: 1; - } +.sticky-filter-inner::-webkit-scrollbar { + display: none; } -@keyframes bounce { - - 0%, - 100% { - transform: translateY(0); - } +.sticky-tab { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 8px 14px; + border-radius: 10px; + font-family: var(--font-sans); + font-size: 0.8rem; + font-weight: 500; + color: var(--text-secondary); + background: transparent; + border: none; + white-space: nowrap; + cursor: pointer; + transition: all var(--duration-fast) ease; +} - 50% { - transform: translateY(-10px); - } +.sticky-tab:hover { + color: var(--text); + background: var(--bg-glass); } -/* ===== RESPONSIVE BREAKPOINTS ===== */ +.sticky-tab.active { + color: var(--text); + background: var(--accent-soft); + border: 1px solid var(--border-accent); +} -/* Tablet: 768px */ +.sticky-tab svg { + width: 14px; + height: 14px; +} -/* Standard mobile: 375px */ +/* ═══════════════════════════════════════════════════════════════ + PLAYGROUND SECTION + ═══════════════════════════════════════════════════════════════ */ +.playground-section { + padding: 40px 0 80px; + position: relative; + z-index: 1; +} -/* Scrollbar */ -::-webkit-scrollbar { - width: 10px; +.playground-header { + text-align: center; + margin-bottom: 32px; } -::-webkit-scrollbar-track { - background: var(--bg-color); +.playground-title-row { + display: flex; + align-items: center; + justify-content: center; + gap: 16px; + flex-wrap: wrap; + margin-bottom: 12px; } -::-webkit-scrollbar-thumb { - background: var(--accent-color); - border-radius: 5px; +.playground-title { + font-family: var(--font-sans); + font-size: 2rem; + font-weight: 700; + letter-spacing: -0.03em; + background: linear-gradient(135deg, var(--accent), #06b6d4); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; } -::-webkit-scrollbar-thumb:hover { - background: var(--accent-color); - opacity: 0.8; +.playground-status { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 6px 14px; + border-radius: 999px; + font-family: var(--font-mono); + font-size: 0.75rem; + font-weight: 500; + color: var(--text-secondary); + background: var(--bg-glass); + border: 1px solid var(--border); } -/* Accessibility */ -.visually-hidden { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - clip: rect(0, 0, 0, 0); - white-space: nowrap; - border: 0; +.status-dot { + width: 8px; + height: 8px; + border-radius: 50%; + background: var(--text-tertiary); + flex-shrink: 0; + transition: background var(--duration-fast) ease; } -.skip-link { - position: absolute; - top: -90px; - left: 50%; - transform: translateX(-50%); +.status-dot.loading { + background: var(--warning); + animation: pulse 1s ease-in-out infinite; +} - background: var(--accent-color); - border-color: var(--accent-color); - color: var(--on-accent); - box-shadow: 0 4px 14px rgba(34, 197, 94, 0.35); - padding: 0.9rem 1.4rem; - border-radius: 5px; +.status-dot.ready { + background: var(--success); +} - font-size: 0.95rem; - font-weight: 700; - letter-spacing: 0.3px; +.status-dot.error { + background: var(--danger); +} - text-decoration: none; - font: 16px Arial, sans-serif; - z-index: 5000; +.playground-subtitle { + font-size: 0.95rem; + color: var(--text-secondary); + margin-bottom: 8px; +} - transition: - top 0.3s ease, - transform 0.3s ease, - box-shadow 0.3s ease; +.playground-subtitle a { + color: var(--accent); + font-weight: 500; } -.skip-link:focus { - top: 1rem; - transform: translateX(-50%) scale(1.03); +.playground-hint { + font-size: 0.8rem; + color: var(--text-tertiary); +} - outline: 2px solid rgba(255, 255, 255, 0.9); - outline-offset: 1px; +.playground-hint kbd { + font-family: var(--font-mono); + font-size: 0.75rem; + padding: 2px 6px; + border-radius: 4px; + background: var(--bg-glass); + border: 1px solid var(--border); + color: var(--text-secondary); +} +.playground-body { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 16px; + margin-bottom: 16px; } -.skip-link:hover { - transform: translateX(-50%) scale(1.05); +@media (max-width: 768px) { + .playground-body { + grid-template-columns: 1fr; + } } -.tab:focus-visible, -.theme-toggle:focus-visible, -.modal-close:focus-visible, -.btn-play:focus-visible { - outline: 2px solid var(--accent-color); - outline-offset: 2px; +.editor-panel, +.console-panel { + display: flex; + flex-direction: column; + background: var(--surface); + border: 1px solid var(--border); + border-radius: 16px; + overflow: hidden; } -/* ===== FOOTER ===== */ -.footer { - background: var(--surface-color); - border-top: 2px solid var(--border-color); - padding: 4rem 0 2rem; - margin-top: 4rem; - position: relative; - box-shadow: 0 -10px 30px rgba(0, 0, 0, 0.05); +.editor-panel:focus-within { + border-color: var(--accent); } -.footer::before { - content: ''; - position: absolute; - top: -2px; - left: 0; - right: 0; - height: 1px; - background: linear-gradient(90deg, transparent, var(--accent-color), transparent); +.panel-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px 16px; + background: var(--bg-glass); + border-bottom: 1px solid var(--border); + font-size: 0.8rem; + font-weight: 600; + color: var(--text-secondary); + gap: 12px; + flex-wrap: wrap; } -.footer-content { - display: flex; - justify-content: space-between; - flex-wrap: wrap; - gap: 2.5rem; - margin-bottom: 3rem; -} - -.footer-brand { - flex: 1 1 300px; - max-width: 400px; - padding: 1.5rem; - background: var(--footer-card-light); - border: 1px solid var(--footer-card-border); - border-radius: 16px; - transition: var(--transition); +.panel-header span { + display: flex; + align-items: center; + gap: 8px; } -.footer-brand:hover { - background: rgba(252, 176, 91, 0.12); - border-color: var(--footer-card-color); - box-shadow: 0 8px 24px rgba(252, 176, 91, 0.15); +.editor-actions { + display: flex; + gap: 6px; +} + +.btn-panel-action { + background: var(--bg-glass); + border: 1px solid var(--border); + color: var(--text-secondary); + padding: 6px 12px; + border-radius: 8px; + cursor: pointer; + font-size: 0.75rem; + font-weight: 600; + transition: all var(--duration-fast) ease; + display: inline-flex; + align-items: center; + gap: 4px; } -.footer-links { - flex: 1 1 150px; - padding: 1.5rem; - background: var(--footer-card-light); - border: 1px solid var(--footer-card-border); - border-radius: 16px; - transition: var(--transition); +.btn-panel-action:hover { + background: var(--accent-soft); + border-color: var(--border-accent); + color: var(--accent); } -.footer-links:hover { - background: rgba(252, 176, 91, 0.12); - border-color: var(--footer-card-color); - box-shadow: 0 8px 24px rgba(252, 176, 91, 0.15); +#pythonEditor { + flex: 1; + min-height: 280px; + background: transparent; + border: none; + outline: none; + resize: vertical; + color: var(--text); + font-family: var(--font-mono); + font-size: 0.85rem; + line-height: 1.7; + padding: 16px; + tab-size: 4; } -.footer-newsletter { - flex: 1 1 250px; - max-width: 350px; - padding: 1.5rem; - background: var(--footer-card-light); - border: 1px solid var(--footer-card-border); - border-radius: 16px; - transition: var(--transition); +#pythonEditor::placeholder { + color: var(--text-tertiary); + opacity: 0.6; } -.footer-newsletter:hover { - background: rgba(252, 176, 91, 0.12); - border-color: var(--footer-card-color); - box-shadow: 0 8px 24px rgba(252, 176, 91, 0.15); +.console-output { + flex: 1; + min-height: 280px; + padding: 16px; + margin: 0; + font-family: var(--font-mono); + font-size: 0.82rem; + line-height: 1.7; + overflow-y: auto; + background: transparent; + color: var(--text); + white-space: pre-wrap; + word-break: break-word; } -[data-theme="dark"] .footer-brand, -[data-theme="dark"] .footer-links, -[data-theme="dark"] .footer-newsletter { - background: rgba(0, 0, 0, 0.4); - border-color: rgba(252, 176, 91, 0.08); +.pg-placeholder { + color: var(--text-tertiary); + font-style: italic; } -[data-theme="dark"] .footer-brand:hover, -[data-theme="dark"] .footer-links:hover, -[data-theme="dark"] .footer-newsletter:hover { - background: rgba(0, 0, 0, 0.5); - border-color: rgba(252, 176, 91, 0.15); - box-shadow: 0 8px 24px rgba(252, 176, 91, 0.08); +.playground-controls { + display: flex; + align-items: center; + gap: 12px; + flex-wrap: wrap; } -.footer-brand h2 { - font-size: 1.5rem; - color: var(--text-color); - margin-bottom: 1rem; - background: linear-gradient(135deg, var(--text-color), var(--footer-card-color)); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; +.btn-run { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 12px 28px; + border-radius: 10px; + font-family: var(--font-sans); + font-size: 0.9rem; + font-weight: 600; + color: #fff; + background: var(--accent); + border: none; + box-shadow: 0 4px 16px var(--accent-glow); + transition: all var(--duration-fast) var(--ease-out-expo); } -.footer-brand p { - color: var(--text-secondary); - margin-bottom: 1.5rem; +.btn-run:hover:not(:disabled) { + transform: translateY(-2px); + box-shadow: 0 8px 24px var(--accent-glow); } -.footer-social-icons { - display: flex; - gap: 1rem; +.btn-run:disabled { + opacity: 0.4; + cursor: not-allowed; } -.footer-social-icons a { - display: flex; - align-items: center; - justify-content: center; - width: 40px; - height: 40px; - border-radius: 50%; - background: rgba(252, 176, 91, 0.1); - color: var(--footer-card-color); - font-size: 1.2rem; - transition: var(--transition); - border: 1px solid rgba(252, 176, 91, 0.2); - text-decoration: none; +.btn-stop { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 12px 24px; + border-radius: 10px; + font-family: var(--font-sans); + font-size: 0.9rem; + font-weight: 600; + color: #fff; + background: var(--danger); + border: none; + transition: all var(--duration-fast) var(--ease-out-expo); } -.footer-social-icons a:hover { - background: linear-gradient(135deg, rgba(252, 176, 91, 0.15), rgba(252, 176, 91, 0.1)); - color: var(--footer-card-color); - border-color: var(--footer-card-color); - transform: translateY(-5px); - box-shadow: 0 10px 25px rgba(252, 176, 91, 0.25); +.btn-stop:hover:not(:disabled) { + transform: translateY(-2px); + box-shadow: 0 4px 16px rgba(239, 68, 68, 0.3); } -[data-theme="dark"] .footer-social-icons a { - background: rgba(0, 0, 0, 0.3); - color: rgba(252, 176, 91, 0.7); - border-color: rgba(252, 176, 91, 0.1); +.btn-stop:disabled { + opacity: 0.4; + cursor: not-allowed; } -[data-theme="dark"] .footer-social-icons a:hover { - background: rgba(252, 176, 91, 0.08); - color: var(--footer-card-color); - border-color: rgba(252, 176, 91, 0.12); - box-shadow: 0 10px 25px rgba(252, 176, 91, 0.1); +/* ═══════════════════════════════════════════════════════════════ + MODAL + ═══════════════════════════════════════════════════════════════ */ +.modal { + display: none; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.6); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + z-index: 2000; + animation: fadeIn 0.25s ease; } -.footer-links h3, -.footer-newsletter h3 { - font-size: 1.2rem; - color: var(--text-color); - margin-bottom: 1.5rem; - position: relative; - padding-bottom: 0.5rem; +.modal.active { + display: flex; + align-items: center; + justify-content: center; } -.footer-links h3::after, -.footer-newsletter h3::after { - content: ''; - position: absolute; - bottom: 0; - left: 0; - width: 40px; - height: 2px; - background: var(--footer-card-color); - border-radius: 2px; - transition: var(--transition); +.modal-content { + background: var(--surface); + padding: 32px; + border-radius: 24px; + max-width: 900px; + width: 90%; + max-height: 85vh; + overflow-y: auto; + position: relative; + border: 1px solid var(--border); + box-shadow: var(--shadow-lg); + animation: modalSlideUp 0.3s var(--ease-out-expo); } -.footer-links:hover h3::after, -.footer-newsletter:hover h3::after { - width: 60px; - background: var(--footer-card-color); +@keyframes modalSlideUp { + from { + opacity: 0; + transform: translateY(20px) scale(0.97); + } + to { + opacity: 1; + transform: translateY(0) scale(1); + } } -[data-theme="dark"] .footer-links h3::after, -[data-theme="dark"] .footer-newsletter h3::after { - background: rgba(252, 176, 91, 0.5); +.modal-close { + position: absolute; + top: 16px; + right: 16px; + width: 36px; + height: 36px; + border-radius: 10px; + background: var(--bg-glass); + border: 1px solid var(--border); + color: var(--text-secondary); + font-size: 1.2rem; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: all var(--duration-fast) ease; + z-index: 10; } -[data-theme="dark"] .footer-links:hover h3::after, -[data-theme="dark"] .footer-newsletter:hover h3::after { - background: rgba(252, 176, 91, 0.7); +.modal-close:hover { + background: rgba(239, 68, 68, 0.15); + color: var(--danger); + border-color: rgba(239, 68, 68, 0.3); + transform: rotate(90deg); } -.footer-links ul { - list-style: none; +.modal-dialog-title { + margin-bottom: 20px; + font-family: var(--font-sans); + font-size: 1.5rem; + font-weight: 700; + letter-spacing: -0.03em; } -.footer-links li { - margin-bottom: 0.8rem; +#modalBody { + min-height: 200px; } -.footer-links a { - color: var(--text-secondary); - text-decoration: none; - transition: var(--transition); - display: flex; - align-items: center; - gap: 0.5rem; +/* ═══════════════════════════════════════════════════════════════ + FOOTER + ═══════════════════════════════════════════════════════════════ */ +.footer { + padding: 60px 0 32px; + border-top: 1px solid var(--border); + background: var(--surface); + position: relative; + z-index: 1; } -.footer-links a i { - font-size: 0.9rem; - color: var(--footer-card-color); - opacity: 0.8; - transition: var(--transition); +.footer-content { + display: grid; + grid-template-columns: 1.5fr 1fr 1fr 1.2fr; + gap: 40px; + margin-bottom: 40px; } -.footer-links a:hover { - color: var(--footer-card-color); - transform: translateX(5px); +.footer-brand h2 { + font-family: var(--font-sans); + font-size: 1.3rem; + font-weight: 700; + letter-spacing: -0.03em; + margin-bottom: 12px; } -.footer-links a:hover i { - opacity: 1; +.footer-brand p { + font-size: 0.88rem; + color: var(--text-secondary); + margin-bottom: 20px; + line-height: 1.7; } -.newsletter-form { - display: flex; - position: relative; +.footer-social-icons { + display: flex; + gap: 10px; } -.newsletter-form input { - width: 100%; - padding: 0.8rem 3.5rem 0.8rem 1.2rem; - border-radius: 50px; - border: 1px solid rgba(252, 176, 91, 0.2); - background: rgba(252, 176, 91, 0.04); - color: var(--text-color); - font-family: inherit; - font-size: 0.95rem; - outline: none; - transition: var(--transition); +.footer-social-icons a { + display: flex; + align-items: center; + justify-content: center; + width: 38px; + height: 38px; + border-radius: 10px; + background: var(--bg-glass); + border: 1px solid var(--border); + color: var(--text-secondary); + font-size: 1rem; + transition: all var(--duration-fast) ease; + text-decoration: none; } -.newsletter-form input::placeholder { - color: rgba(148, 163, 184, 0.6); +.footer-social-icons a:hover { + background: var(--accent-soft); + border-color: var(--border-accent); + color: var(--accent); + transform: translateY(-2px); } -.newsletter-form input:focus { - border-color: var(--footer-card-color); - box-shadow: 0 0 0 3px rgba(252, 176, 91, 0.15); - background: rgba(252, 176, 91, 0.08); +.footer-links h3 { + font-family: var(--font-sans); + font-size: 0.85rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--text); + margin-bottom: 16px; } -.newsletter-form button { - position: absolute; - right: 4px; - top: 50%; - transform: translateY(-50%); - width: 36px; - height: 36px; - border-radius: 50%; - border: none; - background: linear-gradient(135deg, var(--footer-card-color), rgba(252, 176, 91, 0.8)); - color: #5d4a3a; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - transition: var(--transition); - font-weight: 600; +.footer-links ul { + list-style: none; } -.newsletter-form button:hover { - box-shadow: 0 8px 20px rgba(252, 176, 91, 0.35); - transform: translateY(-50%) scale(1.08); +.footer-links li { + margin-bottom: 10px; } -[data-theme="dark"] .newsletter-form button { - background: rgba(252, 176, 91, 0.7); - color: #000000; +.footer-links a { + color: var(--text-secondary); + font-size: 0.85rem; + transition: color var(--duration-fast) ease; + display: inline-flex; + align-items: center; + gap: 6px; } -[data-theme="dark"] .newsletter-form button:hover { - background: rgba(252, 176, 91, 0.85); - box-shadow: 0 8px 20px rgba(252, 176, 91, 0.15); +.footer-links a:hover { + color: var(--accent); } .footer-bottom { - display: flex; - flex-direction: column; - align-items: center; - gap: 1rem; - padding-top: 2rem; - border-top: 1px solid rgba(252, 176, 91, 0.15); - color: var(--text-secondary); - font-size: 0.95rem; + display: flex; + flex-direction: column; + align-items: center; + gap: 12px; + padding-top: 24px; + border-top: 1px solid var(--border); + color: var(--text-tertiary); + font-size: 0.82rem; } .footer-bottom-links { - display: flex; - align-items: center; - gap: 1rem; + display: flex; + align-items: center; + gap: 16px; } .footer-bottom-links a { - color: var(--text-secondary); - text-decoration: none; - transition: var(--transition); + color: var(--text-tertiary); + transition: color var(--duration-fast) ease; } .footer-bottom-links a:hover { - color: var(--footer-card-color); + color: var(--accent); } .separator { - color: rgba(252, 176, 91, 0.3); -} - -[data-theme="dark"] .footer-bottom { - border-top: 1px solid rgba(252, 176, 91, 0.08); -} - -[data-theme="dark"] .footer-bottom-links a:hover { - color: rgba(252, 176, 91, 0.8); -} - -[data-theme="dark"] .separator { - color: rgba(252, 176, 91, 0.12); + color: var(--text-tertiary); + opacity: 0.5; } -/* ═══════════════════════════════════════ +/* ═══════════════════════════════════════════════════════════════ BACK TO TOP -═══════════════════════════════════════ */ + ═══════════════════════════════════════════════════════════════ */ .back-to-top { - position: fixed; - right: 1.5rem; - bottom: 6.5rem; - width: 3.25rem; - height: 3.25rem; - border: none; - border-radius: 50%; - background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); - color: white; - box-shadow: var(--shadow); - cursor: pointer; - display: grid; - place-items: center; - font-size: 1.1rem; - opacity: 0; - transform: translateY(1rem) scale(0.9); - pointer-events: none; - transition: opacity 0.25s ease, transform 0.25s ease, box-shadow 0.25s ease; - z-index: 1500; + position: fixed; + right: 24px; + bottom: 24px; + width: 44px; + height: 44px; + border-radius: 12px; + background: var(--surface); + border: 1px solid var(--border); + color: var(--text-secondary); + box-shadow: var(--shadow-md); + cursor: pointer; + display: grid; + place-items: center; + font-size: 1rem; + opacity: 0; + transform: translateY(16px) scale(0.9); + pointer-events: none; + transition: all var(--duration-base) var(--ease-out-expo); + z-index: 1500; } .back-to-top.visible { - opacity: 1; - transform: translateY(0) scale(1); - pointer-events: auto; + opacity: 1; + transform: translateY(0) scale(1); + pointer-events: auto; } .back-to-top:hover { - box-shadow: 0 12px 30px rgba(34, 197, 94, 0.45); - transform: translateY(-3px) scale(1.05); + background: var(--accent-soft); + border-color: var(--border-accent); + color: var(--accent); + transform: translateY(-4px); + box-shadow: var(--shadow-glow); } -.back-to-top:focus-visible { - outline: 3px solid rgba(255, 255, 255, 0.85); - outline-offset: 3px; +/* ═══════════════════════════════════════════════════════════════ + ANIMATIONS + ═══════════════════════════════════════════════════════════════ */ +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } } -/* ═══════════════════════════════════════ - REDUCED MOTION -═══════════════════════════════════════ */ - - - -/* ── Share Button ───────────────────────────────────────────────── */ -.btn-share { - position: absolute; - bottom: 0.75rem; - right: 0.75rem; - background: rgba(34, 197, 94, 0.1); - border: 1px solid rgba(34, 197, 94, 0.25); - border-radius: 50%; - width: 36px; - height: 36px; - font-size: 0.95rem; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - opacity: 0.6; - transform: scale(0.95); - transition: all 0.25s ease; - z-index: 2; +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(24px); + } + to { + opacity: 1; + transform: translateY(0); + } } -.project-card:hover .btn-share { +@keyframes slideUp { + from { + transform: translateY(30px); + opacity: 0; + } + to { + transform: translateY(0); opacity: 1; - transform: scale(1); - background: rgba(34, 197, 94, 0.2); - box-shadow: 0 4px 12px rgba(34, 197, 94, 0.25); + } } -.btn-share:hover { - background: rgba(34, 197, 94, 0.3) !important; - box-shadow: 0 4px 12px rgba(34, 197, 94, 0.4) !important; - transform: scale(1.15) !important; +@keyframes spin { + to { transform: rotate(360deg); } } -/* ── Toast Notification ─────────────────────────────────────────── */ -.share-toast { - position: fixed; - bottom: 2rem; - left: 50%; - transform: translateX(-50%) translateY(1rem); - background: var(--surface-color); - color: var(--primary-color); - border: 1.5px solid rgba(34, 197, 94, 0.4); - padding: 0.75rem 1.5rem; - border-radius: 50px; - font-size: 0.95rem; - font-weight: 600; - box-shadow: 0 8px 30px rgba(0, 0, 0, 0.3); - z-index: 9999; - opacity: 0; - transition: opacity 0.3s ease, transform 0.3s ease; - pointer-events: none; - white-space: nowrap; +.reveal-on-scroll { + opacity: 0; + transform: translateY(24px); + transition: opacity 0.6s var(--ease-out-expo), transform 0.6s var(--ease-out-expo); } -.share-toast--visible { - opacity: 1; - transform: translateX(-50%) translateY(0); +.reveal-on-scroll.is-visible { + opacity: 1; + transform: translateY(0); } -/* ═══════════════════════════════════════ - STICKY CATEGORY FILTER BAR -═══════════════════════════════════════ */ -.sticky-filter-bar { - position: fixed; - top: 0; - /* will be offset by JS after measuring navbar */ - left: 0; - right: 0; - z-index: 999; - background: color-mix(in srgb, var(--surface-color) 90%, transparent); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); - border-bottom: 1px solid var(--border-color); - box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08); - - opacity: 0; - pointer-events: none; - transform: translateY(-100%); - transition: opacity 0.25s ease, transform 0.25s ease; +/* ── Share Toast ───────────────────────────────────────────── */ +.share-toast { + position: fixed; + bottom: 80px; + left: 50%; + transform: translateX(-50%) translateY(16px); + background: var(--surface); + color: var(--accent); + border: 1px solid var(--border-accent); + padding: 12px 24px; + border-radius: 12px; + font-size: 0.85rem; + font-weight: 600; + box-shadow: var(--shadow-lg); + z-index: 9999; + opacity: 0; + transition: all var(--duration-base) var(--ease-out-expo); + pointer-events: none; + white-space: nowrap; + backdrop-filter: blur(20px); } -.sticky-filter-bar.visible { - opacity: 1; - pointer-events: auto; - transform: translateY(0); +.share-toast.share-toast--visible { + opacity: 1; + transform: translateX(-50%) translateY(0); } -.sticky-filter-inner { - display: flex; - align-items: center; - justify-content: center; - gap: 0.25rem; - padding: 0.55rem 1.25rem; - max-width: 1200px; - margin: 0 auto; - overflow-x: auto; - scrollbar-width: none; - -ms-overflow-style: none; +/* ── Project inline styles override helper ─────────────────── */ +.ui-panel, +.project-content { + background: transparent; + border: none; + border-radius: 0; + color: var(--text); + box-shadow: none; } -.sticky-filter-inner::-webkit-scrollbar { - display: none; +.project-content h2 { + font-family: var(--font-sans); + font-size: 1.6rem; + font-weight: 700; + letter-spacing: -0.03em; + margin-bottom: 16px; } -.sticky-tab { - display: inline-flex; - align-items: center; - gap: 0.38rem; - background: transparent; - border: 0; - border-radius: 50px; - padding: 0.35rem 0.9rem; - cursor: pointer; - font-size: 0.82rem; - font-family: 'IBM Plex Mono', monospace; - font-weight: 600; - color: var(--text-secondary); - text-transform: lowercase; - transition: background-color 0.18s ease, color 0.18s ease; - white-space: nowrap; -} - -.sticky-tab i { - width: 14px; - height: 14px; - stroke-width: 2; - flex-shrink: 0; +.ui-subpanel { + background: var(--bg-glass); + border: 1px solid var(--border); + border-radius: 12px; } -.sticky-tab:hover { - background: var(--accent-soft); - color: var(--text-color); -} +/* ═══════════════════════════════════════════════════════════════ + RESPONSIVE + ═══════════════════════════════════════════════════════════════ */ +@media (max-width: 1100px) { + .projects-grid { + grid-template-columns: repeat(2, 1fr); + } -.sticky-tab.active { - background: var(--accent-soft); - color: var(--accent-color); - border: 1px solid var(--accent-border); + .timeline-item { + grid-template-columns: 1fr 32px 1fr; + gap: 20px; + } } -.sticky-tab:focus-visible { - outline: 2px solid var(--accent-color); - outline-offset: 2px; -} +@media (max-width: 900px) { + .footer-content { + grid-template-columns: 1fr 1fr; + } -/* ═══════════════════════════════════════ - CONSOLIDATED RESPONSIVE BREAKPOINTS -═══════════════════════════════════════ */ + .footer-brand { + grid-column: 1 / -1; + } -@media (max-width: 992px) { - .footer-brand { - flex: 1 1 100%; - max-width: 600px; - } + .hero-timeline-section { + display: none; + } } @media (max-width: 768px) { + .navbar { + top: 8px; + width: calc(100% - 16px); + } + + .nav-island { + height: 60px; + padding: 0 12px; + border-radius: 16px; + } - .hero-code-snippets, - .navbar-tagline, - .search-shortcut { - display: none; - } - - .hero-section { - padding: 7rem 0; - min-height: 400px; - } - - .hero-title { - font-size: 2.3rem; - margin-bottom: 1rem; - } - - .python-orbit { - width: 280px; - height: 280px; - } - - .python-orbiter { - width: 40px; - height: 40px; - top: -20px; - } - - .python-logo { - font-size: 2rem; - } - - .hero-subtitle { - font-size: 1rem; - margin-bottom: 2rem; - } - - .hero-features { - gap: 0.75rem; - margin-bottom: 2rem; - flex-wrap: nowrap; - overflow-x: auto; - -webkit-overflow-scrolling: touch; - scrollbar-width: none; - padding-bottom: 0.5rem; - justify-content: flex-start; - } - - .hero-features::-webkit-scrollbar { - display: none; - } - - .feature-badge { - padding: 0.6rem 1rem; - font-size: 0.9rem; - flex-shrink: 0; - } - - .feature-icon { - font-size: 1.2rem; - } - - .btn-explore { - padding: 0.85rem 2rem; - font-size: 1rem; - } - - .hero-badges { - gap: 0.6rem; - } - - .badge { - padding: 0.4rem 1.1rem; - font-size: 0.9rem; - } - - .tabs-section { - padding: 2rem 0 1.5rem 0; - } - - .tabs-section .container { - gap: 1.5rem; - } - - .tabs-wrapper { - flex-direction: column; - gap: 1rem; - align-items: stretch; - } - - .tabs-wrapper .tabs { - min-width: unset; - width: 100%; - } - - .tabs { - gap: 0.6rem; - margin-top: 0; - justify-content: flex-start; - } - - .tab { - padding: 0.6rem 1.1rem; - font-size: 0.9rem; - } - - .btn-random-project { - width: 100%; - justify-content: center; - padding: 0.75rem 1.25rem; - font-size: 0.95rem; - } - - .projects-section { - padding: 2.5rem 0; - } - - .projects-grid { - grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); - gap: 1rem; - margin-top: 1.5rem; - } - - .project-card { - padding: 1.25rem 1rem; - } - - .project-card h3 { - font-size: 1.15rem; - } - - .search-wrapper { - height: 50px; - padding: 0 1.1rem; - } - - .search-input { - font-size: 0.95rem; - } - - .modal-content { - padding: 1.5rem; - border-radius: 16px; - position: relative; - } -} - -@media (max-width: 576px) { - .footer-content { - grid-template-columns: 1fr; - text-align: center; - gap: 2rem; - } - - .footer-social-icons { - justify-content: center; - } - - .footer-links h3::after, - .footer-newsletter h3::after { - left: 50%; - transform: translateX(-50%); - } - - .footer-links a { - justify-content: center; - } -} + .navbar.scrolled .nav-island { + height: 56px; + } -@media (max-width: 480px) { - .hero-section { - padding: 4rem 0; - } - - .hero-title { - font-size: 1.8rem; - margin-bottom: 0.8rem; - } - - .hero-subtitle { - font-size: 0.95rem; - margin-bottom: 1.5rem; - } - - .hero-features { - flex-direction: row; - flex-wrap: nowrap; - overflow-x: auto; - -webkit-overflow-scrolling: touch; - scrollbar-width: none; - width: 100%; - justify-content: flex-start; - gap: 0.6rem; - margin-bottom: 1.5rem; - } - - .hero-features::-webkit-scrollbar { - display: none; - } - - .feature-badge { - flex-shrink: 0; - padding: 0.5rem 0.9rem; - font-size: 0.85rem; - } - - .btn-explore { - padding: 0.75rem 1.8rem; - font-size: 0.95rem; - } - - .sticky-filter-inner { - justify-content: flex-start; - padding: 0.45rem 1rem; - gap: 0.18rem; - } - - .sticky-tab { - padding: 0.3rem 0.7rem; - font-size: 0.78rem; - } -} - -@media (max-width: 375px) { - .container { - padding: 0 16px; - } - - .hero { - padding: 2rem 0; - } - - .hero-title { - font-size: 1.7rem; - } - - .hero-subtitle { - font-size: 0.95rem; - margin-bottom: 1.25rem; - } - - .hero-badges { - gap: 0.5rem; - } - - .tab { - padding: 0.55rem 1rem; - font-size: 0.85rem; - border-radius: 50px; - } - - .btn-random-project { - width: auto; - justify-content: flex-start; - padding: 0.25rem 0; - font-size: 0.82rem; - gap: 0.4rem; - } - - .projects-grid { - grid-template-columns: 1fr; - gap: 1rem; - } - - .project-card { - padding: 1.25rem 1rem; - } - - .project-card h3 { - font-size: 1rem; - } - - .project-card p { - font-size: 0.85rem; - } - - .btn-generate, - .btn-play { - padding: 0.6rem 1.25rem; - font-size: 0.85rem; - } - - .search-wrapper { - height: 44px; - padding: 0 0.85rem; - } - - .search-input { - font-size: 0.85rem; - } - - .logo h1 { - font-size: 1.1rem; - } - - .theme-toggle, - .sound-toggle { - width: 32px; - height: 32px; - font-size: 0.9rem; - } -} + .logo-tagline { + display: none; + } -@media (prefers-reduced-motion: reduce) { - html { - scroll-behavior: auto; - } - - :root { - --motion-duration: 0.01ms; - --transition: none; - --transition-fast: none; - } - - .project-card { - animation: none !important; - } - - .modal { - animation: none; - backdrop-filter: none; - -webkit-backdrop-filter: none; - } - - .modal-content { - animation: none; - } - - .color-bends-container::before, - .color-bends-container::after, - .code-snippet { - animation: none !important; - } - - .theme-toggle:hover, - .theme-toggle:active, - .tab:hover, - .tab:active, - .project-card:hover, - .project-card:active, - .btn-play:hover, - .btn-play:active, - .modal-close:hover { - transform: none; - } - - .sticky-filter-bar { - transition: none; - } -} - -/* ========================================================================== - CARD AND MODAL LAYOUT STANDARDIZATION - ========================================================================== */ - -.projects-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); - gap: 1.2rem; - margin-top: 2rem; - align-items: stretch; -} - -.project-card { - min-height: 240px; - display: flex; - flex-direction: column; - justify-content: space-between; - align-items: stretch; - padding: 1.75rem 1.5rem 1.5rem; - border-radius: 28px; - background: var(--surface-color); - border: 1px solid var(--accent-border); - box-shadow: 0 18px 40px rgba(0, 0, 0, 0.08); - transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease; -} - -.project-card h3 { - margin-bottom: 0.8rem; - font-size: 1.15rem; - line-height: 1.35; - color: var(--text-color); -} - -.project-card p { - margin-bottom: 1rem; - color: var(--text-secondary); - font-size: 0.95rem; - line-height: 1.65; - flex: 1 1 auto; -} - -.project-card .btn-play { - margin-top: auto; - align-self: flex-start; -} - -.card-icon { - width: 72px; - height: 72px; - display: grid; - place-items: center; - border-radius: 22px; - background: var(--accent-soft); - color: var(--accent-color); - margin-bottom: 1.2rem; - font-size: 2rem; - flex-shrink: 0; -} - -.modal-content { - width: min(100%, 900px); - max-width: 900px; - min-width: 320px; - max-height: 92vh; - overflow-y: auto; - overflow-x: hidden; - padding: 2rem; - border-radius: 20px; - box-shadow: var(--shadow-modal); - background: var(--surface-color); - border: 1px solid var(--border-color); -} - -#modalBody { - width: 100%; - display: flex; - flex-direction: column; - gap: 1rem; - align-items: stretch; -} - -#modalBody > * { + .search-box { + max-width: none; width: 100%; - max-width: 100%; -} - -.modal-close { - z-index: 2100; -} - -@media (max-width: 768px) { - .modal-content { - width: min(100%, 92vw); - padding: 1.5rem; - max-height: 90vh; - } - - .project-card { - min-height: auto; - padding: 1.5rem 1.25rem; - } -} - -@media (max-width: 576px) { - .projects-grid { - grid-template-columns: 1fr; - gap: 1rem; - } - - .project-card { - padding: 1.25rem; - } -} - -/* ========================================================================== - ARCADE REDESIGN OVERRIDES - ========================================================================== */ - -:root { - --accent-color: #6abf8d; - --primary-color: #6abf8d; - --secondary-color: #f2a65a; - --success-color: #5fbf93; - --danger-color: #de6d5b; - --warning-color: #f2b85f; - --bg-color: #f8f5ee; - --surface-color: rgba(255, 253, 248, 0.82); - --panel-color: #fffaf1; - --control-color: #fffdf8; - --text-color: #2c241f; - --text-secondary: #75675d; - --border-color: rgba(118, 99, 84, 0.14); - --accent-soft: rgba(106, 191, 141, 0.16); - --accent-border: rgba(106, 191, 141, 0.28); - --overlay-color: rgba(255, 248, 240, 0.7); - --on-accent: #fffdf8; - --shadow: 0 22px 60px rgba(155, 123, 90, 0.14); - --shadow-modal: 0 35px 90px rgba(101, 72, 39, 0.18); -} - -[data-theme="dark"] { - --accent-color: #9fddb5; - --primary-color: #9fddb5; - --secondary-color: #f6c58d; - --success-color: #9fddb5; - --danger-color: #ff9e8c; - --warning-color: #ffd48f; - --bg-color: #13100f; - --surface-color: rgba(34, 28, 26, 0.92); - --panel-color: #1d1817; - --control-color: #2a2321; - --text-color: #f8eee4; - --text-secondary: #d8c6b6; - --border-color: rgba(255, 239, 223, 0.14); - --accent-soft: rgba(159, 221, 181, 0.14); - --accent-border: rgba(159, 221, 181, 0.28); - --overlay-color: rgba(15, 12, 11, 0.8); - --shadow: 0 28px 70px rgba(0, 0, 0, 0.42); - --shadow-modal: 0 35px 90px rgba(0, 0, 0, 0.5); -} - -body { - font-family: 'DM Sans', 'Segoe UI','sans-serif'; - background: var(--bg-color); - color: var(--text-color); -} - -[data-theme="light"] body { - background: - radial-gradient(circle at top left, rgba(255, 219, 174, 0.44), transparent 30%), - radial-gradient(circle at top right, rgba(192, 245, 224, 0.46), transparent 24%), - linear-gradient(180deg, #fffdf8 0%, #f8f5ee 45%, #f5efe5 100%); -} - -html[data-theme="dark"] body { - background: - radial-gradient(circle at top left, rgba(242, 166, 90, 0.12), transparent 28%), - radial-gradient(circle at top right, rgba(159, 221, 181, 0.1), transparent 24%), - linear-gradient(180deg, #161211 0%, #0f0c0b 100%) !important; -} - -.cursor-glow { - position: fixed; - width: 240px; - height: 240px; - border-radius: 50%; - pointer-events: none; - z-index: 0; - opacity: 0.5; - background: radial-gradient(circle, rgba(255, 197, 115, 0.28) 0%, rgba(121, 221, 183, 0.12) 42%, transparent 72%); - transform: translate3d(-50%, -50%, 0); - transition: opacity 0.25s ease; -} - -.navbar { - padding: 1rem 12px; - background: transparent; - border-bottom: 0; -} - -.nav-island, -.nav-wrapper { - max-width: 1240px; - margin: 0 auto; - background: rgba(255, 252, 245, 0.72); - border: 1px solid rgba(128, 104, 85, 0.12); - border-radius: 999px; - padding: 0.9rem 1.15rem; - box-shadow: 0 18px 44px rgba(146, 111, 74, 0.14); - backdrop-filter: blur(18px); - -webkit-backdrop-filter: blur(18px); - display: flex; - align-items: center; - justify-content: space-between; - gap: 1rem; -} - -[data-theme="dark"] .nav-island, -[data-theme="dark"] .nav-wrapper { - background: rgba(29, 24, 23, 0.88); - border-color: rgba(255, 243, 224, 0.14); -} - -.navbar.scrolled .nav-wrapper { - transform: translateY(-2px); - box-shadow: 0 20px 56px rgba(146, 111, 74, 0.18); -} - -.logo-tagline { - background: rgba(255, 255, 255, 0.7); - color: #6b5c50; - border: 1px solid rgba(128, 104, 85, 0.08); - display: inline-flex; - align-items: center; - gap: 0.35rem; - white-space: nowrap; -} + } -[data-theme="dark"] .logo-tagline { - background: rgba(255, 255, 255, 0.06); - color: #f0dfd1; -} - -.search-box { - background: rgba(255, 255, 255, 0.74); - border: 1px solid rgba(128, 104, 85, 0.12); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7); - height: 54px; - max-width: 460px; -} - -.search-box i, -.search-box input, -[data-theme="light"] .search-box i, -[data-theme="light"] .search-box input { - color: var(--text-color); -} - -.search-box input::placeholder { - color: var(--text-secondary); -} - -[data-theme="dark"] .search-box { - background: rgba(255, 255, 255, 0.06); - border-color: rgba(255, 239, 223, 0.12); -} - -[data-theme="dark"] .search-box i, -[data-theme="dark"] .search-box input { - color: var(--text-color); -} - -[data-theme="dark"] .search-box input::placeholder { - color: var(--text-secondary); -} - -.theme-toggle, -.sound-toggle { - background: linear-gradient(135deg, #fff7ec, #f6dcc5); - color: #58463c; - border: 1px solid rgba(128, 104, 85, 0.1); - box-shadow: 0 10px 20px rgba(180, 145, 100, 0.12); -} - -[data-theme="dark"] .theme-toggle, -[data-theme="dark"] .sound-toggle { - background: linear-gradient(135deg, #443530, #2f2622); - color: #fff5eb; -} - -.hero-section { - min-height: 0; - padding: 5rem 0 20px; - background: transparent; - position: relative; -} - -#boardCanvas { - opacity: 0.17; - mix-blend-mode: multiply; -} - -[data-theme="dark"] #boardCanvas { - opacity: 0.11; - mix-blend-mode: screen; -} - -[data-theme="dark"] .hero-background { - background: - radial-gradient(circle at 20% 30%, rgba(242, 166, 90, 0.08), transparent 40%), - radial-gradient(circle at 85% 15%, rgba(106, 191, 141, 0.10), transparent 35%), - radial-gradient(circle at 50% 80%, rgba(125, 220, 185, 0.06), transparent 40%), - linear-gradient( - 180deg, - #13100f 0%, - #181312 45%, - #0f0c0b 100% - ) !important; -} - - -.hero-orb { - position: absolute; - border-radius: 50%; - filter: blur(10px); - opacity: 0.55; - pointer-events: none; -} - -.hero-orb-one { - width: 240px; - height: 240px; - left: -40px; - top: 60px; - background: radial-gradient(circle, rgba(252, 193, 118, 0.52), transparent 70%); - animation: float 9s ease-in-out infinite; -} - -.hero-orb-two { - width: 280px; - height: 280px; - right: -30px; - top: 100px; - background: radial-gradient(circle, rgba(158, 233, 205, 0.52), transparent 72%); - animation: float 11s ease-in-out infinite reverse; -} - -.hero-shell { - position: relative; - z-index: 2; - display: flex; - justify-content: center; - align-items: flex-start; - margin: 0 auto; - padding: 0 1rem; -} - -.hero-shell .filter-sidebar { - position: absolute; - right: calc(50% + 390px); - top: 0; - width: 200px; - background: rgba(255, 253, 248, 0.78); - backdrop-filter: blur(18px); - -webkit-backdrop-filter: blur(18px); - border: 2px solid rgba(128, 104, 85, 0.1); - border-radius: 20px; - padding: 1.1rem 0.65rem; - box-shadow: - 0 2px 0 rgba(128, 104, 85, 0.06), - 0 8px 24px rgba(146, 111, 74, 0.08), - 0 20px 40px rgba(146, 111, 74, 0.04); + .mobile-menu-toggle { display: flex; - flex-direction: column; - gap: 0.15rem; -} + } -[data-theme="dark"] .hero-shell .filter-sidebar { - background: rgba(29, 24, 23, 0.92); - border-color: rgba(255, 243, 224, 0.08); -} - -/* ── Hero Timeline (right side) ───────────────────────────── */ -.hero-timeline { - position: absolute; - right: calc(50% - 562px); - top: 0.5rem; - width: 160px; - display: flex; + .nav-controls { + display: none; flex-direction: column; - gap: 0.25rem; - padding: 0 0 0 16px; - --spine-progress: 0; -} - -.timeline-spine { - position: absolute; - left: 19px; - top: 12px; - bottom: 4px; - width: 4px; - background: rgba(138, 110, 76, 0.12); - border-radius: 2px; - pointer-events: none; -} - -.timeline-spine::after { - content: ''; + align-items: stretch; + gap: 6px; position: absolute; - inset: 0; - width: 100%; - height: calc(var(--spine-progress) * 1%); - background: #8a6e4c; - border-radius: 2px; - transition: height 0.4s cubic-bezier(0.22, 1, 0.36, 1); -} - -[data-theme="dark"] .timeline-spine { - background: rgba(138, 110, 76, 0.12); -} - -[data-theme="dark"] .timeline-spine::after { - background: #8a6e4c; -} + top: calc(100% + 8px); + left: 0; + right: 0; + background: var(--surface); + border: 1px solid var(--border); + border-radius: 14px; + padding: 8px; + box-shadow: var(--shadow-lg); + } -.timeline-node { - position: relative; + .nav-controls.mobile-active { display: flex; - align-items: center; - gap: 0; - opacity: 0; - transform: translateX(-8px); - transition: opacity 0.45s cubic-bezier(0.22, 1, 0.36, 1), transform 0.45s cubic-bezier(0.22, 1, 0.36, 1); -} - -.timeline-node:nth-child(2) { transition-delay: 0.04s; } -.timeline-node:nth-child(3) { transition-delay: 0.08s; } -.timeline-node:nth-child(4) { transition-delay: 0.12s; } -.timeline-node:nth-child(5) { transition-delay: 0.16s; } -.timeline-node:nth-child(6) { transition-delay: 0.2s; } -.timeline-node:nth-child(7) { transition-delay: 0.24s; } -.timeline-node:nth-child(8) { transition-delay: 0.28s; } -.timeline-node:nth-child(9) { transition-delay: 0.32s; } - -.timeline-node.revealed { - opacity: 1; - transform: translateX(0); -} - -.timeline-dot { - flex-shrink: 0; - width: 10px; - height: 10px; - border-radius: 50%; - background: #6abf8d; - border: 2px solid #fff; - box-shadow: 0 0 0 2px rgba(106, 191, 141, 0.25); - z-index: 1; - transition: box-shadow 0.3s ease, transform 0.3s ease; - position: relative; -} - -.timeline-node.revealed .timeline-dot { - box-shadow: 0 0 0 4px rgba(106, 191, 141, 0.15), 0 0 10px rgba(106, 191, 141, 0.25); - transform: scale(1.1); -} - -[data-theme="dark"] .timeline-dot { - border-color: #1e1a18; - box-shadow: 0 0 0 2px rgba(159, 221, 181, 0.3); -} - -[data-theme="dark"] .timeline-node.revealed .timeline-dot { - box-shadow: 0 0 0 4px rgba(159, 221, 181, 0.2), 0 0 10px rgba(159, 221, 181, 0.2); -} - -.timeline-node::before { - content: ''; - flex-shrink: 0; - width: 14px; - height: 3px; - background: #8a6e4c; - border-radius: 2px; - order: 2; -} - -.timeline-dot { order: 1; } - -.timeline-card { order: 3; } - -[data-theme="dark"] .timeline-node::before { - background: #8a6e4c; -} - -.timeline-card { - flex: 1; - min-width: 0; - background: rgba(255, 255, 255, 0.6); - backdrop-filter: blur(12px); - -webkit-backdrop-filter: blur(12px); - border: 1px solid rgba(128, 104, 85, 0.08); - border-radius: 12px; - padding: 0.3rem 0.55rem; - transition: background 0.3s ease, border-color 0.3s ease, box-shadow 0.3s ease; -} - -.timeline-node.revealed .timeline-card { - background: rgba(255, 255, 255, 0.78); - border-color: rgba(106, 191, 141, 0.18); - box-shadow: 0 4px 14px rgba(146, 111, 74, 0.06); -} - -[data-theme="dark"] .timeline-card { - background: rgba(35, 29, 27, 0.75); - border-color: rgba(255, 243, 224, 0.08); - backdrop-filter: blur(12px); -} + } -[data-theme="dark"] .timeline-node.revealed .timeline-card { - background: rgba(40, 34, 32, 0.85); - border-color: rgba(159, 221, 181, 0.2); - box-shadow: 0 4px 14px rgba(0, 0, 0, 0.15); -} - -.timeline-step { - font-family: 'IBM Plex Mono', monospace; - font-size: 0.5rem; - font-weight: 600; - letter-spacing: 0.08em; - color: #6abf8d; - text-transform: uppercase; - display: block; - margin-bottom: 0.05rem; -} - -[data-theme="dark"] .timeline-step { - color: #7fcfbd; -} - -.timeline-title { - font-family: 'Fredoka', sans-serif; - font-size: 0.78rem; - font-weight: 600; - color: var(--text-color); - margin: 0 0 0.05rem; - text-transform: lowercase; - letter-spacing: -0.01em; -} - -.timeline-desc { - font-size: 0.64rem; - line-height: 1.35; - color: var(--text-secondary); - margin: 0; -} - -.hero-content, -.hero-sidekick-card, -.achievement-card, -.featured-lab-card, -.project-card, -.sticky-filter-bar, -.playground-section, -.footer, -.modal-content { - backdrop-filter: blur(18px); - -webkit-backdrop-filter: blur(18px); -} - -.hero-content { - flex: none; + .nav-controls .sound-toggle, + .nav-controls .theme-toggle { width: 100%; - max-width: 760px; - align-items: center; - text-align: center; - padding: 2.25rem 2.5rem 2.5rem; - border-radius: 34px; - background: linear-gradient(180deg, rgba(255, 255, 255, 0.85), rgba(255, 249, 239, 0.7)); - border: 1px solid rgba(128, 104, 85, 0.12); - box-shadow: - 0 2px 0 rgba(128, 104, 85, 0.06), - 0 8px 24px rgba(146, 111, 74, 0.08), - 0 24px 48px rgba(146, 111, 74, 0.06); - position: relative; -} - -.hero-content::before { - content: ''; - position: absolute; - top: 0; - left: 50%; - transform: translateX(-50%); - width: 120px; - height: 4px; - border-radius: 0 0 4px 4px; - background: linear-gradient(90deg, #6abf8d, #f2c26c, #f09b6b); - opacity: 0.7; -} - -[data-theme="dark"] .hero-content { - background: linear-gradient(180deg, rgba(35, 29, 27, 0.95), rgba(23, 19, 18, 0.98)); - border-color: rgba(255, 243, 224, 0.1); -} - -[data-theme="dark"] .hero-content::before { - opacity: 0.4; -} - -.hero-kicker, -.section-kicker, -.mini-label, -.achievement-label, -.lab-tag { - display: inline-flex; - align-items: center; - gap: 0.4rem; - font-family: 'IBM Plex Mono', monospace; - font-size: 0.78rem; - letter-spacing: 0.1em; - text-transform: uppercase; - color: #8c7055; -} - -[data-theme="dark"] .hero-kicker, -[data-theme="dark"] .section-kicker, -[data-theme="dark"] .mini-label, -[data-theme="dark"] .achievement-label, -[data-theme="dark"] .lab-tag { - color: #e0b88f; -} - -.hero-kicker { - display: inline-flex; - align-items: center; - gap: 0.5rem; - padding: 0.45rem 1rem 0.45rem 0.85rem; - border-radius: 999px; - background: linear-gradient(135deg, rgba(106, 191, 141, 0.12), rgba(242, 194, 108, 0.08)); - border: 1px solid rgba(128, 104, 85, 0.08); - font-weight: 600; - letter-spacing: 0.08em; - color: #7a6045; - box-shadow: 0 2px 8px rgba(146, 111, 74, 0.04); -} - -.hero-kicker::before { - content: ''; - width: 6px; - height: 6px; - border-radius: 50%; - background: #6abf8d; - box-shadow: 0 0 8px rgba(106, 191, 141, 0.5); - flex-shrink: 0; -} - -[data-theme="dark"] .hero-kicker { - background: linear-gradient(135deg, rgba(106, 191, 141, 0.18), rgba(242, 194, 108, 0.1)); - border-color: rgba(255, 243, 224, 0.1); - color: #d4b48a; -} - -[data-theme="dark"] .hero-kicker::before { - background: #7fcfbd; -} - -.hero-badge-row, -.projects-header-badges { - display: flex; - flex-wrap: wrap; - gap: 1.5rem; -} - -.hero-features { - justify-content: center; -} - -.hero-pill, -.mini-badge, -.projects-count-badge { - display: inline-flex; - align-items: center; - gap: 0.5rem; - padding: 0.7rem 1rem; - border-radius: 999px; - background: rgba(255, 255, 255, 0.78); - border: 1px solid rgba(128, 104, 85, 0.1); - box-shadow: 0 10px 18px rgba(176, 137, 94, 0.09); -} - -[data-theme="dark"] .hero-pill, -[data-theme="dark"] .mini-badge, -[data-theme="dark"] .projects-count-badge { - background: rgba(255, 255, 255, 0.08); -} - -.hero-title { - font-family: 'Fredoka', 'Segoe UI', sans-serif; - font-size: clamp(3rem, 7vw, 5.6rem); - line-height: 1; - letter-spacing: -0.05em; - margin-top: 0.75rem; -} - -.hero-title span { - display: block; -} - -.hero-title-line + .hero-title-line { - margin-top: 0.18em; -} - -.hero-title-accent { - font-style: italic; - color: #b87745; - max-width: none; - background: linear-gradient(135deg, #b87745, #d49a6a, #b87745); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; - position: relative; - display: inline-block; -} - -[data-theme="dark"] .hero-title-accent { - background: linear-gradient(135deg, #f0bb83, #f5d4a8, #f0bb83); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; -} - -.hero-subtitle { - font-size: 1.08rem; - line-height: 1.7; - color: var(--text-secondary); - max-width: 56ch; - margin: 0.75rem auto 0; - letter-spacing: 0.01em; -} + border-radius: 10px; + height: 40px; + } -.hero-typing { - display: inline-flex; - flex-wrap: wrap; - gap: 0.55rem; - align-items: center; - margin-top: 1.25rem; - padding: 0.75rem 1.25rem; - border-radius: 14px; - background: rgba(44, 36, 31, 0.05); - border: 1px solid rgba(128, 104, 85, 0.06); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.5); -} + .hero-section { + min-height: auto; + padding-top: 100px; + } -[data-theme="dark"] .hero-typing { - background: rgba(255, 255, 255, 0.06); - border-color: rgba(255, 243, 224, 0.08); - box-shadow: none; -} + .hero-title { + font-size: 2.4rem; + } -.typing-label, -.typing-text { - font-family: 'IBM Plex Mono', monospace; -} + .hero-cta-row { + flex-direction: column; + } + + .projects-grid { + grid-template-columns: 1fr; + gap: 12px; + } + + .card-banner, + .card-banner-wrap .card-banner { + height: 120px; + } + + .card-banner-wrap { + width: calc(100% + 32px); + margin: -16px -16px 0; + } + + .card-banner { + width: calc(100% + 32px); + margin: -16px -16px 12px; + } + + .card-gradient-head { + width: calc(100% + 32px); + margin: -16px -16px 12px; + height: 60px; + } + + .filter-sidebar { + margin-bottom: 24px; + } + + .sticky-filter-bar { + top: 72px; + width: calc(100% - 16px); + } + + .cursor-glow { + display: none; + } -.typing-text { - position: relative; - color: #9a643b; - font-weight: 600; -} + .hero-orb { + display: none; + } -[data-theme="dark"] .typing-text { - color: #f0bb83; -} + .hero-meta span { + font-size: 0.72rem; + } -.typing-text::after { - content: ''; - display: inline-block; - width: 1ch; - height: 1em; - margin-left: 2px; - border-right: 2px solid currentColor; - animation: blink 0.9s steps(1) infinite; - vertical-align: -0.12em; + .footer-content { + grid-template-columns: 1fr; + gap: 24px; + } } -@keyframes blink { - 50% { opacity: 0; } -} +@media (max-width: 480px) { + .container { + padding: 0 16px; + } -.hero-meta { - justify-content: center; - gap: 0.8rem; - margin: 1.25rem 0 0; -} + .hero-title { + font-size: 2rem; + } -.hero-meta span { - padding: 0.6rem 0.95rem; - border-radius: 12px; - background: rgba(255, 255, 255, 0.78); - border: 1px solid rgba(128, 104, 85, 0.08); - font-size: 0.82rem; - box-shadow: 0 2px 6px rgba(146, 111, 74, 0.04); - display: inline-flex; - align-items: center; - gap: 0.45rem; -} + .hero-subtitle { + font-size: 0.9rem; + } -[data-theme="dark"] .hero-meta span { - background: rgba(255, 255, 255, 0.08); - border-color: rgba(255, 239, 223, 0.1); - box-shadow: none; -} + .section-header { + flex-direction: column; + align-items: flex-start; + } -.btn-explore, -.btn-secondary-hero, -.btn-random-project, -.lab-action, -.inline-link-button, -.btn-play { - border-radius: 999px; - font-family: 'Fredoka', 'Segoe UI', sans-serif; - font-weight: 600; - transition: transform 0.22s ease, box-shadow 0.22s ease, background 0.22s ease, color 0.22s ease; -} + .project-card { + padding: 16px; + } -.btn-explore, -.btn-random-project, -.lab-action { - background: linear-gradient(135deg, #6abf8d, #7fcfbd); - color: #fffdf8; - box-shadow: 0 18px 28px rgba(106, 191, 141, 0.28); + .modal-content { + padding: 20px; + width: 95%; + border-radius: 16px; + } } -.btn-explore { - padding: 1rem 2.5rem; - font-size: 1.1rem; - position: relative; - top: 0; - box-shadow: 0 8px 0 rgba(0, 0, 0, 0.08), 0 16px 32px rgba(106, 191, 141, 0.3); -} +/* ═══════════════════════════════════════════════════════════════ + REDUCED MOTION + ═══════════════════════════════════════════════════════════════ */ +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } + + html { + scroll-behavior: auto; + } + + .cursor-glow, + .hero-orb, + .hero-orb-one, + .hero-orb-two, + .hero-orb-three { + display: none; + } -.btn-explore:hover { - transform: translateY(-5px); - box-shadow: 0 13px 0 rgba(0, 0, 0, 0.06), 0 22px 40px rgba(106, 191, 141, 0.35); -} + .project-card:hover { + transform: none; + } -.btn-explore:active { - transform: translateY(3px); - box-shadow: 0 4px 0 rgba(0, 0, 0, 0.08), 0 8px 16px rgba(106, 191, 141, 0.2); -} - -.btn-random-project { - padding: 0.9rem 2rem; - font-size: 1rem; - min-width: 220px; - letter-spacing: 0.01em; - box-shadow: 0 12px 0 rgba(0, 0, 0, 0.06), 0 16px 32px rgba(34, 197, 94, 0.3); - background: linear-gradient(135deg, #22c55e, #14b8a6); - color: #ffffff; - position: relative; - top: 0; - margin-top: 0.75rem; -} - -.btn-random-project:hover { - transform: translateY(-4px); - box-shadow: 0 16px 0 rgba(0, 0, 0, 0.05), 0 24px 40px rgba(34, 197, 94, 0.35); -} - -.btn-random-project:active { - transform: translateY(4px); - box-shadow: 0 4px 0 rgba(0, 0, 0, 0.06), 0 8px 16px rgba(34, 197, 94, 0.2); -} - -.btn-secondary-hero, -.inline-link-button, -.btn-play { - background: rgba(255, 255, 255, 0.85); - color: var(--text-color); - border: 1px solid rgba(128, 104, 85, 0.1); -} - -[data-theme="dark"] .btn-secondary-hero, -[data-theme="dark"] .inline-link-button, -[data-theme="dark"] .btn-play { - background: rgba(255, 255, 255, 0.1); - color: var(--text-color); - border-color: rgba(255, 239, 223, 0.12); -} - -.btn-secondary-hero { - padding: 1rem 1.75rem; - position: relative; - top: 0; - box-shadow: 0 6px 0 rgba(128, 104, 85, 0.12), 0 8px 20px rgba(0, 0, 0, 0.06); - transition: transform 0.18s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.18s ease, background 0.18s ease; -} - -.btn-secondary-hero:hover { - transform: translateY(-4px); - box-shadow: 0 10px 0 rgba(128, 104, 85, 0.08), 0 14px 28px rgba(0, 0, 0, 0.08); - background: rgba(255, 255, 255, 0.95); -} - -.btn-secondary-hero:active { - transform: translateY(4px); - box-shadow: 0 2px 0 rgba(128, 104, 85, 0.08), 0 4px 8px rgba(0, 0, 0, 0.04); - top: 2px; -} - -.hero-features, -.sticky-filter-inner { - background: rgba(255, 255, 255, 0.58); - border: 1px solid rgba(128, 104, 85, 0.08); - border-radius: 24px; - padding: 0.65rem; - gap: 0.55rem; -} - -[data-theme="dark"] .hero-features, -[data-theme="dark"] .sticky-filter-inner { - background: rgba(255, 255, 255, 0.06); - border-color: rgba(255, 239, 223, 0.1); -} - -.feature-badge, -.sticky-tab { - border-radius: 999px; - padding: 0.8rem 1rem; - text-transform: none; - font-family: 'Fredoka', 'Segoe UI', sans-serif; - background: transparent; -} - -.feature-icon { - display: inline-flex; -} - -.feature-badge::before, -.feature-badge::after { - display: none; -} - -.feature-badge.active, -.sticky-tab.active { - background: linear-gradient(135deg, rgba(255, 198, 132, 0.32), rgba(125, 220, 185, 0.3)); - color: var(--text-color); - border: 1px solid rgba(128, 104, 85, 0.1); -} - -[data-theme="dark"] .feature-badge, -[data-theme="dark"] .sticky-tab { - color: var(--text-secondary); -} - -[data-theme="dark"] .feature-badge.active, -[data-theme="dark"] .sticky-tab.active { - border-color: rgba(255, 239, 223, 0.12); -} - -.sticky-filter-bar { - top: 16px; - left: 12px; - right: 12px; - width: auto; - background: transparent; - border-bottom: 0; - box-shadow: none; -} - -.hero-card-topline, -.progress-card-header, -.progress-caption, -.featured-lab-header, -.section-heading, -.projects-header { - display: flex; - justify-content: space-between; - gap: 1rem; -} - -.progress-card { - padding: 1rem; - border-radius: 22px; - background: rgba(255, 255, 255, 0.62); - border: 1px solid rgba(128, 104, 85, 0.08); -} - -[data-theme="dark"] .progress-card { - background: rgba(255, 255, 255, 0.07); - border-color: rgba(255, 239, 223, 0.1); -} - -.progress-track { - overflow: hidden; - height: 12px; - border-radius: 999px; - background: rgba(106, 191, 141, 0.14); - margin: 0.85rem 0 0.75rem; -} - -.progress-fill { - display: block; - height: 100%; - border-radius: inherit; - background: linear-gradient(90deg, #6abf8d, #f2c26c); -} - -.achievement-grid { - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 1rem; -} - -.reward-mint, -.lab-card-mint { - background: linear-gradient(160deg, rgba(203, 247, 224, 0.9), rgba(255, 251, 241, 0.9)); -} - -.reward-lavender { - background: linear-gradient(160deg, rgba(236, 226, 255, 0.9), rgba(255, 251, 241, 0.9)); -} - -.lab-card-orange { - background: linear-gradient(160deg, rgba(255, 225, 191, 0.92), rgba(255, 252, 244, 0.94)); -} - -.lab-card-cyan { - background: linear-gradient(160deg, rgba(208, 245, 246, 0.92), rgba(255, 252, 244, 0.94)); -} - -.achievement-strip, -.projects-section { - padding: 1.2rem 0 2.5rem; -} - -.achievement-grid { - grid-template-columns: 1.4fr repeat(3, minmax(0, 1fr)); -} - -.achievement-card { - padding: 1.35rem; - border-radius: 28px; - background: rgba(255, 255, 255, 0.72); - border: 1px solid rgba(128, 104, 85, 0.1); - box-shadow: var(--shadow); -} - -[data-theme="dark"] .achievement-card { - background: rgba(255, 255, 255, 0.07); - border-color: rgba(255, 239, 223, 0.1); -} - -.achievement-card-gold { - display: grid; - grid-template-columns: auto 1fr; - gap: 1rem; - align-items: start; - background: linear-gradient(145deg, rgba(255, 240, 210, 0.92), rgba(255, 252, 245, 0.94)); -} - -.achievement-icon, -.achievement-stat { - width: 64px; - height: 64px; - border-radius: 20px; - display: grid; - place-items: center; - background: linear-gradient(135deg, #ffd497, #ffefc9); - color: #8f5f26; - font-size: 1.35rem; - font-family: 'Fredoka', sans-serif; -} - -.achievement-stat { - width: auto; - height: auto; - border-radius: 18px; - padding: 0.45rem 0.8rem; - font-size: 2rem; -} - -.section-heading, -.projects-header { - align-items: end; - margin-bottom: 1.3rem; -} - -.section-heading h2, -.projects-header h2 { - font-family: 'Fredoka', sans-serif; - font-size: clamp(1.8rem, 4vw, 3rem); - line-height: 1; - margin-top: 0.4rem; -} - -.projects-grid { - gap: 1.5rem; -} - -.project-card { - position: relative; - overflow: hidden; - padding: 1.5rem 1.5rem 1.25rem; - display: flex; - flex-direction: column; - border-radius: 24px; - background: var(--surface-color); - border: 2px solid var(--accent-border); - box-shadow: var(--shadow); - transition: transform 0.25s ease, box-shadow 0.25s ease; - backdrop-filter: none; - -webkit-backdrop-filter: none; -} - -.project-card:hover { - transform: translateY(-6px); - border-color: var(--accent-color); - box-shadow: 0 20px 35px rgba(0, 0, 0, 0.15); -} - -[data-theme="light"] .project-card { - background: #ffffff; - border-color: rgba(106, 191, 141, 0.4); -} - -[data-theme="dark"] .project-card { - background: #1e1a18; - border-color: rgba(159, 221, 181, 0.3); -} - -.card-banner { - width: calc(100% + 3rem); - max-width: none; - margin: -1.5rem -1.5rem 0.85rem; - height: 140px; - object-fit: cover; - display: block; - border-radius: 24px 24px 0 0; - flex-shrink: 0; -} - -.card-actions { - display: flex; - align-items: center; - gap: 0.5rem; - margin-bottom: 0.75rem; - width: 100%; -} - -.card-actions .btn-play { - margin-top: 0; - padding: 0.5rem 1rem; - font-size: 0.82rem; -} - -.card-actions .btn-favorite, -.card-actions .btn-share { - position: static; - width: 32px; - height: 32px; - display: flex; - align-items: center; - justify-content: center; - border: none; - border-radius: 8px; - cursor: pointer; - flex-shrink: 0; - font-size: 0.85rem; - line-height: 1; - transition: background 0.18s ease, transform 0.18s ease; -} - -[data-theme="dark"] .card-icon { - background: linear-gradient(145deg, rgba(40, 40, 40, 0.8), rgba(20, 20, 20, 0.9)); - color: var(--footer-card-color); - box-shadow: inset 0 1px 0 rgba(252, 176, 91, 0.08); - border-color: rgba(252, 176, 91, 0.1); - -.card-actions .btn-share { - background: rgba(128, 104, 85, 0.06); - color: var(--text-secondary); -} - -.card-actions .btn-share:hover { - background: rgba(106, 191, 141, 0.12); - color: #4a8a5e; - transform: scale(1.1); -} - -.card-actions .btn-favorite { - background: transparent; - color: #a09080; - font-size: 0.9rem; -} - -.card-actions .btn-favorite:hover { - color: #f5a623; - transform: scale(1.15); -} - -.card-actions .btn-favorite.active { - color: #f5a623; -} - -[data-theme="dark"] .card-actions .btn-share { - background: rgba(255, 255, 255, 0.06); - color: var(--text-secondary); -} - -[data-theme="dark"] .card-actions .btn-share:hover { - background: rgba(106, 191, 141, 0.15); - color: #7fcfbd; -} - -[data-theme="dark"] .card-actions .btn-favorite { - color: #7a6a5a; -} - -[data-theme="dark"] .card-actions .btn-favorite:hover, -[data-theme="dark"] .card-actions .btn-favorite.active { - color: #f5a623; -} - -.project-card h3 { - font-family: 'Fredoka', sans-serif; - font-size: 1.3rem; - margin-top: 0.85rem; - margin-bottom: 0; -} - -.project-card p { - color: var(--text-secondary); - margin: 0.5rem 0 0.75rem; - font-size: 0.92rem; - line-height: 1.5; -} - -.btn-play { - padding: 0.75rem 1.2rem; - font-size: 0.92rem; -} - -.project-card-badge { - display: inline-flex; - align-items: center; - gap: 0.3rem; - padding: 0.35rem 0.65rem; - border-radius: 6px; - font-size: 0.7rem; - font-weight: 600; - font-family: 'IBM Plex Mono', monospace; - text-transform: uppercase; - letter-spacing: 0.04em; - background: rgba(106, 191, 141, 0.08); - color: #5a8a5a; - border: none; -} - -[data-theme="dark"] .project-card-badge { - background: rgba(106, 191, 141, 0.12); - color: #7fcfbd; -} - -.project-card-meta { - display: flex; - flex-wrap: wrap; - align-items: center; - gap: 0.5rem; - margin-top: 0.5rem; - padding-top: 0.5rem; - border-top: 1px solid rgba(128, 104, 85, 0.06); -} - -.footer { - margin-top: 3rem; - background: rgba(255, 252, 245, 0.76); - border-top: 1px solid rgba(128, 104, 85, 0.08); -} - -[data-theme="dark"] .footer { - background: rgba(0, 0, 0, 0.7); -} - -[data-theme="dark"] .footer, -[data-theme="dark"] .footer a, -[data-theme="dark"] .footer p, -[data-theme="dark"] .footer h2, -[data-theme="dark"] .footer h3 { - color: var(--text-color); -} - -.reveal-on-scroll { - opacity: 0; - transform: translateY(20px); -} - -.reveal-on-scroll.is-visible { - opacity: 1; - transform: translateY(0); - transition: opacity 0.55s ease, transform 0.55s ease; -} - -@media (max-width: 1100px) { - .achievement-grid { - grid-template-columns: 1fr; - } - - .hero-timeline { - display: none; - } - - .hero-shell { - padding: 0 1rem; - } - - .hero-content { - max-width: 700px; - margin-left: auto; - margin-right: auto; - } -} - -@media (max-width: 900px) { - .hero-shell { - flex-direction: column; - gap: 1rem; - padding: 0; - } - - .hero-shell .filter-sidebar { - position: static; - flex: none; - width: 100%; - flex-direction: row; - flex-wrap: wrap; - align-items: center; - gap: 0.3rem; - padding: 0.75rem; - border-radius: 16px; - overflow-x: auto; - } - - .hero-shell .filter-sidebar .sidebar-header, - .hero-shell .filter-sidebar .sidebar-divider, - .hero-shell .filter-sidebar .sidebar-random-btn { - display: none; - } - - .hero-shell .filter-sidebar .sidebar-tab { - flex: 0 0 auto; - padding: 0.4rem 0.7rem; - font-size: 0.82rem; - border-radius: 999px; - width: auto; - } - - .hero-shell .filter-sidebar .sidebar-tab.active { - border-left: none; - border-radius: 999px; - } - - .hero-shell .filter-sidebar .sidebar-tab:hover { - transform: none; - } - - .hero-shell .filter-sidebar .sidebar-icon { - width: 22px; - height: 22px; - } - - .hero-shell .filter-sidebar .sidebar-icon i, - .hero-shell .filter-sidebar .sidebar-icon svg { - width: 14px; - height: 14px; - } -} - -@media (max-width: 768px) { - .cursor-glow { - display: none; - } - - .nav-wrapper { - border-radius: 28px; - flex-wrap: wrap; - } - - .search-box { - max-width: none; - width: 100%; - order: 3; - } - - .hero-content { - padding: 1.5rem 1.35rem; - } - - .hero-content::before { - width: 80px; - } - - .hero-title { - font-size: 2.8rem; - } - - .hero-meta span { - padding: 0.5rem 0.75rem; - font-size: 0.75rem; - } - - .section-heading, - .projects-header, - .hero-card-topline, - .progress-card-header, - .progress-caption { - flex-direction: column; - align-items: flex-start; - } -} - -@media (prefers-reduced-motion: reduce) { - .cursor-glow, - .hero-orb-one, - .hero-orb-two, - .typing-text::after { - animation: none !important; - } - - .project-card, - .project-card:hover, - .btn-explore, - .btn-secondary-hero, - .btn-random-project, - .lab-action, - .inline-link-button, - .btn-play { - transform: none !important; - } -} - - /* Fix card content alignment */ -.project-card { - text-align: left; - align-items: flex-start; -} - -.project-card h3, -.project-card p { - width: 100%; - text-align: left; -} - -/* Ensure card-actions is aligned properly */ -.project-card .card-actions { - width: 100%; -} - -/* Fix grid gaps on small screens */ -@media (max-width: 480px) { - .projects-grid { - gap: 1rem; - } - .project-card { - padding: 1rem; - } -} - -/* ========================================================================== - FINAL CARD + MODAL CONSISTENCY OVERRIDES - This section is intentionally appended at the end to override earlier - duplicate card/modal definitions and enforce consistent spacing, sizing, - and responsive behavior across all project pages. - ========================================================================== */ - -.projects-grid { - display: grid !important; - grid-template-columns: repeat(auto-fit, minmax(260px, 360px)) !important; - justify-content: center !important; - gap: 1.2rem !important; - margin: 0 auto 2rem !important; - align-items: stretch !important; -} - -.projects-grid > .project-card { - justify-self: center !important; - width: 100% !important; - max-width: 360px !important; -} - -.project-card { - min-height: 240px !important; - display: flex; - flex-direction: column !important; - justify-content: flex-start !important; - align-items: flex-start !important; - padding: 1.75rem 1.5rem 1.5rem !important; - border-radius: 28px !important; - background: var(--surface-color) !important; - border: 1px solid var(--accent-border) !important; - box-shadow: 0 18px 40px rgba(0, 0, 0, 0.08) !important; - transition: transform 0.2s ease !important; - margin: 0 auto !important; - font-size: 0.95rem !important; - line-height: 1.6 !important; -} - -.project-card .btn-play { - margin-top: 1rem !important; - align-self: flex-start !important; -} - -.card-icon { - width: 72px !important; - height: 72px !important; - display: grid !important; - place-items: center !important; - border-radius: 22px !important; - background: var(--accent-soft) !important; - color: var(--accent-color) !important; - margin: 0 auto 1.2rem !important; - font-size: 2rem !important; -} - -.modal { - display: none !important; - position: fixed !important; - top: 0 !important; - left: 0 !important; - width: 100% !important; - height: 100% !important; - background: var(--overlay-color) !important; - backdrop-filter: blur(6px) !important; - -webkit-backdrop-filter: blur(6px) !important; - z-index: 2000 !important; - animation: fadeIn 0.3s ease !important; -} - -.modal.active { - display: flex !important; - align-items: center !important; - justify-content: center !important; -} - -.modal-content { - width: min(100%, 900px) !important; - max-width: 900px !important; - min-width: 320px !important; - max-height: 92vh !important; - overflow-y: auto !important; - overflow-x: hidden !important; - padding: 2rem !important; - border-radius: 20px !important; - box-shadow: var(--shadow-modal) !important; - background: var(--surface-color) !important; - border: 1px solid var(--border-color) !important; - margin: auto !important; -} - -#modalBody { - width: 100% !important; - display: flex !important; - flex-direction: column !important; - gap: 1rem !important; - align-items: stretch !important; -} - -#modalBody > * { - width: 100% !important; - max-width: 100% !important; -} - -.modal-close { - z-index: 2100 !important; -} - -@media (max-width: 768px) { - .modal-content { - width: min(100%, 92vw) !important; - padding: 1.5rem !important; - max-height: 90vh !important; - } - - .project-card { - min-height: auto !important; - padding: 1.5rem 1.25rem !important; - } -} - -@media (max-width: 576px) { - .projects-grid { - grid-template-columns: 1fr !important; - gap: 1rem !important; - } - - .project-card { - padding: 1.25rem !important; - min-height: auto !important; - } -} -/* ============================== - Tic-Tac-Toe - =============================== */ -/* ═══════════════════════════════════════════════════════════ - TTT — Tic Tac Toe (scoped under .ttt-wrap) - Safe to append to the global styles.css. - Every selector starts with .ttt- so nothing leaks out. - Uses repo CSS vars: --accent-color, --surface-color, - --panel-color, --text-color, --text-secondary, --border-color -═══════════════════════════════════════════════════════════ */ - -/* ── Root tokens (scoped inside .ttt-wrap) ── */ -.ttt-wrap { - --ttt-x: #e05c6f; /* red — player X */ - --ttt-o: #3bb8d6; /* cyan — player O */ - --ttt-accent: var(--accent-color, #6abf8d); - --ttt-surface: var(--surface-color, #fff); - --ttt-panel: var(--panel-color, #f5f5f5); - --ttt-border: var(--border-color, rgba(0,0,0,.1)); - --ttt-text: var(--text-color, #1f1f1f); - --ttt-muted: var(--text-secondary, #666); - --ttt-radius: 14px; - --ttt-cell-bg: var(--panel-color, #f5f5f5); - - display: block; - max-width: 400px; - margin: 0 auto; - padding: 4px 0 24px; - font-family: inherit; - color: var(--ttt-text); -} - -/* ── Screens ── */ -.ttt-screen { display: none; } -.ttt-screen--active { display: block; } - -/* ── Logo ── */ -.ttt-logo { - text-align: center; - font-size: 2.2rem; - font-weight: 800; - letter-spacing: .06em; - line-height: 1; - margin-bottom: 2px; -} -.ttt-logo-x { color: var(--ttt-x); text-shadow: 0 0 16px rgba(224,92,111,.35); } -.ttt-logo-o { color: var(--ttt-o); text-shadow: 0 0 16px rgba(59,184,214,.35); } -.ttt-logo-dot { color: var(--ttt-muted); margin: 0 4px; } - -/* ── Titles ── */ -.ttt-title { - text-align: center; - font-size: 1rem; - font-weight: 700; - letter-spacing: .18em; - text-transform: uppercase; - color: var(--ttt-muted); - margin: 0 0 2px; -} -.ttt-sub { - text-align: center; - font-size: .84rem; - color: var(--ttt-muted); - margin: 0 0 20px; -} - -/* ── Field groups ── */ -.ttt-field-group { - margin-bottom: 16px; -} -.ttt-label { - display: block; - font-size: .7rem; - font-weight: 700; - letter-spacing: .15em; - text-transform: uppercase; - color: var(--ttt-muted); - margin-bottom: 7px; - font-style: normal; -} -.ttt-x-tag { color: var(--ttt-x); font-style: normal; } -.ttt-o-tag { color: var(--ttt-o); font-style: normal; } - -/* ── Pill toggle buttons ── - Fully isolated: overrides .game-btn, .mode-btn, .btn from global CSS */ -.ttt-pill-group { - display: flex !important; - gap: 6px !important; - background: var(--ttt-panel) !important; - border: 1px solid var(--ttt-border) !important; - border-radius: 10px !important; - padding: 4px !important; - box-shadow: none !important; -} - -/* Reset every possible global override then re-style */ -.ttt-wrap .ttt-pill, -.ttt-wrap .ttt-pill:hover, -.ttt-wrap .ttt-pill:focus, -.ttt-wrap .ttt-pill:active { - /* layout */ - flex: 1 1 0 !important; - display: inline-flex !important; - align-items: center !important; - justify-content: center !important; - padding: 9px 6px !important; - min-width: 0 !important; - width: auto !important; - - /* look */ - background: transparent !important; - border: none !important; - border-radius: 7px !important; - box-shadow: none !important; - outline: none !important; - - /* type */ - color: var(--ttt-muted) !important; - font-family: inherit !important; - font-size: .82rem !important; - font-weight: 700 !important; - text-transform: none !important; - letter-spacing: normal !important; - white-space: nowrap !important; - - cursor: pointer !important; - transition: background .18s, color .18s, box-shadow .18s !important; - - /* undo global transform/translate tricks */ - transform: none !important; - top: 0 !important; -} - -.ttt-wrap .ttt-pill:hover:not(.ttt-pill--on) { - background: rgba(0,0,0,.06) !important; - color: var(--ttt-text) !important; -} - -[data-theme="dark"] .ttt-wrap .ttt-pill:hover:not(.ttt-pill--on) { - background: rgba(255,255,255,.08) !important; -} - -.ttt-wrap .ttt-pill.ttt-pill--on { - background: var(--ttt-accent) !important; - color: #fff !important; - box-shadow: 0 2px 10px rgba(106,191,141,.4) !important; - transform: none !important; -} - -/* ── Name inputs ── */ -.ttt-names-row { display: flex; gap: 12px; margin-bottom: 18px; } -.ttt-name-box { flex: 1; display: flex; flex-direction: column; gap: 6px; } -.ttt-name-box.ttt-dimmed { opacity: .45; pointer-events: none; } - -.ttt-wrap .ttt-input { - display: block !important; - width: 100% !important; - padding: 9px 11px !important; - background: var(--ttt-panel) !important; - border: 1.5px solid var(--ttt-border) !important; - border-radius: 9px !important; - color: var(--ttt-text) !important; - font-family: inherit !important; - font-size: .9rem !important; - outline: none !important; - box-shadow: none !important; - transition: border-color .2s !important; -} -.ttt-wrap .ttt-input:focus { - border-color: var(--ttt-accent) !important; - box-shadow: 0 0 0 3px rgba(106,191,141,.15) !important; -} - -/* ── CTA / primary button ── */ -.ttt-wrap .ttt-cta { - display: block !important; - width: 100% !important; - padding: 13px 16px !important; - background: var(--ttt-accent) !important; - color: #fff !important; - border: none !important; - border-radius: var(--ttt-radius) !important; - font-family: inherit !important; - font-size: 1rem !important; - font-weight: 800 !important; - letter-spacing: .04em !important; - cursor: pointer !important; - text-align: center !important; - box-shadow: 0 4px 0 rgba(0,0,0,.1), 0 8px 20px rgba(106,191,141,.3) !important; - transition: transform .18s cubic-bezier(.34,1.56,.64,1), box-shadow .18s !important; - transform: none !important; - top: 0 !important; - position: relative !important; -} -.ttt-wrap .ttt-cta:hover { - transform: translateY(-3px) !important; - box-shadow: 0 7px 0 rgba(0,0,0,.08), 0 14px 28px rgba(106,191,141,.4) !important; -} -.ttt-wrap .ttt-cta:active { - transform: translateY(2px) !important; - box-shadow: 0 2px 0 rgba(0,0,0,.1), 0 4px 10px rgba(106,191,141,.2) !important; -} -.ttt-wrap .ttt-cta.ttt-cta--sm { - width: auto !important; - display: inline-block !important; - padding: 10px 28px !important; - font-size: .9rem !important; -} - -/* ── Ghost / back button ── */ -.ttt-wrap .ttt-ghost-btn { - display: block !important; - width: 100% !important; - margin-top: 10px !important; - padding: 10px !important; - background: transparent !important; - border: 1.5px solid var(--ttt-border) !important; - border-radius: var(--ttt-radius) !important; - color: var(--ttt-muted) !important; - font-family: inherit !important; - font-size: .85rem !important; - font-weight: 700 !important; - cursor: pointer !important; - transition: color .2s, border-color .2s !important; - text-align: center !important; - transform: none !important; - box-shadow: none !important; -} -.ttt-wrap .ttt-ghost-btn:hover { - color: var(--ttt-text) !important; - border-color: var(--ttt-text) !important; - box-shadow: none !important; - transform: none !important; -} - -/* ══════════════════════════════════════════ - GAME SCREEN -══════════════════════════════════════════ */ - -/* ── Scoreboard ── */ -.ttt-scores { - display: flex; - gap: 4px; - background: var(--ttt-panel); - border: 1px solid var(--ttt-border); - border-radius: 12px; - padding: 10px 8px; - margin-bottom: 8px; -} -.ttt-score { flex: 1; text-align: center; } -.ttt-score-name { - font-size: .64rem; - font-weight: 700; - letter-spacing: .1em; - text-transform: uppercase; - color: var(--ttt-muted); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - margin-bottom: 2px; -} -.ttt-score-val { font-size: 1.55rem; font-weight: 800; } -.ttt-score--x .ttt-score-val { color: var(--ttt-x); } -.ttt-score--o .ttt-score-val { color: var(--ttt-o); } -.ttt-score--d .ttt-score-val { color: var(--ttt-muted); font-size: 1.1rem; } - -/* ── Round tag ── */ -.ttt-round-tag { - text-align: center; - font-size: .7rem; - font-weight: 700; - letter-spacing: .14em; - text-transform: uppercase; - color: var(--ttt-muted); - margin-bottom: 10px; -} - -/* ── Turn banner ── */ -.ttt-turn-banner { - text-align: center; - font-size: .95rem; - font-weight: 700; - color: var(--ttt-muted); - margin-bottom: 14px; - min-height: 22px; - transition: color .2s; -} -.ttt-turn-sym { - font-size: 1.05rem; - margin-right: 4px; - display: inline-block; -} -.ttt-turn-banner--x .ttt-turn-sym { text-shadow: 0 0 10px rgba(224,92,111,.5); } -.ttt-turn-banner--o .ttt-turn-sym { text-shadow: 0 0 10px rgba(59,184,214,.5); } - -/* ── Board wrapper (position context for SVG overlay) ── */ -#ttt-board { - position: relative; - display: grid !important; - grid-template-columns: repeat(3, 1fr) !important; - gap: 8px !important; - margin-bottom: 0 !important; -} - -/* ── Cells ── */ -.ttt-wrap .ttt-cell { - /* layout */ - aspect-ratio: 1 !important; - display: flex !important; - align-items: center !important; - justify-content: center !important; - - /* look */ - background: var(--ttt-cell-bg) !important; - border: 2px solid var(--ttt-border) !important; - border-radius: var(--ttt-radius) !important; - box-shadow: none !important; - - /* type */ - font-size: 2rem !important; - font-family: inherit !important; - color: var(--ttt-text) !important; - text-align: center !important; - line-height: 1 !important; - - cursor: pointer !important; - user-select: none !important; - transition: background .15s, border-color .15s, transform .12s !important; - transform: none !important; - top: 0 !important; - position: relative !important; - padding: 0 !important; - margin: 0 !important; - width: 100% !important; -} - -.ttt-wrap .ttt-cell:hover:not(:disabled):not(.ttt-cell--x):not(.ttt-cell--o) { - background: var(--ttt-accent) !important; - opacity: .15 !important; - border-color: var(--ttt-accent) !important; - transform: scale(1.04) !important; -} -/* Reset opacity on non-hover so the above doesn't tint filled cells */ -.ttt-wrap .ttt-cell { opacity: 1 !important; } -.ttt-wrap .ttt-cell:hover:not(:disabled):not(.ttt-cell--x):not(.ttt-cell--o) { - opacity: 1 !important; - background: rgba(106,191,141,.12) !important; -} - -.ttt-wrap .ttt-cell:disabled:not(.ttt-cell--x):not(.ttt-cell--o) { - cursor: not-allowed !important; -} - -.ttt-wrap .ttt-cell--x { - color: var(--ttt-x) !important; - border-color: rgba(224,92,111,.3) !important; - background: rgba(224,92,111,.07) !important; - animation: tttPop .22s cubic-bezier(.34,1.56,.64,1) !important; - cursor: default !important; - font-weight: 800 !important; - -} -.ttt-wrap .ttt-cell--o { - color: var(--ttt-o) !important; - border-color: rgba(59,184,214,.3) !important; - background: rgba(59,184,214,.07) !important; - animation: tttPop .22s cubic-bezier(.34,1.56,.64,1) !important; - cursor: default !important; - font-weight: 800 !important; -} -.ttt-wrap .ttt-cell--win { - animation: tttWin .45s ease forwards !important; - border-color: var(--ttt-accent) !important; - background: rgba(106,191,141,.15) !important; -} - -@keyframes tttPop { - from { transform: scale(.35); opacity: 0; } - to { transform: scale(1); opacity: 1; } -} -@keyframes tttWin { - 0% { transform: scale(1); } - 40% { transform: scale(1.14); } - 70% { transform: scale(.96); } - 100% { transform: scale(1.07); } -} - -/* ── Win-line SVG (sits over the board) ── */ -.ttt-win-svg { - position: absolute; - inset: 0; - width: calc(100% + 8px); - height: calc(100% + 8px); - pointer-events: none; - overflow: visible; - opacity: 0; - transition: opacity .25s; - z-index: 2; - display: block; -} -.ttt-win-svg--visible { opacity: 1; } -#ttt-win-line { - stroke: var(--ttt-accent); - stroke-width: .18; - stroke-linecap: round; - filter: drop-shadow(0 0 4px rgba(106,191,141,.6)); - transition: opacity .3s; -} - -/* ── Result overlay (sits on top of board area) ── */ -.ttt-result-overlay { - display: none; - position: absolute; - inset: 0; - background: rgba(0,0,0,.45); - border-radius: var(--ttt-radius); - align-items: center; - justify-content: center; - z-index: 10; - animation: tttFadeIn .3s ease; -} -#ttt-game { position: relative; } - -@keyframes tttFadeIn { - from { opacity:0; } - to { opacity:1; } -} - -.ttt-result-card { - background: var(--ttt-surface); - border: 1px solid var(--ttt-border); - border-radius: var(--ttt-radius); - padding: 24px 28px; - text-align: center; - animation: tttSlideUp .32s cubic-bezier(.34,1.56,.64,1); -} -@keyframes tttSlideUp { - from { transform: translateY(24px) scale(.92); opacity:0; } - to { transform: translateY(0) scale(1); opacity:1; } -} - -.ttt-result-emoji { font-size: 2.4rem; margin-bottom: 6px; } -.ttt-result-text { font-size: 1.15rem; font-weight: 800; margin-bottom: 16px; color: var(--ttt-text); } - -/* ══════════════════════════════════════════ - FINAL SCREEN -══════════════════════════════════════════ */ -#ttt-final { text-align: center; padding: 8px 0; } - -.ttt-final-trophy { font-size: 3rem; margin-bottom: 8px; } -.ttt-final-title { - font-size: 1.3rem; - font-weight: 800; - margin-bottom: 18px; - color: var(--ttt-text); -} -.ttt-final-scoreline { - display: flex; - align-items: center; - justify-content: center; - gap: 12px; - margin-bottom: 6px; -} -.ttt-fs-name { font-size: .82rem; font-weight: 700; } -.ttt-fs-x { color: var(--ttt-x); } -.ttt-fs-o { color: var(--ttt-o); } -.ttt-fs-score { font-size: 2.2rem; font-weight: 800; color: var(--ttt-text); } -.ttt-fs-sep { font-size: 1.3rem; color: var(--ttt-muted); } -.ttt-final-draws { - font-size: .8rem; - color: var(--ttt-muted); - margin-bottom: 20px; -} -.ttt-final-actions { - display: flex; - flex-direction: column; - gap: 8px; -} - -/* ── Mobile ── */ -@media (max-width: 420px) { - .ttt-wrap { padding: 4px 0 16px; } - .ttt-wrap .ttt-cell { font-size: 1.6rem !important; } - .ttt-result-card { padding: 18px 16px; } + .btn-primary-hero:hover, + .btn-secondary-hero:hover { + transform: none; + } } diff --git a/web-app/games.html b/web-app/games.html index 895f670..697ed17 100644 --- a/web-app/games.html +++ b/web-app/games.html @@ -90,44 +90,103 @@ - + - - + +
- -
- -
-
@@ -319,7 +378,9 @@
+ + + + + -
-
- -
-
-

🐍 Python Playground

-
- -Open tab to load Python +
+ +
-
-

- Write and run real Python code in your browser — powered by - Pyodide. - No install, no backend. -

-

-Ctrl+Enter runs code  ·  - Tab inserts 4 spaces -

-
- -
- -
-
- - - Code Editor - -
- - -
-
- -
- -
-
- - - Output Console - - -
-

-
-
- -
- - -
-
-
- - - - - - - - - - - - - - - - - - - - + +
+ +
+ + + + + + +
+ +
+ PyMini Logo +
+ py.mini() +
+
+ +
+ + + Python Arcade + + + + System Status: Nominal + +
+
+

+ Build tiny Python + projects & play +

+
+

Jump into browser-ready games, math experiments, and utility labs — each one ready in your browser, no install required.

+ + +
+ + + + + Open Contribution + +
+
+ 0 total projects + 0 games + 0 utilities +
+
+
- - - - - - - + +
+ + + + +
+
TRACK-ROUTE
+
+
+
+ +
+ 01 +

Welcome

+

Jump into a tiny Python arcade built for curious beginners

+
+
+
+
+
+
+
+
+
+ +
+ 02 +

Explore

+

Browse games, math puzzles & utilities — each one ready in your browser

+
+
+
+
+
+ +
+ 03 +

Play

+

Launch any project instantly, experiment with code, and share your wins

+
+
+
+
+
+
+
+
+
+ +
+ 04 +

Learn

+

Pick up Python concepts as you play — loops, logic, data & more

+
+
+
+
+
+ +
+ 05 +

Build

+

Create your own mini projects and share them with the community

+
+
+
+
+
+
+
+
+
+ +
+ 06 +

Share

+

Show off your work, get feedback, and keep growing as a dev

+
+
+
+
+
+
+ +
+
+
+

Projects

+ +
+
+ +
+ +
+ Game +

BlackJack 21

+

Beat the dealer with strategy!

+
🃏 52 cards🎯 21
+
+
+ +
+ Game +

Rock Paper Scissors

+

Battle against the computer!

+
⚔️ Best of 5
+
+
+ +
+ Game +

Dice Rolling

+

Roll the dice with 3D animation!

+
🎲 2 dice
+
+
+ +
+ Game +

Coin Flip

+

Heads or tails with spinning animation!

+
🪙 50/50
+
+
+ +
+ Game +

Number Guessing

+

Guess the secret number!

+
🎯 1–100
+
+
+ +
+ Game +

Hangman

+

Classic word-guessing game!

+
📝 6 lives
+
+
+ +
+ Game +

Word Scramble

+

Unscramble words before time runs out!

+
🔤 3 rounds
+
+
+ +
+ Game +

FLAMES Game

+

Discover your relationship status!

+
💖 6 outcomes
+
+
+ +
+ Game +

Dots & Boxes AI

+

Challenge friends or AI in strategy!

+
🧠 AI mode
+
+
+ +
+ Game +

Password Forge

+

Survive evolving firewall password rules!

+
🔐 10 levels
+
+
+ +
+ Game +

Math Quiz

+

MCQ quiz with lives and difficulty levels!

+
🧮 4 levels
+
+
+ +
+ Game +

Snake Game

+

Classic snake — eat, grow, survive!

+
🐍 Classic🏆 High score
+
+
+ +
+ Game +

Emoji Memory Game

+

Test your memory with matching emojis!

+
🧩 16 tiles
+
+
+ +
+ Game +

Whack-a-Mole

+

Hit the moles before they disappear!

+
🔨 30s
+
+
+ +
+ Game +

Flappy Game

+

Dodge the incoming balls and survive!

+
🐤 Endless
+
+
+ +
+ Game +

2048 Game

+

Combine tiles and reach 2048!

+
🔢 4×4
+
+
+ +
+ Game +

Tic Tac Toe

+

Classic 3-in-a-row with AI!

+
❌ 3×3
+
+ + +
+ +
+ Math +

Fibonacci Series

+

Generate Fibonacci sequences!

+
🔢 Spiral viz
+
+
+ +
+ Math +

AP/GP/AGP/HP Recognizer

+

Identify progression types from any sequence.

+
📊 4 types
+
+
+ +
+ Math +

Pascal's Triangle

+

Beautiful hexagon visualization!

+
🔺 12 rows
+
+
+ +
+ Math +

Armstrong Numbers

+

Check special number properties!

+
🔢 3 digits
+
+
+ +
+ Math +

Calculator

+

Your mathematical companion!

+
🧮 Full-featured
+
+
+ +
+ Math +

Collatz Conjecture

+

Explore the 3n+1 problem!

+
🔢 Graph viz
+
+
+ +
+ Math +

Prime Analyzer

+

All-in-one prime number toolkit!

+
🔢 Factorizer
+
+
+ +
+ Math +

Projectile Motion

+

Calculate TOF, Hmax, Range!

+
🚀 Physics
+
+
+ +
+ Math +

Coordinate to Polar

+

Transform Cartesian (x, y) into polar (r, θ).

+
📐 Geometry
+
+
+ +
+ Math +

Derivative Calculator

+

Compute 1st/nth polynomial derivatives.

+
📈 Calculus
+
+ + +
+ +
+ Utility +

Morse Code

+

Translate with lights & sound!

+
📡 Audio
+
+
+ +
+ Utility +

Tower of Hanoi

+

Solve the classic puzzle!

+
🗼 3 pegs
+
+
+ +
+ Utility +

Number Converter

+

Convert Dec, Bin, Oct, and Hex!

+
🔢 4 bases
+
+
+ +
+ Utility +

Typing Speed Tester

+

Test your WPM and accuracy!

+
⌨️ WPM
+
+
+ +
+ Utility +

Color Palette Suggestor

+

Generate palettes + CSS snippets for any mood!

+
🎨 CSS ready
+
+
+ +
+ Utility +

Productivity Pet

+

A virtual pet that grows as you work!

+
🐾 Pomodoro
+
+
+ +
+
+ + - - +
- - - - + - + + - + + - + @@ -1627,67 +737,10 @@

Stay Updated

- - - - - - + diff --git a/web-app/js/hero-canvas.js b/web-app/js/hero-canvas.js index ff44838..edc05de 100644 --- a/web-app/js/hero-canvas.js +++ b/web-app/js/hero-canvas.js @@ -1,297 +1,181 @@ -/* ═══════════════════════════════════════════ - ANIMATED SNAKES & LADDERS BOARD ON CANVAS - Fixed: canvas now fills 100% of hero section -═══════════════════════════════════════════ */ -const canvas = document.getElementById('boardCanvas'); - -if (canvas) { - const ctx = canvas.getContext('2d'); - - /* Force canvas to fill the hero section absolutely */ -(function positionCanvas() { - const hero = canvas.closest('.hero-section') || canvas.parentElement; - if (hero && getComputedStyle(hero).position === 'static') { - hero.style.position = 'relative'; - } - canvas.style.cssText = [ - 'position:absolute', - 'top:0', 'left:0', 'right:0', 'bottom:0', - 'width:100%', 'height:100%', - 'z-index:0', - 'pointer-events:none', - 'display:block', - ].join(';'); -})(); - -function resize() { - canvas.width = canvas.offsetWidth * devicePixelRatio; - canvas.height = canvas.offsetHeight * devicePixelRatio; - ctx.setTransform(1, 0, 0, 1, 0, 0); - ctx.scale(devicePixelRatio, devicePixelRatio); -} -resize(); -window.addEventListener('resize', resize); - -const W = () => canvas.offsetWidth; -const H = () => canvas.offsetHeight; - -/* ── Colour palette (pastel greens) ── */ -const C = { - boardLight: '#fff5e6', - boardDark: '#f6ead7', - gridLine: 'rgba(132,97,67,0.12)', - snakeBody: '#f08a6a', - snakeHead: '#d46c4f', - ladderRail: '#d19a55', - ladderRung: '#f0c47c', - dice: 'rgba(255,255,255,0.88)', - diceDot: '#6abf8d', - token1: '#6abf8d', - token2: '#c7a36b', - num: 'rgba(132,97,67,0.45)', -}; - -/* ── Board geometry ── */ -const ROWS = 10, COLS = 10; - -function cellRect(col, row) { - const w = W(), h = H(); - const cw = w / COLS, ch = h / ROWS; - return { x: col * cw, y: (ROWS - 1 - row) * ch, w: cw, h: ch }; -} - -function cellCenter(n) { - const idx = n - 1; - const row = Math.floor(idx / COLS); - let col = idx % COLS; - if (row % 2 === 1) col = COLS - 1 - col; - const r = cellRect(col, row); - return { x: r.x + r.w / 2, y: r.y + r.h / 2 }; -} - -/* ── Snakes & Ladders definitions ── */ -const snakes = [[97,78],[95,56],[88,24],[76,37],[74,53],[62,19],[54,34],[19,7]]; -const ladders = [[4,25],[9,31],[20,41],[28,84],[40,59],[51,67],[63,81],[71,91]]; - -/* ── Animated tokens ── */ -class Token { - constructor(color, speed) { - this.cell = 1 + Math.floor(Math.random() * 100); - this.color = color; - this.speed = speed; - this.wait = Math.random() * 200; - this.r = 0; - const c = cellCenter(this.cell); - this.x = c.x; this.y = c.y; - this.tx = c.x; this.ty = c.y; - } - update() { - if (this.wait > 0) { this.wait--; return; } - const dx = this.tx - this.x, dy = this.ty - this.y; - const dist = Math.sqrt(dx * dx + dy * dy); - if (dist < 1.5) { - this.x = this.tx; this.y = this.ty; - if (Math.random() < 0.015) { - this.cell = 1 + Math.floor(Math.random() * 100); - const c = cellCenter(this.cell); - this.tx = c.x; this.ty = c.y; - this.wait = 30 + Math.random() * 80; - } - } else { - this.x += dx * this.speed; - this.y += dy * this.speed; - } - this.r += 0.04; - } - draw() { - const s = Math.min(W(), H()) * 0.025; - ctx.save(); - ctx.translate(this.x, this.y); - ctx.rotate(Math.sin(this.r) * 0.3); - ctx.beginPath(); - ctx.arc(0, 0, s, 0, Math.PI * 2); - ctx.fillStyle = this.color; - ctx.shadowColor = this.color; - ctx.shadowBlur = 10; - ctx.fill(); - ctx.shadowBlur = 0; - ctx.restore(); - } -} - -const tokens = [ - new Token(C.token1, 0.06), - new Token(C.token2, 0.05), -]; - -/* ── Dice animation ── */ -const diceAnim = { val: 1, t: 0 }; - -/* ── Draw board ── */ -function drawBoard() { - const w = W(), h = H(); - ctx.fillStyle = '#fff9f0'; - ctx.fillRect(0, 0, w, h); - - for (let row = 0; row < ROWS; row++) { - for (let col = 0; col < COLS; col++) { - const r = cellRect(col, row); - ctx.fillStyle = (row + col) % 2 === 0 ? C.boardLight : C.boardDark; - ctx.fillRect(r.x, r.y, r.w, r.h); - ctx.strokeStyle = C.gridLine; - ctx.lineWidth = 0.5; - ctx.strokeRect(r.x, r.y, r.w, r.h); - - /* cell numbers */ - const cellNum = (row % 2 === 0) - ? (row * COLS + col + 1) - : (row * COLS + (COLS - col)); - ctx.fillStyle = C.num; - ctx.font = `bold ${Math.max(9, r.w * 0.22)}px Nunito, sans-serif`; - ctx.textAlign = 'left'; - ctx.textBaseline = 'top'; - ctx.fillText(cellNum, r.x + r.w * 0.08, r.y + r.h * 0.06); - } +/* ═══════════════════════════════════════════════════════════════ + hero-canvas.js — Interactive Particle Mesh Background + Smooth, performance-first particle grid that responds to + mouse movement with subtle connections and gentle drift. + ═══════════════════════════════════════════════════════════════ */ + +(function () { + 'use strict'; + + if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return; + + function ParticleMesh(canvasId) { + var self = this; + this.canvas = document.getElementById(canvasId); + if (!this.canvas) return; + + this.ctx = this.canvas.getContext('2d'); + this.particles = []; + this.mouse = { x: null, y: null, radius: 120 }; + this.W = 0; + this.H = 0; + this.animFrame = null; + this.isDark = document.documentElement.getAttribute('data-theme') !== 'light'; + + this.CONFIG = { + count: 60, + maxDist: 140, + speed: 0.15, + connectionOpacity: this.isDark ? 0.12 : 0.08, + particleRadius: this.isDark ? 3.0 : 2.5, + color: this.isDark ? '255,255,255' : '0,0,0', + mousePull: 0.03, + }; + + // Particle Object constructor inside instance scope + function Particle() { + this.x = Math.random() * self.W; + this.y = Math.random() * self.H; + this.vx = (Math.random() - 0.5) * self.CONFIG.speed; + this.vy = (Math.random() - 0.5) * self.CONFIG.speed; + this.phase = Math.random() * Math.PI * 2; } -} - -/* ── Draw snakes ── */ -function drawSnakes() { - snakes.forEach(([from, to]) => { - const a = cellCenter(from), b = cellCenter(to); - const t = performance.now() / 1200; - const segments = 20; - const amp = Math.min(W(), H()) * 0.025; - ctx.beginPath(); - for (let i = 0; i <= segments; i++) { - const p = i / segments; - const x = a.x + (b.x - a.x) * p + Math.sin(p * Math.PI * 3 + t) * amp; - const y = a.y + (b.y - a.y) * p; - i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y); + Particle.prototype.update = function () { + this.phase += 0.005; + this.x += this.vx + Math.sin(this.phase) * 0.1; + this.y += this.vy + Math.cos(this.phase * 0.7) * 0.1; + + /* Mouse interaction */ + if (self.mouse.x !== null) { + var dx = self.mouse.x - this.x; + var dy = self.mouse.y - this.y; + var dist = Math.sqrt(dx * dx + dy * dy); + if (dist < self.mouse.radius) { + var force = (self.mouse.radius - dist) / self.mouse.radius; + this.x -= dx * force * self.CONFIG.mousePull; + this.y -= dy * force * self.CONFIG.mousePull; } - ctx.strokeStyle = C.snakeBody; - ctx.lineWidth = Math.min(W(), H()) * 0.018; - ctx.lineCap = 'round'; - ctx.lineJoin = 'round'; - ctx.globalAlpha = 0.7; - ctx.stroke(); - ctx.globalAlpha = 1; - - /* head */ - ctx.beginPath(); - ctx.arc(a.x, a.y, Math.min(W(), H()) * 0.016, 0, Math.PI * 2); - ctx.fillStyle = C.snakeHead; - ctx.fill(); - - /* eyes */ - const eyeR = Math.min(W(), H()) * 0.004; - ctx.beginPath(); - ctx.arc(a.x - eyeR * 1.8, a.y - eyeR * 1.5, eyeR, 0, Math.PI * 2); - ctx.arc(a.x + eyeR * 1.8, a.y - eyeR * 1.5, eyeR, 0, Math.PI * 2); - ctx.fillStyle = '#fff'; - ctx.fill(); - }); -} - -/* ── Draw ladders ── */ -function drawLadders() { - ladders.forEach(([from, to]) => { - const a = cellCenter(from), b = cellCenter(to); - const dx = b.x - a.x, dy = b.y - a.y; - const len = Math.sqrt(dx * dx + dy * dy); - const ux = -dy / len, uy = dx / len; - const rail = Math.min(W(), H()) * 0.012; - - [rail, -rail].forEach(off => { - ctx.beginPath(); - ctx.moveTo(a.x + ux * off, a.y + uy * off); - ctx.lineTo(b.x + ux * off, b.y + uy * off); - ctx.strokeStyle = C.ladderRail; - ctx.lineWidth = Math.min(W(), H()) * 0.006; - ctx.globalAlpha = 0.75; - ctx.stroke(); - ctx.globalAlpha = 1; - }); - - const rungs = Math.round(len / (Math.min(W(), H()) * 0.065)); - for (let i = 1; i < rungs; i++) { - const p = i / rungs; - const rx = a.x + dx * p, ry = a.y + dy * p; - ctx.beginPath(); - ctx.moveTo(rx + ux * rail, ry + uy * rail); - ctx.lineTo(rx - ux * rail, ry - uy * rail); - ctx.strokeStyle = C.ladderRung; - ctx.lineWidth = Math.min(W(), H()) * 0.005; - ctx.globalAlpha = 0.7; - ctx.stroke(); - ctx.globalAlpha = 1; + } + + /* Wrap around edges */ + if (this.x < 0) this.x = self.W; + if (this.x > self.W) this.x = 0; + if (this.y < 0) this.y = self.H; + if (this.y > self.H) this.y = 0; + }; + + Particle.prototype.draw = function () { + self.ctx.beginPath(); + self.ctx.arc(this.x, this.y, self.CONFIG.particleRadius, 0, Math.PI * 2); + self.ctx.fillStyle = 'rgba(' + self.CONFIG.color + ', 0.6)'; + self.ctx.fill(); + }; + + this.resize = function () { + var rect = self.canvas.parentElement.getBoundingClientRect(); + self.W = self.canvas.width = rect.width * window.devicePixelRatio; + self.H = self.canvas.height = rect.height * window.devicePixelRatio; + self.canvas.style.width = rect.width + 'px'; + self.canvas.style.height = rect.height + 'px'; + self.ctx.scale(window.devicePixelRatio, window.devicePixelRatio); + self.W = rect.width; + self.H = rect.height; + }; + + this.drawConnections = function () { + var len = self.particles.length; + for (var i = 0; i < len; i++) { + for (var j = i + 1; j < len; j++) { + var dx = self.particles[i].x - self.particles[j].x; + var dy = self.particles[i].y - self.particles[j].y; + var dist = dx * dx + dy * dy; + + if (dist < self.CONFIG.maxDist * self.CONFIG.maxDist) { + var opacity = (1 - dist / (self.CONFIG.maxDist * self.CONFIG.maxDist)) * self.CONFIG.connectionOpacity; + self.ctx.beginPath(); + self.ctx.moveTo(self.particles[i].x, self.particles[i].y); + self.ctx.lineTo(self.particles[j].x, self.particles[j].y); + self.ctx.strokeStyle = 'rgba(' + self.CONFIG.color + ', ' + opacity + ')'; + self.ctx.lineWidth = 0.5; + self.ctx.stroke(); + } } + } + }; + + this.animate = function () { + self.ctx.clearRect(0, 0, self.W, self.H); + + for (var i = 0; i < self.particles.length; i++) { + self.particles[i].update(); + self.particles[i].draw(); + } + + self.drawConnections(); + + self.animFrame = requestAnimationFrame(self.animate.bind(self)); + }; + + this.onMouseMove = function (e) { + var rect = self.canvas.getBoundingClientRect(); + self.mouse.x = e.clientX - rect.left; + self.mouse.y = e.clientY - rect.top; + }; + + this.onMouseLeave = function () { + self.mouse.x = null; + self.mouse.y = null; + }; + + this.onThemeChange = function () { + self.isDark = document.documentElement.getAttribute('data-theme') !== 'light'; + self.CONFIG.color = self.isDark ? '255,255,255' : '0,0,0'; + self.CONFIG.connectionOpacity = self.isDark ? 0.12 : 0.08; + self.CONFIG.particleRadius = self.isDark ? 3.0 : 2.5; + }; + + this.init = function () { + self.resize(); + self.particles = []; + for (var i = 0; i < self.CONFIG.count; i++) { + self.particles.push(new Particle()); + } + self.animate(); + }; + + this.destroy = function () { + if (self.animFrame) cancelAnimationFrame(self.animFrame); + }; + + // Listen to resize and hover events + window.addEventListener('resize', this.resize.bind(this)); + this.canvas.addEventListener('mousemove', this.onMouseMove.bind(this)); + this.canvas.addEventListener('mouseleave', this.onMouseLeave.bind(this)); + + this.init(); + } + + // Initialize animations on both canvases + var mesh1 = new ParticleMesh('boardCanvas'); + var mesh2 = new ParticleMesh('timelineCanvas'); + + /* ── Observe theme changes ───────────────────────────────── */ + var themeObserver = new MutationObserver(function (mutations) { + mutations.forEach(function (m) { + if (m.attributeName === 'data-theme') { + if (mesh1 && typeof mesh1.onThemeChange === 'function') mesh1.onThemeChange(); + if (mesh2 && typeof mesh2.onThemeChange === 'function') mesh2.onThemeChange(); + } }); -} - -/* ── Draw animated dice ── */ -function roundRect(cx, x, y, w, h, r) { - cx.beginPath(); - cx.moveTo(x + r, y); - cx.lineTo(x + w - r, y); cx.arcTo(x + w, y, x + w, y + r, r); - cx.lineTo(x + w, y + h - r); cx.arcTo(x + w, y + h, x + w - r, y + h, r); - cx.lineTo(x + r, y + h); cx.arcTo(x, y + h, x, y + h - r, r); - cx.lineTo(x, y + r); cx.arcTo(x, y, x + r, y, r); - cx.closePath(); -} - -function diceDots(v) { - return { - 1: [[0, 0]], - 2: [[-1,-1],[1,1]], - 3: [[-1,-1],[0,0],[1,1]], - 4: [[-1,-1],[1,-1],[-1,1],[1,1]], - 5: [[-1,-1],[1,-1],[0,0],[-1,1],[1,1]], - 6: [[-1,-1],[1,-1],[-1,0],[1,0],[-1,1],[1,1]], - }[v] || []; -} - -function drawDice() { - const w = W(), h = H(); - const size = Math.min(w, h) * 0.055; - const px = w * 0.06, py = h * 0.12; - - diceAnim.t += 0.03; - if (Math.random() < 0.01) diceAnim.val = 1 + Math.floor(Math.random() * 6); - - ctx.save(); - ctx.translate(px, py); - ctx.rotate(Math.sin(diceAnim.t * 0.7) * 0.2); - - ctx.fillStyle = C.dice; - ctx.shadowColor = 'rgba(30,138,88,0.3)'; - ctx.shadowBlur = 12; - roundRect(ctx, -size / 2, -size / 2, size, size, size * 0.18); - ctx.fill(); - ctx.shadowBlur = 0; - - ctx.fillStyle = C.diceDot; - const dr = size * 0.09; - diceDots(diceAnim.val).forEach(([ddx, ddy]) => { - ctx.beginPath(); - ctx.arc(ddx * size * 0.3, ddy * size * 0.3, dr, 0, Math.PI * 2); - ctx.fill(); - }); + }); + themeObserver.observe(document.documentElement, { attributes: true }); - ctx.restore(); -} + /* ── Cleanup on page unload ──────────────────────────────── */ + window.addEventListener('beforeunload', function () { + if (mesh1) mesh1.destroy(); + if (mesh2) mesh2.destroy(); + themeObserver.disconnect(); + }); -/* ── Main loop ── */ -function loop() { - ctx.clearRect(0, 0, canvas.width, canvas.height); - drawBoard(); - drawLadders(); - drawSnakes(); - tokens.forEach(t => { t.update(); t.draw(); }); - drawDice(); - requestAnimationFrame(loop); - } - - loop(); -} +})(); diff --git a/web-app/js/main.js b/web-app/js/main.js index 40c2072..f22a683 100644 --- a/web-app/js/main.js +++ b/web-app/js/main.js @@ -1,1251 +1,1153 @@ -/* - main.js – lightweight app wiring - ───────────────────────────────────────────────────────────────────── - CHANGES vs original (search "── PLAYGROUND" for every touched area): - 1. projectsSection reference added (to hide/show vs playground) - 2. applyCategoryFilter() guards against 'playground' category - 3. Tab click handler detects playground tab and delegates to - window.playgroundAPI (defined in playground.js) - 4. moveTabFocus() skips playground-only activation; other tabs - call deactivatePlayground automatically - 5. randomProjectBtn is hidden while playground is active - ───────────────────────────────────────────────────────────────────── - Everything else is 100 % identical to the original. -*/ +/* ═══════════════════════════════════════════════════════════════ + main.js — App wiring for Premium Python Projects Gallery + ═══════════════════════════════════════════════════════════════ */ function prefersReducedMotion() { - return window.matchMedia('(prefers-reduced-motion: reduce)').matches; + return window.matchMedia('(prefers-reduced-motion: reduce)').matches; } -// Accessibility helper referenced by modal code -function setMainInert(isInert) { - var main = document.getElementById('main-content'); - if (!main) return; - if (isInert) main.setAttribute('inert', ''); else main.removeAttribute('inert'); -} +function safeRun(fn) { try { fn(); } catch (e) { console.error(e); } } -function safeRun(fn) { - try { fn(); } catch (err) { console.error(err); } +function debounce(fn, ms) { + var timer; + return function () { + var args = arguments; var ctx = this; + clearTimeout(timer); + timer = setTimeout(function () { fn.apply(ctx, args); }, ms); + }; } -// Debounce function for smooth search performance -function debounce(func, delay) { - var timeoutId; - return function () { - var args = arguments; - clearTimeout(timeoutId); - timeoutId = setTimeout(function () { func.apply(null, args); }, delay); - }; +function syncThemeColor(theme) { + var meta = document.getElementById('themeColorMeta'); + if (meta) meta.setAttribute('content', theme === 'light' ? '#f4f6f9' : '#0c0f1a'); } -// Sync the theme-color tag with the current theme -function syncThemeColor(theme) { - var meta = document.getElementById('themeColorMeta'); - if (meta) meta.setAttribute('content', theme === 'light' ? '#fffdf8' : '#201a18'); +function escapeHtml(str) { + var d = document.createElement('div'); + d.textContent = str; + return d.innerHTML; } +/* ── DOMContentLoaded ──────────────────────────────────────── */ document.addEventListener('DOMContentLoaded', function () { - - // Sort project cards lexicographically (alphabetically) by title - var projectsGrid = document.querySelector('.projects-grid'); - var rawCards = projectsGrid ? Array.from(projectsGrid.querySelectorAll('.project-card')) : []; - if (rawCards.length > 0) { - rawCards.sort(function (a, b) { - var h3A = a.querySelector('h3'); - var h3B = b.querySelector('h3'); - var titleA = h3A ? h3A.textContent.trim() : ''; - var titleB = h3B ? h3B.textContent.trim() : ''; - return titleA.localeCompare(titleB); - }); - var reorderedCards = document.createDocumentFragment(); - rawCards.forEach(function (card) { - reorderedCards.appendChild(card); - }); - projectsGrid.appendChild(reorderedCards); - } - - // ── DOM references ────────────────────────────────────────────── - var html = document.documentElement; - var themeToggle = document.getElementById('themeToggle'); - var soundToggle = document.getElementById('soundToggle'); - var backToTopButton = document.getElementById('backToTop'); - var tabs = document.querySelectorAll('.tab'); - var projectCards = Array.from(document.querySelectorAll('.project-card')); - var searchInput = document.getElementById('projectSearch') || document.getElementById('searchInput'); - var searchClear = document.getElementById('searchClear'); - var searchDropdown = document.getElementById('searchDropdown'); - var searchShortcut = document.getElementById('searchShortcut'); - var searchLoader = document.getElementById('searchLoader'); - var projectsSection = document.getElementById('projectsSection'); - var playgroundSection = document.getElementById('playgroundSection'); - var stickyFilterBar = document.getElementById('stickyFilterBar'); - var stickyTabs = document.querySelectorAll('.sticky-tab'); - var heroSection = document.querySelector('.hero-section'); - var cursorGlow = document.getElementById('cursorGlow'); - var heroTypewriter = document.getElementById('heroTypewriter'); - var heroProjectCount = document.getElementById('heroProjectCount'); - var heroGameCount = document.getElementById('heroGameCount'); - var heroUtilityCount = document.getElementById('heroUtilityCount'); - var revealItems = document.querySelectorAll('.reveal-on-scroll'); - var featureLaunchers = document.querySelectorAll('[data-project-target]'); - var emptyState = document.getElementById('emptyState'); - var resultsList = document.getElementById('resultsList'); - var resultsSection = document.getElementById('resultsSection'); - var recentSearchesList = document.getElementById('recentSearchesList'); - var recentSearchesSection = document.getElementById('recentSearchesSection'); - var tipsSection = document.getElementById('tipsSection'); - var noResultsMessage = document.getElementById('noResultsMessage'); - var modal = document.getElementById('projectModal'); - var modalBody = document.getElementById('modalBody'); - var modalClose = document.getElementById('modalClose'); - var modalTitle = document.getElementById('modalDialogTitle'); - /* randomProjectBtn and playgroundHeroBtn removed — sidebar handles those actions */ - - - var currentCategory = 'all'; - var currentSearchQuery = ''; - var playgroundActive = false; - var selectedSuggestionIndex = -1; - var removeTrap = null; - var lastFocusedElement = null; - var recentSearches = JSON.parse(localStorage.getItem('recentSearches') || '[]'); - //-----------------------project count badge------------------------------------ - const projectCountBadge = document.getElementById("projectCountBadge"); - const projectCount = document.querySelectorAll(".project-card").length; - const gameCount = projectCards.filter(function (card) { return card.getAttribute('data-category') === 'games'; }).length; - const utilityCount = projectCards.filter(function (card) { return card.getAttribute('data-category') === 'utilities'; }).length; - - if (projectCountBadge) { - projectCountBadge.textContent = `${projectCount} projects`; - } - if (heroProjectCount) heroProjectCount.textContent = String(projectCount); - if (heroGameCount) heroGameCount.textContent = String(gameCount); - if (heroUtilityCount) heroUtilityCount.textContent = String(utilityCount); - - var rotatePhrases = [ - 'typing speed drills', - 'math quiz rounds', - 'logic puzzle practice', - 'browser-ready Python wins' - ]; - - if (heroTypewriter && !prefersReducedMotion()) { - var phraseIndex = 0; - setInterval(function () { - phraseIndex = (phraseIndex + 1) % rotatePhrases.length; - heroTypewriter.textContent = rotatePhrases[phraseIndex]; - }, 2200); - } - - if (cursorGlow && !prefersReducedMotion()) { - document.addEventListener('pointermove', function (event) { - cursorGlow.style.left = event.clientX + 'px'; - cursorGlow.style.top = event.clientY + 'px'; - }); - document.addEventListener('pointerleave', function () { - cursorGlow.style.opacity = '0'; - }); - document.addEventListener('pointerenter', function () { - cursorGlow.style.opacity = '0.5'; - }); - } - // ── Theme Toggle ──────────────────────────────────────────────── - function updateThemeToggleAria(isLightTheme) { - if (!themeToggle) return; - themeToggle.setAttribute( - 'aria-label', - isLightTheme ? 'Switch to dark mode' : 'Switch to light mode' - ); - } - - if (themeToggle) { - var savedTheme = localStorage.getItem('theme') || 'light'; - html.setAttribute('data-theme', savedTheme); - syncThemeColor(savedTheme); - themeToggle.innerHTML = - savedTheme === 'light' - ? '' - : ''; - updateThemeToggleAria(savedTheme === 'light'); - - themeToggle.addEventListener('click', function () { - var currentTheme = html.getAttribute('data-theme'); - var newTheme = currentTheme === 'light' ? 'dark' : 'light'; - - html.setAttribute('data-theme', newTheme); - localStorage.setItem('theme', newTheme); - syncThemeColor(newTheme); - - themeToggle.innerHTML = - newTheme === 'light' - ? '' - : ''; - updateThemeToggleAria(newTheme === 'light'); - }); + var html = document.documentElement; + var themeToggle = document.getElementById('themeToggle'); + var soundToggle = document.getElementById('soundToggle'); + var backToTopButton = document.getElementById('backToTop'); + var searchInput = document.getElementById('searchInput'); + var searchDropdown = document.getElementById('searchDropdown'); + var searchLoader = document.getElementById('searchLoader'); + var recentSearchesList = document.getElementById('recentSearchesList'); + var recentSearchesSection = document.getElementById('recentSearchesSection'); + var resultsList = document.getElementById('resultsList'); + var resultsSection = document.getElementById('resultsSection'); + var tipsSection = document.getElementById('tipsSection'); + var noResultsMessage = document.getElementById('noResultsMessage'); + var projectsSection = document.getElementById('projectsSection'); + var playgroundSection = document.getElementById('playgroundSection'); + var stickyFilterBar = document.getElementById('stickyFilterBar'); + var stickyTabs = document.querySelectorAll('.sticky-tab'); + var heroSection = document.querySelector('.hero-section'); + var cursorGlow = document.getElementById('cursorGlow'); + var heroProjectCount = document.getElementById('heroProjectCount'); + var heroGameCount = document.getElementById('heroGameCount'); + var heroUtilityCount = document.getElementById('heroUtilityCount'); + var modal = document.getElementById('projectModal'); + var modalBody = document.getElementById('modalBody'); + var modalClose = document.getElementById('modalClose'); + var modalTitle = document.getElementById('modalDialogTitle'); + var exploreBtn = document.getElementById('exploreBtn'); + var randomProjectBtn = document.getElementById('randomProjectBtn'); + var randomProjectBtnSidebar = document.getElementById('randomProjectBtnSidebar'); + var emptyState = document.getElementById('emptyState'); + var emptyStateHint = document.getElementById('emptyStateHint'); + var projectCountBadge = document.getElementById('projectCountBadge'); + var mobileMenuToggle = document.getElementById('mobileMenuToggle'); + var navControls = document.getElementById('navControls'); + var navbar = document.getElementById('mainNavbar'); + + var currentCategory = 'all'; + var currentSearchQuery = ''; + var playgroundActive = false; + var selectedSuggestionIndex = -1; + var removeTrap = null; + var lastFocusedElement = null; + var projectCards = []; + var recentSearches = JSON.parse(localStorage.getItem('recentSearches') || '[]'); + + /* ── Helper: setMainInert ─────────────────────────────────── */ + function setMainInert(isInert) { + var main = document.getElementById('main-content'); + if (!main) return; + if (isInert) main.setAttribute('inert', ''); else main.removeAttribute('inert'); + } + + /* ── Theme Toggle ─────────────────────────────────────────── */ + function updateThemeToggleAria(isLight) { + if (!themeToggle) return; + themeToggle.setAttribute('aria-label', isLight ? 'Switch to dark mode' : 'Switch to light mode'); + } + + if (themeToggle) { + var savedTheme = localStorage.getItem('theme') || 'dark'; + html.setAttribute('data-theme', savedTheme); + syncThemeColor(savedTheme); + themeToggle.innerHTML = savedTheme === 'light' + ? '' + : ''; + updateThemeToggleAria(savedTheme === 'light'); + + themeToggle.addEventListener('click', function () { + var current = html.getAttribute('data-theme'); + var next = current === 'light' ? 'dark' : 'light'; + html.setAttribute('data-theme', next); + localStorage.setItem('theme', next); + syncThemeColor(next); + themeToggle.innerHTML = next === 'light' + ? '' + : ''; + updateThemeToggleAria(next === 'light'); + }); + } + + /* ── Sound Toggle ─────────────────────────────────────────── */ + if (soundToggle && window.audioController) { + function updateSoundIcon() { + soundToggle.innerHTML = window.audioController.isMuted + ? '' + : ''; } - - // ── Sound Toggle ───────────────────────────────────────────────── - if (soundToggle) { - var updateSoundIcon = function () { - if (window.audioController) { - soundToggle.innerHTML = window.audioController.isMuted - ? '' - : ''; - } - }; + updateSoundIcon(); + soundToggle.addEventListener('click', function () { + if (typeof window.audioController.toggleMute === 'function') { + window.audioController.toggleMute(); updateSoundIcon(); - soundToggle.addEventListener('click', function () { - if (window.audioController && typeof window.audioController.toggleMute === 'function') { - window.audioController.toggleMute(); - updateSoundIcon(); - if (!window.audioController.isMuted && typeof window.audioController.play === 'function') { - window.audioController.play('click'); - } - } - }); - } - - // ── Back to Top ────────────────────────────────────────────────── - if (backToTopButton) { - var toggleBackToTopButton = function () { - backToTopButton.classList.toggle('visible', window.scrollY > 300); - var navbar = document.querySelector('.navbar'); - if (navbar) navbar.classList.toggle('scrolled', window.scrollY > 12); - }; - window.addEventListener('scroll', toggleBackToTopButton, { passive: true }); - toggleBackToTopButton(); - - backToTopButton.addEventListener('click', function () { - window.scrollTo({ top: 0, behavior: prefersReducedMotion() ? 'auto' : 'smooth' }); - }); - } - - // ── PLAYGROUND: helpers to show / hide sections ───────────────── - /* ← PLAYGROUND ADD (entire block) */ - function showProjectsSection() { - playgroundActive = false; - if (playgroundSection) playgroundSection.style.display = 'none'; - if (projectsSection) projectsSection.style.display = ''; - if (window.playgroundAPI && typeof window.playgroundAPI.deactivate === 'function') { - window.playgroundAPI.deactivate(); + if (!window.audioController.isMuted && typeof window.audioController.play === 'function') { + window.audioController.play('click'); } - } - - function showPlaygroundSection() { - playgroundActive = true; - syncStickyTabs('playground'); - if (projectsSection) projectsSection.style.display = 'none'; - if (window.playgroundAPI && typeof window.playgroundAPI.activate === 'function') { - window.playgroundAPI.activate(); - } - } - /* ← PLAYGROUND ADD end */ - // ── Sticky Filter Bar: position + show/hide on scroll ──────────── -function syncStickyTabs(category) { - stickyTabs.forEach(function (st) { - var selected = st.getAttribute('data-sticky-category') === category; - st.classList.toggle('active', selected); - st.setAttribute('aria-selected', selected ? 'true' : 'false'); - st.setAttribute('tabindex', selected ? '0' : '-1'); + } }); -} - -if (stickyFilterBar && heroSection) { - // Position the bar directly below the navbar - var navbar = document.querySelector('.navbar'); - function positionStickyBar() { - var navHeight = navbar ? navbar.getBoundingClientRect().height : 0; - stickyFilterBar.style.top = navHeight + 'px'; - } - positionStickyBar(); - window.addEventListener('resize', positionStickyBar); - - var heroObserver = new IntersectionObserver(function (entries) { - entries.forEach(function (entry) { - stickyFilterBar.classList.toggle('visible', !entry.isIntersecting); - }); - }, { threshold: 0 }); - heroObserver.observe(heroSection); -} - - // Wire sticky tab clicks — mirrors main tab behaviour - stickyTabs.forEach(function (st) { - st.addEventListener('click', function () { - var category = st.getAttribute('data-sticky-category'); - - // Sync sticky tabs UI - syncStickyTabs(category); - - // Sync hero tabs UI - tabs.forEach(function (t) { - var selected = t.getAttribute('data-category') === category; - t.classList.toggle('active', selected); - t.setAttribute('aria-selected', selected ? 'true' : 'false'); - t.setAttribute('tabindex', selected ? '0' : '-1'); - }); - - // Delegate section logic (same as hero tab click) - if (category === 'playground') { - showPlaygroundSection(); - } else { - showProjectsSection(); - applyCategoryFilter(category); - } - }); + } else if (soundToggle) { + soundToggle.addEventListener('click', function () { + var icon = soundToggle.querySelector('i'); + if (icon) icon.className = icon.className === 'fas fa-volume-up' ? 'fas fa-volume-mute' : 'fas fa-volume-up'; }); - - // ── Category Filtering ─────────────────────────────────────────── - function applyCategoryFilter(category) { - /* ── PLAYGROUND ADD: skip filtering when playground tab is selected ── */ - if (category === 'playground') return; - /* ── PLAYGROUND ADD end ── */ - - currentCategory = category; - syncStickyTabs(category); - var visibleCount = 0; - var favorites = JSON.parse(localStorage.getItem('favorites') || '[]'); - projectCards.forEach(function (card) { - var cardCategory = card.getAttribute('data-category'); - var projectName = card.getAttribute('data-project'); - var isFavorite = favorites.includes(projectName); - - if (category === 'all' || - (category === 'favorites' && isFavorite) || - (category !== 'favorites' && cardCategory === category)) { - card.style.display = ''; - card.style.animation = prefersReducedMotion() ? 'none' : 'fadeIn 0.6s ease'; - visibleCount++; - } else { - card.style.display = 'none'; - } - }); - - if (emptyState) { - emptyState.style.display = visibleCount === 0 ? 'block' : 'none'; - } - if (sidebarBadge) { - sidebarBadge.textContent = String(visibleCount); - } + } + + /* ── Hero Controls Mirror Toggles ─────────────────────────── */ + var heroSoundToggle = document.getElementById('heroSoundToggle'); + var heroThemeToggle = document.getElementById('heroThemeToggle'); + + function syncHeroControlsIcons() { + if (heroSoundToggle && soundToggle) { + var realSoundIcon = soundToggle.querySelector('i'); + var heroSoundIcon = heroSoundToggle.querySelector('i'); + if (realSoundIcon && heroSoundIcon) { + heroSoundIcon.className = realSoundIcon.className; + } } - - function moveTabFocus(fromIndex, delta) { - var len = tabs.length; - var next = (fromIndex + delta + len) % len; - tabs.forEach(function (t, i) { - var selected = i === next; - t.classList.toggle('active', selected); - t.setAttribute('aria-selected', selected ? 'true' : 'false'); - t.setAttribute('tabindex', selected ? '0' : '-1'); - }); - tabs[next].focus(); - - /* ── PLAYGROUND ADD: delegate to section helpers ── */ - var nextCategory = tabs[next].getAttribute('data-category'); - if (nextCategory === 'playground') { - showPlaygroundSection(); - } else { - showProjectsSection(); - applyCategoryFilter(nextCategory); - } - /* ── PLAYGROUND ADD end ── */ + if (heroThemeToggle && themeToggle) { + var realThemeIcon = themeToggle.querySelector('i'); + var heroThemeIcon = heroThemeToggle.querySelector('i'); + if (realThemeIcon && heroThemeIcon) { + heroThemeIcon.className = realThemeIcon.className; + } } + } - tabs.forEach(function (tab, index) { - tab.addEventListener('click', function () { - /* Update active state on all tabs */ - tabs.forEach(function (t) { - var selected = t === tab; - t.classList.toggle('active', selected); - t.setAttribute('aria-selected', selected ? 'true' : 'false'); - t.setAttribute('tabindex', selected ? '0' : '-1'); - }); - - var category = tab.getAttribute('data-category'); - - /* ── PLAYGROUND ADD: playground tab gets its own section ── */ - if (category === 'playground') { - showPlaygroundSection(); - } else { - showProjectsSection(); - applyCategoryFilter(category); - } - /* ── PLAYGROUND ADD end ── */ - }); - - tab.addEventListener('keydown', function (e) { - var handled = false; - if (e.key === 'ArrowRight' || e.key === 'ArrowDown') { - moveTabFocus(index, 1); - handled = true; - } else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') { - moveTabFocus(index, -1); - handled = true; - } else if (e.key === 'Home') { - moveTabFocus(index, -index); - handled = true; - } else if (e.key === 'End') { - moveTabFocus(index, tabs.length - 1 - index); - handled = true; - } - if (handled) e.preventDefault(); - }); + if (heroSoundToggle && soundToggle) { + heroSoundToggle.addEventListener('click', function () { + soundToggle.click(); + setTimeout(syncHeroControlsIcons, 50); }); + } - // ── Sidebar Filter Tabs ─────────────────────────────────────────── - var sidebarTabs = document.querySelectorAll('.sidebar-tab'); - var sidebarBadge = document.getElementById('sidebarProjectCount'); - var sidebarRandomBtn = document.getElementById('randomProjectBtnSidebar'); - - function syncSidebarTabs(category) { - sidebarTabs.forEach(function (st) { - var selected = st.getAttribute('data-category') === category; - st.classList.toggle('active', selected); - st.setAttribute('aria-selected', selected ? 'true' : 'false'); - st.setAttribute('tabindex', selected ? '0' : '-1'); - }); - if (sidebarBadge) { - var visible = projectCards.filter(function (c) { return c.style.display !== 'none'; }).length; - sidebarBadge.textContent = String(visible); - } - } - - sidebarTabs.forEach(function (st) { - st.addEventListener('click', function () { - var category = st.getAttribute('data-category'); - syncSidebarTabs(category); - syncStickyTabs(category); - - if (category === 'playground') { - showPlaygroundSection(); - var pg = document.getElementById('playgroundSection'); - if (pg) pg.scrollIntoView({ behavior: 'smooth', block: 'start' }); - } else { - showProjectsSection(); - applyCategoryFilter(category); - if (projectsSection) { - projectsSection.scrollIntoView({ behavior: 'smooth', block: 'start' }); - } - } - }); + if (heroThemeToggle && themeToggle) { + heroThemeToggle.addEventListener('click', function () { + themeToggle.click(); + setTimeout(syncHeroControlsIcons, 50); + }); + } + + // Initial sync on load + setTimeout(syncHeroControlsIcons, 100); + + /* ── Mobile Sidebar Toggle ──────────────────────────────── */ + var mobileSidebarToggle = document.getElementById('mobileSidebarToggle'); + var mainSidebar = document.getElementById('mainSidebar'); + if (mobileSidebarToggle && mainSidebar) { + mobileSidebarToggle.addEventListener('click', function () { + var active = mainSidebar.classList.toggle('open'); + mobileSidebarToggle.setAttribute('aria-expanded', active); + var icon = mobileSidebarToggle.querySelector('i'); + if (icon) icon.className = active ? 'fas fa-times' : 'fas fa-bars'; }); - if (sidebarRandomBtn) { - sidebarRandomBtn.addEventListener('click', function () { - var visible = projectCards.filter(function (c) { return c.style.display !== 'none'; }); - var pool = visible.length ? visible : projectCards; - var pick = pool[Math.floor(Math.random() * pool.length)]; - var name = pick.getAttribute('data-project'); - if (name && typeof openProjectSafe === 'function') { - openProjectSafe(name, sidebarRandomBtn); - setTimeout(function () { - pick.scrollIntoView({ behavior: 'smooth', block: 'center' }); - }, 300); - } - }); - } - - // ── Search / Autocomplete ───────────────────────────────────────── - function getMatchingProjects(query) { - if (!query) return []; - var matches = []; - projectCards.forEach(function (card) { - var category = card.getAttribute('data-category'); - var title = card.querySelector('h3').textContent.toLowerCase(); - var description = card.querySelector('p').textContent.toLowerCase(); - var tags = (card.getAttribute('data-tags') || '').toLowerCase(); - - var categoryMatch = currentCategory === 'all' || category === currentCategory; - var searchMatch = title.includes(query) || - description.includes(query) || - tags.includes(query); - - if (categoryMatch && searchMatch) { - matches.push({ - card: card, - title: card.querySelector('h3').textContent, - tags: card.getAttribute('data-tags') || '', - category: category - }); - } - }); - return matches; - } + document.addEventListener('click', function (e) { + if (mainSidebar && mobileSidebarToggle && + !mainSidebar.contains(e.target) && e.target !== mobileSidebarToggle && + mainSidebar.classList.contains('open')) { + mainSidebar.classList.remove('open'); + mobileSidebarToggle.setAttribute('aria-expanded', 'false'); + var icon = mobileSidebarToggle.querySelector('i'); + if (icon) icon.className = 'fas fa-bars'; + } + }); + } - function highlightText(container, text, query) { - var safeQuery = query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - var chunks = text.split(new RegExp('(' + safeQuery + ')', 'gi')); - - chunks.forEach(function (part) { - if (part.toLowerCase() === query.toLowerCase()) { - var highlight = document.createElement('mark'); - highlight.style.background = 'rgba(99, 102, 241, 0.3)'; - highlight.style.color = 'var(--primary-color)'; - highlight.style.fontWeight = '600'; - highlight.textContent = part; - container.appendChild(highlight); - } else if (part) { - container.appendChild(document.createTextNode(part)); - } - }); - } + if (backToTopButton) { + var toggleBackToTop = function () { + backToTopButton.classList.toggle('visible', window.scrollY > 300); + }; + window.addEventListener('scroll', toggleBackToTop, { passive: true }); + toggleBackToTop(); - function updateSuggestionHighlight() { - if (!resultsList) return; - var items = resultsList.querySelectorAll('.dropdown-item'); - items.forEach(function (item, i) { - item.classList.toggle('selected', i === selectedSuggestionIndex); - }); + backToTopButton.addEventListener('click', function () { + window.scrollTo({ top: 0, behavior: prefersReducedMotion() ? 'auto' : 'smooth' }); + }); + } + + /* ── Cursor Glow ──────────────────────────────────────────── */ + if (cursorGlow && !prefersReducedMotion() && html.getAttribute('data-theme') !== 'light') { + var glowTimeout; + document.addEventListener('pointermove', function (e) { + cursorGlow.style.left = e.clientX + 'px'; + cursorGlow.style.top = e.clientY + 'px'; + cursorGlow.style.opacity = '0.5'; + clearTimeout(glowTimeout); + glowTimeout = setTimeout(function () { cursorGlow.style.opacity = '0'; }, 3000); + }); + document.addEventListener('pointerleave', function () { + cursorGlow.style.opacity = '0'; + }); + } + + /* ── Gather Project Cards ─────────────────────────────────── */ + var projectsGrid = document.querySelector('.projects-grid'); + projectCards = Array.from(document.querySelectorAll('.project-card')); + + /* ── Hero Stats ───────────────────────────────────────────── */ + var totalCount = projectCards.length; + var gameCount = projectCards.filter(function (c) { return c.getAttribute('data-category') === 'games'; }).length; + var utilityCount = projectCards.filter(function (c) { return c.getAttribute('data-category') === 'utilities'; }).length; + + if (heroProjectCount) heroProjectCount.textContent = String(totalCount); + if (heroGameCount) heroGameCount.textContent = String(gameCount); + if (heroUtilityCount) heroUtilityCount.textContent = String(utilityCount); + if (projectCountBadge) projectCountBadge.textContent = String(totalCount) + ' projects'; + + /* ── Explore Button ───────────────────────────────────────── */ + if (exploreBtn) { + exploreBtn.addEventListener('click', function () { + if (projectsSection) projectsSection.scrollIntoView({ behavior: 'smooth', block: 'start' }); + }); + } + + /* ── Category Filtering ───────────────────────────────────── */ + var sidebarTabs = document.querySelectorAll('.sidebar-tab'); + var sidebarBadge = null; + + function applyCategoryFilter(category) { + if (category === 'playground') return; + currentCategory = category; + syncSidebarTabs(category); + syncStickyTabs(category); + var visibleCount = 0; + var favorites = JSON.parse(localStorage.getItem('favorites') || '[]'); + projectCards.forEach(function (card) { + var cardCat = card.getAttribute('data-category'); + var projectName = card.getAttribute('data-project'); + var isFav = favorites.includes(projectName); + if (category === 'all' || + (category === 'favorites' && isFav) || + (category !== 'favorites' && cardCat === category)) { + card.style.display = ''; + visibleCount++; + } else { + card.style.display = 'none'; + } + }); + if (emptyState) { + emptyState.style.display = visibleCount === 0 ? 'block' : 'none'; } - - function selectSuggestion(title) { - if (!searchInput) return; - searchInput.value = title; - currentSearchQuery = title.toLowerCase(); - performSearch(); - closeDropdown(); - if (projectsSection) { - projectsSection.scrollIntoView({ - behavior: prefersReducedMotion() ? 'auto' : 'smooth', - block: 'start' - }); - } + if (projectCountBadge) { + projectCountBadge.textContent = String(visibleCount) + ' projects'; } + } - function cleanSearchHighlights() { - projectCards.forEach(function (card) { - var titleEl = card.querySelector('h3'); - var descEl = card.querySelector('p'); - [titleEl, descEl].forEach(function (el) { - if (!el) return; - el.querySelectorAll('mark.search-highlight').forEach(function (m) { - m.replaceWith(m.textContent); - }); - }); - }); - } + function syncSidebarTabs(category) { + sidebarTabs.forEach(function (st) { + var selected = st.getAttribute('data-category') === category; + st.classList.toggle('active', selected); + st.setAttribute('aria-selected', selected ? 'true' : 'false'); + st.setAttribute('tabindex', selected ? '0' : '-1'); + }); + } - function escapeHtml(str) { - var div = document.createElement('div'); - div.textContent = str; - return div.innerHTML; + function syncStickyTabs(category) { + stickyTabs.forEach(function (st) { + var selected = st.getAttribute('data-sticky-category') === category; + st.classList.toggle('active', selected); + st.setAttribute('aria-selected', selected ? 'true' : 'false'); + st.setAttribute('tabindex', selected ? '0' : '-1'); + }); + } + + /* ── Playground Section Toggle ────────────────────────────── */ + function showProjectsSection() { + playgroundActive = false; + if (playgroundSection) playgroundSection.style.display = 'none'; + if (projectsSection) projectsSection.style.display = ''; + if (window.playgroundAPI && typeof window.playgroundAPI.deactivate === 'function') { + window.playgroundAPI.deactivate(); } - - function highlightCardText(el, query) { - var safe = query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - var regex = new RegExp('(' + safe + ')', 'gi'); - var escaped = escapeHtml(el.textContent); - var html = escaped.replace(regex, '$1'); - el.innerHTML = html; + } + + function showPlaygroundSection() { + playgroundActive = true; + syncStickyTabs('playground'); + if (projectsSection) projectsSection.style.display = 'none'; + if (playgroundSection) { + playgroundSection.style.display = ''; + if (window.playgroundAPI && typeof window.playgroundAPI.activate === 'function') { + window.playgroundAPI.activate(); + } } - - function performSearch() { - var query = currentSearchQuery; - cleanSearchHighlights(); - - if (!query) { - applyCategoryFilter(currentCategory); - updateSearchResultCount(0); - var hint = document.getElementById('emptyStateHint'); - if (hint) hint.textContent = 'Try adjusting your search or category filter.'; - return; - } - - if (currentCategory !== 'all') { - currentCategory = 'all'; - syncSidebarTabs('all'); - syncStickyTabs('all'); - } - - recentSearches = recentSearches.filter(function (s) { return s !== query; }); - recentSearches.unshift(query); - recentSearches = recentSearches.slice(0, 10); - localStorage.setItem('recentSearches', JSON.stringify(recentSearches)); - - var visibleCount = 0; - var favorites = JSON.parse(localStorage.getItem('favorites') || '[]'); - projectCards.forEach(function (card) { - var category = card.getAttribute('data-category'); - var titleText = (card.querySelector('h3') || {}).textContent || ''; - var descText = (card.querySelector('p') || {}).textContent || ''; - var title = titleText.toLowerCase(); - var description = descText.toLowerCase(); - var tags = (card.getAttribute('data-tags') || '').toLowerCase(); - var projectName = card.getAttribute('data-project'); - var isFavorite = favorites.includes(projectName); - - var categoryMatch = currentCategory === 'all' || - (currentCategory === 'favorites' && isFavorite) || - (currentCategory !== 'favorites' && category === currentCategory); - var searchMatch = title.includes(query) || - description.includes(query) || - tags.includes(query); - - if (categoryMatch && searchMatch) { - card.style.display = ''; - card.style.animation = prefersReducedMotion() ? 'none' : 'fadeIn 0.5s ease'; - visibleCount++; - var titleEl = card.querySelector('h3'); - var descEl = card.querySelector('p'); - if (titleEl) highlightCardText(titleEl, query); - if (descEl) highlightCardText(descEl, query); - } else { - card.style.display = 'none'; - } - }); - - if (emptyState) { - var showEmpty = visibleCount === 0; - emptyState.style.display = showEmpty ? 'block' : 'none'; - if (showEmpty) { - var hint = document.getElementById('emptyStateHint'); - if (hint) hint.textContent = 'No projects match "' + query + '". Try a different keyword.'; - } + } + + /* ── Sidebar Tabs ─────────────────────────────────────────── */ + sidebarTabs.forEach(function (st) { + st.addEventListener('click', function () { + var category = st.getAttribute('data-category'); + + var pageCategory = document.body.getAttribute('data-page'); + if (pageCategory) { + // We are on a subpage (games, math, or utilities) + if (category === pageCategory) { + var grid = document.getElementById('projectsGrid'); + if (grid) grid.scrollIntoView({ behavior: 'smooth', block: 'start' }); + return; } + var pageMap = { + 'all': 'index.html', + 'games': 'games.html', + 'math': 'math.html', + 'utilities': 'utilities.html', + 'favorites': 'index.html?category=favorites', + 'playground': 'index.html?category=playground' + }; + window.location.href = pageMap[category] || 'index.html'; + return; + } + + syncSidebarTabs(category); + syncStickyTabs(category); + + if (category === 'playground') { + showPlaygroundSection(); + if (playgroundSection) playgroundSection.scrollIntoView({ behavior: 'smooth', block: 'start' }); + } else { + showProjectsSection(); + applyCategoryFilter(category); + if (projectsSection) projectsSection.scrollIntoView({ behavior: 'smooth', block: 'start' }); + } + }); + }); + + /* ── Sticky Tabs ──────────────────────────────────────────── */ + stickyTabs.forEach(function (st) { + st.addEventListener('click', function () { + var category = st.getAttribute('data-sticky-category'); + syncStickyTabs(category); + syncSidebarTabs(category); + + if (category === 'playground') { + showPlaygroundSection(); + if (playgroundSection) playgroundSection.scrollIntoView({ behavior: 'smooth', block: 'start' }); + } else { + showProjectsSection(); + applyCategoryFilter(category); + if (projectsSection) projectsSection.scrollIntoView({ behavior: 'smooth', block: 'start' }); + } + }); + }); - updateSearchResultCount(visibleCount); - } + /* ── Sticky Filter Bar Visibility ─────────────────────────── */ + if (stickyFilterBar && heroSection) { + var heroObserver = new IntersectionObserver(function (entries) { + entries.forEach(function (entry) { + stickyFilterBar.classList.toggle('visible', !entry.isIntersecting); + }); + }, { threshold: 0, rootMargin: '-80px 0px 0px 0px' }); + heroObserver.observe(heroSection); - function updateSearchResultCount(count) { - var badge = document.getElementById('searchResultCount'); - if (!badge) { - if (!searchInput) return; - badge = document.createElement('span'); - badge.id = 'searchResultCount'; - badge.style.cssText = 'font-family:IBM Plex Mono,monospace;font-size:0.72rem;color:var(--text-secondary);margin-left:0.5rem;white-space:nowrap;opacity:0;transition:opacity 0.2s ease'; - searchInput.parentNode.appendChild(badge); - } - if (count > 0) { - badge.textContent = count + ' result' + (count !== 1 ? 's' : ''); - badge.style.opacity = '1'; - } else { - badge.style.opacity = '0'; - } + window.addEventListener('scroll', function () { + var navH = navbar ? navbar.getBoundingClientRect().height : 72; + stickyFilterBar.style.top = (navH + 16) + 'px'; + }, { passive: true }); + } + + /* ── Random Project ───────────────────────────────────────── */ + function openRandomProject(trigger) { + var visible = projectCards.filter(function (c) { return c.style.display !== 'none'; }); + var pool = visible.length ? visible : projectCards; + var pick = pool[Math.floor(Math.random() * pool.length)]; + var name = pick.getAttribute('data-project'); + if (name && typeof openProjectSafe === 'function') { + openProjectSafe(name, trigger); } - - function closeDropdown() { - if (searchDropdown) searchDropdown.classList.remove('active'); + } + + if (randomProjectBtn) { + randomProjectBtn.addEventListener('click', function () { openRandomProject(randomProjectBtn); }); + } + if (randomProjectBtnSidebar) { + randomProjectBtnSidebar.addEventListener('click', function () { openRandomProject(randomProjectBtnSidebar); }); + } + + /* ── Init sidebar ─────────────────────────────────────────── */ + var pageCategory = document.body.getAttribute('data-page'); + if (sidebarTabs.length) { + if (pageCategory) { + syncSidebarTabs(pageCategory); + } else { + syncSidebarTabs('all'); } + } + if (stickyTabs.length) syncStickyTabs('all'); + + /* ── Sidebar Active Scroll Observer ───────────────────────── */ + if (!pageCategory && projectsSection) { + // On homepage, observe projectsSection to toggle sidebar-active class + var sidebarActiveObserver = new IntersectionObserver(function (entries) { + entries.forEach(function (entry) { + var isVisible = entry.isIntersecting || entry.boundingClientRect.top < 200; + document.body.classList.toggle('sidebar-active', isVisible); + }); + }, { threshold: 0.05 }); + sidebarActiveObserver.observe(projectsSection); + } else if (pageCategory) { + // On subpages, always ensure sidebar is active + document.body.classList.add('sidebar-active'); + } + + /* ═══════════════════════════════════════════════════════════════ + SEARCH + ═══════════════════════════════════════════════════════════════ */ + function getMatchingProjects(query) { + if (!query) return []; + var matches = []; + projectCards.forEach(function (card) { + var category = card.getAttribute('data-category'); + var title = (card.querySelector('h3') || {}).textContent || ''; + var desc = (card.querySelector('p') || {}).textContent || ''; + var tags = (card.getAttribute('data-tags') || '').toLowerCase(); + var q = query.toLowerCase(); + + var catMatch = currentCategory === 'all' || category === currentCategory; + var searchMatch = title.toLowerCase().includes(q) || + desc.toLowerCase().includes(q) || + tags.includes(q); + + if (catMatch && searchMatch) { + matches.push({ card: card, title: title, tags: tags, category: category }); + } + }); + return matches; + } + + function highlightText(container, text, query) { + var safe = query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + var parts = text.split(new RegExp('(' + safe + ')', 'gi')); + parts.forEach(function (part) { + if (part.toLowerCase() === query.toLowerCase()) { + var mark = document.createElement('mark'); + mark.style.background = 'var(--accent-soft)'; + mark.style.color = 'var(--accent)'; + mark.style.fontWeight = '600'; + mark.style.borderRadius = '2px'; + mark.style.padding = '0 2px'; + mark.textContent = part; + container.appendChild(mark); + } else if (part) { + container.appendChild(document.createTextNode(part)); + } + }); + } - function renderRecentSearches() { - if (noResultsMessage) noResultsMessage.style.display = 'none'; - if (!recentSearchesSection) return; - - if (recentSearches.length === 0) { - recentSearchesSection.style.display = 'none'; - if (tipsSection) tipsSection.style.display = 'block'; - if (resultsSection) resultsSection.style.display = 'none'; - return; - } + function closeDropdown() { + if (searchDropdown) searchDropdown.classList.remove('active'); + } - if (recentSearchesList) { - recentSearchesList.innerHTML = ''; - recentSearches.slice(0, 5).forEach(function (search) { - var item = document.createElement('div'); - item.className = 'dropdown-recent-item'; - var recentText = document.createElement('div'); - recentText.className = 'dropdown-recent-text'; - - var clockIcon = document.createElement('i'); - clockIcon.className = 'fas fa-history'; - clockIcon.style.opacity = '0.5'; - clockIcon.style.fontSize = '0.9rem'; - - var searchLabel = document.createElement('span'); - searchLabel.style.flex = '1'; - searchLabel.style.cursor = 'pointer'; - searchLabel.style.color = 'var(--text-secondary)'; - searchLabel.textContent = search; - - recentText.append(clockIcon, searchLabel); - - var removeButton = document.createElement('button'); - removeButton.className = 'dropdown-recent-remove'; - removeButton.setAttribute('aria-label', 'Remove search'); - - var removeIcon = document.createElement('i'); - removeIcon.className = 'fas fa-x'; - removeButton.appendChild(removeIcon); - - item.append(recentText, removeButton); - - searchLabel.addEventListener('click', function () { - if (searchInput) { - searchInput.value = search; - currentSearchQuery = search; - performSearch(); - closeDropdown(); - } - }); - - removeButton.addEventListener('click', function (e) { - e.stopPropagation(); - recentSearches = recentSearches.filter(function (s) { return s !== search; }); - localStorage.setItem('recentSearches', JSON.stringify(recentSearches)); - renderRecentSearches(); - }); - - recentSearchesList.appendChild(item); - }); - } + function renderRecentSearches() { + if (noResultsMessage) noResultsMessage.style.display = 'none'; + if (!recentSearchesSection) return; - recentSearchesSection.style.display = 'block'; - if (resultsSection) resultsSection.style.display = 'none'; - if (tipsSection) tipsSection.style.display = 'block'; + if (recentSearches.length === 0) { + recentSearchesSection.style.display = 'none'; + if (tipsSection) tipsSection.style.display = 'block'; + if (resultsSection) resultsSection.style.display = 'none'; + return; } - function renderSuggestions(query) { - if (searchLoader) searchLoader.style.display = 'none'; - if (!query) { renderRecentSearches(); return; } + if (recentSearchesList) { + recentSearchesList.innerHTML = ''; + recentSearches.slice(0, 5).forEach(function (search) { + var item = document.createElement('div'); + item.className = 'dropdown-recent-item'; + var text = document.createElement('div'); + text.className = 'dropdown-recent-text'; - var matches = getMatchingProjects(query); + var clock = document.createElement('i'); + clock.className = 'fas fa-history'; + clock.style.opacity = '0.5'; + clock.style.fontSize = '0.8rem'; - if (matches.length === 0) { - if (resultsSection) resultsSection.style.display = 'none'; - if (recentSearchesSection) recentSearchesSection.style.display = 'none'; - if (tipsSection) tipsSection.style.display = 'block'; - if (noResultsMessage) noResultsMessage.style.display = 'block'; - return; - } - - if (noResultsMessage) noResultsMessage.style.display = 'none'; - - if (resultsList) { - resultsList.innerHTML = ''; - matches.slice(0, 8).forEach(function (project, index) { - var item = document.createElement('div'); - item.className = 'dropdown-item' + (index === selectedSuggestionIndex ? ' selected' : ''); - var bannerEl = project.card.querySelector('.card-banner'); - var iconBox = document.createElement('div'); - iconBox.className = 'dropdown-item-icon'; - if (bannerEl) { - var img = document.createElement('img'); - img.src = bannerEl.src; - img.alt = ''; - img.style.cssText = 'width:24px;height:24px;border-radius:4px;object-fit:cover'; - iconBox.appendChild(img); - } - - var titleBox = document.createElement('div'); - titleBox.className = 'dropdown-item-text'; - highlightText(titleBox, project.title, query); - - var categoryTag = document.createElement('span'); - categoryTag.className = 'dropdown-item-tag'; - categoryTag.textContent = project.category; - - item.append(iconBox, titleBox, categoryTag); - item.addEventListener('click', function () { selectSuggestion(project.title); }); - item.addEventListener('mouseenter', function () { - selectedSuggestionIndex = index; - updateSuggestionHighlight(); - }); - resultsList.appendChild(item); - }); - } + var label = document.createElement('span'); + label.textContent = search; - if (resultsSection) resultsSection.style.display = 'block'; - if (recentSearchesSection) recentSearchesSection.style.display = 'none'; - if (tipsSection) tipsSection.style.display = 'none'; - selectedSuggestionIndex = -1; - } + text.append(clock, label); - if (searchInput) { - var debouncedSearch = debounce(function (query) { - renderSuggestions(query); - }, 200); - - searchInput.addEventListener('input', function (e) { - var query = e.target.value.trim().toLowerCase(); - currentSearchQuery = query; - if (searchClear) searchClear.style.display = query ? 'flex' : 'none'; - if (searchLoader) searchLoader.style.display = query ? 'block' : 'none'; - debouncedSearch(query); - performSearch(); - if (query && projectsSection && document.activeElement === searchInput) { - projectsSection.scrollIntoView({ - behavior: prefersReducedMotion() ? 'auto' : 'smooth', - block: 'start' - }); - } - }); + var removeBtn = document.createElement('button'); + removeBtn.className = 'dropdown-recent-remove'; + removeBtn.setAttribute('aria-label', 'Remove search'); + removeBtn.innerHTML = ''; - searchInput.addEventListener('focus', function () { - if (searchDropdown) searchDropdown.classList.add('active'); - if (searchShortcut) searchShortcut.style.display = 'none'; - if (!currentSearchQuery) renderRecentSearches(); - }); + item.append(text, removeBtn); - searchInput.addEventListener('keydown', function (e) { - if (e.key === 'Escape') { closeDropdown(); searchInput.blur(); } + label.addEventListener('click', function () { + if (searchInput) searchInput.value = search; + currentSearchQuery = search; + performSearch(); + closeDropdown(); }); - } - if (searchClear) { - searchClear.addEventListener('click', function () { - if (searchInput) searchInput.value = ''; - currentSearchQuery = ''; - searchClear.style.display = 'none'; - if (searchLoader) searchLoader.style.display = 'none'; - cleanSearchHighlights(); - updateSearchResultCount(0); - var hint = document.getElementById('emptyStateHint'); - if (hint) hint.textContent = 'Try adjusting your search or category filter.'; - applyCategoryFilter(currentCategory); - closeDropdown(); + removeBtn.addEventListener('click', function (e) { + e.stopPropagation(); + recentSearches = recentSearches.filter(function (s) { return s !== search; }); + localStorage.setItem('recentSearches', JSON.stringify(recentSearches)); + renderRecentSearches(); }); - } - - document.addEventListener('click', function (e) { - if (searchDropdown && searchInput && - !searchDropdown.contains(e.target) && e.target !== searchInput) { - closeDropdown(); - } - }); - - document.addEventListener('keydown', function (e) { - if ((e.ctrlKey || e.metaKey) && e.key === 'k') { - e.preventDefault(); - if (searchInput) searchInput.focus(); - } - }); - - renderRecentSearches(); - // ── Init sidebar ───────────────────────────────────────────────── - if (sidebarTabs.length) syncSidebarTabs('all'); - - // ── Central Dynamic Auto-Scaling (ResizeObserver) ───────────────── - var modalResizeObserver = null; - - function applyModalScaling() { - var modalContent = document.querySelector('.modal-content'); - var modalBody = document.getElementById('modalBody'); - if (!modalContent || !modalBody) return; - - modalContent.scrollTop = 0; - modalBody.scrollTop = 0; + recentSearchesList.appendChild(item); + }); + } - modalContent.style.overflow = 'auto'; + recentSearchesSection.style.display = 'block'; + if (resultsSection) resultsSection.style.display = 'none'; + if (tipsSection) tipsSection.style.display = 'block'; + } - modalBody.style.transform = ''; - modalBody.style.transformOrigin = ''; - modalBody.style.width = '100%'; - modalBody.style.height = ''; - modalBody.style.display = 'flex'; - modalBody.style.flexDirection = 'column'; - modalBody.style.alignItems = 'stretch'; - modalBody.style.gap = '1rem'; + function renderSuggestions(query) { + if (searchLoader) searchLoader.style.display = 'none'; + if (!query) { renderRecentSearches(); return; } - var targetEl = Array.from(modalBody.children).find(function (el) { - return el.tagName.toLowerCase() !== 'style'; - }) || modalBody.firstElementChild; - if (!targetEl) return; + var matches = getMatchingProjects(query); - targetEl.style.transform = ''; - targetEl.style.transformOrigin = ''; - targetEl.style.width = '100%'; - targetEl.style.maxWidth = '100%'; + if (matches.length === 0) { + if (resultsSection) resultsSection.style.display = 'none'; + if (recentSearchesSection) recentSearchesSection.style.display = 'none'; + if (tipsSection) tipsSection.style.display = 'block'; + if (noResultsMessage) noResultsMessage.style.display = 'block'; + return; } - function initModalScaling() { - applyModalScaling(); - - if (modalResizeObserver) { - modalResizeObserver.disconnect(); - } - - var modalBody = document.getElementById('modalBody'); - var targetEl = modalBody ? Array.from(modalBody.children).find(function (el) { - return el.tagName.toLowerCase() !== 'style'; - }) || modalBody.firstElementChild : null; - if (targetEl) { - modalResizeObserver = new ResizeObserver(function () { - requestAnimationFrame(applyModalScaling); - }); - modalResizeObserver.observe(targetEl); + if (noResultsMessage) noResultsMessage.style.display = 'none'; + + if (resultsList) { + resultsList.innerHTML = ''; + matches.slice(0, 8).forEach(function (project, index) { + var item = document.createElement('div'); + item.className = 'dropdown-item' + (index === selectedSuggestionIndex ? ' selected' : ''); + + var iconBox = document.createElement('div'); + iconBox.className = 'dropdown-item-icon'; + var banner = project.card.querySelector('.card-banner'); + if (banner) { + var img = document.createElement('img'); + img.src = banner.src; + img.alt = ''; + iconBox.appendChild(img); } - window.addEventListener('resize', applyModalScaling); - } + var titleBox = document.createElement('div'); + titleBox.className = 'dropdown-item-text'; + highlightText(titleBox, project.title, query); - function destroyModalScaling() { - if (modalResizeObserver) { - modalResizeObserver.disconnect(); - modalResizeObserver = null; - } - window.removeEventListener('resize', applyModalScaling); - } + var tag = document.createElement('span'); + tag.className = 'dropdown-item-tag'; + tag.textContent = project.category; - // ── Focus Trap for Modal ────────────────────────────────────────── - function getFocusableElements(root) { - var selector = - 'button:not([disabled]), [href], input:not([disabled]), ' + - 'select:not([disabled]), textarea:not([disabled]), ' + - '[tabindex]:not([tabindex="-1"])'; - return Array.from(root.querySelectorAll(selector)).filter(function (el) { - return !el.closest('[aria-hidden="true"]') && - !el.classList.contains('visually-hidden'); + item.append(iconBox, titleBox, tag); + item.addEventListener('click', function () { selectSuggestion(project.title); }); + item.addEventListener('mouseenter', function () { + selectedSuggestionIndex = index; + updateSuggestionHighlight(); }); + resultsList.appendChild(item); + }); } - function trapFocus(modalEl) { - var handler = function (e) { - if (e.key !== 'Tab' || !modalEl.classList.contains('active')) return; - var focusables = getFocusableElements(modalEl); - if (!focusables.length) return; - var first = focusables[0]; - var last = focusables[focusables.length - 1]; - if (e.shiftKey && document.activeElement === first) { - e.preventDefault(); last.focus({ preventScroll: true }); - } else if (!e.shiftKey && document.activeElement === last) { - e.preventDefault(); first.focus({ preventScroll: true }); - } - }; - document.addEventListener('keydown', handler, true); - return function () { document.removeEventListener('keydown', handler, true); }; + if (resultsSection) resultsSection.style.display = 'block'; + if (recentSearchesSection) recentSearchesSection.style.display = 'none'; + if (tipsSection) tipsSection.style.display = 'none'; + selectedSuggestionIndex = -1; + } + + function updateSuggestionHighlight() { + if (!resultsList) return; + var items = resultsList.querySelectorAll('.dropdown-item'); + items.forEach(function (item, i) { + item.classList.toggle('selected', i === selectedSuggestionIndex); + }); + } + + function selectSuggestion(title) { + if (!searchInput) return; + searchInput.value = title; + currentSearchQuery = title.toLowerCase(); + performSearch(); + closeDropdown(); + if (projectsSection) { + projectsSection.scrollIntoView({ behavior: prefersReducedMotion() ? 'auto' : 'smooth', block: 'start' }); } - - // ── Open / Close Project Modal ──────────────────────────────────── - /* - * PLAYGROUND NOTE: openProjectSafe() is called synchronously from - * card clicks. It NEVER touches Pyodide and NEVER waits for it. - * The modal opens instantly regardless of Pyodide load state. - */ - function openProjectSafe(name, trigger) { - if (!modal || !modalBody) return; - - lastFocusedElement = trigger || document.activeElement; - - if (modalTitle) modalTitle.textContent = name || 'Interactive project'; - - modal.classList.add('active'); - modal.setAttribute('aria-hidden', 'false'); - - // Prevent scroll shift by adding padding equal to scrollbar width - var scrollbarWidth = window.innerWidth - document.documentElement.clientWidth; - document.body.style.paddingRight = scrollbarWidth + 'px'; - document.body.style.overflow = 'hidden'; - setMainInert(true); - - safeRun(function () { - if (typeof getProjectHTML === 'function') { - modalBody.innerHTML = - getProjectHTML(name) || - '
Project content unavailable.
'; - } else { - modalBody.innerHTML = '
Project content unavailable.
'; - } - if (typeof initializeProject === 'function') initializeProject(name); - }); - - // Initialize reactive scale calculations - initModalScaling(); - - removeTrap = trapFocus(modal); - var focusables = getFocusableElements(modalBody); - var firstFocusable = focusables[0] || modalClose; - if (firstFocusable && typeof firstFocusable.focus === 'function') { - firstFocusable.focus({ preventScroll: true }); - } + } + + function performSearch() { + var query = currentSearchQuery; + if (!query) { + applyCategoryFilter(currentCategory); + if (emptyStateHint) emptyStateHint.textContent = 'Try adjusting your search or category filter.'; + return; } - function closeProjectSafe() { - if (!modal || !modal.classList.contains('active')) return; - - destroyModalScaling(); - - modal.classList.remove('active'); - modal.setAttribute('aria-hidden', 'true'); - document.body.style.paddingRight = ''; - document.body.style.overflow = ''; - setMainInert(false); - if (removeTrap) { removeTrap(); removeTrap = null; } - if (modalBody) { - modalBody.innerHTML = ''; - modalBody.style.transform = ''; - modalBody.style.transformOrigin = ''; - modalBody.style.width = ''; - modalBody.style.height = ''; - modalBody.style.display = ''; - modalBody.style.alignItems = ''; - modalBody.style.gap = ''; - } - if (lastFocusedElement && typeof lastFocusedElement.focus === 'function') { - lastFocusedElement.focus({ preventScroll: true }); - } - lastFocusedElement = null; + if (currentCategory !== 'all') { + currentCategory = 'all'; + syncSidebarTabs('all'); + syncStickyTabs('all'); } - if (modalClose) modalClose.addEventListener('click', closeProjectSafe); - if (modal) { - modal.addEventListener('click', function (e) { - if (e.target === modal) closeProjectSafe(); - }); - } - document.addEventListener('keydown', function (e) { - if (e.key === 'Escape') closeProjectSafe(); - }); + recentSearches = recentSearches.filter(function (s) { return s !== query; }); + recentSearches.unshift(query); + recentSearches = recentSearches.slice(0, 10); + localStorage.setItem('recentSearches', JSON.stringify(recentSearches)); - // ── Wire Cards and Play Buttons ─────────────────────────────────── + var visibleCount = 0; + var favorites = JSON.parse(localStorage.getItem('favorites') || '[]'); projectCards.forEach(function (card) { - var name = card.getAttribute('data-project'); - var categoryName = card.getAttribute('data-category') || 'project'; - var metaRow = document.createElement('div'); - metaRow.className = 'project-card-meta'; - - var categoryBadge = document.createElement('span'); - categoryBadge.className = 'project-card-badge'; - categoryBadge.textContent = categoryName; - metaRow.appendChild(categoryBadge); - - var favBtn = document.createElement('button'); - favBtn.className = 'btn-favorite'; - favBtn.setAttribute('aria-label', 'Toggle favorite'); - favBtn.textContent = '\u2606'; - - var favorites = JSON.parse(localStorage.getItem('favorites') || '[]'); - if (favorites.includes(name)) { - favBtn.classList.add('active'); - favBtn.textContent = '\u2605'; - } - - favBtn.addEventListener('click', function (e) { - e.stopPropagation(); - var favs = JSON.parse(localStorage.getItem('favorites') || '[]'); - var idx = favs.indexOf(name); - if (idx === -1) { - favs.push(name); - favBtn.classList.add('active'); - favBtn.textContent = '\u2605'; - } else { - favs.splice(idx, 1); - favBtn.classList.remove('active'); - favBtn.textContent = '\u2606'; - if (currentCategory === 'favorites') { - card.style.display = 'none'; - } - } - localStorage.setItem('favorites', JSON.stringify(favs)); - }); - var cardActions = card.querySelector('.card-actions'); - if (cardActions) { - cardActions.appendChild(favBtn); - } else { - card.appendChild(favBtn); - } - - var play = card.querySelector('.btn-play'); - if (play) { - play.setAttribute('aria-label', 'Open ' + name); - play.addEventListener('click', function (e) { - e.stopPropagation(); - openProjectSafe(name, play); - }); - } - card.appendChild(metaRow); - card.addEventListener('click', function () { openProjectSafe(name, card); }); + var category = card.getAttribute('data-category'); + var title = (card.querySelector('h3') || {}).textContent || ''; + var desc = (card.querySelector('p') || {}).textContent || ''; + var tags = (card.getAttribute('data-tags') || '').toLowerCase(); + var projectName = card.getAttribute('data-project'); + var isFav = favorites.includes(projectName); + + var catMatch = (currentCategory === 'all') || + (currentCategory === 'favorites' && isFav) || + (currentCategory !== 'favorites' && category === currentCategory); + var searchMatch = title.toLowerCase().includes(query) || + desc.toLowerCase().includes(query) || + tags.includes(query); + + if (catMatch && searchMatch) { + card.style.display = ''; + visibleCount++; + } else { + card.style.display = 'none'; + } }); - // ── Random button is in the sidebar (#randomProjectBtnSidebar) ───── + if (emptyState) { + emptyState.style.display = visibleCount === 0 ? 'block' : 'none'; + if (visibleCount === 0 && emptyStateHint) { + emptyStateHint.textContent = 'No projects match "' + query + '". Try a different keyword.'; + } + } + if (projectCountBadge) projectCountBadge.textContent = String(visibleCount) + ' projects'; + } + + if (searchInput) { + var debouncedSearch = debounce(function (query) { + renderSuggestions(query); + }, 200); + + searchInput.addEventListener('input', function (e) { + var query = e.target.value.trim().toLowerCase(); + currentSearchQuery = query; + if (searchLoader) searchLoader.style.display = query ? 'block' : 'none'; + debouncedSearch(query); + performSearch(); + }); - featureLaunchers.forEach(function (node) { - node.addEventListener('click', function (e) { - if (e.target.closest('[data-project-target]') === node) { - var targetProject = node.getAttribute('data-project-target'); - if (targetProject) openProjectSafe(targetProject, node); - } - }); + searchInput.addEventListener('focus', function () { + if (searchDropdown) searchDropdown.classList.add('active'); + if (!currentSearchQuery) renderRecentSearches(); }); - if (!prefersReducedMotion()) { - projectCards.forEach(function (card) { - card.addEventListener('mousemove', function (event) { - var rect = card.getBoundingClientRect(); - var px = (event.clientX - rect.left) / rect.width; - var py = (event.clientY - rect.top) / rect.height; - var rotateY = (px - 0.5) * 10; - var rotateX = (0.5 - py) * 8; - card.style.transform = 'perspective(900px) rotateX(' + rotateX + 'deg) rotateY(' + rotateY + 'deg) translateY(-8px)'; - }); - card.addEventListener('mouseleave', function () { - card.style.transform = ''; - }); - }); + searchInput.addEventListener('keydown', function (e) { + if (e.key === 'Escape') { closeDropdown(); searchInput.blur(); } + }); + } - var parallaxItems = document.querySelectorAll('[data-parallax]'); - window.addEventListener('scroll', function () { - var scrollY = window.scrollY; - parallaxItems.forEach(function (item) { - var ratio = parseFloat(item.getAttribute('data-parallax') || '0'); - item.style.transform = 'translateY(' + Math.round(scrollY * ratio) + 'px)'; - }); - }, { passive: true }); + document.addEventListener('click', function (e) { + if (searchDropdown && searchInput && + !searchDropdown.contains(e.target) && e.target !== searchInput) { + closeDropdown(); } + }); - // ── Intersection Observer Animations ───────────────────────────── - if (!prefersReducedMotion()) { - try { - var observer = new IntersectionObserver(function (entries) { - entries.forEach(function (entry) { - if (entry.isIntersecting) { - entry.target.style.animation = 'fadeInUp 0.6s ease'; - } - }); - }, { threshold: 0.1, rootMargin: '0px 0px -100px 0px' }); - projectCards.forEach(function (c) { observer.observe(c); }); - } catch (e) { /* ignore */ } + document.addEventListener('keydown', function (e) { + if ((e.ctrlKey || e.metaKey) && e.key === 'k') { + e.preventDefault(); + if (searchInput) searchInput.focus(); } + }); - if (!prefersReducedMotion()) { - try { - var revealObserver = new IntersectionObserver(function (entries, obs) { - entries.forEach(function (entry) { - if (!entry.isIntersecting) return; - entry.target.classList.add('is-visible'); - obs.unobserve(entry.target); - }); - }, { threshold: 0.2 }); - - revealItems.forEach(function (item) { revealObserver.observe(item); }); - } catch (e) { /* ignore */ } - } else { - revealItems.forEach(function (item) { item.classList.add('is-visible'); }); - } + renderRecentSearches(); - // ── Hero Timeline scroll reveal ──────────────────────────── - var timelineNodes = document.querySelectorAll('.timeline-node[data-reveal]'); - var heroTimeline = document.getElementById('heroTimeline'); + /* ═══════════════════════════════════════════════════════════════ + MODAL + ═══════════════════════════════════════════════════════════════ */ + function getFocusableElements(root) { + var sel = 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'; + return Array.from(root.querySelectorAll(sel)).filter(function (el) { + return !el.closest('[aria-hidden="true"]') && !el.classList.contains('visually-hidden'); + }); + } + + function trapFocus(modalEl) { + var handler = function (e) { + if (e.key !== 'Tab' || !modalEl.classList.contains('active')) return; + var focusables = getFocusableElements(modalEl); + if (!focusables.length) return; + var first = focusables[0]; + var last = focusables[focusables.length - 1]; + if (e.shiftKey && document.activeElement === first) { + e.preventDefault(); last.focus({ preventScroll: true }); + } else if (!e.shiftKey && document.activeElement === last) { + e.preventDefault(); first.focus({ preventScroll: true }); + } + }; + document.addEventListener('keydown', handler, true); + return function () { document.removeEventListener('keydown', handler, true); }; + } + + function openProjectSafe(name, trigger) { + if (!modal || !modalBody) return; + lastFocusedElement = trigger || document.activeElement; + if (modalTitle) modalTitle.textContent = name || 'Interactive project'; + modal.classList.add('active'); + modal.setAttribute('aria-hidden', 'false'); + var scrollbarWidth = window.innerWidth - document.documentElement.clientWidth; + document.body.style.paddingRight = scrollbarWidth + 'px'; + document.body.style.overflow = 'hidden'; + setMainInert(true); + + safeRun(function () { + if (typeof getProjectHTML === 'function') { + modalBody.innerHTML = getProjectHTML(name) || '
Project content unavailable.
'; + } else { + modalBody.innerHTML = '
Project content unavailable.
'; + } + if (typeof initializeProject === 'function') initializeProject(name); + }); - function updateSpineProgress() { - if (!heroTimeline) return; - var revealed = document.querySelectorAll('.timeline-node.revealed').length; - var total = timelineNodes.length; - var pct = total > 0 ? (revealed / total) * 100 : 0; - heroTimeline.style.setProperty('--spine-progress', pct); + removeTrap = trapFocus(modal); + var focusables = getFocusableElements(modalBody); + var firstFocusable = focusables[0] || modalClose; + if (firstFocusable && typeof firstFocusable.focus === 'function') { + firstFocusable.focus({ preventScroll: true }); + } + } + + function closeProjectSafe() { + if (!modal || !modal.classList.contains('active')) return; + modal.classList.remove('active'); + modal.setAttribute('aria-hidden', 'true'); + document.body.style.paddingRight = ''; + document.body.style.overflow = ''; + setMainInert(false); + if (removeTrap) { removeTrap(); removeTrap = null; } + if (modalBody) { + modalBody.innerHTML = ''; } + if (lastFocusedElement && typeof lastFocusedElement.focus === 'function') { + lastFocusedElement.focus({ preventScroll: true }); + } + lastFocusedElement = null; + } - if (timelineNodes.length) { - timelineNodes[0].classList.add('revealed'); - updateSpineProgress(); + if (modalClose) modalClose.addEventListener('click', closeProjectSafe); + if (modal) { + modal.addEventListener('click', function (e) { + if (e.target === modal) closeProjectSafe(); + }); + } + document.addEventListener('keydown', function (e) { + if (e.key === 'Escape') closeProjectSafe(); + }); + + /* ── Expose for inline use ────────────────────────────────── */ + window.openProjectSafe = openProjectSafe; + window.closeProjectSafe = closeProjectSafe; + + /* ═══════════════════════════════════════════════════════════════ + WIRE PROJECT CARDS + ═══════════════════════════════════════════════════════════════ */ + projectCards.forEach(function (card) { + var name = card.getAttribute('data-project'); + + /* ── Favorite Button ──────────────────────────────────── */ + var favBtn = document.createElement('button'); + favBtn.className = 'btn-favorite'; + favBtn.setAttribute('aria-label', 'Toggle favorite'); + favBtn.innerHTML = ''; + + var favorites = JSON.parse(localStorage.getItem('favorites') || '[]'); + if (favorites.includes(name)) { + favBtn.classList.add('active'); + favBtn.innerHTML = ''; } - if (timelineNodes.length > 1 && !prefersReducedMotion()) { - var revealedCount = 1; - var scrollAccum = 0; - var prevScrollY = window.scrollY; - - window.addEventListener('scroll', function () { - var currentScrollY = window.scrollY; - var delta = currentScrollY - prevScrollY; - scrollAccum += delta; - prevScrollY = currentScrollY; - - if (scrollAccum >= 60) { - while (scrollAccum >= 60 && revealedCount < timelineNodes.length) { - timelineNodes[revealedCount].classList.add('revealed'); - revealedCount++; - scrollAccum -= 60; - } - } else if (scrollAccum <= -60) { - while (scrollAccum <= -60 && revealedCount > 1) { - revealedCount--; - timelineNodes[revealedCount].classList.remove('revealed'); - scrollAccum += 60; - } - } - updateSpineProgress(); - }, { passive: true }); - } else if (timelineNodes.length > 1) { - for (var i = 1; i < timelineNodes.length; i++) { - timelineNodes[i].classList.add('revealed'); + favBtn.addEventListener('click', function (e) { + e.stopPropagation(); + var favs = JSON.parse(localStorage.getItem('favorites') || '[]'); + var idx = favs.indexOf(name); + if (idx === -1) { + favs.push(name); + favBtn.classList.add('active'); + favBtn.innerHTML = ''; + } else { + favs.splice(idx, 1); + favBtn.classList.remove('active'); + favBtn.innerHTML = ''; + if (currentCategory === 'favorites') { + card.style.display = 'none'; } - updateSpineProgress(); - } + } + localStorage.setItem('favorites', JSON.stringify(favs)); + }); - // ── Share Button Feature ────────────────────────────────────────── + var cardActions = card.querySelector('.card-actions'); + if (cardActions) cardActions.appendChild(favBtn); -// 1. Inject share button into every card dynamically -projectCards.forEach(function (card) { - var projectName = card.getAttribute('data-project'); + /* ── Share Button ─────────────────────────────────────── */ var shareBtn = document.createElement('button'); shareBtn.className = 'btn-share'; - shareBtn.setAttribute('aria-label', 'Share ' + projectName); + shareBtn.setAttribute('aria-label', 'Share ' + name); shareBtn.innerHTML = ''; shareBtn.title = 'Copy shareable link'; shareBtn.addEventListener('click', function (e) { - e.stopPropagation(); // prevent card click opening modal - var url = window.location.origin + window.location.pathname + '?project=' + encodeURIComponent(projectName); - navigator.clipboard.writeText(url).then(function () { - showToast('Link copied!'); - }).catch(function () { - // Fallback for browsers that block clipboard - showToast('Copy this: ' + url); - }); + e.stopPropagation(); + var url = window.location.origin + window.location.pathname + '?project=' + encodeURIComponent(name); + navigator.clipboard.writeText(url).then(function () { + showToast('Link copied!'); + }).catch(function () { + showToast('Copy this: ' + url); + }); }); - var cardActions = card.querySelector('.card-actions'); - if (cardActions) { - cardActions.appendChild(shareBtn); - } else { - card.appendChild(shareBtn); + if (cardActions) cardActions.appendChild(shareBtn); + + /* ── Play Button ──────────────────────────────────────── */ + var playBtns = card.querySelectorAll('.btn-play'); + playBtns.forEach(function (play) { + play.setAttribute('aria-label', 'Open ' + name); + play.addEventListener('click', function (e) { + e.stopPropagation(); + openProjectSafe(name, play); + }); + }); + + /* ── Card Click ───────────────────────────────────────── */ + card.addEventListener('click', function (e) { + if (e.target.closest('.btn-play') || e.target.closest('.btn-favorite') || e.target.closest('.btn-share')) return; + openProjectSafe(name, card); + }); + + /* ── Card Mouse Tracking for Border Glow ──────────────── */ + if (!prefersReducedMotion()) { + card.addEventListener('mousemove', function (e) { + var rect = card.getBoundingClientRect(); + var x = ((e.clientX - rect.left) / rect.width) * 100; + var y = ((e.clientY - rect.top) / rect.height) * 100; + card.style.setProperty('--mouse-x', x + '%'); + card.style.setProperty('--mouse-y', y + '%'); + }); } -}); + }); -// 2. Toast notification helper -function showToast(message) { + /* ── Toast ─────────────────────────────────────────────────── */ + function showToast(message) { var existing = document.getElementById('shareToast'); if (existing) existing.remove(); - var toast = document.createElement('div'); toast.id = 'shareToast'; toast.className = 'share-toast'; toast.textContent = message; document.body.appendChild(toast); - - // Trigger animation requestAnimationFrame(function () { - toast.classList.add('share-toast--visible'); + toast.classList.add('share-toast--visible'); }); - setTimeout(function () { - toast.classList.remove('share-toast--visible'); - setTimeout(function () { toast.remove(); }, 300); + toast.classList.remove('share-toast--visible'); + setTimeout(function () { toast.remove(); }, 300); }, 2500); -} + } -// 3. On page load, check for ?project= param and auto-open it -(function () { + /* ── URL params auto-open ──────────────────────────────────── */ + (function () { var params = new URLSearchParams(window.location.search); var projectParam = params.get('project'); - if (!projectParam) return; + if (projectParam) { + var match = projectCards.find(function (c) { return c.getAttribute('data-project') === projectParam; }); + if (match) { + setTimeout(function () { + openProjectSafe(projectParam, match); + match.scrollIntoView({ behavior: 'smooth', block: 'center' }); + }, 300); + } + } + var catParam = params.get('category'); + var valid = ['all', 'games', 'math', 'utilities', 'playground', 'favorites']; + if (catParam && valid.includes(catParam)) { + var tab = document.querySelector('[data-category="' + catParam + '"]'); + if (tab) setTimeout(function () { tab.click(); }, 100); + } + })(); + + /* ═══════════════════════════════════════════════════════════════ + TIMELINE SCROLL REVEAL + ═══════════════════════════════════════════════════════════════ */ + var timelineItems = document.querySelectorAll('.timeline-item[data-reveal]'); + var timelineFill = document.getElementById('timelineFill'); + var timelineSection = document.getElementById('timelineSection'); + + if (timelineItems.length && !prefersReducedMotion()) { + var timelineObserver = new IntersectionObserver(function (entries) { + entries.forEach(function (entry) { + if (entry.isIntersecting) { + entry.target.classList.add('visible'); + } + }); + }, { threshold: 0.25, rootMargin: '0px 0px -50px 0px' }); - var matchingCard = projectCards.find(function (card) { - return card.getAttribute('data-project') === projectParam; + timelineItems.forEach(function (item) { + timelineObserver.observe(item); }); - if (matchingCard) { - setTimeout(function () { - var projectName = matchingCard.getAttribute('data-project'); - openProjectSafe(projectName, matchingCard); - matchingCard.scrollIntoView({ behavior: 'smooth', block: 'center' }); - }, 300); // small delay so the page fully loads first + /* ── Serpentine SVG Winding Timeline Path ─────────────────── */ + var svgNamespace = 'http://www.w3.org/2000/svg'; + + function getElementOffset(el, parent) { + var top = 0; + var left = 0; + var curr = el; + while (curr && curr !== parent) { + top += curr.offsetTop || 0; + left += curr.offsetLeft || 0; + curr = curr.offsetParent; + } + return { top: top, left: left }; } -})(); -// 4. On page load, check for ?category= param and apply filter -(function () { - var params = new URLSearchParams(window.location.search); - var categoryParam = params.get('category'); - var validCategories = ['all', 'games', 'math', 'utilities', 'playground', 'favorites']; - if (!categoryParam || !validCategories.includes(categoryParam)) return; + function rebuildTimelineSvg() { + var container = document.querySelector('.timeline-container'); + if (!container) return; + var dots = document.querySelectorAll('.timeline-dot'); + if (dots.length < 2) return; - var matchingTab = document.querySelector('[data-category="' + categoryParam + '"]'); - if (matchingTab) { - setTimeout(function () { - matchingTab.click(); - }, 100); + var containerWidth = container.offsetWidth; + var containerHeight = container.offsetHeight; + + var svg = document.getElementById('timelineSvg'); + if (!svg) { + svg = document.createElementNS(svgNamespace, 'svg'); + svg.id = 'timelineSvg'; + svg.setAttribute('class', 'timeline-svg'); + + var defs = document.createElementNS(svgNamespace, 'defs'); + var grad = document.createElementNS(svgNamespace, 'linearGradient'); + grad.id = 'timelineGrad'; + grad.setAttribute('x1', '0%'); + grad.setAttribute('y1', '0%'); + grad.setAttribute('x2', '0%'); + grad.setAttribute('y2', '100%'); + + var stop1 = document.createElementNS(svgNamespace, 'stop'); + stop1.setAttribute('offset', '0%'); + stop1.setAttribute('stop-color', 'var(--accent)'); + + var stop2 = document.createElementNS(svgNamespace, 'stop'); + stop2.setAttribute('offset', '100%'); + stop2.setAttribute('stop-color', '#06b6d4'); + + grad.appendChild(stop1); + grad.appendChild(stop2); + defs.appendChild(grad); + + // Define a dynamic layout mask path for progress crawling + var mask = document.createElementNS(svgNamespace, 'mask'); + mask.id = 'timelineMask'; + + var maskPath = document.createElementNS(svgNamespace, 'path'); + maskPath.id = 'timelineMaskPath'; + maskPath.setAttribute('fill', 'none'); + maskPath.setAttribute('stroke', '#ffffff'); + maskPath.setAttribute('stroke-width', '24'); // Wide enough to fully cover glowing dots + maskPath.setAttribute('stroke-linecap', 'round'); + + mask.appendChild(maskPath); + defs.appendChild(mask); + svg.appendChild(defs); + + var track = document.createElementNS(svgNamespace, 'path'); + track.id = 'timelineSvgTrack'; + track.setAttribute('class', 'timeline-svg-track'); + track.setAttribute('fill', 'none'); + + var fill = document.createElementNS(svgNamespace, 'path'); + fill.id = 'timelineSvgFill'; + fill.setAttribute('class', 'timeline-svg-fill'); + fill.setAttribute('fill', 'none'); + fill.setAttribute('stroke', 'var(--accent)'); + fill.setAttribute('mask', 'url(#timelineMask)'); + + svg.appendChild(track); + svg.appendChild(fill); + + var grid = document.querySelector('.timeline-grid'); + container.insertBefore(svg, grid); + } + + // Determine layout stable coordinate points for all timeline dots + var coords = []; + dots.forEach(function (dot) { + var offset = getElementOffset(dot, container); + var x = offset.left + dot.offsetWidth / 2; + var y = offset.top + dot.offsetHeight / 2; + coords.push({ x: x, y: y }); + }); + + // Create the winding path + var d = ""; + var startX = containerWidth / 2; + d += "M " + startX + " 0"; + d += " L " + coords[0].x + " " + coords[0].y; + + // Calculate a sweep width that is perfectly responsive + // e.g. 16% of container width, capped at 180px for desktop beauty + var W = Math.min(180, containerWidth * 0.16); + + for (var i = 0; i < coords.length - 1; i++) { + var pStart = coords[i]; + var pEnd = coords[i + 1]; + var H = pEnd.y - pStart.y; + var dy = H * 0.35; // Symmetrical control point height + + // Even segments (0, 2, 4...) snake to the right, odd segments to the left + var dx = (i % 2 === 0) ? W : -W; + + var cp1x = pStart.x + dx; + var cp1y = pStart.y + dy; + var cp2x = pEnd.x + dx; + var cp2y = pEnd.y - dy; + + d += " C " + cp1x + " " + cp1y + ", " + cp2x + " " + cp2y + ", " + pEnd.x + " " + pEnd.y; + } + + // Straight exit to the bottom + d += " L " + coords[coords.length - 1].x + " " + containerHeight; + + var trackPath = document.getElementById('timelineSvgTrack'); + var fillPath = document.getElementById('timelineSvgFill'); + var maskPath = document.getElementById('timelineMaskPath'); + if (trackPath && fillPath && maskPath) { + trackPath.setAttribute('d', d); + fillPath.setAttribute('d', d); + maskPath.setAttribute('d', d); + + var totalLength = maskPath.getTotalLength(); + maskPath.style.strokeDasharray = totalLength; + maskPath.dataset.totalLength = totalLength; + + // Trigger scroll progress sync immediately + updateTimelineFill(); + } } -})(); + + /* ── Timeline Fill Progress ───────────────────────────── */ + function updateTimelineFill() { + if (!timelineSection) return; + var container = document.querySelector('.timeline-container'); + if (!container) return; + + var containerRect = container.getBoundingClientRect(); + var viewportCenterY = window.innerHeight / 2; + + // Calculate relative vertical scroll position of the viewport center relative to the container + var relativeY = viewportCenterY - containerRect.top; + var offset = Math.max(0, Math.min(1, relativeY / containerRect.height)); + + /* ── Dynamic SVG path mask scroll synchronization ──────── */ + var maskPath = document.getElementById('timelineMaskPath'); + if (maskPath && maskPath.dataset.totalLength) { + var totalLength = parseFloat(maskPath.dataset.totalLength); + var dashoffset = totalLength - (offset * totalLength); + maskPath.style.strokeDashoffset = Math.max(0, Math.min(totalLength, dashoffset)); + } + + /* ── Activate item based on viewport center crossing timeline dots ── */ + var activeIdx = -1; + var dots = document.querySelectorAll('.timeline-dot'); + + dots.forEach(function (dot, i) { + var dotRect = dot.getBoundingClientRect(); + var dotCenterY = dotRect.top + dotRect.height / 2; + + // A dot is crossed/passed if its vertical center in the viewport is <= the viewport center + if (dotCenterY <= viewportCenterY) { + activeIdx = i; + } + }); + + timelineItems.forEach(function (item, i) { + item.classList.toggle('active', i === activeIdx); + }); + } + + // Initialize SVG path layout recalculations on page render & resize + rebuildTimelineSvg(); + window.addEventListener('resize', debounce(rebuildTimelineSvg, 150)); + window.addEventListener('scroll', updateTimelineFill, { passive: true }); + } else if (timelineItems.length) { + timelineItems.forEach(function (item) { item.classList.add('visible'); }); + } + + /* ── Reveal on Scroll (general) ────────────────────────────── */ + var revealItems = document.querySelectorAll('.reveal-on-scroll'); + if (revealItems.length && !prefersReducedMotion()) { + var revealObserver = new IntersectionObserver(function (entries, obs) { + entries.forEach(function (entry) { + if (!entry.isIntersecting) return; + entry.target.classList.add('is-visible'); + obs.unobserve(entry.target); + }); + }, { threshold: 0.1, rootMargin: '0px 0px -50px 0px' }); + revealItems.forEach(function (item) { revealObserver.observe(item); }); + } else { + revealItems.forEach(function (item) { item.classList.add('is-visible'); }); + } + + /* ── Footer category links ────────────────────────────────── */ + document.querySelectorAll('.footer-cat-link').forEach(function (a) { + a.addEventListener('click', function (e) { + e.preventDefault(); + var cat = a.getAttribute('data-cat'); + var tab = document.querySelector('.sidebar-tab[data-category="' + cat + '"]'); + if (tab) tab.click(); + }); + }); }); diff --git a/web-app/math.html b/web-app/math.html index 46a91a0..71c5785 100644 --- a/web-app/math.html +++ b/web-app/math.html @@ -91,44 +91,103 @@ - + - - + +
- -
- -
-
@@ -267,7 +326,9 @@
-
+ - -
- -
+ + + +
@@ -226,7 +285,9 @@