feat: replace placeholder image with ProxyForwarding component and add ContentDistribution section
This commit is contained in:
182
src/components/ContentDistribution.tsx
Normal file
182
src/components/ContentDistribution.tsx
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import { motion, useReducedMotion } from 'framer-motion';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
className?: string; // e.g. "w-full h-80"
|
||||||
|
bg?: string; // default white
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Palette */
|
||||||
|
const ACCENT = '#00b8db';
|
||||||
|
const STROKE = '#111827';
|
||||||
|
const GRAY = '#9CA3AF';
|
||||||
|
const GRAY_LT = '#E5E7EB';
|
||||||
|
|
||||||
|
/* ---------- small generic icons (no brands) ---------- */
|
||||||
|
const IconSquare = () => (
|
||||||
|
<rect x={-14} y={-14} width={28} height={28} rx={6} fill={ACCENT} stroke={STROKE} strokeWidth={3} />
|
||||||
|
);
|
||||||
|
const IconTriangle = () => (
|
||||||
|
<path d="M 0 -15 L 14 12 L -14 12 Z" fill="#fff" stroke={STROKE} strokeWidth={3} />
|
||||||
|
);
|
||||||
|
const IconHex = () => (
|
||||||
|
<path
|
||||||
|
d="M 0 -15 L 13 -7 L 13 7 L 0 15 L -13 7 L -13 -7 Z"
|
||||||
|
fill="#fff"
|
||||||
|
stroke={STROKE}
|
||||||
|
strokeWidth={3}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
const IconBolt = () => (
|
||||||
|
<path d="M -5 -14 L 4 -2 L -1 -2 L 5 14 L -6 1 L -1 1 Z" fill={ACCENT} stroke={STROKE} strokeWidth={3} />
|
||||||
|
);
|
||||||
|
const IconPlay = () => (
|
||||||
|
<circle r={15} fill="#fff" stroke={STROKE} strokeWidth={3} />
|
||||||
|
);
|
||||||
|
const IconDB = () => (
|
||||||
|
<>
|
||||||
|
<ellipse cx={0} cy={-10} rx={16} ry={8} fill="#fff" stroke={STROKE} strokeWidth={3} />
|
||||||
|
<rect x={-16} y={-10} width={32} height={20} fill="#fff" stroke={STROKE} strokeWidth={3} />
|
||||||
|
<ellipse cx={0} cy={10} rx={16} ry={8} fill="#fff" stroke={STROKE} strokeWidth={3} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
/* icon inside white circular badge */
|
||||||
|
function Badge({ children }: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<circle r={26} fill="#fff" stroke={GRAY_LT} strokeWidth={3} />
|
||||||
|
<g>{children}</g>
|
||||||
|
<filter id="shadow" x="-200%" y="-200%" width="400%" height="400%">
|
||||||
|
<feDropShadow dx="0" dy="2" stdDeviation="2" floodColor={GRAY} floodOpacity="0.25" />
|
||||||
|
</filter>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- central cloud ---------- */
|
||||||
|
function Cloud({ pulse = true }: { pulse?: boolean }) {
|
||||||
|
const prefersReduced = useReducedMotion();
|
||||||
|
return (
|
||||||
|
<g>
|
||||||
|
<g fill={STROKE}>
|
||||||
|
<circle cx={-18} cy={0} r={14} />
|
||||||
|
<circle cx={0} cy={-10} r={18} />
|
||||||
|
<circle cx={18} cy={0} r={16} />
|
||||||
|
<rect x={-30} y={0} width={54} height={16} rx={8} />
|
||||||
|
</g>
|
||||||
|
{/* subtle accent aura */}
|
||||||
|
<motion.circle
|
||||||
|
r={36}
|
||||||
|
fill="none"
|
||||||
|
stroke={ACCENT}
|
||||||
|
strokeWidth={4}
|
||||||
|
initial={{ opacity: 0.15, scale: 0.9 }}
|
||||||
|
animate={pulse && !prefersReduced ? { opacity: [0.15, 0.35, 0.15], scale: [0.9, 1.05, 0.9] } : {}}
|
||||||
|
transition={{ duration: 1.8, repeat: Infinity }}
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* a small packet line from center to a node */
|
||||||
|
function Beam({
|
||||||
|
x2,
|
||||||
|
y2,
|
||||||
|
delay = 0,
|
||||||
|
}: {
|
||||||
|
x2: number;
|
||||||
|
y2: number;
|
||||||
|
delay?: number;
|
||||||
|
}) {
|
||||||
|
const prefersReduced = useReducedMotion();
|
||||||
|
return (
|
||||||
|
<motion.line
|
||||||
|
x1={0}
|
||||||
|
y1={0}
|
||||||
|
x2={x2}
|
||||||
|
y2={y2}
|
||||||
|
stroke={ACCENT}
|
||||||
|
strokeWidth={4}
|
||||||
|
strokeLinecap="round"
|
||||||
|
initial={{ pathLength: 0, opacity: 0.0 }}
|
||||||
|
animate={{ pathLength: 1, opacity: 0.9 }}
|
||||||
|
transition={{
|
||||||
|
duration: prefersReduced ? 0.01 : 0.9,
|
||||||
|
delay,
|
||||||
|
repeat: prefersReduced ? 0 : Infinity,
|
||||||
|
repeatDelay: 1.2,
|
||||||
|
repeatType: 'reverse',
|
||||||
|
ease: [0.22, 1, 0.36, 1],
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ContentDistribution({ className, bg = '#ffffff' }: Props) {
|
||||||
|
const W = 900;
|
||||||
|
const H = 560;
|
||||||
|
|
||||||
|
// ring radii
|
||||||
|
const rings = [110, 190, 270];
|
||||||
|
|
||||||
|
// positions (angle degrees) for badges on rings
|
||||||
|
const layout = [
|
||||||
|
{ r: rings[1], a: -20, icon: <IconSquare /> },
|
||||||
|
{ r: rings[2], a: 20, icon: <IconTriangle /> },
|
||||||
|
{ r: rings[0], a: 155, icon: <IconHex /> },
|
||||||
|
{ r: rings[2], a: -145, icon: <IconBolt /> },
|
||||||
|
{ r: rings[1], a: 210, icon: <IconDB /> },
|
||||||
|
{ r: rings[0], a: 60, icon: <IconPlay /> },
|
||||||
|
];
|
||||||
|
|
||||||
|
const prefersReduced = useReducedMotion();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={className} aria-hidden="true" role="img" style={{ background: bg }}>
|
||||||
|
<svg viewBox={`0 0 ${W} ${H}`} width="100%" height="100%">
|
||||||
|
{/* subtle radial background + rings */}
|
||||||
|
<defs>
|
||||||
|
<radialGradient id="fade" cx="50%" cy="50%" r="60%">
|
||||||
|
<stop offset="0%" stopColor="#ffffff" />
|
||||||
|
<stop offset="100%" stopColor="#ffffff" />
|
||||||
|
</radialGradient>
|
||||||
|
</defs>
|
||||||
|
<rect width={W} height={H} fill="url(#fade)" />
|
||||||
|
<g transform={`translate(${W / 2}, ${H / 2})`}>
|
||||||
|
{rings.map((r, i) => (
|
||||||
|
<circle key={i} r={r} fill="none" stroke={GRAY_LT} strokeWidth={2} />
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* central cloud */}
|
||||||
|
<Cloud />
|
||||||
|
|
||||||
|
{/* rotating layer with badges */}
|
||||||
|
<motion.g
|
||||||
|
initial={{ rotate: 0 }}
|
||||||
|
animate={{ rotate: prefersReduced ? 0 : 360 }}
|
||||||
|
transition={{ duration: 40, ease: 'linear', repeat: prefersReduced ? 0 : Infinity }}
|
||||||
|
>
|
||||||
|
{layout.map((n, i) => {
|
||||||
|
const rad = (n.a * Math.PI) / 180;
|
||||||
|
const x = n.r * Math.cos(rad);
|
||||||
|
const y = n.r * Math.sin(rad);
|
||||||
|
return (
|
||||||
|
<g key={i} transform={`translate(${x}, ${y})`} filter="url(#shadow)">
|
||||||
|
<circle r={34} fill="#fff" stroke={GRAY_LT} strokeWidth={3} />
|
||||||
|
<g transform="scale(1)">
|
||||||
|
{n.icon}
|
||||||
|
</g>
|
||||||
|
{/* beam from center to this node (animated) */}
|
||||||
|
<Beam x2={-x} y2={-y} delay={i * 0.15} />
|
||||||
|
</g>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</motion.g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@@ -2,6 +2,7 @@ import Pathfinding from '@/components/Pathfinding'
|
|||||||
import MessageBus from '@/components/MessageBus'
|
import MessageBus from '@/components/MessageBus'
|
||||||
import ProxyDetection from '@/components/ProxyDetection'
|
import ProxyDetection from '@/components/ProxyDetection'
|
||||||
import ProxyForwarding from '@/components/ProxyForwarding'
|
import ProxyForwarding from '@/components/ProxyForwarding'
|
||||||
|
import ContentDistribution from '@/components/ContentDistribution'
|
||||||
|
|
||||||
export function Features() {
|
export function Features() {
|
||||||
return (
|
return (
|
||||||
@@ -75,11 +76,7 @@ export function Features() {
|
|||||||
<div className="relative lg:col-span-2">
|
<div className="relative lg:col-span-2">
|
||||||
<div className="absolute inset-0 rounded-lg bg-white" />
|
<div className="absolute inset-0 rounded-lg bg-white" />
|
||||||
<div className="relative flex h-full flex-col overflow-hidden rounded-[calc(var(--radius-lg)+1px)]">
|
<div className="relative flex h-full flex-col overflow-hidden rounded-[calc(var(--radius-lg)+1px)]">
|
||||||
<img
|
<ProxyForwarding className="h-80" />
|
||||||
alt=""
|
|
||||||
src="https://tailwindcss.com/plus-assets/img/component-images/bento-01-integrations.png"
|
|
||||||
className="h-80 object-cover"
|
|
||||||
/>
|
|
||||||
<div className="p-10 pt-4">
|
<div className="p-10 pt-4">
|
||||||
<h3 className="text-sm/4 font-semibold text-cyan-500">Connectivity</h3>
|
<h3 className="text-sm/4 font-semibold text-cyan-500">Connectivity</h3>
|
||||||
<p className="mt-2 text-lg font-medium tracking-tight text-gray-950">
|
<p className="mt-2 text-lg font-medium tracking-tight text-gray-950">
|
||||||
@@ -96,7 +93,7 @@ export function Features() {
|
|||||||
<div className="relative lg:col-span-2">
|
<div className="relative lg:col-span-2">
|
||||||
<div className="absolute inset-0 rounded-lg bg-white max-lg:rounded-b-4xl lg:rounded-br-4xl" />
|
<div className="absolute inset-0 rounded-lg bg-white max-lg:rounded-b-4xl lg:rounded-br-4xl" />
|
||||||
<div className="relative flex h-full flex-col overflow-hidden rounded-[calc(var(--radius-lg)+1px)] max-lg:rounded-b-[calc(2rem+1px)] lg:rounded-br-[calc(2rem+1px)]">
|
<div className="relative flex h-full flex-col overflow-hidden rounded-[calc(var(--radius-lg)+1px)] max-lg:rounded-b-[calc(2rem+1px)] lg:rounded-br-[calc(2rem+1px)]">
|
||||||
<ProxyForwarding />
|
<ContentDistribution className="h-80" />
|
||||||
<div className="p-10 pt-4">
|
<div className="p-10 pt-4">
|
||||||
<h3 className="text-sm/4 font-semibold text-cyan-500">Delivery</h3>
|
<h3 className="text-sm/4 font-semibold text-cyan-500">Delivery</h3>
|
||||||
<p className="mt-2 text-lg font-medium tracking-tight text-gray-950">
|
<p className="mt-2 text-lg font-medium tracking-tight text-gray-950">
|
||||||
|
Reference in New Issue
Block a user