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 ( +
+ + {/* Anvil shape */} + + + + {/* Gear hint */} + + + + + + + + + + + + + + + + +
+ ); +} + +/* ─── 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 */}