add spotlight
This commit is contained in:
		
							
								
								
									
										21
									
								
								components.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								components.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "$schema": "https://ui.shadcn.com/schema.json",
 | 
				
			||||||
 | 
					  "style": "new-york",
 | 
				
			||||||
 | 
					  "rsc": true,
 | 
				
			||||||
 | 
					  "tsx": true,
 | 
				
			||||||
 | 
					  "tailwind": {
 | 
				
			||||||
 | 
					    "config": "",
 | 
				
			||||||
 | 
					    "css": "src/styles/tailwind.css",
 | 
				
			||||||
 | 
					    "baseColor": "stone",
 | 
				
			||||||
 | 
					    "cssVariables": true,
 | 
				
			||||||
 | 
					    "prefix": ""
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "aliases": {
 | 
				
			||||||
 | 
					    "components": "@/components",
 | 
				
			||||||
 | 
					    "utils": "@/lib/utils",
 | 
				
			||||||
 | 
					    "ui": "@/components/ui",
 | 
				
			||||||
 | 
					    "lib": "@/lib",
 | 
				
			||||||
 | 
					    "hooks": "@/hooks"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "iconLibrary": "lucide"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										6
									
								
								lib/utils.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								lib/utils.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					import { ClassValue, clsx } from "clsx";
 | 
				
			||||||
 | 
					import { twMerge } from "tailwind-merge";
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					export function cn(...inputs: ClassValue[]) {
 | 
				
			||||||
 | 
					  return twMerge(clsx(inputs));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										2151
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2151
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										24
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								package.json
									
									
									
									
									
								
							@@ -12,17 +12,24 @@
 | 
				
			|||||||
  "dependencies": {
 | 
					  "dependencies": {
 | 
				
			||||||
    "@headlessui/react": "^2.1.0",
 | 
					    "@headlessui/react": "^2.1.0",
 | 
				
			||||||
    "@heroicons/react": "^2.2.0",
 | 
					    "@heroicons/react": "^2.2.0",
 | 
				
			||||||
 | 
					    "@react-three/drei": "^9.88.13",
 | 
				
			||||||
 | 
					    "@react-three/fiber": "^8.15.11",
 | 
				
			||||||
    "@tailwindcss/forms": "^0.5.3",
 | 
					    "@tailwindcss/forms": "^0.5.3",
 | 
				
			||||||
    "@tailwindcss/postcss": "^4.1.7",
 | 
					    "@tailwindcss/postcss": "^4.1.7",
 | 
				
			||||||
    "@types/node": "^20.10.8",
 | 
					    "@types/node": "^20.10.8",
 | 
				
			||||||
    "@types/react": "^18.2.47",
 | 
					    "@types/react": "^18.3.23",
 | 
				
			||||||
    "@types/react-dom": "^18.2.18",
 | 
					    "@types/react-dom": "^18.3.7",
 | 
				
			||||||
    "clsx": "^2.1.0",
 | 
					    "class-variance-authority": "^0.7.1",
 | 
				
			||||||
    "framer-motion": "^10.15.0",
 | 
					    "clsx": "^2.1.1",
 | 
				
			||||||
    "next": "^14.0.4",
 | 
					    "framer-motion": "^12.23.12",
 | 
				
			||||||
    "react": "^18.2.0",
 | 
					    "lucide-react": "^0.536.0",
 | 
				
			||||||
    "react-dom": "^18.2.0",
 | 
					    "next": "15.0.3",
 | 
				
			||||||
 | 
					    "react": "^18.3.1",
 | 
				
			||||||
 | 
					    "react-dom": "^18.3.1",
 | 
				
			||||||
 | 
					    "tailwind-merge": "^2.6.0",
 | 
				
			||||||
    "tailwindcss": "^4.1.7",
 | 
					    "tailwindcss": "^4.1.7",
 | 
				
			||||||
 | 
					    "three": "^0.179.1",
 | 
				
			||||||
 | 
					    "three-globe": "^2.27.2",
 | 
				
			||||||
    "typescript": "^5.3.3",
 | 
					    "typescript": "^5.3.3",
 | 
				
			||||||
    "use-debounce": "^10.0.0"
 | 
					    "use-debounce": "^10.0.0"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
@@ -31,6 +38,7 @@
 | 
				
			|||||||
    "eslint-config-next": "^14.0.4",
 | 
					    "eslint-config-next": "^14.0.4",
 | 
				
			||||||
    "prettier": "^3.3.2",
 | 
					    "prettier": "^3.3.2",
 | 
				
			||||||
    "prettier-plugin-tailwindcss": "^0.6.11",
 | 
					    "prettier-plugin-tailwindcss": "^0.6.11",
 | 
				
			||||||
    "sharp": "0.33.1"
 | 
					    "sharp": "0.33.1",
 | 
				
			||||||
 | 
					    "tw-animate-css": "^1.3.6"
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,18 +8,12 @@ import { SecondaryFeatures } from '@/components/SecondaryFeatures'
 | 
				
			|||||||
import Tractions from '@/components/Tractions'
 | 
					import Tractions from '@/components/Tractions'
 | 
				
			||||||
import Benefits from '@/components/Benefits'
 | 
					import Benefits from '@/components/Benefits'
 | 
				
			||||||
import  Cta from '@/components/Cta'
 | 
					import  Cta from '@/components/Cta'
 | 
				
			||||||
 | 
					import { GlobeDemo } from '@/components/GlobeDemo'
 | 
				
			||||||
 | 
					import { SpotlightPreview } from '@/components/Spotlight'
 | 
				
			||||||
export default function Home() {
 | 
					export default function Home() {
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <>
 | 
					    <>
 | 
				
			||||||
      <Hero />
 | 
					      <SpotlightPreview />
 | 
				
			||||||
      <Tractions />
 | 
					 | 
				
			||||||
      <Benefits />
 | 
					 | 
				
			||||||
      <SecondaryFeatures />
 | 
					 | 
				
			||||||
      <Reviews />
 | 
					 | 
				
			||||||
      <Pricing />
 | 
					 | 
				
			||||||
      <Cta />
 | 
					 | 
				
			||||||
      <Faqs />
 | 
					 | 
				
			||||||
    </>
 | 
					    </>
 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										111
									
								
								src/components/GlobeDemo.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								src/components/GlobeDemo.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,111 @@
 | 
				
			|||||||
 | 
					"use client";
 | 
				
			||||||
 | 
					import React from "react";
 | 
				
			||||||
 | 
					import { motion } from "framer-motion";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export 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>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -105,7 +105,7 @@ export function Header() {
 | 
				
			|||||||
                            y: -32,
 | 
					                            y: -32,
 | 
				
			||||||
                            transition: { duration: 0.2 },
 | 
					                            transition: { duration: 0.2 },
 | 
				
			||||||
                          }}
 | 
					                          }}
 | 
				
			||||||
                          className="absolute inset-x-0 top-0 z-0 origin-top rounded-b-2xl bg-gray-900 px-6 pt-32 pb-6 shadow-2xl shadow-black/20"
 | 
					                          className="absolute inset-x-0 top-0 z-0 origin-top rounded-b-2xl bg-gray-50 px-6 pt-32 pb-6 shadow-2xl shadow-gray-900/20"
 | 
				
			||||||
                        >
 | 
					                        >
 | 
				
			||||||
                          <div className="space-y-4">
 | 
					                          <div className="space-y-4">
 | 
				
			||||||
                            <MobileNavLink href="/#features">
 | 
					                            <MobileNavLink href="/#features">
 | 
				
			||||||
 
 | 
				
			|||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										32
									
								
								src/components/Spotlight.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/components/Spotlight.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
				
			|||||||
 | 
					import React from "react";
 | 
				
			||||||
 | 
					import { cn } from "@/lib/utils";
 | 
				
			||||||
 | 
					import { Spotlight } from "@/components/ui/spotlight";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function SpotlightPreview() {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div className="relative flex h-[40rem] w-full overflow-hidden rounded-md bg-black/[0.96] antialiased md:items-center md:justify-center">
 | 
				
			||||||
 | 
					      <div
 | 
				
			||||||
 | 
					        className={cn(
 | 
				
			||||||
 | 
					          "pointer-events-none absolute inset-0 [background-size:40px_40px] select-none",
 | 
				
			||||||
 | 
					          "[background-image:linear-gradient(to_right,#171717_1px,transparent_1px),linear-gradient(to_bottom,#171717_1px,transparent_1px)]",
 | 
				
			||||||
 | 
					        )}
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <Spotlight
 | 
				
			||||||
 | 
					        className="-top-40 left-0 md:-top-20 md:left-60"
 | 
				
			||||||
 | 
					        fill="white"
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					      <div className="relative z-10 mx-auto w-full max-w-7xl p-4 pt-20 md:pt-0">
 | 
				
			||||||
 | 
					        <h1 className="bg-opacity-50 bg-gradient-to-b from-neutral-50 to-neutral-400 bg-clip-text text-center text-4xl font-bold text-transparent md:text-7xl">
 | 
				
			||||||
 | 
					          Spotlight <br /> is the new trend.
 | 
				
			||||||
 | 
					        </h1>
 | 
				
			||||||
 | 
					        <p className="mx-auto mt-4 max-w-lg text-center text-base font-normal text-neutral-300">
 | 
				
			||||||
 | 
					          Spotlight effect is a great way to draw attention to a specific part
 | 
				
			||||||
 | 
					          of the page. Here, we are drawing the attention towards the text
 | 
				
			||||||
 | 
					          section of the page. I don't know why but I'm running out of
 | 
				
			||||||
 | 
					          copy.
 | 
				
			||||||
 | 
					        </p>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										309
									
								
								src/components/ui/Globe.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										309
									
								
								src/components/ui/Globe.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,309 @@
 | 
				
			|||||||
 | 
					"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;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										56
									
								
								src/components/ui/Spotlight.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								src/components/ui/Spotlight.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,56 @@
 | 
				
			|||||||
 | 
					import React from "react";
 | 
				
			||||||
 | 
					import { cn } from "@/lib/utils";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type SpotlightProps = {
 | 
				
			||||||
 | 
					  className?: string;
 | 
				
			||||||
 | 
					  fill?: string;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const Spotlight = ({ className, fill }: SpotlightProps) => {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <svg
 | 
				
			||||||
 | 
					      className={cn(
 | 
				
			||||||
 | 
					        "animate-spotlight pointer-events-none absolute z-[1]  h-[169%] w-[138%] lg:w-[84%] opacity-0",
 | 
				
			||||||
 | 
					        className
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					      xmlns="http://www.w3.org/2000/svg"
 | 
				
			||||||
 | 
					      viewBox="0 0 3787 2842"
 | 
				
			||||||
 | 
					      fill="none"
 | 
				
			||||||
 | 
					    >
 | 
				
			||||||
 | 
					      <g filter="url(#filter)">
 | 
				
			||||||
 | 
					        <ellipse
 | 
				
			||||||
 | 
					          cx="1924.71"
 | 
				
			||||||
 | 
					          cy="273.501"
 | 
				
			||||||
 | 
					          rx="1924.71"
 | 
				
			||||||
 | 
					          ry="273.501"
 | 
				
			||||||
 | 
					          transform="matrix(-0.822377 -0.568943 -0.568943 0.822377 3631.88 2291.09)"
 | 
				
			||||||
 | 
					          fill={fill || "white"}
 | 
				
			||||||
 | 
					          fillOpacity="0.21"
 | 
				
			||||||
 | 
					        ></ellipse>
 | 
				
			||||||
 | 
					      </g>
 | 
				
			||||||
 | 
					      <defs>
 | 
				
			||||||
 | 
					        <filter
 | 
				
			||||||
 | 
					          id="filter"
 | 
				
			||||||
 | 
					          x="0.860352"
 | 
				
			||||||
 | 
					          y="0.838989"
 | 
				
			||||||
 | 
					          width="3785.16"
 | 
				
			||||||
 | 
					          height="2840.26"
 | 
				
			||||||
 | 
					          filterUnits="userSpaceOnUse"
 | 
				
			||||||
 | 
					          colorInterpolationFilters="sRGB"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          <feFlood floodOpacity="0" result="BackgroundImageFix"></feFlood>
 | 
				
			||||||
 | 
					          <feBlend
 | 
				
			||||||
 | 
					            mode="normal"
 | 
				
			||||||
 | 
					            in="SourceGraphic"
 | 
				
			||||||
 | 
					            in2="BackgroundImageFix"
 | 
				
			||||||
 | 
					            result="shape"
 | 
				
			||||||
 | 
					          ></feBlend>
 | 
				
			||||||
 | 
					          <feGaussianBlur
 | 
				
			||||||
 | 
					            stdDeviation="151"
 | 
				
			||||||
 | 
					            result="effect1_foregroundBlur_1065_8"
 | 
				
			||||||
 | 
					          ></feGaussianBlur>
 | 
				
			||||||
 | 
					        </filter>
 | 
				
			||||||
 | 
					      </defs>
 | 
				
			||||||
 | 
					    </svg>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										182
									
								
								src/data/globe.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										182
									
								
								src/data/globe.json
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										6
									
								
								src/lib/utils.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/lib/utils.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					import { clsx, type ClassValue } from "clsx"
 | 
				
			||||||
 | 
					import { twMerge } from "tailwind-merge"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function cn(...inputs: ClassValue[]) {
 | 
				
			||||||
 | 
					  return twMerge(clsx(inputs))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,4 +1,7 @@
 | 
				
			|||||||
@import 'tailwindcss';
 | 
					@import 'tailwindcss';
 | 
				
			||||||
 | 
					@import "tw-animate-css";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@custom-variant dark (&:is(.dark *));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@plugin '@tailwindcss/forms';
 | 
					@plugin '@tailwindcss/forms';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -78,10 +81,142 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
@theme inline {
 | 
					@theme inline {
 | 
				
			||||||
  --animate-marquee: marquee var(--marquee-duration) linear infinite;
 | 
					  --animate-marquee: marquee var(--marquee-duration) linear infinite;
 | 
				
			||||||
 | 
					  --color-sidebar-ring: var(--sidebar-ring);
 | 
				
			||||||
 | 
					  --color-sidebar-border: var(--sidebar-border);
 | 
				
			||||||
 | 
					  --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
 | 
				
			||||||
 | 
					  --color-sidebar-accent: var(--sidebar-accent);
 | 
				
			||||||
 | 
					  --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
 | 
				
			||||||
 | 
					  --color-sidebar-primary: var(--sidebar-primary);
 | 
				
			||||||
 | 
					  --color-sidebar-foreground: var(--sidebar-foreground);
 | 
				
			||||||
 | 
					  --color-sidebar: var(--sidebar);
 | 
				
			||||||
 | 
					  --color-chart-5: var(--chart-5);
 | 
				
			||||||
 | 
					  --color-chart-4: var(--chart-4);
 | 
				
			||||||
 | 
					  --color-chart-3: var(--chart-3);
 | 
				
			||||||
 | 
					  --color-chart-2: var(--chart-2);
 | 
				
			||||||
 | 
					  --color-chart-1: var(--chart-1);
 | 
				
			||||||
 | 
					  --color-ring: var(--ring);
 | 
				
			||||||
 | 
					  --color-input: var(--input);
 | 
				
			||||||
 | 
					  --color-border: var(--border);
 | 
				
			||||||
 | 
					  --color-destructive: var(--destructive);
 | 
				
			||||||
 | 
					  --color-accent-foreground: var(--accent-foreground);
 | 
				
			||||||
 | 
					  --color-accent: var(--accent);
 | 
				
			||||||
 | 
					  --color-muted-foreground: var(--muted-foreground);
 | 
				
			||||||
 | 
					  --color-muted: var(--muted);
 | 
				
			||||||
 | 
					  --color-secondary-foreground: var(--secondary-foreground);
 | 
				
			||||||
 | 
					  --color-secondary: var(--secondary);
 | 
				
			||||||
 | 
					  --color-primary-foreground: var(--primary-foreground);
 | 
				
			||||||
 | 
					  --color-primary: var(--primary);
 | 
				
			||||||
 | 
					  --color-popover-foreground: var(--popover-foreground);
 | 
				
			||||||
 | 
					  --color-popover: var(--popover);
 | 
				
			||||||
 | 
					  --color-card-foreground: var(--card-foreground);
 | 
				
			||||||
 | 
					  --color-card: var(--card);
 | 
				
			||||||
 | 
					  --color-foreground: var(--foreground);
 | 
				
			||||||
 | 
					  --color-background: var(--background);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @keyframes marquee {
 | 
					  @keyframes marquee {
 | 
				
			||||||
    100% {
 | 
					    100% {
 | 
				
			||||||
      transform: translateY(-50%);
 | 
					      transform: translateY(-50%);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  --radius-sm: calc(var(--radius) - 4px);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  --radius-md: calc(var(--radius) - 2px);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  --radius-lg: var(--radius);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  --radius-xl: calc(var(--radius) + 4px)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@theme inline {
 | 
				
			||||||
 | 
					  --animate-spotlight: spotlight 2s ease 0.75s 1 forwards;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@keyframes spotlight {
 | 
				
			||||||
 | 
					  0% {
 | 
				
			||||||
 | 
					    opacity: 0;
 | 
				
			||||||
 | 
					    transform: translate(-72%, -62%) scale(0.5);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  100% {
 | 
				
			||||||
 | 
					    opacity: 1;
 | 
				
			||||||
 | 
					    transform: translate(-50%, -40%) scale(1);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					:root {
 | 
				
			||||||
 | 
					  --radius: 0.625rem;
 | 
				
			||||||
 | 
					  --background: oklch(1 0 0);
 | 
				
			||||||
 | 
					  --foreground: oklch(0.147 0.004 49.25);
 | 
				
			||||||
 | 
					  --card: oklch(1 0 0);
 | 
				
			||||||
 | 
					  --card-foreground: oklch(0.147 0.004 49.25);
 | 
				
			||||||
 | 
					  --popover: oklch(1 0 0);
 | 
				
			||||||
 | 
					  --popover-foreground: oklch(0.147 0.004 49.25);
 | 
				
			||||||
 | 
					  --primary: oklch(0.216 0.006 56.043);
 | 
				
			||||||
 | 
					  --primary-foreground: oklch(0.985 0.001 106.423);
 | 
				
			||||||
 | 
					  --secondary: oklch(0.97 0.001 106.424);
 | 
				
			||||||
 | 
					  --secondary-foreground: oklch(0.216 0.006 56.043);
 | 
				
			||||||
 | 
					  --muted: oklch(0.97 0.001 106.424);
 | 
				
			||||||
 | 
					  --muted-foreground: oklch(0.553 0.013 58.071);
 | 
				
			||||||
 | 
					  --accent: oklch(0.97 0.001 106.424);
 | 
				
			||||||
 | 
					  --accent-foreground: oklch(0.216 0.006 56.043);
 | 
				
			||||||
 | 
					  --destructive: oklch(0.577 0.245 27.325);
 | 
				
			||||||
 | 
					  --border: oklch(0.923 0.003 48.717);
 | 
				
			||||||
 | 
					  --input: oklch(0.923 0.003 48.717);
 | 
				
			||||||
 | 
					  --ring: oklch(0.709 0.01 56.259);
 | 
				
			||||||
 | 
					  --chart-1: oklch(0.646 0.222 41.116);
 | 
				
			||||||
 | 
					  --chart-2: oklch(0.6 0.118 184.704);
 | 
				
			||||||
 | 
					  --chart-3: oklch(0.398 0.07 227.392);
 | 
				
			||||||
 | 
					  --chart-4: oklch(0.828 0.189 84.429);
 | 
				
			||||||
 | 
					  --chart-5: oklch(0.769 0.188 70.08);
 | 
				
			||||||
 | 
					  --sidebar: oklch(0.985 0.001 106.423);
 | 
				
			||||||
 | 
					  --sidebar-foreground: oklch(0.147 0.004 49.25);
 | 
				
			||||||
 | 
					  --sidebar-primary: oklch(0.216 0.006 56.043);
 | 
				
			||||||
 | 
					  --sidebar-primary-foreground: oklch(0.985 0.001 106.423);
 | 
				
			||||||
 | 
					  --sidebar-accent: oklch(0.97 0.001 106.424);
 | 
				
			||||||
 | 
					  --sidebar-accent-foreground: oklch(0.216 0.006 56.043);
 | 
				
			||||||
 | 
					  --sidebar-border: oklch(0.923 0.003 48.717);
 | 
				
			||||||
 | 
					  --sidebar-ring: oklch(0.709 0.01 56.259);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.dark {
 | 
				
			||||||
 | 
					  --background: oklch(0.147 0.004 49.25);
 | 
				
			||||||
 | 
					  --foreground: oklch(0.985 0.001 106.423);
 | 
				
			||||||
 | 
					  --card: oklch(0.216 0.006 56.043);
 | 
				
			||||||
 | 
					  --card-foreground: oklch(0.985 0.001 106.423);
 | 
				
			||||||
 | 
					  --popover: oklch(0.216 0.006 56.043);
 | 
				
			||||||
 | 
					  --popover-foreground: oklch(0.985 0.001 106.423);
 | 
				
			||||||
 | 
					  --primary: oklch(0.923 0.003 48.717);
 | 
				
			||||||
 | 
					  --primary-foreground: oklch(0.216 0.006 56.043);
 | 
				
			||||||
 | 
					  --secondary: oklch(0.268 0.007 34.298);
 | 
				
			||||||
 | 
					  --secondary-foreground: oklch(0.985 0.001 106.423);
 | 
				
			||||||
 | 
					  --muted: oklch(0.268 0.007 34.298);
 | 
				
			||||||
 | 
					  --muted-foreground: oklch(0.709 0.01 56.259);
 | 
				
			||||||
 | 
					  --accent: oklch(0.268 0.007 34.298);
 | 
				
			||||||
 | 
					  --accent-foreground: oklch(0.985 0.001 106.423);
 | 
				
			||||||
 | 
					  --destructive: oklch(0.704 0.191 22.216);
 | 
				
			||||||
 | 
					  --border: oklch(1 0 0 / 10%);
 | 
				
			||||||
 | 
					  --input: oklch(1 0 0 / 15%);
 | 
				
			||||||
 | 
					  --ring: oklch(0.553 0.013 58.071);
 | 
				
			||||||
 | 
					  --chart-1: oklch(0.488 0.243 264.376);
 | 
				
			||||||
 | 
					  --chart-2: oklch(0.696 0.17 162.48);
 | 
				
			||||||
 | 
					  --chart-3: oklch(0.769 0.188 70.08);
 | 
				
			||||||
 | 
					  --chart-4: oklch(0.627 0.265 303.9);
 | 
				
			||||||
 | 
					  --chart-5: oklch(0.645 0.246 16.439);
 | 
				
			||||||
 | 
					  --sidebar: oklch(0.216 0.006 56.043);
 | 
				
			||||||
 | 
					  --sidebar-foreground: oklch(0.985 0.001 106.423);
 | 
				
			||||||
 | 
					  --sidebar-primary: oklch(0.488 0.243 264.376);
 | 
				
			||||||
 | 
					  --sidebar-primary-foreground: oklch(0.985 0.001 106.423);
 | 
				
			||||||
 | 
					  --sidebar-accent: oklch(0.268 0.007 34.298);
 | 
				
			||||||
 | 
					  --sidebar-accent-foreground: oklch(0.985 0.001 106.423);
 | 
				
			||||||
 | 
					  --sidebar-border: oklch(1 0 0 / 10%);
 | 
				
			||||||
 | 
					  --sidebar-ring: oklch(0.553 0.013 58.071);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@layer base {
 | 
				
			||||||
 | 
					  * {
 | 
				
			||||||
 | 
					    @apply border-border outline-ring/50;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  body {
 | 
				
			||||||
 | 
					    @apply bg-background text-foreground;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user