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 ProxyDetection from '@/components/ProxyDetection'
 | 
			
		||||
import ProxyForwarding from '@/components/ProxyForwarding'
 | 
			
		||||
import ContentDistribution from '@/components/ContentDistribution'
 | 
			
		||||
 | 
			
		||||
export function Features() {
 | 
			
		||||
  return (
 | 
			
		||||
@@ -75,11 +76,7 @@ export function Features() {
 | 
			
		||||
          <div className="relative lg:col-span-2">
 | 
			
		||||
            <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)]">
 | 
			
		||||
              <img
 | 
			
		||||
                alt=""
 | 
			
		||||
                src="https://tailwindcss.com/plus-assets/img/component-images/bento-01-integrations.png"
 | 
			
		||||
                className="h-80 object-cover"
 | 
			
		||||
              />
 | 
			
		||||
              <ProxyForwarding className="h-80" />
 | 
			
		||||
              <div className="p-10 pt-4">
 | 
			
		||||
                <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">
 | 
			
		||||
@@ -96,7 +93,7 @@ export function Features() {
 | 
			
		||||
          <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="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">
 | 
			
		||||
                <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">
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user