66 XCircleIcon ,
77 ArrowUpRightIcon ,
88} from '@phosphor-icons/react' ;
9- import { motion , AnimatePresence } from 'framer-motion' ;
9+ import { motion } from 'framer-motion' ;
1010import { vocabulary } from '../data/vocabulary' ;
1111import Seo from '../components/Seo' ;
1212import { useSidePanel } from '../context/SidePanelContext' ;
@@ -75,10 +75,14 @@ const VocabPage = () => {
7575 } ) ) . sort ( ( a , b ) => a . title . localeCompare ( b . title ) ) ,
7676 [ ] ) ;
7777
78- const filteredEntries = vocabEntries . filter ( ( entry ) =>
79- entry . title . toLowerCase ( ) . includes ( searchQuery . toLowerCase ( ) ) ||
80- entry . slug . toLowerCase ( ) . includes ( searchQuery . toLowerCase ( ) )
81- ) ;
78+ const filteredEntries = useMemo ( ( ) => {
79+ const query = searchQuery . toLowerCase ( ) . trim ( ) ;
80+ if ( ! query ) return vocabEntries ;
81+ return vocabEntries . filter ( ( entry ) =>
82+ entry . title . toLowerCase ( ) . includes ( query ) ||
83+ entry . slug . toLowerCase ( ) . includes ( query )
84+ ) ;
85+ } , [ vocabEntries , searchQuery ] ) ;
8286
8387 const groupedEntries = useMemo ( ( ) => {
8488 const groups = { } ;
@@ -90,7 +94,7 @@ const VocabPage = () => {
9094 return groups ;
9195 } , [ filteredEntries ] ) ;
9296
93- const alphabet = Object . keys ( groupedEntries ) . sort ( ) ;
97+ const alphabet = useMemo ( ( ) => Object . keys ( groupedEntries ) . sort ( ) , [ groupedEntries ] ) ;
9498
9599 const handleOpenVocab = ( entry ) => {
96100 const LazyComponent = React . lazy ( entry . loader ) ;
@@ -113,134 +117,133 @@ const VocabPage = () => {
113117 }
114118 } ;
115119
116- return (
117- < div className = "min-h-screen bg-[#050505] text-[#f4f4f4] selection:bg-emerald-500/30 font-sans" >
118- < Seo
119- title = "Glossary | Fezcodex"
120- description = "A dictionary of technical concepts and patterns."
121- keywords = { [ 'Fezcodex' , 'vocabulary' , 'glossary' , 'definitions' ] }
122- />
123-
124- { /* Bauhaus Grid Background Pattern */ }
125- < div className = "fixed inset-0 pointer-events-none opacity-[0.02] z-0"
126- style = { {
127- backgroundImage : 'linear-gradient(#ffffff 1px, transparent 1px), linear-gradient(90deg, #ffffff 1px, transparent 1px)' ,
128- backgroundSize : '40px 40px'
129- } }
130- />
131- < div className = "relative z-10 mx-auto max-w-7xl px-6 py-24 md:px-12" >
132- { /* Header Section */ }
133- < header className = "mb-24 flex flex-col items-start" >
134- < Link
135- to = "/"
136- className = "mb-12 inline-flex items-center gap-3 text-md font-instr-sans text-gray-500 hover:text-white transition-colors"
137- >
138- < ArrowLeftIcon size = { 16 } />
139- < span > Fezcodex Index</ span >
140- </ Link >
141-
142- < h1 className = "text-7xl md:text-9xl font-instr-serif italic tracking-tight mb-6 text-white" >
143- Glossary
144- </ h1 >
145- < p className = "text-xl font-light text-gray-400 max-w-2xl font-instr-sans" >
146- A curated collection of technical concepts, design patterns, and terminology.
147- </ p >
148- </ header >
149-
150- { /* Sticky Search & Nav */ }
151- < div className = "sticky top-6 z-30 mb-20" >
152- < div className = "bg-[#0a0a0a]/80 backdrop-blur-xl border border-white/10 shadow-lg rounded-2xl p-2 flex flex-col md:flex-row items-center gap-4" >
153- < div className = "relative w-full md:w-96" >
154- < MagnifyingGlassIcon size = { 18 } className = "absolute left-4 top-1/2 -translate-y-1/2 text-gray-500" />
155- < input
156- type = "text"
157- placeholder = "Search terms..."
158- value = { searchQuery }
159- onChange = { ( e ) => setSearchQuery ( e . target . value ) }
160- className = "w-full bg-transparent text-lg font-instr-sans placeholder-gray-600 focus:outline-none py-3 pl-12 pr-4 text-white"
161- />
162- { searchQuery && (
163- < button onClick = { ( ) => setSearchQuery ( '' ) } className = "absolute right-4 top-1/2 -translate-y-1/2 text-gray-500 hover:text-red-500" >
164- < XCircleIcon size = { 18 } weight = "fill" />
165- </ button >
166- ) }
167- </ div >
168-
169- < div className = "h-8 w-px bg-white/10 hidden md:block" />
170-
171- < div className = "flex flex-wrap gap-1 justify-center md:justify-start px-2 py-2 md:py-0 w-full overflow-x-auto no-scrollbar" >
172- { alphabet . map ( letter => (
173- < button
174- key = { letter }
175- onClick = { ( ) => scrollToLetter ( letter ) }
176- className = "w-8 h-8 flex items-center justify-center rounded-full text-xs font-bold text-gray-500 hover:bg-white hover:text-black transition-all font-mono"
177- >
178- { letter }
120+ return (
121+ < div className = "min-h-screen bg-[#050505] text-[#f4f4f4] selection:bg-emerald-500/30 font-sans relative overflow-x-hidden" >
122+ < Seo
123+ title = "Glossary | Fezcodex"
124+ description = "A dictionary of technical concepts and patterns."
125+ keywords = { [ 'Fezcodex' , 'vocabulary' , 'glossary' , 'definitions' ] }
126+ />
127+
128+ { /* Bauhaus Grid Background Pattern */ }
129+ < div className = "fixed inset-0 pointer-events-none opacity-[0.02] z-0"
130+ style = { {
131+ backgroundImage : 'linear-gradient(#ffffff 1px, transparent 1px), linear-gradient(90deg, #ffffff 1px, transparent 1px)' ,
132+ backgroundSize : '40px 40px'
133+ } }
134+ />
135+
136+ < div className = "relative z-10 mx-auto max-w-7xl px-6 py-24 md:px-12 flex flex-col" >
137+ { /* Header Section */ }
138+ < header className = "mb-24 flex flex-col items-start shrink-0" >
139+ < Link
140+ to = "/"
141+ className = "mb-12 inline-flex items-center gap-3 text-md font-instr-sans text-gray-500 hover:text-white transition-colors"
142+ >
143+ < ArrowLeftIcon size = { 16 } />
144+ < span > Fezcodex Index</ span >
145+ </ Link >
146+
147+ < h1 className = "text-7xl md:text-9xl font-instr-serif italic tracking-tight mb-6 text-white leading-none" >
148+ Glossary
149+ </ h1 >
150+ < p className = "text-xl font-light text-gray-400 max-w-2xl font-instr-sans" >
151+ A curated collection of technical concepts, design patterns, and terminology.
152+ </ p >
153+ </ header >
154+
155+ { /* Sticky Search & Nav */ }
156+ < div className = "sticky top-6 z-30 mb-20 shrink-0" >
157+ < div className = "bg-[#0a0a0a]/80 backdrop-blur-xl border border-white/10 shadow-2xl rounded-2xl p-2 flex flex-col md:flex-row items-center gap-4" >
158+ < div className = "relative w-full md:w-96" >
159+ < MagnifyingGlassIcon size = { 18 } className = "absolute left-4 top-1/2 -translate-y-1/2 text-gray-500" />
160+ < input
161+ type = "text"
162+ placeholder = "Search terms..."
163+ value = { searchQuery }
164+ onChange = { ( e ) => setSearchQuery ( e . target . value ) }
165+ className = "w-full bg-transparent text-lg font-instr-sans placeholder-gray-600 focus:outline-none py-3 pl-12 pr-4 text-white"
166+ />
167+ { searchQuery && (
168+ < button onClick = { ( ) => setSearchQuery ( '' ) } className = "absolute right-4 top-1/2 -translate-y-1/2 text-gray-500 hover:text-red-500" >
169+ < XCircleIcon size = { 18 } weight = "fill" />
179170 </ button >
180- ) ) }
181- </ div >
182- </ div >
183- </ div >
184-
185- { /* Content */ }
186- < div className = "pb-48 space-y-20" >
187- < AnimatePresence mode = "popLayout" >
188- { alphabet . map ( ( letter ) => (
189- < motion . section
190- key = { letter }
191- id = { `letter-${ letter } ` }
192- initial = { { opacity : 0 , y : 20 } }
193- whileInView = { { opacity : 1 , y : 0 } }
194- viewport = { { once : true , margin : "-100px" } }
195- transition = { { duration : 0.5 , ease : "easeOut" } }
196- >
197- < div className = "flex items-baseline gap-6 mb-10 border-b border-white/10 pb-4" >
198- < h2 className = "text-6xl font-instr-serif italic text-white/20" > { letter } </ h2 >
199- </ div >
200-
201- < div className = "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-x-8 gap-y-8" >
202- { groupedEntries [ letter ] . map ( ( entry ) => (
203- < motion . button
204- key = { entry . slug }
205- layout
206- onClick = { ( ) => handleOpenVocab ( entry ) }
207- className = "group flex flex-col text-left p-8 bg-[#0a0a0a] border border-white/5 hover:border-white/20 hover:shadow-2xl hover:shadow-emerald-900/10 hover:-translate-y-1 transition-all duration-300 rounded-xl relative overflow-hidden"
208- >
209- { /* Procedural Bauhaus Background */ }
210- < div className = "absolute inset-0 opacity-40 group-hover:opacity-100 transition-opacity duration-700" >
211- < BauhausShapes seed = { entry . slug } />
212- </ div >
213-
214- { /* Decorative Bauhaus Shape Overlay */ }
215- < div className = "absolute top-0 right-0 w-24 h-24 bg-gradient-to-bl from-white/5 to-transparent rounded-bl-full opacity-0 group-hover:opacity-100 transition-opacity duration-500" />
216-
217- < div className = "flex justify-between items-start w-full mb-6 relative z-10" > < span className = "font-mono text-[10px] text-gray-500 uppercase tracking-widest group-hover:text-emerald-400 transition-colors" >
218- { entry . slug }
219- </ span >
220- < ArrowUpRightIcon
221- size = { 18 }
222- className = "text-gray-600 group-hover:text-white transition-colors"
223- />
224- </ div >
225-
226- < h3 className = "text-2xl font-instr-serif text-white mb-3 group-hover:underline decoration-1 underline-offset-4 decoration-white/30 relative z-10" >
227- { entry . title }
228- </ h3 >
229- </ motion . button >
171+ ) }
172+ </ div >
173+
174+ < div className = "h-8 w-px bg-white/10 hidden md:block" />
175+
176+ < div className = "flex flex-wrap gap-1 justify-center md:justify-start px-2 py-2 md:py-0 w-full overflow-x-auto no-scrollbar" >
177+ { alphabet . map ( letter => (
178+ < button
179+ key = { letter }
180+ onClick = { ( ) => scrollToLetter ( letter ) }
181+ className = "w-8 h-8 flex items-center justify-center rounded-full text-xs font-bold text-gray-500 hover:bg-white hover:text-black transition-all font-mono"
182+ >
183+ { letter }
184+ </ button >
230185 ) ) }
231- </ div >
232- </ motion . section >
233- ) ) }
234- </ AnimatePresence >
235-
236- { filteredEntries . length === 0 && (
237- < div className = "py-32 text-center" >
238- < p className = "font-instr-serif italic text-2xl text-gray-600" > No definitions found for "{ searchQuery } "</ p >
186+ </ div >
187+ </ div >
188+ </ div >
189+
190+ { /* Content Container - Shrinks with content */ }
191+ < div className = "flex-1 space-y-24 mb-32" >
192+ { alphabet . map ( ( letter ) => (
193+ < motion . section
194+ key = { letter }
195+ id = { `letter-${ letter } ` }
196+ initial = { { opacity : 0 } }
197+ animate = { { opacity : 1 } }
198+ className = "scroll-mt-40"
199+ >
200+ < div className = "flex items-baseline gap-6 mb-10 border-b border-white/10 pb-4" >
201+ < h2 className = "text-6xl font-instr-serif italic text-white/20" > { letter } </ h2 >
239202 </ div >
240- ) }
241- </ div >
203+
204+ < div className = "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8" >
205+ { groupedEntries [ letter ] . map ( ( entry ) => (
206+ < button
207+ key = { entry . slug }
208+ onClick = { ( ) => handleOpenVocab ( entry ) }
209+ className = "group flex flex-col text-left p-8 bg-[#0a0a0a] border border-white/5 hover:border-white/20 hover:shadow-2xl hover:shadow-emerald-900/10 transition-all duration-300 rounded-xl relative overflow-hidden h-full"
210+ >
211+ { /* Procedural Bauhaus Background */ }
212+ < div className = "absolute inset-0 opacity-40 group-hover:opacity-100 transition-opacity duration-700 pointer-events-none" >
213+ < BauhausShapes seed = { entry . slug } />
214+ </ div >
215+
216+ { /* Decorative Bauhaus Shape Overlay */ }
217+ < div className = "absolute top-0 right-0 w-24 h-24 bg-gradient-to-bl from-white/5 to-transparent rounded-bl-full opacity-0 group-hover:opacity-100 transition-opacity duration-500" />
218+
219+ < div className = "flex justify-between items-start w-full mb-6 relative z-10" >
220+ < span className = "font-mono text-[10px] text-gray-500 uppercase tracking-widest group-hover:text-emerald-400 transition-colors" >
221+ { entry . slug }
222+ </ span >
223+ < ArrowUpRightIcon
224+ size = { 18 }
225+ className = "text-gray-600 group-hover:text-white transition-colors"
226+ />
227+ </ div >
228+
229+ < h3 className = "text-2xl font-instr-serif text-white mb-3 group-hover:underline decoration-1 underline-offset-4 decoration-white/30 relative z-10" >
230+ { entry . title }
231+ </ h3 >
232+ </ button >
233+ ) ) }
234+ </ div >
235+ </ motion . section >
236+ ) ) }
237+
238+ { filteredEntries . length === 0 && (
239+ < div className = "py-32 text-center" >
240+ < p className = "font-instr-serif italic text-2xl text-gray-600" > No definitions found for "{ searchQuery } "</ p >
241+ </ div >
242+ ) }
242243 </ div >
243244 </ div >
244- ) ; } ;
245+ </ div >
246+ ) ;
247+ } ;
245248
246249export default VocabPage ;
0 commit comments