forked from ourworld_web/www_engage_os
		
	add
This commit is contained in:
		
							
								
								
									
										18
									
								
								src/components/Apps.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/components/Apps.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
"use client";
 | 
			
		||||
 | 
			
		||||
import React from "react";
 | 
			
		||||
import { Button } from "@/components/Button";
 | 
			
		||||
 | 
			
		||||
export function AppsPreview() {
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="relative flex h-[40rem] w-full overflow-hidden rounded-md bg-transparent antialiased md:items-center md:justify-center">
 | 
			
		||||
      <div className="relative z-10 mx-auto w-full max-w-4xl p-4 pt-20 md:pt-0">
 | 
			
		||||
        <div className="flex flex-col justify-center items-center mb-6">
 | 
			
		||||
          <h1 className="bg-opacity-50 bg-gradient-to-b from-neutral-50 to-neutral-400 bg-clip-text tracking-tighter text-center text-4xl font-semibold text-transparent lg:text-6xl">
 | 
			
		||||
            Anything That Runs on Linux Can Run on ThreeFold
 | 
			
		||||
          </h1>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										118
									
								
								src/components/Dashboard.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								src/components/Dashboard.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,118 @@
 | 
			
		||||
"use client";
 | 
			
		||||
 | 
			
		||||
import CountUp from "react-countup";
 | 
			
		||||
import React from "react";
 | 
			
		||||
 | 
			
		||||
export function Dashboard() {
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="py-24 bg-transparent">
 | 
			
		||||
      <div className="mx-auto max-w-2xl px-6 lg:max-w-7xl lg:px-8">
 | 
			
		||||
        <div className="grid grid-cols-1 gap-8 lg:grid-cols-3">
 | 
			
		||||
          {/* Column 1: Title & NODES */}
 | 
			
		||||
          <div className="flex flex-col space-y-10">
 | 
			
		||||
            {/* Title + Description */}
 | 
			
		||||
            <div>
 | 
			
		||||
              <h2 className="text-2xl font-semibold tracking-tight leading-tight text-white lg:text-4xl">
 | 
			
		||||
                Powered by a Global Community
 | 
			
		||||
              </h2>
 | 
			
		||||
              <p className="mt-4 sm:mt-6 text-sm font-light text-pretty text-white lg:text-base">
 | 
			
		||||
                ThreeFold’s groundbreaking technology enables anyone – individuals, organizations, and communities – to deploy their own Internet infrastructure.
 | 
			
		||||
              </p>
 | 
			
		||||
              <button className="mt-6" variant="primary" color="transparent" href="https://threefold.io/build" >Explore TFGrid →</button>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
          {/* Column 2: CORES (staggered) + SSD */}
 | 
			
		||||
          <div className="flex flex-col space-y-10">
 | 
			
		||||
            <StatCard
 | 
			
		||||
              label="CORES"
 | 
			
		||||
              description="A globally distributed mesh of CPU cores powering decentralized applications, AI workloads, and edge computing — without central bottlenecks."
 | 
			
		||||
              value={<CountUp end={54_958} duration={2.5} separator="," />}
 | 
			
		||||
              note="Total Central Processing Unit Cores available on the grid."
 | 
			
		||||
              className="mt-24"
 | 
			
		||||
            />
 | 
			
		||||
 | 
			
		||||
            <StatCard
 | 
			
		||||
              label="SSD CAPACITY"
 | 
			
		||||
              description="A distributed network of storage capacity — ready to support Web3, AI, and edge computing workloads around the world."
 | 
			
		||||
              value={<CountUp end={7_364_506} duration={2.5} separator="," />}
 | 
			
		||||
              unit="GB"
 | 
			
		||||
              note="The total amount of storage (SSD, HDD, & RAM) on the grid."
 | 
			
		||||
            />
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
          
 | 
			
		||||
 | 
			
		||||
          {/* Column 3: nodes countries */}
 | 
			
		||||
          <div className="flex flex-col space-y-10 justify-start">
 | 
			
		||||
            <StatCard
 | 
			
		||||
              label="NODES"
 | 
			
		||||
              description="A computer server 100% dedicated to the network. It is a building block of the ThreeFold Grid, providing compute, storage, and network resources."
 | 
			
		||||
              value={<CountUp end={1778} duration={2.5} separator="," />}
 | 
			
		||||
              note="The total number of nodes on the grid."
 | 
			
		||||
 | 
			
		||||
            />
 | 
			
		||||
 | 
			
		||||
            <StatCard
 | 
			
		||||
              label="COUNTRIES"
 | 
			
		||||
              description="The number of countries where at least one node is connected and operational on the grid."
 | 
			
		||||
              value={<CountUp end={51} duration={2.5} separator="," />}
 | 
			
		||||
              note="The total number of countries with active nodes."
 | 
			
		||||
            />
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 🧱 Stat Card Component
 | 
			
		||||
function StatCard({
 | 
			
		||||
  label,
 | 
			
		||||
  description,
 | 
			
		||||
  value,
 | 
			
		||||
  unit,
 | 
			
		||||
  note,
 | 
			
		||||
  className = "",
 | 
			
		||||
}: {
 | 
			
		||||
  label: string;
 | 
			
		||||
  description: string;
 | 
			
		||||
  value: React.ReactNode;
 | 
			
		||||
  unit?: string;
 | 
			
		||||
  note: string;
 | 
			
		||||
  className?: string;
 | 
			
		||||
}) {
 | 
			
		||||
  return (
 | 
			
		||||
    <div
 | 
			
		||||
      className={`relative flex flex-col overflow-hidden rounded-2xl bg-stat-gradient p-8 shadow-sm backdrop-blur transition-all duration-300 ease-out hover:scale-105 ${className}`}
 | 
			
		||||
      style={{
 | 
			
		||||
        filter: 'brightness(1)',
 | 
			
		||||
      }}
 | 
			
		||||
      onMouseEnter={(e) => {
 | 
			
		||||
        e.currentTarget.style.filter = 'brightness(0.8) drop-shadow(0 0 20px rgba(156, 163, 175, 0.5))';
 | 
			
		||||
      }}
 | 
			
		||||
      onMouseLeave={(e) => {
 | 
			
		||||
        e.currentTarget.style.filter = 'brightness(1)';
 | 
			
		||||
      }}
 | 
			
		||||
    >
 | 
			
		||||
      <h3 className="text-lg font-semibold text-cyan-400">{label}</h3>
 | 
			
		||||
      <p className="mt-2 text-sm font-light text-pretty text-white lg:text-base">
 | 
			
		||||
        {description}
 | 
			
		||||
      </p>
 | 
			
		||||
      <div className="mt-8 flex items-center space-x-3">
 | 
			
		||||
        <span className="text-cyan-400 text-3xl">•</span>
 | 
			
		||||
        <div className="text-5xl font-semibold tracking-tight text-white tabular-nums">
 | 
			
		||||
          {value}
 | 
			
		||||
          {unit && (
 | 
			
		||||
            <span className="ml-2 text-lg font-normal text-gray-400">{unit}</span>
 | 
			
		||||
          )}
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      <p className="mt-2 text-sm text-gray-400 uppercase tracking-wider">
 | 
			
		||||
        {note}
 | 
			
		||||
      </p>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										121
									
								
								src/components/Globe.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								src/components/Globe.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,121 @@
 | 
			
		||||
"use client";
 | 
			
		||||
 | 
			
		||||
import createGlobe, { COBEOptions } from "cobe";
 | 
			
		||||
import { useMotionValue, useSpring } from "framer-motion";
 | 
			
		||||
import { useEffect, useRef } from "react";
 | 
			
		||||
 | 
			
		||||
const MOVEMENT_DAMPING = 1400;
 | 
			
		||||
 | 
			
		||||
const GLOBE_CONFIG: COBEOptions = {
 | 
			
		||||
  width: 800,
 | 
			
		||||
  height: 800,
 | 
			
		||||
  onRender: () => {},
 | 
			
		||||
  devicePixelRatio: 2,
 | 
			
		||||
  phi: 0,
 | 
			
		||||
  theta: 0.3,
 | 
			
		||||
  dark: 1,
 | 
			
		||||
  diffuse: 1.2,
 | 
			
		||||
  mapSamples: 16000,
 | 
			
		||||
  mapBrightness: 0.5,
 | 
			
		||||
  baseColor: [1, 1, 1], // tailwind gray-700
 | 
			
		||||
  markerColor: [1, 1, 1], // white dots
 | 
			
		||||
  glowColor: [0.6, 0.6, 0.6],
 | 
			
		||||
  markers: [
 | 
			
		||||
    { location: [14.5995, 120.9842], size: 0.03 },
 | 
			
		||||
    { location: [19.076, 72.8777], size: 0.1 },
 | 
			
		||||
    { location: [23.8103, 90.4125], size: 0.05 },
 | 
			
		||||
    { location: [30.0444, 31.2357], size: 0.07 },
 | 
			
		||||
    { location: [39.9042, 116.4074], size: 0.08 },
 | 
			
		||||
    { location: [-23.5505, -46.6333], size: 0.1 },
 | 
			
		||||
    { location: [19.4326, -99.1332], size: 0.1 },
 | 
			
		||||
    { location: [40.7128, -74.006], size: 0.1 },
 | 
			
		||||
    { location: [34.6937, 135.5022], size: 0.05 },
 | 
			
		||||
    { location: [41.0082, 28.9784], size: 0.06 },
 | 
			
		||||
  ],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export function Globe({
 | 
			
		||||
  className,
 | 
			
		||||
  config = GLOBE_CONFIG,
 | 
			
		||||
}: {
 | 
			
		||||
  className?: string;
 | 
			
		||||
  config?: COBEOptions;
 | 
			
		||||
}) {
 | 
			
		||||
  let phi = 0;
 | 
			
		||||
  let width = 0;
 | 
			
		||||
  const canvasRef = useRef<HTMLCanvasElement>(null);
 | 
			
		||||
  const pointerInteracting = useRef<number | null>(null);
 | 
			
		||||
  const pointerInteractionMovement = useRef(0);
 | 
			
		||||
 | 
			
		||||
  const r = useMotionValue(0);
 | 
			
		||||
  const rs = useSpring(r, {
 | 
			
		||||
    mass: 1,
 | 
			
		||||
    damping: 30,
 | 
			
		||||
    stiffness: 100,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const updatePointerInteraction = (value: number | null) => {
 | 
			
		||||
    pointerInteracting.current = value;
 | 
			
		||||
    if (canvasRef.current) {
 | 
			
		||||
      canvasRef.current.style.cursor = value !== null ? "grabbing" : "grab";
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const updateMovement = (clientX: number) => {
 | 
			
		||||
    if (pointerInteracting.current !== null) {
 | 
			
		||||
      const delta = clientX - pointerInteracting.current;
 | 
			
		||||
      pointerInteractionMovement.current = delta;
 | 
			
		||||
      r.set(r.get() + delta / MOVEMENT_DAMPING);
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const onResize = () => {
 | 
			
		||||
      if (canvasRef.current) {
 | 
			
		||||
        width = canvasRef.current.offsetWidth;
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    window.addEventListener("resize", onResize);
 | 
			
		||||
    onResize();
 | 
			
		||||
 | 
			
		||||
    const globe = createGlobe(canvasRef.current!, {
 | 
			
		||||
      ...config,
 | 
			
		||||
      width: width * 2,
 | 
			
		||||
      height: width * 2,
 | 
			
		||||
      onRender: (state) => {
 | 
			
		||||
        if (!pointerInteracting.current) phi += 0.005;
 | 
			
		||||
        state.phi = phi + rs.get();
 | 
			
		||||
        state.width = width * 2;
 | 
			
		||||
        state.height = width * 2;
 | 
			
		||||
      },
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    setTimeout(() => (canvasRef.current!.style.opacity = "1"), 0);
 | 
			
		||||
    return () => {
 | 
			
		||||
      globe.destroy();
 | 
			
		||||
      window.removeEventListener("resize", onResize);
 | 
			
		||||
    };
 | 
			
		||||
  }, [rs, config]);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div
 | 
			
		||||
      className={`absolute inset-0 mx-auto aspect-[1/1] w-full max-w-[600px] ${className}`}
 | 
			
		||||
    >
 | 
			
		||||
      <canvas
 | 
			
		||||
        className="size-full opacity-0 transition-opacity duration-500 [contain:layout_paint_size]"
 | 
			
		||||
        ref={canvasRef}
 | 
			
		||||
        onPointerDown={(e) => {
 | 
			
		||||
          pointerInteracting.current = e.clientX;
 | 
			
		||||
          updatePointerInteraction(e.clientX);
 | 
			
		||||
        }}
 | 
			
		||||
        onPointerUp={() => updatePointerInteraction(null)}
 | 
			
		||||
        onPointerOut={() => updatePointerInteraction(null)}
 | 
			
		||||
        onMouseMove={(e) => updateMovement(e.clientX)}
 | 
			
		||||
        onTouchMove={(e) =>
 | 
			
		||||
          e.touches[0] && updateMovement(e.touches[0].clientX)
 | 
			
		||||
        }
 | 
			
		||||
      />
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
@@ -1,111 +1,9 @@
 | 
			
		||||
"use client";
 | 
			
		||||
import React from "react";
 | 
			
		||||
import { motion } from "framer-motion";
 | 
			
		||||
import { Globe } from "@/components/Globe";
 | 
			
		||||
 | 
			
		||||
export function GlobeDemo() {
 | 
			
		||||
export default function GlobeDemo() {
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="flex flex-row items-center justify-center py-20 h-screen md:h-auto dark:bg-black bg-white relative w-full">
 | 
			
		||||
      <div className="max-w-7xl mx-auto w-full relative overflow-hidden h-full md:h-[40rem] px-4">
 | 
			
		||||
        <motion.div
 | 
			
		||||
          initial={{
 | 
			
		||||
            opacity: 0,
 | 
			
		||||
            y: 20,
 | 
			
		||||
          }}
 | 
			
		||||
          animate={{
 | 
			
		||||
            opacity: 1,
 | 
			
		||||
            y: 0,
 | 
			
		||||
          }}
 | 
			
		||||
          transition={{
 | 
			
		||||
            duration: 1,
 | 
			
		||||
          }}
 | 
			
		||||
          className="div"
 | 
			
		||||
        >
 | 
			
		||||
          <h2 className="text-center text-xl md:text-4xl font-bold text-black dark:text-white">
 | 
			
		||||
            We sell soap worldwide
 | 
			
		||||
          </h2>
 | 
			
		||||
          <p className="text-center text-base md:text-lg font-normal text-neutral-700 dark:text-neutral-200 max-w-md mt-2 mx-auto">
 | 
			
		||||
            This globe is interactive and customizable. Have fun with it, and
 | 
			
		||||
            don't forget to share it. :)
 | 
			
		||||
          </p>
 | 
			
		||||
        </motion.div>
 | 
			
		||||
        
 | 
			
		||||
        {/* Simple CSS Globe */}
 | 
			
		||||
        <div className="absolute w-full -bottom-20 h-72 md:h-full z-10 flex items-center justify-center">
 | 
			
		||||
          <div className="relative">
 | 
			
		||||
            {/* Globe sphere */}
 | 
			
		||||
            <motion.div
 | 
			
		||||
              className="w-64 h-64 md:w-80 md:h-80 rounded-full bg-gradient-to-br from-blue-900 via-blue-700 to-blue-500 relative overflow-hidden shadow-2xl"
 | 
			
		||||
              animate={{ 
 | 
			
		||||
                rotateY: 360,
 | 
			
		||||
              }}
 | 
			
		||||
              transition={{
 | 
			
		||||
                duration: 20,
 | 
			
		||||
                repeat: Infinity,
 | 
			
		||||
                ease: "linear"
 | 
			
		||||
              }}
 | 
			
		||||
            >
 | 
			
		||||
              {/* Globe grid lines */}
 | 
			
		||||
              <div className="absolute inset-0">
 | 
			
		||||
                {/* Horizontal lines */}
 | 
			
		||||
                {[...Array(8)].map((_, i) => (
 | 
			
		||||
                  <div
 | 
			
		||||
                    key={`h-${i}`}
 | 
			
		||||
                    className="absolute w-full border-t border-blue-300/30"
 | 
			
		||||
                    style={{ top: `${(i + 1) * 12.5}%` }}
 | 
			
		||||
                  />
 | 
			
		||||
                ))}
 | 
			
		||||
                {/* Vertical lines */}
 | 
			
		||||
                {[...Array(12)].map((_, i) => (
 | 
			
		||||
                  <div
 | 
			
		||||
                    key={`v-${i}`}
 | 
			
		||||
                    className="absolute h-full border-l border-blue-300/30"
 | 
			
		||||
                    style={{ left: `${(i + 1) * 8.33}%` }}
 | 
			
		||||
                  />
 | 
			
		||||
                ))}
 | 
			
		||||
              </div>
 | 
			
		||||
              
 | 
			
		||||
              {/* Continents (simplified shapes) */}
 | 
			
		||||
              <div className="absolute top-8 left-12 w-16 h-12 bg-green-600/60 rounded-lg transform rotate-12"></div>
 | 
			
		||||
              <div className="absolute top-16 right-8 w-12 h-8 bg-green-600/60 rounded-full"></div>
 | 
			
		||||
              <div className="absolute bottom-12 left-8 w-20 h-16 bg-green-600/60 rounded-2xl transform -rotate-6"></div>
 | 
			
		||||
              <div className="absolute bottom-8 right-12 w-14 h-10 bg-green-600/60 rounded-lg"></div>
 | 
			
		||||
              
 | 
			
		||||
              {/* Glow effect */}
 | 
			
		||||
              <div className="absolute inset-0 rounded-full bg-gradient-to-r from-transparent via-white/10 to-transparent"></div>
 | 
			
		||||
            </motion.div>
 | 
			
		||||
            
 | 
			
		||||
            {/* Orbiting dots representing connections */}
 | 
			
		||||
            {[...Array(6)].map((_, i) => (
 | 
			
		||||
              <motion.div
 | 
			
		||||
                key={i}
 | 
			
		||||
                className="absolute w-2 h-2 bg-cyan-400 rounded-full"
 | 
			
		||||
                style={{
 | 
			
		||||
                  top: "50%",
 | 
			
		||||
                  left: "50%",
 | 
			
		||||
                }}
 | 
			
		||||
                animate={{
 | 
			
		||||
                  rotate: 360,
 | 
			
		||||
                }}
 | 
			
		||||
                transition={{
 | 
			
		||||
                  duration: 8 + i * 2,
 | 
			
		||||
                  repeat: Infinity,
 | 
			
		||||
                  ease: "linear",
 | 
			
		||||
                  delay: i * 0.5,
 | 
			
		||||
                }}
 | 
			
		||||
              >
 | 
			
		||||
                <div 
 | 
			
		||||
                  className="w-2 h-2 bg-cyan-400 rounded-full shadow-lg shadow-cyan-400/50"
 | 
			
		||||
                  style={{
 | 
			
		||||
                    transform: `translate(-50%, -50%) translateX(${120 + i * 20}px)`,
 | 
			
		||||
                  }}
 | 
			
		||||
                />
 | 
			
		||||
              </motion.div>
 | 
			
		||||
            ))}
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        
 | 
			
		||||
        <div className="absolute w-full bottom-0 inset-x-0 h-40 bg-gradient-to-b pointer-events-none select-none from-transparent dark:to-black to-white z-40" />
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <main className="relative min-h-screen bg-transparent flex items-center justify-center">
 | 
			
		||||
      <Globe />
 | 
			
		||||
    </main>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -44,9 +44,6 @@ export function SpotlightPreview() {
 | 
			
		||||
          <Button href="#" variant="outline" color="gray">
 | 
			
		||||
            Start Hosting
 | 
			
		||||
          </Button>
 | 
			
		||||
          <Button href="#" variant="solid" color="white">
 | 
			
		||||
            How it Works →
 | 
			
		||||
          </Button>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,10 @@ import { StackedCubes } from "@/components/ui/StackedCubes";
 | 
			
		||||
export function StackSectionPreview() {
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <section className="w-full bg-transparent px-4 py-8 sm:px-6 sm:pb-12 lg:px-8">
 | 
			
		||||
    <section className="w-full bg-transparent px-4 py-8 sm:px-6 sm:pb-12 lg:px-8 relative">
 | 
			
		||||
    {/* Gradient Blob Component */}
 | 
			
		||||
      <div className="absolute w-[400px] h-[200px] bg-gradient-to-br from-[#505050] to-[#7e7e7e] opacity-40 rounded-full blur-[150px] bottom-[200px] left-[-150px] z-0" />
 | 
			
		||||
      <div className="absolute w-[200px] h-[100px] bg-gradient-to-br from-[#505050] to-[#7e7e7e] opacity-50 rounded-full blur-[150px] top-[200px] right-[-150px] z-0" />
 | 
			
		||||
      <div className="mx-auto max-w-7xl">
 | 
			
		||||
        <div className="grid grid-cols-1 lg:grid-cols-3 gap-4 lg:gap-16 items-center lg:items-start">
 | 
			
		||||
          {/* Left Column - Text (1/3 width) */}
 | 
			
		||||
@@ -13,9 +16,10 @@ export function StackSectionPreview() {
 | 
			
		||||
            <h2 className="text-xl sm:text-2xl font-semibold tracking-tight leading-tight text-white lg:text-3xl">
 | 
			
		||||
              A Decentralized Infrastructure Layer
 | 
			
		||||
            </h2>
 | 
			
		||||
            <p className="mt-4 sm:mt-6 text-sm font-light text-pretty text-gray-700 lg:text-base">
 | 
			
		||||
            <p className="mt-4 sm:mt-6 text-sm font-light text-pretty text-white lg:text-base">
 | 
			
		||||
              We have built a foundational platform that runs directly on bare metal, offering a scalable solution focused on the essential building blocks of the Internet and Cloud: compute, data, and network.
 | 
			
		||||
            </p>
 | 
			
		||||
            <button className="mt-4" variant="primary" color="transparent" href="https://threefold.io/build" >Discover How It Works →</button>
 | 
			
		||||
          </div>
 | 
			
		||||
          
 | 
			
		||||
          {/* Right Column - Stacked Cubes (2/3 width) */}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,309 +0,0 @@
 | 
			
		||||
"use client";
 | 
			
		||||
import { useEffect, useRef, useState } from "react";
 | 
			
		||||
import { Color, Scene, Fog, PerspectiveCamera, Vector3 } from "three";
 | 
			
		||||
import ThreeGlobe from "three-globe";
 | 
			
		||||
import { useThree, Canvas, extend } from "@react-three/fiber";
 | 
			
		||||
import { OrbitControls } from "@react-three/drei";
 | 
			
		||||
import countries from "@/data/globe.json";
 | 
			
		||||
declare module "@react-three/fiber" {
 | 
			
		||||
  interface ThreeElements {
 | 
			
		||||
    threeGlobe: ThreeElements["mesh"] & {
 | 
			
		||||
      new (): ThreeGlobe;
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
extend({ ThreeGlobe: ThreeGlobe });
 | 
			
		||||
 | 
			
		||||
const RING_PROPAGATION_SPEED = 3;
 | 
			
		||||
const aspect = 1.2;
 | 
			
		||||
const cameraZ = 300;
 | 
			
		||||
 | 
			
		||||
type Position = {
 | 
			
		||||
  order: number;
 | 
			
		||||
  startLat: number;
 | 
			
		||||
  startLng: number;
 | 
			
		||||
  endLat: number;
 | 
			
		||||
  endLng: number;
 | 
			
		||||
  arcAlt: number;
 | 
			
		||||
  color: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type GlobeConfig = {
 | 
			
		||||
  pointSize?: number;
 | 
			
		||||
  globeColor?: string;
 | 
			
		||||
  showAtmosphere?: boolean;
 | 
			
		||||
  atmosphereColor?: string;
 | 
			
		||||
  atmosphereAltitude?: number;
 | 
			
		||||
  emissive?: string;
 | 
			
		||||
  emissiveIntensity?: number;
 | 
			
		||||
  shininess?: number;
 | 
			
		||||
  polygonColor?: string;
 | 
			
		||||
  ambientLight?: string;
 | 
			
		||||
  directionalLeftLight?: string;
 | 
			
		||||
  directionalTopLight?: string;
 | 
			
		||||
  pointLight?: string;
 | 
			
		||||
  arcTime?: number;
 | 
			
		||||
  arcLength?: number;
 | 
			
		||||
  rings?: number;
 | 
			
		||||
  maxRings?: number;
 | 
			
		||||
  initialPosition?: {
 | 
			
		||||
    lat: number;
 | 
			
		||||
    lng: number;
 | 
			
		||||
  };
 | 
			
		||||
  autoRotate?: boolean;
 | 
			
		||||
  autoRotateSpeed?: number;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
interface WorldProps {
 | 
			
		||||
  globeConfig: GlobeConfig;
 | 
			
		||||
  data: Position[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
let numbersOfRings = [0];
 | 
			
		||||
 | 
			
		||||
export function Globe({ globeConfig, data }: WorldProps) {
 | 
			
		||||
  const globeRef = useRef<ThreeGlobe | null>(null);
 | 
			
		||||
  const groupRef = useRef();
 | 
			
		||||
  const [isInitialized, setIsInitialized] = useState(false);
 | 
			
		||||
 | 
			
		||||
  const defaultProps = {
 | 
			
		||||
    pointSize: 1,
 | 
			
		||||
    atmosphereColor: "#ffffff",
 | 
			
		||||
    showAtmosphere: true,
 | 
			
		||||
    atmosphereAltitude: 0.1,
 | 
			
		||||
    polygonColor: "rgba(255,255,255,0.7)",
 | 
			
		||||
    globeColor: "#1d072e",
 | 
			
		||||
    emissive: "#000000",
 | 
			
		||||
    emissiveIntensity: 0.1,
 | 
			
		||||
    shininess: 0.9,
 | 
			
		||||
    arcTime: 2000,
 | 
			
		||||
    arcLength: 0.9,
 | 
			
		||||
    rings: 1,
 | 
			
		||||
    maxRings: 3,
 | 
			
		||||
    ...globeConfig,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  // Initialize globe only once
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!globeRef.current && groupRef.current) {
 | 
			
		||||
      globeRef.current = new ThreeGlobe();
 | 
			
		||||
      (groupRef.current as any).add(globeRef.current);
 | 
			
		||||
      setIsInitialized(true);
 | 
			
		||||
    }
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  // Build material when globe is initialized or when relevant props change
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!globeRef.current || !isInitialized) return;
 | 
			
		||||
 | 
			
		||||
    const globeMaterial = globeRef.current.globeMaterial() as unknown as {
 | 
			
		||||
      color: Color;
 | 
			
		||||
      emissive: Color;
 | 
			
		||||
      emissiveIntensity: number;
 | 
			
		||||
      shininess: number;
 | 
			
		||||
    };
 | 
			
		||||
    globeMaterial.color = new Color(globeConfig.globeColor);
 | 
			
		||||
    globeMaterial.emissive = new Color(globeConfig.emissive);
 | 
			
		||||
    globeMaterial.emissiveIntensity = globeConfig.emissiveIntensity || 0.1;
 | 
			
		||||
    globeMaterial.shininess = globeConfig.shininess || 0.9;
 | 
			
		||||
  }, [
 | 
			
		||||
    isInitialized,
 | 
			
		||||
    globeConfig.globeColor,
 | 
			
		||||
    globeConfig.emissive,
 | 
			
		||||
    globeConfig.emissiveIntensity,
 | 
			
		||||
    globeConfig.shininess,
 | 
			
		||||
  ]);
 | 
			
		||||
 | 
			
		||||
  // Build data when globe is initialized or when data changes
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!globeRef.current || !isInitialized || !data) return;
 | 
			
		||||
 | 
			
		||||
    const arcs = data;
 | 
			
		||||
    let points = [];
 | 
			
		||||
    for (let i = 0; i < arcs.length; i++) {
 | 
			
		||||
      const arc = arcs[i];
 | 
			
		||||
      const rgb = hexToRgb(arc.color) as { r: number; g: number; b: number };
 | 
			
		||||
      points.push({
 | 
			
		||||
        size: defaultProps.pointSize,
 | 
			
		||||
        order: arc.order,
 | 
			
		||||
        color: arc.color,
 | 
			
		||||
        lat: arc.startLat,
 | 
			
		||||
        lng: arc.startLng,
 | 
			
		||||
      });
 | 
			
		||||
      points.push({
 | 
			
		||||
        size: defaultProps.pointSize,
 | 
			
		||||
        order: arc.order,
 | 
			
		||||
        color: arc.color,
 | 
			
		||||
        lat: arc.endLat,
 | 
			
		||||
        lng: arc.endLng,
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // remove duplicates for same lat and lng
 | 
			
		||||
    const filteredPoints = points.filter(
 | 
			
		||||
      (v, i, a) =>
 | 
			
		||||
        a.findIndex((v2) =>
 | 
			
		||||
          ["lat", "lng"].every(
 | 
			
		||||
            (k) => v2[k as "lat" | "lng"] === v[k as "lat" | "lng"],
 | 
			
		||||
          ),
 | 
			
		||||
        ) === i,
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    globeRef.current
 | 
			
		||||
      .hexPolygonsData(countries.features)
 | 
			
		||||
      .hexPolygonResolution(3)
 | 
			
		||||
      .hexPolygonMargin(0.7)
 | 
			
		||||
      .showAtmosphere(defaultProps.showAtmosphere)
 | 
			
		||||
      .atmosphereColor(defaultProps.atmosphereColor)
 | 
			
		||||
      .atmosphereAltitude(defaultProps.atmosphereAltitude)
 | 
			
		||||
      .hexPolygonColor(() => defaultProps.polygonColor);
 | 
			
		||||
 | 
			
		||||
    globeRef.current
 | 
			
		||||
      .arcsData(data)
 | 
			
		||||
      .arcStartLat((d) => (d as { startLat: number }).startLat * 1)
 | 
			
		||||
      .arcStartLng((d) => (d as { startLng: number }).startLng * 1)
 | 
			
		||||
      .arcEndLat((d) => (d as { endLat: number }).endLat * 1)
 | 
			
		||||
      .arcEndLng((d) => (d as { endLng: number }).endLng * 1)
 | 
			
		||||
      .arcColor((e: any) => (e as { color: string }).color)
 | 
			
		||||
      .arcAltitude((e) => (e as { arcAlt: number }).arcAlt * 1)
 | 
			
		||||
      .arcStroke(() => [0.32, 0.28, 0.3][Math.round(Math.random() * 2)])
 | 
			
		||||
      .arcDashLength(defaultProps.arcLength)
 | 
			
		||||
      .arcDashInitialGap((e) => (e as { order: number }).order * 1)
 | 
			
		||||
      .arcDashGap(15)
 | 
			
		||||
      .arcDashAnimateTime(() => defaultProps.arcTime);
 | 
			
		||||
 | 
			
		||||
    globeRef.current
 | 
			
		||||
      .pointsData(filteredPoints)
 | 
			
		||||
      .pointColor((e) => (e as { color: string }).color)
 | 
			
		||||
      .pointsMerge(true)
 | 
			
		||||
      .pointAltitude(0.0)
 | 
			
		||||
      .pointRadius(2);
 | 
			
		||||
 | 
			
		||||
    globeRef.current
 | 
			
		||||
      .ringsData([])
 | 
			
		||||
      .ringColor(() => defaultProps.polygonColor)
 | 
			
		||||
      .ringMaxRadius(defaultProps.maxRings)
 | 
			
		||||
      .ringPropagationSpeed(RING_PROPAGATION_SPEED)
 | 
			
		||||
      .ringRepeatPeriod(
 | 
			
		||||
        (defaultProps.arcTime * defaultProps.arcLength) / defaultProps.rings,
 | 
			
		||||
      );
 | 
			
		||||
  }, [
 | 
			
		||||
    isInitialized,
 | 
			
		||||
    data,
 | 
			
		||||
    defaultProps.pointSize,
 | 
			
		||||
    defaultProps.showAtmosphere,
 | 
			
		||||
    defaultProps.atmosphereColor,
 | 
			
		||||
    defaultProps.atmosphereAltitude,
 | 
			
		||||
    defaultProps.polygonColor,
 | 
			
		||||
    defaultProps.arcLength,
 | 
			
		||||
    defaultProps.arcTime,
 | 
			
		||||
    defaultProps.rings,
 | 
			
		||||
    defaultProps.maxRings,
 | 
			
		||||
  ]);
 | 
			
		||||
 | 
			
		||||
  // Handle rings animation with cleanup
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!globeRef.current || !isInitialized || !data) return;
 | 
			
		||||
 | 
			
		||||
    const interval = setInterval(() => {
 | 
			
		||||
      if (!globeRef.current) return;
 | 
			
		||||
 | 
			
		||||
      const newNumbersOfRings = genRandomNumbers(
 | 
			
		||||
        0,
 | 
			
		||||
        data.length,
 | 
			
		||||
        Math.floor((data.length * 4) / 5),
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      const ringsData = data
 | 
			
		||||
        .filter((d, i) => newNumbersOfRings.includes(i))
 | 
			
		||||
        .map((d) => ({
 | 
			
		||||
          lat: d.startLat,
 | 
			
		||||
          lng: d.startLng,
 | 
			
		||||
          color: d.color,
 | 
			
		||||
        }));
 | 
			
		||||
 | 
			
		||||
      globeRef.current.ringsData(ringsData);
 | 
			
		||||
    }, 2000);
 | 
			
		||||
 | 
			
		||||
    return () => {
 | 
			
		||||
      clearInterval(interval);
 | 
			
		||||
    };
 | 
			
		||||
  }, [isInitialized, data]);
 | 
			
		||||
 | 
			
		||||
  return <group ref={groupRef} />;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function WebGLRendererConfig() {
 | 
			
		||||
  const { gl, size } = useThree();
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    gl.setPixelRatio(window.devicePixelRatio);
 | 
			
		||||
    gl.setSize(size.width, size.height);
 | 
			
		||||
    gl.setClearColor(0xffaaff, 0);
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  return null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function World(props: WorldProps) {
 | 
			
		||||
  const { globeConfig } = props;
 | 
			
		||||
  const scene = new Scene();
 | 
			
		||||
  scene.fog = new Fog(0xffffff, 400, 2000);
 | 
			
		||||
  return (
 | 
			
		||||
    <Canvas scene={scene} camera={new PerspectiveCamera(50, aspect, 180, 1800)}>
 | 
			
		||||
      <WebGLRendererConfig />
 | 
			
		||||
      <ambientLight color={globeConfig.ambientLight} intensity={0.6} />
 | 
			
		||||
      <directionalLight
 | 
			
		||||
        color={globeConfig.directionalLeftLight}
 | 
			
		||||
        position={new Vector3(-400, 100, 400)}
 | 
			
		||||
      />
 | 
			
		||||
      <directionalLight
 | 
			
		||||
        color={globeConfig.directionalTopLight}
 | 
			
		||||
        position={new Vector3(-200, 500, 200)}
 | 
			
		||||
      />
 | 
			
		||||
      <pointLight
 | 
			
		||||
        color={globeConfig.pointLight}
 | 
			
		||||
        position={new Vector3(-200, 500, 200)}
 | 
			
		||||
        intensity={0.8}
 | 
			
		||||
      />
 | 
			
		||||
      <Globe {...props} />
 | 
			
		||||
      <OrbitControls
 | 
			
		||||
        enablePan={false}
 | 
			
		||||
        enableZoom={false}
 | 
			
		||||
        minDistance={cameraZ}
 | 
			
		||||
        maxDistance={cameraZ}
 | 
			
		||||
        autoRotateSpeed={1}
 | 
			
		||||
        autoRotate={true}
 | 
			
		||||
        minPolarAngle={Math.PI / 3.5}
 | 
			
		||||
        maxPolarAngle={Math.PI - Math.PI / 3}
 | 
			
		||||
      />
 | 
			
		||||
    </Canvas>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function hexToRgb(hex: string) {
 | 
			
		||||
  var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
 | 
			
		||||
  hex = hex.replace(shorthandRegex, function (m, r, g, b) {
 | 
			
		||||
    return r + r + g + g + b + b;
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
 | 
			
		||||
  return result
 | 
			
		||||
    ? {
 | 
			
		||||
        r: parseInt(result[1], 16),
 | 
			
		||||
        g: parseInt(result[2], 16),
 | 
			
		||||
        b: parseInt(result[3], 16),
 | 
			
		||||
      }
 | 
			
		||||
    : null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function genRandomNumbers(min: number, max: number, count: number) {
 | 
			
		||||
  const arr = [];
 | 
			
		||||
  while (arr.length < count) {
 | 
			
		||||
    const r = Math.floor(Math.random() * (max - min)) + min;
 | 
			
		||||
    if (arr.indexOf(r) === -1) arr.push(r);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return arr;
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user