"use client"; import { useEffect, useRef } from "react"; import { useTheme } from "next-themes"; import Matter from "matter-js"; import { twj } from "tw-to-css"; 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]; }; useEffect(() => { const pillTexts: HTMLDivElement[] = []; let container = containerRef.current; if (!container) { return; } const width = container.offsetWidth; const height = container.offsetHeight; // TODO: ask Shubh about using variables in useEffect not working as expected // const bgColor = getComputedStyle(document.documentElement) // .getPropertyValue("--color-background") // .trim(); // const fgColor = getComputedStyle(document.documentElement) // .getPropertyValue("--color-foreground") // .trim(); const bgColor = resolvedTheme === "dark" ? "oklch(0.145 0 0)" : "oklch(1 0 0)"; const fgColor = resolvedTheme === "dark" ? "oklch(0.985 0 0)" : "oklch(0.145 0 0)"; const { Engine, Events, Render, Bodies, Composite, Runner, Mouse, MouseConstraint, } = Matter; const engine = Engine.create(); engine.gravity.y = 0.3; engine.enableSleeping = true; 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 ballRadius = 15; let [randomX, randomY] = getRandomXY(width, height, ballRadius); const ballRed = Bodies.circle(randomX, randomY, ballRadius, { restitution: 0.8, render: { fillStyle: "red" }, }); [randomX, randomY] = getRandomXY(width, height, ballRadius); const ballBlue = Bodies.circle(randomX, randomY, ballRadius, { restitution: 0.8, render: { fillStyle: "blue" }, }); [randomX, randomY] = getRandomXY(width, height, ballRadius); const ballGreen = Bodies.circle(randomX, randomY, ballRadius, { 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 }, }, }); const pills: Matter.Body[] = []; const pillsToRenderInfo = [ { text: "ENGINEERING MANAGER" }, { text: "PRINCIPAL ENGINEER" }, { text: "NIX", chamferRadius: 20 }, { text: "AI", chamferRadius: 20 }, ]; pillsToRenderInfo.forEach(function (pillInfo) { const fontSize = 32; const pillText = pillInfo.text; const ctx = render.canvas.getContext("2d"); ctx.font = `${fontSize}px Mono`; const textWidth = ctx.measureText(pillText).width; const pillHeight = fontSize * 2; const pillWidth = textWidth + pillHeight; [randomX, randomY] = getRandomXY(width, height, pillWidth / 2); const chamferRadius = pillInfo.chamferRadius || pillHeight / 2; const pill = Matter.Bodies.rectangle( randomX, randomY, pillWidth, pillHeight, { chamfer: { radius: chamferRadius }, isStatic: false, density: 0.001, friction: 0.05, frictionAir: 0.01, render: { fillStyle: "rgba(255, 255, 255, 0)", // transparent fill strokeStyle: fgColor, // black border lineWidth: 1, }, }, ); pills.push(pill); const text: HTMLDivElement = document.createElement("div"); Object.assign(text.style, { ...twj("absolute flex justify-center items-center"), ...{ width: `${pillWidth}px`, height: `${pillHeight}px`, fontFamily: "Space Mono", fontSize: `${fontSize}px`, color: fgColor, pointerEvents: "none", }, }); text.innerText = pillText; pillTexts.push(text); 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(); pillTexts.forEach((pillText) => { pillText.remove(); }); }; }, [resolvedTheme]); return (
); }