update d2c brands
This commit is contained in:
85
src/components/brands/NetworkBackground.tsx
Normal file
85
src/components/brands/NetworkBackground.tsx
Normal file
@@ -0,0 +1,85 @@
|
||||
"use client";
|
||||
|
||||
import { motion, useReducedMotion } from "framer-motion";
|
||||
|
||||
const NODES = [
|
||||
{ cx: 60, cy: 80 }, { cx: 220, cy: 40 }, { cx: 380, cy: 120 },
|
||||
{ cx: 520, cy: 60 }, { cx: 680, cy: 140 }, { cx: 140, cy: 260 },
|
||||
{ cx: 320, cy: 300 }, { cx: 480, cy: 260 }, { cx: 620, cy: 320 },
|
||||
{ cx: 80, cy: 420 }, { cx: 260, cy: 460 }, { cx: 440, cy: 420 },
|
||||
{ cx: 600, cy: 480 }, { cx: 740, cy: 400 },
|
||||
];
|
||||
|
||||
const LINES: [number, number][] = [
|
||||
[0, 1], [1, 2], [2, 3], [3, 4], [0, 5], [1, 6], [2, 7], [3, 8], [4, 8],
|
||||
[5, 6], [6, 7], [7, 8], [5, 9], [6, 10], [7, 11], [8, 12], [9, 10], [10, 11], [11, 12], [12, 13], [8, 13],
|
||||
];
|
||||
|
||||
const PARTICLES = [
|
||||
{ cx: 100, cy: 500 }, { cx: 260, cy: 80 }, { cx: 420, cy: 520 }, { cx: 580, cy: 100 },
|
||||
{ cx: 720, cy: 480 }, { cx: 40, cy: 250 }, { cx: 380, cy: 200 }, { cx: 660, cy: 260 },
|
||||
];
|
||||
|
||||
/** Ambient network backdrop for Section 1 — nodes, routes, and drifting particles at very low opacity. Purely decorative. */
|
||||
export function NetworkBackground() {
|
||||
const reduce = useReducedMotion();
|
||||
|
||||
return (
|
||||
<div className="pointer-events-none absolute inset-0 overflow-hidden opacity-[0.07]" aria-hidden="true">
|
||||
<motion.div
|
||||
className="absolute left-1/2 top-1/2 h-[520px] w-[520px] -translate-x-1/2 -translate-y-1/2 rounded-full bg-[#6330D6] blur-[130px]"
|
||||
animate={reduce ? undefined : { opacity: [0.45, 0.9, 0.45] }}
|
||||
transition={{ duration: 8, repeat: Infinity, ease: "easeInOut" }}
|
||||
/>
|
||||
<svg
|
||||
className="absolute inset-0 h-full w-full"
|
||||
viewBox="0 0 800 600"
|
||||
fill="none"
|
||||
preserveAspectRatio="xMidYMid slice"
|
||||
>
|
||||
{LINES.map(([a, b], i) => {
|
||||
const n1 = NODES[a];
|
||||
const n2 = NODES[b];
|
||||
return (
|
||||
<motion.line
|
||||
key={`l-${a}-${b}`}
|
||||
x1={n1.cx}
|
||||
y1={n1.cy}
|
||||
x2={n2.cx}
|
||||
y2={n2.cy}
|
||||
stroke="#6330D6"
|
||||
strokeWidth={1}
|
||||
strokeDasharray="4 7"
|
||||
animate={reduce ? undefined : { strokeDashoffset: [0, -44] }}
|
||||
transition={{ duration: 6 + (i % 5), repeat: Infinity, ease: "linear" }}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
{NODES.map((n, i) => (
|
||||
<motion.circle
|
||||
key={`n-${i}`}
|
||||
cx={n.cx}
|
||||
cy={n.cy}
|
||||
fill="#6330D6"
|
||||
animate={reduce ? undefined : { r: [3, 5, 3], opacity: [0.4, 1, 0.4] }}
|
||||
initial={{ r: 3, opacity: 0.6 }}
|
||||
transition={{ duration: 3 + (i % 4), repeat: Infinity, ease: "easeInOut", delay: (i % 5) * 0.4 }}
|
||||
/>
|
||||
))}
|
||||
|
||||
{PARTICLES.map((p, i) => (
|
||||
<motion.circle
|
||||
key={`p-${i}`}
|
||||
cx={p.cx}
|
||||
r={2}
|
||||
fill="#6330D6"
|
||||
initial={{ cy: p.cy, opacity: 0 }}
|
||||
animate={reduce ? undefined : { cy: [p.cy, p.cy - 46, p.cy], opacity: [0, 0.85, 0] }}
|
||||
transition={{ duration: 7 + (i % 4), repeat: Infinity, ease: "easeInOut", delay: i * 0.6 }}
|
||||
/>
|
||||
))}
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
55
src/components/brands/PartnershipConnector.tsx
Normal file
55
src/components/brands/PartnershipConnector.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
"use client";
|
||||
|
||||
import { motion, useReducedMotion } from "framer-motion";
|
||||
|
||||
const EASE = [0.22, 1, 0.36, 1] as [number, number, number, number];
|
||||
|
||||
/** One-shot workflow connector for the Partnership Process — draws once on scroll into view, then a glow dot travels Step 1 → Step 4 and stops. No looping. Desktop-only (aligns to the single-row xl:grid-cols-4 layout). */
|
||||
export function PartnershipConnector({ steps }: { steps: number }) {
|
||||
const reduce = useReducedMotion();
|
||||
const positions = Array.from({ length: steps }, (_, i) => ((i + 0.5) / steps) * 100);
|
||||
const start = positions[0];
|
||||
const end = positions[positions.length - 1];
|
||||
const lineDuration = 0.9;
|
||||
const dotDuration = 1.1;
|
||||
|
||||
return (
|
||||
<div className="relative mb-3 hidden h-6 xl:block" aria-hidden="true">
|
||||
<motion.div
|
||||
className="absolute top-1/2 h-0 origin-left -translate-y-1/2 border-t-2 border-dashed border-[#6330D6]/20"
|
||||
style={{ left: `${start}%`, right: `${100 - end}%` }}
|
||||
initial={{ scaleX: 0 }}
|
||||
whileInView={{ scaleX: 1 }}
|
||||
viewport={{ once: true, amount: 0.4 }}
|
||||
transition={reduce ? { duration: 0 } : { duration: lineDuration, ease: EASE }}
|
||||
/>
|
||||
|
||||
{positions.map((left, i) => (
|
||||
<motion.div
|
||||
key={i}
|
||||
className="absolute top-1/2 h-2.5 w-2.5 -translate-x-1/2 -translate-y-1/2 rounded-full bg-[#6330D6]/40"
|
||||
style={{ left: `${left}%` }}
|
||||
initial={{ opacity: 0, scale: 0.4 }}
|
||||
whileInView={{ opacity: 1, scale: 1 }}
|
||||
viewport={{ once: true, amount: 0.4 }}
|
||||
transition={reduce ? { duration: 0 } : { duration: 0.35, ease: EASE, delay: lineDuration * 0.6 }}
|
||||
/>
|
||||
))}
|
||||
|
||||
<motion.div
|
||||
className="absolute top-1/2 h-2.5 w-2.5 -translate-x-1/2 -translate-y-1/2 rounded-full bg-[#6330D6] shadow-[0_0_14px_4px_rgba(99,48,214,0.55)]"
|
||||
initial={{ left: `${start}%`, opacity: 0 }}
|
||||
whileInView={{ left: `${end}%`, opacity: 1 }}
|
||||
viewport={{ once: true, amount: 0.4 }}
|
||||
transition={
|
||||
reduce
|
||||
? { duration: 0 }
|
||||
: {
|
||||
left: { duration: dotDuration, ease: EASE, delay: lineDuration },
|
||||
opacity: { duration: 0.2, delay: lineDuration },
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user