This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

at main 12 kB View raw
1// src/components/MatterLoadingAnimation.jsx 2import React, { useEffect, useRef, useState, useMemo } from "react"; 3import Matter from "matter-js"; 4import "./MatterLoadingAnimation.css"; 5 6const MatterLoadingAnimation = () => { 7 const sceneRef = useRef(null); 8 const timeoutRef = useRef(null); // For circle appearance 9 const gravityTimerRef = useRef(null); // For dynamic gravity 10 const messageTimeoutRef = useRef(null); // For updating the message 11 12// Wrap messages in useMemo so they don't change on every render. 13const messages = useMemo( 14 () => [ 15 "Counting total posts", 16 "Calculating posts per day", 17 "Measuring replies per day", 18 "Estimating reply percentages", 19 "Tracking repost activity", 20 "Differentiating self-reposts from reposts of others", 21 "Counting posts with images", 22 "Measuring image posts per day", 23 "Calculating posts with alt text", 24 "Determining posts without alt text", 25 "Highlighting text-only posts", 26 "Comparing text posts to media posts", 27 "Measuring posts containing mentions", 28 "Tracking posts with links", 29 "Assessing posts with video content", 30 "Calculating engagement metrics", 31 "Determining posting style", 32 "Identifying social interactions", 33 "Analyzing social status trends", 34 "Calculating total collections", 35 "Differentiating Bluesky and Atproto collections", 36 "Comparing records per day", 37 "Tracking Bluesky record percentages", 38 "Calculating account age in days", 39 "Measuring posts per account age", 40 "Estimating blobs per post", 41 "Evaluating profile completion", 42 "Analyzing follower-to-following ratios", 43 "Compiling narrative insights", 44 "Detecting log operations", 45 "Analyzing handle history", 46 "Tracking recent profile edits", 47 "Identifying active aliases", 48 "Calculating domain uniqueness", 49 "Generating Bluesky scores", 50 "Calculating Atproto scores", 51 "Evaluating combined scores", 52 ], 53 [] 54 ); 55 56 57 // State to hold the current message. 58 const [message, setMessage] = useState("Loading account data"); 59 // State to control the fade effect. 60 const [fade, setFade] = useState(false); 61 const [circleCount, setCircleCount] = useState(0); // Track circle count 62 const [specialCircleCount, setSpecialCircleCount] = useState(0); // Special circle count 63 64 65 // Update the message on a random interval between 4 and 10 seconds. 66 useEffect(() => { 67 const updateMessage = () => { 68 // Random delay between 4000ms and 10000ms. 69 const delay = Math.random() * (10000 - 1000) + 4000; 70 messageTimeoutRef.current = setTimeout(() => { 71 // Trigger fade-out. 72 setFade(true); 73 // After fade-out duration (300ms), update the message and fade back in. 74 setTimeout(() => { 75 const randomMessage = messages[Math.floor(Math.random() * messages.length)]; 76 setMessage(randomMessage); 77 setFade(false); 78 }, 300); 79 updateMessage(); 80 }, delay); 81 }; 82 updateMessage(); 83 return () => { 84 clearTimeout(messageTimeoutRef.current); 85 }; 86 }, [messages]); 87 88 useEffect(() => { 89 // Set the fixed dimensions for the canvas. 90 const width = 250; 91 const height = 250; 92 93 // Create engine and set initial gravity. 94 const engine = Matter.Engine.create(); 95 engine.world.gravity.scale = 0.001; 96 // Start with base gravity values (we'll update these continuously). 97 engine.world.gravity.x = 0; 98 engine.world.gravity.y = 0; 99 100 // Create the renderer with the fixed dimensions. 101 const render = Matter.Render.create({ 102 element: sceneRef.current, 103 engine: engine, 104 options: { 105 width, 106 height, 107 showIds: false, 108 wireframes: false, 109 pixelRatio: 'auto', 110 }, 111 }); 112 Matter.Render.run(render); 113 114 // Create and run the runner. 115 const runner = Matter.Runner.create(); 116 Matter.Runner.run(runner, engine); 117 118 // Walls settings. 119 const wallThickness = 6; 120 const wallRenderOptions = { 121 fillStyle: "#f0f0f000", // Custom fill (transparent in this case) 122 }; 123 124 // Create walls with custom styling (top, bottom, left, and right). 125 const walls = [ 126 // Top wall. 127 Matter.Bodies.rectangle( 128 width / 2, 129 0, 130 width, 131 wallThickness, 132 { isStatic: true, render: wallRenderOptions } 133 ), 134 // Bottom wall. 135 Matter.Bodies.rectangle( 136 width / 2, 137 height, 138 width, 139 wallThickness, 140 { isStatic: true, render: wallRenderOptions } 141 ), 142 // Left wall. 143 Matter.Bodies.rectangle( 144 0, 145 height / 2, 146 wallThickness, 147 height, 148 { isStatic: true, render: wallRenderOptions } 149 ), 150 // Right wall. 151 Matter.Bodies.rectangle( 152 width, 153 height / 2, 154 wallThickness, 155 height, 156 { isStatic: true, render: wallRenderOptions } 157 ), 158 ]; 159 Matter.World.add(engine.world, walls); 160 161 // Enable mouse control. 162 const mouse = Matter.Mouse.create(render.canvas); 163 const mouseConstraint = Matter.MouseConstraint.create(engine, { 164 mouse: mouse, 165 constraint: { stiffness: 0.2, render: { visible: false } }, 166 }); 167 Matter.World.add(engine.world, mouseConstraint); 168 render.mouse = mouse; 169 170 // Fit the render viewport to the scene. 171 Matter.Render.lookAt(render, { 172 min: { x: 0, y: 0 }, 173 max: { x: width, y: height }, 174 }); 175 176 // SETTINGS FOR OUR BLUE CIRCLES. 177 const minRadius = 4; 178 const maxRadius = 12; 179 const growthDuration = 400; // milliseconds over which the circle grows 180 181 // Get custom circle render styling from CSS variables. 182 const rootStyles = getComputedStyle(document.documentElement); 183 const circleFill = rootStyles.getPropertyValue("--circle-fill").trim() || "#004f84c2"; 184 const circleStroke = rootStyles.getPropertyValue("--circle-stroke").trim() || "#3498dbdc"; 185 const circleLineWidth = parseFloat(rootStyles.getPropertyValue("--circle-linewidth")) || 6; 186 187 // Bounce easing function. 188 function bounceEaseOut(t) { 189 const n1 = 7.5625; 190 const d1 = 2.75; 191 if (t < 1 / d1) { 192 return n1 * t * t; 193 } else if (t < 2 / d1) { 194 t -= 1.5 / d1; 195 return n1 * t * t + 0.75; 196 } else if (t < 2.5 / d1) { 197 t -= 2.25 / d1; 198 return n1 * t * t + 0.9375; 199 } else { 200 t -= 2.625 / d1; 201 return n1 * t * t + 0.984375; 202 } 203 } 204 205 // Create a blue circle that "pops" up with a bounce effect. 206 // The circle will appear at a random position on the canvas. 207 const createCircle = () => { 208 // Determine the final target radius. 209 const targetRadius = Math.random() * (maxRadius - minRadius) + minRadius; 210 // Use a very small initial radius so we can "grow" it. 211 const initialRadius = 0.1; 212 // Randomly choose its starting position, ensuring it fits within the canvas. 213 const xPos = Math.random() * (width - 2 * targetRadius) + targetRadius; 214 const yPos = Math.random() * (height - 2 * targetRadius) + targetRadius; 215 216 // Determine if the circle will be special. 217 const isSpecial = Math.random() < 0.05; // 1 in 100 chance 218 const specialFill = "#FFD700"; // Gold color for special circles 219 const specialStroke = "#FFA500"; // Orange stroke for special circles 220 221 // Increment special circle count if the circle is special. 222 if (isSpecial) { 223 setSpecialCircleCount((prev) => prev + 1); 224 } 225 226 // Create the circle at the random position, with the tiny initial radius. 227 const circle = Matter.Bodies.circle( 228 xPos, 229 yPos, 230 initialRadius, 231 { 232 render: { 233 fillStyle: isSpecial ? specialFill : circleFill, 234 strokeStyle: isSpecial ? specialStroke : circleStroke, 235 lineWidth: circleLineWidth 236 }, 237 restitution: 0.6 238 } 239 ); 240 Matter.World.add(engine.world, circle); 241 setCircleCount((prev) => prev + 1); // Increment circle count 242 243 // Animate the growth of the circle. 244 let currentScale = 1; // Body initially at scale factor 1 245 const targetScale = targetRadius / initialRadius; // Scale factor needed to reach targetRadius 246 const startTime = performance.now(); 247 248 const grow = (time) => { 249 const elapsed = time - startTime; 250 const progress = Math.min(elapsed / growthDuration, 1); 251 const easing = bounceEaseOut(progress); 252 const desiredScale = 1 + (targetScale - 1) * easing; 253 const scaleFactor = desiredScale / currentScale; 254 Matter.Body.scale(circle, scaleFactor, scaleFactor); 255 currentScale = desiredScale; 256 if (progress < 1) { 257 requestAnimationFrame(grow); 258 } 259 }; 260 requestAnimationFrame(grow); 261 }; 262 263 // Instead of a fixed interval, schedule the next circle appearance 264 // randomly between 100 and 500 ms. 265 const scheduleNextCircle = () => { 266 const delay = Math.random() * (2500 - 500) + 100; // delay in ms between 100 and 500 267 timeoutRef.current = setTimeout(() => { 268 createCircle(); 269 scheduleNextCircle(); 270 }, delay); 271 }; 272 scheduleNextCircle(); 273 274 // Now, update gravity dynamically to simulate water-like motion. 275 // Define base values and amplitudes: 276 const baseGravityX = 0; 277 const amplitudeX = 0.008; // gravity.x will vary by ±0.008 around baseGravityX 278 const baseGravityY = 0.001; // base vertical gravity 279 const amplitudeY = 0.008; // gravity.y will vary by ±0.008 around baseGravityY 280 281 const updateGravity = () => { 282 const t = performance.now() * 0.001; // convert time to seconds 283 engine.world.gravity.x = baseGravityX + amplitudeX * Math.sin(t); 284 engine.world.gravity.y = baseGravityY + amplitudeY * Math.cos(t); 285 }; 286 287 // Update gravity approximately every 16 ms (~60fps). 288 gravityTimerRef.current = setInterval(updateGravity, 16); 289 290 // Cleanup on unmount. 291 return () => { 292 clearTimeout(timeoutRef.current); 293 clearInterval(gravityTimerRef.current); 294 Matter.Render.stop(render); 295 Matter.Runner.stop(runner); 296 Matter.World.clear(engine.world); 297 Matter.Engine.clear(engine); 298 render.canvas.remove(); 299 render.textures = {}; 300 }; 301 }, []); 302 303 // Render the fixed-size 250x250 canvas and center it with dynamic loading text. 304 return ( 305 <div 306 className={ 307 'loading-container-1' 308 } 309 style={{ 310 display: "flex", 311 flexDirection: "column", 312 alignItems: "center", 313 marginLeft: "20px", 314 marginRight: "20px", 315 justifyContent: "center", 316 minHeight: "80vh", 317 background: "none" 318 }} 319 > 320 <div 321 ref={sceneRef} 322 style={{ 323 boxSizing: "border-box", 324 }} 325 /> 326 <div className="loading-text-container"> 327 <p className={`loading-text ${fade ? "fade" : ""}`} style={{ marginTop: "20px", fontSize: "1em" }}> 328 {message}<span className="dots"></span> 329 </p> 330 <div className="circle-counters"> 331 <div className="counter-item"> 332 <div className="legend-circle regular-circle" /> 333 <p className="counter-item-regular">{circleCount}</p> 334 </div> 335 <div className="counter-item"> 336 <div className="legend-circle special-circle" /> 337 <p className="counter-item-special">{specialCircleCount}</p> 338 </div> 339 </div> 340 <div className="version-number"> 341 <p>cred.blue v1.2</p> 342 </div> 343 </div> 344 </div> 345 ); 346}; 347 348export default MatterLoadingAnimation;