Refactor: centralize theme definitions and enhance dynamic theming capabilities

This commit is contained in:
2026-02-22 11:45:59 +05:30
parent 8df4ef3b49
commit c0c363a77e
3 changed files with 349 additions and 103 deletions

View File

@@ -1,14 +1,7 @@
import { useState, useEffect } from 'react';
import { Terminal, Github, Mail, Layout, Cpu, Network, ExternalLink, ChevronRight, Activity, User } from 'lucide-react';
import './App.css';
// --- THEME CONFIGURATION ---
const THEMES = [
{ name: 'Blueprint Cyan', value: '#38bdf8', id: 'cyan' },
{ name: 'Drafting Yellow', value: '#fde047', id: 'yellow' },
{ name: 'Red Ink', value: '#f87171', id: 'red' },
{ name: 'Chalk White', value: '#f8fafc', id: 'white' }
];
import { THEMES } from './themes';
// --- MOCK DATA ---
const PROJECTS = [
@@ -45,14 +38,40 @@ const LAB_NOTES = [
];
export default function App() {
const [activeTheme, setActiveTheme] = useState(THEMES[0]);
const [activeTheme] = useState(() => {
const idx = Math.floor(Math.random() * THEMES.length);
return THEMES[idx];
});
const [mousePos, setMousePos] = useState({ x: 0, y: 0 });
const [hasClickedThemePill, setHasClickedThemePill] = useState(false);
const [showThemePillTooltip, setShowThemePillTooltip] = useState(false);
// Update CSS variables for dynamic theming
useEffect(() => {
document.documentElement.style.setProperty('--color-accent', activeTheme.value);
// Calculate a semi-transparent version for backgrounds/borders
document.documentElement.style.setProperty('--color-accent-muted', `${activeTheme.value}33`);
const root = document.documentElement;
// Central palette
root.style.setProperty('--color-accent', activeTheme.colors.accent);
root.style.setProperty('--color-accent-muted', `${activeTheme.colors.accent}33`);
root.style.setProperty('--bg-base', activeTheme.colors.bgBase);
root.style.setProperty('--bg-surface', activeTheme.colors.bgSurface);
root.style.setProperty('--bg-panel', activeTheme.colors.bgPanel);
root.style.setProperty('--text-main', activeTheme.colors.textMain);
root.style.setProperty('--text-muted', activeTheme.colors.textMuted);
root.style.setProperty('--text-dim', activeTheme.colors.textDim);
root.style.setProperty('--border-main', activeTheme.colors.borderMain);
root.style.setProperty('--grid-pattern', activeTheme.colors.gridPattern);
root.style.setProperty('--status-success', activeTheme.colors.statusSuccess);
// Cat
root.style.setProperty('--cat-body', activeTheme.colors.bgBase);
root.style.setProperty('--cat-stroke', activeTheme.colors.catStroke);
root.style.setProperty('--cat-nose', activeTheme.colors.catNose);
root.style.setProperty('--cat-eyes', activeTheme.colors.catEyes);
root.style.setProperty('--heart-color', activeTheme.colors.heartColor);
}, [activeTheme]);
// Subtle interactive background effect
@@ -64,77 +83,16 @@ export default function App() {
return () => window.removeEventListener('mousemove', handleMouseMove);
}, []);
// Auto-hide the "HAHA Made you click" tooltip
useEffect(() => {
if (!showThemePillTooltip) return;
const t = window.setTimeout(() => setShowThemePillTooltip(false), 1400);
return () => window.clearTimeout(t);
}, [showThemePillTooltip]);
return (
<div className="min-h-screen bg-[var(--bg-base)] text-[var(--text-main)] font-mono selection:bg-[var(--color-accent)] selection:text-[var(--bg-base)] relative overflow-x-hidden">
{/* Global CSS for the dynamic theme and indie style */}
<style>{`
@import url('https://fonts.googleapis.com/css2?family=Architects+Daughter&family=Space+Mono:ital,wght@0,400;0,700;1,400&display=swap');
:root {
/* =========================================
🎨 CENTRAL COLOR PALETTE
Modify these variables to change the theme!
========================================= */
/* Backgrounds */
--bg-base: #111113; /* Main page background */
--bg-surface: rgba(24, 24, 27, 0.4); /* Cards and secondary backgrounds */
--bg-panel: rgba(9, 9, 11, 0.85); /* Terminal panes and rigid components */
/* Typography */
--text-main: #f4f4f5; /* Headings and primary text */
--text-muted: #a1a1aa; /* Paragraphs and secondary text */
--text-dim: #52525b; /* Annotations and subtle details */
/* Structure */
--border-main: #27272a; /* Dashed lines and pane borders */
--grid-pattern: rgba(255, 255, 255, 0.08); /* Background dot grid color */
/* Status Indicators */
--status-success: #34d399; /* Emerald color for 'Online' status */
/* Easter Egg (Cat) */
--cat-body: #111113; /* Main body color (usually matches bg-base) */
--cat-stroke: #60a5fa; /* Blue stroke outline */
--cat-nose: #f472b6; /* Pink nose */
--cat-eyes: #ffffff; /* White eyes */
--heart-color: #f472b6; /* Heart animation color */
}
.font-sketch { font-family: 'Architects Daughter', cursive; }
.font-mono { font-family: 'Space Mono', monospace; }
.accent-text { color: var(--color-accent); transition: color 0.3s ease; }
.accent-bg { background-color: var(--color-accent); transition: background-color 0.3s ease; }
/* Indie / Hand-drawn borders */
.sketch-border {
border: 2px solid var(--color-accent-muted);
border-radius: 255px 15px 225px 15px/15px 225px 15px 255px;
transition: all 0.3s ease;
}
.sketch-border:hover {
border-color: var(--color-accent);
border-radius: 15px 225px 15px 255px/255px 15px 225px 15px;
}
.sketch-border-subtle {
border: 1px solid var(--border-main);
border-radius: 2px 255px 3px 25px / 255px 5px 225px 3px;
transition: all 0.3s ease;
}
.sketch-border-subtle:hover {
border-color: var(--color-accent);
border-radius: 255px 5px 225px 3px / 2px 255px 3px 25px;
}
.dot-grid {
background-image: radial-gradient(var(--grid-pattern) 1px, transparent 1px);
background-size: 24px 24px;
}
`}</style>
{/* SVG Noise Filter for rough paper texture */}
<svg className="hidden">
<filter id="noiseFilter">
@@ -163,7 +121,7 @@ export default function App() {
<nav className="flex flex-col sm:flex-row justify-between items-start sm:items-center border-b-2 border-dashed border-[var(--border-main)] pb-4 gap-4">
<div className="flex items-center gap-2 text-sm text-[var(--text-muted)] relative group">
<Terminal className="w-5 h-5 accent-text transform -rotate-12 group-hover:rotate-12 transition-transform" />
<span className="font-sketch text-2xl text-[var(--text-main)] tracking-wide">aditya_gupta <span className="text-[var(--text-dim)]">//</span> homelab</span>
<span className="font-sketch text-2xl text-[var(--text-main)] tracking-wide">aditya_gupta <span className="text-[var(--text-dim)]">//</span> sortedcord</span>
{/* Hand-drawn annotation */}
<div className="absolute -top-6 -right-16 font-sketch text-sm accent-text rotate-12 flex items-center gap-1 opacity-80">
@@ -173,22 +131,6 @@ export default function App() {
</div>
<div className="flex items-center gap-6">
<div className="flex items-center gap-2">
<span className="font-sketch text-[var(--text-dim)] text-sm rotate-[-2deg]">theme_picker:</span>
<div className="flex gap-2">
{THEMES.map(theme => (
<button
key={theme.id}
onClick={() => setActiveTheme(theme)}
title={`Switch to ${theme.name}`}
className={`w-5 h-5 transition-all duration-200 sketch-border-subtle ${activeTheme.id === theme.id ? 'scale-125 shadow-[0_0_10px_var(--color-accent-muted)]' : 'hover:scale-110'}`}
style={{ backgroundColor: theme.value, borderColor: activeTheme.id === theme.id ? theme.value : 'transparent' }}
aria-label={`Switch theme to ${theme.name}`}
/>
))}
</div>
</div>
<div className="h-6 w-px border-l-2 border-dashed border-[var(--border-main)]" />
<a href="#" className="font-sketch text-lg hover:text-[var(--text-main)] transition-colors flex items-center gap-2 group"><Github className="w-4 h-4 group-hover:-translate-y-1 transition-transform" /> github</a>
<a href="#" className="font-sketch text-lg hover:text-[var(--text-main)] transition-colors flex items-center gap-2 group"><Mail className="w-4 h-4 group-hover:-translate-y-1 transition-transform" /> contact</a>
</div>
@@ -197,9 +139,28 @@ export default function App() {
{/* Hero Section */}
<header className="flex flex-col md:flex-row gap-12 items-center justify-between relative">
<div className="space-y-6 max-w-2xl">
<div className="inline-flex items-center gap-2 px-4 py-1.5 sketch-border bg-[var(--bg-surface)] text-xs mb-4 transform -rotate-1">
<Activity className="w-3 h-3 accent-text animate-pulse" />
<span className="font-sketch text-sm">STATUS: <span className="text-[var(--text-main)]">ONLINE</span></span>
<div className="relative inline-flex mb-4">
<button
type="button"
onClick={() => {
setHasClickedThemePill(true);
setShowThemePillTooltip(true);
}}
className={`inline-flex items-center gap-2 px-4 py-1.5 sketch-border bg-[var(--bg-surface)] text-xs transform -rotate-1 cursor-pointer select-none ${hasClickedThemePill ? '' : 'animate-bounce'}`}
aria-label="Theme pill"
>
<Activity className={`w-3 h-3 accent-text ${hasClickedThemePill ? '' : 'animate-pulse'}`} />
<span className="font-sketch text-sm">YOU GET: <span className="text-[var(--text-main)]">{activeTheme.name}</span></span>
</button>
{showThemePillTooltip && (
<div
role="tooltip"
className="absolute left-1/2 top-full mt-2 -translate-x-1/2 whitespace-nowrap px-3 py-1.5 text-xs font-sketch bg-[var(--bg-panel)] text-[var(--text-main)] sketch-border-subtle shadow-lg"
>
HAHA Made you click
</div>
)}
</div>
<h1 className="text-4xl sm:text-5xl font-bold tracking-tight text-[var(--text-main)] leading-tight">
I engineer <span className="accent-text relative inline-block">
@@ -234,9 +195,6 @@ export default function App() {
{/* System Overview Dashboard (Replaced Interactive Tiling Demo) */}
<section className="space-y-4 relative mt-12 md:mt-0">
<div className="absolute -left-12 top-10 font-sketch accent-text -rotate-90 hidden lg:block tracking-widest text-sm opacity-60">
[ SYSTEM_SPECS ]
</div>
<div className="flex items-center justify-between border-b-2 border-dashed border-[var(--border-main)] pb-2">
<h2 className="font-sketch text-2xl text-[var(--text-main)] flex items-center gap-2">
<Cpu className="w-5 h-5 accent-text" /> system_overview.sh

View File

@@ -87,6 +87,35 @@
}
.dot-grid {
position: fixed;
inset: 0;
pointer-events: none;
}
.dot-grid::before {
content: '';
position: fixed;
inset: 0;
background-image: radial-gradient(var(--grid-pattern) 1px, transparent 1px);
background-size: 24px 24px;
/* Lock Y so scroll never changes it; keep X drift via CSS variable. */
background-position: var(--dot-grid-x, 0px) 0px;
will-change: background-position;
animation: dot-grid-drift-x 3s linear infinite;
}
@keyframes dot-grid-drift-x {
from {
background-position: 0px 0px;
}
to {
background-position: 24px 0px;
}
}
@media (prefers-reduced-motion: reduce) {
.dot-grid::before {
animation: none;
}
}

259
src/themes.ts Normal file
View File

@@ -0,0 +1,259 @@
export type Theme = {
id: string;
name: string;
variant: 'dark' | 'light';
colors: {
accent: string;
bgBase: string;
bgSurface: string;
bgPanel: string;
textMain: string;
textMuted: string;
textDim: string;
borderMain: string;
gridPattern: string;
statusSuccess: string;
catStroke: string;
catNose: string;
catEyes: string;
heartColor: string;
cursor?: string;
};
};
export const THEMES: Theme[] = [
{
id: 'blueprint-default',
name: 'Blueprint (Default)',
variant: 'dark',
colors: {
accent: '#38bdf8',
bgBase: '#111113',
bgSurface: 'rgba(24, 24, 27, 0.4)',
bgPanel: 'rgba(9, 9, 11, 0.85)',
textMain: '#f4f4f5',
textMuted: '#a1a1aa',
textDim: '#52525b',
borderMain: '#27272a',
gridPattern: 'rgba(255, 255, 255, 0.08)',
statusSuccess: '#34d399',
catStroke: '#60a5fa',
catNose: '#f472b6',
catEyes: '#ffffff',
heartColor: '#f472b6',
},
},
{
id: 'acme-light',
name: 'Acme',
variant: 'light',
colors: {
accent: '#aeeeee',
bgBase: '#ffffea',
bgSurface: '#fcfcce',
bgPanel: '#ffffca',
textMain: '#000000',
textMuted: '#303030',
textDim: '#505050',
borderMain: '#303030',
gridPattern: 'rgba(0, 0, 0, 0.10)',
statusSuccess: '#cccc7c',
catStroke: '#000000',
catNose: '#303030',
catEyes: '#000000',
heartColor: '#303030',
cursor: '#000000',
},
},
{
id: 'ayaka-dark',
name: 'Ayaka',
variant: 'dark',
colors: {
accent: '#71ADE9',
bgBase: '#36283d',
bgSurface: 'rgba(54, 40, 61, 0.72)',
bgPanel: 'rgba(54, 40, 61, 0.88)',
textMain: '#cedaeb',
textMuted: '#9098a4',
textDim: 'rgba(206, 218, 235, 0.45)',
borderMain: 'rgba(206, 218, 235, 0.18)',
gridPattern: 'rgba(255, 255, 255, 0.08)',
statusSuccess: '#E59DB1',
catStroke: '#8BB8E9',
catNose: '#E1B4CE',
catEyes: '#FFFEFE',
heartColor: '#E1B4CE',
cursor: '#cedaeb',
},
}
,
{
id: 'borland',
name: 'Borland',
variant: 'dark',
colors: {
accent: '#96CBFE',
bgBase: '#0000A4',
bgSurface: 'rgba(0, 0, 164, 0.72)',
bgPanel: 'rgba(0, 0, 164, 0.88)',
textMain: '#FFFF4E',
textMuted: '#FFFFCC',
textDim: 'rgba(255, 255, 78, 0.45)',
borderMain: 'rgba(255, 255, 78, 0.22)',
gridPattern: 'rgba(255, 255, 255, 0.08)',
statusSuccess: '#A8FF60',
catStroke: '#C6C5FE',
catNose: '#FF73FD',
catEyes: '#FFFFFF',
heartColor: '#FF73FD',
cursor: '#FFFF4E',
},
}
,
{
id: 'catppuccin-latte',
name: 'Catppuccin Latte',
variant: 'light',
colors: {
accent: '#1E66F5',
bgBase: '#EFF1F5',
bgSurface: 'rgba(239, 241, 245, 0.72)',
bgPanel: 'rgba(239, 241, 245, 0.88)',
textMain: '#4C4F69',
textMuted: '#5C5F77',
textDim: 'rgba(76, 79, 105, 0.45)',
borderMain: 'rgba(76, 79, 105, 0.22)',
gridPattern: 'rgba(0, 0, 0, 0.08)',
statusSuccess: '#40A02B',
catStroke: '#5C5F77',
catNose: '#EA76CB',
catEyes: '#4C4F69',
heartColor: '#D20F39',
cursor: '#4C4F69',
},
}
,
{
id: 'dracula',
name: 'Dracula',
variant: 'dark',
colors: {
accent: '#BD93F9',
bgBase: '#282A36',
bgSurface: 'rgba(40, 42, 54, 0.72)',
bgPanel: 'rgba(40, 42, 54, 0.88)',
textMain: '#F8F8F2',
textMuted: 'rgba(248, 248, 242, 0.74)',
textDim: 'rgba(248, 248, 242, 0.45)',
borderMain: 'rgba(248, 248, 242, 0.18)',
gridPattern: 'rgba(255, 255, 255, 0.08)',
statusSuccess: '#50FA7B',
catStroke: '#BD93F9',
catNose: '#FF79C6',
catEyes: '#F8F8F2',
heartColor: '#FF79C6',
cursor: '#F8F8F2',
},
}
,
{
id: 'gruvbox',
name: 'Gruvbox',
variant: 'light',
colors: {
accent: '#458588',
bgBase: '#FBF1C7',
bgSurface: 'rgba(251, 241, 199, 0.72)',
bgPanel: 'rgba(251, 241, 199, 0.88)',
textMain: '#3C3836',
textMuted: '#7C6F64',
textDim: 'rgba(60, 56, 54, 0.45)',
borderMain: 'rgba(60, 56, 54, 0.22)',
gridPattern: 'rgba(0, 0, 0, 0.08)',
statusSuccess: '#98971A',
catStroke: '#3C3836',
catNose: '#B16286',
catEyes: '#3C3836',
heartColor: '#CC241D',
cursor: '#3C3836',
},
}
,
{
id: 'gruvbox-dark',
name: 'Gruvbox Dark',
variant: 'dark',
colors: {
accent: '#83A598',
bgBase: '#282828',
bgSurface: 'rgba(40, 40, 40, 0.72)',
bgPanel: 'rgba(40, 40, 40, 0.88)',
textMain: '#EBDBB2',
textMuted: '#A89984',
textDim: 'rgba(235, 219, 178, 0.45)',
borderMain: 'rgba(235, 219, 178, 0.18)',
gridPattern: 'rgba(255, 255, 255, 0.08)',
statusSuccess: '#B8BB26',
catStroke: '#83A598',
catNose: '#D3869B',
catEyes: '#EBDBB2',
heartColor: '#FB4934',
cursor: '#EBDBB2',
},
}
,
{
id: 'nord',
name: 'Nord',
variant: 'dark',
colors: {
accent: '#88C0D0',
bgBase: '#2E3440',
bgSurface: 'rgba(46, 52, 64, 0.72)',
bgPanel: 'rgba(46, 52, 64, 0.88)',
textMain: '#D8DEE9',
textMuted: '#E5E9F0',
textDim: 'rgba(216, 222, 233, 0.45)',
borderMain: 'rgba(216, 222, 233, 0.18)',
gridPattern: 'rgba(255, 255, 255, 0.08)',
statusSuccess: '#A3BE8C',
catStroke: '#81A1C1',
catNose: '#B48EAD',
catEyes: '#ECEFF4',
heartColor: '#BF616A',
cursor: '#D8DEE9',
},
}
,
{
id: 'rose-pine',
name: 'Rosé Pine',
variant: 'dark',
colors: {
accent: '#C4A7E7',
bgBase: '#191724',
bgSurface: 'rgba(25, 23, 36, 0.72)',
bgPanel: 'rgba(25, 23, 36, 0.88)',
textMain: '#E0DEF4',
textMuted: '#EBBCBA',
textDim: 'rgba(224, 222, 244, 0.45)',
borderMain: 'rgba(224, 222, 244, 0.18)',
gridPattern: 'rgba(255, 255, 255, 0.08)',
statusSuccess: '#9CCFD8',
catStroke: '#9CCFD8',
catNose: '#EB6F92',
catEyes: '#E0DEF4',
heartColor: '#EB6F92',
cursor: '#E0DEF4',
},
}
];