Files
mi-sitio-personal/src/App.tsx
Aditya Gupta cfee953dee feat: Implement IP to color scheme mapping
closes #2
- updated page title
- attatched modal to "YOU GET" pill
2026-02-23 13:23:59 +05:30

165 lines
6.7 KiB
TypeScript

import { useState, useEffect } from 'react';
import './App.css';
import { THEMES } from './themes';
import { CatEasterEgg } from './components/CatEasterEgg';
import { FooterSection } from './components/FooterSection';
import { EducationSection } from './components/EducationSection';
import { HeroSection } from './components/HeroSection';
import { LabNotesSection } from './components/LabNotesSection';
import { NavigationBar } from './components/NavigationBar';
import { ProjectsSection } from './components/ProjectsSection';
import { ResumeSection } from './components/ResumeSection';
import { SystemOverviewSection } from './components/SystemOverviewSection';
import { deterministicIpValue } from './utils';
import { ThemeMapperModal } from './components/ThemeMapperModal';
export default function App() {
const [activeTheme, setActiveTheme] = useState(() => THEMES[0]);
const [viewerIp, setViewerIp] = useState<string | null>(null);
const [deterministicIndex, setDeterministicIndex] = useState<number | null>(null);
const [mousePos, setMousePos] = useState({ x: 0, y: 0 });
const [hasClickedThemePill, setHasClickedThemePill] = useState(false);
const [showThemePillTooltip, setShowThemePillTooltip] = useState(false);
const [cpuModeEnabled, setCpuModeEnabled] = useState(false);
const [showThemeModal, setShowThemeModal] = useState(false);
useEffect(() => {
const shouldRandomize = import.meta.env.DEV && import.meta.env.VITE_RANDOMIZE_THEME === 'true';
if (shouldRandomize) {
const idx = Math.floor(Math.random() * THEMES.length);
setActiveTheme(THEMES[idx]);
return;
}
const setDeterministicTheme = async () => {
try {
const response = await fetch('https://api.ipify.org?format=json');
if (!response.ok) {
throw new Error('Failed to fetch IP address');
}
const data = await response.json();
const ip = typeof data.ip === 'string' ? data.ip : '';
const nextDeterministicIndex = deterministicIpValue(ip, THEMES.length);
console.log('[theme] viewer ip', ip, 'deterministic index', nextDeterministicIndex);
setViewerIp(ip);
setDeterministicIndex(nextDeterministicIndex);
const themeIndex = nextDeterministicIndex - 1;
setActiveTheme(THEMES[Math.max(0, Math.min(themeIndex, THEMES.length - 1))]);
} catch {
const fallbackIndex = Math.floor(Math.random() * THEMES.length);
setViewerIp(null);
setDeterministicIndex(null);
setActiveTheme(THEMES[fallbackIndex]);
}
};
void setDeterministicTheme();
}, []);
// Update CSS variables for dynamic theming
useEffect(() => {
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
useEffect(() => {
const handleMouseMove = (e: MouseEvent) => {
setMousePos({ x: e.clientX, y: e.clientY });
};
window.addEventListener('mousemove', handleMouseMove);
return () => window.removeEventListener('mousemove', handleMouseMove);
}, []);
useEffect(() => {
if (!showThemePillTooltip) return;
const t = window.setTimeout(() => setShowThemePillTooltip(false), 1400);
return () => window.clearTimeout(t);
}, [showThemePillTooltip]);
return (
<div className="min-h-screen bg-(--bg-base) text-(--text-main) font-mono selection:bg-(--color-accent) selection:text-(--bg-base) relative overflow-x-hidden">
{/* <svg className="hidden">
<filter id="noiseFilter">
<feTurbulence type="fractalNoise" baseFrequency="0.7" numOctaves="3" stitchTiles="stitch" />
</filter>
</svg> */}
<div className="fixed inset-0 z-0 pointer-events-none opacity-[0.04]" style={{ filter: 'url(#noiseFilter)' }} />
{/* Blueprint Dot Grid Background */}
<div className={`fixed inset-0 dot-grid z-0 pointer-events-none opacity-50 ${cpuModeEnabled ? 'dot-grid-static' : ''}`} />
{/* Subtle mouse glow */}
<div
className="fixed inset-0 z-0 pointer-events-none opacity-20 transition-opacity duration-300"
style={{
background: `radial-gradient(400px circle at ${mousePos.x}px ${mousePos.y}px, var(--color-accent-muted), transparent 40%)`
}}
/>
<CatEasterEgg />
<div className="relative z-10 max-w-5xl mx-auto px-6 py-12 flex flex-col gap-16">
<NavigationBar
cpuModeEnabled={cpuModeEnabled}
onCpuModeToggle={() => setCpuModeEnabled(prev => !prev)}
/>
<HeroSection
activeThemeName={activeTheme.name}
hasClickedThemePill={hasClickedThemePill}
showThemePillTooltip={showThemePillTooltip}
onThemePillClick={() => {
setHasClickedThemePill(true);
setShowThemePillTooltip(true);
setShowThemeModal(true);
}}
cpuModeEnabled={cpuModeEnabled}
/>
<SystemOverviewSection />
<div className="grid grid-cols-1 gap-12 md:grid-cols-7">
<div className="md:col-span-5">
<EducationSection />
</div>
<div className="md:col-span-2">
<ResumeSection />
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-12 mt-8">
<ProjectsSection />
<LabNotesSection />
</div>
<FooterSection />
</div>
<ThemeMapperModal
isOpen={showThemeModal}
viewerIp={viewerIp}
deterministicIndex={deterministicIndex}
themeName={activeTheme.name}
onClose={() => setShowThemeModal(false)}
/>
</div>
);
}