Skip to content

Commit de96857

Browse files
committed
feat(terracotta): add TerracottaPinnedAppPage
1 parent 9c57806 commit de96857

1 file changed

Lines changed: 156 additions & 0 deletions

File tree

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

0 commit comments

Comments
 (0)