Skip to content

Commit 76bfee3

Browse files
committed
style: the new style for /vocab page
1 parent 4d24af5 commit 76bfee3

File tree

3 files changed

+175
-176
lines changed

3 files changed

+175
-176
lines changed

src/pages/TheVaguePage.jsx

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -129,11 +129,6 @@ const TheVaguePage = () => {
129129
<div className={`c-nav-bg ${isNavOpen ? 'is-visible' : ''}`} onClick={toggleNav}></div>
130130
<nav className={`c-nav border-r ${isInvert ? 'border-[#f4f4f4]/25' : 'border-[#1a1a1a]/25'} bg-inherit`}>
131131
<div className="c-nav_panel">
132-
<div className="md:hidden flex justify-end mb-4">
133-
<button onClick={toggleNav} className="p-2">
134-
<XIcon size={24} weight="bold" />
135-
</button>
136-
</div>
137132
<div className="c-nav_panel_main font-instr-serif italic">
138133
Collection Index
139134
<ol className="c-nav_panel_list font-instr-serif not-italic">

src/pages/VocabPage.jsx

Lines changed: 174 additions & 170 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,67 @@ import React, { useState, useMemo } from 'react';
22
import { Link } from 'react-router-dom';
33
import {
44
ArrowLeftIcon,
5-
BookBookmarkIcon,
65
MagnifyingGlassIcon,
76
XCircleIcon,
8-
ArrowSquareOutIcon,
9-
FileTextIcon,
7+
ArrowUpRightIcon,
108
} from '@phosphor-icons/react';
119
import { motion, AnimatePresence } from 'framer-motion';
1210
import { vocabulary } from '../data/vocabulary';
1311
import Seo from '../components/Seo';
1412
import { 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

1867
const 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

Comments
 (0)