Skip to content

Commit 2309158

Browse files
committed
feat: new pinned apps pages
1 parent c23d7b1 commit 2309158

File tree

3 files changed

+263
-154
lines changed

3 files changed

+263
-154
lines changed

src/pages/PinnedAppPage.jsx

Lines changed: 9 additions & 154 deletions
Original file line numberDiff line numberDiff line change
@@ -1,161 +1,16 @@
1-
import React, { useState, useEffect } from 'react';
2-
import { Link } from 'react-router-dom';
3-
import { ArrowLeft, ArrowRight, Star, Hash } from '@phosphor-icons/react';
4-
import { motion } from 'framer-motion';
5-
import Seo from '../components/Seo';
6-
import { appIcons } from '../utils/appIcons';
7-
import GenerativeArt from '../components/GenerativeArt';
8-
9-
const PinnedAppCard = ({ app, index }) => {
10-
const Icon = appIcons[app.icon] || Star;
11-
12-
return (
13-
<motion.div
14-
whileHover={{ y: -5 }}
15-
className="group relative flex flex-col overflow-hidden rounded-sm bg-zinc-900 border border-white/10 h-full"
16-
>
17-
<Link to={app.to} className="flex flex-col h-full">
18-
{/* Visual Header */}
19-
<div className="relative h-48 w-full overflow-hidden border-b border-white/5">
20-
<GenerativeArt
21-
seed={app.title + (app.icon || 'pinned')}
22-
className="w-full h-full opacity-40 transition-transform duration-700 ease-out group-hover:scale-110"
23-
/>
24-
<div className="absolute inset-0 bg-gradient-to-t from-zinc-900 to-transparent" />
25-
26-
{/* Icon Overlay */}
27-
<div className="absolute inset-0 flex items-center justify-center pointer-events-none">
28-
<div className="p-4 rounded-full bg-black/40 backdrop-blur-md border border-white/10 text-emerald-400 transform group-hover:scale-110 transition-transform duration-500">
29-
<Icon size={40} weight="duotone" />
30-
</div>
31-
</div>
32-
33-
{/* Rank Badge */}
34-
<div className="absolute top-3 left-3 flex items-center gap-2 px-2 py-1 bg-black/60 backdrop-blur-md rounded border border-white/10">
35-
<span className="font-mono text-[10px] font-bold text-white uppercase tracking-widest">
36-
Rank {index + 1}
37-
</span>
38-
</div>
39-
</div>
40-
41-
{/* Content */}
42-
<div className="flex flex-col flex-grow p-6">
43-
<div className="flex items-center gap-2 mb-3">
44-
<span className="font-mono text-[10px] text-gray-500 uppercase tracking-widest flex items-center gap-1">
45-
<Hash size={12} className="text-emerald-500" />
46-
ID: {String(app.pinned_order).padStart(3, '0')}
47-
</span>
48-
<span className="text-gray-700 text-[10px]"></span>
49-
<span className="font-mono text-[10px] text-emerald-500 uppercase tracking-widest">
50-
Pinned
51-
</span>
52-
</div>
53-
54-
<h3 className="text-2xl font-bold font-sans uppercase text-white mb-3 group-hover:text-emerald-400 transition-colors tracking-tight">
55-
{app.title}
56-
</h3>
57-
58-
<p className="text-sm text-gray-400 line-clamp-3 leading-relaxed mb-6 flex-grow font-sans">
59-
{app.description}
60-
</p>
61-
62-
<div className="mt-auto pt-4 flex items-center justify-between border-t border-white/5">
63-
<span className="text-[10px] font-mono font-bold uppercase tracking-widest text-gray-500 group-hover:text-white transition-colors">
64-
Open Application
65-
</span>
66-
<ArrowRight
67-
weight="bold"
68-
size={16}
69-
className="text-emerald-500 transform -translate-x-2 opacity-0 transition-all duration-300 group-hover:translate-x-0 group-hover:opacity-100"
70-
/>
71-
</div>
72-
</div>
73-
</Link>
74-
</motion.div>
75-
);
76-
};
1+
import React from 'react';
2+
import { useVisualSettings } from '../context/VisualSettingsContext';
3+
import BrutalistPinnedAppPage from './brutalist-views/BrutalistPinnedAppPage';
4+
import LuxePinnedAppsPage from './luxe-views/LuxePinnedAppsPage';
775

786
const PinnedAppPage = () => {
79-
const [pinnedApps, setPinnedApps] = useState([]);
80-
const [isLoading, setIsLoading] = useState(true);
81-
82-
useEffect(() => {
83-
fetch('/apps/apps.json')
84-
.then((res) => res.json())
85-
.then((data) => {
86-
const allApps = Object.values(data).flatMap((cat) =>
87-
cat.apps.map((app) => ({ ...app, categoryName: cat.name })),
88-
);
89-
const pinned = allApps
90-
.filter((app) => app.pinned_order)
91-
.sort((a, b) => a.pinned_order - b.pinned_order);
92-
setPinnedApps(pinned);
93-
})
94-
.catch((err) => console.error(err))
95-
.finally(() => setIsLoading(false));
96-
}, []);
97-
98-
return (
99-
<div className="min-h-screen bg-[#050505] text-white selection:bg-emerald-500/30">
100-
<Seo
101-
title="Featured Apps | Fezcodex"
102-
description="A curated selection of core tools and essential applications."
103-
keywords={['pinned', 'apps', 'tools', 'featured', 'core']}
104-
/>
105-
<div className="mx-auto max-w-7xl px-6 py-24 md:px-12">
106-
{/* Header Section */}
107-
<header className="mb-20 text-center md:text-left">
108-
<Link
109-
to="/apps"
110-
className="mb-12 inline-flex items-center gap-2 text-xs font-mono text-gray-500 hover:text-white transition-colors uppercase tracking-widest"
111-
>
112-
<ArrowLeft weight="bold" />
113-
<span>All Applications</span>
114-
</Link>
115-
116-
<div className="flex flex-col gap-4">
117-
<h1 className="text-6xl md:text-8xl font-black tracking-tighter text-white mb-4 leading-none uppercase">
118-
Featured
119-
</h1>
120-
<p className="text-gray-400 font-mono text-sm max-w-2xl uppercase tracking-[0.2em] leading-relaxed mx-auto md:mx-0">
121-
A curated collection of essential tools and high-performance
122-
modules.
123-
</p>
124-
</div>
125-
</header>
7+
const { fezcodexTheme } = useVisualSettings();
1268

127-
{isLoading ? (
128-
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
129-
{[...Array(6)].map((_, i) => (
130-
<div
131-
key={i}
132-
className="h-80 w-full bg-white/5 border border-white/10 rounded-sm animate-pulse"
133-
/>
134-
))}
135-
</div>
136-
) : (
137-
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 auto-rows-fr">
138-
{pinnedApps.map((app, index) => (
139-
<motion.div
140-
key={app.slug}
141-
initial={{ opacity: 0, y: 20 }}
142-
animate={{ opacity: 1, y: 0 }}
143-
transition={{ duration: 0.4, delay: index * 0.05 }}
144-
>
145-
<PinnedAppCard app={app} index={index} />
146-
</motion.div>
147-
))}
148-
</div>
149-
)}
9+
if (fezcodexTheme === 'luxe') {
10+
return <LuxePinnedAppsPage />;
11+
}
15012

151-
{!isLoading && pinnedApps.length === 0 && (
152-
<div className="py-32 text-center font-mono text-gray-600 uppercase tracking-widest border border-dashed border-white/10">
153-
No featured applications found.
154-
</div>
155-
)}
156-
</div>
157-
</div>
158-
);
13+
return <BrutalistPinnedAppPage />;
15914
};
16015

16116
export default PinnedAppPage;
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
import React, { useState, useEffect } from 'react';
2+
import { Link } from 'react-router-dom';
3+
import { ArrowLeft, ArrowRight, Star, Hash } from '@phosphor-icons/react';
4+
import { motion } from 'framer-motion';
5+
import Seo from '../../components/Seo';
6+
import { appIcons } from '../../utils/appIcons';
7+
import GenerativeArt from '../../components/GenerativeArt';
8+
9+
const PinnedAppCard = ({ app, index }) => {
10+
const Icon = appIcons[app.icon] || Star;
11+
12+
return (
13+
<motion.div
14+
whileHover={{ y: -5 }}
15+
className="group relative flex flex-col overflow-hidden rounded-sm bg-zinc-900 border border-white/10 h-full"
16+
>
17+
<Link to={app.to} className="flex flex-col h-full">
18+
{/* Visual Header */}
19+
<div className="relative h-48 w-full overflow-hidden border-b border-white/5">
20+
<GenerativeArt
21+
seed={app.title + (app.icon || 'pinned')}
22+
className="w-full h-full opacity-40 transition-transform duration-700 ease-out group-hover:scale-110"
23+
/>
24+
<div className="absolute inset-0 bg-gradient-to-t from-zinc-900 to-transparent" />
25+
26+
{/* Icon Overlay */}
27+
<div className="absolute inset-0 flex items-center justify-center pointer-events-none">
28+
<div className="p-4 rounded-full bg-black/40 backdrop-blur-md border border-white/10 text-emerald-400 transform group-hover:scale-110 transition-transform duration-500">
29+
<Icon size={40} weight="duotone" />
30+
</div>
31+
</div>
32+
33+
{/* Rank Badge */}
34+
<div className="absolute top-3 left-3 flex items-center gap-2 px-2 py-1 bg-black/60 backdrop-blur-md rounded border border-white/10">
35+
<span className="font-mono text-[10px] font-bold text-white uppercase tracking-widest">
36+
Rank {index + 1}
37+
</span>
38+
</div>
39+
</div>
40+
41+
{/* Content */}
42+
<div className="flex flex-col flex-grow p-6">
43+
<div className="flex items-center gap-2 mb-3">
44+
<span className="font-mono text-[10px] text-gray-500 uppercase tracking-widest flex items-center gap-1">
45+
<Hash size={12} className="text-emerald-500" />
46+
ID: {String(app.pinned_order).padStart(3, '0')}
47+
</span>
48+
<span className="text-gray-700 text-[10px]"></span>
49+
<span className="font-mono text-[10px] text-emerald-500 uppercase tracking-widest">
50+
Pinned
51+
</span>
52+
</div>
53+
54+
<h3 className="text-2xl font-bold font-sans uppercase text-white mb-3 group-hover:text-emerald-400 transition-colors tracking-tight">
55+
{app.title}
56+
</h3>
57+
58+
<p className="text-sm text-gray-400 line-clamp-3 leading-relaxed mb-6 flex-grow font-sans">
59+
{app.description}
60+
</p>
61+
62+
<div className="mt-auto pt-4 flex items-center justify-between border-t border-white/5">
63+
<span className="text-[10px] font-mono font-bold uppercase tracking-widest text-gray-500 group-hover:text-white transition-colors">
64+
Open Application
65+
</span>
66+
<ArrowRight
67+
weight="bold"
68+
size={16}
69+
className="text-emerald-500 transform -translate-x-2 opacity-0 transition-all duration-300 group-hover:translate-x-0 group-hover:opacity-100"
70+
/>
71+
</div>
72+
</div>
73+
</Link>
74+
</motion.div>
75+
);
76+
};
77+
78+
const BrutalistPinnedAppPage = () => {
79+
const [pinnedApps, setPinnedApps] = useState([]);
80+
const [isLoading, setIsLoading] = useState(true);
81+
82+
useEffect(() => {
83+
fetch('/apps/apps.json')
84+
.then((res) => res.json())
85+
.then((data) => {
86+
const allApps = Object.values(data).flatMap((cat) =>
87+
cat.apps.map((app) => ({ ...app, categoryName: cat.name })),
88+
);
89+
const pinned = allApps
90+
.filter((app) => app.pinned_order)
91+
.sort((a, b) => a.pinned_order - b.pinned_order);
92+
setPinnedApps(pinned);
93+
})
94+
.catch((err) => console.error(err))
95+
.finally(() => setIsLoading(false));
96+
}, []);
97+
98+
return (
99+
<div className="min-h-screen bg-[#050505] text-white selection:bg-emerald-500/30">
100+
<Seo
101+
title="Featured Apps | Fezcodex"
102+
description="A curated selection of core tools and essential applications."
103+
keywords={['pinned', 'apps', 'tools', 'featured', 'core']}
104+
/>
105+
<div className="mx-auto max-w-7xl px-6 py-24 md:px-12">
106+
{/* Header Section */}
107+
<header className="mb-20 text-center md:text-left">
108+
<Link
109+
to="/apps"
110+
className="mb-12 inline-flex items-center gap-2 text-xs font-mono text-gray-500 hover:text-white transition-colors uppercase tracking-widest"
111+
>
112+
<ArrowLeft weight="bold" />
113+
<span>All Applications</span>
114+
</Link>
115+
116+
<div className="flex flex-col gap-4">
117+
<h1 className="text-6xl md:text-8xl font-black tracking-tighter text-white mb-4 leading-none uppercase">
118+
Featured
119+
</h1>
120+
<p className="text-gray-400 font-mono text-sm max-w-2xl uppercase tracking-[0.2em] leading-relaxed mx-auto md:mx-0">
121+
A curated collection of essential tools and high-performance
122+
modules.
123+
</p>
124+
</div>
125+
</header>
126+
127+
{isLoading ? (
128+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
129+
{[...Array(6)].map((_, i) => (
130+
<div
131+
key={i}
132+
className="h-80 w-full bg-white/5 border border-white/10 rounded-sm animate-pulse"
133+
/>
134+
))}
135+
</div>
136+
) : (
137+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 auto-rows-fr">
138+
{pinnedApps.map((app, index) => (
139+
<motion.div
140+
key={app.slug}
141+
initial={{ opacity: 0, y: 20 }}
142+
animate={{ opacity: 1, y: 0 }}
143+
transition={{ duration: 0.4, delay: index * 0.05 }}
144+
>
145+
<PinnedAppCard app={app} index={index} />
146+
</motion.div>
147+
))}
148+
</div>
149+
)}
150+
151+
{!isLoading && pinnedApps.length === 0 && (
152+
<div className="py-32 text-center font-mono text-gray-600 uppercase tracking-widest border border-dashed border-white/10">
153+
No featured applications found.
154+
</div>
155+
)}
156+
</div>
157+
</div>
158+
);
159+
};
160+
161+
export default BrutalistPinnedAppPage;

0 commit comments

Comments
 (0)