2025-06-04 20:47:50 +00:00
|
|
|
"use client";
|
|
|
|
|
|
|
|
|
|
import { useEffect, useRef } from "react";
|
|
|
|
|
import { useTheme } from "next-themes";
|
|
|
|
|
import Matter from "matter-js";
|
2025-06-07 11:03:42 +00:00
|
|
|
import { twj } from "tw-to-css";
|
2025-06-04 20:47:50 +00:00
|
|
|
|
|
|
|
|
export function World() {
|
2025-06-07 08:39:58 +00:00
|
|
|
const containerRef = useRef<HTMLDivElement>(null);
|
2025-06-04 20:47:50 +00:00
|
|
|
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(() => {
|
2025-06-07 10:19:11 +00:00
|
|
|
const pillTexts: HTMLDivElement[] = [];
|
|
|
|
|
|
|
|
|
|
let container = containerRef.current;
|
2025-06-07 08:39:58 +00:00
|
|
|
if (!container) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-04 20:47:50 +00:00
|
|
|
const width = container.offsetWidth;
|
|
|
|
|
const height = container.offsetHeight;
|
2025-06-07 10:19:11 +00:00
|
|
|
|
2025-06-07 11:03:42 +00:00
|
|
|
// 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)";
|
2025-06-04 20:47:50 +00:00
|
|
|
|
|
|
|
|
const {
|
|
|
|
|
Engine,
|
2025-06-07 08:39:58 +00:00
|
|
|
Events,
|
2025-06-04 20:47:50 +00:00
|
|
|
Render,
|
|
|
|
|
Bodies,
|
|
|
|
|
Composite,
|
|
|
|
|
Runner,
|
|
|
|
|
Mouse,
|
|
|
|
|
MouseConstraint,
|
|
|
|
|
} = Matter;
|
|
|
|
|
const engine = Engine.create();
|
|
|
|
|
engine.gravity.y = 0.3;
|
2025-06-07 10:19:11 +00:00
|
|
|
engine.enableSleeping = true;
|
2025-06-04 20:47:50 +00:00
|
|
|
|
|
|
|
|
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
|
2025-06-07 11:03:42 +00:00
|
|
|
const ballRadius = 15;
|
2025-06-04 20:47:50 +00:00
|
|
|
|
2025-06-07 11:03:42 +00:00
|
|
|
let [randomX, randomY] = getRandomXY(width, height, ballRadius);
|
|
|
|
|
const ballRed = Bodies.circle(randomX, randomY, ballRadius, {
|
2025-06-04 20:47:50 +00:00
|
|
|
restitution: 0.8,
|
|
|
|
|
render: { fillStyle: "red" },
|
|
|
|
|
});
|
2025-06-07 11:03:42 +00:00
|
|
|
[randomX, randomY] = getRandomXY(width, height, ballRadius);
|
|
|
|
|
const ballBlue = Bodies.circle(randomX, randomY, ballRadius, {
|
2025-06-04 20:47:50 +00:00
|
|
|
restitution: 0.8,
|
|
|
|
|
render: { fillStyle: "blue" },
|
|
|
|
|
});
|
2025-06-07 11:03:42 +00:00
|
|
|
[randomX, randomY] = getRandomXY(width, height, ballRadius);
|
|
|
|
|
const ballGreen = Bodies.circle(randomX, randomY, ballRadius, {
|
2025-06-04 20:47:50 +00:00
|
|
|
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 },
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
2025-06-07 10:19:11 +00:00
|
|
|
const pills: Matter.Body[] = [];
|
2025-06-07 08:52:30 +00:00
|
|
|
const pillsToRenderInfo = [
|
|
|
|
|
{ text: "ENGINEERING MANAGER" },
|
|
|
|
|
{ text: "PRINCIPAL ENGINEER" },
|
|
|
|
|
{ text: "NIX", chamferRadius: 20 },
|
|
|
|
|
{ text: "AI", chamferRadius: 20 },
|
2025-06-07 08:39:58 +00:00
|
|
|
];
|
|
|
|
|
|
2025-06-07 08:52:30 +00:00
|
|
|
pillsToRenderInfo.forEach(function (pillInfo) {
|
2025-06-07 08:39:58 +00:00
|
|
|
const fontSize = 32;
|
|
|
|
|
|
2025-06-07 08:52:30 +00:00
|
|
|
const pillText = pillInfo.text;
|
|
|
|
|
|
2025-06-07 08:39:58 +00:00
|
|
|
const ctx = render.canvas.getContext("2d");
|
2025-06-07 11:03:42 +00:00
|
|
|
ctx.font = `${fontSize}px Mono`;
|
2025-06-07 08:39:58 +00:00
|
|
|
const textWidth = ctx.measureText(pillText).width;
|
|
|
|
|
|
|
|
|
|
const pillHeight = fontSize * 2;
|
|
|
|
|
const pillWidth = textWidth + pillHeight;
|
2025-06-07 11:03:42 +00:00
|
|
|
[randomX, randomY] = getRandomXY(width, height, pillWidth / 2);
|
2025-06-07 08:52:30 +00:00
|
|
|
const chamferRadius = pillInfo.chamferRadius || pillHeight / 2;
|
2025-06-07 08:39:58 +00:00
|
|
|
const pill = Matter.Bodies.rectangle(
|
|
|
|
|
randomX,
|
|
|
|
|
randomY,
|
|
|
|
|
pillWidth,
|
|
|
|
|
pillHeight,
|
|
|
|
|
{
|
2025-06-07 08:52:30 +00:00
|
|
|
chamfer: { radius: chamferRadius },
|
2025-06-07 08:39:58 +00:00
|
|
|
isStatic: false,
|
|
|
|
|
density: 0.001,
|
|
|
|
|
friction: 0.05,
|
|
|
|
|
frictionAir: 0.01,
|
|
|
|
|
render: {
|
|
|
|
|
fillStyle: "rgba(255, 255, 255, 0)", // transparent fill
|
2025-06-07 08:52:30 +00:00
|
|
|
strokeStyle: fgColor, // black border
|
2025-06-07 08:39:58 +00:00
|
|
|
lineWidth: 1,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
pills.push(pill);
|
2025-06-07 10:19:11 +00:00
|
|
|
const text: HTMLDivElement = document.createElement("div");
|
2025-06-07 08:39:58 +00:00
|
|
|
Object.assign(text.style, {
|
2025-06-07 11:03:42 +00:00
|
|
|
...twj("absolute flex justify-center items-center"),
|
|
|
|
|
...{
|
|
|
|
|
width: `${pillWidth}px`,
|
|
|
|
|
height: `${pillHeight}px`,
|
|
|
|
|
fontFamily: "Space Mono",
|
|
|
|
|
fontSize: `${fontSize}px`,
|
|
|
|
|
color: fgColor,
|
|
|
|
|
pointerEvents: "none",
|
|
|
|
|
},
|
2025-06-07 08:39:58 +00:00
|
|
|
});
|
|
|
|
|
text.innerText = pillText;
|
2025-06-07 10:19:11 +00:00
|
|
|
pillTexts.push(text);
|
2025-06-07 08:39:58 +00:00
|
|
|
|
|
|
|
|
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)`;
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2025-06-04 20:47:50 +00:00
|
|
|
// Add all elements to the world
|
|
|
|
|
Composite.add(engine.world, [
|
|
|
|
|
ground,
|
|
|
|
|
leftWall,
|
|
|
|
|
rightWall,
|
|
|
|
|
topWall,
|
|
|
|
|
ballGreen,
|
|
|
|
|
ballBlue,
|
|
|
|
|
ballRed,
|
2025-06-07 08:39:58 +00:00
|
|
|
...pills,
|
2025-06-04 20:47:50 +00:00
|
|
|
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();
|
2025-06-07 10:19:11 +00:00
|
|
|
pillTexts.forEach((pillText) => {
|
|
|
|
|
pillText.remove();
|
|
|
|
|
});
|
2025-06-04 20:47:50 +00:00
|
|
|
};
|
|
|
|
|
}, [resolvedTheme]);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
ref={containerRef}
|
|
|
|
|
className="w-[95vw] h-[40vh] mx-auto relative overflow-hidden"
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
}
|