Skip to content

Commit 94ae63e

Browse files
committed
feat: more apps
1 parent bdc7895 commit 94ae63e

File tree

7 files changed

+703
-5
lines changed

7 files changed

+703
-5
lines changed

public/apps/apps.json

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -147,11 +147,25 @@
147147
"icon": "QuestionIcon"
148148
},
149149
{
150-
"slug": "bubble-wrap",
151-
"to": "/apps/bubble-wrap",
152-
"title": "Bubble Wrap",
153-
"description": "Pop some virtual bubble wrap to relieve stress.",
154-
"icon": "CirclesFourIcon"
150+
"slug": "banana-converter",
151+
"to": "/apps/banana-converter",
152+
"title": "Banana for Scale",
153+
"description": "Convert real-world measurements into standard bananas.",
154+
"icon": "PlantIcon"
155+
},
156+
{
157+
"slug": "pirate-translator",
158+
"to": "/apps/pirate-translator",
159+
"title": "Pirate Speak Translator",
160+
"description": "Translate your landlubber words into proper Pirate speak!",
161+
"icon": "SkullIcon"
162+
},
163+
{
164+
"slug": "galactic-age",
165+
"to": "/apps/galactic-age",
166+
"title": "Galactic Age Converter",
167+
"description": "Calculate your age on other planets in our solar system.",
168+
"icon": "PlanetIcon"
155169
}
156170
]
157171
},
@@ -337,6 +351,13 @@
337351
"title": "Pomodoro Timer",
338352
"description": "A simple and customizable Pomodoro timer to boost your productivity.",
339353
"icon": "TimerIcon"
354+
},
355+
{
356+
"slug": "bpm-guesser",
357+
"to": "/apps/bpm-guesser",
358+
"title": "BPM Guesser",
359+
"description": "Tap the beat to guess the BPM of a song.",
360+
"icon": "MetronomeIcon"
340361
}
341362
]
342363
}

src/components/AnimatedRoutes.js

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ import WhackABugPage from '../pages/apps/WhackABugPage';
6262
import BubbleWrapPage from '../pages/apps/BubbleWrapPage';
6363
import LoremIpsumGeneratorPage from '../pages/apps/LoremIpsumGeneratorPage';
6464
import SimonSaysPage from '../pages/apps/SimonSaysPage';
65+
import BananaConverterPage from '../pages/apps/BananaConverterPage';
66+
import PirateTranslatorPage from '../pages/apps/PirateTranslatorPage';
67+
import GalacticAgePage from '../pages/apps/GalacticAgePage';
68+
import BpmGuesserPage from '../pages/apps/BpmGuesserPage';
6569
import SettingsPage from '../pages/SettingsPage';
6670

6771
import UsefulLinksPage from '../pages/UsefulLinksPage';
@@ -579,6 +583,22 @@ function AnimatedRoutes() {
579583
path="/apps::lorem"
580584
element={<Navigate to="/apps/lorem-ipsum-generator" replace />}
581585
/>
586+
<Route
587+
path="/apps::banana"
588+
element={<Navigate to="/apps/banana-converter" replace />}
589+
/>
590+
<Route
591+
path="/apps::pirate"
592+
element={<Navigate to="/apps/pirate-translator" replace />}
593+
/>
594+
<Route
595+
path="/apps::space"
596+
element={<Navigate to="/apps/galactic-age" replace />}
597+
/>
598+
<Route
599+
path="/apps::bpm"
600+
element={<Navigate to="/apps/bpm-guesser" replace />}
601+
/>
582602
{/* End of hardcoded redirects */}
583603
<Route
584604
path="/apps/ip"
@@ -1197,6 +1217,62 @@ function AnimatedRoutes() {
11971217
</motion.div>
11981218
}
11991219
/>
1220+
<Route
1221+
path="/apps/banana-converter"
1222+
element={
1223+
<motion.div
1224+
initial="initial"
1225+
animate="in"
1226+
exit="out"
1227+
variants={pageVariants}
1228+
transition={pageTransition}
1229+
>
1230+
<BananaConverterPage />
1231+
</motion.div>
1232+
}
1233+
/>
1234+
<Route
1235+
path="/apps/pirate-translator"
1236+
element={
1237+
<motion.div
1238+
initial="initial"
1239+
animate="in"
1240+
exit="out"
1241+
variants={pageVariants}
1242+
transition={pageTransition}
1243+
>
1244+
<PirateTranslatorPage />
1245+
</motion.div>
1246+
}
1247+
/>
1248+
<Route
1249+
path="/apps/galactic-age"
1250+
element={
1251+
<motion.div
1252+
initial="initial"
1253+
animate="in"
1254+
exit="out"
1255+
variants={pageVariants}
1256+
transition={pageTransition}
1257+
>
1258+
<GalacticAgePage />
1259+
</motion.div>
1260+
}
1261+
/>
1262+
<Route
1263+
path="/apps/bpm-guesser"
1264+
element={
1265+
<motion.div
1266+
initial="initial"
1267+
animate="in"
1268+
exit="out"
1269+
variants={pageVariants}
1270+
transition={pageTransition}
1271+
>
1272+
<BpmGuesserPage />
1273+
</motion.div>
1274+
}
1275+
/>
12001276
{/* D&D specific 404 page */}
12011277
<Route
12021278
path="/stories/*"
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
import React, {useState} from 'react';
2+
import {Link} from 'react-router-dom';
3+
import {ArrowLeftIcon, PlantIcon} from '@phosphor-icons/react';
4+
import colors from '../../config/colors';
5+
import useSeo from '../../hooks/useSeo';
6+
7+
const BANANA_LENGTH_CM = 18; // Average banana length
8+
const BANANA_WEIGHT_G = 120; // Average banana weight
9+
10+
const BananaConverterPage = () => {
11+
useSeo({
12+
title: 'Banana for Scale Converter | Fezcodex',
13+
description: 'Convert measuring units into the internet standard: Bananas.',
14+
keywords: ['Fezcodex', 'banana for scale', 'converter', 'fun', 'measurement'],
15+
ogTitle: 'Banana for Scale Converter | Fezcodex',
16+
ogDescription: 'Convert measuring units into the internet standard: Bananas.',
17+
ogImage: 'https://fezcode.github.io/logo512.png',
18+
twitterCard: 'summary_large_image',
19+
twitterTitle: 'Banana for Scale Converter | Fezcodex',
20+
twitterDescription: 'Convert measuring units into the internet standard: Bananas.',
21+
twitterImage: 'https://fezcode.github.io/logo512.png',
22+
});
23+
24+
const [inputValue, setInputValue] = useState('');
25+
const [inputUnit, setInputUnit] = useState('cm');
26+
const [result, setResult] = useState(null);
27+
28+
const handleConvert = () => {
29+
const val = parseFloat(inputValue);
30+
if (isNaN(val)) {
31+
setResult(null);
32+
return;
33+
}
34+
35+
let valInCm = 0;
36+
let valInG = 0;
37+
let isLength = true;
38+
39+
switch (inputUnit) {
40+
// Lengths
41+
case 'cm':
42+
valInCm = val;
43+
break;
44+
case 'm':
45+
valInCm = val * 100;
46+
break;
47+
case 'km':
48+
valInCm = val * 100000;
49+
break;
50+
case 'in':
51+
valInCm = val * 2.54;
52+
break;
53+
case 'ft':
54+
valInCm = val * 30.48;
55+
break;
56+
// Weights
57+
case 'g':
58+
valInG = val;
59+
isLength = false;
60+
break;
61+
case 'kg':
62+
valInG = val * 1000;
63+
isLength = false;
64+
break;
65+
case 'lb':
66+
valInG = val * 453.592;
67+
isLength = false;
68+
break;
69+
case 'oz':
70+
valInG = val * 28.3495;
71+
isLength = false;
72+
break;
73+
default:
74+
break;
75+
}
76+
77+
if (isLength) {
78+
setResult(`${(valInCm / BANANA_LENGTH_CM).toFixed(2)} Bananas long`);
79+
} else {
80+
setResult(`${(valInG / BANANA_WEIGHT_G).toFixed(2)} Bananas heavy`);
81+
}
82+
};
83+
84+
const cardStyle = {
85+
backgroundColor: colors['app-alpha-10'],
86+
borderColor: colors['app-alpha-50'],
87+
color: colors.app,
88+
};
89+
90+
return (
91+
<div className="py-16 sm:py-24">
92+
<div className="mx-auto max-w-7xl px-6 lg:px-8 text-gray-300">
93+
<Link
94+
to="/apps"
95+
className="text-article hover:underline flex items-center justify-center gap-2 text-lg mb-4"
96+
>
97+
<ArrowLeftIcon size={24}/> Back to Apps
98+
</Link>
99+
<h1 className="text-4xl font-bold tracking-tight sm:text-6xl mb-4 flex items-center justify-center">
100+
<span className="codex-color">fc</span>
101+
<span className="separator-color">::</span>
102+
<span className="apps-color">apps</span>
103+
<span className="separator-color">::</span>
104+
<span className="single-app-color">banana</span>
105+
</h1>
106+
<hr className="border-gray-700"/>
107+
<div className="flex justify-center items-center mt-16">
108+
<div
109+
className="group bg-transparent border rounded-lg shadow-2xl p-6 flex flex-col justify-between relative transform overflow-hidden h-full w-full max-w-2xl"
110+
style={cardStyle}
111+
>
112+
<div
113+
className="absolute top-0 left-0 w-full h-full opacity-10"
114+
style={{
115+
backgroundImage:
116+
'radial-gradient(circle, white 1px, transparent 1px)',
117+
backgroundSize: '10px 10px',
118+
}}
119+
></div>
120+
<div className="relative z-10 p-1 text-center">
121+
<h1 className="text-3xl font-arvo font-normal mb-4 text-app flex items-center justify-center gap-2">
122+
<PlantIcon size={32}/> Banana Converter
123+
</h1>
124+
<hr className="border-gray-700 mb-6"/>
125+
126+
<div className="flex flex-col gap-4 mb-6">
127+
<div className="flex gap-2">
128+
<input
129+
type="number"
130+
value={inputValue}
131+
onChange={(e) => setInputValue(e.target.value)}
132+
placeholder="Value"
133+
className="w-full bg-black/20 border border-gray-600 rounded px-3 py-2 focus:outline-none focus:border-yellow-500 transition-colors"
134+
/>
135+
<select
136+
value={inputUnit}
137+
onChange={(e) => setInputUnit(e.target.value)}
138+
className="bg-black/20 border border-gray-600 rounded px-3 py-2 focus:outline-none focus:border-yellow-500 transition-colors"
139+
>
140+
<optgroup label="Length">
141+
<option value="cm">cm</option>
142+
<option value="m">m</option>
143+
<option value="km">km</option>
144+
<option value="in">inches</option>
145+
<option value="ft">feet</option>
146+
</optgroup>
147+
<optgroup label="Weight">
148+
<option value="g">grams</option>
149+
<option value="kg">kg</option>
150+
<option value="lb">lbs</option>
151+
<option value="oz">oz</option>
152+
</optgroup>
153+
</select>
154+
</div>
155+
<button
156+
onClick={handleConvert}
157+
className="px-6 py-2 rounded-md font-arvo font-normal border transition-colors duration-300 hover:bg-yellow-500/20 text-yellow-500 border-yellow-500"
158+
>
159+
Scale It!
160+
</button>
161+
</div>
162+
163+
{result && (
164+
<div className="text-3xl font-bold text-yellow-400 animate-pulse">
165+
{result} 🍌
166+
</div>
167+
)}
168+
169+
</div>
170+
</div>
171+
</div>
172+
</div>
173+
</div>
174+
);
175+
};
176+
177+
export default BananaConverterPage;

0 commit comments

Comments
 (0)