plano/apps/www/src/components/NetworkAnimation.tsx
Musa 0c3efdbef2
feat: redesign archgw -> plano + website in Next.js (#613)
* feat: redesign archgw -> plano + website

* feat(www): refactor landing page sections, add new diagrams and UI improvements

* feat(www): sections enhanced for clarify & diagrams added

* feat(www): improvements to mobile design, layout of diagrams

* feat(www): clean + typecheck

* feat(www): feedback loop changes

* feat(www): fix type error

* fix lib/utils error

* feat(www): ran biome formatting

* feat(www): graphic changes

* feat(www): web analytics

* fea(www): changes

* feat(www): introduce monorepo

This change brings Turborepo monorepo to independently handle the marketing website, the docs website and any other future use cases for mutli-platform support. They are using internal @katanemo package handlers for the design system and logic.

* fix(www): transpiler failure

* fix(www): tsconfig issue

* fix(www): next.config issue

* feat(docs): hold off on docs

* Delete next.config.ts

* feat(www): content fix

* feat(www): introduce blog

* feat(www): content changes

* Update package-lock.json

* feat: update text

* Update IntroSection.tsx

* feat: Turbopack issue

* fix

* Update IntroSection.tsx

* feat: updated Research page

* refactor(www): text clarity, padding adj.

* format(www)

* fix: add missing lib/ files to git - fixes Vercel GitHub deployment

- Updated .gitignore to properly exclude Python lib/ but include Next.js lib/ directories
- Added packages/ui/src/lib/utils.ts (cn utility function)
- Added apps/www/src/lib/sanity.ts (Sanity client configuration)
- Fixes module resolution errors in Vercel GitHub deployments (case-sensitive filesystem)

* Update .gitignore

* style(www): favicon + metadata

* fix(www): links

* fix(www): add analytics

* fix(www): add

* fix(www): fix links + image

* fix(www): fix links + image

* fix(www): fix links

* fix(www): remove from tools testing.md
2025-12-18 15:55:15 -08:00

206 lines
7.9 KiB
TypeScript

"use client";
import React, { useId } from "react";
import { motion } from "framer-motion";
// Define the grid of squares with their positions and colors
const squares = [
// Column 1 (x=3)
{ x: 3, y: 3, color: "#B0B7FF", col: 0, row: 0 },
{ x: 3, y: 6, color: "#B0B7FF", col: 0, row: 1 },
{ x: 3, y: 9, color: "#B0B7FF", col: 0, row: 2 },
{ x: 3, y: 12, color: "#ABB2FA", col: 0, row: 3 },
{ x: 3, y: 15, color: "#ABB2FA", col: 0, row: 4 },
{ x: 3, y: 18, color: "#ABB2FA", col: 0, row: 5 },
{ x: 3, y: 21, color: "#969FF4", col: 0, row: 6 },
// Column 2 (x=6)
{ x: 6, y: 3, color: "#B0B7FF", col: 1, row: 0 },
{ x: 6, y: 6, color: "#B0B7FF", col: 1, row: 1 },
{ x: 6, y: 9, color: "#ABB2FA", col: 1, row: 2 },
{ x: 6, y: 12, color: "#ABB2FA", col: 1, row: 3 },
{ x: 6, y: 15, color: "#ABB2FA", col: 1, row: 4 },
{ x: 6, y: 18, color: "#969FF4", col: 1, row: 5 },
{ x: 6, y: 21, color: "#969FF4", col: 1, row: 6 },
// Column 3 (x=9)
{ x: 9, y: 3, color: "#B0B7FF", col: 2, row: 0 },
{ x: 9, y: 6, color: "#ABB2FA", col: 2, row: 1 },
{ x: 9, y: 9, color: "#ABB2FA", col: 2, row: 2 },
{ x: 9, y: 12, color: "#ABB2FA", col: 2, row: 3 },
{ x: 9, y: 15, color: "#969FF4", col: 2, row: 4 },
{ x: 9, y: 18, color: "#969FF4", col: 2, row: 5 },
{ x: 9, y: 21, color: "#969FF4", col: 2, row: 6 },
// Column 4 (x=12)
{ x: 12, y: 3, color: "#ABB2FA", col: 3, row: 0 },
{ x: 12, y: 6, color: "#ABB2FA", col: 3, row: 1 },
{ x: 12, y: 9, color: "#ABB2FA", col: 3, row: 2 },
{ x: 12, y: 12, color: "#969FF4", col: 3, row: 3 },
{ x: 12, y: 15, color: "#969FF4", col: 3, row: 4 },
{ x: 12, y: 18, color: "#969FF4", col: 3, row: 5 },
{ x: 12, y: 21, color: "#969FF4", col: 3, row: 6 },
// Column 5 (x=15)
{ x: 15, y: 3, color: "#ABB2FA", col: 4, row: 0 },
{ x: 15, y: 6, color: "#ABB2FA", col: 4, row: 1 },
{ x: 15, y: 9, color: "#969FF4", col: 4, row: 2 },
{ x: 15, y: 12, color: "#969FF4", col: 4, row: 3 },
{ x: 15, y: 15, color: "#969FF4", col: 4, row: 4 },
{ x: 15, y: 18, color: "#969FF4", col: 4, row: 5 },
{ x: 15, y: 21, color: "#969FF4", col: 4, row: 6 },
// Column 6 (x=18)
{ x: 18, y: 3, color: "#ABB2FA", col: 5, row: 0 },
{ x: 18, y: 6, color: "#969FF4", col: 5, row: 1 },
{ x: 18, y: 9, color: "#969FF4", col: 5, row: 2 },
{ x: 18, y: 12, color: "#969FF4", col: 5, row: 3 },
{ x: 18, y: 15, color: "#969FF4", col: 5, row: 4 },
{ x: 18, y: 18, color: "#969FF4", col: 5, row: 5 },
{ x: 18, y: 21, color: "#969FF4", col: 5, row: 6 },
// Column 7 (x=21)
{ x: 21, y: 3, color: "#969FF4", col: 6, row: 0 },
{ x: 21, y: 6, color: "#969FF4", col: 6, row: 1 },
{ x: 21, y: 9, color: "#969FF4", col: 6, row: 2 },
{ x: 21, y: 12, color: "#969FF4", col: 6, row: 3 },
{ x: 21, y: 15, color: "#969FF4", col: 6, row: 4 },
{ x: 21, y: 18, color: "#969FF4", col: 6, row: 5 },
{ x: 21, y: 21, color: "#969FF4", col: 6, row: 6 },
];
interface NetworkAnimationProps {
className?: string;
}
// Deterministic seeded random number generator for consistent SSR/client values
function seededRandom(seed: number): number {
const x = Math.sin(seed) * 10000;
return x - Math.floor(x);
}
// Round to fixed precision to avoid floating-point precision differences
function roundToPrecision(value: number, precision: number = 10): number {
return Math.round(value * Math.pow(10, precision)) / Math.pow(10, precision);
}
// Generate deterministic random values based on index
function getDeterministicValues(index: number) {
const seed1 = index * 0.1;
const seed2 = index * 0.2;
const seed3 = index * 0.3;
const seed4 = index * 0.4;
const seed5 = index * 0.5;
const seed6 = index * 0.6;
return {
duration: roundToPrecision(3 + seededRandom(seed1) * 3, 10), // 3-6 seconds
peakOpacity: roundToPrecision(0.7 + seededRandom(seed2) * 0.3, 10),
baseOpacity: roundToPrecision(0.3 + seededRandom(seed3) * 0.2, 10),
midOpacity: roundToPrecision(0.5 + seededRandom(seed4) * 0.2, 10),
baseBrightness: roundToPrecision(0.85 + seededRandom(seed5) * 0.15, 10),
peakBrightness: roundToPrecision(1.0 + seededRandom(seed6) * 0.2, 10),
};
}
export function NetworkAnimation({ className }: NetworkAnimationProps) {
// Generate unique IDs for gradient and mask to avoid conflicts when multiple instances exist
const gradientId = useId().replace(/:/g, "-");
const maskId = useId().replace(/:/g, "-");
return (
<div className="absolute inset-0 pointer-events-none opacity-100">
<motion.div
className={`absolute
top-[9%] right-[-3%] w-[380px] h-[380px] ${className || ""}`}
initial={{
rotate: 9, // Start at the same rotation as animation to prevent flicker
}}
animate={{
rotate: [9, 10, 9], // Slight breathing rotation
}}
transition={{
duration: 8,
repeat: Infinity,
ease: "easeInOut",
}}
>
<svg
width="100%"
height="100%"
viewBox="0 0 32 32"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<defs>
{/* Gradient mask: transparent at bottom, opaque at top */}
<linearGradient id={gradientId} x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stopColor="white" stopOpacity="1" />
<stop offset="50%" stopColor="white" stopOpacity="0.5" />
<stop offset="100%" stopColor="white" stopOpacity="0" />
</linearGradient>
<mask id={maskId}>
<rect width="26" height="26" fill={`url(#${gradientId})`} />
</mask>
</defs>
<g mask={`url(#${maskId})`}>
{/* Outer border */}
<rect width="26" height="26" fill="#7780D9" />
{/* Inner background */}
<rect x="2" y="2" width="22" height="22" fill="#B9BFFF" />
{/* Animated squares with wave effect */}
{squares.map((square, index) => {
// Use deterministic values based on index for SSR/client consistency
const {
duration,
peakOpacity,
baseOpacity,
midOpacity,
baseBrightness,
peakBrightness,
} = getDeterministicValues(index);
return (
<motion.path
key={`square-${index}`}
d={`M${square.x} ${square.y}H${square.x + 2}V${square.y + 2}H${square.x}V${square.y}Z`}
fill={square.color}
initial={{
opacity: roundToPrecision(baseOpacity, 10),
filter: `brightness(${roundToPrecision(baseBrightness, 10)})`,
}}
animate={{
opacity: [
roundToPrecision(baseOpacity, 10),
roundToPrecision(midOpacity, 10),
roundToPrecision(peakOpacity, 10),
roundToPrecision(midOpacity, 10),
roundToPrecision(baseOpacity, 10),
],
filter: [
`brightness(${roundToPrecision(baseBrightness, 10)})`,
`brightness(${roundToPrecision((baseBrightness + peakBrightness) / 2, 10)})`,
`brightness(${roundToPrecision(peakBrightness, 10)})`,
`brightness(${roundToPrecision((baseBrightness + peakBrightness) / 2, 10)})`,
`brightness(${roundToPrecision(baseBrightness, 10)})`,
],
}}
transition={{
duration: roundToPrecision(duration, 10),
delay: 0, // No delay - instant start
repeat: Infinity,
ease: "easeInOut",
repeatDelay: 0, // No pause between cycles
}}
/>
);
})}
</g>
</svg>
</motion.div>
</div>
);
}