"use client"; import { useEffect, useRef } from "react"; import { useTheme } from "next-themes"; import Matter from "matter-js"; export function World() { const containerRef = useRef(null); const { resolvedTheme } = useTheme(); const getRandomXY = (width: number, height: number, radius: number) => { const randomX = Math.floor(Math.random() * (width - 2 * radius)) + radius; const randomY = Math.floor(Math.random() * (height - 2 * radius)) + radius; return [randomX, randomY]; }; const getBgColor = () => { if (typeof window !== "undefined") { return getComputedStyle(document.documentElement) .getPropertyValue("--color-background") .trim(); } }; useEffect(() => { const container = containerRef.current; if (!container) { return; } const width = container.offsetWidth; const height = container.offsetHeight; const bgColor = getBgColor(); const { Engine, Events, Render, Bodies, Composite, Runner, Mouse, MouseConstraint, } = Matter; const engine = Engine.create(); engine.gravity.y = 0.3; engine.enableSleeping = false; const render = Render.create({ element: container, engine, options: { width, height, wireframes: false, background: bgColor, }, }); // Bottom wall (ground) const ground = Bodies.rectangle(width / 2, height - 10, width, 20, { isStatic: true, render: { fillStyle: bgColor }, }); // Left wall const leftWall = Bodies.rectangle(-10, height / 2, 20, height + 40, { isStatic: true, render: { fillStyle: bgColor }, }); // Right wall const rightWall = Bodies.rectangle( width + 10, height / 2, 20, height + 40, { isStatic: true, render: { fillStyle: bgColor }, }, ); // Top wall const topWall = Bodies.rectangle(width / 2, -10, width, 20, { isStatic: true, render: { fillStyle: bgColor }, }); // Random ball position const radius = 15; let [randomX, randomY] = getRandomXY(width, height, radius); const ballRed = Bodies.circle(randomX, randomY, radius, { restitution: 0.8, render: { fillStyle: "red" }, }); [randomX, randomY] = getRandomXY(width, height, radius); const ballBlue = Bodies.circle(randomX, randomY, radius, { restitution: 0.8, render: { fillStyle: "blue" }, }); [randomX, randomY] = getRandomXY(width, height, radius); const ballGreen = Bodies.circle(randomX, randomY, radius, { restitution: 0.8, render: { fillStyle: "green" }, }); // Mouse interaction const mouse = Mouse.create(render.canvas); const mouseConstraint = MouseConstraint.create(engine, { mouse, constraint: { stiffness: 1, angularStiffness: 0, render: { visible: false }, }, }); let pills: Matter.Body[] = []; const pillsToRender = [ "ENGINEERING MANAGER", "NIX", "PRINCIPAL ENGINEER", "AI", ]; pillsToRender.forEach(function (pillText) { const fontSize = 32; const ctx = render.canvas.getContext("2d"); ctx.font = `${fontSize}px Arial`; const textWidth = ctx.measureText(pillText).width; const pillHeight = fontSize * 2; const pillWidth = textWidth + pillHeight; [randomX, randomY] = getRandomXY(width, height, radius); const pill = Matter.Bodies.rectangle( randomX, randomY, pillWidth, pillHeight, { chamfer: { radius: pillHeight / 2 }, isStatic: false, density: 0.001, friction: 0.05, frictionAir: 0.01, render: { fillStyle: "rgba(255, 255, 255, 0)", // transparent fill strokeStyle: "#000000", // black border lineWidth: 1, }, }, ); pills.push(pill); const text = document.createElement("div"); Object.assign(text.style, { position: "absolute", width: `${pillWidth}px`, height: `${pillHeight}px`, display: "flex", justifyContent: "center", alignItems: "center", fontFamily: "Space Mono", fontSize: `${fontSize}px`, color: "black", pointerEvents: "none", }); text.innerText = pillText; render.canvas.getContext("2d").measure; document.body.appendChild(text); Events.on(engine, "afterUpdate", () => { text.style.left = `${pill.position.x - pillWidth / 2 + 60}px`; text.style.top = `${pill.position.y + pillHeight / 2 - 4}px`; text.style.transform = `rotate(${pill.angle}rad)`; }); }); // Add all elements to the world Composite.add(engine.world, [ ground, leftWall, rightWall, topWall, ballGreen, ballBlue, ballRed, ...pills, mouseConstraint, ]); Render.run(render); const runner = Runner.create(); Runner.run(runner, engine); return () => { Render.stop(render); Runner.stop(runner); Composite.clear(engine.world); Engine.clear(engine); render.canvas.remove(); }; }, [resolvedTheme]); return (
); }