Skip to content

Commit 0605367

Browse files
committed
feat: roadmap
1 parent 6a23b04 commit 0605367

File tree

5 files changed

+672
-0
lines changed

5 files changed

+672
-0
lines changed

public/roadmap/roadmap.json

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
[
2+
{
3+
"id": "21aef85f-77c7-4eb4-9d1a-e54d0bf80185",
4+
"title": "MIDI Support for FeZynth",
5+
"description": "Integrate MIDI input to play FeZynth with external MIDI controllers.",
6+
"category": "Games",
7+
"status": "Planned",
8+
"priority": "High",
9+
"created_at": "2025-11-29T10:00:00+03:00",
10+
"due_date": "2026-01-15T00:00:00+03:00",
11+
"notes": "Requires Web MIDI API research."
12+
},
13+
{
14+
"id": "0afd0226-8076-4b92-a597-e7f8118106cd",
15+
"title": "Multiplayer FezType",
16+
"description": "Allow users to compete in real-time typing races.",
17+
"category": "Games",
18+
"status": "In Progress",
19+
"priority": "High",
20+
"created_at": "2025-11-20T11:30:00+03:00",
21+
"due_date": "2026-03-01T00:00:00+03:00",
22+
"notes": "Backend infrastructure and WebSocket implementation needed."
23+
},
24+
{
25+
"id": "6286e039-75ec-4b8c-8056-4f6a71e06362",
26+
"title": "More Commands for Code Seance",
27+
"description": "Expand the set of available commands and interactions in Code Seance.",
28+
"category": "Games",
29+
"status": "In Progress",
30+
"priority": "Medium",
31+
"created_at": "2025-11-28T15:00:00+03:00",
32+
"notes": "Focus on developer-themed commands."
33+
},
34+
{
35+
"id": "237dffab-441f-4942-bc51-2ae4865a6d32",
36+
"title": "Save/Load Color Palettes",
37+
"description": "Add functionality to save and load generated color palettes.",
38+
"category": "Generators",
39+
"status": "Planned",
40+
"priority": "Medium",
41+
"created_at": "2025-11-25T09:00:00+03:00",
42+
"notes": "Local storage first, then user accounts."
43+
},
44+
{
45+
"id": "41ae55f4-8641-4565-b817-5024fd4bd1e7",
46+
"title": "Notepad Markdown Support",
47+
"description": "Implement basic Markdown rendering and editing capabilities in Notepad.",
48+
"category": "Utilities",
49+
"status": "Planned",
50+
"priority": "High",
51+
"created_at": "2025-11-27T16:00:00+03:00",
52+
"notes": "Requires a Markdown parsing library."
53+
},
54+
{
55+
"id": "ef9a9a2e-a929-44d8-a2f4-1ccd0018b6fa",
56+
"title": "Customizable Morse Code Translator",
57+
"description": "Allow users to customize Morse code speed, tone, and character mappings.",
58+
"category": "Converters",
59+
"status": "On Hold",
60+
"priority": "Low",
61+
"created_at": "2025-11-15T14:00:00+03:00",
62+
"notes": "Lower priority, focus on core features first."
63+
},
64+
{
65+
"id": "f7a9de56-d1db-48ec-9607-3e37dda35c30",
66+
"title": "New App: Project Timeline Visualizer",
67+
"description": "A tool to visualize project timelines and dependencies.",
68+
"category": "Utilities",
69+
"status": "Planned",
70+
"priority": "High",
71+
"created_at": "2025-11-29T18:00:00+03:00",
72+
"notes": "Research Gantt chart libraries."
73+
}
74+
]

src/components/AnimatedRoutes.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,8 @@ const SpirographPage = lazy(() => import('../pages/apps/SpirographPage'));
128128
const FractalFloraPage = lazy(() => import('../pages/apps/FractalFloraPage'));
129129
const FezynthPage = lazy(() => import('../pages/apps/FezynthPage'));
130130
const CodeSeancePage = lazy(() => import('../pages/apps/CodeSeancePage'));
131+
const RoadmapViewerPage = lazy(() => import('../pages/roadmap/RoadmapViewerPage'));
132+
const RoadmapItemDetailPage = lazy(() => import('../pages/roadmap/RoadmapItemDetailPage'));
131133
const PinnedAppPage = lazy(() => import('../pages/PinnedAppPage'));
132134
const SettingsPage = lazy(() => import('../pages/SettingsPage'));
133135
const TimelinePage = lazy(() => import('../pages/TimelinePage'));
@@ -1752,6 +1754,38 @@ function AnimatedRoutes() {
17521754
</motion.div>
17531755
}
17541756
/>
1757+
<Route
1758+
path="/roadmap"
1759+
element={
1760+
<motion.div
1761+
initial="initial"
1762+
animate="in"
1763+
exit="out"
1764+
variants={pageVariants}
1765+
transition={pageTransition}
1766+
>
1767+
<Suspense fallback={<Loading />}>
1768+
<RoadmapViewerPage />
1769+
</Suspense>
1770+
</motion.div>
1771+
}
1772+
/>
1773+
<Route
1774+
path="/roadmap/:id"
1775+
element={
1776+
<motion.div
1777+
initial="initial"
1778+
animate="in"
1779+
exit="out"
1780+
variants={pageVariants}
1781+
transition={pageTransition}
1782+
>
1783+
<Suspense fallback={<Loading />}>
1784+
<RoadmapItemDetailPage />
1785+
</Suspense>
1786+
</motion.div>
1787+
}
1788+
/>
17551789
{/*Pinned Apps*/}
17561790
<Route
17571791
path="/pinned-apps"

src/components/RoadmapCard.js

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import React from 'react';
2+
import { Link } from 'react-router-dom';
3+
import { motion } from 'framer-motion';
4+
import { KanbanIcon } from '@phosphor-icons/react'; // Using KanbanIcon as a default/watermark icon
5+
6+
const RoadmapCard = ({ app, index }) => {
7+
const getStatusColor = (status) => {
8+
switch (status) {
9+
case 'Planned':
10+
return 'bg-blue-500';
11+
case 'In Progress':
12+
return 'bg-yellow-500';
13+
case 'Completed':
14+
return 'bg-green-500';
15+
case 'On Hold':
16+
return 'bg-red-500';
17+
default:
18+
return 'bg-gray-500';
19+
}
20+
};
21+
22+
const getPriorityColor = (priority) => {
23+
switch (priority) {
24+
case 'High':
25+
return 'text-red-400';
26+
case 'Medium':
27+
return 'text-yellow-400';
28+
case 'Low':
29+
return 'text-green-400';
30+
default:
31+
return 'text-gray-400';
32+
}
33+
};
34+
35+
const statusTextColor = (status) => {
36+
return 'text-white';
37+
};
38+
39+
return (
40+
<motion.div
41+
initial={{ opacity: 0, y: 20 }}
42+
animate={{ opacity: 1, y: 0 }}
43+
transition={{ duration: 0.3, delay: index * 0.05 }}
44+
>
45+
<Link to={`/roadmap/${app.id}`} className="block group relative h-full">
46+
{/* Main Card Container */}
47+
<div className="relative flex flex-col h-full bg-gray-900/80 backdrop-blur-xl border border-gray-800 rounded-xl p-4 overflow-hidden group-hover:border-purple-600 transition-all duration-300 shadow-xl">
48+
{/* Subtle Grid Background */}
49+
<div
50+
className="absolute inset-0 opacity-[0.03] pointer-events-none z-0"
51+
style={{
52+
backgroundImage:
53+
'linear-gradient(to right, #ffffff 1px, transparent 1px), linear-gradient(to bottom, #ffffff 1px, transparent 1px)',
54+
backgroundSize: '20px 20px',
55+
}}
56+
></div>
57+
{/* Watermark Icon */}
58+
<div className="absolute -bottom-6 -right-6 text-gray-700 opacity-20 group-hover:opacity-30 group-hover:scale-110 transition-all duration-500 rotate-12 pointer-events-none z-0">
59+
<KanbanIcon size={120} weight="fill" />
60+
</div>
61+
62+
{/* Content */}
63+
<div className="relative z-10 flex-grow">
64+
<div className="flex items-center justify-between mb-2">
65+
<span
66+
className={`px-2 py-1 inline-flex text-xs leading-5 font-semibold rounded-full ${getStatusColor(app.status || 'Planned')} ${statusTextColor(app.status || 'Planned')}`}
67+
>
68+
{app.status || 'Planned'}
69+
</span>
70+
<span
71+
className={`text-xs font-medium ${getPriorityColor(app.priority)}`}
72+
>
73+
Priority: {app.priority || 'Low'}
74+
</span>
75+
</div>
76+
<h4 className="text-xl font-bold font-mono text-white mb-2 tracking-tight group-hover:text-purple-400 transition-colors">
77+
{app.title}
78+
</h4>
79+
<p className="text-gray-400 font-mono text-sm leading-relaxed line-clamp-3">
80+
{app.description}
81+
</p>
82+
</div>
83+
84+
{app.notes && (
85+
<div className="relative z-10 mt-3 pt-3 border-t border-gray-700">
86+
<p className="text-gray-500 text-xs italic line-clamp-2">
87+
Notes: {app.notes}
88+
</p>
89+
</div>
90+
)}
91+
</div>
92+
</Link>
93+
</motion.div>
94+
);
95+
};
96+
97+
export default RoadmapCard;
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import React, { useState, useEffect } from 'react';
2+
import { useParams, Link } from 'react-router-dom';
3+
import useSeo from '../../hooks/useSeo';
4+
import { ArrowLeftIcon } from '@phosphor-icons/react';
5+
import { motion } from 'framer-motion';
6+
7+
const RoadmapItemDetailPage = () => {
8+
const { id } = useParams();
9+
const [roadmapItem, setRoadmapItem] = useState(null);
10+
const [isLoading, setIsLoading] = useState(true);
11+
12+
useSeo({
13+
title: roadmapItem ? `${roadmapItem.title} | Roadmap` : 'Roadmap Item | Fezcodex',
14+
description: roadmapItem ? roadmapItem.description : 'Details of a roadmap item.',
15+
keywords: ['Fezcodex', 'roadmap', 'item', id],
16+
ogTitle: roadmapItem ? `${roadmapItem.title} | Roadmap` : 'Roadmap Item | Fezcodex',
17+
ogDescription: roadmapItem ? roadmapItem.description : 'Details of a roadmap item.',
18+
ogImage: 'https://fezcode.github.io/logo512.png', // Assuming a default image
19+
twitterCard: 'summary_large_image',
20+
twitterTitle: roadmapItem ? `${roadmapItem.title} | Roadmap` : 'Roadmap Item | Fezcodex',
21+
twitterDescription: roadmapItem ? roadmapItem.description : 'Details of a roadmap item.',
22+
twitterImage: 'https://fezcode.github.io/logo512.png',
23+
});
24+
25+
useEffect(() => {
26+
const fetchRoadmapItem = async () => {
27+
try {
28+
const response = await fetch('/roadmap/roadmap.json');
29+
const data = await response.json();
30+
const foundItem = data.find((item) => item.id === id);
31+
setRoadmapItem(foundItem);
32+
setIsLoading(false);
33+
} catch (error) {
34+
console.error('Failed to fetch roadmap item:', error);
35+
setIsLoading(false);
36+
}
37+
};
38+
39+
fetchRoadmapItem();
40+
}, [id]);
41+
42+
const getStatusColor = (status) => {
43+
switch (status) {
44+
case 'Planned':
45+
return 'bg-blue-500';
46+
case 'In Progress':
47+
return 'bg-yellow-500';
48+
case 'Completed':
49+
return 'bg-green-500';
50+
case 'On Hold':
51+
return 'bg-red-500';
52+
default:
53+
return 'bg-gray-500';
54+
}
55+
};
56+
57+
const getPriorityColor = (priority) => {
58+
switch (priority) {
59+
case 'High':
60+
return 'text-red-400';
61+
case 'Medium':
62+
return 'text-yellow-400';
63+
case 'Low':
64+
return 'text-green-400';
65+
default:
66+
return 'text-gray-400';
67+
}
68+
};
69+
70+
if (isLoading) {
71+
return (
72+
<div className="py-8 sm:py-16 text-center text-gray-400">Loading...</div>
73+
);
74+
}
75+
76+
if (!roadmapItem) {
77+
return (
78+
<div className="py-8 sm:py-16 text-center text-gray-400">
79+
Roadmap item not found.
80+
</div>
81+
);
82+
}
83+
84+
return (
85+
<motion.div
86+
initial={{ opacity: 0, y: 20 }}
87+
animate={{ opacity: 1, y: 0 }}
88+
transition={{ duration: 0.5 }}
89+
className="py-8 sm:py-16"
90+
>
91+
<div className="mx-auto max-w-4xl px-6 lg:px-8">
92+
<Link
93+
to="/roadmap"
94+
className="group text-primary-400 hover:underline flex items-center gap-2 text-lg mb-8"
95+
>
96+
<ArrowLeftIcon className="text-xl transition-transform group-hover:-translate-x-1" />{' '}
97+
Back to Roadmap
98+
</Link>
99+
100+
<div className="bg-gray-900/80 backdrop-blur-xl rounded-xl shadow-xl p-6 lg:p-8 border border-gray-700 relative overflow-hidden">
101+
{/* Subtle Grid Background */}
102+
<div
103+
className="absolute inset-0 opacity-[0.03] pointer-events-none z-0"
104+
style={{
105+
backgroundImage:
106+
'linear-gradient(to right, #ffffff 1px, transparent 1px), linear-gradient(to bottom, #ffffff 1px, transparent 1px)',
107+
backgroundSize: '20px 20px',
108+
}}
109+
></div>
110+
<h1 className="text-4xl font-bold text-white mb-4 relative z-10 font-mono tracking-tight">
111+
{roadmapItem.title}
112+
</h1>
113+
<p className="text-gray-300 text-lg mb-6 relative z-10 font-mono">
114+
{roadmapItem.description}
115+
</p>
116+
117+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6 relative z-10">
118+
<div>
119+
<p className="text-gray-400 font-mono font-medium">Status:</p>
120+
<span
121+
className={`px-3 py-1 inline-flex text-sm leading-5 font-semibold rounded-full ${getStatusColor(roadmapItem.status)}`}
122+
>
123+
{roadmapItem.status || 'Planned'}
124+
</span>
125+
</div>
126+
<div>
127+
<p className="text-gray-400 font-mono font-medium">Priority:</p>
128+
<span
129+
className={`px-3 py-1 inline-flex text-sm leading-5 font-semibold rounded-full ${getPriorityColor(roadmapItem.priority)}`}
130+
>
131+
{roadmapItem.priority || 'Low'}
132+
</span>
133+
</div>
134+
<div>
135+
<p className="text-gray-400 font-mono font-medium">Category:</p>
136+
<p className="text-white font-mono">{roadmapItem.category}</p>
137+
</div>
138+
<div>
139+
<p className="text-gray-400 font-mono font-medium">Created At:</p>
140+
<p className="text-white font-mono">
141+
{new Date(roadmapItem.created_at).toLocaleDateString()}
142+
</p>
143+
</div>
144+
{roadmapItem.due_date && (
145+
<div>
146+
<p className="text-gray-400 font-mono font-medium">Due Date:</p>
147+
<p className="text-white font-mono">
148+
{new Date(roadmapItem.due_date).toLocaleDateString()}
149+
</p>
150+
</div>
151+
)}
152+
</div>
153+
154+
{roadmapItem.notes && (
155+
<div className="mt-6 border-t border-gray-700 pt-6 relative z-10">
156+
<h3 className="text-xl font-mono font-semibold text-white mb-2">Notes:</h3>
157+
<p className="text-gray-300 font-mono whitespace-pre-wrap">
158+
{roadmapItem.notes}
159+
</p>
160+
</div>
161+
)}
162+
</div>
163+
</div>
164+
</motion.div>
165+
);
166+
};
167+
168+
export default RoadmapItemDetailPage;

0 commit comments

Comments
 (0)