Skip to content

Commit bc371f2

Browse files
committed
feat: fng.
1 parent 4448b00 commit bc371f2

File tree

3 files changed

+177
-0
lines changed

3 files changed

+177
-0
lines changed

src/components/AnimatedRoutes.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import HashGeneratorPage from '../pages/apps/HashGeneratorPage';
2828
import UuidGeneratorPage from '../pages/apps/UuidGeneratorPage';
2929
import ColorPaletteGeneratorPage from '../pages/apps/ColorPaletteGeneratorPage';
3030
import CssUnitConverterPage from '../pages/apps/CssUnitConverterPage';
31+
import FantasyNameGeneratorPage from '../pages/apps/FantasyNameGeneratorPage';
3132

3233
import UsefulLinksPage from '../pages/UsefulLinksPage';
3334

@@ -450,6 +451,20 @@ function AnimatedRoutes() {
450451
</motion.div>
451452
}
452453
/>
454+
<Route
455+
path="/apps/fantasy-name-generator"
456+
element={
457+
<motion.div
458+
initial="initial"
459+
animate="in"
460+
exit="out"
461+
variants={pageVariants}
462+
transition={pageTransition}
463+
>
464+
<FantasyNameGeneratorPage />
465+
</motion.div>
466+
}
467+
/>
453468
{/* D&D specific 404 page */}
454469
<Route
455470
path="/dnd/*"

src/pages/AppPage.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,11 @@ const apps = [
5555
title: 'CSS Unit Converter',
5656
description: 'Convert between px, em, rem, vw, vh, and % units.',
5757
},
58+
{
59+
to: '/apps/fantasy-name-generator',
60+
title: 'Fantasy Name Generator',
61+
description: 'Generate fantasy names for characters, places, and items.',
62+
},
5863
];
5964

6065
function AppPage() {
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
import React, { useState } from 'react';
2+
import usePageTitle from '../../utils/usePageTitle';
3+
import { Link } from 'react-router-dom';
4+
import { ArrowLeftIcon, CopySimpleIcon } from '@phosphor-icons/react';
5+
import colors from '../../config/colors';
6+
import { useToast } from '../../hooks/useToast';
7+
8+
const FantasyNameGeneratorPage = () => {
9+
usePageTitle('Fantasy Name Generator');
10+
11+
const [nameType, setNameType] = useState('human');
12+
const [generatedName, setGeneratedName] = useState('');
13+
const { addToast } = useToast();
14+
15+
const handleCopy = async () => {
16+
if (generatedName) {
17+
try {
18+
await navigator.clipboard.writeText(generatedName);
19+
addToast({title: 'Success', message: 'Name copied to clipboard!', duration: 3000});
20+
} catch (err) {
21+
addToast({title: 'Failure', message: 'Failed to copy name!', duration: 3000});
22+
console.error('Failed to copy: ', err);
23+
}
24+
}
25+
};
26+
27+
const generateName = () => {
28+
const nameParts = {
29+
human: {
30+
prefixes: ['Ael', 'Bor', 'Cael', 'Dra', 'El', 'Fen', 'Gor', 'Hal', 'Isol', 'Jor', 'Kal', 'Lor', 'Mor', 'Nor', 'Orin', 'Per', 'Quinn', 'Roric', 'Ser', 'Thorn', 'Ulric', 'Val', 'Wyn'],
31+
middles: ['an', 'den', 'ric', 'wyn', 'gar', 'lin', 'dor', 'mar', 'van', 'thar', 'mond', 'bert', 'fred', 'gorn', 'hald', 'kiel', 'land', 'morn', 'niel', 'rath', 'sian', 'tian', 'vyn'],
32+
suffixes: ['us', 'a', 'on', 'en', 'or', 'yn', 'ia', 'eth', 'an', 'ar', 'da', 'el', 'is', 'ra', 'os', 'er', 'in', 'of', 'um', 'ald', 'ard', 'bert', 'mond', 'red', 'son', 'ton'],
33+
},
34+
elf: {
35+
prefixes: ['Aer', 'Ael', 'El', 'Fael', 'Lae', 'Lir', 'Sil', 'Thran', 'Val', 'Xyl', 'Yl', 'Zyl'],
36+
middles: ['an', 'ara', 'en', 'iel', 'ion', 'ith', 'or', 'wen', 'yn', 'dor', 'mar', 'van', 'thal', 'rion', 'syl', 'tir'],
37+
suffixes: ['as', 'a', 'el', 'en', 'ia', 'ion', 'is', 'or', 'os', 'ra', 'us', 'wyn', 'ys', 'eth', 'iel', 'in', 'on', 'ril', 'wen'],
38+
},
39+
dwarf: {
40+
prefixes: ['Bor', 'Dur', 'Gim', 'Kael', 'Thrain', 'Bal', 'Dwal', 'Fili', 'Kili', 'Oin', 'Gloin', 'Thor', 'Bif', 'Bof', 'Bomb'],
41+
middles: ['in', 'grim', 'li', 'son', 'gar', 'rek', 'und', 'orn', 'rak', 'dal', 'mar', 'stone', 'beard', 'hammer', 'axe'],
42+
suffixes: ['son', 'in', 'grim', 'li', 'rek', 'und', 'orn', 'rak', 'dal', 'mar', 'stone', 'beard', 'hammer', 'axe', 'foot', 'hand', 'shield'],
43+
},
44+
orc: {
45+
prefixes: ['Grak', 'Thorg', 'Ur', 'Morg', 'Grish', 'Azog', 'Bolg', 'Drog', 'Grog', 'Karg', 'Maug', 'Snag'],
46+
middles: ['ash', 'uk', 'og', 'nar', 'gul', 'rak', 'oth', 'fang', 'skull', 'blood', 'hide', 'tooth'],
47+
suffixes: ['ak', 'ug', 'osh', 'uk', 'a', 'ar', 'da', 'er', 'ish', 'ok', 'or', 'oth', 'ra', 'rag', 'rot', 'ruk', 'um', 'un', 'ur'],
48+
},
49+
};
50+
51+
const getRandomElement = (arr) => arr[Math.floor(Math.random() * arr.length)];
52+
53+
const selectedParts = nameParts[nameType];
54+
if (!selectedParts) {
55+
setGeneratedName('Invalid name type');
56+
return;
57+
}
58+
59+
let name = getRandomElement(selectedParts.prefixes);
60+
if (Math.random() > 0.3) { // 70% chance to add a middle part
61+
name += getRandomElement(selectedParts.middles);
62+
}
63+
name += getRandomElement(selectedParts.suffixes);
64+
65+
// Simple capitalization
66+
setGeneratedName(name.charAt(0).toUpperCase() + name.slice(1));
67+
};
68+
69+
const cardStyle = {
70+
backgroundColor: colors['app-alpha-10'],
71+
borderColor: colors['app-alpha-50'],
72+
color: colors.app,
73+
};
74+
75+
return (
76+
<div className="py-16 sm:py-24">
77+
<div className="mx-auto max-w-7xl px-6 lg:px-8 text-gray-300">
78+
<Link
79+
to="/apps"
80+
className="text-article hover:underline flex items-center justify-center gap-2 text-lg mb-4"
81+
>
82+
<ArrowLeftIcon size={24} /> Back to Apps
83+
</Link>
84+
<h1 className="text-4xl font-bold tracking-tight sm:text-6xl mb-4 flex items-center">
85+
<span className="codex-color">fc</span>
86+
<span className="separator-color">::</span>
87+
<span className="apps-color">apps</span>
88+
<span className="separator-color">::</span>
89+
<span className="single-app-color">fantasy-names</span>
90+
</h1>
91+
<hr className="border-gray-700" />
92+
<div className="flex justify-center items-center mt-16">
93+
<div
94+
className="group bg-transparent border rounded-lg shadow-2xl p-6 flex flex-col justify-between relative transform transition-all duration-300 ease-in-out scale-105 overflow-hidden h-full w-full max-w-4xl"
95+
style={cardStyle}
96+
>
97+
<div
98+
className="absolute top-0 left-0 w-full h-full opacity-10"
99+
style={{
100+
backgroundImage:
101+
'radial-gradient(circle, white 1px, transparent 1px)',
102+
backgroundSize: '10px 10px',
103+
}}
104+
></div>
105+
<div className="relative z-10 p-1">
106+
<div className="mb-6">
107+
<label htmlFor="nameType" className="block text-sm font-medium text-gray-300 mb-2">
108+
Name Type
109+
</label>
110+
<select
111+
id="nameType"
112+
className="mt-1 block w-full p-2 border border-gray-600 rounded-md bg-gray-700 text-white focus:ring-blue-500 focus:border-blue-500"
113+
value={nameType}
114+
onChange={(e) => setNameType(e.target.value)}
115+
>
116+
<option value="human">Human</option>
117+
<option value="elf">Elf</option>
118+
<option value="dwarf">Dwarf</option>
119+
<option value="orc">Orc</option>
120+
</select>
121+
</div>
122+
<button
123+
onClick={generateName}
124+
className="px-6 py-2 rounded-md text-lg font-arvo font-normal transition-colors duration-300 ease-in-out"
125+
style={{
126+
backgroundColor: 'rgba(0, 0, 0, 0.2)',
127+
color: cardStyle.color,
128+
borderColor: cardStyle.borderColor,
129+
border: '1px solid',
130+
'--hover-bg-color': colors['app-alpha-50'],
131+
}}
132+
onMouseEnter={(e) => e.currentTarget.style.backgroundColor = 'var(--hover-bg-color)'}
133+
onMouseLeave={(e) => e.currentTarget.style.backgroundColor = 'rgba(0, 0, 0, 0.2)'}
134+
>
135+
Generate Name
136+
</button>
137+
{generatedName && (
138+
<div className="mt-6 p-4 bg-gray-700 rounded-md text-center flex items-center justify-center space-x-2">
139+
<p className="text-xl font-arvo font-normal text-blue-400">{generatedName}</p>
140+
<button
141+
onClick={handleCopy}
142+
className="p-2 rounded-full bg-gray-600 hover:bg-gray-500 transition-colors duration-200"
143+
title="Copy to clipboard"
144+
>
145+
<CopySimpleIcon size={20} color={colors.app} />
146+
</button>
147+
</div>
148+
)}
149+
</div>
150+
</div>
151+
</div>
152+
</div>
153+
</div>
154+
);
155+
};
156+
157+
export default FantasyNameGeneratorPage;

0 commit comments

Comments
 (0)