@@ -2,18 +2,67 @@ import React, { useState, useMemo } from 'react';
22import { Link } from 'react-router-dom' ;
33import {
44 ArrowLeftIcon ,
5- BookBookmarkIcon ,
65 MagnifyingGlassIcon ,
76 XCircleIcon ,
8- ArrowSquareOutIcon ,
9- FileTextIcon ,
7+ ArrowUpRightIcon ,
108} from '@phosphor-icons/react' ;
119import { motion , AnimatePresence } from 'framer-motion' ;
1210import { vocabulary } from '../data/vocabulary' ;
1311import Seo from '../components/Seo' ;
1412import { useSidePanel } from '../context/SidePanelContext' ;
15- import BreadcrumbTitle from '../components/BreadcrumbTitle' ;
16- import GenerativeArt from '../components/GenerativeArt' ;
13+
14+ const BauhausShapes = ( { seed } ) => {
15+ const shapes = useMemo ( ( ) => {
16+ // Simple deterministic RNG based on string seed
17+ let hash = 0 ;
18+ for ( let i = 0 ; i < seed . length ; i ++ ) {
19+ hash = seed . charCodeAt ( i ) + ( ( hash << 5 ) - hash ) ;
20+ }
21+
22+ const rng = ( ) => {
23+ const x = Math . sin ( hash ++ ) * 10000 ;
24+ return x - Math . floor ( x ) ;
25+ } ;
26+
27+ const count = 6 ;
28+ const items = [ ] ;
29+ const colors = [ '#10b981' , '#3b82f6' , '#f87171' , '#fb923c' , '#ffffff' ] ;
30+
31+ for ( let i = 0 ; i < count ; i ++ ) {
32+ items . push ( {
33+ type : Math . floor ( rng ( ) * 4 ) , // 0: rect, 1: circle, 2: triangle, 3: arc
34+ x : rng ( ) * 100 ,
35+ y : rng ( ) * 100 ,
36+ size : 15 + rng ( ) * 25 ,
37+ rotation : Math . floor ( rng ( ) * 4 ) * 90 ,
38+ color : colors [ Math . floor ( rng ( ) * colors . length ) ] ,
39+ opacity : 0.03 + rng ( ) * 0.07
40+ } ) ;
41+ }
42+ return items ;
43+ } , [ seed ] ) ;
44+
45+ return (
46+ < svg className = "absolute inset-0 w-full h-full pointer-events-none" viewBox = "0 0 100 100" preserveAspectRatio = "xMidYMid slice" >
47+ { shapes . map ( ( s , i ) => (
48+ < g key = { i } transform = { `translate(${ s . x } , ${ s . y } ) rotate(${ s . rotation } )` } >
49+ { s . type === 0 && (
50+ < rect x = { - s . size / 2 } y = { - s . size / 2 } width = { s . size } height = { s . size } fill = { s . color } fillOpacity = { s . opacity } />
51+ ) }
52+ { s . type === 1 && (
53+ < circle cx = { 0 } cy = { 0 } r = { s . size / 2 } fill = { s . color } fillOpacity = { s . opacity } />
54+ ) }
55+ { s . type === 2 && (
56+ < path d = { `M 0 ${ - s . size / 2 } L ${ s . size / 2 } ${ s . size / 2 } L ${ - s . size / 2 } ${ s . size / 2 } Z` } fill = { s . color } fillOpacity = { s . opacity } />
57+ ) }
58+ { s . type === 3 && (
59+ < path d = { `M ${ - s . size / 2 } ${ - s . size / 2 } A ${ s . size } ${ s . size } 0 0 1 ${ s . size / 2 } ${ s . size / 2 } L ${ - s . size / 2 } ${ s . size / 2 } Z` } fill = { s . color } fillOpacity = { s . opacity } />
60+ ) }
61+ </ g >
62+ ) ) }
63+ </ svg >
64+ ) ;
65+ } ;
1766
1867const VocabPage = ( ) => {
1968 const [ searchQuery , setSearchQuery ] = useState ( '' ) ;
@@ -51,7 +100,7 @@ const VocabPage = () => {
51100 const scrollToLetter = ( letter ) => {
52101 const element = document . getElementById ( `letter-${ letter } ` ) ;
53102 if ( element ) {
54- const offset = 120 ; // sticky header offset
103+ const offset = 140 ;
55104 const bodyRect = document . body . getBoundingClientRect ( ) . top ;
56105 const elementRect = element . getBoundingClientRect ( ) . top ;
57106 const elementPosition = elementRect - bodyRect ;
@@ -64,179 +113,134 @@ const VocabPage = () => {
64113 }
65114 } ;
66115
67- return (
68- < div className = "min-h-screen bg-[#050505] text-white selection:bg-emerald-500/30 font-sans" >
69- < Seo
70- title = "Glossary | Fezcodex"
71- description = "A collection of technical terms, concepts, and definitions used across Fezcodex."
72- keywords = { [ 'Fezcodex' , 'vocabulary' , 'glossary' , 'definitions' ] }
73- />
74- < div className = "mx-auto max-w-6xl px-6 py-24 md:px-12" >
75- { /* Header Section */ }
76- < header className = "mb-20" >
77- < Link
78- to = "/"
79- className = "mb-8 inline-flex items-center gap-2 text-xs font-mono text-gray-500 hover:text-white transition-colors uppercase tracking-widest"
80- >
81- < ArrowLeftIcon weight = "bold" />
82- < span > Back to Home</ span >
83- </ Link >
84-
85- < BreadcrumbTitle
86- title = "Glossary"
87- breadcrumbs = { [ 'fc' , 'reference' , 'terms' ] }
88- variant = "brutalist"
89- />
90-
91- < div className = "mt-8 flex flex-col md:flex-row md:items-end justify-between gap-8" >
92- < p className = "text-gray-400 font-mono text-[10px] uppercase tracking-[0.2em] max-w-sm" >
93- { '//' } A dictionary of technical concepts, patterns, and terminology used throughout this digital garden.
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.
94147 </ p >
95- < div className = "flex gap-4" >
96- < span className = "px-2 py-1 bg-white/5 border border-white/10 text-gray-500 font-mono text-[9px] uppercase tracking-widest" >
97- Entries: { vocabEntries . length }
98- </ span >
99- </ div >
100- </ div >
101- </ header >
102-
103- { /* Dictionary Navigation & Search Sticky Bar */ }
104-
105- < div className = "sticky top-0 z-30 bg-[#050505]/95 backdrop-blur-md pb-6 pt-2 mb-16" >
106-
107- { /* Alphabet Nav */ }
108-
109- < div className = "flex flex-wrap gap-2 mb-8 mt-2" >
110-
111- { alphabet . map ( letter => (
112-
113- < button
114-
115- key = { letter }
116-
117- onClick = { ( ) => scrollToLetter ( letter ) }
118-
119- className = "w-8 h-8 flex items-center justify-center border border-white/10 bg-white/5 hover:bg-emerald-500 hover:text-black transition-all font-black text-xs uppercase"
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 >
120168
121- >
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 }
179+ </ button >
180+ ) ) }
181+ </ div >
182+ </ div >
183+ </ div >
122184
123- { letter }
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 >
124200
125- </ button >
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 >
126225
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 >
127230 ) ) }
128-
129231 </ div >
130-
131- < div className = "relative group" >
132-
133- < MagnifyingGlassIcon
134-
135- size = { 20 }
136-
137- className = "absolute left-0 top-1/2 -translate-y-1/2 text-gray-700 group-focus-within:text-emerald-500 transition-colors"
138-
139- />
140-
141- < input
142-
143- type = "text"
144-
145- placeholder = "Search terms..."
146-
147- value = { searchQuery }
148-
149- onChange = { ( e ) => setSearchQuery ( e . target . value ) }
150-
151- className = "w-full bg-transparent border-b border-gray-800 text-xl md:text-2xl font-light text-white placeholder-gray-700 focus:border-emerald-500 focus:outline-none py-2 pl-8 transition-colors font-mono"
152-
153- />
154-
155- { searchQuery && (
156- < button
157- onClick = { ( ) => setSearchQuery ( '' ) }
158- className = "absolute right-0 top-1/2 -translate-y-1/2 text-red-500 hover:text-red-400 transition-colors"
159- >
160- < XCircleIcon size = { 20 } weight = "fill" />
161- </ button >
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 >
239+ </ div >
162240 ) }
163241 </ div >
164242 </ div >
165-
166- { /* Dictionary Groups */ }
167- < div className = "pb-48 space-y-24" >
168- < AnimatePresence mode = "popLayout" >
169- { alphabet . map ( ( letter ) => (
170- < motion . section
171- key = { letter }
172- id = { `letter-${ letter } ` }
173- initial = { { opacity : 0 , y : 20 } }
174- whileInView = { { opacity : 1 , y : 0 } }
175- viewport = { { once : true } }
176- transition = { { duration : 0.4 } }
177- className = "space-y-8"
178- >
179- < div className = "flex items-center gap-6" >
180- < h2 className = "text-7xl font-black text-white leading-none opacity-20" > { letter } </ h2 >
181- < div className = "h-px flex-grow bg-white/10" />
182- </ div >
183-
184- < div className = "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6" >
185- { groupedEntries [ letter ] . map ( ( entry ) => (
186- < div key = { entry . slug } className = "flex flex-col h-full space-y-4" >
187- < span className = "text-[10px] font-mono text-gray-600 uppercase tracking-widest flex items-center gap-2" >
188- < FileTextIcon size = { 14 } /> { entry . slug . toUpperCase ( ) }
189- </ span >
190- < motion . button
191- layout
192- onClick = { ( ) => handleOpenVocab ( entry ) }
193- className = "group relative flex flex-col items-start text-left p-8 rounded-none bg-zinc-900 border border-white/10 hover:border-emerald-500/50 transition-all hover:bg-emerald-500/[0.02] flex-grow overflow-hidden"
194- >
195- { /* Background Generative Art - High Visibility */ }
196- < div className = "absolute inset-0 opacity-30 group-hover:opacity-80 transition-opacity pointer-events-none scale-125 grayscale group-hover:grayscale-0 group-hover:scale-100 duration-1000" >
197- < GenerativeArt seed = { entry . slug } className = "w-full h-full" />
198- < div className = "absolute inset-0 bg-black/40 group-hover:bg-black/20 transition-colors" />
199- </ div >
200-
201- { /* Ribbon */ }
202- < div className = "absolute top-0 right-0 overflow-hidden w-16 h-16 pointer-events-none z-10" >
203- < div className = "absolute top-[12px] right-[-24px] w-[80px] bg-emerald-500 text-black text-[8px] font-black font-mono text-center rotate-45 uppercase py-0.5 shadow-lg" >
204- TERM
205- </ div >
206- </ div >
207-
208- < div className = "mb-6 text-emerald-500 group-hover:text-emerald-400 transition-colors relative z-10" >
209- < BookBookmarkIcon size = { 32 } weight = "duotone" />
210- </ div >
211-
212- < h3 className = "text-2xl font-black tracking-tighter uppercase mb-4 group-hover:text-emerald-400 transition-colors leading-none relative z-10" >
213- { entry . title }
214- </ h3 >
215-
216- < span className = "font-mono text-[10px] text-gray-500 uppercase tracking-widest mt-auto group-hover:text-gray-400 transition-colors border-t border-white/5 pt-4 w-full relative z-10" >
217- { '//' } { entry . slug }
218- </ span >
219-
220- < div className = "absolute bottom-4 right-4 opacity-0 group-hover:opacity-100 transition-all transform translate-x-2 group-hover:translate-x-0 z-10" >
221- < ArrowSquareOutIcon size = { 16 } className = "text-emerald-500" />
222- </ div >
223- </ motion . button >
224- </ div >
225- ) ) }
226- </ div >
227- </ motion . section >
228- ) ) }
229- </ AnimatePresence >
230-
231- { filteredEntries . length === 0 && (
232- < div className = "py-20 text-center font-mono text-gray-600 uppercase tracking-widest border border-dashed border-white/5 bg-white/[0.01]" >
233- No matching terms found.
234- </ div >
235- ) }
236- </ div >
237243 </ div >
238- </ div >
239- ) ;
240- } ;
244+ ) ; } ;
241245
242- export default VocabPage ;
246+ export default VocabPage ;
0 commit comments