diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 3a96bde2c..f61f668c3 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -2031,12 +2031,14 @@
"version": "15.7.15",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
"integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
+ "dev": true,
"license": "MIT"
},
"node_modules/@types/react": {
"version": "18.3.28",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz",
"integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@types/prop-types": "*",
@@ -2560,6 +2562,7 @@
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "dev": true,
"license": "MIT"
},
"node_modules/d3-array": {
@@ -5048,6 +5051,7 @@
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
+ "dev": true,
"license": "MIT"
},
"node_modules/react-markdown": {
diff --git a/frontend/src/components/home/ForgeBackground.tsx b/frontend/src/components/home/ForgeBackground.tsx
new file mode 100644
index 000000000..59cd8cbee
--- /dev/null
+++ b/frontend/src/components/home/ForgeBackground.tsx
@@ -0,0 +1,269 @@
+import React, { useMemo } from 'react';
+
+/* ───────────────────────────────────────────────────────────────
+ * ForgeBackground — Animated forge/factory hero background
+ *
+ * Pure CSS animations for 60fps performance.
+ * Layers: spark shower → ember drift → molten glow → smoke wisps
+ * ─────────────────────────────────────────────────────────────── */
+
+/* ─── Spark particles (fast, small, upward) ─── */
+function SparkShower({ count = 40 }: { count?: number }) {
+ const sparks = useMemo(() =>
+ Array.from({ length: count }, (_, i) => ({
+ id: i,
+ x: `${5 + Math.random() * 90}%`,
+ delay: `${Math.random() * 4}s`,
+ duration: `${1.5 + Math.random() * 2.5}s`,
+ size: 1 + Math.random() * 2,
+ drift: (Math.random() - 0.5) * 60,
+ })), [count]);
+
+ return (
+ <>
+ {sparks.map((s) => (
+
2 ? '#FFB300' : '#FF6D00',
+ boxShadow: s.size > 2
+ ? '0 0 4px rgba(255,179,0,0.6), 0 0 8px rgba(255,179,0,0.3)'
+ : '0 0 2px rgba(255,109,0,0.4)',
+ animationDelay: s.delay,
+ animationDuration: s.duration,
+ ['--spark-drift' as string]: `${s.drift}px`,
+ } as React.CSSProperties}
+ />
+ ))}
+ >
+ );
+}
+
+/* ─── Ember particles (slow, large, drifting) ─── */
+function EmberDrift({ count = 15 }: { count?: number }) {
+ const embers = useMemo(() =>
+ Array.from({ length: count }, (_, i) => ({
+ id: i,
+ x: `${10 + Math.random() * 80}%`,
+ delay: `${Math.random() * 6}s`,
+ duration: `${6 + Math.random() * 8}s`,
+ size: 3 + Math.random() * 4,
+ opacity: 0.3 + Math.random() * 0.4,
+ hue: Math.random() > 0.5 ? 0 : 40, // red or orange
+ })), [count]);
+
+ return (
+ <>
+ {embers.map((e) => (
+
+ ))}
+ >
+ );
+}
+
+/* ─── Molten glow at the bottom ─── */
+function MoltenGlow() {
+ return (
+ <>
+ {/* Main glow */}
+
+ {/* Hot core */}
+
+ {/* Animated molten ripple */}
+
+ >
+ );
+}
+
+/* ─── Smoke wisps ─── */
+function SmokeWisps({ count = 6 }: { count?: number }) {
+ const wisps = useMemo(() =>
+ Array.from({ length: count }, (_, i) => ({
+ id: i,
+ x: `${15 + i * 14}%`,
+ delay: `${i * 1.5}s`,
+ duration: `${10 + Math.random() * 8}s`,
+ size: 80 + Math.random() * 120,
+ opacity: 0.02 + Math.random() * 0.03,
+ })), [count]);
+
+ return (
+ <>
+ {wisps.map((w) => (
+
+ ))}
+ >
+ );
+}
+
+/* ─── Forge silhouette (decorative SVG shape at bottom) ─── */
+function ForgeSilhouette() {
+ return (
+
+
+
+ );
+}
+
+/* ─── Main export ─── */
+export function ForgeBackground() {
+ return (
+ <>
+ {/* Grid overlay (reuse existing) */}
+
+
+ {/* Particles */}
+
+
+
+ {/* Molten glow */}
+
+
+ {/* Smoke */}
+
+
+ {/* Forge silhouette */}
+
+
+ {/* Gradient overlay (top, for depth) */}
+
+ >
+ );
+}
\ No newline at end of file
diff --git a/frontend/src/components/home/HeroSection.tsx b/frontend/src/components/home/HeroSection.tsx
index e37307166..439ec070a 100644
--- a/frontend/src/components/home/HeroSection.tsx
+++ b/frontend/src/components/home/HeroSection.tsx
@@ -1,4 +1,5 @@
import React, { useState, useEffect } from 'react';
+import { ForgeBackground } from './ForgeBackground';
import { Link, useNavigate } from 'react-router-dom';
import { motion, useInView, animate, useMotionValue } from 'framer-motion';
import { useStats } from '../../hooks/useStats';
@@ -12,35 +13,6 @@ const GitHubIcon = () => (
);
-function EmberParticles({ count = 5 }: { count?: number }) {
- const particles = Array.from({ length: count }, (_, i) => ({
- id: i,
- left: `${15 + i * 15}%`,
- delay: `${i * 0.8}s`,
- color: i % 2 === 0 ? '#00E676' : '#E040FB',
- size: 2 + (i % 3),
- }));
-
- return (
- <>
- {particles.map((p) => (
-
- ))}
- >
- );
-}
-
function CountUp({ target, prefix = '', suffix = '' }: { target: number; prefix?: string; suffix?: string }) {
const ref = React.useRef
(null);
const motionValue = useMotionValue(0);
@@ -88,10 +60,8 @@ export function HeroSection() {
return (
- {/* Background layers */}
-
-
-
+ {/* Animated forge background */}
+
{/* Terminal card */}