From 771781e2c61b42e4bda464847edfd92ebf84d1fd Mon Sep 17 00:00:00 2001 From: Rivka Weiss Date: Mon, 12 Jan 2026 09:03:43 +0200 Subject: [PATCH 1/2] feat: add guest mode for playing without OAuth authentication MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add 'Play as Guest' button in Login.jsx with guest user creation - Make Google OAuth optional in index.jsx (app works without VITE_GOOGLE_CLIENT_ID) - Guest users get default stats (100 coins, 100 energy, 0 score) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- client/src/index.jsx | 43 +++++++++++++++++++++++++++--------- client/src/pages/Login.jsx | 45 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 76 insertions(+), 12 deletions(-) diff --git a/client/src/index.jsx b/client/src/index.jsx index 47817f5..e233b20 100644 --- a/client/src/index.jsx +++ b/client/src/index.jsx @@ -1,22 +1,45 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; -import './styles/global.css'; +import { BrowserRouter, Routes, Route } from 'react-router-dom'; +import './styles/global.css'; import { GoogleOAuthProvider } from '@react-oauth/google'; -import {GameProvider} from '../src/context/GameContext'; +import { GameProvider } from './context/GameContext'; +import { ThemeProvider } from './context/ThemeContext'; +import HomePage from './pages/HomePage'; import App from './App'; -// הכניסי את המפתח שלך בקובץ .env בתיקיית client כך: -// VITE_GOOGLE_CLIENT_ID=YOUR_NEW_CLIENT_ID_HERE.apps.googleusercontent.com - const GOOGLE_CLIENT_ID = import.meta.env.VITE_GOOGLE_CLIENT_ID; const root = ReactDOM.createRoot(document.getElementById('root')); + +const AppRouter = () => ( + + + + } /> + + + + + + ) : ( + + + + ) + } + /> + + + +); + root.render( - - - - - + ); \ No newline at end of file diff --git a/client/src/pages/Login.jsx b/client/src/pages/Login.jsx index 5ac2a07..110af48 100644 --- a/client/src/pages/Login.jsx +++ b/client/src/pages/Login.jsx @@ -7,7 +7,10 @@ const Login = ({ onClose, onLoginSuccess }) => { const [showMessage, setShowMessage] = useState(''); const [isLoading, setIsLoading] = useState(false); - const googleLogin = useGoogleLogin({ + // Check if Google OAuth is available + const hasGoogleAuth = Boolean(import.meta.env.VITE_GOOGLE_CLIENT_ID); + + const googleLogin = hasGoogleAuth ? useGoogleLogin({ onSuccess: async (tokenResponse) => { try { setIsLoading(true); @@ -67,9 +70,14 @@ const Login = ({ onClose, onLoginSuccess }) => { setShowMessage('Google login failed. Please try again.'); setTimeout(() => setShowMessage(''), 3000); }, - }); + }) : null; const handleGoogleLogin = () => { + if (!hasGoogleAuth) { + setShowMessage('Google login is not configured. Please use Guest mode.'); + setTimeout(() => setShowMessage(''), 3000); + return; + } googleLogin(); }; @@ -83,6 +91,22 @@ const Login = ({ onClose, onLoginSuccess }) => { setTimeout(() => setShowMessage(''), 3000); }; + const handleGuestLogin = () => { + const guestUser = { + id: 'guest-' + Date.now(), + username: 'Guest Player', + email: 'guest@localhost', + score: 0, + coins: 100, + energy: 100 + }; + + console.log('🎮 Guest login:', guestUser); + onLoginSuccess(guestUser, ''); + setShowMessage('Welcome, Guest! 🎮'); + setTimeout(() => onClose(), 1000); + }; + return (
e.stopPropagation()}> @@ -132,6 +156,23 @@ const Login = ({ onClose, onLoginSuccess }) => { Continue with Instagram
+
+ or +
+
+ +

By continuing, you agree to our Terms of Service and Privacy Policy

From ab207a1e4268298b77c52fae9adeca2837d2c08a Mon Sep 17 00:00:00 2001 From: Rivka Weiss Date: Mon, 12 Jan 2026 09:11:49 +0200 Subject: [PATCH 2/2] feat: add modern home page with dark/light theme and animations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement a professional landing page for Netropolise cybersecurity game with: - React Router integration for / (home) and /game routes - Dark/light theme toggle with ThemeContext and localStorage persistence - ShaderGradient 3D animated backgrounds - Anime.js micro-animations for smooth entrance effects - Responsive navbar with colorful gradient accents - Hero section with Orbitron font (cyberpunk aesthetic) - About section with feature cards showcasing game capabilities - Demo section with video placeholder - Footer with contact information and navigation links - Colorful accents added to in-game sidebar navigation Dependencies added: - react-router-dom@6 for client-side routing - @shadergradient/react for 3D gradient backgrounds - animejs@3 for UI animations Design features: - Orbitron font for all text (tech/cyberpunk theme) - Multi-color gradient borders (cyan, purple, pink, yellow) - Glassmorphism effects with backdrop blur - Smooth theme transitions without animation retriggering - Mobile-responsive layout 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- client/index.html | 2 +- client/package-lock.json | 60 +++++ client/package.json | 3 + .../SideNavigation/SideNavigation.module.css | 119 +++++++-- client/src/components/home/AboutSection.jsx | 108 ++++++++ .../components/home/AboutSection.module.css | 98 ++++++++ client/src/components/home/DemoSection.jsx | 75 ++++++ .../components/home/DemoSection.module.css | 118 +++++++++ client/src/components/home/HeroSection.jsx | 106 ++++++++ .../components/home/HeroSection.module.css | 113 +++++++++ client/src/components/layout/Footer.jsx | 70 ++++++ .../src/components/layout/Footer.module.css | 112 +++++++++ client/src/components/layout/Navbar.jsx | 76 ++++++ .../src/components/layout/Navbar.module.css | 233 ++++++++++++++++++ client/src/context/ThemeContext.jsx | 41 +++ client/src/pages/HomePage.jsx | 20 ++ client/src/pages/HomePage.module.css | 11 + client/src/styles/global.css | 66 ++++- 18 files changed, 1409 insertions(+), 22 deletions(-) create mode 100644 client/src/components/home/AboutSection.jsx create mode 100644 client/src/components/home/AboutSection.module.css create mode 100644 client/src/components/home/DemoSection.jsx create mode 100644 client/src/components/home/DemoSection.module.css create mode 100644 client/src/components/home/HeroSection.jsx create mode 100644 client/src/components/home/HeroSection.module.css create mode 100644 client/src/components/layout/Footer.jsx create mode 100644 client/src/components/layout/Footer.module.css create mode 100644 client/src/components/layout/Navbar.jsx create mode 100644 client/src/components/layout/Navbar.module.css create mode 100644 client/src/context/ThemeContext.jsx create mode 100644 client/src/pages/HomePage.jsx create mode 100644 client/src/pages/HomePage.module.css diff --git a/client/index.html b/client/index.html index ece7b61..5c7cd34 100644 --- a/client/index.html +++ b/client/index.html @@ -11,7 +11,7 @@ - netropolis + Netropolise
diff --git a/client/package-lock.json b/client/package-lock.json index cbda6c1..9370492 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -15,12 +15,15 @@ "@react-three/drei": "^10.7.7", "@react-three/fiber": "^9.5.0", "@react-three/postprocessing": "^3.0.4", + "@shadergradient/react": "^2.4.20", + "animejs": "^3.2.2", "axios": "^1.13.2", "postprocessing": "^6.38.2", "prop-types": "^15.8.1", "react": "^19.2.0", "react-dom": "^19.2.0", "react-router": "^7.9.6", + "react-router-dom": "^6.30.3", "three": "^0.182.0" }, "devDependencies": { @@ -1209,6 +1212,15 @@ "three": ">=0.144.0" } }, + "node_modules/@remix-run/router": { + "version": "1.23.2", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz", + "integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.47", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.47.tgz", @@ -1531,6 +1543,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@shadergradient/react": { + "version": "2.4.20", + "resolved": "https://registry.npmjs.org/@shadergradient/react/-/react-2.4.20.tgz", + "integrity": "sha512-MVYvYgTHK3d36C2jNKzt4OzWeyNyc/bWI0zKRyv5EgI2hmUee8nD0cmuNX9X1lw1RCcuof6n1Rnj63jWepZHJA==", + "license": "MIT", + "peerDependencies": { + "react": "^18.2.0 || ^19.0.0", + "react-dom": "^18.2.0 || ^19.0.0" + } + }, "node_modules/@tweenjs/tween.js": { "version": "23.1.3", "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.3.tgz", @@ -1737,6 +1759,12 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/animejs": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/animejs/-/animejs-3.2.2.tgz", + "integrity": "sha512-Ao95qWLpDPXXM+WrmwcKbl6uNlC5tjnowlaRYtuVDHHoygjtIPfDUoK9NthrlZsQSKjZXlmji2TrBUAVbiH0LQ==", + "license": "MIT" + }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -4763,6 +4791,38 @@ } } }, + "node_modules/react-router-dom": { + "version": "6.30.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.3.tgz", + "integrity": "sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.2", + "react-router": "6.30.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/react-router-dom/node_modules/react-router": { + "version": "6.30.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.3.tgz", + "integrity": "sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, "node_modules/react-use-measure": { "version": "2.1.7", "resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.7.tgz", diff --git a/client/package.json b/client/package.json index ef33af1..cff5bfd 100644 --- a/client/package.json +++ b/client/package.json @@ -17,12 +17,15 @@ "@react-three/drei": "^10.7.7", "@react-three/fiber": "^9.5.0", "@react-three/postprocessing": "^3.0.4", + "@shadergradient/react": "^2.4.20", + "animejs": "^3.2.2", "axios": "^1.13.2", "postprocessing": "^6.38.2", "prop-types": "^15.8.1", "react": "^19.2.0", "react-dom": "^19.2.0", "react-router": "^7.9.6", + "react-router-dom": "^6.30.3", "three": "^0.182.0" }, "devDependencies": { diff --git a/client/src/components/common/SideNavigation/SideNavigation.module.css b/client/src/components/common/SideNavigation/SideNavigation.module.css index be01daf..7fd1c64 100644 --- a/client/src/components/common/SideNavigation/SideNavigation.module.css +++ b/client/src/components/common/SideNavigation/SideNavigation.module.css @@ -13,11 +13,20 @@ background: color-mix(in srgb, white 46%, transparent); backdrop-filter: blur(16px) saturate(1.25); -webkit-backdrop-filter: blur(16px) saturate(1.25); - border: 1px solid color-mix(in srgb, white 34%, transparent); + border: 2px solid transparent; + border-image: linear-gradient( + 180deg, + rgba(0, 242, 255, 0.4), + rgba(112, 0, 255, 0.3), + rgba(255, 0, 85, 0.3), + rgba(255, 237, 78, 0.4) + ); + border-image-slice: 1; box-shadow: 0 18px 44px color-mix(in srgb, var(--neo-text) 18%, transparent), - 0 0 0 1px color-mix(in srgb, var(--candy-sky) 16%, transparent) inset, - 0 0 26px color-mix(in srgb, var(--candy-sky) 14%, transparent); + 0 0 30px rgba(0, 242, 255, 0.15), + 0 0 30px rgba(112, 0, 255, 0.12), + 0 0 30px rgba(255, 237, 78, 0.1); display: flex; flex-direction: column; animation: panelIn 520ms cubic-bezier(0.2, 0.8, 0.2, 1) both; @@ -103,6 +112,10 @@ .header .title { font-size: 14px; letter-spacing: 0.06em; + background: linear-gradient(90deg, rgba(0, 242, 255, 0.9), rgba(112, 0, 255, 0.9)); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; } .title { @@ -186,24 +199,39 @@ padding: 6px 10px; border-radius: 999px; background: color-mix(in srgb, white 24%, transparent); - border: 1px solid color-mix(in srgb, var(--candy-sky) 32%, transparent); + border: 1.5px solid transparent; + background-clip: padding-box; + position: relative; color: color-mix(in srgb, var(--neo-text) 92%, transparent); font-size: 13px; font-weight: 700; box-shadow: 0 0 0 1px color-mix(in srgb, white 30%, transparent) inset, 0 14px 28px color-mix(in srgb, var(--neo-text) 12%, transparent), - 0 0 18px color-mix(in srgb, var(--candy-sky) 10%, transparent); + 0 0 18px rgba(0, 242, 255, 0.15); cursor: pointer; transition: transform 160ms ease, box-shadow 160ms ease, border-color 160ms ease, background 160ms ease; } +.kpi::before { + content: ''; + position: absolute; + inset: -1.5px; + border-radius: 999px; + padding: 1.5px; + background: linear-gradient(135deg, rgba(0, 242, 255, 0.5), rgba(112, 0, 255, 0.4)); + -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); + -webkit-mask-composite: xor; + mask-composite: exclude; +} + .kpi:hover { transform: translateY(-1px) scale(1.03); box-shadow: - 0 0 0 1px color-mix(in srgb, var(--candy-sky) 40%, transparent) inset, + 0 0 0 1px rgba(0, 242, 255, 0.3) inset, 0 18px 36px color-mix(in srgb, var(--neo-text) 18%, transparent), - 0 0 22px color-mix(in srgb, var(--candy-sky) 22%, transparent); + 0 0 25px rgba(0, 242, 255, 0.35), + 0 0 25px rgba(112, 0, 255, 0.25); } .kpi:focus-visible { @@ -229,20 +257,35 @@ border-radius: 22px; padding: 9px; background: color-mix(in srgb, white 22%, transparent); - border: 1px solid color-mix(in srgb, var(--candy-sky) 18%, transparent); + border: 1.5px solid transparent; + background-clip: padding-box; + position: relative; box-shadow: 0 14px 26px color-mix(in srgb, var(--neo-text) 12%, transparent), - 0 0 18px color-mix(in srgb, var(--candy-sky) 10%, transparent); + 0 0 18px rgba(0, 242, 255, 0.12), + 0 0 18px rgba(255, 237, 78, 0.1); transition: transform 160ms ease, box-shadow 160ms ease, border-color 160ms ease; } +.gestureCard::before { + content: ''; + position: absolute; + inset: -1.5px; + border-radius: 22px; + padding: 1.5px; + background: linear-gradient(135deg, rgba(0, 242, 255, 0.4), rgba(255, 237, 78, 0.4)); + -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); + -webkit-mask-composite: xor; + mask-composite: exclude; +} + .gestureCard:hover { transform: translateY(-1px) scale(1.02); - border-color: color-mix(in srgb, var(--candy-sky) 34%, transparent); box-shadow: - 0 0 0 1px color-mix(in srgb, var(--candy-sky) 26%, transparent) inset, + 0 0 0 1px rgba(0, 242, 255, 0.2) inset, 0 18px 34px color-mix(in srgb, var(--neo-text) 16%, transparent), - 0 0 22px color-mix(in srgb, var(--candy-sky) 18%, transparent); + 0 0 25px rgba(0, 242, 255, 0.25), + 0 0 25px rgba(255, 237, 78, 0.2); } .gestureName { @@ -266,10 +309,14 @@ } .active { - border-color: color-mix(in srgb, var(--candy-sky) 55%, transparent); box-shadow: - 0 0 0 1px color-mix(in srgb, var(--candy-sky) 28%, transparent) inset, - 0 0 18px color-mix(in srgb, var(--candy-sky) 26%, transparent); + 0 0 0 1px rgba(0, 242, 255, 0.4) inset, + 0 0 30px rgba(0, 242, 255, 0.4), + 0 0 30px rgba(112, 0, 255, 0.3); +} + +.active::before { + background: linear-gradient(135deg, rgba(0, 242, 255, 0.7), rgba(112, 0, 255, 0.6)); } .iconWrap { @@ -279,10 +326,25 @@ display: grid; place-items: center; background: color-mix(in srgb, white 18%, transparent); - border: 1px solid color-mix(in srgb, var(--candy-lemon) 24%, transparent); + border: 1.5px solid transparent; + background-clip: padding-box; + position: relative; box-shadow: 0 10px 18px color-mix(in srgb, var(--neo-text) 10%, transparent), - 0 0 14px color-mix(in srgb, var(--candy-lemon) 10%, transparent); + 0 0 15px rgba(255, 237, 78, 0.2), + 0 0 15px rgba(255, 0, 85, 0.15); +} + +.iconWrap::before { + content: ''; + position: absolute; + inset: -1.5px; + border-radius: 18px; + padding: 1.5px; + background: linear-gradient(135deg, rgba(255, 237, 78, 0.6), rgba(255, 0, 85, 0.4)); + -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); + -webkit-mask-composite: xor; + mask-composite: exclude; } .footer { @@ -359,8 +421,27 @@ height: 10px; transform: translateY(-50%); border-radius: 999px; - background: color-mix(in srgb, var(--candy-sky) 55%, white); - box-shadow: 0 0 0 3px color-mix(in srgb, white 75%, transparent); + background: linear-gradient(135deg, rgba(0, 242, 255, 0.9), rgba(112, 0, 255, 0.8)); + box-shadow: + 0 0 0 3px color-mix(in srgb, white 75%, transparent), + 0 0 10px rgba(0, 242, 255, 0.5), + 0 0 10px rgba(112, 0, 255, 0.4); + animation: mapPinPulse 2s ease-in-out infinite; +} + +@keyframes mapPinPulse { + 0%, 100% { + box-shadow: + 0 0 0 3px color-mix(in srgb, white 75%, transparent), + 0 0 10px rgba(0, 242, 255, 0.5), + 0 0 10px rgba(112, 0, 255, 0.4); + } + 50% { + box-shadow: + 0 0 0 3px color-mix(in srgb, white 75%, transparent), + 0 0 15px rgba(0, 242, 255, 0.7), + 0 0 15px rgba(112, 0, 255, 0.6); + } } .mapPin::after { diff --git a/client/src/components/home/AboutSection.jsx b/client/src/components/home/AboutSection.jsx new file mode 100644 index 0000000..7b23892 --- /dev/null +++ b/client/src/components/home/AboutSection.jsx @@ -0,0 +1,108 @@ +import { useRef, useEffect } from 'react'; +import anime from 'animejs'; +import styles from './AboutSection.module.css'; + +export default function AboutSection() { + const sectionRef = useRef(null); + const titleRef = useRef(null); + const cardsRef = useRef(null); + const hasAnimated = useRef(false); + + useEffect(() => { + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting && !hasAnimated.current) { + hasAnimated.current = true; + + anime({ + targets: titleRef.current, + translateY: [40, 0], + opacity: [0, 1], + duration: 800, + easing: 'easeOutExpo', + }); + + anime({ + targets: cardsRef.current?.children, + translateY: [60, 0], + opacity: [0, 1], + duration: 800, + delay: anime.stagger(150), + easing: 'easeOutExpo', + }); + + observer.unobserve(entry.target); + } + }); + }, + { threshold: 0.2 } + ); + + if (sectionRef.current) { + observer.observe(sectionRef.current); + } + + return () => observer.disconnect(); + }, []); + + const features = [ + { + icon: '🔐', + title: 'Password Security', + description: 'Learn to create strong passwords and understand common vulnerabilities through interactive challenges.', + }, + { + icon: '🛡️', + title: 'Privacy Protection', + description: 'Master data protection techniques and learn how to keep your personal information safe online.', + }, + { + icon: '🎮', + title: 'Gesture Controls', + description: 'Navigate through challenges using hand gestures powered by MediaPipe technology.', + }, + { + icon: '🤖', + title: 'AI Companions', + description: 'Collect unique AI-generated robot companions to help you on your cybersecurity journey.', + }, + { + icon: '🏆', + title: 'Earn Rewards', + description: 'Gain points, coins, badges, and unlock new areas as you master security concepts.', + }, + { + icon: '🌐', + title: 'Immersive 3D World', + description: 'Explore cyberpunk environments built with Three.js in a visually stunning game world.', + }, + ]; + + return ( +
+
+

+ What is Netropolise? +

+ +

+ Netropolise is an immersive cybersecurity education game that transforms + complex security concepts into engaging, interactive challenges. Learn to + protect yourself in the digital world while playing through a cyberpunk + adventure. +

+ +
+ {features.map((feature, index) => ( +
+
{feature.icon}
+

{feature.title}

+

{feature.description}

+
+ ))} +
+
+
+ ); +} diff --git a/client/src/components/home/AboutSection.module.css b/client/src/components/home/AboutSection.module.css new file mode 100644 index 0000000..d6ff2b2 --- /dev/null +++ b/client/src/components/home/AboutSection.module.css @@ -0,0 +1,98 @@ +.aboutSection { + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + padding: 100px 24px; + background: var(--neo-bg); +} + +.container { + max-width: 1200px; + width: 100%; + margin: 0 auto; +} + +.sectionTitle { + font-family: 'Orbitron', sans-serif; + font-size: clamp(42px, 6vw, 64px); + font-weight: 800; + color: var(--neo-text); + text-align: center; + margin: 0 0 30px; + letter-spacing: -0.5px; + text-transform: uppercase; +} + +.intro { + font-family: 'Orbitron', sans-serif; + font-size: clamp(16px, 2.2vw, 19px); + font-weight: 400; + color: var(--neo-text-dim); + text-align: center; + max-width: 850px; + margin: 0 auto 60px; + line-height: 1.8; + letter-spacing: -0.2px; +} + +.featuresGrid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 24px; + margin-top: 60px; +} + +.featureCard { + background: var(--glass-bg); + backdrop-filter: blur(16px); + -webkit-backdrop-filter: blur(16px); + border: 1px solid var(--glass-border); + border-radius: 20px; + padding: 32px; + transition: transform 0.3s, box-shadow 0.3s; +} + +.featureCard:hover { + transform: translateY(-8px); + box-shadow: 0 0 40px rgba(0, 242, 255, 0.2), 0 20px 40px rgba(0, 0, 0, 0.2); +} + +.icon { + font-size: 48px; + margin-bottom: 16px; +} + +.featureTitle { + font-family: 'Orbitron', sans-serif; + font-size: 20px; + font-weight: 700; + color: var(--neo-text); + margin: 0 0 12px; + letter-spacing: -0.3px; +} + +.featureDescription { + font-family: 'Orbitron', sans-serif; + font-size: 14px; + font-weight: 400; + color: var(--neo-text-dim); + line-height: 1.7; + margin: 0; + letter-spacing: -0.2px; +} + +@media (max-width: 768px) { + .aboutSection { + padding: 80px 16px; + } + + .featuresGrid { + grid-template-columns: 1fr; + gap: 16px; + } + + .featureCard { + padding: 24px; + } +} diff --git a/client/src/components/home/DemoSection.jsx b/client/src/components/home/DemoSection.jsx new file mode 100644 index 0000000..53ffe1a --- /dev/null +++ b/client/src/components/home/DemoSection.jsx @@ -0,0 +1,75 @@ +import { useRef, useEffect } from 'react'; +import anime from 'animejs'; +import styles from './DemoSection.module.css'; + +export default function DemoSection() { + const sectionRef = useRef(null); + const videoRef = useRef(null); + const titleRef = useRef(null); + const hasAnimated = useRef(false); + + useEffect(() => { + // Intersection Observer for scroll animation + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting && !hasAnimated.current) { + hasAnimated.current = true; + + anime({ + targets: titleRef.current, + translateY: [40, 0], + opacity: [0, 1], + duration: 800, + easing: 'easeOutExpo', + }); + + anime({ + targets: videoRef.current, + translateY: [60, 0], + opacity: [0, 1], + duration: 1000, + delay: 200, + easing: 'easeOutExpo', + }); + + observer.unobserve(entry.target); + } + }); + }, + { threshold: 0.2 } + ); + + if (sectionRef.current) { + observer.observe(sectionRef.current); + } + + return () => observer.disconnect(); + }, []); + + return ( +
+
+

+ See It In Action +

+ +
+ {/* Video placeholder - replace with actual video later */} +
+
+ +
+

Demo Video Coming Soon

+
+
+ +

+ Watch how players navigate through cyberpunk environments, solve security + challenges using hand gestures, and earn rewards for mastering digital + safety skills. +

+
+
+ ); +} diff --git a/client/src/components/home/DemoSection.module.css b/client/src/components/home/DemoSection.module.css new file mode 100644 index 0000000..b0c979e --- /dev/null +++ b/client/src/components/home/DemoSection.module.css @@ -0,0 +1,118 @@ +.demoSection { + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + padding: 80px 24px; + background: var(--neo-bg); +} + +.container { + max-width: 1200px; + width: 100%; + margin: 0 auto; +} + +.sectionTitle { + font-family: 'Orbitron', sans-serif; + font-size: clamp(42px, 6vw, 64px); + font-weight: 800; + color: var(--neo-text); + text-align: center; + margin: 0 0 60px; + background: linear-gradient(135deg, var(--neo-cyan), var(--neo-purple)); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + letter-spacing: -0.5px; + text-transform: uppercase; +} + +.videoWrapper { + position: relative; + width: 100%; + max-width: 900px; + margin: 0 auto 40px; + border-radius: 24px; + overflow: hidden; +} + +.videoPlaceholder { + position: relative; + width: 100%; + aspect-ratio: 16 / 9; + background: linear-gradient(135deg, rgba(0, 242, 255, 0.1), rgba(112, 0, 255, 0.1)); + border: 2px solid var(--neo-border); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 20px; + border-radius: 24px; + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); +} + +.playButton { + width: 80px; + height: 80px; + border-radius: 50%; + background: linear-gradient(135deg, var(--neo-cyan), var(--neo-purple)); + display: grid; + place-items: center; + cursor: pointer; + box-shadow: var(--neo-glow-cyan), var(--neo-glow-purple); + transition: transform 0.3s, box-shadow 0.3s; +} + +.playButton:hover { + transform: scale(1.1); + box-shadow: 0 0 50px rgba(0, 242, 255, 0.6), 0 0 70px rgba(112, 0, 255, 0.5); +} + +.playIcon { + font-size: 32px; + color: white; + margin-left: 4px; +} + +.placeholderText { + font-family: 'Orbitron', sans-serif; + font-size: 22px; + font-weight: 700; + color: var(--neo-text); + margin: 0; + letter-spacing: -0.3px; + text-transform: uppercase; +} + +.description { + font-family: 'Orbitron', sans-serif; + font-size: clamp(14px, 1.8vw, 18px); + font-weight: 400; + color: var(--neo-text-dim); + text-align: center; + max-width: 750px; + margin: 0 auto; + line-height: 1.8; + letter-spacing: -0.2px; +} + +@media (max-width: 768px) { + .demoSection { + padding: 60px 16px; + } + + .playButton { + width: 60px; + height: 60px; + } + + .playIcon { + font-size: 24px; + } + + .placeholderText { + font-size: 18px; + } +} diff --git a/client/src/components/home/HeroSection.jsx b/client/src/components/home/HeroSection.jsx new file mode 100644 index 0000000..0b8f3b7 --- /dev/null +++ b/client/src/components/home/HeroSection.jsx @@ -0,0 +1,106 @@ +import { useRef, useEffect, useMemo } from 'react'; +import { ShaderGradientCanvas, ShaderGradient } from '@shadergradient/react'; +import anime from 'animejs'; +import { useNavigate } from 'react-router-dom'; +import { useTheme } from '../../context/ThemeContext'; +import styles from './HeroSection.module.css'; + +export default function HeroSection() { + const { isDark } = useTheme(); + const navigate = useNavigate(); + const titleRef = useRef(null); + const subtitleRef = useRef(null); + const ctaRef = useRef(null); + const hasAnimated = useRef(false); + + // Memoize gradient URL to prevent unnecessary re-renders + const gradientUrl = useMemo(() => { + return isDark + ? 'https://www.shadergradient.co/customize?animate=on&axesHelper=off&bgColor1=%230a0e1a&bgColor2=%2300f2ff&brightness=1.2&cAzimuthAngle=180&cDistance=3.6&cPolarAngle=90&cameraZoom=1&color1=%230a0e1a&color2=%2300f2ff&color3=%237000ff&destination=onCanvas&embedMode=off&envPreset=city&format=gif&fov=45&frameRate=10&gizmoHelper=hide&grain=on&lightType=3d&pixelDensity=1&positionX=-1.4&positionY=0&positionZ=0&range=enabled&rangeEnd=40&rangeStart=0&reflection=0.1&rotationX=0&rotationY=10&rotationZ=50&shader=defaults&type=waterPlane&uAmplitude=0&uDensity=1.3&uFrequency=5.5&uSpeed=0.4&uStrength=4&uTime=0&wireframe=false' + : 'https://www.shadergradient.co/customize?animate=on&axesHelper=off&bgColor1=%23fff7fd&bgColor2=%2387CEEB&brightness=1.2&cAzimuthAngle=180&cDistance=3.6&cPolarAngle=90&cameraZoom=1&color1=%23fff7fd&color2=%2387CEEB&color3=%23D7B7FF&destination=onCanvas&embedMode=off&envPreset=city&format=gif&fov=45&frameRate=10&gizmoHelper=hide&grain=on&lightType=3d&pixelDensity=1&positionX=-1.4&positionY=0&positionZ=0&range=enabled&rangeEnd=40&rangeStart=0&reflection=0.1&rotationX=0&rotationY=10&rotationZ=50&shader=defaults&type=waterPlane&uAmplitude=0&uDensity=1.3&uFrequency=5.5&uSpeed=0.4&uStrength=4&uTime=0&wireframe=false'; + }, [isDark]); + + useEffect(() => { + // Only animate once on mount + if (hasAnimated.current) return; + hasAnimated.current = true; + + // Staggered text animation + const timeline = anime.timeline({ + easing: 'easeOutExpo', + }); + + timeline + .add({ + targets: titleRef.current, + translateY: [50, 0], + opacity: [0, 1], + duration: 1000, + }) + .add( + { + targets: subtitleRef.current, + translateY: [30, 0], + opacity: [0, 1], + duration: 800, + }, + '-=600' + ) + .add( + { + targets: ctaRef.current, + scale: [0.8, 1], + opacity: [0, 1], + duration: 600, + }, + '-=400' + ); + }, []); + + const handleCtaClick = () => { + // Animate before navigation + anime({ + targets: ctaRef.current, + scale: [1, 0.95, 1.05], + duration: 300, + easing: 'easeInOutQuad', + complete: () => navigate('/game'), + }); + }; + + return ( +
+ {/* 3D Gradient Background */} +
+ + + +
+ + {/* Content Overlay */} +
+

+ Learn Cybersecurity +
+ Through Play +

+ +

+ Master password security, privacy protection, and digital safety in an + immersive 3D game world with gesture controls. +

+ + +
+
+ ); +} diff --git a/client/src/components/home/HeroSection.module.css b/client/src/components/home/HeroSection.module.css new file mode 100644 index 0000000..a3d1574 --- /dev/null +++ b/client/src/components/home/HeroSection.module.css @@ -0,0 +1,113 @@ +.hero { + position: relative; + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; +} + +.gradientWrapper { + position: absolute; + inset: 0; + z-index: 0; + transition: opacity 0.3s ease; +} + +.gradientWrapper canvas { + width: 100% !important; + height: 100% !important; +} + +.content { + position: relative; + z-index: 10; + text-align: center; + padding: 0 24px; + max-width: 900px; +} + +.title { + font-family: 'Orbitron', sans-serif; + font-size: clamp(48px, 8vw, 84px); + font-weight: 800; + color: var(--neo-text); + line-height: 1.1; + margin: 0 0 24px; + text-shadow: 0 4px 30px rgba(0, 0, 0, 0.3); + will-change: transform, opacity; + letter-spacing: -1px; + text-transform: uppercase; +} + +.highlight { + background: linear-gradient(135deg, var(--neo-cyan), var(--neo-purple)); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + filter: drop-shadow(0 0 20px rgba(0, 242, 255, 0.5)); + font-weight: 900; +} + +.subtitle { + font-family: 'Orbitron', sans-serif; + font-size: clamp(16px, 2.2vw, 20px); + font-weight: 400; + color: var(--neo-text-dim); + max-width: 650px; + margin: 0 auto 40px; + line-height: 1.7; + will-change: transform, opacity; + letter-spacing: -0.2px; +} + +.ctaButton { + display: inline-flex; + align-items: center; + gap: 12px; + padding: 18px 40px; + border-radius: 999px; + border: none; + background: linear-gradient(135deg, var(--neo-cyan), var(--neo-purple)); + color: white; + font-family: 'Orbitron', sans-serif; + font-size: 18px; + font-weight: 700; + cursor: pointer; + box-shadow: 0 0 40px rgba(0, 242, 255, 0.4), 0 0 60px rgba(112, 0, 255, 0.3), + 0 20px 40px rgba(0, 0, 0, 0.2); + transition: transform 0.3s, box-shadow 0.3s; + will-change: transform, opacity; + letter-spacing: -0.3px; + text-transform: uppercase; +} + +.ctaButton:hover { + transform: translateY(-4px) scale(1.02); + box-shadow: 0 0 60px rgba(0, 242, 255, 0.6), 0 0 80px rgba(112, 0, 255, 0.5), + 0 30px 60px rgba(0, 0, 0, 0.3); +} + +.ctaArrow { + font-size: 24px; + transition: transform 0.3s; +} + +.ctaButton:hover .ctaArrow { + transform: translateX(6px); +} + +@media (max-width: 768px) { + .hero { + padding-top: 80px; + } + + .content { + padding: 0 16px; + } + + .ctaButton { + font-size: 18px; + padding: 16px 32px; + } +} diff --git a/client/src/components/layout/Footer.jsx b/client/src/components/layout/Footer.jsx new file mode 100644 index 0000000..ab038f6 --- /dev/null +++ b/client/src/components/layout/Footer.jsx @@ -0,0 +1,70 @@ +import { useNavigate } from 'react-router-dom'; +import styles from './Footer.module.css'; + +export default function Footer() { + const navigate = useNavigate(); + const currentYear = new Date().getFullYear(); + + return ( + + ); +} diff --git a/client/src/components/layout/Footer.module.css b/client/src/components/layout/Footer.module.css new file mode 100644 index 0000000..32bc05b --- /dev/null +++ b/client/src/components/layout/Footer.module.css @@ -0,0 +1,112 @@ +.footer { + background: var(--glass-bg); + backdrop-filter: blur(16px); + -webkit-backdrop-filter: blur(16px); + border-top: 1px solid var(--glass-border); + padding: 60px 24px 30px; +} + +.container { + max-width: 1200px; + margin: 0 auto; +} + +.grid { + display: grid; + grid-template-columns: 2fr 1fr 1fr; + gap: 40px; + margin-bottom: 40px; +} + +.brand { + display: flex; + flex-direction: column; + gap: 12px; +} + +.logo { + font-family: 'Orbitron', sans-serif; + font-size: 32px; + font-weight: 800; + margin: 0; + background: linear-gradient(135deg, var(--neo-cyan), var(--neo-purple)); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + letter-spacing: -0.5px; +} + +.tagline { + font-family: 'Orbitron', sans-serif; + font-size: 14px; + font-weight: 400; + color: var(--neo-text-dim); + margin: 0; + max-width: 320px; + line-height: 1.6; + letter-spacing: -0.2px; +} + +.heading { + font-family: 'Orbitron', sans-serif; + font-size: 16px; + font-weight: 700; + color: var(--neo-text); + margin: 0 0 16px; + letter-spacing: -0.3px; + text-transform: uppercase; +} + +.linkList { + list-style: none; + padding: 0; + margin: 0; + display: flex; + flex-direction: column; + gap: 10px; +} + +.link { + font-family: 'Orbitron', sans-serif; + font-size: 14px; + font-weight: 400; + color: var(--neo-text-dim); + text-decoration: none; + background: none; + border: none; + padding: 0; + cursor: pointer; + transition: color 0.2s; + text-align: left; + letter-spacing: -0.2px; +} + +.link:hover { + color: var(--neo-cyan); +} + +.bottom { + padding-top: 30px; + border-top: 1px solid var(--neo-border); + text-align: center; +} + +.copyright { + font-family: 'Orbitron', sans-serif; + font-size: 13px; + font-weight: 400; + color: var(--neo-text-dim); + margin: 0; + letter-spacing: -0.2px; +} + +@media (max-width: 768px) { + .grid { + grid-template-columns: 1fr; + gap: 30px; + } + + .footer { + padding: 40px 16px 20px; + } +} diff --git a/client/src/components/layout/Navbar.jsx b/client/src/components/layout/Navbar.jsx new file mode 100644 index 0000000..e91b7ac --- /dev/null +++ b/client/src/components/layout/Navbar.jsx @@ -0,0 +1,76 @@ +import { useRef, useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; +import anime from 'animejs'; +import { useTheme } from '../../context/ThemeContext'; +import styles from './Navbar.module.css'; + +export default function Navbar() { + const { isDark, toggleTheme } = useTheme(); + const navigate = useNavigate(); + const navRef = useRef(null); + const hasAnimated = useRef(false); + + useEffect(() => { + // Only animate once on mount + if (hasAnimated.current) return; + hasAnimated.current = true; + + // Animate navbar on mount + anime({ + targets: navRef.current, + translateY: [-20, 0], + opacity: [0, 1], + duration: 800, + easing: 'easeOutExpo', + }); + }, []); + + const handleTryThis = () => { + // Animate before navigation + anime({ + targets: '.tryThisBtn', + scale: [1, 0.95, 1.05, 1], + duration: 400, + easing: 'easeInOutQuad', + complete: () => navigate('/game'), + }); + }; + + const scrollToTop = () => { + window.scrollTo({ + top: 0, + behavior: 'smooth', + }); + }; + + return ( + + ); +} diff --git a/client/src/components/layout/Navbar.module.css b/client/src/components/layout/Navbar.module.css new file mode 100644 index 0000000..adfeeec --- /dev/null +++ b/client/src/components/layout/Navbar.module.css @@ -0,0 +1,233 @@ +.navbar { + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 1000; + display: flex; + align-items: center; + justify-content: space-between; + padding: 20px 60px; + background: var(--glass-bg); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); + border-bottom: 3px solid transparent; + border-image: linear-gradient( + 90deg, + var(--neo-cyan), + var(--neo-purple), + #ff0055, + #ffed4e, + var(--neo-cyan) + ); + border-image-slice: 1; + font-family: 'Fredoka', system-ui, sans-serif; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05); + position: relative; + will-change: transform, opacity; +} + +.navbar::before { + content: ''; + position: absolute; + top: 0; + left: -100px; + width: 200px; + height: 100%; + background: radial-gradient( + circle, + rgba(0, 242, 255, 0.15) 0%, + transparent 70% + ); + pointer-events: none; +} + +.navbar::after { + content: ''; + position: absolute; + top: 0; + right: -100px; + width: 200px; + height: 100%; + background: radial-gradient( + circle, + rgba(112, 0, 255, 0.15) 0%, + transparent 70% + ); + pointer-events: none; +} + +.logo { + display: flex; + align-items: center; + gap: 8px; +} + +.logoText { + font-family: 'Orbitron', sans-serif; + font-size: 26px; + font-weight: 700; + color: var(--neo-text); + letter-spacing: -0.5px; + position: relative; + text-transform: uppercase; +} + +.logoText::after { + content: ''; + position: absolute; + bottom: -2px; + left: 0; + width: 100%; + height: 3px; + background: linear-gradient(90deg, var(--neo-cyan), var(--neo-purple), #ff0055); + border-radius: 2px; + box-shadow: 0 0 8px rgba(0, 242, 255, 0.5); +} + +.navLinks { + display: flex; + gap: 40px; + align-items: center; +} + +.navLink { + font-family: 'Orbitron', sans-serif; + color: var(--neo-text); + text-decoration: none; + font-size: 14px; + font-weight: 500; + letter-spacing: -0.2px; + transition: all 0.2s ease; + position: relative; + padding: 8px 0; + text-transform: uppercase; +} + +.navLink::after { + content: ''; + position: absolute; + bottom: 0; + left: 0; + width: 0; + height: 2px; + background: linear-gradient(90deg, var(--neo-cyan), var(--neo-purple)); + transition: width 0.3s ease; +} + +.navLink:hover { + color: var(--neo-cyan); + transform: translateY(-1px); +} + +.navLink:hover::after { + width: 100%; +} + +.navActions { + display: flex; + align-items: center; + gap: 16px; +} + +.themeToggle { + width: 40px; + height: 40px; + border-radius: 12px; + border: 2px solid transparent; + background: var(--neo-surface); + background-clip: padding-box; + cursor: pointer; + font-size: 18px; + display: grid; + place-items: center; + transition: all 0.2s ease; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); + position: relative; +} + +.themeToggle::before { + content: ''; + position: absolute; + inset: -2px; + border-radius: 12px; + padding: 2px; + background: linear-gradient(135deg, var(--neo-cyan), var(--neo-purple), #ff0055); + -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); + -webkit-mask-composite: xor; + mask-composite: exclude; + opacity: 0; + transition: opacity 0.2s ease; +} + +.themeToggle:hover::before { + opacity: 1; +} + +.themeToggle:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0, 242, 255, 0.3); +} + +.tryThisBtn { + padding: 10px 24px; + border-radius: 12px; + border: none; + background: linear-gradient(135deg, var(--neo-cyan), var(--neo-purple), #ff0055); + background-size: 200% 200%; + animation: gradientShift 3s ease infinite; + color: white; + font-size: 14px; + font-weight: 700; + cursor: pointer; + transition: all 0.2s ease; + font-family: 'Orbitron', system-ui, sans-serif; + letter-spacing: -0.2px; + text-transform: uppercase; + box-shadow: 0 4px 12px rgba(0, 242, 255, 0.25); +} + +@keyframes gradientShift { + 0%, 100% { + background-position: 0% 50%; + } + 50% { + background-position: 100% 50%; + } +} + +@media (prefers-reduced-motion: reduce) { + .tryThisBtn { + animation: none; + } +} + +.tryThisBtn:hover { + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(0, 242, 255, 0.4), 0 6px 20px rgba(112, 0, 255, 0.3), 0 6px 20px rgba(255, 0, 85, 0.3); +} + +@media (max-width: 768px) { + .navbar { + padding: 16px 24px; + } + + .navLinks { + display: none; + } + + .logoText { + font-size: 22px; + } + + .themeToggle { + width: 36px; + height: 36px; + font-size: 16px; + } + + .tryThisBtn { + padding: 8px 18px; + font-size: 14px; + } +} diff --git a/client/src/context/ThemeContext.jsx b/client/src/context/ThemeContext.jsx new file mode 100644 index 0000000..b02fee0 --- /dev/null +++ b/client/src/context/ThemeContext.jsx @@ -0,0 +1,41 @@ +import { createContext, useContext, useState, useEffect, useMemo } from 'react'; +import PropTypes from 'prop-types'; + +const ThemeContext = createContext(null); + +export function ThemeProvider({ children }) { + const [isDark, setIsDark] = useState(() => { + // Check localStorage first, then system preference + const stored = localStorage.getItem('theme'); + if (stored) return stored === 'dark'; + return window.matchMedia('(prefers-color-scheme: dark)').matches; + }); + + useEffect(() => { + // Apply theme class to document + document.documentElement.setAttribute('data-theme', isDark ? 'dark' : 'light'); + localStorage.setItem('theme', isDark ? 'dark' : 'light'); + }, [isDark]); + + const toggleTheme = () => setIsDark(prev => !prev); + + const value = useMemo(() => ({ isDark, toggleTheme }), [isDark]); + + return ( + + {children} + + ); +} + +ThemeProvider.propTypes = { + children: PropTypes.node.isRequired, +}; + +export const useTheme = () => { + const context = useContext(ThemeContext); + if (!context) { + throw new Error('useTheme must be used within a ThemeProvider'); + } + return context; +}; diff --git a/client/src/pages/HomePage.jsx b/client/src/pages/HomePage.jsx new file mode 100644 index 0000000..fe5df20 --- /dev/null +++ b/client/src/pages/HomePage.jsx @@ -0,0 +1,20 @@ +import Navbar from '../components/layout/Navbar'; +import Footer from '../components/layout/Footer'; +import HeroSection from '../components/home/HeroSection'; +import AboutSection from '../components/home/AboutSection'; +import DemoSection from '../components/home/DemoSection'; +import styles from './HomePage.module.css'; + +export default function HomePage() { + return ( +
+ +
+ + + +
+
+ ); +} diff --git a/client/src/pages/HomePage.module.css b/client/src/pages/HomePage.module.css new file mode 100644 index 0000000..537d2a8 --- /dev/null +++ b/client/src/pages/HomePage.module.css @@ -0,0 +1,11 @@ +.homePage { + min-height: 100vh; + width: 100%; + overflow-x: hidden; + background: var(--neo-bg); + transition: background 0.3s ease; +} + +.homePage main { + width: 100%; +} diff --git a/client/src/styles/global.css b/client/src/styles/global.css index 39fed15..d07e478 100644 --- a/client/src/styles/global.css +++ b/client/src/styles/global.css @@ -4,7 +4,7 @@ - Bubbly bold typography */ -@import url('https://fonts.googleapis.com/css2?family=Fredoka:wght@400;600;700&family=Assistant:wght@400;600;700&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Fredoka:wght@400;600;700&family=Inter:wght@400;500;600;700&family=Orbitron:wght@400;500;600;700;800;900&display=swap'); :root { /* Candy palette */ @@ -44,11 +44,73 @@ --glass-shadow: 0 18px 44px rgba(40, 18, 46, 0.18); /* Typography */ - --font-sans: 'Fredoka', 'Assistant', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, + --font-sans: 'Fredoka', 'Inter', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', 'Liberation Sans', sans-serif; + --font-body: 'Inter', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, + 'Helvetica Neue', Arial, sans-serif; --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; } +/* Dark theme variables */ +:root[data-theme="dark"] { + /* Cyberpunk dark palette */ + --neo-cyan: #00f2ff; + --neo-purple: #7000ff; + --neo-red: #ff2d55; + --neo-bg: #0a0e1a; + + /* Surfaces */ + --neo-surface: rgba(20, 24, 40, 0.85); + --neo-surface-strong: rgba(30, 35, 55, 0.92); + + /* Text */ + --neo-text: rgba(255, 255, 255, 0.92); + --neo-text-dim: rgba(255, 255, 255, 0.65); + + /* Borders */ + --neo-border: rgba(0, 242, 255, 0.25); + + /* Glassmorphism - dark variant */ + --glass-bg: rgba(20, 24, 40, 0.72); + --glass-border: rgba(0, 242, 255, 0.18); + --glass-shadow: 0 18px 44px rgba(0, 0, 0, 0.45); + + /* Glow effects */ + --neo-glow-cyan: 0 0 30px rgba(0, 242, 255, 0.35); + --neo-glow-purple: 0 0 30px rgba(112, 0, 255, 0.35); + --neo-glow-soft: 0 18px 60px rgba(0, 0, 0, 0.35); +} + +/* Light theme - explicit declaration for clarity */ +:root[data-theme="light"] { + /* Keep candy palette but ensure proper contrast */ + --neo-cyan: #4DB8E8; + --neo-purple: #A366FF; + --neo-red: #FF5A8E; + --neo-bg: #FFF7FD; + + /* Surfaces */ + --neo-surface: rgba(255, 255, 255, 0.75); + --neo-surface-strong: rgba(255, 255, 255, 0.90); + + /* Text */ + --neo-text: rgba(40, 18, 46, 0.92); + --neo-text-dim: rgba(40, 18, 46, 0.65); + + /* Borders */ + --neo-border: rgba(160, 130, 180, 0.35); + + /* Glassmorphism - light variant */ + --glass-bg: rgba(255, 255, 255, 0.70); + --glass-border: rgba(215, 183, 255, 0.30); + --glass-shadow: 0 18px 44px rgba(40, 18, 46, 0.12); + + /* Glow effects */ + --neo-glow-cyan: 0 0 30px rgba(77, 184, 232, 0.25); + --neo-glow-purple: 0 0 30px rgba(163, 102, 255, 0.25); + --neo-glow-soft: 0 18px 60px rgba(40, 18, 46, 0.10); +} + html, body { height: 100%;