Skip to content

Commit 125c7c6

Browse files
committed
new(apps): magic 8-ball
1 parent 5b0e011 commit 125c7c6

File tree

4 files changed

+170
-0
lines changed

4 files changed

+170
-0
lines changed

public/apps/apps.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,13 @@
4646
"title": "Excuse Generator",
4747
"description": "Generate funny and absurd excuses for any situation.",
4848
"icon": "SmileyWinkIcon"
49+
},
50+
{
51+
"slug": "magic-8-ball",
52+
"to": "/apps/magic-8-ball",
53+
"title": "Magic 8-Ball",
54+
"description": "Ask a yes/no question and let the Magic 8-Ball reveal your fate!",
55+
"icon": "QuestionIcon"
4956
}
5057
]
5158
},

src/components/AnimatedRoutes.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import JsonPimlConverterPage from '../pages/apps/JsonPimlConverterPage';
4242
import TextDiffCheckerPage from '../pages/apps/TextDiffCheckerPage';
4343
import CronJobGeneratorPage from '../pages/apps/CronJobGeneratorPage';
4444
import ExcuseGeneratorPage from '../pages/apps/ExcuseGeneratorPage';
45+
import MagicEightBallPage from '../pages/apps/MagicEightBallPage';
4546
import JSONGeneratorPage from '../pages/apps/JSONGeneratorPage';
4647
import SettingsPage from '../pages/SettingsPage'; // Import SettingsPage
4748

@@ -397,6 +398,7 @@ function AnimatedRoutes() {
397398
<Route path="/apps::tdc" element={<Navigate to="/apps/text-diff-checker" replace />} />
398399
<Route path="/apps::cron" element={<Navigate to="/apps/cron-job-generator" replace />} />
399400
<Route path="/apps::excuse" element={<Navigate to="/apps/excuse-generator" replace />} />
401+
<Route path="/apps::8ball" element={<Navigate to="/apps/magic-8-ball" replace />} />
400402
{/* End of hardcoded redirects */}
401403
<Route
402404
path="/apps/ip"
@@ -440,6 +442,20 @@ function AnimatedRoutes() {
440442
</motion.div>
441443
}
442444
/>
445+
<Route
446+
path="/apps/magic-8-ball"
447+
element={
448+
<motion.div
449+
initial="initial"
450+
animate="in"
451+
exit="out"
452+
variants={pageVariants}
453+
transition={pageTransition}
454+
>
455+
<MagicEightBallPage />
456+
</motion.div>
457+
}
458+
/>
443459
<Route
444460
path="/apps/text-diff-checker"
445461
element={
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import React, { useState } from 'react';
2+
import { Link } from 'react-router-dom';
3+
import { ArrowLeftIcon, Question } from '@phosphor-icons/react';
4+
import useSeo from "../../hooks/useSeo";
5+
import colors from "../../config/colors";
6+
7+
const magicEightBallAnswers = [
8+
// Positive
9+
"It is certain.",
10+
"It is decidedly so.",
11+
"Without a doubt.",
12+
"Yes, definitely.",
13+
"You may rely on it.",
14+
"As I see it, yes.",
15+
"Most likely.",
16+
"Outlook good.",
17+
"Yes.",
18+
"Signs point to yes.",
19+
// Neutral
20+
"Reply hazy, try again.",
21+
"Ask again later.",
22+
"Better not tell you now.",
23+
"Cannot predict now.",
24+
"Concentrate and ask again.",
25+
// Negative
26+
"Don't count on it.",
27+
"My reply is no.",
28+
"My sources say no.",
29+
"Outlook not so good.",
30+
"Very doubtful.",
31+
];
32+
33+
function MagicEightBallPage() {
34+
useSeo({
35+
title: 'Magic 8-Ball | Fezcodex',
36+
description: 'Ask a yes/no question and let the Magic 8-Ball reveal your fate!',
37+
keywords: ['Fezcodex', 'magic 8-ball', 'decision maker', 'fun app', 'random answer'],
38+
ogTitle: 'Magic 8-Ball | Fezcodex',
39+
ogDescription: 'Ask a yes/no question and let the Magic 8-Ball reveal your fate!',
40+
ogImage: 'https://fezcode.github.io/logo512.png',
41+
twitterCard: 'summary_large_image',
42+
twitterTitle: 'Magic 8-Ball | Fezcodex',
43+
twitterDescription: 'Ask a yes/no question and let the Magic 8-Ball reveal your fate!',
44+
twitterImage: 'https://fezcode.github.io/logo512.png'
45+
});
46+
47+
const [question, setQuestion] = useState('');
48+
const [answer, setAnswer] = useState("Ask the 8-Ball a question!");
49+
const [isShaking, setIsShaking] = useState(false);
50+
51+
const askEightBall = () => {
52+
if (!question.trim()) {
53+
setAnswer("Please ask a question first!");
54+
return;
55+
}
56+
57+
setIsShaking(true);
58+
setAnswer("Thinking..."); // Show thinking state
59+
60+
setTimeout(() => {
61+
const randomIndex = Math.floor(Math.random() * magicEightBallAnswers.length);
62+
setAnswer(magicEightBallAnswers[randomIndex]);
63+
setIsShaking(false);
64+
}, 1500); // Simulate a shake/think time
65+
};
66+
67+
const buttonStyle = `px-6 py-2 rounded-md text-lg font-arvo font-normal transition-colors duration-300 ease-in-out border bg-tb text-app border-app-alpha-50 hover:bg-app/15`;
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">8ball</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 overflow-hidden h-full w-full max-w-md"
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+
<h1 className="text-3xl font-arvo font-normal mb-4 text-app"> Magic 8-Ball </h1>
107+
<hr className="border-gray-700 mb-4" />
108+
109+
<div className="mb-6">
110+
<label htmlFor="question-input" className="block text-lg font-semibold mb-2 text-app">
111+
Your Question:
112+
</label>
113+
<input
114+
id="question-input"
115+
type="text"
116+
value={question}
117+
onChange={(e) => setQuestion(e.target.value)}
118+
placeholder="e.g., Will I become a millionaire?"
119+
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"
120+
onKeyPress={(e) => {
121+
if (e.key === 'Enter') {
122+
askEightBall();
123+
}
124+
}}
125+
/>
126+
</div>
127+
128+
<div className={`flex flex-col items-center justify-center mb-6 p-4 bg-gray-800/50 rounded-md min-h-[120px] transition-all duration-300 ${isShaking ? 'animate-shake' : ''}`}>
129+
<p className="text-2xl text-center font-bold text-app-light">{answer}</p>
130+
</div>
131+
132+
<div className="flex justify-center">
133+
<button onClick={askEightBall} className={`${buttonStyle} flex items-center gap-2`} disabled={isShaking}>
134+
<Question size={24} /> Ask the 8-Ball
135+
</button>
136+
</div>
137+
</div>
138+
</div>
139+
</div>
140+
</div>
141+
</div>
142+
);
143+
}
144+
145+
export default MagicEightBallPage;

src/utils/appIcons.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
GameControllerIcon,
2323
ClockIcon,
2424
SmileyWinkIcon,
25+
QuestionIcon,
2526
} from '@phosphor-icons/react';
2627

2728
export const appIcons = {
@@ -48,4 +49,5 @@ export const appIcons = {
4849
GameControllerIcon,
4950
ClockIcon,
5051
SmileyWinkIcon,
52+
QuestionIcon,
5153
};

0 commit comments

Comments
 (0)