diff --git a/src/App.tsx b/src/App.tsx
index b965f67..1e53690 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,12 +1,17 @@
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { useState } from "react"
+import { ConstellationWidget } from "./components/ConstellationWidget"
function App() {
const [count, setCount] = useState(0)
return (
-
+
+ {/* Creative Widget - 400x400 Interactive Constellation */}
+
+
+ {/* Hello World Card */}
diff --git a/src/components/ConstellationWidget.tsx b/src/components/ConstellationWidget.tsx
new file mode 100644
index 0000000..9f35ba5
--- /dev/null
+++ b/src/components/ConstellationWidget.tsx
@@ -0,0 +1,153 @@
+import { useEffect, useRef, useState } from 'react';
+
+interface Particle {
+ x: number;
+ y: number;
+ vx: number;
+ vy: number;
+ radius: number;
+ color: string;
+}
+
+export function ConstellationWidget() {
+ const canvasRef = useRef(null);
+ const [mousePos, setMousePos] = useState({ x: 0, y: 0 });
+ const [isHovering, setIsHovering] = useState(false);
+ const particlesRef = useRef([]);
+ const animationRef = useRef();
+
+ const colors = [
+ '#60A5FA', // blue-400
+ '#A78BFA', // violet-400
+ '#F472B6', // pink-400
+ '#34D399', // emerald-400
+ '#FBBF24', // amber-400
+ ];
+
+ useEffect(() => {
+ const canvas = canvasRef.current;
+ if (!canvas) return;
+
+ const ctx = canvas.getContext('2d');
+ if (!ctx) return;
+
+ // Set canvas size
+ canvas.width = 400;
+ canvas.height = 400;
+
+ // Initialize particles
+ if (particlesRef.current.length === 0) {
+ for (let i = 0; i < 25; i++) {
+ particlesRef.current.push({
+ x: Math.random() * 400,
+ y: Math.random() * 400,
+ vx: (Math.random() - 0.5) * 0.5,
+ vy: (Math.random() - 0.5) * 0.5,
+ radius: Math.random() * 3 + 2,
+ color: colors[Math.floor(Math.random() * colors.length)],
+ });
+ }
+ }
+
+ const animate = () => {
+ ctx.fillStyle = 'rgba(15, 23, 42, 0.1)';
+ ctx.fillRect(0, 0, 400, 400);
+
+ const particles = particlesRef.current;
+
+ // Update and draw particles
+ particles.forEach((particle, i) => {
+ // Mouse interaction
+ if (isHovering) {
+ const dx = mousePos.x - particle.x;
+ const dy = mousePos.y - particle.y;
+ const dist = Math.sqrt(dx * dx + dy * dy);
+ if (dist < 100) {
+ particle.vx += dx * 0.0001;
+ particle.vy += dy * 0.0001;
+ }
+ }
+
+ // Update position
+ particle.x += particle.vx;
+ particle.y += particle.vy;
+
+ // Bounce off walls
+ if (particle.x < 0 || particle.x > 400) particle.vx *= -1;
+ if (particle.y < 0 || particle.y > 400) particle.vy *= -1;
+
+ // Keep in bounds
+ particle.x = Math.max(0, Math.min(400, particle.x));
+ particle.y = Math.max(0, Math.min(400, particle.y));
+
+ // Draw particle
+ ctx.beginPath();
+ ctx.arc(particle.x, particle.y, particle.radius, 0, Math.PI * 2);
+ ctx.fillStyle = particle.color;
+ ctx.fill();
+
+ // Draw connections
+ for (let j = i + 1; j < particles.length; j++) {
+ const other = particles[j];
+ const dx = particle.x - other.x;
+ const dy = particle.y - other.y;
+ const dist = Math.sqrt(dx * dx + dy * dy);
+
+ if (dist < 100) {
+ ctx.beginPath();
+ ctx.moveTo(particle.x, particle.y);
+ ctx.lineTo(other.x, other.y);
+ ctx.strokeStyle = `rgba(96, 165, 250, ${0.2 * (1 - dist / 100)})`;
+ ctx.lineWidth = 1;
+ ctx.stroke();
+ }
+ }
+ });
+
+ animationRef.current = requestAnimationFrame(animate);
+ };
+
+ animate();
+
+ return () => {
+ if (animationRef.current) {
+ cancelAnimationFrame(animationRef.current);
+ }
+ };
+ }, [mousePos, isHovering]);
+
+ const handleMouseMove = (e: React.MouseEvent) => {
+ const canvas = canvasRef.current;
+ if (!canvas) return;
+ const rect = canvas.getBoundingClientRect();
+ setMousePos({
+ x: e.clientX - rect.left,
+ y: e.clientY - rect.top,
+ });
+ };
+
+ return (
+
+
+
+
+ ✨ Interactive Constellation Widget • {isHovering ? 'Mouse active' : 'Hover to interact'}
+
+
+ );
+}
\ No newline at end of file