diff --git a/docs/suspensive.org/public/img/homepage_background.svg b/docs/suspensive.org/public/img/homepage_background.svg index 2fa51713f..70058547f 100644 --- a/docs/suspensive.org/public/img/homepage_background.svg +++ b/docs/suspensive.org/public/img/homepage_background.svg @@ -1,5 +1,5 @@ - + @@ -348,91 +348,91 @@ - + - + - + - + - - + + - - + + - - + + - - + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + diff --git a/docs/suspensive.org/src/components/HomePage.tsx b/docs/suspensive.org/src/components/HomePage.tsx index 6d0b876f9..da315380b 100644 --- a/docs/suspensive.org/src/components/HomePage.tsx +++ b/docs/suspensive.org/src/components/HomePage.tsx @@ -2,6 +2,7 @@ import { motion } from 'motion/react' import Image from 'next/image' import Link from 'next/link' import { useRouter } from 'nextra/hooks' +import { useEffect, useRef } from 'react' const CodeBlockClassName = 'nextra-code' @@ -33,8 +34,9 @@ export const HomePage = ({ +
) } + +interface Vertex { + pos: number[] + velocity: number[] + distance: number + size: number +} + +const TILE = 80 +const OFFSET_FACTOR = 0.75 +const RANDOM_Z_FACTOR = 1 +const VELOCITY_CONSTANT = 8 +const RANDOM_DISTANCE_MAX = 900 +const RANDOM_SIZE_FACTOR = 2 + +const StarCanvas = () => { + const animationFrameIdRef = useRef(null) + const resizeAnimationFrameIdRef = useRef(0) + const onRenderRef = useRef(null) + const canvasRef = useRef(null) + + useEffect(() => { + const canvas = canvasRef.current + const parentElement = canvas?.parentElement + const ctx = canvas?.getContext('2d') + const vertexMap: Record = {} + const startTime = Date.now() + + function onResize() { + const inlineSize = parentElement?.offsetWidth ?? 0 + const blockSize = parentElement?.offsetHeight ?? 0 + + cancelAnimationFrame(resizeAnimationFrameIdRef.current) + resizeAnimationFrameIdRef.current = requestAnimationFrame(() => { + if (canvas) { + canvas.width = inlineSize + canvas.height = blockSize + canvas.style.cssText += `width: ${inlineSize}px; height: ${blockSize}px;` + onRenderRef.current?.() + } + }) + } + + function getVertex(sx: number, sy: number): Vertex { + const id = `${sx}x${sy}` + + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (!vertexMap[id]) { + const x = TILE * sx + TILE * 1.5 * Math.random() - TILE * OFFSET_FACTOR + const y = TILE * sy + TILE * 1.5 * Math.random() - TILE * OFFSET_FACTOR + const z = Math.random() * RANDOM_Z_FACTOR + const vx = 1 + Math.random() * VELOCITY_CONSTANT + const vy = 1 + Math.random() * VELOCITY_CONSTANT + const distance = 10 + Math.random() * RANDOM_DISTANCE_MAX + const size = 0.1 + Math.random() * RANDOM_SIZE_FACTOR + + vertexMap[id] = { + pos: [x, y, z], + velocity: [vx, vy], + size, + distance, + } + } + return vertexMap[id] + } + + function onRender() { + const width = canvas?.width ?? 0 + const height = canvas?.height ?? 0 + const distTime = Date.now() - startTime + + ctx?.clearRect(0, 0, width, height) + + const maxSX = Math.ceil(width / TILE) + const maxSY = Math.ceil(height / TILE) + + for (let sx = 0; sx <= maxSX; ++sx) { + for (let sy = 0; sy <= maxSY; ++sy) { + const { velocity, distance, pos, size } = getVertex(sx, sy) + const scalar = Math.sqrt( + velocity[0] * velocity[0] + velocity[1] * velocity[1] + ) + const totalDistance = (distTime * scalar) / 1000 + const isReverse = Math.floor(totalDistance / distance) % 2 !== 0 + let nextDistance = totalDistance % distance + + if (isReverse) { + nextDistance = distance - nextDistance + } + const x = pos[0] + (nextDistance / scalar) * velocity[0] + const y = pos[1] + (nextDistance / scalar) * velocity[1] + const a = 1 - pos[2] + + ctx?.beginPath() + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + ctx!.fillStyle = `rgba(255, 255, 255, ${a})` + ctx?.arc(x, y, size, 0, 2 * Math.PI) + ctx?.fill() + } + } + } + + onRenderRef.current = onRender + const observer = new ResizeObserver(onResize) + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + parentElement && observer.observe(parentElement) + + return () => { + cancelAnimationFrame(resizeAnimationFrameIdRef.current) + observer.disconnect() + } + }, []) + + useEffect(() => { + const requestAnimation = () => { + onRenderRef.current?.() + animationFrameIdRef.current = requestAnimationFrame(requestAnimation) + } + + if (animationFrameIdRef.current === null) { + animationFrameIdRef.current = requestAnimationFrame(requestAnimation) + } + + return () => { + if (animationFrameIdRef.current !== null) { + cancelAnimationFrame(animationFrameIdRef.current) + animationFrameIdRef.current = null + } + } + }, []) + + return ( + + ) +} diff --git a/docs/suspensive.org/src/components/SectionText.tsx b/docs/suspensive.org/src/components/SectionText.tsx index 0827530f0..bacd7adcf 100644 --- a/docs/suspensive.org/src/components/SectionText.tsx +++ b/docs/suspensive.org/src/components/SectionText.tsx @@ -6,5 +6,5 @@ export const SectionTitle = ({ children }: { children: ReactNode }) => ( ) export const SectionDescription = ({ children }: { children: ReactNode }) => ( -

{children}

+

{children}

) diff --git a/docs/suspensive.org/src/pages/en/index.mdx b/docs/suspensive.org/src/pages/en/index.mdx index 5d45df6fe..a043186f1 100644 --- a/docs/suspensive.org/src/pages/en/index.mdx +++ b/docs/suspensive.org/src/pages/en/index.mdx @@ -303,7 +303,7 @@ const Page = () => ( -ErrorBoundaryGroup, ErrorBoundary +ErrorBoundary, ErrorBoundaryGroup
diff --git a/docs/suspensive.org/src/pages/ko/index.mdx b/docs/suspensive.org/src/pages/ko/index.mdx index 74a862d7f..528dfbf6e 100644 --- a/docs/suspensive.org/src/pages/ko/index.mdx +++ b/docs/suspensive.org/src/pages/ko/index.mdx @@ -304,7 +304,7 @@ const Page = () => ( -ErrorBoundaryGroup, ErrorBoundary +ErrorBoundary, ErrorBoundaryGroup