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 */}
+
router.push(section.href)}
+ className='group inline-flex items-center gap-2 rounded-xl bg-[var(--accent)] px-6 py-3 font-semibold text-white transition-all duration-300 hover:scale-105 hover:shadow-[0_0_30px_rgba(99,102,241,0.4)]'
+ onClick={() => router.push('/frontend/junior')}
type='button'
>
-
-
-
- {section.title}
-
- {section.level && (
-
- {section.level}
-
- )}
- {section.inProgress && (
-
- (in progress)
-
- )}
+ Get Started
+
+
+
+
+
+ {/* 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 */}
+
+
+
router.push('/')}
+ type='button'
+ >
+
+ Home
+
+
+
+
+ {/* 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 (
- router.push(section.href)}
- type='button'
- >
-
-
- {section.title}{' '}
- {section.inProgress && (
- (in progress)
+ {/* Status */}
+
+ {section.inProgress ? (
+
+
+ Coming soon
+
+ ) : (
+
)}
-
-
- {section.description}
-
-
-
- Learn more →
-
-
- );
- })}
-
+
+
+ ))}
+
+
+
);
}
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'
>
-
+
)}
router.push('/')}
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 */}
{
}}
/>
- {/* Preview hint when closed - same height as open state */}
{!open && (
{
}}
transition={{ type: 'tween', ease: 'easeInOut', duration: 0.35 }}
>
- {/* Soft glow hint */}
)}
- {/* Main menu */}
{
}}
>
- {/* Pin icon aligned with first section */}
setPinned(!pinned)}
type='button'
>
{pinned ? (
-
+
) : (
-
+
)}
{items.map((section) => (
- {/* Level 1: SectionCard (h2) */}
@@ -287,26 +274,23 @@ export const TableOfContents = () => {
{section.children.length > 0 && (
-
+
{section.children.map((child) => {
- // Check if this is a Header (h3) or Subheader (h4)
const isSubheader = child.level === 4;
return (
{isSubheader ? (
- /* Level 3: Subheader (h4) with double border */
{child.text}
) : (
- /* Level 2: Header (h3) */
diff --git a/src/components/typography/callout.tsx b/src/components/typography/callout.tsx
index bdc7fd0..84a25b8 100644
--- a/src/components/typography/callout.tsx
+++ b/src/components/typography/callout.tsx
@@ -3,7 +3,7 @@ import type { CalloutProps } from '@/types';
export const Callout = ({ children, className = '' }: CalloutProps) => {
return (
{children}
diff --git a/src/components/typography/code-span.tsx b/src/components/typography/code-span.tsx
index 13778e9..2644426 100644
--- a/src/components/typography/code-span.tsx
+++ b/src/components/typography/code-span.tsx
@@ -9,7 +9,7 @@ export const CodeSpan = ({
return (
{children}
diff --git a/src/components/typography/header.tsx b/src/components/typography/header.tsx
index 2d7e70b..6ed1572 100644
--- a/src/components/typography/header.tsx
+++ b/src/components/typography/header.tsx
@@ -6,7 +6,7 @@ export const Header = ({ children, className = '', id }: HeaderProps) => {
const headerId = id || slugify(text);
return (
{children}
;
};
diff --git a/src/components/width-switcher/width-switcher.tsx b/src/components/width-switcher/width-switcher.tsx
index d478462..85a625f 100644
--- a/src/components/width-switcher/width-switcher.tsx
+++ b/src/components/width-switcher/width-switcher.tsx
@@ -14,7 +14,6 @@ export function WidthSwitcher({
onChangeWidth,
headerHeightFallback = 120,
}: WidthSwitcherProps) {
- // Track live header height via ResizeObserver as a fallback when the CSS var is not yet set
const [headerHeight, setHeaderHeight] =
useState(headerHeightFallback);
@@ -38,15 +37,15 @@ export function WidthSwitcher({
transform: 'translateY(50%)',
}}
>
-
+
{(['narrow', 'comfortable', 'wide', 'full'] as WidthPreset[]).map(
(preset) => (
onChangeWidth(preset)}