[READ-ONLY] One Calendar is a privacy-first calendar web app built with Next.js. It has modern security features, including e2ee, password-protected sharing, and self-destructing share links 📅 calendar.xyehr.cn
nextjs
0

Configure Feed

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

One-Calendar / components / landing / animated-sphere.tsx
2.8 kB 103 lines
1'use client' 2 3import { useEffect, useRef } from 'react' 4 5export function AnimatedSphere() { 6 const canvasRef = useRef<HTMLCanvasElement>(null) 7 const frameRef = useRef(0) 8 9 useEffect(() => { 10 const canvas = canvasRef.current 11 if (!canvas) return 12 13 const ctx = canvas.getContext('2d') 14 if (!ctx) return 15 16 const chars = '░▒▓█▀▄▌▐│─┤├┴┬╭╮╰╯' 17 let time = 0 18 19 const resize = () => { 20 const dpr = window.devicePixelRatio || 1 21 const rect = canvas.getBoundingClientRect() 22 canvas.width = rect.width * dpr 23 canvas.height = rect.height * dpr 24 ctx.scale(dpr, dpr) 25 } 26 27 resize() 28 window.addEventListener('resize', resize) 29 30 const render = () => { 31 const rect = canvas.getBoundingClientRect() 32 ctx.clearRect(0, 0, rect.width, rect.height) 33 34 const centerX = rect.width / 2 35 const centerY = rect.height / 2 36 const radius = Math.min(rect.width, rect.height) * 0.525 37 38 ctx.font = '12px monospace' 39 ctx.textAlign = 'center' 40 ctx.textBaseline = 'middle' 41 42 const step = 12 43 const points: { x: number; y: number; z: number; char: string }[] = [] 44 45 // Generate sphere points 46 for (let phi = 0; phi < Math.PI * 2; phi += 0.15) { 47 for (let theta = 0; theta < Math.PI; theta += 0.15) { 48 const x = Math.sin(theta) * Math.cos(phi + time * 0.5) 49 const y = Math.sin(theta) * Math.sin(phi + time * 0.5) 50 const z = Math.cos(theta) 51 52 // Rotate around Y axis 53 const rotY = time * 0.3 54 const newX = x * Math.cos(rotY) - z * Math.sin(rotY) 55 const newZ = x * Math.sin(rotY) + z * Math.cos(rotY) 56 57 // Rotate around X axis 58 const rotX = time * 0.2 59 const newY = y * Math.cos(rotX) - newZ * Math.sin(rotX) 60 const finalZ = y * Math.sin(rotX) + newZ * Math.cos(rotX) 61 62 const depth = (finalZ + 1) / 2 63 const charIndex = Math.floor(depth * (chars.length - 1)) 64 65 points.push({ 66 x: centerX + newX * radius, 67 y: centerY + newY * radius, 68 z: finalZ, 69 char: chars[charIndex], 70 }) 71 } 72 } 73 74 // Sort by z for depth 75 points.sort((a, b) => a.z - b.z) 76 77 // Draw points 78 points.forEach((point) => { 79 const alpha = 0.2 + (point.z + 1) * 0.4 80 ctx.fillStyle = `rgba(0, 0, 0, ${alpha})` 81 ctx.fillText(point.char, point.x, point.y) 82 }) 83 84 time += 0.02 85 frameRef.current = requestAnimationFrame(render) 86 } 87 88 render() 89 90 return () => { 91 window.removeEventListener('resize', resize) 92 cancelAnimationFrame(frameRef.current) 93 } 94 }, []) 95 96 return ( 97 <canvas 98 ref={canvasRef} 99 className="w-full h-full" 100 style={{ display: 'block' }} 101 /> 102 ) 103}