Skip to content

Commit 0279cf2

Browse files
committed
content(app): echo chamber
1 parent fabf78e commit 0279cf2

File tree

3 files changed

+299
-0
lines changed

3 files changed

+299
-0
lines changed

public/apps/apps.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,15 @@
155155
"icon": "SparkleIcon",
156156
"order": 2,
157157
"apps": [
158+
{
159+
"slug": "echo-chamber",
160+
"to": "/apps/echo-chamber",
161+
"title": "The Echo Chamber",
162+
"description": "A solitary social network where everyone agrees with you.",
163+
"icon": "ChatCircleIcon",
164+
"pinned_order": 27,
165+
"created_at": "2025-12-25T16:00:00+03:00"
166+
},
158167
{
159168
"slug": "synergy-flow",
160169
"to": "/apps/synergy-flow",

src/components/AnimatedRoutes.jsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ const MorseCodeTranslatorPage = lazy(
9999
);
100100
const MastermindPage = lazy(() => import('../pages/apps/MastermindPage'));
101101
const SynergyFlowPage = lazy(() => import('../pages/apps/SynergyFlowPage'));
102+
const EchoChamberPage = lazy(() => import('../pages/apps/EchoChamberPage'));
102103
const WordLadderPage = lazy(() => import('../pages/apps/WordLadderPage'));
103104
const LightsOutPage = lazy(() => import('../pages/apps/LightsOutPage'));
104105
const NonogramPage = lazy(() => import('../pages/apps/NonogramPage'));
@@ -1738,6 +1739,22 @@ const AnimatedRoutes = ({
17381739
</motion.div>
17391740
}
17401741
/>
1742+
<Route
1743+
path="/apps/echo-chamber"
1744+
element={
1745+
<motion.div
1746+
initial="initial"
1747+
animate="in"
1748+
exit="out"
1749+
variants={pageVariants}
1750+
transition={pageTransition}
1751+
>
1752+
<Suspense fallback={<Loading />}>
1753+
<EchoChamberPage />
1754+
</Suspense>
1755+
</motion.div>
1756+
}
1757+
/>
17411758
<Route
17421759
path="/apps/word-ladder"
17431760
element={

src/pages/apps/EchoChamberPage.jsx

Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
import React, { useState, useCallback, useRef } from 'react';
2+
import { motion, AnimatePresence } from 'framer-motion';
3+
import { Link } from 'react-router-dom';
4+
import {
5+
HeartIcon,
6+
ChatCircleIcon,
7+
PaperPlaneRightIcon,
8+
ArrowLeftIcon,
9+
UserIcon,
10+
DotsThreeIcon,
11+
ShareNetworkIcon
12+
} from '@phosphor-icons/react';
13+
import useSeo from '../../hooks/useSeo';
14+
15+
const BOT_NAMES = [
16+
'Skylar_99', 'NeonVibe', 'CryptoKing', 'ZenMaster', 'Bella_Rose',
17+
'TechGuru', 'PixelArtist', 'Influencer_01', 'HappyLife', 'TravelBug'
18+
];
19+
20+
const POSITIVE_COMMENTS = [
21+
"This is literally me right now! 😍",
22+
"So brave to say this. 👏👏",
23+
"I feel seen! ✨",
24+
"This! 1000x This!",
25+
"Undefined behavior? More like undefined brilliance.",
26+
"You're disrupting the paradigm!",
27+
"Slay! 🔥",
28+
"Big mood.",
29+
"Manifesting this energy. ✨",
30+
"Louder for the people in the back! 🗣️"
31+
];
32+
33+
const PRESET_POSTS = [
34+
"Just drank water. Hydration is key! 💧",
35+
"Hustling 25/8. Sleep is for the weak. 💼",
36+
"Thinking about switching to a new JS framework...",
37+
"Sunset state of mind. 🌅",
38+
"Is it just me or is the algorithm weird today?",
39+
];
40+
41+
const EchoChamberPage = () => {
42+
useSeo({
43+
title: 'The Echo Chamber | Fezcodex',
44+
description: 'A solitary social network where everyone agrees with you.',
45+
keywords: ['social media', 'simulation', 'glassmorphism', 'echo chamber', 'bots'],
46+
});
47+
48+
const [posts, setPosts] = useState([]);
49+
const [inputText, setInputText] = useState('');
50+
const feedEndRef = useRef(null);
51+
52+
const simulateEngagement = useCallback((postId) => {
53+
// Rapidly increase likes
54+
const likeInterval = setInterval(() => {
55+
setPosts(prev => prev.map(post => {
56+
if (post.id === postId) {
57+
return { ...post, likes: post.likes + Math.floor(Math.random() * 50) + 1 };
58+
}
59+
return post;
60+
}));
61+
}, 200);
62+
63+
// Add random comments
64+
let commentCount = 0;
65+
const commentInterval = setInterval(() => {
66+
if (commentCount > 5) {
67+
clearInterval(commentInterval);
68+
clearInterval(likeInterval); // Stop likes eventually
69+
return;
70+
}
71+
72+
const botName = BOT_NAMES[Math.floor(Math.random() * BOT_NAMES.length)];
73+
const botComment = POSITIVE_COMMENTS[Math.floor(Math.random() * POSITIVE_COMMENTS.length)];
74+
75+
setPosts(prev => prev.map(post => {
76+
if (post.id === postId) {
77+
return {
78+
...post,
79+
comments: [...post.comments, { id: Date.now(), user: botName, text: botComment }]
80+
};
81+
}
82+
return post;
83+
}));
84+
commentCount++;
85+
}, 1500);
86+
}, []);
87+
88+
const createPost = useCallback((text) => {
89+
const newId = Date.now();
90+
const newPost = {
91+
id: newId,
92+
text: text,
93+
likes: 0,
94+
comments: [],
95+
timestamp: 'Just now',
96+
};
97+
setPosts(prev => [newPost, ...prev]);
98+
simulateEngagement(newId);
99+
}, [simulateEngagement]);
100+
101+
const handleSubmit = (e) => {
102+
e.preventDefault();
103+
if (!inputText.trim()) return;
104+
createPost(inputText);
105+
setInputText('');
106+
};
107+
108+
// Glassmorphism Styles
109+
const glassCard = "bg-white/10 backdrop-blur-xl border border-white/20 rounded-3xl shadow-xl";
110+
const glassInput = "bg-white/5 backdrop-blur-md border border-white/10 rounded-full focus:outline-none focus:ring-2 focus:ring-pink-300 text-white placeholder-pink-100/50";
111+
112+
return (
113+
<div className="min-h-screen bg-gradient-to-br from-indigo-500 via-purple-500 to-pink-500 font-sans text-white overflow-hidden relative selection:bg-pink-300 selection:text-pink-900">
114+
115+
{/* Animated Background Blobs */}
116+
<div className="absolute top-0 left-0 w-full h-full overflow-hidden -z-0">
117+
<div className="absolute top-[-10%] left-[-10%] w-[50%] h-[50%] bg-purple-400 rounded-full mix-blend-multiply filter blur-3xl opacity-70 animate-blob"></div>
118+
<div className="absolute top-[-10%] right-[-10%] w-[50%] h-[50%] bg-yellow-400 rounded-full mix-blend-multiply filter blur-3xl opacity-70 animate-blob animation-delay-2000"></div>
119+
<div className="absolute bottom-[-20%] left-[20%] w-[50%] h-[50%] bg-pink-400 rounded-full mix-blend-multiply filter blur-3xl opacity-70 animate-blob animation-delay-4000"></div>
120+
</div>
121+
122+
{/* Navigation Return Link */}
123+
<div className="absolute top-6 left-6 z-50">
124+
<Link to="/apps" className={`${glassCard} px-6 py-3 flex items-center gap-2 hover:bg-white/20 transition-all text-xs font-bold font-mono tracking-widest uppercase`}>
125+
<ArrowLeftIcon weight="bold" />
126+
<span>Return to Reality</span>
127+
</Link>
128+
</div>
129+
130+
<div className="container mx-auto max-w-2xl px-4 py-20 relative z-10 flex flex-col h-screen">
131+
132+
{/* Header */}
133+
<div className="text-center mb-8">
134+
<h1 className="text-6xl font-playfairDisplay font-bold mb-2 tracking-tight drop-shadow-lg">The Echo Chamber</h1>
135+
<p className="text-pink-100 text-xl font-arvo opacity-90 italic">Where everyone agrees with you. Forever.</p>
136+
</div>
137+
138+
{/* Feed Area */}
139+
<div className="flex-1 overflow-y-auto custom-scrollbar space-y-6 pr-2 pb-24">
140+
<AnimatePresence>
141+
{posts.map(post => (
142+
<motion.div
143+
key={post.id}
144+
initial={{ opacity: 0, y: 50, scale: 0.9 }}
145+
animate={{ opacity: 1, y: 0, scale: 1 }}
146+
exit={{ opacity: 0 }}
147+
className={`${glassCard} p-6`}
148+
>
149+
{/* Post Header */}
150+
<div className="flex items-center justify-between mb-4">
151+
<div className="flex items-center gap-3">
152+
<div className="w-10 h-10 rounded-full bg-gradient-to-tr from-yellow-200 to-pink-400 flex items-center justify-center text-xl shadow-inner">
153+
<UserIcon weight="fill" className="text-white" />
154+
</div>
155+
<div>
156+
<h3 className="font-playfairDisplay font-bold text-xl leading-tight">You</h3>
157+
<p className="text-xs font-mono text-pink-100 opacity-70 uppercase tracking-tighter">{post.timestamp}</p>
158+
</div>
159+
</div>
160+
<DotsThreeIcon size={24} className="opacity-70 cursor-pointer hover:opacity-100" />
161+
</div>
162+
163+
{/* Post Content */}
164+
<p className="text-2xl font-arvo mb-6 font-medium leading-relaxed drop-shadow-sm">{post.text}</p>
165+
166+
{/* Post Actions */}
167+
<div className="flex items-center gap-6 mb-4 text-pink-100 font-mono text-sm">
168+
<div className="flex items-center gap-2 cursor-pointer hover:text-white transition-colors">
169+
<HeartIcon weight="fill" className="text-pink-300" size={20} />
170+
<span className="font-bold">{post.likes.toLocaleString()}</span>
171+
</div>
172+
<div className="flex items-center gap-2 cursor-pointer hover:text-white transition-colors">
173+
<ChatCircleIcon weight="bold" size={20} />
174+
<span>{post.comments.length}</span>
175+
</div>
176+
<div className="flex items-center gap-2 cursor-pointer hover:text-white transition-colors ml-auto">
177+
<ShareNetworkIcon weight="bold" size={20} />
178+
</div>
179+
</div>
180+
181+
{/* Comments Section */}
182+
<div className="space-y-3 pt-4 border-t border-white/10 font-arvo">
183+
<AnimatePresence>
184+
{post.comments.map(comment => (
185+
<motion.div
186+
key={comment.id}
187+
initial={{ opacity: 0, x: -10 }}
188+
animate={{ opacity: 1, x: 0 }}
189+
className="flex gap-3 text-sm"
190+
>
191+
<div className="w-8 h-8 rounded-full bg-gradient-to-br from-blue-300 to-purple-400 flex-shrink-0"></div>
192+
<div className="bg-white/5 rounded-2xl px-4 py-2">
193+
<span className="font-bold font-mono text-pink-200 block text-[10px] uppercase mb-1 tracking-wider">@{comment.user}</span>
194+
<span className="opacity-90 leading-relaxed">{comment.text}</span>
195+
</div>
196+
</motion.div>
197+
))}
198+
</AnimatePresence>
199+
</div>
200+
</motion.div>
201+
))}
202+
</AnimatePresence>
203+
<div ref={feedEndRef} />
204+
</div>
205+
206+
{/* Input Area */}
207+
<div className="absolute bottom-0 left-0 w-full p-6 bg-gradient-to-t from-purple-900/50 to-transparent">
208+
<form onSubmit={handleSubmit} className="relative max-w-2xl mx-auto">
209+
<input
210+
type="text"
211+
value={inputText}
212+
onChange={(e) => setInputText(e.target.value)}
213+
placeholder="Say something brilliant..."
214+
className={`${glassInput} w-full py-5 pl-8 pr-16 text-xl font-arvo shadow-2xl`}
215+
/>
216+
<button
217+
type="submit"
218+
className="absolute right-3 top-1/2 -translate-y-1/2 p-3 rounded-full bg-pink-500 hover:bg-pink-400 transition-colors shadow-lg text-white"
219+
>
220+
<PaperPlaneRightIcon size={24} weight="fill" />
221+
</button>
222+
</form>
223+
{/* Presets */}
224+
<div className="flex gap-2 overflow-x-auto mt-4 pb-2 no-scrollbar justify-center">
225+
{PRESET_POSTS.map((preset, i) => (
226+
<button
227+
key={i}
228+
onClick={() => createPost(preset)}
229+
className="whitespace-nowrap px-4 py-2 rounded-full bg-white/10 hover:bg-white/20 text-xs font-bold font-mono tracking-tighter uppercase backdrop-blur-sm border border-white/10 transition-colors"
230+
>
231+
{preset}
232+
</button>
233+
))}
234+
</div>
235+
</div>
236+
237+
</div>
238+
239+
<style jsx="true">{`
240+
.custom-scrollbar::-webkit-scrollbar {
241+
width: 6px;
242+
}
243+
.custom-scrollbar::-webkit-scrollbar-track {
244+
background: rgba(255, 255, 255, 0.05);
245+
}
246+
.custom-scrollbar::-webkit-scrollbar-thumb {
247+
background: rgba(255, 255, 255, 0.2);
248+
border-radius: 10px;
249+
}
250+
.no-scrollbar::-webkit-scrollbar {
251+
display: none;
252+
}
253+
@keyframes blob {
254+
0% { transform: translate(0px, 0px) scale(1); }
255+
33% { transform: translate(30px, -50px) scale(1.1); }
256+
66% { transform: translate(-20px, 20px) scale(0.9); }
257+
100% { transform: translate(0px, 0px) scale(1); }
258+
}
259+
.animate-blob {
260+
animation: blob 7s infinite;
261+
}
262+
.animation-delay-2000 {
263+
animation-delay: 2s;
264+
}
265+
.animation-delay-4000 {
266+
animation-delay: 4s;
267+
}
268+
`}</style>
269+
</div>
270+
);
271+
};
272+
273+
export default EchoChamberPage;

0 commit comments

Comments
 (0)