Skip to content

Commit 5b9419a

Browse files
committed
feat: new achievements pages
1 parent fb122cc commit 5b9419a

File tree

3 files changed

+579
-309
lines changed

3 files changed

+579
-309
lines changed

src/pages/AchievementsPage.jsx

Lines changed: 10 additions & 309 deletions
Original file line numberDiff line numberDiff line change
@@ -1,315 +1,16 @@
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

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

Comments
 (0)