Compare commits

..

No commits in common. "main" and "main" have entirely different histories.
main ... main

27 changed files with 503 additions and 3166 deletions

View File

@ -1,21 +0,0 @@
{
"$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"
}

View File

@ -1,6 +0,0 @@
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
View File

@ -2,4 +2,4 @@
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
// see https://nextjs.org/docs/basic-features/typescript for more information.

2154
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -12,24 +12,17 @@
"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.3.23",
"@types/react-dom": "^18.3.7",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"framer-motion": "^12.23.12",
"lucide-react": "^0.536.0",
"next": "15.0.3",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"tailwind-merge": "^2.6.0",
"@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",
"tailwindcss": "^4.1.7",
"three": "^0.179.1",
"three-globe": "^2.27.2",
"typescript": "^5.3.3",
"use-debounce": "^10.0.0"
},
@ -38,7 +31,6 @@
"eslint-config-next": "^14.0.4",
"prettier": "^3.3.2",
"prettier-plugin-tailwindcss": "^0.6.11",
"sharp": "0.33.1",
"tw-animate-css": "^1.3.6"
"sharp": "0.33.1"
}
}

View File

@ -8,15 +8,18 @@ import { SecondaryFeatures } from '@/components/SecondaryFeatures'
import Tractions from '@/components/Tractions'
import Benefits from '@/components/Benefits'
import Cta from '@/components/Cta'
import { GlobeDemo } from '@/components/GlobeDemo'
import { SpotlightPreview } from '@/components/Spotlight'
import { StackSectionPreview } from '@/components/StackSection'
export default function Home() {
return (
<>
<SpotlightPreview />
<StackSectionPreview />
<Hero />
<Tractions />
<Benefits />
<SecondaryFeatures />
<Reviews />
<Pricing />
<Cta />
<Faqs />
</>
)
}

View File

@ -25,8 +25,8 @@ export default function RootLayout({
children: React.ReactNode
}) {
return (
<html lang="en" className={clsx('antialiased', inter.variable)} style={{ backgroundColor: '#121212' }}>
<body style={{ backgroundColor: '#121212', color: '#ffffff' }}>{children}</body>
<html lang="en" className={clsx('bg-white antialiased', inter.variable)}>
<body>{children}</body>
</html>
)
}

View File

@ -8,78 +8,78 @@ import Benefits4 from '@/images/benefits/benefits4.jpg'
export default function Benefits() {
return (
<div style={{ backgroundColor: '#121212' }} className="py-12">
<div className="bg-white 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-white">
<h2 className="lg:text-4xl text-3xl font-medium tracking-tight text-gray-900">
Built Different. For a Change.
</h2>
<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 className="mt-6 lg:text-lg text-base text-gray-600">
EngageOS isnt just another tech platform its 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-gray-900 ring-1 ring-gray-700 max-lg:rounded-t-4xl lg:rounded-tl-4xl">
<div className="overflow-hidden rounded-lg bg-white ring-1 ring-black/15 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-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">
<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">
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-gray-900 ring-1 ring-gray-700 lg:rounded-tr-4xl">
<div className="overflow-hidden rounded-lg bg-white ring-1 ring-black/15 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-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">
<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">
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-gray-900 ring-1 ring-gray-700 lg:rounded-bl-4xl">
<div className="overflow-hidden rounded-lg bg-white ring-1 ring-black/15 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-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.
<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 dont 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-gray-900 ring-1 ring-gray-700 max-lg:rounded-b-4xl lg:rounded-br-4xl">
<div className="overflow-hidden rounded-lg bg-white ring-1 ring-black/15 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-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">
<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">
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>

View File

@ -1,6 +1,6 @@
export default function Cta() {
return (
<div style={{ backgroundColor: '#121212' }}>
<div className="bg-white">
<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%" stopColor="#caa5f0" />
<stop offset="50%" stopColor="#8f79f9" />
<stop offset="100%" stopColor="#5d84e1" />
<stop offset="0%" stop-color="#caa5f0" />
<stop offset="50%" stop-color="#8f79f9" />
<stop offset="100%" stop-color="#5d84e1" />
</radialGradient>
</defs>

View File

@ -22,11 +22,11 @@ function QrCodeBorder(props: React.ComponentPropsWithoutRef<'svg'>) {
export function Footer() {
return (
<footer className="border-t border-gray-700" style={{ backgroundColor: '#121212' }}>
<footer className="border-t border-gray-200">
<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-white">
<div className="flex items-center text-gray-900">
<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-800 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-100 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-600 transition-colors group-hover:stroke-cyan-500" />
<QrCodeBorder className="absolute inset-0 h-full w-full stroke-gray-300 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-white">
<p className="text-base font-semibold text-gray-900">
<Link href="#">
<span className="absolute inset-0 sm:rounded-2xl" />
Download the app
</Link>
</p>
<p className="mt-1 text-sm text-gray-300">
<p className="mt-1 text-sm text-gray-700">
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-700 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-200 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-400 md:mt-0">
<p className="mt-6 text-sm text-gray-500 md:mt-0">
&copy; Copyright {new Date().getFullYear()}. All rights reserved.
</p>
</div>

View File

@ -1,111 +0,0 @@
"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

@ -49,7 +49,7 @@ function MobileNavLink(
return (
<PopoverButton
as={Link}
className="block text-base/7 tracking-tight text-gray-300"
className="block text-base/7 tracking-tight text-gray-700"
{...props}
/>
)
@ -59,7 +59,7 @@ export function Header() {
return (
<header>
<nav>
<Container className="relative z-50 flex justify-between py-4">
<Container className="relative z-50 flex justify-between py-8">
<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-white p-2 hover:bg-gray-800/50 hover:stroke-gray-300 focus:not-data-focus:outline-hidden active:stroke-white"
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"
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-black/60 backdrop-blur-sm"
className="fixed inset-0 z-0 bg-gray-300/60 backdrop-blur-sm"
/>
<PopoverPanel
static

View File

@ -14,14 +14,14 @@ const navigation = [
export default function HeroHome() {
return (
<div style={{ backgroundColor: '#121212' }}>
<div className="bg-white">
<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-white lg:text-5xl">
<h1 className="text-3xl font-medium tracking-tight text-gray-900 lg:text-5xl">
Empowering Purpose-Driven Organizations.
</h1>
<p className="mt-8 lg:lg:text-lg text-base text-base text-gray-300">
<p className="mt-8 lg:lg:text-lg text-base text-base text-gray-600">
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 fundraiseat 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

View File

@ -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-300 transition-colors delay-150 hover:text-white hover:delay-0"
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"
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-800"
className="absolute inset-0 rounded-lg bg-gray-100"
layoutId="hoverBackground"
initial={{ opacity: 0 }}
animate={{ opacity: 1, transition: { duration: 0.15 } }}

View File

@ -126,14 +126,14 @@ function Plan({
return (
<section
className={clsx(
'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',
'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',
)}
>
<h3
className={clsx(
'flex items-center text-sm font-semibold',
featured ? 'text-white' : 'text-white',
featured ? 'text-white' : 'text-gray-900',
)}
>
<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-white',
featured ? 'text-white' : 'text-gray-900',
)}
>
{price.Monthly === price.Annually ? (
@ -175,7 +175,7 @@ function Plan({
<p
className={clsx(
'mt-3 text-sm',
featured ? 'text-gray-100' : 'text-gray-300',
featured ? 'text-gray-300' : 'text-gray-700',
)}
>
{description}
@ -186,8 +186,8 @@ function Plan({
className={clsx(
'-my-2 divide-y text-sm',
featured
? 'divide-gray-700 text-gray-100'
: 'divide-gray-700 text-gray-300',
? 'divide-gray-800 text-gray-300'
: 'divide-gray-200 text-gray-700',
)}
>
{features.map((feature) => (
@ -195,7 +195,7 @@ function Plan({
<CheckIcon
className={clsx(
'h-6 w-6 flex-none',
featured ? 'text-white' : 'text-cyan-400',
featured ? 'text-white' : 'text-cyan-500',
)}
/>
<span className="ml-4">{feature}</span>
@ -205,7 +205,7 @@ function Plan({
</div>
<Button
href={button.href}
color={featured ? 'white' : 'cyan'}
color={featured ? 'cyan' : 'gray'}
className="mt-6"
aria-label={`Get started with the ${name} plan for ${price}`}
>
@ -224,20 +224,19 @@ export function Pricing() {
<section
id="pricing"
aria-labelledby="pricing-title"
className="border-t border-gray-800 py-24"
style={{ backgroundColor: '#121212' }}
className="border-t border-gray-200 bg-white py-24"
>
<Container>
<div className="mx-auto max-w-2xl text-center">
<h2
id="pricing-title"
className="text-3xl font-medium tracking-tight text-white"
className="text-3xl font-medium tracking-tight text-gray-900"
>
Flat pricing, no management fees.
</h2>
<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 className="mt-2 lg:text-lg text-base text-gray-600">
Whether youre one person trying to get ahead or a big firm trying
to take over the world, weve got a plan for you.
</p>
</div>
@ -253,7 +252,7 @@ export function Pricing() {
key={period}
value={period}
className={clsx(
'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',
'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',
period === 'Monthly'
? 'rounded-l-lg'
: '-ml-px rounded-r-lg',
@ -287,7 +286,7 @@ export function Pricing() {
</div>
</div>
<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">
<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">
{plans.map((plan) => (
<Plan key={plan.name} {...plan} activePeriod={activePeriod} />
))}

View File

@ -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 have seen a 3x jump in community engagement.',
body: 'EngageOS allowed us to centralize our volunteer hub, training, and crowdfunding into one platform. Weve 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 is intuitive, scalable, and beautifully designed.',
body: 'Launching our own branded platform felt intimidating—until EngageOS. Its 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 is helping our community organize and train in ways we never imagined.',
body: 'Even with limited staff, we launched a branded hub in 10 days. Its helping our community organize and train in ways we never imagined.',
author: 'Tania B., Founder of SpeakUp Brazil',
rating: 5,
},
@ -69,13 +69,14 @@ 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}>
@ -118,21 +119,21 @@ function Review({
return (
<figure
className={clsx(
'animate-fade-in rounded-3xl bg-gray-900 p-6 opacity-0 shadow-md shadow-black/20',
'animate-fade-in rounded-3xl bg-white p-6 opacity-0 shadow-md shadow-gray-900/5',
className,
)}
style={{ animationDelay }}
{...props}
>
<blockquote className="text-white">
<blockquote className="text-gray-900">
<StarRating rating={rating} />
<p className="mt-4 lg:text-lg text-base/6 font-semibold">
"{title}"
<p className="mt-4 lg:text-lg text-base/6 font-semibold before:content-['“'] after:content-['”']">
{title}
</p>
<p className="mt-3 text-base/7">{body}</p>
</blockquote>
<figcaption className="mt-3 text-sm text-gray-300">
{author}
<figcaption className="mt-3 text-sm text-gray-600 before:content-['_']">
{author}
</figcaption>
</figure>
)
@ -240,8 +241,8 @@ function ReviewGrid() {
/>
</>
)}
<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 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>
)
}
@ -252,16 +253,15 @@ 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-white sm:text-center"
className="text-3xl font-medium tracking-tight text-gray-900 sm:text-center"
>
Everyone is changing their life with EngageOS.
</h2>
<p className="mt-2 lg:text-lg text-base text-gray-300 sm:text-center">
<p className="mt-2 lg:text-lg text-base text-gray-600 sm:text-center">
Thousands of people have doubled their net-worth in the last 30 days.
</p>
<ReviewGrid />

View File

@ -1,54 +0,0 @@
"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">&rarr;</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>
<Button href="#" variant="solid" color="white">
How it Works
</Button>
</div>
</div>
</div>
);
}

View File

@ -1,29 +0,0 @@
"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">
<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-gray-700 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>
</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>
);
}

View File

@ -19,11 +19,11 @@ const stats = [
export default function Tractions() {
return (
<div className="relative py-12" style={{ backgroundColor: '#121212' }}>
<div className="relative bg-white py-12">
<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-gray-700 rounded-3xl overflow-hidden max-lg:rounded-t-4xl lg:rounded-tl-4xl">
<div className="w-full ring-1 ring-black/15 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-white">Our track record</h2>
<p className="mt-2 text-3xl font-medium tracking-tight text-white sm:text-4xl">
<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">
Trusted by Changemakers worldwide
</p>
<p className="mt-6 lg:text-lg text-base text-gray-300">
<p className="mt-6 lg:text-lg text-base text-gray-600">
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-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 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>
))}
</dl>

View File

@ -1,147 +0,0 @@
"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>
);
}

View File

@ -1,309 +0,0 @@
"use client";
import { useEffect, useRef, useState } from "react";
import { Color, Scene, Fog, PerspectiveCamera, Vector3 } from "three";
import ThreeGlobe from "three-globe";
import { useThree, Canvas, extend } from "@react-three/fiber";
import { OrbitControls } from "@react-three/drei";
import countries from "@/data/globe.json";
declare module "@react-three/fiber" {
interface ThreeElements {
threeGlobe: ThreeElements["mesh"] & {
new (): ThreeGlobe;
};
}
}
extend({ ThreeGlobe: ThreeGlobe });
const RING_PROPAGATION_SPEED = 3;
const aspect = 1.2;
const cameraZ = 300;
type Position = {
order: number;
startLat: number;
startLng: number;
endLat: number;
endLng: number;
arcAlt: number;
color: string;
};
export type GlobeConfig = {
pointSize?: number;
globeColor?: string;
showAtmosphere?: boolean;
atmosphereColor?: string;
atmosphereAltitude?: number;
emissive?: string;
emissiveIntensity?: number;
shininess?: number;
polygonColor?: string;
ambientLight?: string;
directionalLeftLight?: string;
directionalTopLight?: string;
pointLight?: string;
arcTime?: number;
arcLength?: number;
rings?: number;
maxRings?: number;
initialPosition?: {
lat: number;
lng: number;
};
autoRotate?: boolean;
autoRotateSpeed?: number;
};
interface WorldProps {
globeConfig: GlobeConfig;
data: Position[];
}
let numbersOfRings = [0];
export function Globe({ globeConfig, data }: WorldProps) {
const globeRef = useRef<ThreeGlobe | null>(null);
const groupRef = useRef();
const [isInitialized, setIsInitialized] = useState(false);
const defaultProps = {
pointSize: 1,
atmosphereColor: "#ffffff",
showAtmosphere: true,
atmosphereAltitude: 0.1,
polygonColor: "rgba(255,255,255,0.7)",
globeColor: "#1d072e",
emissive: "#000000",
emissiveIntensity: 0.1,
shininess: 0.9,
arcTime: 2000,
arcLength: 0.9,
rings: 1,
maxRings: 3,
...globeConfig,
};
// Initialize globe only once
useEffect(() => {
if (!globeRef.current && groupRef.current) {
globeRef.current = new ThreeGlobe();
(groupRef.current as any).add(globeRef.current);
setIsInitialized(true);
}
}, []);
// Build material when globe is initialized or when relevant props change
useEffect(() => {
if (!globeRef.current || !isInitialized) return;
const globeMaterial = globeRef.current.globeMaterial() as unknown as {
color: Color;
emissive: Color;
emissiveIntensity: number;
shininess: number;
};
globeMaterial.color = new Color(globeConfig.globeColor);
globeMaterial.emissive = new Color(globeConfig.emissive);
globeMaterial.emissiveIntensity = globeConfig.emissiveIntensity || 0.1;
globeMaterial.shininess = globeConfig.shininess || 0.9;
}, [
isInitialized,
globeConfig.globeColor,
globeConfig.emissive,
globeConfig.emissiveIntensity,
globeConfig.shininess,
]);
// Build data when globe is initialized or when data changes
useEffect(() => {
if (!globeRef.current || !isInitialized || !data) return;
const arcs = data;
let points = [];
for (let i = 0; i < arcs.length; i++) {
const arc = arcs[i];
const rgb = hexToRgb(arc.color) as { r: number; g: number; b: number };
points.push({
size: defaultProps.pointSize,
order: arc.order,
color: arc.color,
lat: arc.startLat,
lng: arc.startLng,
});
points.push({
size: defaultProps.pointSize,
order: arc.order,
color: arc.color,
lat: arc.endLat,
lng: arc.endLng,
});
}
// remove duplicates for same lat and lng
const filteredPoints = points.filter(
(v, i, a) =>
a.findIndex((v2) =>
["lat", "lng"].every(
(k) => v2[k as "lat" | "lng"] === v[k as "lat" | "lng"],
),
) === i,
);
globeRef.current
.hexPolygonsData(countries.features)
.hexPolygonResolution(3)
.hexPolygonMargin(0.7)
.showAtmosphere(defaultProps.showAtmosphere)
.atmosphereColor(defaultProps.atmosphereColor)
.atmosphereAltitude(defaultProps.atmosphereAltitude)
.hexPolygonColor(() => defaultProps.polygonColor);
globeRef.current
.arcsData(data)
.arcStartLat((d) => (d as { startLat: number }).startLat * 1)
.arcStartLng((d) => (d as { startLng: number }).startLng * 1)
.arcEndLat((d) => (d as { endLat: number }).endLat * 1)
.arcEndLng((d) => (d as { endLng: number }).endLng * 1)
.arcColor((e: any) => (e as { color: string }).color)
.arcAltitude((e) => (e as { arcAlt: number }).arcAlt * 1)
.arcStroke(() => [0.32, 0.28, 0.3][Math.round(Math.random() * 2)])
.arcDashLength(defaultProps.arcLength)
.arcDashInitialGap((e) => (e as { order: number }).order * 1)
.arcDashGap(15)
.arcDashAnimateTime(() => defaultProps.arcTime);
globeRef.current
.pointsData(filteredPoints)
.pointColor((e) => (e as { color: string }).color)
.pointsMerge(true)
.pointAltitude(0.0)
.pointRadius(2);
globeRef.current
.ringsData([])
.ringColor(() => defaultProps.polygonColor)
.ringMaxRadius(defaultProps.maxRings)
.ringPropagationSpeed(RING_PROPAGATION_SPEED)
.ringRepeatPeriod(
(defaultProps.arcTime * defaultProps.arcLength) / defaultProps.rings,
);
}, [
isInitialized,
data,
defaultProps.pointSize,
defaultProps.showAtmosphere,
defaultProps.atmosphereColor,
defaultProps.atmosphereAltitude,
defaultProps.polygonColor,
defaultProps.arcLength,
defaultProps.arcTime,
defaultProps.rings,
defaultProps.maxRings,
]);
// Handle rings animation with cleanup
useEffect(() => {
if (!globeRef.current || !isInitialized || !data) return;
const interval = setInterval(() => {
if (!globeRef.current) return;
const newNumbersOfRings = genRandomNumbers(
0,
data.length,
Math.floor((data.length * 4) / 5),
);
const ringsData = data
.filter((d, i) => newNumbersOfRings.includes(i))
.map((d) => ({
lat: d.startLat,
lng: d.startLng,
color: d.color,
}));
globeRef.current.ringsData(ringsData);
}, 2000);
return () => {
clearInterval(interval);
};
}, [isInitialized, data]);
return <group ref={groupRef} />;
}
export function WebGLRendererConfig() {
const { gl, size } = useThree();
useEffect(() => {
gl.setPixelRatio(window.devicePixelRatio);
gl.setSize(size.width, size.height);
gl.setClearColor(0xffaaff, 0);
}, []);
return null;
}
export function World(props: WorldProps) {
const { globeConfig } = props;
const scene = new Scene();
scene.fog = new Fog(0xffffff, 400, 2000);
return (
<Canvas scene={scene} camera={new PerspectiveCamera(50, aspect, 180, 1800)}>
<WebGLRendererConfig />
<ambientLight color={globeConfig.ambientLight} intensity={0.6} />
<directionalLight
color={globeConfig.directionalLeftLight}
position={new Vector3(-400, 100, 400)}
/>
<directionalLight
color={globeConfig.directionalTopLight}
position={new Vector3(-200, 500, 200)}
/>
<pointLight
color={globeConfig.pointLight}
position={new Vector3(-200, 500, 200)}
intensity={0.8}
/>
<Globe {...props} />
<OrbitControls
enablePan={false}
enableZoom={false}
minDistance={cameraZ}
maxDistance={cameraZ}
autoRotateSpeed={1}
autoRotate={true}
minPolarAngle={Math.PI / 3.5}
maxPolarAngle={Math.PI - Math.PI / 3}
/>
</Canvas>
);
}
export function hexToRgb(hex: string) {
var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
hex = hex.replace(shorthandRegex, function (m, r, g, b) {
return r + r + g + g + b + b;
});
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result
? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16),
}
: null;
}
export function genRandomNumbers(min: number, max: number, count: number) {
const arr = [];
while (arr.length < count) {
const r = Math.floor(Math.random() * (max - min)) + min;
if (arr.indexOf(r) === -1) arr.push(r);
}
return arr;
}

View File

@ -1,58 +0,0 @@
"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>
);
};

View File

@ -1,61 +0,0 @@
"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>
);
}

File diff suppressed because one or more lines are too long

View File

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

View File

@ -1,7 +1,4 @@
@import 'tailwindcss';
@import "tw-animate-css";
@custom-variant dark (&:is(.dark *));
@plugin '@tailwindcss/forms';
@ -44,29 +41,22 @@
--radius-4xl: 2rem;
--radius-5xl: 2.5rem;
/* 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-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);
--color-gray-500: oklch(0.556 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;
--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);
--font-sans: var(--font-inter);
--container-2xl: 40rem;
/* 3D perspective utilities */
--perspective-1000: 1000px;
@keyframes fade-in {
from {
opacity: 0;
@ -84,142 +74,10 @@
@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;
}
}