diff --git a/frontend/src/hooks/useRainbowTheme.ts b/frontend/src/hooks/useRainbowTheme.ts index 5fc049007..f1005f462 100644 --- a/frontend/src/hooks/useRainbowTheme.ts +++ b/frontend/src/hooks/useRainbowTheme.ts @@ -1,6 +1,7 @@ import { useCallback, useRef, useEffect } from 'react'; import { usePreferences } from '../contexts/PreferencesContext'; import type { ThemeMode } from '../constants/theme'; +import rainbowStyles from '../styles/rainbow.module.css'; interface RainbowThemeHook { themeMode: ThemeMode; @@ -21,15 +22,56 @@ export function useRainbowTheme(): RainbowThemeHook { const toggleCount = useRef(0); const lastToggleTime = useRef(Date.now()); const isToggleDisabled = useRef(false); + const rainbowIntervalRef = useRef(null); // Apply rainbow class to body whenever theme changes useEffect(() => { + const root = document.documentElement; + + const clearRainbowInterval = () => { + if (rainbowIntervalRef.current !== null) { + window.clearInterval(rainbowIntervalRef.current); + rainbowIntervalRef.current = null; + } + }; + + const resetRainbowVariables = () => { + root.style.removeProperty('--rainbow-hue'); + root.style.removeProperty('--rainbow-angle'); + root.style.removeProperty('--rainbow-sparkle-opacity'); + root.style.removeProperty('--rainbow-glow-strength'); + }; + if (themeMode === 'rainbow') { document.body.classList.add('rainbow-mode-active'); showRainbowNotification(); + + const applyRainbowVariables = () => { + const hue = Math.floor(Math.random() * 360); + const angle = Math.floor(Math.random() * 360); + const sparkle = (Math.random() * 0.4 + 0.4).toFixed(2); + const glow = (Math.random() * 0.3 + 0.35).toFixed(2); + + root.style.setProperty('--rainbow-hue', hue.toString()); + root.style.setProperty('--rainbow-angle', `${angle}deg`); + root.style.setProperty('--rainbow-sparkle-opacity', sparkle); + root.style.setProperty('--rainbow-glow-strength', glow); + }; + + applyRainbowVariables(); + clearRainbowInterval(); + rainbowIntervalRef.current = window.setInterval(applyRainbowVariables, 1400); } else { document.body.classList.remove('rainbow-mode-active'); + clearRainbowInterval(); + resetRainbowVariables(); } + + return () => { + clearRainbowInterval(); + resetRainbowVariables(); + document.body.classList.remove('rainbow-mode-active'); + }; }, [themeMode]); const showRainbowNotification = () => { @@ -42,26 +84,8 @@ export function useRainbowTheme(): RainbowThemeHook { // Create and show rainbow notification const notification = document.createElement('div'); notification.id = 'rainbow-notification'; + notification.className = rainbowStyles.rainbowNotification; notification.innerHTML = '🌈 RAINBOW MODE ACTIVATED! 🌈
Button disabled for 3 seconds, then click to exit'; - notification.style.cssText = ` - position: fixed; - top: 20px; - right: 20px; - background: linear-gradient(45deg, #ff0000, #ff8800, #ffff00, #88ff00, #00ff88, #00ffff, #0088ff, #8800ff); - background-size: 300% 300%; - animation: rainbowBackground 1s ease infinite; - color: white; - padding: 15px 20px; - border-radius: 25px; - font-weight: bold; - font-size: 16px; - z-index: 1000; - border: 2px solid white; - box-shadow: 0 0 20px rgba(255, 255, 255, 0.8); - user-select: none; - pointer-events: none; - transition: opacity 0.3s ease; - `; document.body.appendChild(notification); @@ -88,20 +112,31 @@ export function useRainbowTheme(): RainbowThemeHook { // Create and show exit notification const notification = document.createElement('div'); notification.id = 'rainbow-exit-notification'; - notification.innerHTML = '🌙 Rainbow mode deactivated'; + + const rootStyles = getComputedStyle(document.documentElement); + const hueValue = rootStyles.getPropertyValue('--rainbow-hue').trim(); + const baseHue = Number.isNaN(Number.parseFloat(hueValue)) + ? 0 + : Number.parseFloat(hueValue); + const exitHue = (baseHue + 200) % 360; + const accentHue = (exitHue + 45) % 360; + + notification.innerHTML = '🌙 Rainbow mode deactivated — back to reality'; notification.style.cssText = ` position: fixed; top: 20px; right: 20px; - background: linear-gradient(45deg, #333, #666); + background: linear-gradient(135deg, hsla(${exitHue}deg, 85%, 40%, 0.85), hsla(${accentHue}deg, 90%, 45%, 0.9)); + background-size: 220% 220%; + animation: rainbowBackground 1.8s ease infinite; color: white; padding: 15px 20px; border-radius: 25px; font-weight: bold; font-size: 16px; z-index: 1000; - border: 2px solid #999; - box-shadow: 0 0 20px rgba(0, 0, 0, 0.3); + border: 2px solid hsla(${accentHue}deg, 95%, 75%, 0.9); + box-shadow: 0 0 25px hsla(${accentHue}deg, 100%, 65%, 0.45); user-select: none; pointer-events: none; transition: opacity 0.3s ease; diff --git a/frontend/src/styles/rainbow.module.css b/frontend/src/styles/rainbow.module.css index d56578a14..c9fac0278 100644 --- a/frontend/src/styles/rainbow.module.css +++ b/frontend/src/styles/rainbow.module.css @@ -1,8 +1,27 @@ /* Rainbow Mode Styles - Easter Egg! */ +:global(:root) { + --rainbow-hue: 0; + --rainbow-angle: -45deg; + --rainbow-sparkle-opacity: 0.45; + --rainbow-glow-strength: 0.4; +} + @keyframes rainbowBackground { - 0% { background-position: 0% 50%; } - 50% { background-position: 100% 50%; } - 100% { background-position: 0% 50%; } + 0% { + background-position: 0% 50%; + } + 25% { + background-position: 50% 0%; + } + 50% { + background-position: 100% 50%; + } + 75% { + background-position: 50% 100%; + } + 100% { + background-position: 0% 50%; + } } @keyframes rainbowBorder { @@ -32,16 +51,102 @@ 50% { transform: scale(1.05); } } +@keyframes rainbowAurora { + 0% { + transform: rotate(0deg) scale(1); + opacity: calc(var(--rainbow-glow-strength) * 0.9); + } + 50% { + transform: rotate(180deg) scale(1.05); + opacity: calc(var(--rainbow-glow-strength) * 1.15); + } + 100% { + transform: rotate(360deg) scale(1); + opacity: calc(var(--rainbow-glow-strength) * 0.9); + } +} + +@keyframes rainbowSparkle { + 0% { + background-position: 0% 0%, 50% 50%, 100% 100%; + opacity: calc(var(--rainbow-sparkle-opacity) * 0.6); + } + 50% { + background-position: 50% 50%, 100% 0%, 0% 100%; + opacity: calc(var(--rainbow-sparkle-opacity) * 1.1); + } + 100% { + background-position: 100% 100%, 0% 0%, 50% 50%; + opacity: calc(var(--rainbow-sparkle-opacity) * 0.6); + } +} + /* Main rainbow theme class */ .rainbowMode { + position: relative; + z-index: 0; + isolation: isolate; background: linear-gradient( - -45deg, - #ff0000, #ff8800, #ffff00, #88ff00, #00ff88, #00ffff, #0088ff, #8800ff, #ff0088, #ff0000 + var(--rainbow-angle), + #ff0000, + #ff8800, + #ffff00, + #88ff00, + #00ff88, + #00ffff, + #0088ff, + #8800ff, + #ff0088, + #ff0000 ) !important; - background-size: 400% 400% !important; + background-size: 500% 500% !important; animation: rainbowBackground 3s ease infinite !important; color: white !important; overflow-x: hidden; + filter: hue-rotate(calc(var(--rainbow-hue) * 1deg)); + box-shadow: inset 0 0 45px rgba(255, 255, 255, 0.25); +} + +.rainbowMode::before, +.rainbowMode::after { + content: ''; + position: fixed; + inset: -40vh; + pointer-events: none; + z-index: -1; + transition: opacity 1s ease; +} + +.rainbowMode::before { + background: conic-gradient( + from calc(var(--rainbow-angle) + 90deg), + rgba(255, 0, 0, 0.5), + rgba(255, 154, 0, 0.5), + rgba(208, 222, 33, 0.5), + rgba(79, 220, 74, 0.5), + rgba(63, 218, 216, 0.5), + rgba(47, 201, 226, 0.5), + rgba(28, 127, 238, 0.5), + rgba(95, 21, 242, 0.5), + rgba(186, 12, 248, 0.5), + rgba(251, 7, 217, 0.5), + rgba(255, 0, 0, 0.5) + ); + mix-blend-mode: screen; + filter: blur(80px); + animation: rainbowAurora 14s linear infinite; + opacity: var(--rainbow-glow-strength); +} + +.rainbowMode::after { + background-image: + radial-gradient(circle at 10% 20%, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0) 45%), + radial-gradient(circle at 80% 30%, rgba(255, 255, 255, 0.75), rgba(255, 255, 255, 0) 40%), + radial-gradient(circle at 30% 75%, rgba(255, 255, 255, 0.6), rgba(255, 255, 255, 0) 35%); + background-size: 250px 250px, 320px 320px, 280px 280px; + mix-blend-mode: screen; + animation: rainbowSparkle 9s ease-in-out infinite; + opacity: var(--rainbow-sparkle-opacity); } /* Rainbow components */ @@ -55,7 +160,13 @@ color: white !important; border: 2px solid !important; border-radius: 15px !important; - box-shadow: 0 0 20px rgba(255, 255, 255, 0.3) !important; + box-shadow: 0 0 25px rgba(255, 255, 255, 0.35) !important; + filter: hue-rotate(calc(var(--rainbow-hue) * 1deg)); + transition: transform 0.4s ease, filter 0.8s ease; +} + +.rainbowCard:hover { + transform: translateY(-6px) scale(1.02); } .rainbowButton { @@ -70,12 +181,15 @@ border-radius: 8px !important; transition: all 0.3s ease !important; font-weight: bold !important; + filter: hue-rotate(calc(var(--rainbow-hue) * 1deg)); + box-shadow: 0 8px 20px rgba(0, 0, 0, 0.25) !important; } .rainbowButton:hover { transform: scale(1.05) !important; animation: rainbowBackground 1s ease infinite, rainbowBorder 0.5s linear infinite, rainbowPulse 0.5s ease infinite !important; box-shadow: 0 0 25px rgba(255, 255, 255, 0.6) !important; + filter: saturate(140%) hue-rotate(calc((var(--rainbow-hue) + 40) * 1deg)); } .rainbowInput { @@ -88,6 +202,9 @@ border: 2px solid !important; color: white !important; border-radius: 8px !important; + filter: hue-rotate(calc(var(--rainbow-hue) * 1deg)); + box-shadow: inset 0 0 12px rgba(255, 255, 255, 0.25) !important; + transition: filter 0.6s ease; } .rainbowInput::placeholder { @@ -98,6 +215,7 @@ animation: rainbowText 3s linear infinite !important; font-weight: bold !important; text-shadow: 0 0 10px currentColor !important; + filter: hue-rotate(calc(var(--rainbow-hue) * 1deg)); } .rainbowSegmentedControl { @@ -110,6 +228,8 @@ border: 2px solid !important; border-radius: 12px !important; padding: 4px !important; + filter: hue-rotate(calc(var(--rainbow-hue) * 1deg)); + box-shadow: 0 0 18px rgba(255, 255, 255, 0.35) !important; } .rainbowPaper { @@ -122,6 +242,7 @@ border: 2px solid !important; color: white !important; border-radius: 12px !important; + filter: hue-rotate(calc(var(--rainbow-hue) * 1deg)); } /* Easter egg notification */ @@ -129,7 +250,12 @@ position: fixed !important; top: 20px !important; right: 20px !important; - background: linear-gradient(45deg,#ffff00, #88ff00, #00ff88, #00ffff) !important; + background: linear-gradient( + 135deg, + hsla(calc(var(--rainbow-hue) * 1deg), 100%, 65%, 0.95), + hsla(calc((var(--rainbow-hue) + 60) * 1deg), 100%, 70%, 0.95), + hsla(calc((var(--rainbow-hue) + 120) * 1deg), 100%, 72%, 0.95) + ) !important; background-size: 300% 300% !important; animation: rainbowBackground 1s ease infinite, rainbowBorder 0.5s linear infinite !important; color: white !important; @@ -142,22 +268,24 @@ box-shadow: 0 0 20px rgba(255, 255, 255, 0.8) !important; user-select: none !important; pointer-events: none !important; + transition: opacity 0.4s ease !important; } /* Specific component overrides */ .rainbowMode [data-mantine-color-scheme] { background: linear-gradient( - -45deg, + var(--rainbow-angle), #ff0000, #ff8800, #ffff00, #88ff00, #00ff88, #00ffff, #0088ff, #8800ff, #ff0088, #ff0000 ) !important; background-size: 400% 400% !important; animation: rainbowBackground 3s ease infinite !important; + filter: hue-rotate(calc(var(--rainbow-hue) * 1deg)); } /* Make all buttons rainbow in rainbow mode */ .rainbowMode button { background: linear-gradient( - 45deg, + calc(var(--rainbow-angle) + 45deg), #ffff00, #88ff00, #00ff88, #00ffff ) !important; background-size: 100% 100% !important; @@ -166,6 +294,8 @@ animation: rainbowBackground 2s ease infinite, rainbowBorder 1.5s linear infinite !important; color: white !important; font-weight: bold !important; + filter: hue-rotate(calc(var(--rainbow-hue) * 1deg)); + box-shadow: 0 4px 14px rgba(0, 0, 0, 0.25) !important; } /* Make all inputs rainbow in rainbow mode */ @@ -173,7 +303,7 @@ .rainbowMode select, .rainbowMode textarea { background: linear-gradient( - 90deg, + calc(var(--rainbow-angle) + 90deg), #ffff00, #88ff00, #00ff88, #00ffff ) !important; background-size: 100% 100% !important; @@ -181,6 +311,8 @@ border: 2px solid !important; animation: rainbowBackground 2.5s ease infinite, rainbowBorder 1.5s linear infinite !important; color: white !important; + filter: hue-rotate(calc(var(--rainbow-hue) * 1deg)); + box-shadow: inset 0 0 12px rgba(255, 255, 255, 0.25) !important; } /* Rainbow text class */ @@ -188,6 +320,7 @@ animation: rainbowText 3s linear infinite !important; font-weight: bold !important; text-shadow: 0 0 10px currentColor !important; + filter: hue-rotate(calc(var(--rainbow-hue) * 1deg)); } /* Make all text rainbow */ @@ -199,4 +332,5 @@ .rainbowMode h6 { animation: rainbowText 3s linear infinite !important; font-weight: bold !important; + filter: hue-rotate(calc(var(--rainbow-hue) * 1deg)); }