Compare commits
4 Commits
Author | SHA1 | Date | |
---|---|---|---|
6eadc482eb | |||
045486365d | |||
0eee1629c6 | |||
663fd167b7 |
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"
|
||||
}
|
92
components/uilayouts/globe.tsx
Normal file
92
components/uilayouts/globe.tsx
Normal file
@ -0,0 +1,92 @@
|
||||
'use client';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import createGlobe from 'cobe';
|
||||
import { cn } from '@/lib/utils';
|
||||
interface EarthProps {
|
||||
className?: string;
|
||||
theta?: number;
|
||||
dark?: number;
|
||||
scale?: number;
|
||||
diffuse?: number;
|
||||
mapSamples?: number;
|
||||
mapBrightness?: number;
|
||||
baseColor?: [number, number, number];
|
||||
markerColor?: [number, number, number];
|
||||
glowColor?: [number, number, number];
|
||||
}
|
||||
const Earth: React.FC<EarthProps> = ({
|
||||
className,
|
||||
theta = 0.25,
|
||||
dark = 1,
|
||||
scale = 1.1,
|
||||
diffuse = 1.2,
|
||||
mapSamples = 40000,
|
||||
mapBrightness = 6,
|
||||
baseColor = [0.4, 0.6509, 1],
|
||||
markerColor = [1, 0, 0],
|
||||
glowColor = [0.2745, 0.5765, 0.898],
|
||||
}) => {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let width = 0;
|
||||
const onResize = () =>
|
||||
canvasRef.current && (width = canvasRef.current.offsetWidth);
|
||||
window.addEventListener('resize', onResize);
|
||||
onResize();
|
||||
let phi = 0;
|
||||
|
||||
onResize();
|
||||
const globe = createGlobe(canvasRef.current!, {
|
||||
devicePixelRatio: 2,
|
||||
width: width * 2,
|
||||
height: width * 2,
|
||||
phi: 0,
|
||||
theta: theta,
|
||||
dark: dark,
|
||||
scale: scale,
|
||||
diffuse: diffuse,
|
||||
mapSamples: mapSamples,
|
||||
mapBrightness: mapBrightness,
|
||||
baseColor: baseColor,
|
||||
markerColor: markerColor,
|
||||
glowColor: glowColor,
|
||||
opacity: 1,
|
||||
offset: [0, 0],
|
||||
markers: [
|
||||
// longitude latitude
|
||||
],
|
||||
onRender: (state: Record<string, any>) => {
|
||||
// Called on every animation frame.
|
||||
// `state` will be an empty object, return updated params.\
|
||||
state.phi = phi;
|
||||
phi += 0.003;
|
||||
},
|
||||
});
|
||||
|
||||
return () => {
|
||||
globe.destroy();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center justify-center z-[10] w-full max-w-[350px] mx-auto',
|
||||
className
|
||||
)}
|
||||
>
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
maxWidth: '100%',
|
||||
aspectRatio: '1',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Earth;
|
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));
|
||||
}
|
2
next-env.d.ts
vendored
2
next-env.d.ts
vendored
@ -2,4 +2,4 @@
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
|
2300
package-lock.json
generated
2300
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
27
package.json
27
package.json
@ -12,17 +12,27 @@
|
||||
"dependencies": {
|
||||
"@headlessui/react": "^2.1.0",
|
||||
"@heroicons/react": "^2.2.0",
|
||||
"@react-three/drei": "^9.88.13",
|
||||
"@react-three/fiber": "^8.15.11",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"@tailwindcss/postcss": "^4.1.7",
|
||||
"@types/node": "^20.10.8",
|
||||
"@types/react": "^18.2.47",
|
||||
"@types/react-dom": "^18.2.18",
|
||||
"clsx": "^2.1.0",
|
||||
"framer-motion": "^10.15.0",
|
||||
"next": "^14.0.4",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"@types/react": "^18.3.23",
|
||||
"@types/react-dom": "^18.3.7",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"cobe": "^0.6.4",
|
||||
"framer-motion": "^12.23.12",
|
||||
"lucide-react": "^0.536.0",
|
||||
"motion": "^12.23.12",
|
||||
"next": "^15.4.5",
|
||||
"react": "^18.3.1",
|
||||
"react-countup": "^6.5.3",
|
||||
"react-dom": "^18.3.1",
|
||||
"tailwind-merge": "^2.6.0",
|
||||
"tailwindcss": "^4.1.7",
|
||||
"three": "^0.179.1",
|
||||
"three-globe": "^2.27.2",
|
||||
"typescript": "^5.3.3",
|
||||
"use-debounce": "^10.0.0"
|
||||
},
|
||||
@ -31,6 +41,7 @@
|
||||
"eslint-config-next": "^14.0.4",
|
||||
"prettier": "^3.3.2",
|
||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||
"sharp": "0.33.1"
|
||||
"sharp": "0.33.1",
|
||||
"tw-animate-css": "^1.3.6"
|
||||
}
|
||||
}
|
||||
|
@ -8,18 +8,20 @@ import { SecondaryFeatures } from '@/components/SecondaryFeatures'
|
||||
import Tractions from '@/components/Tractions'
|
||||
import Benefits from '@/components/Benefits'
|
||||
import Cta from '@/components/Cta'
|
||||
import { SpotlightPreview } from '@/components/Spotlight'
|
||||
import { StackSectionPreview } from '@/components/StackSection'
|
||||
import GlobeDemo from '@/components/GlobeDemo'
|
||||
import { Dashboard } from '@/components/Dashboard'
|
||||
import { AppsPreview } from '@/components/Apps'
|
||||
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<>
|
||||
<Hero />
|
||||
<Tractions />
|
||||
<Benefits />
|
||||
<SecondaryFeatures />
|
||||
<Reviews />
|
||||
<Pricing />
|
||||
<Cta />
|
||||
<Faqs />
|
||||
<SpotlightPreview />
|
||||
<StackSectionPreview />
|
||||
<Dashboard />
|
||||
<AppsPreview />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -25,8 +25,8 @@ export default function RootLayout({
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<html lang="en" className={clsx('bg-white antialiased', inter.variable)}>
|
||||
<body>{children}</body>
|
||||
<html lang="en" className={clsx('antialiased', inter.variable)} style={{ backgroundColor: '#121212' }}>
|
||||
<body style={{ backgroundColor: '#121212', color: '#ffffff' }}>{children}</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
|
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>
|
||||
);
|
||||
}
|
@ -8,78 +8,78 @@ import Benefits4 from '@/images/benefits/benefits4.jpg'
|
||||
|
||||
export default function Benefits() {
|
||||
return (
|
||||
<div className="bg-white py-12">
|
||||
<div style={{ backgroundColor: '#121212' }} className="py-12">
|
||||
<div className="mx-auto max-w-2xl px-6 lg:max-w-7xl lg:px-8">
|
||||
<Container>
|
||||
<div className="mx-auto max-w-2xl lg:mx-0 lg:max-w-3xl">
|
||||
<h2 className="lg:text-4xl text-3xl font-medium tracking-tight text-gray-900">
|
||||
<h2 className="lg:text-4xl text-3xl font-medium tracking-tight text-white">
|
||||
Built Different. For a Change.
|
||||
</h2>
|
||||
<p className="mt-6 lg:text-lg text-base text-gray-600">
|
||||
EngageOS isn’t just another tech platform — it’s a digital infrastructure built from the ground up for purpose-driven organizations. From white-label sovereignty to field-ready resilience, every element of EngageOS is designed to meet the real-world challenges of civil society.
|
||||
<p className="mt-6 lg:text-lg text-base text-gray-300">
|
||||
EngageOS isn't just another tech platform — it's a digital infrastructure built from the ground up for purpose-driven organizations. From white-label sovereignty to field-ready resilience, every element of EngageOS is designed to meet the real-world challenges of civil society.
|
||||
</p>
|
||||
</div>
|
||||
</Container>
|
||||
<div className="mt-10 grid grid-cols-1 gap-4 sm:mt-16 lg:grid-cols-6 lg:grid-rows-2">
|
||||
<div className="flex p-px lg:col-span-4">
|
||||
<div className="overflow-hidden rounded-lg bg-white ring-1 ring-black/15 max-lg:rounded-t-4xl lg:rounded-tl-4xl">
|
||||
<div className="overflow-hidden rounded-lg bg-gray-900 ring-1 ring-gray-700 max-lg:rounded-t-4xl lg:rounded-tl-4xl">
|
||||
<Image
|
||||
alt=""
|
||||
src={Benefits1}
|
||||
className="h-80 object-cover object-left"
|
||||
/>
|
||||
<div className="p-10">
|
||||
<h3 className="text-sm/4 font-semibold text-gray-900"> Built for Civil Society</h3>
|
||||
<p className="mt-2 text-lg font-medium tracking-tight text-gray-900">Purpose-First, Not Profit-First</p>
|
||||
<p className="mt-2 max-w-lg text-sm/6 text-gray-600">
|
||||
<h3 className="text-sm/4 font-semibold text-white"> Built for Civil Society</h3>
|
||||
<p className="mt-2 text-lg font-medium tracking-tight text-white">Purpose-First, Not Profit-First</p>
|
||||
<p className="mt-2 max-w-lg text-sm/6 text-gray-300">
|
||||
Unlike traditional SaaS built for commercial scale, EngageOS was born from the realities of NGOs, grassroots coalitions, and purpose-led institutions. Every module, flow, and metric is optimized to serve impact — not ad revenue or venture capital.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex p-px lg:col-span-2">
|
||||
<div className="overflow-hidden rounded-lg bg-white ring-1 ring-black/15 lg:rounded-tr-4xl">
|
||||
<div className="overflow-hidden rounded-lg bg-gray-900 ring-1 ring-gray-700 lg:rounded-tr-4xl">
|
||||
<Image
|
||||
alt=""
|
||||
src={Benefits2}
|
||||
className="h-80 object-cover"
|
||||
/>
|
||||
<div className="p-10">
|
||||
<h3 className="text-sm/4 font-semibold text-gray-900">White-Label, Zero-Code</h3>
|
||||
<p className="mt-2 text-lg font-medium tracking-tight text-gray-900">Your Brand, Your Movements</p>
|
||||
<p className="mt-2 max-w-lg text-sm/6 text-gray-600">
|
||||
<h3 className="text-sm/4 font-semibold text-white">White-Label, Zero-Code</h3>
|
||||
<p className="mt-2 text-lg font-medium tracking-tight text-white">Your Brand, Your Movements</p>
|
||||
<p className="mt-2 max-w-lg text-sm/6 text-gray-300">
|
||||
EngageOS empowers organizations to fully own their digital identity. From Red Cross OS to Montessori OS, each instance is custom-branded — no tech team required. You launch a platform that looks and feels like you, not us.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex p-px lg:col-span-2">
|
||||
<div className="overflow-hidden rounded-lg bg-white ring-1 ring-black/15 lg:rounded-bl-4xl">
|
||||
<div className="overflow-hidden rounded-lg bg-gray-900 ring-1 ring-gray-700 lg:rounded-bl-4xl">
|
||||
<Image
|
||||
alt=""
|
||||
src={Benefits3}
|
||||
className="h-80 object-cover"
|
||||
/>
|
||||
<div className="p-10">
|
||||
<h3 className="text-sm/4 font-semibold text-gray-900">Sovereign & Ethical Infrastructure</h3>
|
||||
<p className="mt-2 text-lg font-medium tracking-tight text-gray-900">Own Your Data. Always.</p>
|
||||
<p className="mt-2 max-w-lg text-sm/6 text-gray-600">
|
||||
We don’t mine or monetize user data. EngageOS runs on decentralized, privacy-respecting infrastructure — built for trust, compliance, and sovereignty. You control where your data lives and who sees it.
|
||||
<h3 className="text-sm/4 font-semibold text-white">Sovereign & Ethical Infrastructure</h3>
|
||||
<p className="mt-2 text-lg font-medium tracking-tight text-white">Own Your Data. Always.</p>
|
||||
<p className="mt-2 max-w-lg text-sm/6 text-gray-300">
|
||||
We don't mine or monetize user data. EngageOS runs on decentralized, privacy-respecting infrastructure — built for trust, compliance, and sovereignty. You control where your data lives and who sees it.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex p-px lg:col-span-4">
|
||||
<div className="overflow-hidden rounded-lg bg-white ring-1 ring-black/15 max-lg:rounded-b-4xl lg:rounded-br-4xl">
|
||||
<div className="overflow-hidden rounded-lg bg-gray-900 ring-1 ring-gray-700 max-lg:rounded-b-4xl lg:rounded-br-4xl">
|
||||
<Image
|
||||
alt=""
|
||||
src={Benefits4}
|
||||
className="h-80 object-cover object-left"
|
||||
/>
|
||||
<div className="p-10">
|
||||
<h3 className="text-sm/4 font-semibold text-gray-900">Mutualized Model</h3>
|
||||
<p className="mt-2 text-lg font-medium tracking-tight text-gray-900">Share Infrastructure. Multiply Impact.</p>
|
||||
<p className="mt-2 max-w-lg text-sm/6 text-gray-600">
|
||||
<h3 className="text-sm/4 font-semibold text-white">Mutualized Model</h3>
|
||||
<p className="mt-2 text-lg font-medium tracking-tight text-white">Share Infrastructure. Multiply Impact.</p>
|
||||
<p className="mt-2 max-w-lg text-sm/6 text-gray-300">
|
||||
By pooling tech costs across aligned organizations, EngageOS offers enterprise-grade functionality at a fraction of the price. When one partner grows, the entire ecosystem benefits — through shared modules, updates, and insights.
|
||||
</p>
|
||||
</div>
|
||||
|
@ -1,6 +1,6 @@
|
||||
export default function Cta() {
|
||||
return (
|
||||
<div className="bg-white">
|
||||
<div style={{ backgroundColor: '#121212' }}>
|
||||
<div className="mx-auto max-w-7xl py-24 sm:px-6 sm:py-32 lg:px-8">
|
||||
<div className="relative isolate overflow-hidden bg-gray-900 px-6 py-24 text-center shadow-2xl sm:rounded-3xl sm:px-16">
|
||||
<h2 className="text-4xl font-semibold tracking-tight text-balance text-white sm:text-5xl">
|
||||
@ -30,9 +30,9 @@ export default function Cta() {
|
||||
<circle r={512} cx={512} cy={512} fill="url(#engage-gradient)" fillOpacity="0.7" />
|
||||
<defs>
|
||||
<radialGradient id="engage-gradient">
|
||||
<stop offset="0%" stop-color="#caa5f0" />
|
||||
<stop offset="50%" stop-color="#8f79f9" />
|
||||
<stop offset="100%" stop-color="#5d84e1" />
|
||||
<stop offset="0%" stopColor="#caa5f0" />
|
||||
<stop offset="50%" stopColor="#8f79f9" />
|
||||
<stop offset="100%" stopColor="#5d84e1" />
|
||||
</radialGradient>
|
||||
|
||||
</defs>
|
||||
|
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>
|
||||
);
|
||||
}
|
@ -22,11 +22,11 @@ function QrCodeBorder(props: React.ComponentPropsWithoutRef<'svg'>) {
|
||||
|
||||
export function Footer() {
|
||||
return (
|
||||
<footer className="border-t border-gray-200">
|
||||
<footer className="border-t border-gray-700" style={{ backgroundColor: '#121212' }}>
|
||||
<Container>
|
||||
<div className="flex flex-col items-start justify-between gap-y-12 pt-16 pb-6 lg:flex-row lg:items-center lg:py-16">
|
||||
<div>
|
||||
<div className="flex items-center text-gray-900">
|
||||
<div className="flex items-center text-white">
|
||||
<Logomark className="h-10 w-10 flex-none fill-cyan-500" />
|
||||
<div className="ml-4">
|
||||
<p className="text-base font-semibold">EngageOS</p>
|
||||
@ -37,25 +37,25 @@ export function Footer() {
|
||||
<NavLinks />
|
||||
</nav>
|
||||
</div>
|
||||
<div className="group relative -mx-4 flex items-center self-stretch p-4 transition-colors hover:bg-gray-100 sm:self-auto sm:rounded-2xl lg:mx-0 lg:self-auto lg:p-6">
|
||||
<div className="group relative -mx-4 flex items-center self-stretch p-4 transition-colors hover:bg-gray-800 sm:self-auto sm:rounded-2xl lg:mx-0 lg:self-auto lg:p-6">
|
||||
<div className="relative flex h-24 w-24 flex-none items-center justify-center">
|
||||
<QrCodeBorder className="absolute inset-0 h-full w-full stroke-gray-300 transition-colors group-hover:stroke-cyan-500" />
|
||||
<QrCodeBorder className="absolute inset-0 h-full w-full stroke-gray-600 transition-colors group-hover:stroke-cyan-500" />
|
||||
<Image src={qrCode} alt="" unoptimized />
|
||||
</div>
|
||||
<div className="ml-8 lg:w-64">
|
||||
<p className="text-base font-semibold text-gray-900">
|
||||
<p className="text-base font-semibold text-white">
|
||||
<Link href="#">
|
||||
<span className="absolute inset-0 sm:rounded-2xl" />
|
||||
Download the app
|
||||
</Link>
|
||||
</p>
|
||||
<p className="mt-1 text-sm text-gray-700">
|
||||
<p className="mt-1 text-sm text-gray-300">
|
||||
Scan the QR code to download the app from the App Store.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col items-center border-t border-gray-200 pt-8 pb-12 md:flex-row-reverse md:justify-between md:pt-6">
|
||||
<div className="flex flex-col items-center border-t border-gray-700 pt-8 pb-12 md:flex-row-reverse md:justify-between md:pt-6">
|
||||
<form className="flex w-full justify-center md:w-auto">
|
||||
<TextField
|
||||
type="email"
|
||||
@ -70,7 +70,7 @@ export function Footer() {
|
||||
<span className="lg:hidden">Join newsletter</span>
|
||||
</Button>
|
||||
</form>
|
||||
<p className="mt-6 text-sm text-gray-500 md:mt-0">
|
||||
<p className="mt-6 text-sm text-gray-400 md:mt-0">
|
||||
© Copyright {new Date().getFullYear()}. All rights reserved.
|
||||
</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>
|
||||
);
|
||||
}
|
9
src/components/GlobeDemo.tsx
Normal file
9
src/components/GlobeDemo.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
import { Globe } from "@/components/Globe";
|
||||
|
||||
export default function GlobeDemo() {
|
||||
return (
|
||||
<main className="relative min-h-screen bg-transparent flex items-center justify-center">
|
||||
<Globe />
|
||||
</main>
|
||||
);
|
||||
}
|
@ -49,7 +49,7 @@ function MobileNavLink(
|
||||
return (
|
||||
<PopoverButton
|
||||
as={Link}
|
||||
className="block text-base/7 tracking-tight text-gray-700"
|
||||
className="block text-base/7 tracking-tight text-gray-300"
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
@ -59,7 +59,7 @@ export function Header() {
|
||||
return (
|
||||
<header>
|
||||
<nav>
|
||||
<Container className="relative z-50 flex justify-between py-8">
|
||||
<Container className="relative z-50 flex justify-between py-4">
|
||||
<div className="relative z-10 flex items-center gap-16">
|
||||
<Link href="/" aria-label="Home">
|
||||
<Logo className="h-10 w-auto" />
|
||||
@ -73,7 +73,7 @@ export function Header() {
|
||||
{({ open }) => (
|
||||
<>
|
||||
<PopoverButton
|
||||
className="relative z-10 -m-2 inline-flex items-center rounded-lg stroke-gray-900 p-2 hover:bg-gray-200/50 hover:stroke-gray-600 focus:not-data-focus:outline-hidden active:stroke-gray-900"
|
||||
className="relative z-10 -m-2 inline-flex items-center rounded-lg stroke-white p-2 hover:bg-gray-800/50 hover:stroke-gray-300 focus:not-data-focus:outline-hidden active:stroke-white"
|
||||
aria-label="Toggle site navigation"
|
||||
>
|
||||
{({ open }) =>
|
||||
@ -93,7 +93,7 @@ export function Header() {
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="fixed inset-0 z-0 bg-gray-300/60 backdrop-blur-sm"
|
||||
className="fixed inset-0 z-0 bg-black/60 backdrop-blur-sm"
|
||||
/>
|
||||
<PopoverPanel
|
||||
static
|
||||
|
@ -14,14 +14,14 @@ const navigation = [
|
||||
|
||||
export default function HeroHome() {
|
||||
return (
|
||||
<div className="bg-white">
|
||||
<div style={{ backgroundColor: '#121212' }}>
|
||||
<div className="">
|
||||
<div className="mx-auto max-w-7xl px-8 lg:px-8">
|
||||
<div className="mx-auto max-w-3xl text-center">
|
||||
<h1 className="text-3xl font-medium tracking-tight text-gray-900 lg:text-5xl">
|
||||
<h1 className="text-3xl font-medium tracking-tight text-white lg:text-5xl">
|
||||
Empowering Purpose-Driven Organizations.
|
||||
</h1>
|
||||
<p className="mt-8 lg:lg:text-lg text-base text-base text-gray-600">
|
||||
<p className="mt-8 lg:lg:text-lg text-base text-base text-gray-300">
|
||||
Welcome to <span className={`font-semibold ${gradientText}`}>EngageOS</span>: the first all-in-one, white-label engagement platform to mobilize communities, engage supporters, scale impact, and fundraise—at a fraction of the cost.
|
||||
</p>
|
||||
<div className="mt-12 flex items-center justify-center gap-x-6 relative z-10">
|
||||
|
File diff suppressed because one or more lines are too long
@ -17,7 +17,7 @@ export function NavLinks() {
|
||||
<Link
|
||||
key={label}
|
||||
href={href}
|
||||
className="relative -mx-3 -my-2 rounded-lg px-3 py-2 text-sm text-gray-700 transition-colors delay-150 hover:text-gray-900 hover:delay-0"
|
||||
className="relative -mx-3 -my-2 rounded-lg px-3 py-2 text-sm text-gray-300 transition-colors delay-150 hover:text-white hover:delay-0"
|
||||
onMouseEnter={() => {
|
||||
if (timeoutRef.current) {
|
||||
window.clearTimeout(timeoutRef.current)
|
||||
@ -33,7 +33,7 @@ export function NavLinks() {
|
||||
<AnimatePresence>
|
||||
{hoveredIndex === index && (
|
||||
<motion.span
|
||||
className="absolute inset-0 rounded-lg bg-gray-100"
|
||||
className="absolute inset-0 rounded-lg bg-gray-800"
|
||||
layoutId="hoverBackground"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1, transition: { duration: 0.15 } }}
|
||||
|
@ -126,14 +126,14 @@ function Plan({
|
||||
return (
|
||||
<section
|
||||
className={clsx(
|
||||
'flex flex-col overflow-hidden rounded-3xl p-6 shadow-lg shadow-gray-900/5',
|
||||
featured ? 'order-first bg-gray-900 lg:order-none' : 'bg-white',
|
||||
'flex flex-col overflow-hidden rounded-3xl p-6 shadow-lg shadow-black/20',
|
||||
featured ? 'order-first bg-cyan-600 lg:order-none' : 'bg-gray-900',
|
||||
)}
|
||||
>
|
||||
<h3
|
||||
className={clsx(
|
||||
'flex items-center text-sm font-semibold',
|
||||
featured ? 'text-white' : 'text-gray-900',
|
||||
featured ? 'text-white' : 'text-white',
|
||||
)}
|
||||
>
|
||||
<Logomark className={clsx('h-6 w-6 flex-none', logomarkClassName)} />
|
||||
@ -142,7 +142,7 @@ function Plan({
|
||||
<p
|
||||
className={clsx(
|
||||
'relative mt-5 flex text-3xl tracking-tight',
|
||||
featured ? 'text-white' : 'text-gray-900',
|
||||
featured ? 'text-white' : 'text-white',
|
||||
)}
|
||||
>
|
||||
{price.Monthly === price.Annually ? (
|
||||
@ -175,7 +175,7 @@ function Plan({
|
||||
<p
|
||||
className={clsx(
|
||||
'mt-3 text-sm',
|
||||
featured ? 'text-gray-300' : 'text-gray-700',
|
||||
featured ? 'text-gray-100' : 'text-gray-300',
|
||||
)}
|
||||
>
|
||||
{description}
|
||||
@ -186,8 +186,8 @@ function Plan({
|
||||
className={clsx(
|
||||
'-my-2 divide-y text-sm',
|
||||
featured
|
||||
? 'divide-gray-800 text-gray-300'
|
||||
: 'divide-gray-200 text-gray-700',
|
||||
? 'divide-gray-700 text-gray-100'
|
||||
: 'divide-gray-700 text-gray-300',
|
||||
)}
|
||||
>
|
||||
{features.map((feature) => (
|
||||
@ -195,7 +195,7 @@ function Plan({
|
||||
<CheckIcon
|
||||
className={clsx(
|
||||
'h-6 w-6 flex-none',
|
||||
featured ? 'text-white' : 'text-cyan-500',
|
||||
featured ? 'text-white' : 'text-cyan-400',
|
||||
)}
|
||||
/>
|
||||
<span className="ml-4">{feature}</span>
|
||||
@ -205,7 +205,7 @@ function Plan({
|
||||
</div>
|
||||
<Button
|
||||
href={button.href}
|
||||
color={featured ? 'cyan' : 'gray'}
|
||||
color={featured ? 'white' : 'cyan'}
|
||||
className="mt-6"
|
||||
aria-label={`Get started with the ${name} plan for ${price}`}
|
||||
>
|
||||
@ -224,19 +224,20 @@ export function Pricing() {
|
||||
<section
|
||||
id="pricing"
|
||||
aria-labelledby="pricing-title"
|
||||
className="border-t border-gray-200 bg-white py-24"
|
||||
className="border-t border-gray-800 py-24"
|
||||
style={{ backgroundColor: '#121212' }}
|
||||
>
|
||||
<Container>
|
||||
<div className="mx-auto max-w-2xl text-center">
|
||||
<h2
|
||||
id="pricing-title"
|
||||
className="text-3xl font-medium tracking-tight text-gray-900"
|
||||
className="text-3xl font-medium tracking-tight text-white"
|
||||
>
|
||||
Flat pricing, no management fees.
|
||||
</h2>
|
||||
<p className="mt-2 lg:text-lg text-base text-gray-600">
|
||||
Whether you’re one person trying to get ahead or a big firm trying
|
||||
to take over the world, we’ve got a plan for you.
|
||||
<p className="mt-2 lg:text-lg text-base text-gray-300">
|
||||
Whether you're one person trying to get ahead or a big firm trying
|
||||
to take over the world, we've got a plan for you.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@ -252,7 +253,7 @@ export function Pricing() {
|
||||
key={period}
|
||||
value={period}
|
||||
className={clsx(
|
||||
'cursor-pointer border border-gray-300 px-[calc(--spacing(3)-1px)] py-[calc(--spacing(2)-1px)] text-sm text-gray-700 transition-colors hover:border-gray-400 data-focus:outline-2 data-focus:outline-offset-2',
|
||||
'cursor-pointer border border-gray-600 px-[calc(--spacing(3)-1px)] py-[calc(--spacing(2)-1px)] text-sm text-gray-300 transition-colors hover:border-gray-500 data-focus:outline-2 data-focus:outline-offset-2 bg-gray-800',
|
||||
period === 'Monthly'
|
||||
? 'rounded-l-lg'
|
||||
: '-ml-px rounded-r-lg',
|
||||
@ -286,7 +287,7 @@ export function Pricing() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mx-auto bg-white mt-16 grid max-w-2xl grid-cols-1 items-start gap-x-8 gap-y-10 sm:mt-20 lg:max-w-none lg:grid-cols-3">
|
||||
<div className="mx-auto mt-16 grid max-w-2xl grid-cols-1 items-start gap-x-8 gap-y-10 sm:mt-20 lg:max-w-none lg:grid-cols-3">
|
||||
{plans.map((plan) => (
|
||||
<Plan key={plan.name} {...plan} activePeriod={activePeriod} />
|
||||
))}
|
||||
|
@ -16,13 +16,13 @@ interface Review {
|
||||
const reviews: Array<Review> = [
|
||||
{
|
||||
title: 'A true game-changer for nonprofits.',
|
||||
body: 'EngageOS allowed us to centralize our volunteer hub, training, and crowdfunding into one platform. We’ve seen a 3x jump in community engagement.',
|
||||
body: 'EngageOS allowed us to centralize our volunteer hub, training, and crowdfunding into one platform. We have seen a 3x jump in community engagement.',
|
||||
author: 'Sarah D., Program Director at WomenRise',
|
||||
rating: 5,
|
||||
},
|
||||
{
|
||||
title: 'No tech team needed.',
|
||||
body: 'Launching our own branded platform felt intimidating—until EngageOS. It’s intuitive, scalable, and beautifully designed.',
|
||||
body: 'Launching our own branded platform felt intimidating—until EngageOS. It is intuitive, scalable, and beautifully designed.',
|
||||
author: 'Ahmed K., Director at The Green Schools Alliance',
|
||||
rating: 5,
|
||||
},
|
||||
@ -52,7 +52,7 @@ const reviews: Array<Review> = [
|
||||
},
|
||||
{
|
||||
title: 'Highly recommend for grassroots orgs.',
|
||||
body: 'Even with limited staff, we launched a branded hub in 10 days. It’s helping our community organize and train in ways we never imagined.',
|
||||
body: 'Even with limited staff, we launched a branded hub in 10 days. It is helping our community organize and train in ways we never imagined.',
|
||||
author: 'Tania B., Founder of SpeakUp Brazil',
|
||||
rating: 5,
|
||||
},
|
||||
@ -69,14 +69,13 @@ const reviews: Array<Review> = [
|
||||
rating: 5,
|
||||
},
|
||||
{
|
||||
title: 'This platform *is* our movement.',
|
||||
title: 'This platform is our movement.',
|
||||
body: 'Before EngageOS, our digital presence was scattered. Now we have a true home where our supporters connect and take action.',
|
||||
author: 'Ravi P., Strategy Director at Clean Energy for All',
|
||||
rating: 5,
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
function StarIcon(props: React.ComponentPropsWithoutRef<'svg'>) {
|
||||
return (
|
||||
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
|
||||
@ -119,21 +118,21 @@ function Review({
|
||||
return (
|
||||
<figure
|
||||
className={clsx(
|
||||
'animate-fade-in rounded-3xl bg-white p-6 opacity-0 shadow-md shadow-gray-900/5',
|
||||
'animate-fade-in rounded-3xl bg-gray-900 p-6 opacity-0 shadow-md shadow-black/20',
|
||||
className,
|
||||
)}
|
||||
style={{ animationDelay }}
|
||||
{...props}
|
||||
>
|
||||
<blockquote className="text-gray-900">
|
||||
<blockquote className="text-white">
|
||||
<StarRating rating={rating} />
|
||||
<p className="mt-4 lg:text-lg text-base/6 font-semibold before:content-['“'] after:content-['”']">
|
||||
{title}
|
||||
<p className="mt-4 lg:text-lg text-base/6 font-semibold">
|
||||
"{title}"
|
||||
</p>
|
||||
<p className="mt-3 text-base/7">{body}</p>
|
||||
</blockquote>
|
||||
<figcaption className="mt-3 text-sm text-gray-600 before:content-['–_']">
|
||||
{author}
|
||||
<figcaption className="mt-3 text-sm text-gray-300">
|
||||
— {author}
|
||||
</figcaption>
|
||||
</figure>
|
||||
)
|
||||
@ -241,8 +240,8 @@ function ReviewGrid() {
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<div className="pointer-events-none absolute inset-x-0 top-0 h-32 bg-linear-to-b from-gray-50" />
|
||||
<div className="pointer-events-none absolute inset-x-0 bottom-0 h-32 bg-linear-to-t from-gray-50" />
|
||||
<div className="pointer-events-none absolute inset-x-0 top-0 h-32 bg-gradient-to-b from-gray-950" />
|
||||
<div className="pointer-events-none absolute inset-x-0 bottom-0 h-32 bg-gradient-to-t from-gray-950" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -253,15 +252,16 @@ export function Reviews() {
|
||||
id="reviews"
|
||||
aria-labelledby="reviews-title"
|
||||
className="pt-20 pb-16 sm:pt-32 sm:pb-24"
|
||||
style={{ backgroundColor: '#121212' }}
|
||||
>
|
||||
<Container>
|
||||
<h2
|
||||
id="reviews-title"
|
||||
className="text-3xl font-medium tracking-tight text-gray-900 sm:text-center"
|
||||
className="text-3xl font-medium tracking-tight text-white sm:text-center"
|
||||
>
|
||||
Everyone is changing their life with EngageOS.
|
||||
</h2>
|
||||
<p className="mt-2 lg:text-lg text-base text-gray-600 sm:text-center">
|
||||
<p className="mt-2 lg:text-lg text-base text-gray-300 sm:text-center">
|
||||
Thousands of people have doubled their net-worth in the last 30 days.
|
||||
</p>
|
||||
<ReviewGrid />
|
||||
|
51
src/components/Spotlight.tsx
Normal file
51
src/components/Spotlight.tsx
Normal file
@ -0,0 +1,51 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Spotlight } from "@/components/ui/spotlight";
|
||||
import { Logomark } from "@/components/Logo";
|
||||
import { Button } from "@/components/Button";
|
||||
|
||||
export function SpotlightPreview() {
|
||||
return (
|
||||
<div className="relative flex h-[40rem] w-full overflow-hidden rounded-md bg-transparent 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">
|
||||
<div className="flex justify-center mb-6">
|
||||
<div className="mb-4 relative rounded-full px-3 py-1 text-sm/6 text-gray-600 ring-1 ring-gray-900/10 hover:ring-gray-900/20">
|
||||
Announcing The New TF Marketplace.{' '}
|
||||
<a href="#" className="font-semibold text-white hover:text-gray-200">
|
||||
<span aria-hidden="true" className="absolute inset-0" />
|
||||
Read more <span aria-hidden="true">→</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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">
|
||||
Built by Everyone <br /> for Everyone.
|
||||
</h1>
|
||||
<p className="mx-auto mt-8 max-w-lg text-center text-base lg:text-xl font-light text-neutral-300">
|
||||
ThreeFold is a fully operational, decentralized internet infrastructure – deployed locally, scalable globally, and owned and powered by the people.
|
||||
</p>
|
||||
<div className="mt-8 flex flex-col sm:flex-row justify-center gap-4">
|
||||
<Button href="/login" variant="outline" color="gray">
|
||||
Start Building
|
||||
</Button>
|
||||
<Button href="#" variant="outline" color="gray">
|
||||
Start Hosting
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
33
src/components/StackSection.tsx
Normal file
33
src/components/StackSection.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
"use client";
|
||||
|
||||
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 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) */}
|
||||
<div className="text-center lg:text-left lg:col-span-1 order-1 lg:order-1">
|
||||
<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-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) */}
|
||||
<div className="lg:col-span-2 flex items-center justify-center lg:justify-start order-2 lg:order-2">
|
||||
<StackedCubes />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
@ -19,11 +19,11 @@ const stats = [
|
||||
|
||||
export default function Tractions() {
|
||||
return (
|
||||
<div className="relative bg-white py-12">
|
||||
<div className="relative py-12" style={{ backgroundColor: '#121212' }}>
|
||||
<div className="mx-auto grid max-w-7xl lg:grid-cols-2">
|
||||
{/* LEFT IMAGE + LOGO */}
|
||||
<div className="flex flex-col items-center lg:items-start gap-8 px-6 pb-12 lg:px-8">
|
||||
<div className="w-full ring-1 ring-black/15 rounded-3xl overflow-hidden max-lg:rounded-t-4xl lg:rounded-tl-4xl">
|
||||
<div className="w-full ring-1 ring-gray-700 rounded-3xl overflow-hidden max-lg:rounded-t-4xl lg:rounded-tl-4xl">
|
||||
<Image
|
||||
alt=""
|
||||
src={Traction}
|
||||
@ -56,18 +56,18 @@ export default function Tractions() {
|
||||
{/* RIGHT TEXT BLOCK */}
|
||||
<div className="px-6 lg:px-8">
|
||||
<div className="mx-auto max-w-2xl lg:mr-0 lg:max-w-lg">
|
||||
<h2 className="text-base/8 font-semibold text-gray-900">Our track record</h2>
|
||||
<p className="mt-2 text-3xl font-medium tracking-tight text-gray-900 sm:text-4xl">
|
||||
<h2 className="text-base/8 font-semibold text-white">Our track record</h2>
|
||||
<p className="mt-2 text-3xl font-medium tracking-tight text-white sm:text-4xl">
|
||||
Trusted by Changemakers worldwide
|
||||
</p>
|
||||
<p className="mt-6 lg:text-lg text-base text-gray-600">
|
||||
<p className="mt-6 lg:text-lg text-base text-gray-300">
|
||||
EngageOS powers the digital headquarters for over 300,000 users across 50+ countries. From grassroots NGOs to global movements, our platform is built to scale impact, not just numbers.
|
||||
</p>
|
||||
<dl className="mt-16 grid max-w-xl grid-cols-1 gap-8 sm:mt-20 sm:grid-cols-2 xl:mt-16">
|
||||
{stats.map((stat) => (
|
||||
<div key={stat.id} className="flex flex-col gap-y-3 border-l border-gray-900/10 pl-6">
|
||||
<dt className="text-sm/6 text-gray-600">{stat.name}</dt>
|
||||
<dd className="order-first text-3xl font-semibold tracking-tight text-gray-900">{stat.value}</dd>
|
||||
<div key={stat.id} className="flex flex-col gap-y-3 border-l border-gray-600 pl-6">
|
||||
<dt className="text-sm/6 text-gray-300">{stat.name}</dt>
|
||||
<dd className="order-first text-3xl font-semibold tracking-tight text-white">{stat.value}</dd>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
|
147
src/components/ui/Cube.tsx
Normal file
147
src/components/ui/Cube.tsx
Normal file
@ -0,0 +1,147 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
interface CubeProps {
|
||||
title: string;
|
||||
descriptionTitle: string;
|
||||
description: string;
|
||||
isActive: boolean;
|
||||
index: number;
|
||||
onHover: () => void;
|
||||
onLeave: () => void;
|
||||
}
|
||||
|
||||
const CubeSvg: React.FC<React.SVGProps<SVGSVGElement> & { index: number }> = ({ index, ...props }) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="507"
|
||||
height="234"
|
||||
fill="none"
|
||||
viewBox="0 0 507 234"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
fill={`url(#cube-gradient-${index})`}
|
||||
d="M491.651 144.747L287.198 227.339C265.219 236.22 241.783 236.22 219.802 227.339L15.3486 144.747C-5.11621 136.479 -5.11621 97.5191 15.3486 89.2539L219.802 6.65884C241.783 -2.21961 265.219 -2.21961 287.198 6.65884L491.651 89.2539C512.116 97.5191 512.116 136.479 491.651 144.747Z"
|
||||
/>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id={`cube-gradient-${index}`}
|
||||
x1="185.298"
|
||||
x2="185.298"
|
||||
y1="-27.5515"
|
||||
y2="206.448"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop />
|
||||
<stop offset="1" stopColor="#3F3B3E" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export function Cube({ title, descriptionTitle, description, isActive, index, onHover, onLeave }: CubeProps) {
|
||||
return (
|
||||
<div className="relative flex flex-col items-center">
|
||||
<motion.div
|
||||
className="relative cursor-pointer"
|
||||
onMouseEnter={onHover}
|
||||
onMouseLeave={onLeave}
|
||||
style={{
|
||||
zIndex: 10 - index,
|
||||
}}
|
||||
animate={{
|
||||
scale: isActive ? 1.05 : 1,
|
||||
}}
|
||||
transition={{
|
||||
duration: 0.3,
|
||||
ease: "easeOut",
|
||||
}}
|
||||
>
|
||||
{/* SVG Cube */}
|
||||
<CubeSvg
|
||||
index={index}
|
||||
className="w-48 sm:w-64 lg:w-80 h-auto drop-shadow-lg opacity-50"
|
||||
style={{
|
||||
filter: isActive ? 'brightness(1.2) drop-shadow(0 0 20px rgba(156, 163, 175, 0.5))' : 'brightness(0.9)',
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Title overlay */}
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<h3
|
||||
className="text-white text-sm lg:text-base font-medium text-center px-4 drop-shadow-lg"
|
||||
style={{
|
||||
transform: 'rotate(0deg) skewX(0deg)',
|
||||
transformOrigin: 'center'
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
{/* Description with arrow line - Desktop */}
|
||||
{isActive && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
className="hidden lg:block absolute left-full top-1/2 -translate-y-1/2 z-50"
|
||||
>
|
||||
{/* Arrow line */}
|
||||
<svg
|
||||
className="absolute left-0 top-1/2 -translate-y-1/2"
|
||||
width="120"
|
||||
height="2"
|
||||
viewBox="0 0 120 2"
|
||||
fill="none"
|
||||
>
|
||||
<line
|
||||
x1="0"
|
||||
y1="1"
|
||||
x2="120"
|
||||
y2="1"
|
||||
stroke="white"
|
||||
strokeWidth="1"
|
||||
opacity="0.6"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
{/* Description text */}
|
||||
<div className="ml-32 w-80">
|
||||
<h4 className="text-white text-base font-semibold mb-2">
|
||||
{descriptionTitle}
|
||||
</h4>
|
||||
<p className="text-white text-sm leading-relaxed font-light">
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{/* Description for Mobile - Below cube */}
|
||||
{isActive && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: 10 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
className="lg:hidden absolute top-full left-1/2 -translate-x-1/2 mt-8 z-50"
|
||||
>
|
||||
<div className="w-64 sm:w-80 px-4">
|
||||
<h4 className="text-white text-base font-semibold mb-2 text-center">
|
||||
{descriptionTitle}
|
||||
</h4>
|
||||
<p className="text-white text-sm leading-relaxed font-light text-center">
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</motion.div>
|
||||
</div>
|
||||
);
|
||||
}
|
58
src/components/ui/Spotlight.tsx
Normal file
58
src/components/ui/Spotlight.tsx
Normal file
@ -0,0 +1,58 @@
|
||||
"use client";
|
||||
|
||||
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>
|
||||
);
|
||||
};
|
61
src/components/ui/StackedCubes.tsx
Normal file
61
src/components/ui/StackedCubes.tsx
Normal file
@ -0,0 +1,61 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Cube } from "@/components/ui/Cube";
|
||||
|
||||
const stackData = [
|
||||
{
|
||||
id: "network",
|
||||
title: "Network",
|
||||
descriptionTitle: "Secure Network",
|
||||
description:
|
||||
"End-to-end encrypted overlay network, always looking for the shortest possible path between participants. Logical Internet address securely linked to a private key. Unlimited scale and performance optimizations.",
|
||||
position: "top",
|
||||
},
|
||||
{
|
||||
id: "data",
|
||||
title: "Data",
|
||||
descriptionTitle: "Unbreakable Data",
|
||||
description:
|
||||
"Private, distributed, and AI-native storage with user sovereignty at its core. End-to-end encryption and redundancy, with no central control. Optimized for performance and sustainability, far surpassing traditional cloud.",
|
||||
position: "middle",
|
||||
},
|
||||
{
|
||||
id: "compute",
|
||||
title: "Compute",
|
||||
descriptionTitle: "Bare Metal OS",
|
||||
description:
|
||||
"Zero OS, an efficient and secure operating system, runs directly on the hardware – enabling an autonomous cloud. Can run any Web2, Web3, or AI workload at the edge of the Internet, with more scalability and reliability.",
|
||||
position: "bottom",
|
||||
},
|
||||
];
|
||||
|
||||
export function StackedCubes() {
|
||||
const [active, setActive] = useState<string | null>("compute");
|
||||
|
||||
return (
|
||||
<div className="relative w-full flex items-center justify-center lg:justify-start min-h-[600px] sm:min-h-[700px] lg:min-h-[600px]">
|
||||
<div className="relative ml-0 sm:ml-4 lg:ml-8 flex flex-col items-center -space-y-4 sm:-space-y-6 lg:-space-y-8">
|
||||
{stackData.map((layer, index) => (
|
||||
<div
|
||||
key={layer.id}
|
||||
className="relative"
|
||||
style={{
|
||||
zIndex: 10 - index,
|
||||
}}
|
||||
>
|
||||
<Cube
|
||||
title={layer.title}
|
||||
descriptionTitle={layer.descriptionTitle}
|
||||
description={layer.description}
|
||||
isActive={active === layer.id}
|
||||
index={index}
|
||||
onHover={() => setActive(layer.id)}
|
||||
onLeave={() => setActive("compute")}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
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 "tw-animate-css";
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
@plugin '@tailwindcss/forms';
|
||||
|
||||
@ -41,22 +44,29 @@
|
||||
--radius-4xl: 2rem;
|
||||
--radius-5xl: 2.5rem;
|
||||
|
||||
--color-gray-50: oklch(0.985 0 0);
|
||||
--color-gray-100: oklch(0.97 0 0);
|
||||
--color-gray-200: oklch(0.922 0 0);
|
||||
--color-gray-300: oklch(0.87 0 0);
|
||||
--color-gray-400: oklch(0.708 0 0);
|
||||
/* Dark mode color palette - inverted grays */
|
||||
--color-gray-50: oklch(0.145 0 0);
|
||||
--color-gray-100: oklch(0.205 0 0);
|
||||
--color-gray-200: oklch(0.269 0 0);
|
||||
--color-gray-300: oklch(0.371 0 0);
|
||||
--color-gray-400: oklch(0.439 0 0);
|
||||
--color-gray-500: oklch(0.556 0 0);
|
||||
--color-gray-600: oklch(0.439 0 0);
|
||||
--color-gray-700: oklch(0.371 0 0);
|
||||
--color-gray-800: oklch(0.269 0 0);
|
||||
--color-gray-900: oklch(0.205 0 0);
|
||||
--color-gray-950: oklch(0.145 0 0);
|
||||
--color-gray-600: oklch(0.708 0 0);
|
||||
--color-gray-700: oklch(0.87 0 0);
|
||||
--color-gray-800: oklch(0.922 0 0);
|
||||
--color-gray-900: oklch(0.97 0 0);
|
||||
--color-gray-950: oklch(0.985 0 0);
|
||||
|
||||
/* Custom dark background */
|
||||
--color-dark-bg: #121212;
|
||||
|
||||
--font-sans: var(--font-inter);
|
||||
|
||||
--container-2xl: 40rem;
|
||||
|
||||
/* 3D perspective utilities */
|
||||
--perspective-1000: 1000px;
|
||||
|
||||
@keyframes fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
@ -74,10 +84,148 @@
|
||||
|
||||
@theme inline {
|
||||
--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 {
|
||||
100% {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.bg-stat-gradient {
|
||||
background: linear-gradient(to bottom, rgba(17, 17, 17, 0.5), rgba(50, 48, 49, 0.5));
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user