1- import React , { useState } from 'react' ;
2- import { Link } from 'react-router-dom' ;
3- import {
4- ArrowLeftIcon ,
5- TrophyIcon ,
6- LockIcon ,
7- InfoIcon ,
8- BellSlashIcon ,
9- FunnelIcon ,
10- XCircleIcon ,
11- CalendarBlankIcon ,
12- } from '@phosphor-icons/react' ;
13- import Seo from '../components/Seo' ;
14- import { useAchievements } from '../context/AchievementContext' ;
15- import { ACHIEVEMENTS } from '../config/achievements' ;
1+ import React from 'react' ;
2+ import { useVisualSettings } from '../context/VisualSettingsContext' ;
3+ import BrutalistAchievementsPage from './brutalist-views/BrutalistAchievementsPage' ;
4+ import LuxeAchievementsPage from './luxe-views/LuxeAchievementsPage' ;
165
176const AchievementsPage = ( ) => {
18- const { unlockedAchievements, showAchievementToast } = useAchievements ( ) ;
19- const [ selectedCategories , setSelectedCategories ] = useState ( [ ] ) ;
7+ const { fezcodexTheme } = useVisualSettings ( ) ;
208
21- const uniqueCategories = [
22- 'All' ,
23- ...new Set ( ACHIEVEMENTS . map ( ( ach ) => ach . category ) ) ,
24- ] . sort ( ) ;
9+ if ( fezcodexTheme === 'luxe' ) {
10+ return < LuxeAchievementsPage /> ;
11+ }
2512
26- const unlockedCount = Object . keys ( unlockedAchievements ) . filter (
27- ( key ) => unlockedAchievements [ key ] . unlocked ,
28- ) . length ;
29- const totalCount = ACHIEVEMENTS . length ;
30- const progressPercentage = Math . round ( ( unlockedCount / totalCount ) * 100 ) ;
31-
32- const toggleCategory = ( category ) => {
33- if ( category === 'All' ) {
34- setSelectedCategories ( [ ] ) ;
35- } else {
36- setSelectedCategories ( ( prev ) =>
37- prev . includes ( category )
38- ? prev . filter ( ( c ) => c !== category )
39- : [ ...prev , category ] ,
40- ) ;
41- }
42- } ;
43-
44- const clearFilters = ( ) => {
45- setSelectedCategories ( [ ] ) ;
46- } ;
47-
48- const filteredAchievements = ACHIEVEMENTS . filter ( ( achievement ) => {
49- const matchesCategory =
50- selectedCategories . length === 0 ||
51- selectedCategories . includes ( achievement . category ) ;
52- return matchesCategory ;
53- } ) ;
54-
55- return (
56- // Changed main background to stone-950 for an earthy dark feel
57- < div className = "py-16 sm:py-24 bg-stone-950 min-h-screen" >
58- < Seo
59- title = "Achievements | Fezcodex"
60- description = "Track your progress and unlocked secrets in Fezcodex."
61- keywords = { [ 'Fezcodex' , 'achievements' , 'gamification' , 'trophies' ] }
62- image = "/images/asset/achievements-page.webp"
63- />
64-
65- < div className = "mx-auto max-w-7xl px-6 lg:px-8" >
66- { /* Navigation */ }
67- < Link
68- to = "/"
69- className = "group text-emerald-500 hover:text-emerald-400 flex items-center justify-center gap-2 text-lg mb-4 transition-all font-playfairDisplay"
70- >
71- < ArrowLeftIcon className = "text-xl transition-transform group-hover:-translate-x-1" /> { ' ' }
72- Back to Home
73- </ Link >
74-
75- { /* Title Section */ }
76- < div className = "mx-auto max-w-2xl text-center relative z-10" >
77- < h1 className = "text-4xl font-bold font-playfairDisplay tracking-tight text-stone-100 sm:text-6xl flex flex-col sm:flex-row items-center justify-center gap-4" >
78- < div className = "relative" >
79- { /* Nature Glow behind Trophy */ }
80- < div className = "absolute inset-0 bg-emerald-500 blur-2xl opacity-20 rounded-full" > </ div >
81- < TrophyIcon
82- size = { 56 }
83- weight = "duotone"
84- className = "text-emerald-400 relative z-10 drop-shadow-[0_0_15px_rgba(52,211,153,0.4)]"
85- />
86- </ div >
87- Achievements
88- < TrophyIcon
89- size = { 56 }
90- weight = "duotone"
91- className = "text-emerald-400 relative z-10 drop-shadow-[0_0_15px_rgba(52,211,153,0.4)]"
92- />
93- </ h1 >
94- < p className = "mt-6 text-lg leading-8 text-stone-400 font-arvo" >
95- Discover the wild secrets hidden within Fezcodex.
96- </ p >
97-
98- { /* Filter Pills - Nature Themed */ }
99- < div className = "flex flex-wrap items-center justify-center gap-2 mt-8 max-w-2xl mx-auto font-arvo" >
100- < div className = "flex items-center gap-2 mr-2 text-stone-500 font-mono text-sm" >
101- < FunnelIcon size = { 16 } />
102- < span > Filter:</ span >
103- </ div >
104- { uniqueCategories . map ( ( category ) => {
105- const isSelected =
106- selectedCategories . includes ( category ) ||
107- ( category === 'All' && selectedCategories . length === 0 ) ;
108- // Stone/Emerald Logic for pills
109- const colorClass = isSelected
110- ? 'bg-emerald-900/40 text-emerald-300 border-emerald-500/50 shadow-[0_0_10px_rgba(16,185,129,0.2)]'
111- : 'bg-stone-900/50 text-stone-500 border-stone-800 hover:border-stone-600 hover:text-stone-300' ;
112- return (
113- < button
114- key = { category }
115- onClick = { ( ) => toggleCategory ( category ) }
116- className = { `px-3 py-1 rounded-full text-sm font-medium border transition-colors duration-200 ${ colorClass } ` }
117- >
118- { category }
119- </ button >
120- ) ;
121- } ) }
122-
123- { selectedCategories . length > 0 && (
124- < button
125- onClick = { clearFilters }
126- className = "ml-2 text-sm text-red-400 hover:text-red-300 flex items-center gap-1 transition-colors"
127- >
128- < XCircleIcon size = { 20 } /> Clear
129- </ button >
130- ) }
131- </ div >
132-
133- { /* Progress Bar - Nature Themed */ }
134- < div className = "mt-8 max-w-md mx-auto" >
135- < div className = "flex justify-between text-sm text-stone-400 mb-2" >
136- < span > Nature's Progress</ span >
137- < span >
138- { unlockedCount } / { totalCount }
139- </ span >
140- </ div >
141- < div className = "w-full bg-stone-900 rounded-full h-4 overflow-hidden border border-stone-800 shadow-inner" >
142- < div
143- // Gradient from deep green to bright teal
144- className = "bg-gradient-to-r from-emerald-700 via-teal-500 to-emerald-400 h-4 rounded-full transition-all duration-1000 ease-out shadow-[0_0_10px_rgba(52,211,153,0.4)]"
145- style = { { width : `${ progressPercentage } %` } }
146- > </ div >
147- </ div >
148-
149- { /* Notification Toast Status - Keeping colors functional but tweaking background */ }
150- < div
151- className = { `mt-8 flex items-center gap-4 p-4 rounded-xl border backdrop-blur-sm transition-all duration-300 shadow-lg ${
152- showAchievementToast
153- ? 'bg-teal-950/30 border-teal-500/30 text-teal-100 shadow-teal-900/10'
154- : 'bg-stone-800/20 border-stone-700/30 text-stone-300 shadow-stone-900/10'
155- } `}
156- >
157- < div
158- className = { `p-2.5 rounded-full shrink-0 ${
159- showAchievementToast
160- ? 'bg-teal-500/20 text-teal-400'
161- : 'bg-stone-500/20 text-stone-400'
162- } `}
163- >
164- { showAchievementToast ? (
165- < InfoIcon size = { 24 } weight = "duotone" />
166- ) : (
167- < BellSlashIcon size = { 24 } weight = "duotone" />
168- ) }
169- </ div >
170- < div className = "flex-1 text-left" >
171- < p className = "font-medium text-sm tracking-wide" >
172- NOTIFICATIONS:{ ' ' }
173- < span className = "font-bold" >
174- { showAchievementToast ? 'ACTIVE' : 'MUTED' }
175- </ span >
176- </ p >
177- < p
178- className = { `text-xs mt-1 ${ showAchievementToast ? 'text-teal-400/70' : 'text-stone-500' } ` }
179- >
180- Manage in{ ' ' }
181- < Link
182- to = "/settings"
183- className = "underline underline-offset-2 hover:text-white transition-colors"
184- >
185- Settings
186- </ Link >
187- .
188- </ p >
189- </ div >
190- </ div >
191- </ div >
192- </ div >
193-
194- { /* Cards Grid */ }
195- < div className = "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6 mt-16" >
196- { filteredAchievements . map ( ( achievement ) => {
197- const isUnlocked = unlockedAchievements [ achievement . id ] ?. unlocked ;
198- const unlockedDate = isUnlocked
199- ? new Date ( unlockedAchievements [ achievement . id ] . unlockedAt )
200- : null ;
201-
202- return (
203- < div
204- key = { achievement . id }
205- className = { `relative group flex flex-col h-full overflow-hidden rounded-2xl border transition-all duration-500 ease-out ${
206- isUnlocked
207- ? 'border-emerald-500/30 hover:-translate-y-2 hover:shadow-[0_15px_40px_-10px_rgba(16,185,129,0.3)]'
208- : 'border-stone-800 bg-stone-900/50 grayscale opacity-70 hover:opacity-100 hover:border-stone-700'
209- } `}
210- >
211- { /* Card Background */ }
212- < div
213- className = { `absolute inset-0 z-0 transition-all duration-500 ${
214- isUnlocked
215- ? // A deep forest gradient
216- 'bg-gradient-to-br from-emerald-950/80 via-stone-950 to-stone-950 opacity-100'
217- : 'bg-stone-950'
218- } `}
219- >
220- { /* Unlocked "Magical Spores" overlay pattern */ }
221- { isUnlocked && (
222- < div className = "absolute inset-0 opacity-20 bg-[radial-gradient(circle_at_top_right,_var(--tw-gradient-stops))] from-emerald-500/40 via-transparent to-transparent size-full" > </ div >
223- ) }
224- </ div >
225-
226- < div className = "relative z-10 flex flex-col items-center text-center p-6 flex-grow font-sans" >
227- { /* Category Pill */ }
228- < span
229- className = { `mb-6 px-3 py-1 text-[10px] font-bold tracking-widest uppercase rounded-full border ${
230- isUnlocked
231- ? 'bg-emerald-900/50 text-emerald-300 border-emerald-500/30'
232- : 'bg-stone-800 text-stone-500 border-stone-700'
233- } `}
234- >
235- { achievement . category }
236- </ span >
237-
238- { /* Icon Container */ }
239- < div className = "relative mb-6 group-hover:scale-105 transition-transform duration-300" >
240- { /* Magical Glow */ }
241- { isUnlocked && (
242- < div className = "absolute inset-0 bg-emerald-500 blur-2xl opacity-20 rounded-full animate-pulse-slow" > </ div >
243- ) }
244-
245- < div
246- className = { `relative h-24 w-24 rounded-full flex items-center justify-center border-[3px] shadow-2xl ${
247- isUnlocked
248- ? // Emerald to Teal gradient for the ring
249- 'bg-gradient-to-b from-stone-900 to-emerald-950 border-emerald-500/50 text-emerald-300 shadow-emerald-900/50 ring-4 ring-emerald-500/10'
250- : 'bg-stone-900 border-stone-800 text-stone-600 shadow-black/50'
251- } `}
252- >
253- < div className = "scale-[1.4] drop-shadow-lg" >
254- { isUnlocked ? (
255- achievement . icon
256- ) : (
257- < LockIcon weight = "fill" />
258- ) }
259- </ div >
260- </ div >
261- </ div >
262-
263- { /* Text Content */ }
264- < h3
265- className = { `text-2xl font-playfairDisplay tracking-tight mb-3 ${
266- isUnlocked
267- ? 'text-transparent bg-clip-text bg-gradient-to-r from-emerald-200 to-teal-100'
268- : 'text-stone-600'
269- } `}
270- >
271- { achievement . title }
272- </ h3 >
273- < p
274- className = { `text-sm font-arvo leading-relaxed ${ isUnlocked ? 'text-stone-300' : 'text-stone-600' } ` }
275- >
276- { achievement . description }
277- </ p >
278- </ div >
279-
280- { /* Footer / Date */ }
281- < div
282- className = { `relative z-10 mt-auto p-4 w-full border-t ${
283- isUnlocked
284- ? 'border-emerald-500/10 bg-emerald-950/20'
285- : 'border-stone-800/50 bg-stone-900/30'
286- } `}
287- >
288- { isUnlocked ? (
289- < div className = "flex items-center justify-center gap-2 text-xs text-emerald-400/70 font-medium font-mono uppercase tracking-widest" >
290- < CalendarBlankIcon weight = "duotone" size = { 16 } />
291- < span >
292- Unlocked:{ ' ' }
293- { unlockedDate . toLocaleDateString ( undefined , {
294- year : 'numeric' ,
295- month : 'short' ,
296- day : 'numeric' ,
297- } ) }
298- </ span >
299- </ div >
300- ) : (
301- < div className = "text-center text-xs text-stone-600 font-mono uppercase tracking-widest flex items-center justify-center gap-2" >
302- < LockIcon size = { 14 } /> Locked
303- </ div >
304- ) }
305- </ div >
306- </ div >
307- ) ;
308- } ) }
309- </ div >
310- </ div >
311- </ div >
312- ) ;
13+ return < BrutalistAchievementsPage /> ;
31314} ;
31415
315- export default AchievementsPage ;
16+ export default AchievementsPage ;
0 commit comments