11import React , { useState , useEffect } from 'react' ;
2- import ReactMarkdown from 'react-markdown' ;
3- import remarkGfm from 'remark-gfm' ;
4- import rehypeRaw from 'rehype-raw' ;
2+ import { motion , AnimatePresence } from 'framer-motion' ;
53import { Link } from 'react-router-dom' ;
64import {
7- ArrowLeftIcon ,
8- ArrowSquareOutIcon ,
9- EnvelopeIcon ,
5+ ArrowLeft ,
6+ Desktop ,
7+ Graph ,
8+ Terminal ,
9+ Article ,
1010} from '@phosphor-icons/react' ;
11- import useSeo from '../hooks/useSeo' ;
12- import piml from 'piml' ;
11+ import NeuromancerHUD from './about-views/NeuromancerHUD' ;
12+ import InteractiveDesk from './about-views/InteractiveDesk' ;
13+ import MindMapConstellation from './about-views/MindMapConstellation' ;
14+ import SimpleText from './about-views/SimpleText' ;
1315import { useAchievements } from '../context/AchievementContext' ;
1416
15- const LinkRenderer = ( { href, children } ) => {
16- const isExternal = href . startsWith ( 'http' ) || href . startsWith ( 'https' ) ;
17+ const ViewSwitcher = ( { currentView, setView } ) => {
18+ const views = [
19+ { id : 'simple' , icon : Article , label : 'Simple' } ,
20+ { id : 'hud' , icon : Terminal , label : 'Terminal' } ,
21+ { id : 'desk' , icon : Desktop , label : 'Desk' } ,
22+ { id : 'map' , icon : Graph , label : 'Mind Map' } ,
23+ ] ;
24+
1725 return (
18- < a
19- href = { href }
20- className = "text-primary-400 hover:text-primary-600 transition-colors inline-flex items-center gap-1"
21- target = { isExternal ? '_blank' : undefined }
22- rel = { isExternal ? 'noopener noreferrer' : undefined }
23- >
24- { children } { isExternal && < ArrowSquareOutIcon className = "text-xs" /> }
25- </ a >
26+ < div className = "fixed bottom-8 left-1/2 -translate-x-1/2 z-50 bg-black/50 backdrop-blur-md p-2 rounded-full border border-white/10 shadow-2xl flex gap-2" >
27+ { views . map ( ( view ) => (
28+ < button
29+ key = { view . id }
30+ onClick = { ( ) => setView ( view . id ) }
31+ className = { `relative px-4 py-2 rounded-full flex items-center gap-2 transition-all ${
32+ currentView === view . id
33+ ? 'bg-white text-black font-bold shadow-lg'
34+ : 'text-white/60 hover:text-white hover:bg-white/10'
35+ } `}
36+ >
37+ < view . icon size = { 20 } />
38+ < span className = "text-sm hidden md:inline" > { view . label } </ span >
39+ { currentView === view . id && (
40+ < motion . div
41+ layoutId = "view-pill"
42+ className = "absolute inset-0 bg-white rounded-full mix-blend-difference -z-10"
43+ />
44+ ) }
45+ </ button >
46+ ) ) }
47+ </ div >
2648 ) ;
2749} ;
2850
2951const AboutPage = ( ) => {
30- const [ content , setContent ] = useState ( '' ) ;
31- const [ email , setEmail ] = useState ( '' ) ;
32- const [ title , setTitle ] = useState ( 'About Me' ) ;
33- const [ loading , setLoading ] = useState ( true ) ;
52+ const [ view , setView ] = useState ( 'simple' ) ;
3453 const { unlockAchievement } = useAchievements ( ) ;
3554
36- useSeo ( {
37- title : `${ title } | Fezcodex` ,
38- description :
39- 'Learn more about Fezcodex, the developer behind this website.' ,
40- keywords : [
41- 'Fezcodex' ,
42- 'about' ,
43- 'portfolio' ,
44- 'developer' ,
45- 'software engineer' ,
46- ] ,
47- ogTitle : `${ title } | Fezcodex` ,
48- ogDescription :
49- 'Learn more about Fezcodex, the developer behind this website.' ,
50- ogImage : 'https://fezcode.github.io/logo512.png' ,
51- twitterCard : 'summary_large_image' ,
52- twitterTitle : `${ title } | Fezcodex` ,
53- twitterDescription :
54- 'Learn more about Fezcodex, the developer behind this website.' ,
55- twitterImage : 'https://fezcode.github.io/logo512.png' ,
56- } ) ;
57-
5855 useEffect ( ( ) => {
5956 unlockAchievement ( 'curious_soul' ) ;
60- const fetchAboutContent = async ( ) => {
61- try {
62- const [ metaResponse , contentResponse ] = await Promise . all ( [
63- fetch ( '/about-me/about.piml' ) ,
64- fetch ( '/about-me/about.txt' ) ,
65- ] ) ;
66-
67- let attributes = { } ;
68- if ( metaResponse . ok ) {
69- const pimlText = await metaResponse . text ( ) ;
70- attributes = piml . parse ( pimlText ) ;
71- } else {
72- console . error ( 'Failed to fetch about.piml' ) ;
73- }
74-
75- let body = '' ;
76- if ( contentResponse . ok ) {
77- body = await contentResponse . text ( ) ;
78- } else {
79- console . error ( 'Failed to fetch about.txt' ) ;
80- }
81-
82- setTitle ( attributes . title || 'About Me' ) ;
83- setEmail ( attributes . email || '' ) ;
84- setContent ( body ) ;
85- } catch ( err ) {
86- console . error ( 'Error fetching about page content:' , err ) ;
87- } finally {
88- setLoading ( false ) ;
89- }
57+ // Hide overflow on body when this component is mounted
58+ document . body . style . overflow = 'hidden' ;
59+ return ( ) => {
60+ document . body . style . overflow = 'auto' ;
9061 } ;
91-
92- fetchAboutContent ( ) ;
93- } , [ ] ) ;
94-
95- if ( loading ) {
96- // Skeleton loading screen for AboutPage
97- return (
98- < div className = "py-16 sm:py-24" >
99- < div className = "mx-auto max-w-7xl px-6 lg:px-8 text-gray-300" >
100- < div className = "border border-gray-700 p-8 rounded-lg shadow-xl flex" >
101- < div className = "w-1 bg-gray-600 mr-1 hidden sm:block" > </ div >
102- < div className = "w-1 bg-gray-700 mr-1 hidden sm:block" > </ div >
103- < div className = "w-1 bg-gray-800 mr-8 hidden sm:block" > </ div >
104- < div className = "flex-grow" >
105- < div className = "h-10 bg-gray-800 rounded w-3/4 mb-8 animate-pulse" > </ div >
106- < div className = "space-y-4" >
107- < div className = "h-6 bg-gray-800 rounded w-full animate-pulse" > </ div >
108- < div className = "h-6 bg-gray-800 rounded w-5/6 animate-pulse" > </ div >
109- < div className = "h-6 bg-gray-800 rounded w-full animate-pulse" > </ div >
110- < div className = "h-6 bg-gray-800 rounded w-2/3 animate-pulse" > </ div >
111- </ div >
112- < div className = "mt-8" >
113- < div className = "h-8 bg-gray-800 rounded w-1/4 mb-4 animate-pulse" > </ div >
114- < div className = "h-6 bg-gray-800 rounded w-1/2 animate-pulse" > </ div >
115- </ div >
116- </ div >
117- </ div >
118- </ div >
119- </ div >
120- ) ;
121- }
62+ } , [ unlockAchievement ] ) ;
12263
12364 return (
124- < div className = "py-8 sm:py-16" >
125- < div className = "mx-auto max-w-7xl px-6 lg:px-8 text-gray-300" >
65+ < div className = "fixed inset-0 z-[100] bg-black overflow-hidden" >
66+ { /* Global Back Button */ }
67+ < motion . div
68+ initial = { { opacity : 0 , y : - 20 } }
69+ animate = { { opacity : 1 , y : 0 } }
70+ className = "fixed top-6 left-6 z-50"
71+ >
12672 < Link
12773 to = "/"
128- className = "group text-primary-400 hover:underline flex items-center justify-center gap-2 text-lg mb-4"
74+ className = { `group flex items-center gap-2 px-4 py-2 backdrop-blur-md border transition-all ${
75+ view === 'simple'
76+ ? 'bg-transparent text-black border-black border-2 font-mono uppercase tracking-widest text-xs hover:bg-[#4a0404] hover:text-white hover:border-[#4a0404] rounded-none'
77+ : 'bg-black/50 text-white border-white/10 hover:bg-white hover:text-black rounded-full font-bold'
78+ } `}
12979 >
130- < ArrowLeftIcon className = "text-xl transition-transform group-hover:-translate-x-1" /> { ' ' }
131- Back to Home
80+ < ArrowLeft weight = "bold" className = " group-hover:-translate-x-1 transition-transform " />
81+ < span className = "hidden sm:inline" > Back to Reality </ span >
13282 </ Link >
133- < div className = "border border-gray-700 p-8 rounded-lg shadow-xl flex" >
134- < div className = "w-1 bg-gray-600 mr-1 hidden sm:block" > </ div >
135- < div className = "w-1 bg-gray-700 mr-1 hidden sm:block" > </ div >
136- < div className = "w-1 bg-gray-800 mr-8 hidden sm:block" > </ div >
137- < div className = "flex-grow" >
138- < h1 className = "text-4xl font-bold tracking-tight text-primary-400 sm:text-6xl mb-8" >
139- { title }
140- </ h1 >
83+ </ motion . div >
14184
142- < div className = "prose prose-invert max-w-none leading-snug font-mono" >
143- < ReactMarkdown
144- remarkPlugins = { [ remarkGfm ] }
145- rehypePlugins = { [ rehypeRaw ] }
146- components = { { a : LinkRenderer } }
147- >
148- { content }
149- </ ReactMarkdown >
150- </ div >
85+ { /* View Container */ }
86+ < AnimatePresence mode = "wait" >
87+ < motion . div
88+ key = { view }
89+ initial = { { opacity : 0 } }
90+ animate = { { opacity : 1 } }
91+ exit = { { opacity : 0 } }
92+ transition = { { duration : 0.5 } }
93+ className = "w-full h-full"
94+ >
95+ { view === 'hud' && < NeuromancerHUD /> }
96+ { view === 'desk' && < InteractiveDesk /> }
97+ { view === 'map' && < MindMapConstellation /> }
98+ { view === 'simple' && < SimpleText /> }
99+ </ motion . div >
100+ </ AnimatePresence >
151101
152- { email && (
153- < div className = "mt-8" >
154- < h2 className = "text-3xl font-semibold tracking-tight text-white mb-4" >
155- Contact
156- </ h2 >
157- < p className = "flex items-center gap-2" >
158- < EnvelopeIcon className = "text-primary-400" /> Feel free to
159- reach out to me at{ ' ' }
160- < a
161- href = { `mailto:${ email } ` }
162- className = "text-primary-400 hover:text-primary-500 transition-colors"
163- >
164- { email }
165- </ a >
166- .
167- </ p >
168- </ div >
169- ) }
170- </ div >
171- </ div >
172- </ div >
102+ { /* Switcher Controls */ }
103+ < ViewSwitcher currentView = { view } setView = { setView } />
173104 </ div >
174105 ) ;
175106} ;
176107
177- export default AboutPage ;
108+ export default AboutPage ;
0 commit comments