add spotlight

This commit is contained in:
sasha-astiadi 2025-08-03 16:40:51 +02:00
parent 663fd167b7
commit 0eee1629c6
14 changed files with 2800 additions and 410 deletions

21
components.json Normal file
View 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
View File

@ -0,0 +1,6 @@
import { ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}

2149
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -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"
} }
} }

View File

@ -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 />
</> </>
) )
} }

View 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&apos;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>
);
}

View File

@ -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

View 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&apos;t know why but I&apos;m running out of
copy.
</p>
</div>
</div>
);
}

309
src/components/ui/Globe.tsx Normal file
View 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;
}

View 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

File diff suppressed because one or more lines are too long

6
src/lib/utils.ts Normal file
View 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))
}

View File

@ -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;
}
} }