|
| 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