diff --git a/.gitignore b/.gitignore index 5d1eac7..802c1c5 100644 --- a/.gitignore +++ b/.gitignore @@ -41,4 +41,7 @@ yarn-error.log* next-env.d.ts # env files -!.env.local.example \ No newline at end of file +!.env.local.example + +# claude +.claude \ No newline at end of file diff --git a/bun.lock b/bun.lock index fb95158..0807800 100644 --- a/bun.lock +++ b/bun.lock @@ -5,14 +5,14 @@ "": { "name": "prep", "dependencies": { - "@next/env": "^16.0.1", + "@next/env": "^16.1.6", "@vercel/analytics": "^1.5.0", "dedent": "^1.6.0", "framer-motion": "^12.23.0", "lucide-react": "^0.523.0", - "next": "16.0.1", - "react": "^19.0.0", - "react-dom": "^19.0.0", + "next": "16.1.6", + "react": "^19.2.4", + "react-dom": "^19.2.4", "react-syntax-highlighter": "^15.6.1", }, "devDependencies": { @@ -281,23 +281,23 @@ "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], - "@next/env": ["@next/env@16.0.3", "", {}, "sha512-IqgtY5Vwsm14mm/nmQaRMmywCU+yyMIYfk3/MHZ2ZTJvwVbBn3usZnjMi1GacrMVzVcAxJShTCpZlPs26EdEjQ=="], + "@next/env": ["@next/env@16.1.6", "", {}, "sha512-N1ySLuZjnAtN3kFnwhAwPvZah8RJxKasD7x1f8shFqhncnWZn4JMfg37diLNuoHsLAlrDfM3g4mawVdtAG8XLQ=="], - "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.0.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-R0YxRp6/4W7yG1nKbfu41bp3d96a0EalonQXiMe+1H9GTHfKxGNCGFNWUho18avRBPsO8T3RmdWuzmfurlQPbg=="], + "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.1.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-wTzYulosJr/6nFnqGW7FrG3jfUUlEf8UjGA0/pyypJl42ExdVgC6xJgcXQ+V8QFn6niSG2Pb8+MIG1mZr2vczw=="], - "@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.0.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-kETZBocRux3xITiZtOtVoVvXyQLB7VBxN7L6EPqgI5paZiUlnsgYv4q8diTNYeHmF9EiehydOBo20lTttCbHAg=="], + "@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.1.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-BLFPYPDO+MNJsiDWbeVzqvYd4NyuRrEYVB5k2N3JfWncuHAy2IVwMAOlVQDFjj+krkWzhY2apvmekMkfQR0CUQ=="], - "@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.0.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-hWg3BtsxQuSKhfe0LunJoqxjO4NEpBmKkE+P2Sroos7yB//OOX3jD5ISP2wv8QdUwtRehMdwYz6VB50mY6hqAg=="], + "@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.1.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-OJYkCd5pj/QloBvoEcJ2XiMnlJkRv9idWA/j0ugSuA34gMT6f5b7vOiCQHVRpvStoZUknhl6/UxOXL4OwtdaBw=="], - "@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.0.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-UPnOvYg+fjAhP3b1iQStcYPWeBFRLrugEyK/lDKGk7kLNua8t5/DvDbAEFotfV1YfcOY6bru76qN9qnjLoyHCQ=="], + "@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.1.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ=="], - "@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.0.1", "", { "os": "linux", "cpu": "x64" }, "sha512-Et81SdWkcRqAJziIgFtsFyJizHoWne4fzJkvjd6V4wEkWTB4MX6J0uByUb0peiJQ4WeAt6GGmMszE5KrXK6WKg=="], + "@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.1.6", "", { "os": "linux", "cpu": "x64" }, "sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ=="], - "@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.0.1", "", { "os": "linux", "cpu": "x64" }, "sha512-qBbgYEBRrC1egcG03FZaVfVxrJm8wBl7vr8UFKplnxNRprctdP26xEv9nJ07Ggq4y1adwa0nz2mz83CELY7N6Q=="], + "@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.1.6", "", { "os": "linux", "cpu": "x64" }, "sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg=="], - "@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.0.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-cPuBjYP6I699/RdbHJonb3BiRNEDm5CKEBuJ6SD8k3oLam2fDRMKAvmrli4QMDgT2ixyRJ0+DTkiODbIQhRkeQ=="], + "@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.1.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-gQmm8izDTPgs+DCWH22kcDmuUp7NyiJgEl18bcr8irXA5N2m2O+JQIr6f3ct42GOs9c0h8QF3L5SzIxcYAAXXw=="], - "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.0.1", "", { "os": "win32", "cpu": "x64" }, "sha512-XeEUJsE4JYtfrXe/LaJn3z1pD19fK0Q6Er8Qoufi+HqvdO4LEPyCxLUt4rxA+4RfYo6S9gMlmzCMU2F+AatFqQ=="], + "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.1.6", "", { "os": "win32", "cpu": "x64" }, "sha512-NRfO39AIrzBnixKbjuo2YiYhB6o9d8v/ymU9m/Xk8cyVk+k7XylniXkHwjs4s70wedVffc6bQNbufk5v0xEm0A=="], "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.53.3", "", { "os": "android", "cpu": "arm" }, "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w=="], @@ -967,7 +967,7 @@ "neo-async": ["neo-async@2.6.2", "", {}, "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="], - "next": ["next@16.0.1", "", { "dependencies": { "@next/env": "16.0.1", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.0.1", "@next/swc-darwin-x64": "16.0.1", "@next/swc-linux-arm64-gnu": "16.0.1", "@next/swc-linux-arm64-musl": "16.0.1", "@next/swc-linux-x64-gnu": "16.0.1", "@next/swc-linux-x64-musl": "16.0.1", "@next/swc-win32-arm64-msvc": "16.0.1", "@next/swc-win32-x64-msvc": "16.0.1", "sharp": "^0.34.4" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-e9RLSssZwd35p7/vOa+hoDFggUZIUbZhIUSLZuETCwrCVvxOs87NamoUzT+vbcNAL8Ld9GobBnWOA6SbV/arOw=="], + "next": ["next@16.1.6", "", { "dependencies": { "@next/env": "16.1.6", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.8.3", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.1.6", "@next/swc-darwin-x64": "16.1.6", "@next/swc-linux-arm64-gnu": "16.1.6", "@next/swc-linux-arm64-musl": "16.1.6", "@next/swc-linux-x64-gnu": "16.1.6", "@next/swc-linux-x64-musl": "16.1.6", "@next/swc-win32-arm64-msvc": "16.1.6", "@next/swc-win32-x64-msvc": "16.1.6", "sharp": "^0.34.4" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw=="], "node-int64": ["node-int64@0.4.0", "", {}, "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw=="], @@ -1045,9 +1045,9 @@ "querystringify": ["querystringify@2.2.0", "", {}, "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ=="], - "react": ["react@19.2.0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="], + "react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="], - "react-dom": ["react-dom@19.2.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ=="], + "react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="], "react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="], @@ -1291,8 +1291,6 @@ "jest-worker/supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="], - "next/@next/env": ["@next/env@16.0.1", "", {}, "sha512-LFvlK0TG2L3fEOX77OC35KowL8D7DlFF45C0OvKMC4hy8c/md1RC4UMNDlUGJqfCoCS2VWrZ4dSE6OjaX5+8mw=="], - "next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="], "p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], diff --git a/package.json b/package.json index 0c5945e..1cfdc8f 100644 --- a/package.json +++ b/package.json @@ -14,14 +14,14 @@ "test": "bun jest" }, "dependencies": { - "@next/env": "^16.0.1", + "@next/env": "^16.1.6", "@vercel/analytics": "^1.5.0", "dedent": "^1.6.0", "framer-motion": "^12.23.0", "lucide-react": "^0.523.0", - "next": "16.0.1", - "react": "^19.0.0", - "react-dom": "^19.0.0", + "next": "16.1.6", + "react": "^19.2.4", + "react-dom": "^19.2.4", "react-syntax-highlighter": "^15.6.1" }, "devDependencies": { diff --git a/src/app/(main)/home.tsx b/src/app/(main)/home.tsx index 831aacb..6fe13fa 100644 --- a/src/app/(main)/home.tsx +++ b/src/app/(main)/home.tsx @@ -1,4 +1,14 @@ 'use client'; + +import { motion } from 'framer-motion'; +import { + ArrowRight, + BookOpen, + Code2, + Layers, + Sparkles, + Zap, +} from 'lucide-react'; import { useRouter } from 'next/navigation'; import type { Section } from '@/types'; @@ -13,76 +23,255 @@ const sections: Section[] = [ }, ]; +const features = [ + { + icon: Code2, + title: 'Code Examples', + description: 'Syntax-highlighted snippets with line-by-line annotations', + }, + { + icon: BookOpen, + title: 'Interactive Notes', + description: 'Study materials organized by topic with deep-dive sections', + }, + { + icon: Layers, + title: 'Structured Learning', + description: 'Progressive curriculum from fundamentals to advanced topics', + }, + { + icon: Zap, + title: 'Interview Ready', + description: 'Curated content focused on what interviewers actually ask', + }, +]; + +const stagger = { + hidden: {}, + show: { + transition: { + staggerChildren: 0.1, + }, + }, +}; + +const fadeUp = { + hidden: { opacity: 0, y: 30 }, + show: { + opacity: 1, + y: 0, + transition: { duration: 0.6, ease: 'easeOut' as const }, + }, +}; + export default function HomeComponent() { const router = useRouter(); - const totalItems = sections.length; - const columns = 3; + return ( +
+ {/* Hero Section */} +
+ {/* Decorative grid */} +
+ + + {/* Badge */} + + + + + Interview Preparation Platform + + + - const colSpanClasses: { [key: number]: string } = { - 2: 'md:col-span-2', - 3: 'md:col-span-3', - 6: 'md:col-span-6', - }; + {/* Main Heading */} + + Master your +
+ next interview +
- return ( -
-
-

- Any interview prep -

-
-
- {sections.map((section, index) => { - const rowNumber = Math.floor(index / columns); - const isLastRow = - rowNumber === Math.floor((totalItems - 1) / columns); - - const itemsOnLastRow = totalItems % columns; - const itemsOnThisRow = - isLastRow && itemsOnLastRow > 0 ? itemsOnLastRow : columns; - - const colSpan = Math.round(6 / itemsOnThisRow); - - const className = `rounded-lg border border-zinc-800 bg-zinc-900/90 p-6 shadow-sm transition-colors duration-200 hover:border-zinc-600 ${ - colSpanClasses[colSpan] - }`; - - return ( + {/* Subtitle */} + + A structured study resource with code examples, interactive notes, + and curated interview prep materials for multiple positions and + experience levels. + + + {/* CTA */} + + + + + {/* Scroll indicator */} + +
+ +
+
+
+ + {/* Topics Section */} +
+ +
+

+ Choose your + learning path +

+

+ Start with structured preparation tracks designed for specific + roles and experience levels. +

+
+ +
+ {sections.map((section) => ( + !section.inProgress && router.push(section.href)} + type='button' + whileHover={{ y: -4 }} + whileTap={{ scale: 0.98 }} + > +
+
+
+
+ +
+
+

+ {section.title} +

+ {section.level && ( + + {section.level} + + )} +
+
+

+ {section.description} +

+
+
+ {section.inProgress ? ( + Coming soon + ) : ( + <> + Start learning + + + )} +
-

- {section.description} + + ))} +

+
+
+ + {/* Features Grid */} +
+ +
+

+ Built for + real preparation +

+
+ +
+ {features.map((feature) => ( + +
+ +
+

+ {feature.title} +

+

+ {feature.description}

-
-
- {section.inProgress ? 'Coming soon...' : 'Start learning →'} -
- - ); - })} -
+ + ))} + + + + + {/* Footer */} + ); } diff --git a/src/app/frontend/junior/frontend-junior.tsx b/src/app/frontend/junior/frontend-junior.tsx index 86969bd..39961f0 100644 --- a/src/app/frontend/junior/frontend-junior.tsx +++ b/src/app/frontend/junior/frontend-junior.tsx @@ -1,104 +1,211 @@ 'use client'; + +import { motion } from 'framer-motion'; +import { + ArrowLeft, + ArrowRight, + CheckCircle2, + Clock, + Code2, + Globe, + Paintbrush, + Terminal, + Wrench, +} from 'lucide-react'; import { useRouter } from 'next/navigation'; import type { Section } from '@/types'; -const sections: Section[] = [ +interface TopicSection extends Section { + icon: typeof Code2; + color: string; + gradient: string; +} + +const sections: TopicSection[] = [ { href: '/frontend/junior/html&css', title: 'HTML & CSS', description: 'Semantic HTML, accessibility basics, Flexbox, Grid, responsive design', inProgress: false, + icon: Paintbrush, + color: 'var(--aurora-3)', + gradient: 'from-pink-500/10 to-rose-500/10', }, { href: '#', title: 'JavaScript Fundamentals', description: 'ES6+ syntax, scope, closures, async patterns, DOM APIs', inProgress: true, + icon: Code2, + color: 'var(--aurora-1)', + gradient: 'from-indigo-500/10 to-violet-500/10', }, { href: '#', title: 'API Integration', description: 'fetch/axios, REST, GraphQL, error handling, loading states', inProgress: true, + icon: Globe, + color: 'var(--aurora-4)', + gradient: 'from-cyan-500/10 to-teal-500/10', }, { href: '#', title: 'Framework Basics (React)', description: 'Components, props, state, hooks, Context API, lifecycle', inProgress: true, + icon: Terminal, + color: 'var(--aurora-2)', + gradient: 'from-violet-500/10 to-purple-500/10', }, { href: '#', title: 'Tooling & Debugging', description: 'Chrome DevTools, ESLint/Prettier, npm scripts, build tools', inProgress: true, + icon: Wrench, + color: 'var(--aurora-5)', + gradient: 'from-emerald-500/10 to-green-500/10', }, ]; +const stagger = { + hidden: {}, + show: { + transition: { + staggerChildren: 0.08, + }, + }, +}; + +const fadeUp = { + hidden: { opacity: 0, y: 20 }, + show: { + opacity: 1, + y: 0, + transition: { duration: 0.5, ease: 'easeOut' as const }, + }, +}; + export default function FrontendJunior() { const router = useRouter(); - const totalItems = sections.length; - const columns = 3; - - const colSpanClasses: { [key: number]: string } = { - 2: 'md:col-span-2', - 3: 'md:col-span-3', - 6: 'md:col-span-6', - }; + const completedCount = sections.filter((s) => !s.inProgress).length; + const totalCount = sections.length; return ( -
-
-

- Junior Frontend Developer Preparation -

-

- (in development, 'in progress' parts are not completed) -

-
-
- {sections.map((section, index) => { - const rowNumber = Math.floor(index / columns); - const isLastRow = - rowNumber === Math.floor((totalItems - 1) / columns); +
+ {/* Header */} +
+
+ +
+
+ + {/* Content */} +
+ + {/* Page Title */} + +

+ Junior Frontend +
+ Developer Prep +

+

+ Master the fundamentals of frontend development. Complete each + topic to build a strong foundation for your interviews. +

+ + {/* Progress bar */} +
+
+ +
+ + {completedCount}/{totalCount} topics + +
+
- const itemsOnLastRow = totalItems % columns; - const itemsOnThisRow = - isLastRow && itemsOnLastRow > 0 ? itemsOnLastRow : columns; + {/* Topic Cards */} +
+ {sections.map((section, index) => ( + !section.inProgress && router.push(section.href)} + type='button' + variants={fadeUp} + whileHover={section.inProgress ? {} : { x: 4 }} + > + {/* Number */} +
+ {String(index + 1).padStart(2, '0')} +
- const colSpan = Math.round(6 / itemsOnThisRow); + {/* Icon */} +
+ +
- const className = `rounded-lg border border-zinc-800 bg-zinc-900/90 p-6 shadow-sm transition-colors duration-200 hover:border-zinc-600 ${ - colSpanClasses[colSpan] - }`; + {/* Content */} +
+

+ {section.title} +

+

+ {section.description} +

+
- return ( - - ); - })} -
+
+ + ))} +
+ +
); } diff --git a/src/app/globals.css b/src/app/globals.css index e1274dc..3cfd0a8 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -1,29 +1,40 @@ @import "tailwindcss"; :root { - /* Default to dark theme */ - --background: #0a0a0a; - --foreground: #ededed; - --accent: #eab308; /* yellow-500 */ - --muted: #a1a1aa; /* zinc-400 */ - --muted-foreground: #d4d4d8; /* zinc-300 */ - --border: rgba(255, 255, 255, 0.08); - --glass-bg: rgba(0, 0, 0, 0.1); - --glass-strong-bg: rgba(0, 0, 0, 0.22); + /* New design system: Deep space + aurora */ + --background: #050a18; + --foreground: #e8edf5; + --accent: #6366f1; /* indigo-500 */ + --accent-light: #818cf8; /* indigo-400 */ + --accent-glow: rgba(99, 102, 241, 0.35); + --secondary: #f472b6; /* pink-400 */ + --secondary-glow: rgba(244, 114, 182, 0.3); + --tertiary: #34d399; /* emerald-400 */ + --tertiary-glow: rgba(52, 211, 153, 0.25); + --muted: #94a3b8; /* slate-400 */ + --muted-foreground: #cbd5e1; /* slate-300 */ + --border: rgba(148, 163, 184, 0.1); + --border-hover: rgba(148, 163, 184, 0.2); + --glass-bg: rgba(15, 23, 42, 0.6); + --glass-strong-bg: rgba(15, 23, 42, 0.8); + --card-bg: rgba(15, 23, 42, 0.5); --ring: var(--accent); - /* Current page header height (controlled by PageHeader) */ + --surface-1: #0f172a; + --surface-2: #1e293b; + + /* Page header */ --page-header-height: 128px; - /* Offset built-in anchor navigation and scrollIntoView to account for sticky header */ scroll-padding-top: var(--page-header-height, 128px); - /* Ambient background hues */ - --ambient-1: 255 90 31; /* warm orange */ - --ambient-2: 234 179 8; /* yellow-500 */ - --ambient-3: 168 85 247; /* purple */ - --ambient-4: 14 165 233; /* cyan */ + /* Aurora gradient colors */ + --aurora-1: #6366f1; + --aurora-2: #8b5cf6; + --aurora-3: #ec4899; + --aurora-4: #06b6d4; + --aurora-5: #34d399; } -/* Smooth scrolling for anchor navigation; respect reduced motion */ +/* Smooth scrolling; respect reduced motion */ html { scroll-behavior: smooth; } @@ -31,9 +42,13 @@ html { html { scroll-behavior: auto; } + html .motion-safe-only, + body .motion-safe-only { + animation: none; + transition: none; + } } -/* Ensure headings with anchors don't hide under the sticky header */ h2[id], h3[id], h4[id] { @@ -43,20 +58,31 @@ h4[id] { @theme inline { --color-background: var(--background); --color-foreground: var(--foreground); + --color-accent: var(--accent); + --color-accent-light: var(--accent-light); + --color-secondary: var(--secondary); + --color-tertiary: var(--tertiary); + --color-muted: var(--muted); + --color-surface-1: var(--surface-1); + --color-surface-2: var(--surface-2); --font-sans: var(--font-geist-sans); --font-mono: var(--font-geist-mono); } -/* Respect system light preference while favoring dark by default */ +/* Light mode */ @media (prefers-color-scheme: light) { :root { - --background: #ffffff; - --foreground: #171717; - --muted: #52525b; /* zinc-600 */ - --muted-foreground: #3f3f46; /* zinc-700 */ - --border: rgba(0, 0, 0, 0.08); - --glass-bg: rgba(255, 255, 255, 0.5); - --glass-strong-bg: rgba(255, 255, 255, 0.6); + --background: #f8fafc; + --foreground: #0f172a; + --muted: #64748b; + --muted-foreground: #475569; + --border: rgba(15, 23, 42, 0.1); + --border-hover: rgba(15, 23, 42, 0.2); + --glass-bg: rgba(248, 250, 252, 0.7); + --glass-strong-bg: rgba(248, 250, 252, 0.85); + --card-bg: rgba(255, 255, 255, 0.7); + --surface-1: #ffffff; + --surface-2: #f1f5f9; } } @@ -76,25 +102,6 @@ body { "Segoe UI Emoji"; } -/* Image + gradient background is rendered via AmbientBackground component */ - -/* subtle vignette to highlight glass layers */ -body::after { - content: ""; - position: fixed; - inset: 0; - z-index: -1; - background: - radial-gradient( - 60% 60% at 50% 0%, - rgba(255, 255, 255, 0.03), - transparent 70% - ), - radial-gradient(80% 50% at 50% 100%, rgba(0, 0, 0, 0.25), transparent 70%); - pointer-events: none; - transition: filter 0.28s ease-in-out; -} - /* Accessible focus styles */ button:focus-visible, a:focus-visible, @@ -107,24 +114,119 @@ a:focus-visible, .glass { background: var(--glass-bg); border: 1px solid var(--border); - backdrop-filter: blur(12px) saturate(140%); + backdrop-filter: blur(16px) saturate(180%); + -webkit-backdrop-filter: blur(16px) saturate(180%); } .glass-strong { background: var(--glass-strong-bg); border-bottom: 1px solid var(--border); - backdrop-filter: blur(14px) saturate(160%); + backdrop-filter: blur(20px) saturate(200%); + -webkit-backdrop-filter: blur(20px) saturate(200%); +} + +/* Aurora card hover glow */ +.card-glow { + position: relative; + overflow: hidden; +} + +.card-glow::before { + content: ""; + position: absolute; + inset: -1px; + border-radius: inherit; + padding: 1px; + background: linear-gradient( + 135deg, + var(--aurora-1), + var(--aurora-3), + var(--aurora-4) + ); + mask: + linear-gradient(#fff 0 0) content-box, + linear-gradient(#fff 0 0); + mask-composite: exclude; + -webkit-mask-composite: xor; + opacity: 0; + transition: opacity 0.4s ease; } -/* Mobile: allow full viewport width */ +.card-glow:hover::before { + opacity: 1; +} + +/* Gradient text utility */ +.gradient-text { + background: linear-gradient( + 135deg, + var(--aurora-1), + var(--aurora-2), + var(--aurora-3) + ); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.gradient-text-cool { + background: linear-gradient( + 135deg, + var(--aurora-4), + var(--aurora-1), + var(--aurora-5) + ); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +/* Aurora mesh background animation */ +@keyframes aurora-shift { + 0%, + 100% { + background-position: 0% 50%; + } + 25% { + background-position: 100% 0%; + } + 50% { + background-position: 100% 100%; + } + 75% { + background-position: 0% 100%; + } +} + +@keyframes float { + 0%, + 100% { + transform: translateY(0) scale(1); + } + 50% { + transform: translateY(-20px) scale(1.05); + } +} + +@keyframes pulse-glow { + 0%, + 100% { + opacity: 0.4; + transform: scale(1); + } + 50% { + opacity: 0.7; + transform: scale(1.1); + } +} + +/* Content container widths */ @media (max-width: 640px) { .content-container { max-width: 100vw; } } -/* SSR-safe initial width via data attribute (desktop only to avoid descending specificity with mobile override) */ -/* Runtime inline style from PageContainer drives width; these are fallback for SSR/first paint */ @media (min-width: 641px) { html[data-content-width="narrow"] .content-container { max-width: 50vw; @@ -140,7 +242,7 @@ a:focus-visible, } } -/* Hide scrollbars */ +/* Scrollbar */ .scrollbar-hide { -ms-overflow-style: none; scrollbar-width: none; @@ -149,3 +251,27 @@ a:focus-visible, .scrollbar-hide::-webkit-scrollbar { display: none; } + +/* Custom styled scrollbar for content */ +::-webkit-scrollbar { + width: 6px; +} + +::-webkit-scrollbar-track { + background: transparent; +} + +::-webkit-scrollbar-thumb { + background: rgba(99, 102, 241, 0.3); + border-radius: 3px; +} + +::-webkit-scrollbar-thumb:hover { + background: rgba(99, 102, 241, 0.5); +} + +/* Selection */ +::selection { + background: rgba(99, 102, 241, 0.3); + color: #e8edf5; +} diff --git a/src/components/ambient-background/ambient-background.tsx b/src/components/ambient-background/ambient-background.tsx index bef1f56..1303f66 100644 --- a/src/components/ambient-background/ambient-background.tsx +++ b/src/components/ambient-background/ambient-background.tsx @@ -1,29 +1,114 @@ 'use client'; -import Image from 'next/image'; +import { motion } from 'framer-motion'; export function AmbientBackground() { return (
- + {/* Base gradient */}
+ + {/* Aurora orbs */} + + + + + + + + + {/* Noise texture overlay for depth */} +
+ + {/* Top edge glow */} +
diff --git a/src/components/code-block/code-block.tsx b/src/components/code-block/code-block.tsx index c83e946..1a71292 100644 --- a/src/components/code-block/code-block.tsx +++ b/src/components/code-block/code-block.tsx @@ -3,7 +3,6 @@ import { coldarkDark } from 'react-syntax-highlighter/dist/esm/styles/prism'; import { parseHighlightLines } from '@/helpers/parse-highlight-lines'; import type { CodeBlockProps } from '@/types'; -// Move regex patterns to top level for better performance const COMMENT_REGEX = /^\/\*\s*\w+\s*\*\/\s*\n?/; const LEADING_SPACES_REGEX = /^\s+/; const TRAILING_SPACES_REGEX = /\s+$/; @@ -45,7 +44,6 @@ export const CodeBlock = ({ const highlightedLines = parseHighlightLines(highlightLines); const highlightedLinesEnd = parseHighlightLines(highlightLinesEnd); - // Line props function for highlighting const getLineProps = (lineNumber: number) => { const isHighlighted = highlightedLines.includes(lineNumber); const isHighlightedEnd = highlightedLinesEnd.includes(lineNumber); @@ -54,12 +52,11 @@ export const CodeBlock = ({ let borderLeft = 'none'; if (isHighlightedEnd) { - backgroundColor = 'rgba(34, 197, 94, 0.15)'; // Green for end lines - borderLeft = '3px solid rgb(34, 197, 94)'; + backgroundColor = 'rgba(52, 211, 153, 0.12)'; + borderLeft = '3px solid rgb(52, 211, 153)'; } else if (isHighlighted) { - // Yellow accent for highlighted lines - backgroundColor = 'rgba(234, 179, 8, 0.18)'; - borderLeft = '3px solid rgb(234, 179, 8)'; + backgroundColor = 'rgba(99, 102, 241, 0.15)'; + borderLeft = '3px solid rgb(99, 102, 241)'; } return { @@ -76,9 +73,9 @@ export const CodeBlock = ({ }; return ( -
+
{comment && ( -
+
{`/* ${comment} */`}
)} diff --git a/src/components/layout/content-page.tsx b/src/components/layout/content-page.tsx index 92cf0dd..53f1f93 100644 --- a/src/components/layout/content-page.tsx +++ b/src/components/layout/content-page.tsx @@ -14,7 +14,7 @@ export function ContentPage({ allowWidthToggle = true, }: ContentPageProps) { return ( -
+
{ return (
-

{placeholder}

+

{placeholder}

); }; diff --git a/src/components/page-header/page-header.tsx b/src/components/page-header/page-header.tsx index 219d2b2..d2b9113 100644 --- a/src/components/page-header/page-header.tsx +++ b/src/components/page-header/page-header.tsx @@ -28,7 +28,6 @@ export const PageHeader = ({ const isHiddenOnMobileRef = useRef(false); const lastCssHeaderHeight = useRef(null); - // Threshold constants for mobile hysteresis const JITTER_PX = 5; const HIDE_THRESHOLD_PX = 24; const SHOW_THRESHOLD_PX = 64; @@ -111,7 +110,6 @@ export const PageHeader = ({ ] ); - // Track mobile breakpoint to enable full hide behavior on small screens useEffect(() => { if (typeof window === 'undefined') return; const mq: MediaQueryList = window.matchMedia('(max-width: 639px)'); @@ -135,7 +133,6 @@ export const PageHeader = ({ }; }, []); - // Scroll listener with rAF batching useEffect(() => { if (typeof window === 'undefined') return; let rafId: number | null = null; @@ -145,7 +142,6 @@ export const PageHeader = ({ rafId = null; const currentScrollY = Math.max(0, window.scrollY); const delta = currentScrollY - lastScrollY.current; - // Avoid toggling collapse state on mobile to reduce flicker; rely on full-hide if (!isMobile.current) { setIsScrolled(currentScrollY > 20 && delta > 0); } @@ -163,7 +159,6 @@ export const PageHeader = ({ }; }, [handleMobileScroll]); - // Expose current header height as a CSS variable for other components useEffect(() => { if (typeof document === 'undefined') { return; @@ -174,7 +169,6 @@ export const PageHeader = ({ if (isHiddenOnMobile) { current = '0px'; } else if (isMobileScreen) { - // On mobile, when visible, keep the header at full height for clear tap targets current = expandedHeight; } else if (isInitialLoad || !isScrolled) { current = expandedHeight; @@ -206,7 +200,6 @@ export const PageHeader = ({ style={{ willChange: 'height', overflow: 'hidden', - // Remove bottom border when fully hidden on mobile to avoid a 1px line borderBottomWidth: isHiddenOnMobile ? 0 : 1, transform: 'translateZ(0)', backfaceVisibility: 'hidden', @@ -221,7 +214,7 @@ export const PageHeader = ({
@@ -230,7 +223,7 @@ export const PageHeader = ({ {(isInitialLoad || !isScrolled) && ( router.push(topicHome)} type='button' > - + )}
diff --git a/src/components/section-card/section-card.tsx b/src/components/section-card/section-card.tsx index 26e0617..db263bc 100644 --- a/src/components/section-card/section-card.tsx +++ b/src/components/section-card/section-card.tsx @@ -5,16 +5,18 @@ export const SectionCard = ({ title, children }: SectionCardProps) => { const id = slugify(title); return (

{title}

-
{children}
+
+ {children} +
); }; diff --git a/src/components/table-of-contents/table-of-contents.tsx b/src/components/table-of-contents/table-of-contents.tsx index 8bde15c..3150315 100644 --- a/src/components/table-of-contents/table-of-contents.tsx +++ b/src/components/table-of-contents/table-of-contents.tsx @@ -57,7 +57,6 @@ export const TableOfContents = () => { setIsLoaded(true); }; - // Initial update with delay to ensure content is rendered const timeoutId = setTimeout(updateTOC, 0); const header = document.getElementById('page-header'); @@ -103,7 +102,6 @@ export const TableOfContents = () => { }; }, []); - // Also open on single tap anywhere near the left edge on mobile useEffect(() => { const onTouchTap = (e: TouchEvent) => { if (window.innerWidth >= 768) return; @@ -130,7 +128,6 @@ export const TableOfContents = () => { setOpen(false); } - // Prevent default navigation and use CSS scroll-margin/scroll-padding e.preventDefault(); const href = e.currentTarget.getAttribute('href'); if (href?.startsWith('#')) { @@ -138,7 +135,6 @@ export const TableOfContents = () => { const targetElement = document.getElementById(targetId); if (targetElement) { targetElement.scrollIntoView({ behavior: 'smooth', block: 'start' }); - // Update the address bar without triggering a jump if ( typeof history !== 'undefined' && typeof history.replaceState === 'function' @@ -153,7 +149,6 @@ export const TableOfContents = () => { setOpen(!open); }; - // Handle click outside on mobile and always unpin when mobile useEffect(() => { openRef.current = open; pinnedRef.current = pinned; @@ -162,7 +157,6 @@ export const TableOfContents = () => { useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (window.innerWidth >= 768) return; - // On mobile ensure menu is unpinned if (pinnedRef.current) { setPinned(false); } @@ -184,7 +178,6 @@ export const TableOfContents = () => { }; }, []); - // Don't render until content is loaded to prevent flickering if (!isLoaded) { return null; } @@ -198,7 +191,6 @@ export const TableOfContents = () => { }} >
- {/* Hover/edge affordance – no explicit button */}