feat: replace static icons with animated SVG components in GPU capabilities

- Replaced Heroicons with custom animated SVG components for each GPU capability card
- Added four new animation components: InterferenceAnimation, KubernetesAcceleration, RenderingSimulation, and RAGPipeline
- Updated card layout to accommodate full-width animations above text content
This commit is contained in:
2025-11-08 00:56:07 +01:00
parent 5ab909bd12
commit 22e2e4b80c
6 changed files with 698 additions and 23 deletions

View File

@@ -4,12 +4,12 @@ import { useRef } from "react";
import { Eyebrow, CP, CT, H5 } from "@/components/Texts"; import { Eyebrow, CP, CT, H5 } from "@/components/Texts";
import { IoArrowBackOutline, IoArrowForwardOutline } from "react-icons/io5"; import { IoArrowBackOutline, IoArrowForwardOutline } from "react-icons/io5";
import { // ✅ Import animations
SparklesIcon,
Cog6ToothIcon, import KubernetesAcceleration from "./animations/KubernetesAcceleration";
CubeTransparentIcon, import RenderingSimulation from "./animations/RenderingSimulation";
CpuChipIcon, import RAGPipeline from "./animations/RAGPipeline";
} from "@heroicons/react/24/solid"; import InterferenceAnimation from "./animations/InterferenceAnimation";
const gpuCapabilities = [ const gpuCapabilities = [
{ {
@@ -22,31 +22,33 @@ const gpuCapabilities = [
{ {
name: "AI / ML Inference & Training", name: "AI / ML Inference & Training",
description: "LLMs, embeddings, transformers, fine-tuning", description: "LLMs, embeddings, transformers, fine-tuning",
icon: SparklesIcon, animation: InterferenceAnimation,
}, },
{ {
name: "Acceleration in Kubernetes Workloads", name: "Acceleration in Kubernetes Workloads",
description: "GPU-backed deployments on Mycelium Cloud", description: "GPU-backed deployments on Mycelium Cloud",
icon: Cog6ToothIcon, animation: KubernetesAcceleration,
}, },
{ {
name: "Rendering & Simulation", name: "Rendering & Simulation",
description: "Scientific visualization, VFX, real-time 3D", description: "Scientific visualization, VFX, real-time 3D",
icon: CubeTransparentIcon, animation: RenderingSimulation,
}, },
{ {
name: "Agent Compute & RAG Pipelines", name: "Agent Compute & RAG Pipelines",
description: description:
"Vector memory + model execution on sovereign hardware", "Vector memory + model execution on sovereign hardware",
icon: CpuChipIcon, animation: RAGPipeline,
}, },
]; ];
export function GpuCapabilities() { export function GpuCapabilities() {
const sliderRef = useRef<HTMLUListElement>(null); const sliderRef = useRef<HTMLUListElement>(null);
const scrollLeft = () => sliderRef.current?.scrollBy({ left: -400, behavior: "smooth" }); const scrollLeft = () =>
const scrollRight = () => sliderRef.current?.scrollBy({ left: 400, behavior: "smooth" }); sliderRef.current?.scrollBy({ left: -400, behavior: "smooth" });
const scrollRight = () =>
sliderRef.current?.scrollBy({ left: 400, behavior: "smooth" });
return ( return (
<section className="bg-[#121212] w-full max-w-8xl mx-auto"> <section className="bg-[#121212] w-full max-w-8xl mx-auto">
@@ -63,16 +65,17 @@ export function GpuCapabilities() {
{gpuCapabilities.map((item, idx) => ( {gpuCapabilities.map((item, idx) => (
<li <li
key={idx} key={idx}
className={`snap-start shrink-0 w-[85%] sm:w-[50%] lg:w-[33%] border border-gray-800 p-10 relative ${ className={`snap-start shrink-0 w-[85%] sm:w-[50%] lg:w-[33%] border border-gray-800 relative flex flex-col ${
item.isIntro ? "bg-[#1b1b1b]" : "bg-[#111]/60" item.isIntro ? "bg-[#1b1b1b] p-10" : "bg-[#111]/60"
}`} }`}
> >
{/* ✅ First intro card with arrows */}
{item.isIntro ? ( {item.isIntro ? (
<div className="flex flex-col justify-between h-full"> <div className="flex flex-col justify-between h-full">
<div> <div>
<Eyebrow>{item.eyebrow}</Eyebrow> <Eyebrow>{item.eyebrow}</Eyebrow>
<H5 className="text-white mt-4 lg:text-2xl text-xl">{item.title}</H5> <H5 className="text-white mt-4 lg:text-2xl text-xl">
{item.title}
</H5>
<p className="mt-4 text-gray-400 lg:text-lg text-sm leading-relaxed"> <p className="mt-4 text-gray-400 lg:text-lg text-sm leading-relaxed">
{item.description} {item.description}
</p> </p>
@@ -102,14 +105,21 @@ export function GpuCapabilities() {
</div> </div>
</div> </div>
) : ( ) : (
<div className="flex flex-col items-center text-center"> <>
<div className="flex items-center justify-center h-12 w-12 rounded-xl bg-[#1d1d1d] border border-gray-800"> {/* ✅ STREAMING ANIMATION REPLACES ICON */}
<item.icon className="h-6 w-6 text-cyan-400" /> <div className="w-full flex items-center justify-center">
{item.animation && <item.animation />}
</div> </div>
<CT className="text-lg font-semibold text-white mt-4">{item.name}</CT> <div className="p-6 text-center">
<CP className="mt-2 text-gray-400 leading-snug">{item.description}</CP> <CT className="text-lg font-semibold text-white mt-2">
</div> {item.name}
</CT>
<CP className="mt-2 text-gray-400 leading-snug">
{item.description}
</CP>
</div>
</>
)} )}
</li> </li>
))} ))}

View File

@@ -0,0 +1,296 @@
"use client";
export default function InferenceAnimation() {
return (
<svg
width="382"
height="282"
viewBox="0 0 382 282"
xmlns="http://www.w3.org/2000/svg"
role="img"
aria-labelledby="title desc"
>
<title id="title">AI / ML Inference & Training Animation</title>
<desc id="desc">
Animated neural graph sending signals to a glowing GPU chip on a dark
background.
</desc>
{/* ---------- DEFINITIONS ---------- */}
<defs>
{/* Dark grid pattern */}
<pattern id="grid" width="16" height="16" patternUnits="userSpaceOnUse">
<path d="M16 0H0V16" fill="none" stroke="#0f1621" strokeWidth="1" />
</pattern>
{/* Cyan gradient */}
<linearGradient id="cyanglow" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stopColor="#00E5FF">
<animate
attributeName="stop-color"
values="#00E5FF;#00B8DB;#00E5FF"
dur="4s"
repeatCount="indefinite"
/>
</stop>
<stop offset="100%" stopColor="#00B8DB">
<animate
attributeName="stop-color"
values="#00B8DB;#00E5FF;#00B8DB"
dur="4s"
repeatCount="indefinite"
/>
</stop>
</linearGradient>
{/* Soft outer glow */}
<filter id="softGlow" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="6" result="blur" />
<feMerge>
<feMergeNode in="blur" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
{/* Sharper glow for nodes */}
<filter id="nodeGlow" x="-150%" y="-150%" width="400%" height="400%">
<feGaussianBlur stdDeviation="3" result="ng" />
<feMerge>
<feMergeNode in="ng" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
{/* Dashed link style */}
<style>{`
.link {
stroke: #11cdef;
stroke-width: 1.75;
stroke-linecap: round;
stroke-dasharray: 6 10;
opacity: 0.9;
}
.muted { opacity: 0.65; }
.chip-line { stroke: #93c5fd; stroke-width: 1; opacity: 0.7; }
`}</style>
{/* Reusable motion path for packet */}
<path
id="path1"
d="M 64 156 C 110 118, 160 130, 206 140 S 280 158, 316 150"
fill="none"
/>
<path
id="path2"
d="M 64 116 C 116 96, 160 104, 204 112 S 280 126, 316 112"
fill="none"
/>
</defs>
{/* ---------- BACKGROUND ---------- */}
<rect width="382" height="282" fill="transparent" />
<rect width="382" height="282" fill="url(#grid)" opacity="0.55" />
{/* ---------- GPU CHIP (RIGHT) ---------- */}
<g transform="translate(252,62)">
{/* Outer casing */}
<rect
x="0"
y="0"
width="112"
height="158"
rx="18"
fill="#0e1724"
stroke="url(#cyanglow)"
strokeWidth="2"
filter="url(#softGlow)"
/>
{/* Inner frame */}
<rect
x="12"
y="14"
width="88"
height="130"
rx="10"
fill="#0b1320"
stroke="#163447"
strokeWidth="1.5"
/>
{/* Matrix / cores */}
{[...Array(5)].map((_, r) => (
<line
key={`row-${r}`}
x1="20"
x2="92"
y1={28 + r * 24}
y2={28 + r * 24}
className="chip-line"
/>
))}
{[...Array(6)].map((_, c) => (
<line
key={`col-${c}`}
y1="26"
y2="126"
x1={22 + c * 12}
x2={22 + c * 12}
className="chip-line"
/>
))}
{/* Pulsing GPU die */}
<rect
x="40"
y="58"
width="32"
height="32"
rx="6"
fill="url(#cyanglow)"
opacity="0.85"
>
<animate
attributeName="opacity"
values="0.5;0.95;0.5"
dur="2.2s"
repeatCount="indefinite"
/>
</rect>
{/* Glow ring */}
<rect
x="36"
y="54"
width="40"
height="40"
rx="8"
stroke="url(#cyanglow)"
strokeWidth="2"
fill="none"
>
<animate
attributeName="stroke-width"
values="2;4;2"
dur="2.2s"
repeatCount="indefinite"
/>
</rect>
</g>
{/* ---------- NEURAL GRAPH (LEFT/MID) ---------- */}
<g transform="translate(28,34)">
{/* Links */}
<g className="muted">
{/* lower cluster to chip */}
<path className="link" d="M36 148 L96 124" >
<animate
attributeName="stroke-dashoffset"
values="0;-32"
dur="1.6s"
repeatCount="indefinite"
/>
</path>
<path className="link" d="M96 124 L152 138" >
<animate
attributeName="stroke-dashoffset"
values="0;-32"
dur="1.6s"
repeatCount="indefinite"
/>
</path>
<path className="link" d="M152 138 L212 132" >
<animate
attributeName="stroke-dashoffset"
values="0;-32"
dur="1.6s"
repeatCount="indefinite"
/>
</path>
{/* upper cluster to mid */}
<path className="link" d="M40 76 L92 88" >
<animate
attributeName="stroke-dashoffset"
values="0;-28"
dur="1.4s"
repeatCount="indefinite"
/>
</path>
<path className="link" d="M92 88 L140 96" >
<animate
attributeName="stroke-dashoffset"
values="0;-28"
dur="1.4s"
repeatCount="indefinite"
/>
</path>
<path className="link" d="M140 96 L206 106" >
<animate
attributeName="stroke-dashoffset"
values="0;-28"
dur="1.4s"
repeatCount="indefinite"
/>
</path>
{/* vertical cross links */}
<path className="link" d="M92 88 L96 124" >
<animate
attributeName="stroke-dashoffset"
values="0;-24"
dur="1.2s"
repeatCount="indefinite"
/>
</path>
<path className="link" d="M140 96 L152 138" >
<animate
attributeName="stroke-dashoffset"
values="0;-24"
dur="1.2s"
repeatCount="indefinite"
/>
</path>
</g>
{/* Nodes */}
{[
{ x: 36, y: 148, r: 6 },
{ x: 40, y: 76, r: 6 },
{ x: 96, y: 124, r: 7 },
{ x: 92, y: 88, r: 7 },
{ x: 140, y: 96, r: 7 },
{ x: 152, y: 138, r: 7 },
{ x: 206, y: 106, r: 6.5 },
{ x: 212, y: 132, r: 6.5 },
].map((n, i) => (
<g key={i} filter="url(#nodeGlow)">
<circle cx={n.x} cy={n.y} r={n.r} fill="#06121a" stroke="#163447" />
<circle cx={n.x} cy={n.y} r={n.r - 2} fill="url(#cyanglow)" opacity="0.9">
<animate
attributeName="opacity"
values="0.6;1;0.6"
dur={`${1.2 + (i % 4) * 0.2}s`}
repeatCount="indefinite"
/>
</circle>
</g>
))}
{/* Data packets moving along paths */}
<g>
<circle r="3.2" fill="url(#cyanglow)">
<animateMotion dur="2.6s" repeatCount="indefinite">
<mpath href="#path1" />
</animateMotion>
</circle>
<circle r="2.8" fill="url(#cyanglow)" opacity="0.85">
<animateMotion dur="2.2s" begin="0.4s" repeatCount="indefinite">
<mpath href="#path2" />
</animateMotion>
</circle>
</g>
</g>
{/* ---------- LABELS ---------- */}
</svg>
);
}

View File

@@ -0,0 +1,124 @@
"use client";
export default function KubernetesAcceleration() {
return (
<svg
width="382"
height="282"
viewBox="0 0 382 282"
xmlns="http://www.w3.org/2000/svg"
role="img"
aria-labelledby="title desc"
>
<title id="title">Acceleration in Kubernetes</title>
<desc id="desc">Pods spin up on a grid and get GPU badges.</desc>
<defs>
{/* Dark grid */}
<pattern id="grid" width="16" height="16" patternUnits="userSpaceOnUse">
<path d="M16 0H0V16" fill="none" stroke="#0f1621" strokeWidth="1" />
</pattern>
{/* Cyan gradient */}
<linearGradient id="cyanglow" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stopColor="#00E5FF">
<animate attributeName="stop-color" values="#00E5FF;#00B8DB;#00E5FF" dur="4s" repeatCount="indefinite" />
</stop>
<stop offset="100%" stopColor="#00B8DB">
<animate attributeName="stop-color" values="#00B8DB;#00E5FF;#00B8DB" dur="4s" repeatCount="indefinite" />
</stop>
</linearGradient>
<filter id="softGlow" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="5" result="b" />
<feMerge>
<feMergeNode in="b" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
<style>{`
.pod {
fill: #0e1724;
stroke: #163447;
stroke-width: 1.5;
}
.podOn {
stroke: url(#cyanglow);
}
.badge {
stroke: url(#cyanglow);
stroke-width: 1.5;
fill: #09131c;
}
`}</style>
</defs>
{/* Background */}
<rect width="382" height="282" fill="transparent" />
<rect width="382" height="282" fill="url(#grid)" opacity="0.55" />
{/* Cluster grid */}
<g transform="translate(40,50)">
{/* 4 rows × 6 cols */}
{Array.from({ length: 4 }).map((_, r) =>
Array.from({ length: 6 }).map((__, c) => {
const x = 18 + c * 48;
const y = 24 + r * 46;
const delay = (r * 6 + c) * 0.06;
const on = (r + c) % 2 === 0; // mark some pods "on"
return (
<g key={`${r}-${c}`}>
{/* Pod */}
<rect
x={x}
y={y}
width="34"
height="26"
rx="6"
className={`pod ${on ? "podOn" : ""}`}
>
<animate
attributeName="opacity"
values="0;1"
dur="0.35s"
begin={`${delay}s`}
fill="freeze"
/>
<animateTransform
attributeName="transform"
attributeType="XML"
type="scale"
additive="sum"
values="0.7 0.7;1 1"
dur="0.35s"
begin={`${delay}s`}
fill="freeze"
/>
</rect>
{/* GPU badge (appears on powered pods) */}
{on && (
<g transform={`translate(${x + 20},${y - 10})`} filter="url(#softGlow)">
{/* small chip */}
<rect x="-10" y="-10" width="20" height="20" rx="4" className="badge" />
<rect x="-6" y="-6" width="12" height="12" rx="2" fill="url(#cyanglow)" opacity="0.9">
<animate attributeName="opacity" values="0;1;0.6;1" dur="1.8s" begin={`${delay + 0.25}s`} repeatCount="indefinite" />
</rect>
{/* pins */}
{[-10, 10].map((px, i) => (
<line key={i} x1={px} y1="0" x2={px + (px > 0 ? 6 : -6)} y2="0" stroke="#93c5fd" strokeWidth="1" />
))}
{/* pop-in */}
<animateTransform attributeName="transform" type="scale" values="0 0;1 1" dur="0.28s" begin={`${delay + 0.18}s`} fill="freeze" />
</g>
)}
</g>
);
})
)}
</g>
</svg>
);
}

View File

@@ -0,0 +1,144 @@
"use client";
export default function RAGPipeline() {
return (
<svg
width="382"
height="282"
viewBox="0 0 382 282"
xmlns="http://www.w3.org/2000/svg"
role="img"
aria-labelledby="title desc"
>
<title id="title">Agent Compute & RAG Pipelines</title>
<desc id="desc">Documents flow into a vector DB, then a model, and finally an answer bubble.</desc>
<defs>
<pattern id="grid" width="16" height="16" patternUnits="userSpaceOnUse">
<path d="M16 0H0V16" fill="none" stroke="#0f1621" strokeWidth="1" />
</pattern>
<linearGradient id="cyanglow" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stopColor="#00E5FF">
<animate attributeName="stop-color" values="#00E5FF;#00B8DB;#00E5FF" dur="4s" repeatCount="indefinite" />
</stop>
<stop offset="100%" stopColor="#00B8DB">
<animate attributeName="stop-color" values="#00B8DB;#00E5FF;#00B8DB" dur="4s" repeatCount="indefinite" />
</stop>
</linearGradient>
<filter id="glow" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="4" result="b" />
<feMerge>
<feMergeNode in="b" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
<style>{`
.label { fill: #8ea2b2; font-weight: 600; letter-spacing: .06em; font-size: 12px; }
.box { fill: #0e1724; stroke: #163447; stroke-width: 1.5; }
.cyl { fill: #0b1320; stroke: url(#cyanglow); stroke-width: 2; }
.brain{ fill: #09131c; stroke: url(#cyanglow); stroke-width: 2; }
.arrow{ stroke: #11cdef; stroke-width: 2; fill: none; stroke-linecap: round; marker-end: url(#arrowhead); }
.pkt { fill: url(#cyanglow); }
.doc { fill: #0b1320; stroke: #2a4253; stroke-width: 1.2; }
`}</style>
{/* Arrowhead */}
<marker id="arrowhead" markerWidth="8" markerHeight="8" refX="6" refY="3.5" orient="auto">
<polygon points="0 0, 7 3.5, 0 7" fill="#11cdef" />
</marker>
{/* Paths for packets */}
<path id="p1" d="M 96 132 C 140 132, 164 132, 200 132" fill="none" />
<path id="p2" d="M 240 132 C 270 132, 300 132, 328 116" fill="none" />
</defs>
{/* Background */}
<rect width="382" height="282" fill="transparent" />
<rect width="382" height="282" fill="url(#grid)" opacity="0.55" />
{/* Documents (left) */}
<g transform="translate(36,84)">
{[-16, 0, 16].map((dy, i) => (
<g key={i} transform={`translate(0,${dy})`} filter="url(#glow)">
<rect x="0" y="0" width="56" height="70" rx="6" className="doc" />
<rect x="0" y="10" width="56" height="10" fill="#0f1b27" />
<line x1="10" y1="28" x2="46" y2="28" stroke="#294155" strokeWidth="2" />
<line x1="10" y1="38" x2="40" y2="38" stroke="#294155" strokeWidth="2" />
<line x1="10" y1="48" x2="44" y2="48" stroke="#294155" strokeWidth="2" />
<animateTransform attributeName="transform" type="translate" values="0,8;0,0;0,8" dur="4s" begin={`${i * 0.25}s`} repeatCount="indefinite" />
<animate attributeName="opacity" values="0.8;1;0.8" dur="3.2s" begin={`${i * 0.25}s`} repeatCount="indefinite" />
</g>
))}
</g>
{/* Arrow: docs -> vector DB */}
<path className="arrow" d="M 96 120 L 190 120" opacity="0.7" />
<path className="arrow" d="M 96 144 L 190 144" opacity="0.7" />
{/* Vector DB (middle cylinder stack) */}
<g transform="translate(190,84)" filter="url(#glow)">
{/* cylinder 1 */}
<ellipse cx="50" cy="12" rx="50" ry="12" className="cyl" fillOpacity="0.9" />
<rect x="0" y="12" width="100" height="22" className="cyl" fillOpacity="0.9" />
{/* cylinder 2 */}
<ellipse cx="50" cy="34" rx="50" ry="12" className="cyl" fillOpacity="0.9" />
<rect x="0" y="34" width="100" height="22" className="cyl" fillOpacity="0.9" />
{/* cylinder 3 */}
<ellipse cx="50" cy="56" rx="50" ry="12" className="cyl" fillOpacity="0.9" />
<rect x="0" y="56" width="100" height="22" className="cyl" fillOpacity="0.9" />
<ellipse cx="50" cy="78" rx="50" ry="12" className="cyl" />
{/* pulse */}
<rect x="0" y="12" width="100" height="68" fill="url(#cyanglow)" opacity="0.0">
<animate attributeName="opacity" values="0;0.12;0" dur="2.4s" repeatCount="indefinite" />
</rect>
</g>
{/* Arrow: DB -> model */}
<path className="arrow" d="M 290 132 L 332 112" />
{/* Model (right) */}
<g transform="translate(300,84)" filter="url(#glow)">
{/* brain-ish blob */}
<path
d="M 32 30
c 10 -16, 28 -12, 30 6
c 12 2, 14 18, 2 26
c -2 16, -18 20, -28 10
c -10 10, -28 4, -26 -12
c -12 -6, -10 -22, 6 -28
c 2 -12, 16 -12, 16 -2 z"
className="brain"
/>
{/* inner highlight */}
<circle cx="42" cy="46" r="10" fill="url(#cyanglow)" opacity="0.85">
<animate attributeName="r" values="9;12;9" dur="2.2s" repeatCount="indefinite" />
</circle>
</g>
{/* Output bubble */}
<g transform="translate(312,54)">
<rect x="-6" y="-6" width="64" height="28" rx="8" fill="#0e1724" stroke="url(#cyanglow)" strokeWidth="1.5" opacity="0.95" />
<text x="6" y="13" fontSize="10" fill="#cbe7f3" fontFamily="ui-sans-serif, system-ui">Answer </text>
<animateTransform attributeName="transform" type="translate" values="312,64;312,54;312,64" dur="3.4s" repeatCount="indefinite" />
</g>
{/* Packets along paths */}
<g>
<circle r="3.2" className="pkt">
<animateMotion dur="2.4s" repeatCount="indefinite">
<mpath href="#p1" />
</animateMotion>
</circle>
<circle r="3.2" className="pkt" opacity="0.85">
<animateMotion dur="2.0s" begin="0.35s" repeatCount="indefinite">
<mpath href="#p2" />
</animateMotion>
</circle>
</g>
</svg>
);
}

View File

@@ -0,0 +1,101 @@
"use client";
export default function RenderingSimulation() {
return (
<svg
width="382"
height="282"
viewBox="0 0 382 282"
xmlns="http://www.w3.org/2000/svg"
role="img"
aria-labelledby="title desc"
>
<title id="title">Rendering & Simulation</title>
<desc id="desc">Wireframe mesh animates and resolves into a shaded render.</desc>
<defs>
<pattern id="grid" width="16" height="16" patternUnits="userSpaceOnUse">
<path d="M16 0H0V16" fill="none" stroke="#0f1621" strokeWidth="1" />
</pattern>
<linearGradient id="cyanglow" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stopColor="#00E5FF">
<animate attributeName="stop-color" values="#00E5FF;#00B8DB;#00E5FF" dur="4s" repeatCount="indefinite" />
</stop>
<stop offset="100%" stopColor="#00B8DB">
<animate attributeName="stop-color" values="#00B8DB;#00E5FF;#00B8DB" dur="4s" repeatCount="indefinite" />
</stop>
</linearGradient>
<filter id="softGlow" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="4" result="b" />
<feMerge>
<feMergeNode in="b" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
<style>{`
.wire { stroke: #11cdef; stroke-width: 1.6; fill: none; opacity: 0.9; }
.tri { fill: url(#cyanglow); opacity: 0; }
`}</style>
</defs>
{/* Background */}
<rect width="382" height="282" fill="transparent" />
<rect width="382" height="282" fill="url(#grid)" opacity="0.55" />
{/* Mesh group */}
<g transform="translate(191,150)">
{/* Orbit rotation for the model */}
<g>
{/* Wireframe: icosahedron-like */}
{/* Outer ring */}
<ellipse rx="110" ry="44" className="wire">
<animateTransform attributeName="transform" type="rotate" from="0" to="360" dur="12s" repeatCount="indefinite" />
</ellipse>
{/* Cross rings */}
<ellipse rx="110" ry="44" className="wire" transform="rotate(60)">
<animateTransform attributeName="transform" type="rotate" from="60" to="420" dur="12s" repeatCount="indefinite" />
</ellipse>
<ellipse rx="110" ry="44" className="wire" transform="rotate(120)">
<animateTransform attributeName="transform" type="rotate" from="120" to="480" dur="12s" repeatCount="indefinite" />
</ellipse>
{/* Connecting chords */}
{[-80, -40, 0, 40, 80].map((a, i) => (
<line
key={i}
x1={-100}
y1={Math.sin((a * Math.PI) / 180) * 40}
x2={100}
y2={-Math.sin((a * Math.PI) / 180) * 40}
className="wire"
opacity="0.7"
/>
))}
{/* Triangles that fade in to "render" */}
<g filter="url(#softGlow)">
{/* central tri */}
<polygon points="-36,-10 0,-58 36,-10" className="tri">
<animate attributeName="opacity" values="0;0.85" dur="1.4s" begin="0.4s" fill="freeze" />
</polygon>
{/* lower tri */}
<polygon points="-30,-4 -2,30 26,-4" className="tri">
<animate attributeName="opacity" values="0;0.75" dur="1.2s" begin="0.7s" fill="freeze" />
</polygon>
{/* side tris */}
<polygon points="-62,-6 -36,-10 -46,-40" className="tri">
<animate attributeName="opacity" values="0;0.6" dur="1.0s" begin="0.9s" fill="freeze" />
</polygon>
<polygon points="62,-6 36,-10 46,-40" className="tri">
<animate attributeName="opacity" values="0;0.6" dur="1.0s" begin="1.0s" fill="freeze" />
</polygon>
</g>
</g>
</g>
</svg>
);
}

View File

@@ -14,7 +14,7 @@
/* Bundler mode */ /* Bundler mode */
"moduleResolution": "bundler", "moduleResolution": "bundler",
"allowImportingTsExtensions": true, "allowImportingTsExtensions": false,
"verbatimModuleSyntax": true, "verbatimModuleSyntax": true,
"moduleDetection": "force", "moduleDetection": "force",
"noEmit": true, "noEmit": true,