| <!doctype html> |
| <html lang="en"> |
| <head> |
| <meta charset="utf-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1"> |
| <title>React 10 High Level Animations</title> |
| <style> |
| :root { |
| --bg: #070912; |
| --panel: rgba(15, 19, 34, 0.82); |
| --panel-solid: #111624; |
| --ink: #f8fafc; |
| --muted: #a6b0c3; |
| --line: rgba(255, 255, 255, 0.14); |
| --accent: #7dd3fc; |
| --accent-2: #fb7185; |
| --gold: #facc15; |
| } |
| |
| * { |
| box-sizing: border-box; |
| } |
| |
| html, |
| body { |
| margin: 0; |
| min-height: 100%; |
| background: var(--bg); |
| color: var(--ink); |
| font-family: Inter, Arial, Helvetica, sans-serif; |
| overflow-x: hidden; |
| } |
| |
| button, |
| input { |
| font: inherit; |
| } |
| |
| #root { |
| min-height: 100vh; |
| } |
| |
| .shell { |
| min-height: 100vh; |
| display: grid; |
| grid-template-columns: 318px 1fr; |
| background: |
| radial-gradient(circle at 20% 10%, rgba(125, 211, 252, 0.16), transparent 34%), |
| radial-gradient(circle at 80% 80%, rgba(251, 113, 133, 0.14), transparent 32%), |
| #070912; |
| } |
| |
| .sidebar { |
| position: sticky; |
| top: 0; |
| height: 100vh; |
| border-right: 1px solid var(--line); |
| background: rgba(8, 11, 20, 0.94); |
| backdrop-filter: blur(14px); |
| overflow: auto; |
| z-index: 4; |
| } |
| |
| .brand { |
| padding: 22px 18px 16px; |
| border-bottom: 1px solid var(--line); |
| } |
| |
| h1 { |
| margin: 0 0 8px; |
| font-size: 30px; |
| line-height: 1; |
| } |
| |
| p { |
| margin: 0; |
| color: var(--muted); |
| line-height: 1.5; |
| font-size: 14px; |
| } |
| |
| .nav { |
| padding: 12px; |
| } |
| |
| .nav button { |
| display: grid; |
| grid-template-columns: 36px 1fr; |
| gap: 10px; |
| width: 100%; |
| min-height: 68px; |
| margin-bottom: 8px; |
| border: 1px solid var(--line); |
| border-radius: 8px; |
| padding: 10px; |
| color: var(--ink); |
| background: rgba(255, 255, 255, 0.04); |
| cursor: pointer; |
| text-align: left; |
| } |
| |
| .nav button:hover, |
| .nav button.active { |
| border-color: var(--accent); |
| background: rgba(125, 211, 252, 0.12); |
| } |
| |
| .badge { |
| display: grid; |
| place-items: center; |
| width: 36px; |
| height: 36px; |
| border-radius: 8px; |
| background: linear-gradient(135deg, var(--accent), var(--accent-2)); |
| color: #06111c; |
| font-weight: 900; |
| } |
| |
| .nav strong, |
| .nav span { |
| display: block; |
| } |
| |
| .nav strong { |
| margin-bottom: 5px; |
| font-size: 14px; |
| line-height: 1.2; |
| } |
| |
| .nav span { |
| color: var(--muted); |
| font-size: 12px; |
| line-height: 1.35; |
| } |
| |
| .stage-wrap { |
| min-width: 0; |
| padding: 22px; |
| } |
| |
| .topbar { |
| display: grid; |
| grid-template-columns: 1fr auto; |
| gap: 16px; |
| align-items: end; |
| margin-bottom: 16px; |
| } |
| |
| h2 { |
| margin: 0 0 7px; |
| font-size: clamp(30px, 5vw, 54px); |
| line-height: 0.95; |
| } |
| |
| .controls { |
| display: flex; |
| gap: 10px; |
| align-items: center; |
| border: 1px solid var(--line); |
| border-radius: 8px; |
| padding: 10px 12px; |
| background: var(--panel); |
| } |
| |
| .controls label { |
| color: var(--muted); |
| font-size: 12px; |
| text-transform: uppercase; |
| } |
| |
| input[type="range"] { |
| accent-color: var(--accent); |
| } |
| |
| .stage { |
| position: relative; |
| min-height: 620px; |
| border: 1px solid var(--line); |
| border-radius: 8px; |
| background: |
| linear-gradient(90deg, rgba(255, 255, 255, 0.05) 1px, transparent 1px), |
| linear-gradient(rgba(255, 255, 255, 0.05) 1px, transparent 1px), |
| rgba(10, 14, 25, 0.82); |
| background-size: 36px 36px; |
| box-shadow: 0 24px 80px rgba(0, 0, 0, 0.34); |
| overflow: hidden; |
| } |
| |
| .demo { |
| position: absolute; |
| inset: 0; |
| display: grid; |
| place-items: center; |
| padding: 24px; |
| } |
| |
| .caption { |
| position: absolute; |
| left: 16px; |
| bottom: 16px; |
| max-width: min(620px, calc(100% - 32px)); |
| border: 1px solid var(--line); |
| border-radius: 8px; |
| padding: 12px 14px; |
| background: rgba(7, 9, 18, 0.76); |
| color: var(--muted); |
| backdrop-filter: blur(10px); |
| } |
| |
| .mobile-select { |
| display: none; |
| width: 100%; |
| height: 44px; |
| margin-bottom: 12px; |
| border: 1px solid var(--line); |
| border-radius: 8px; |
| padding: 0 12px; |
| color: var(--ink); |
| background: var(--panel-solid); |
| } |
| |
| .particle-canvas { |
| width: min(760px, 92vw); |
| height: min(470px, 62vh); |
| border: 1px solid var(--line); |
| border-radius: 8px; |
| background: radial-gradient(circle at center, rgba(56, 189, 248, 0.13), rgba(3, 7, 18, 0.82)); |
| } |
| |
| .blob-scene { |
| position: relative; |
| width: min(760px, 86vw); |
| height: 440px; |
| filter: contrast(1.16) saturate(1.2); |
| } |
| |
| .liquid-blob { |
| position: absolute; |
| border-radius: 48% 52% 42% 58% / 58% 41% 59% 42%; |
| background: radial-gradient(circle at 28% 24%, #fff, var(--color), transparent 72%); |
| mix-blend-mode: screen; |
| filter: blur(2px); |
| animation: liquid var(--speed) ease-in-out infinite alternate; |
| } |
| |
| .liquid-blob:nth-child(1) { width: 240px; height: 240px; left: 12%; top: 18%; --color: #22d3ee; --speed: 5s; } |
| .liquid-blob:nth-child(2) { width: 300px; height: 300px; left: 36%; top: 8%; --color: #fb7185; --speed: 6.2s; animation-delay: -1.2s; } |
| .liquid-blob:nth-child(3) { width: 210px; height: 210px; left: 56%; top: 40%; --color: #a78bfa; --speed: 4.4s; animation-delay: -2s; } |
| |
| @keyframes liquid { |
| to { |
| transform: translate(42px, -24px) rotate(55deg) scale(1.16); |
| border-radius: 57% 43% 63% 37% / 34% 57% 43% 66%; |
| } |
| } |
| |
| .carousel { |
| position: relative; |
| width: 420px; |
| height: 420px; |
| transform-style: preserve-3d; |
| animation: carousel-spin calc(14s / var(--speed-factor)) linear infinite; |
| } |
| |
| .card3d { |
| position: absolute; |
| left: 120px; |
| top: 108px; |
| width: 180px; |
| height: 220px; |
| border: 1px solid var(--line); |
| border-radius: 8px; |
| background: linear-gradient(145deg, rgba(125, 211, 252, 0.88), rgba(251, 113, 133, 0.82)); |
| box-shadow: 0 20px 50px rgba(0, 0, 0, 0.38); |
| transform: rotateY(calc(var(--i) * 45deg)) translateZ(280px); |
| display: grid; |
| place-items: center; |
| font-size: 44px; |
| font-weight: 900; |
| color: #07111c; |
| } |
| |
| @keyframes carousel-spin { |
| to { transform: rotateY(360deg) rotateX(8deg); } |
| } |
| |
| .tunnel { |
| position: relative; |
| width: 520px; |
| height: 520px; |
| transform-style: preserve-3d; |
| perspective: 700px; |
| } |
| |
| .tunnel-ring { |
| position: absolute; |
| inset: 50%; |
| width: 280px; |
| height: 280px; |
| margin: -140px; |
| border: 2px solid hsl(calc(var(--i) * 28), 90%, 66%); |
| border-radius: 18px; |
| transform: translateZ(calc(var(--i) * -70px)) rotateZ(calc(var(--i) * 13deg)); |
| animation: tunnel calc(2.4s / var(--speed-factor)) linear infinite; |
| animation-delay: calc(var(--i) * -0.13s); |
| } |
| |
| @keyframes tunnel { |
| from { opacity: 0; transform: translateZ(-900px) rotateZ(0deg) scale(0.2); } |
| 35% { opacity: 1; } |
| to { opacity: 0; transform: translateZ(220px) rotateZ(120deg) scale(1.8); } |
| } |
| |
| .hologram { |
| position: relative; |
| width: 430px; |
| height: 430px; |
| display: grid; |
| place-items: center; |
| } |
| |
| .holo-core { |
| width: 190px; |
| height: 190px; |
| border-radius: 50%; |
| background: |
| radial-gradient(circle at 35% 25%, #fff, rgba(125, 211, 252, 0.85) 25%, rgba(59, 130, 246, 0.18) 58%, transparent 70%); |
| box-shadow: 0 0 60px rgba(125, 211, 252, 0.55); |
| animation: pulse calc(2.4s / var(--speed-factor)) ease-in-out infinite; |
| } |
| |
| .holo-ring { |
| position: absolute; |
| width: calc(150px + var(--i) * 62px); |
| height: calc(150px + var(--i) * 62px); |
| border: 2px dashed rgba(125, 211, 252, 0.72); |
| border-radius: 50%; |
| transform: rotateX(64deg) rotateZ(calc(var(--i) * 24deg)); |
| animation: orbit calc((4s + var(--i) * .7s) / var(--speed-factor)) linear infinite; |
| } |
| |
| @keyframes pulse { |
| 50% { transform: scale(1.08); filter: brightness(1.35); } |
| } |
| |
| @keyframes orbit { |
| to { transform: rotateX(64deg) rotateZ(360deg); } |
| } |
| |
| .type-stack { |
| display: grid; |
| gap: 8px; |
| text-align: center; |
| font-size: clamp(42px, 9vw, 112px); |
| font-weight: 900; |
| line-height: 0.86; |
| text-transform: uppercase; |
| } |
| |
| .type-stack span { |
| display: block; |
| background: linear-gradient(90deg, #7dd3fc, #fb7185, #facc15, #7dd3fc); |
| background-size: 280% 100%; |
| color: transparent; |
| -webkit-background-clip: text; |
| background-clip: text; |
| animation: text-wave calc(2.2s / var(--speed-factor)) ease-in-out infinite; |
| animation-delay: calc(var(--i) * .18s); |
| } |
| |
| @keyframes text-wave { |
| 0%, 100% { transform: translateX(-20px) skewX(-8deg); background-position: 0% 50%; } |
| 50% { transform: translateX(20px) skewX(8deg); background-position: 100% 50%; } |
| } |
| |
| .magnet-field { |
| position: relative; |
| width: min(760px, 90vw); |
| height: 430px; |
| } |
| |
| .magnet-dot { |
| position: absolute; |
| left: calc(var(--x) * 1%); |
| top: calc(var(--y) * 1%); |
| width: 38px; |
| height: 38px; |
| border-radius: 50%; |
| background: radial-gradient(circle at 30% 25%, #fff, var(--accent)); |
| transform: translate(calc(var(--mx) * 1px), calc(var(--my) * 1px)) scale(var(--scale)); |
| box-shadow: 0 0 26px rgba(125, 211, 252, 0.45); |
| transition: transform 120ms linear; |
| } |
| |
| .aurora { |
| position: relative; |
| width: min(820px, 92vw); |
| height: 440px; |
| overflow: hidden; |
| border-radius: 8px; |
| background: #020617; |
| } |
| |
| .aurora-band { |
| position: absolute; |
| left: -12%; |
| width: 125%; |
| height: 170px; |
| border-radius: 50%; |
| filter: blur(22px); |
| opacity: .78; |
| mix-blend-mode: screen; |
| animation: aurora calc(var(--duration) / var(--speed-factor)) ease-in-out infinite alternate; |
| } |
| |
| .aurora-band:nth-child(1) { top: 60px; background: #22d3ee; --duration: 4.5s; } |
| .aurora-band:nth-child(2) { top: 150px; background: #a78bfa; --duration: 6s; animation-delay: -1s; } |
| .aurora-band:nth-child(3) { top: 230px; background: #fb7185; --duration: 5.2s; animation-delay: -2s; } |
| |
| @keyframes aurora { |
| to { transform: translateY(-48px) skewY(-8deg) scaleX(1.08); } |
| } |
| |
| .scanner { |
| position: relative; |
| width: min(760px, 90vw); |
| height: 430px; |
| border: 1px solid rgba(34, 211, 238, 0.4); |
| background: |
| linear-gradient(90deg, rgba(34, 211, 238, 0.13) 1px, transparent 1px), |
| linear-gradient(rgba(34, 211, 238, 0.13) 1px, transparent 1px), |
| radial-gradient(circle at center, rgba(34, 211, 238, 0.12), #020617 70%); |
| background-size: 32px 32px, 32px 32px, auto; |
| overflow: hidden; |
| } |
| |
| .scan-line { |
| position: absolute; |
| inset: 0 auto 0 0; |
| width: 90px; |
| background: linear-gradient(90deg, transparent, rgba(34, 211, 238, 0.7), transparent); |
| animation: scan calc(2.3s / var(--speed-factor)) linear infinite; |
| } |
| |
| .scan-target { |
| position: absolute; |
| left: calc(var(--x) * 1%); |
| top: calc(var(--y) * 1%); |
| width: 46px; |
| height: 46px; |
| border: 2px solid #22d3ee; |
| border-radius: 8px; |
| transform: rotate(45deg); |
| animation: blink calc((1.1s + var(--i) * .2s) / var(--speed-factor)) infinite alternate; |
| } |
| |
| @keyframes scan { |
| to { transform: translateX(820px); } |
| } |
| |
| @keyframes blink { |
| to { opacity: .25; transform: rotate(45deg) scale(.72); } |
| } |
| |
| .timeline { |
| display: flex; |
| align-items: center; |
| gap: clamp(8px, 2vw, 22px); |
| width: min(860px, 92vw); |
| justify-content: center; |
| } |
| |
| .time-node { |
| position: relative; |
| width: clamp(54px, 9vw, 86px); |
| height: clamp(54px, 9vw, 86px); |
| border: 1px solid var(--line); |
| border-radius: 50%; |
| background: rgba(255, 255, 255, 0.06); |
| display: grid; |
| place-items: center; |
| color: var(--muted); |
| font-weight: 900; |
| transition: 360ms ease; |
| } |
| |
| .time-node.active { |
| color: #07111c; |
| background: var(--gold); |
| box-shadow: 0 0 42px rgba(250, 204, 21, 0.5); |
| transform: translateY(-22px) scale(1.13); |
| } |
| |
| .time-node:after { |
| content: ""; |
| position: absolute; |
| left: 100%; |
| width: clamp(8px, 2vw, 22px); |
| height: 2px; |
| background: var(--line); |
| } |
| |
| .time-node:last-child:after { |
| display: none; |
| } |
| |
| .energy-mesh { |
| position: relative; |
| width: min(720px, 90vw); |
| height: 430px; |
| display: grid; |
| place-items: center; |
| } |
| |
| .mesh-cell { |
| position: absolute; |
| width: 110px; |
| height: 110px; |
| border: 1px solid rgba(125, 211, 252, 0.45); |
| border-radius: 28px; |
| transform: rotate(calc(var(--i) * 18deg)) translateY(calc(var(--i) * 9px)); |
| animation: mesh calc((2.4s + var(--i) * .15s) / var(--speed-factor)) ease-in-out infinite alternate; |
| box-shadow: inset 0 0 30px rgba(125, 211, 252, 0.12), 0 0 28px rgba(251, 113, 133, 0.12); |
| } |
| |
| @keyframes mesh { |
| to { |
| width: 360px; |
| height: 360px; |
| border-radius: 50%; |
| transform: rotate(calc(var(--i) * 38deg)) translateY(calc(var(--i) * -4px)); |
| } |
| } |
| |
| @media (max-width: 860px) { |
| .shell { |
| display: block; |
| } |
| |
| .sidebar { |
| display: none; |
| } |
| |
| .stage-wrap { |
| padding: 14px; |
| } |
| |
| .topbar { |
| grid-template-columns: 1fr; |
| } |
| |
| .mobile-select { |
| display: block; |
| } |
| |
| .stage { |
| min-height: 570px; |
| } |
| |
| .controls { |
| width: fit-content; |
| } |
| } |
| </style> |
| </head> |
| <body> |
| <div id="root"></div> |
|
|
| <script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script> |
| <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script> |
| <script> |
| const h = React.createElement; |
| const { useEffect, useMemo, useRef, useState } = React; |
| |
| const demos = [ |
| { name: "Quantum Particle Field", label: "Canvas particle network with depth drift.", key: "particles" }, |
| { name: "Liquid Morph System", label: "Metaball-style blobs with fluid morphing.", key: "liquid" }, |
| { name: "3D Card Orbit", label: "Perspective carousel with layered glass cards.", key: "cards" }, |
| { name: "Neon Warp Tunnel", label: "Fast depth tunnel made from animated geometry.", key: "tunnel" }, |
| { name: "Hologram Core", label: "Orbital rings around a pulsing digital planet.", key: "holo" }, |
| { name: "Kinetic Typography", label: "Large animated headline with gradient motion.", key: "type" }, |
| { name: "Magnetic Particle Board", label: "Pointer-reactive dots pulled by cursor gravity.", key: "magnet" }, |
| { name: "Aurora Wave Layers", label: "Soft color fields moving at layered speeds.", key: "aurora" }, |
| { name: "Cyber Scanner Grid", label: "Scanning beam with tracked targets.", key: "scanner" }, |
| { name: "Reactive Timeline Orbs", label: "Autoplay sequence with animated state changes.", key: "timeline" } |
| ]; |
| |
| function ParticleField({ speed }) { |
| const ref = useRef(null); |
| |
| useEffect(() => { |
| const canvas = ref.current; |
| const context = canvas.getContext("2d"); |
| let raf = 0; |
| let width = 0; |
| let height = 0; |
| const particles = Array.from({ length: 95 }, () => ({ |
| x: Math.random(), |
| y: Math.random(), |
| vx: (Math.random() - 0.5) * 0.0014, |
| vy: (Math.random() - 0.5) * 0.0014, |
| r: 1.4 + Math.random() * 2.7 |
| })); |
| |
| function resize() { |
| const rect = canvas.getBoundingClientRect(); |
| width = Math.max(1, Math.floor(rect.width * devicePixelRatio)); |
| height = Math.max(1, Math.floor(rect.height * devicePixelRatio)); |
| canvas.width = width; |
| canvas.height = height; |
| } |
| |
| function draw() { |
| context.clearRect(0, 0, width, height); |
| context.fillStyle = "rgba(3, 7, 18, 0.24)"; |
| context.fillRect(0, 0, width, height); |
| |
| for (const particle of particles) { |
| particle.x += particle.vx * speed; |
| particle.y += particle.vy * speed; |
| if (particle.x < 0 || particle.x > 1) particle.vx *= -1; |
| if (particle.y < 0 || particle.y > 1) particle.vy *= -1; |
| } |
| |
| for (let i = 0; i < particles.length; i += 1) { |
| for (let j = i + 1; j < particles.length; j += 1) { |
| const a = particles[i]; |
| const b = particles[j]; |
| const dx = (a.x - b.x) * width; |
| const dy = (a.y - b.y) * height; |
| const dist = Math.sqrt(dx * dx + dy * dy); |
| if (dist < 116) { |
| context.strokeStyle = `rgba(125, 211, 252, ${1 - dist / 116})`; |
| context.lineWidth = devicePixelRatio; |
| context.beginPath(); |
| context.moveTo(a.x * width, a.y * height); |
| context.lineTo(b.x * width, b.y * height); |
| context.stroke(); |
| } |
| } |
| } |
| |
| for (const particle of particles) { |
| const gradient = context.createRadialGradient( |
| particle.x * width, particle.y * height, 0, |
| particle.x * width, particle.y * height, particle.r * 6 * devicePixelRatio |
| ); |
| gradient.addColorStop(0, "rgba(255,255,255,.95)"); |
| gradient.addColorStop(0.36, "rgba(125,211,252,.8)"); |
| gradient.addColorStop(1, "rgba(125,211,252,0)"); |
| context.fillStyle = gradient; |
| context.beginPath(); |
| context.arc(particle.x * width, particle.y * height, particle.r * devicePixelRatio, 0, Math.PI * 2); |
| context.fill(); |
| } |
| |
| raf = requestAnimationFrame(draw); |
| } |
| |
| resize(); |
| draw(); |
| window.addEventListener("resize", resize); |
| return () => { |
| cancelAnimationFrame(raf); |
| window.removeEventListener("resize", resize); |
| }; |
| }, [speed]); |
| |
| return h("canvas", { className: "particle-canvas", ref }); |
| } |
| |
| function LiquidMorph() { |
| return h("div", { className: "blob-scene" }, |
| h("div", { className: "liquid-blob" }), |
| h("div", { className: "liquid-blob" }), |
| h("div", { className: "liquid-blob" }) |
| ); |
| } |
| |
| function CardOrbit() { |
| return h("div", { className: "carousel" }, |
| Array.from({ length: 8 }, (_, index) => |
| h("div", { className: "card3d", style: { "--i": index }, key: index }, String(index + 1).padStart(2, "0")) |
| ) |
| ); |
| } |
| |
| function NeonTunnel() { |
| return h("div", { className: "tunnel" }, |
| Array.from({ length: 15 }, (_, index) => |
| h("div", { className: "tunnel-ring", style: { "--i": index }, key: index }) |
| ) |
| ); |
| } |
| |
| function HologramCore() { |
| return h("div", { className: "hologram" }, |
| h("div", { className: "holo-core" }), |
| Array.from({ length: 5 }, (_, index) => |
| h("div", { className: "holo-ring", style: { "--i": index + 1 }, key: index }) |
| ) |
| ); |
| } |
| |
| function KineticType() { |
| return h("div", { className: "type-stack" }, |
| ["REACT", "MOTION", "SYSTEM"].map((word, index) => |
| h("span", { style: { "--i": index }, key: word }, word) |
| ) |
| ); |
| } |
| |
| function MagneticBoard() { |
| const [pos, setPos] = useState({ x: 50, y: 50 }); |
| const dots = useMemo(() => Array.from({ length: 42 }, () => ({ |
| x: 8 + Math.random() * 84, |
| y: 8 + Math.random() * 84 |
| })), []); |
| |
| return h("div", { |
| className: "magnet-field", |
| onPointerMove: (event) => { |
| const rect = event.currentTarget.getBoundingClientRect(); |
| setPos({ x: ((event.clientX - rect.left) / rect.width) * 100, y: ((event.clientY - rect.top) / rect.height) * 100 }); |
| } |
| }, |
| dots.map((dot, index) => { |
| const dx = pos.x - dot.x; |
| const dy = pos.y - dot.y; |
| const dist = Math.max(12, Math.sqrt(dx * dx + dy * dy)); |
| const pull = Math.max(0, 1 - dist / 45); |
| return h("span", { |
| className: "magnet-dot", |
| key: index, |
| style: { |
| "--x": dot.x, |
| "--y": dot.y, |
| "--mx": dx * pull * 0.46, |
| "--my": dy * pull * 0.46, |
| "--scale": 0.76 + pull * 1.05 |
| } |
| }); |
| }) |
| ); |
| } |
| |
| function AuroraWaves() { |
| return h("div", { className: "aurora" }, |
| h("div", { className: "aurora-band" }), |
| h("div", { className: "aurora-band" }), |
| h("div", { className: "aurora-band" }) |
| ); |
| } |
| |
| function CyberScanner() { |
| const targets = [ |
| [18, 24], [34, 58], [54, 34], [73, 68], [82, 22], [14, 75] |
| ]; |
| return h("div", { className: "scanner" }, |
| h("div", { className: "scan-line" }), |
| targets.map(([x, y], index) => |
| h("span", { className: "scan-target", style: { "--x": x, "--y": y, "--i": index }, key: index }) |
| ) |
| ); |
| } |
| |
| function TimelineOrbs({ speed }) { |
| const [active, setActive] = useState(0); |
| useEffect(() => { |
| const interval = setInterval(() => setActive((value) => (value + 1) % 8), 900 / speed); |
| return () => clearInterval(interval); |
| }, [speed]); |
| |
| return h("div", { className: "timeline" }, |
| Array.from({ length: 8 }, (_, index) => |
| h("div", { className: `time-node${active === index ? " active" : ""}`, key: index }, index + 1) |
| ) |
| ); |
| } |
| |
| function EnergyMesh() { |
| return h("div", { className: "energy-mesh" }, |
| Array.from({ length: 14 }, (_, index) => |
| h("span", { className: "mesh-cell", style: { "--i": index }, key: index }) |
| ) |
| ); |
| } |
| |
| const demoMap = { |
| particles: ParticleField, |
| liquid: LiquidMorph, |
| cards: CardOrbit, |
| tunnel: NeonTunnel, |
| holo: HologramCore, |
| type: KineticType, |
| magnet: MagneticBoard, |
| aurora: AuroraWaves, |
| scanner: CyberScanner, |
| timeline: TimelineOrbs, |
| mesh: EnergyMesh |
| }; |
| |
| function App() { |
| const [active, setActive] = useState(0); |
| const [speed, setSpeed] = useState(1.15); |
| const current = demos[active]; |
| const Demo = demoMap[current.key]; |
| |
| return h("div", { className: "shell", style: { "--speed-factor": speed } }, |
| h("aside", { className: "sidebar" }, |
| h("div", { className: "brand" }, |
| h("h1", null, "React 10 High Level Animations"), |
| h("p", null, "Interactive motion demos built with React state, CSS animation, canvas, and pointer input.") |
| ), |
| h("nav", { className: "nav" }, |
| demos.map((demo, index) => |
| h("button", { |
| className: active === index ? "active" : "", |
| type: "button", |
| onClick: () => setActive(index), |
| key: demo.name |
| }, |
| h("span", { className: "badge" }, String(index + 1).padStart(2, "0")), |
| h("span", null, h("strong", null, demo.name), h("span", null, demo.label)) |
| ) |
| ) |
| ) |
| ), |
| h("main", { className: "stage-wrap" }, |
| h("select", { |
| className: "mobile-select", |
| value: active, |
| onChange: (event) => setActive(Number(event.target.value)) |
| }, |
| demos.map((demo, index) => h("option", { value: index, key: demo.name }, `${index + 1}. ${demo.name}`)) |
| ), |
| h("section", { className: "topbar" }, |
| h("div", null, h("h2", null, current.name), h("p", null, current.label)), |
| h("div", { className: "controls" }, |
| h("label", { htmlFor: "speed" }, "Speed"), |
| h("input", { |
| id: "speed", |
| type: "range", |
| min: "0.5", |
| max: "2", |
| step: "0.05", |
| value: speed, |
| onChange: (event) => setSpeed(Number(event.target.value)) |
| }) |
| ) |
| ), |
| h("section", { className: "stage" }, |
| h("div", { className: "demo" }, h(Demo, { speed })), |
| h("div", { className: "caption" }, "React controls the selected demo, animation speed, pointer state, and canvas lifecycle. CSS and canvas handle the high-frequency motion.") |
| ) |
| ) |
| ); |
| } |
| |
| ReactDOM.createRoot(document.getElementById("root")).render(h(App)); |
| </script> |
| </body> |
| </html> |
|
|