Merge branch 'development'

This commit is contained in:
2025-09-15 18:14:48 +02:00
47 changed files with 1372 additions and 304 deletions

24
components.json Normal file
View File

@@ -0,0 +1,24 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "",
"css": "src/styles/tailwind.css",
"baseColor": "gray",
"cssVariables": true,
"prefix": ""
},
"iconLibrary": "lucide",
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"registries": {
"@magicui": "https://magicui.design/r/{name}.json"
}
}

126
package-lock.json generated
View File

@@ -15,8 +15,12 @@
"@types/node": "^20.10.8", "@types/node": "^20.10.8",
"@types/react": "^18.2.55", "@types/react": "^18.2.55",
"@types/react-dom": "^18.2.18", "@types/react-dom": "^18.2.18",
"clsx": "^2.1.0", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cobe": "^0.6.4",
"framer-motion": "^10.15.0", "framer-motion": "^10.15.0",
"lucide-react": "^0.544.0",
"motion": "^12.23.12",
"next": "^14.0.4", "next": "^14.0.4",
"popmotion": "^11.0.5", "popmotion": "^11.0.5",
"react": "^18.2.0", "react": "^18.2.0",
@@ -35,7 +39,8 @@
"eslint-config-next": "^14.0.4", "eslint-config-next": "^14.0.4",
"prettier": "^3.3.2", "prettier": "^3.3.2",
"prettier-plugin-tailwindcss": "^0.6.11", "prettier-plugin-tailwindcss": "^0.6.11",
"sharp": "0.33.1" "sharp": "0.33.1",
"tw-animate-css": "^1.3.8"
} }
}, },
"node_modules/@alloc/quick-lru": { "node_modules/@alloc/quick-lru": {
@@ -1728,6 +1733,15 @@
"integrity": "sha512-lb49vf1Xzfx080OKA0o6l8DQQpV+6Vg95zyCJX9VB/BqKYlhG7N4wgROUUHRA+ZPUefLnteQOad7z1kT2bV7bg==", "integrity": "sha512-lb49vf1Xzfx080OKA0o6l8DQQpV+6Vg95zyCJX9VB/BqKYlhG7N4wgROUUHRA+ZPUefLnteQOad7z1kT2bV7bg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@lobehub/fluent-emoji/node_modules/lucide-react": {
"version": "0.469.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.469.0.tgz",
"integrity": "sha512-28vvUnnKQ/dBwiCQtwJw7QauYnE7yd2Cyp4tTTJpvglX4EMpbflcdBgrgToX2j71B3YvugK/NH3BGUk+E/p/Fw==",
"license": "ISC",
"peerDependencies": {
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/@lobehub/icons": { "node_modules/@lobehub/icons": {
"version": "1.97.2", "version": "1.97.2",
"resolved": "https://registry.npmjs.org/@lobehub/icons/-/icons-1.97.2.tgz", "resolved": "https://registry.npmjs.org/@lobehub/icons/-/icons-1.97.2.tgz",
@@ -1746,6 +1760,15 @@
"react-dom": "^18.0.0 || ^19.0.0" "react-dom": "^18.0.0 || ^19.0.0"
} }
}, },
"node_modules/@lobehub/icons/node_modules/lucide-react": {
"version": "0.469.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.469.0.tgz",
"integrity": "sha512-28vvUnnKQ/dBwiCQtwJw7QauYnE7yd2Cyp4tTTJpvglX4EMpbflcdBgrgToX2j71B3YvugK/NH3BGUk+E/p/Fw==",
"license": "ISC",
"peerDependencies": {
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/@lobehub/ui": { "node_modules/@lobehub/ui": {
"version": "1.171.0", "version": "1.171.0",
"resolved": "https://registry.npmjs.org/@lobehub/ui/-/ui-1.171.0.tgz", "resolved": "https://registry.npmjs.org/@lobehub/ui/-/ui-1.171.0.tgz",
@@ -5037,6 +5060,15 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/cobe": {
"version": "0.6.4",
"resolved": "https://registry.npmjs.org/cobe/-/cobe-0.6.4.tgz",
"integrity": "sha512-huuGFnDoXLy/tsCZYYa/H35BBRs9cxsS0XKJ3BXjRp699cQKuoEVrvKlAQMx0DKXG7+VUv4jsHVrS7yPbkLSkQ==",
"license": "MIT",
"dependencies": {
"phenomenon": "^1.6.0"
}
},
"node_modules/collapse-white-space": { "node_modules/collapse-white-space": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz", "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz",
@@ -9007,9 +9039,9 @@
"license": "ISC" "license": "ISC"
}, },
"node_modules/lucide-react": { "node_modules/lucide-react": {
"version": "0.469.0", "version": "0.544.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.469.0.tgz", "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.544.0.tgz",
"integrity": "sha512-28vvUnnKQ/dBwiCQtwJw7QauYnE7yd2Cyp4tTTJpvglX4EMpbflcdBgrgToX2j71B3YvugK/NH3BGUk+E/p/Fw==", "integrity": "sha512-t5tS44bqd825zAW45UQxpG2CvcC4urOwn2TrwSH8u+MjeE+1NnWl6QqeQ/6NdjMqdOygyiT9p3Ev0p1NJykxjw==",
"license": "ISC", "license": "ISC",
"peerDependencies": { "peerDependencies": {
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
@@ -10317,6 +10349,32 @@
"pathe": "^2.0.1" "pathe": "^2.0.1"
} }
}, },
"node_modules/motion": {
"version": "12.23.12",
"resolved": "https://registry.npmjs.org/motion/-/motion-12.23.12.tgz",
"integrity": "sha512-8jCD8uW5GD1csOoqh1WhH1A6j5APHVE15nuBkFeRiMzYBdRwyAHmSP/oXSuW0WJPZRXTFdBoG4hY9TFWNhhwng==",
"license": "MIT",
"dependencies": {
"framer-motion": "^12.23.12",
"tslib": "^2.4.0"
},
"peerDependencies": {
"@emotion/is-prop-valid": "*",
"react": "^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@emotion/is-prop-valid": {
"optional": true
},
"react": {
"optional": true
},
"react-dom": {
"optional": true
}
}
},
"node_modules/motion-dom": { "node_modules/motion-dom": {
"version": "11.18.1", "version": "11.18.1",
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-11.18.1.tgz", "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-11.18.1.tgz",
@@ -10332,6 +10390,48 @@
"integrity": "sha512-49Kt+HKjtbJKLtgO/LKj9Ld+6vw9BjH5d9sc40R/kVyH8GLAXgT42M2NnuPcJNuA3s9ZfZBUcwIgpmZWGEE+hA==", "integrity": "sha512-49Kt+HKjtbJKLtgO/LKj9Ld+6vw9BjH5d9sc40R/kVyH8GLAXgT42M2NnuPcJNuA3s9ZfZBUcwIgpmZWGEE+hA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/motion/node_modules/framer-motion": {
"version": "12.23.12",
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.12.tgz",
"integrity": "sha512-6e78rdVtnBvlEVgu6eFEAgG9v3wLnYEboM8I5O5EXvfKC8gxGQB8wXJdhkMy10iVcn05jl6CNw7/HTsTCfwcWg==",
"license": "MIT",
"dependencies": {
"motion-dom": "^12.23.12",
"motion-utils": "^12.23.6",
"tslib": "^2.4.0"
},
"peerDependencies": {
"@emotion/is-prop-valid": "*",
"react": "^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@emotion/is-prop-valid": {
"optional": true
},
"react": {
"optional": true
},
"react-dom": {
"optional": true
}
}
},
"node_modules/motion/node_modules/motion-dom": {
"version": "12.23.12",
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.12.tgz",
"integrity": "sha512-RcR4fvMCTESQBD/uKQe49D5RUeDOokkGRmz4ceaJKDBgHYtZtntC/s2vLvY38gqGaytinij/yi3hMcWVcEF5Kw==",
"license": "MIT",
"dependencies": {
"motion-utils": "^12.23.6"
}
},
"node_modules/motion/node_modules/motion-utils": {
"version": "12.23.6",
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.23.6.tgz",
"integrity": "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==",
"license": "MIT"
},
"node_modules/ms": { "node_modules/ms": {
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -10858,6 +10958,12 @@
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/phenomenon": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/phenomenon/-/phenomenon-1.6.0.tgz",
"integrity": "sha512-7h9/fjPD3qNlgggzm88cY58l9sudZ6Ey+UmZsizfhtawO6E3srZQXywaNm2lBwT72TbpHYRPy7ytIHeBUD/G0A==",
"license": "MIT"
},
"node_modules/picocolors": { "node_modules/picocolors": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -13546,6 +13652,16 @@
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD" "license": "0BSD"
}, },
"node_modules/tw-animate-css": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.3.8.tgz",
"integrity": "sha512-Qrk3PZ7l7wUcGYhwZloqfkWCmaXZAoqjkdbIDvzfGshwGtexa/DAs9koXxIkrpEasyevandomzCBAV1Yyop5rw==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/Wombosvideo"
}
},
"node_modules/type-check": { "node_modules/type-check": {
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",

View File

@@ -17,8 +17,12 @@
"@types/node": "^20.10.8", "@types/node": "^20.10.8",
"@types/react": "^18.2.55", "@types/react": "^18.2.55",
"@types/react-dom": "^18.2.18", "@types/react-dom": "^18.2.18",
"clsx": "^2.1.0", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cobe": "^0.6.4",
"framer-motion": "^10.15.0", "framer-motion": "^10.15.0",
"lucide-react": "^0.544.0",
"motion": "^12.23.12",
"next": "^14.0.4", "next": "^14.0.4",
"popmotion": "^11.0.5", "popmotion": "^11.0.5",
"react": "^18.2.0", "react": "^18.2.0",
@@ -37,6 +41,7 @@
"eslint-config-next": "^14.0.4", "eslint-config-next": "^14.0.4",
"prettier": "^3.3.2", "prettier": "^3.3.2",
"prettier-plugin-tailwindcss": "^0.6.11", "prettier-plugin-tailwindcss": "^0.6.11",
"sharp": "0.33.1" "sharp": "0.33.1",
"tw-animate-css": "^1.3.8"
} }
} }

BIN
public/images/benefits.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

15
public/images/logo.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 120 KiB

BIN
public/images/m.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

View File

@@ -11,6 +11,7 @@ import { CallTo } from '@/components/CallTo'
import { ScrollDown } from '@/components/ui/ScrollDown' import { ScrollDown } from '@/components/ui/ScrollDown'
import { ScrollUp } from '@/components/ui/ScrollUp' import { ScrollUp } from '@/components/ui/ScrollUp'
import { GridStats } from '@/components/GridStats' import { GridStats } from '@/components/GridStats'
import { WorldMap } from '@/components/WorldMap'
export default function Home() { export default function Home() {
return ( return (
@@ -18,23 +19,17 @@ export default function Home() {
<section id="home-hero"> <section id="home-hero">
<HomeHero /> <HomeHero />
</section> </section>
<section id="home-about">
<HomeAbout />
</section>
<section id="grid-stats">
<GridStats />
</section>
<section id="companies"> <section id="companies">
<Companies /> <Companies />
</section> </section>
<section id="stack-section">
<StackSectionPreview />
</section>
<section id="steps"> <section id="steps">
<Steps /> <Steps />
</section> </section>
<section id="clickable-gallery"> <section id="world-map">
<ClickableGallery /> <WorldMap />
</section>
<section id="grid-stats">
<GridStats />
</section> </section>
<section id="use-cases"> <section id="use-cases">
<UseCases /> <UseCases />
@@ -42,9 +37,6 @@ export default function Home() {
<section id="call-to-action"> <section id="call-to-action">
<CallTo /> <CallTo />
</section> </section>
<section id="faqs">
<Faqs />
</section>
<ScrollDown /> <ScrollDown />
<ScrollUp /> <ScrollUp />
</> </>

View File

@@ -25,8 +25,8 @@ export default function RootLayout({
children: React.ReactNode children: React.ReactNode
}) { }) {
return ( return (
<html lang="en" className={clsx('bg-white antialiased', mulish.variable)}> <html lang="en" className={clsx('antialiased', mulish.variable)}>
<body>{children}</body> <body className="bg-black text-white">{children}</body>
</html> </html>
) )
} }

View File

@@ -0,0 +1,92 @@
"use client";
import React from "react";
import { motion } from "framer-motion";
import { H2, P } from '@/components/Texts';
import Ai21 from '@/components/logos/Ai21';
import Claude from '@/components/logos/Claude';
import BaiduCloud from '@/components/logos/BaiduCloud';
import ByteDance from '@/components/logos/ByteDance';
import DeepSeek from '@/components/logos/DeepSeek';
import DeepMind from '@/components/logos/DeepMind';
import Minimax from '@/components/logos/Minimax';
import Mistral from '@/components/logos/Mistral';
import Moonshot from '@/components/logos/Moonshot';
import AlibabaCloud from '@/components/logos/AlibabaCloud';
import TencentCloud from '@/components/logos/TencentCloud';
import OpenAI from '@/components/logos/OpenAI';
import XAI from '@/components/logos/XAI';
const row1 = [Ai21, Claude, BaiduCloud, ByteDance];
const row2 = [DeepSeek, DeepMind, Minimax, Mistral];
const row3 = [Moonshot, AlibabaCloud];
const row4 = [TencentCloud, OpenAI, XAI];
export function Companies() {
return (
<div id="companies" className="relative flex h-screen 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-6xl p-4 py-12">
{/* Heading */}
<motion.div
className="flex flex-col justify-center max-w-4xl items-center mb-6 mx-auto"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 1 }}
>
<H2 className="text-center pb-6">
Deploy the Worlds Leading AI Models
</H2>
<P className="pb-8 text-center" color="custom">
Deploy and scale AI from top global providers on a decentralized, privacy-first infrastructure.
</P>
</motion.div>
{/* Animated Line */}
<motion.div
className="h-[2px] bg-neutral-400 rounded-full mx-auto mb-12"
initial={{ width: 0 }}
animate={{ width: "100%" }}
transition={{ duration: 2, delay: 1 }}
/>
{/* Logos grid */}
<div className="flex flex-col items-center gap-y-10">
{[row1, row2, row3, row4].map((row, rowIndex) => (
<motion.div
key={rowIndex}
className="flex flex-wrap justify-center items-center gap-x-8 sm:gap-x-10"
initial="hidden"
animate="visible"
variants={{
visible: {
transition: {
staggerChildren: 0.15,
delayChildren: 2.8 + rowIndex * 0.5, // Stagger rows
},
},
}}
>
{row.map((Logo, i) => (
<motion.div
key={i}
className="flex justify-center"
variants={{
hidden: { opacity: 0, y: 30 },
visible: { opacity: 1, y: 0 },
}}
transition={{ duration: 0.6, ease: "easeOut" }}
>
<div className="text-[#2F3178]"><Logo /></div>
</motion.div>
))}
</motion.div>
))}
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,117 @@
"use client";
import CountUp from "react-countup";
import React from "react";
import { Button } from "@/components/Button";
export function GridStats() {
return (
<div id="grid-stats" className="py-24 bg-transparent relative">
<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-black lg:text-4xl">
Powered by a Global Community
</h2>
<p className="mt-4 sm:mt-6 text-sm font-light text-pretty text-black lg:text-base">
ThreeFolds groundbreaking technology enables anyone individuals, organizations, and communities to deploy their own Internet infrastructure.
</p>
<Button className="mt-8" variant="outline" 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."
/>
<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-3xl bg-white shadow-md shadow-gray-900/5 p-8 transition-all duration-300 ease-out hover:scale-105 ${className}`}
style={{
filter: 'brightness(1)',
}}
onMouseEnter={(e) => {
e.currentTarget.style.filter = 'brightness(0.8)';
}}
onMouseLeave={(e) => {
e.currentTarget.style.filter = 'brightness(1)';
}}
>
<h3 className="text-lg font-semibold text-gradient-neutral-vertical" style={{ textShadow: '0 0 12px rgba(255, 255, 255, 0.4), 0 0 24px rgba(255, 255, 255, 0.2)' }}>{label}</h3>
<p className="mt-2 text-sm font-light text-pretty text-black lg:text-base">
{description}
</p>
<div className="mt-8 flex items-center space-x-3">
<span className="text-gradient-neutral-vertical text-3xl"></span>
<div className="text-5xl font-semibold tracking-tight text-black tabular-nums">
{value}
{unit && (
<span className="ml-2 text-lg font-normal text-gray-800">{unit}</span>
)}
</div>
</div>
<p className="mt-2 text-sm text-gray-800 uppercase tracking-wider">
{note}
</p>
</div>
);
}

View File

@@ -0,0 +1,90 @@
'use client'
import { useState } from 'react'
import { motion } from 'framer-motion'
import { TypeAnimation } from 'react-type-animation'
import { Dialog, DialogPanel } from '@headlessui/react'
import { Bars3Icon, XMarkIcon, ChevronDoubleDownIcon } from '@heroicons/react/24/outline'
import { H1, PL } from '@/components/Texts'
const navigation = [
{ name: 'Product', href: '#' },
{ name: 'Features', href: '#' },
{ name: 'Marketplace', href: '#' },
{ name: 'Company', href: '#' },
]
export function HomeHero() {
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
return (
<div className="relative h-screen -top-15">
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 2 }}
className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[700px] h-[700px] -z-10 rounded-full overflow-hidden"
>
<video
autoPlay
loop
muted
playsInline
className="h-full w-full object-cover"
>
<source src="/videos/mycelium.mp4" type="video/mp4" />
</video>
</motion.div>
<div className="relative isolate px-6 lg:px-8">
<div
aria-hidden="true"
className="absolute inset-x-0 -top-40 -z-10 transform-gpu overflow-hidden blur-3xl lg:-top-80"
>
<div
style={{
clipPath:
'polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 85.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 27.5% 76.7%, 0.1% 64.9%, 17.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 74.1% 44.1%)',
}}
className="relative left-[calc(50%-11rem)] aspect-[1155/678] w-[36.125rem] -translate-x-1/2 rotate-[30deg] bg-gradient-to-tr from-[#a4caf6] to-[#aaa4fa] opacity-15 sm:left-[calc(50%-30rem)] sm:w-[72.1875rem]"
/>
</div>
<div
aria-hidden="true"
className="absolute inset-x-0 bottom-10 -z-10 transform-gpu overflow-hidden blur-3xl lg:bottom-40"
>
<div
style={{
clipPath:
'polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 85.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 27.5% 76.7%, 0.1% 64.9%, 17.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 74.1% 44.1%)',
}}
className="relative bottom-[calc(50%+3rem)] aspect-[1155/678] w-[36.125rem] -translate-x-1/2 bg-gradient-to-tr from-[#93c5fd] to-[#9089fc] opacity-15 sm:left-[calc(50%+30rem)] sm:w-[72.1875rem]"
/>
</div>
<div className="relative -top-15 mx-auto max-w-8xl h-screen flex items-center justify-center">
<div className="text-center max-w-5xl">
<H1>
<TypeAnimation
sequence={[
'Decentralized Autonomous Agentic Cloud.',
1000,
]}
wrapper="span"
speed={50}
repeat={0}
/>
</H1>
</div>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 1, delay: 1 }}
>
<PL className="absolute bottom-0 left-0 max-w-xl text-left" color="custom">
Mycelium's advancements in Agentic infrastructure supports private, secure and autonomous Agents that connect, learn and grow with you.
</PL>
</motion.div>
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,71 @@
'use client'
import React, { useRef } from 'react'
import { motion, useInView } from 'framer-motion'
import { H2, P, CT, CP } from '@/components/Texts'
import { TbCircleNumber1Filled, TbCircleNumber2Filled, TbCircleNumber3Filled } from 'react-icons/tb'
const features = [
{
name: 'Choose Your Intelligence',
description: 'Explore a library of leading LLMs and agentic functions. Pick the ones that fit your use case, from general assistants to specialized reasoning models.',
icon: TbCircleNumber1Filled,
},
{
name: 'Add Your Knowledge',
description:
'Connect your data or knowledge base to enable personalized, context-aware results while keeping your information private.',
icon: TbCircleNumber2Filled,
},
{
name: 'Define Your Network',
description:
'Set up and manage your nodes with ease. Scale compute and storage as you grow, while staying fully sovereign and decentralized.',
icon: TbCircleNumber3Filled,
},
]
export function Steps() {
const ref = useRef(null);
const isInView = useInView(ref, { once: true });
return (
<section id="benefits" ref={ref} className="bg-white pt-0 pb-24 dark:bg-gray-900">
<div className="mx-auto max-w-7xl px-6 lg:px-0">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={isInView ? { opacity: 1, y: 0 } : { opacity: 0, y: 20 }}
transition={{ duration: 0.8, delay: 0.1 }}
className="mx-auto max-w-5xl lg:mx-0"
>
<H2 className="text-3xl font-medium tracking-tight">
Deploy Scalable LLMs and AI Agents in Seconds
</H2>
<P className="mt-6 text-lg" color="custom">
Launch and scale intelligence on your own terms. Mycelium Cloud makes it simple to deploy models, integrate knowledge, and run everything on a network you control.
</P>
</motion.div>
<motion.ul
initial={{ opacity: 0 }}
animate={isInView ? { opacity: 1 } : { opacity: 0 }}
transition={{ duration: 0.5, delay: 0.2, staggerChildren: 0.2 }}
className="mx-auto mt-16 grid max-w-2xl grid-cols-1 gap-x-8 gap-y-16 text-base/7 sm:grid-cols-2 lg:mx-0 lg:max-w-none lg:grid-cols-3"
>
{features.map((feature, index) => (
<motion.li
key={feature.name}
initial={{ opacity: 0, y: 20 }}
animate={isInView ? { opacity: 1, y: 0 } : { opacity: 0, y: 20 }}
transition={{ duration: 0.5, delay: 0.3 + index * 0.2 }}
className="rounded-2xl border border-gray-200 p-8 dark:border-gray-700"
>
<feature.icon className="h-8 w-8 mb-4 text-[#2F3178]" />
<CT as="span" className="font-semibold">{feature.name}</CT>
<CP className="mt-2 text-sm" color="custom">{feature.description}</CP>
</motion.li>
))}
</motion.ul>
</div>
</section>
)
}

View File

@@ -0,0 +1,263 @@
'use client'
import React, { useEffect, useMemo, useRef, useState } from 'react'
import clsx from 'clsx'
import {
ArchiveBoxIcon,
CodeBracketIcon,
CpuChipIcon,
GlobeAltIcon,
MagnifyingGlassIcon,
ShareIcon,
UserGroupIcon,
CheckBadgeIcon,
} from '@heroicons/react/24/solid'
import { Container } from '@/components/Container'
import { H2, P, CT, CP } from '@/components/Texts'
import { motion, useInView } from 'framer-motion'
interface Review {
title: string
body: string
}
const reviews: Array<Review> = [
{
title: 'FungiStor: Long-Term AI Memory',
body: 'Quantum-safe permanent storage preserving AI knowledge forever. Zero-knowledge architecture with mathematical dispersal ensures immortality.',
},
{
title: 'HeroDB: Active AI Memory',
body: 'High-performance datastore for AI working memory. Multi-modal indexing enables vector search with global accessibility.',
},
{
title: 'MOS Sandboxes: Secure Agent Workspaces',
body: 'Lightweight isolated environments deploying globally in five seconds. Hardware-level isolation ensures maximum security for agents.',
},
{
title: 'Mycelium Mesh: Secure Communication Network',
body: 'Peer-to-peer overlay network with end-to-end encryption. Self-healing shortest-path routing creates resilient agentic communication.',
},
{
title: 'Deterministic Deployment: Verifiable Code Execution',
body: 'Cryptographic guarantee system ensuring deployed code matches specifications. Prevents supply-chain attacks with immutable trails.',
},
{
title: 'Agent Coordination: Sovereign Workflow Management',
body: 'User-centric orchestration where HERO agents coordinate worker fleets. Planetary-scale coordination with instant spawning.',
},
{
title: 'Universal Interface Layer: AI Service Gateway',
body: 'Unified broker connecting agents to LLMs, APIs, and services. Integrated micropayments simplify development.',
},
{
title: 'Semantic Index & Search: Navigable Knowledge Fabric',
body: 'Transforms data chaos into unified knowledge graphs. Goes beyond keywords to understand meaning and context.',
},
]
function getReviewIcon(title: string) {
if (title.startsWith('FungiStor')) return ArchiveBoxIcon;
if (title.startsWith('HeroDB')) return CpuChipIcon;
if (title.startsWith('MOS Sandboxes')) return CodeBracketIcon;
if (title.startsWith('Mycelium Mesh')) return ShareIcon;
if (title.startsWith('Deterministic Deployment')) return CheckBadgeIcon;
if (title.startsWith('Agent Coordination')) return UserGroupIcon;
if (title.startsWith('Universal Interface Layer')) return GlobeAltIcon;
if (title.startsWith('Semantic Index & Search')) return MagnifyingGlassIcon;
return GlobeAltIcon; // default
}
function Review({
title,
body,
className,
...props
}: Omit<React.ComponentPropsWithoutRef<'figure'>, keyof Review> & Review) {
let animationDelay = useMemo(() => {
let possibleAnimationDelays = ['0s', '0.1s', '0.2s', '0.3s', '0.4s', '0.5s']
return possibleAnimationDelays[
Math.floor(Math.random() * possibleAnimationDelays.length)
]
}, [])
return (
<figure
className={clsx(
'animate-fade-in rounded-3xl bg-white p-6 opacity-0 shadow-md shadow-gray-900/5',
className,
)}
style={{ animationDelay }}
{...props}
>
<blockquote className="text-gray-900">
{React.createElement(getReviewIcon(title), { className: "h-6 w-6 text-[#2F3178] mb-2" })}
<CT color="primary" className="mt-4 text-lg/6 font-semibold">
{title}
</CT>
<CP color="custom" className="mt-3 text-sm">{body}</CP>
</blockquote>
</figure>
)
}
function splitArray<T>(array: Array<T>, numParts: number) {
let result: Array<Array<T>> = []
for (let i = 0; i < array.length; i++) {
let index = i % numParts
if (!result[index]) {
result[index] = []
}
result[index].push(array[i])
}
return result
}
function ReviewColumn({
reviews,
className,
reviewClassName,
msPerPixel = 0,
}: {
reviews: Array<Review>
className?: string
reviewClassName?: (reviewIndex: number) => string
msPerPixel?: number
}) {
let columnRef = useRef<React.ElementRef<'div'>>(null)
let [columnHeight, setColumnHeight] = useState(0)
let duration = `${columnHeight * msPerPixel}ms`
useEffect(() => {
if (!columnRef.current) {
return
}
let resizeObserver = new window.ResizeObserver(() => {
setColumnHeight(columnRef.current?.offsetHeight ?? 0)
})
resizeObserver.observe(columnRef.current)
return () => {
resizeObserver.disconnect()
}
}, [])
return (
<div
ref={columnRef}
className={clsx('animate-marquee space-y-8 py-4', className)}
style={{ '--marquee-duration': duration } as React.CSSProperties}
>
{reviews.concat(reviews).map((review, reviewIndex) => (
<Review
key={reviewIndex}
aria-hidden={reviewIndex >= reviews.length}
className={reviewClassName?.(reviewIndex % reviews.length)}
{...review}
/>
))}
</div>
)
}
function ReviewGrid() {
let containerRef = useRef<React.ElementRef<'div'>>(null)
let isInView = useInView(containerRef, { once: true, amount: 0.4 })
let columns = splitArray(reviews, 3)
let column1 = columns[0]
let column2 = columns[1]
let column3 = splitArray(columns[2], 2)
return (
<div
ref={containerRef}
className="relative -mx-4 mt-0 grid h-196 max-h-[150vh] grid-cols-1 items-start gap-8 overflow-hidden px-4 sm:mt-20 md:grid-cols-2 lg:grid-cols-3"
>
{isInView && (
<>
<ReviewColumn
reviews={[...column1, ...column3.flat(), ...column2]}
reviewClassName={(reviewIndex) =>
clsx(
reviewIndex >= column1.length + column3[0].length &&
'md:hidden',
reviewIndex >= column1.length && 'lg:hidden',
)
}
msPerPixel={10}
/>
<ReviewColumn
reviews={[...column2, ...column3[1]]}
className="hidden md:block"
reviewClassName={(reviewIndex) =>
reviewIndex >= column2.length ? 'lg:hidden' : ''
}
msPerPixel={15}
/>
<ReviewColumn
reviews={column3.flat()}
className="hidden lg:block"
msPerPixel={10}
/>
</>
)}
<div className="pointer-events-none absolute inset-x-0 top-0 h-32 bg-linear-to-b from-white" />
<div className="pointer-events-none absolute inset-x-0 bottom-0 h-32 bg-linear-to-t from-white" />
</div>
)
}
export function UseCases() {
const ref = useRef(null);
const isInView = useInView(ref, { once: true });
return (
<section
id="usecases"
ref={ref}
aria-labelledby="usecases-title"
className="py-12"
>
<Container className=''>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={isInView ? { opacity: 1, y: 0 } : { opacity: 0, y: 20 }}
transition={{ duration: 0.8, delay: 0.1 }}
className="mx-auto max-w-2xl lg:max-w-5xl"
>
<H2
id="usecases-title"
color="primary"
className="text-center"
>
Coming Soon: The Future of Mycelium
</H2>
<P className="mt-6 text-center" color="custom">
Mycelium Cloud is evolving to bring even more powerful decentralized features, designed to enhance your experience and expand possibilities. Be the first to explore what's coming next by staying connected with our latest updates.
</P>
</motion.div>
<motion.div
initial={{ opacity: 0 }}
animate={isInView ? { opacity: 1 } : { opacity: 0 }}
transition={{ duration: 1, delay: 0.2 }}
aria-hidden="true"
className="absolute inset-x-0 -top-40 -z-10 transform-gpu overflow-hidden blur-3xl sm:-top-80"
>
<div
style={{
clipPath:
'polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 85.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 27.5% 76.7%, 0.1% 64.9%, 17.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 74.1% 44.1%)',
}}
className="relative left-[calc(50%-30rem)] aspect-[1155/678] w-[36.125rem] -translate-x-1/2 bg-gradient-to-tr from-[#93c5fd] to-[#9089fc] opacity-30 sm:left-[calc(50%-36rem)] sm:w-[72.1875rem]"
/>
</motion.div>
<ReviewGrid />
</Container>
</section>
)
}

View File

@@ -10,13 +10,13 @@ const baseStyles = {
const variantStyles = { const variantStyles = {
solid: { solid: {
cyan: 'relative overflow-hidden bg-[#2F3178] text-white before:absolute before:inset-0 active:before:bg-transparent hover:before:bg-white/10 active:bg-[#2F3178] active:text-white/80 before:transition-colors', cyan: 'relative overflow-hidden bg-[#005eff] text-white before:absolute before:inset-0 active:before:bg-transparent hover:before:bg-white/10 active:bg-[#005eff] active:text-white/80 before:transition-colors',
white: white:
'bg-white text-cyan-900 hover:bg-white/90 active:bg-white/90 active:text-cyan-900/70', 'bg-white text-cyan-900 hover:bg-white/90 active:bg-white/90 active:text-cyan-900/70',
gray: 'bg-gray-800 text-white hover:bg-gray-900 active:bg-gray-800 active:text-white/80', gray: 'bg-gray-800 text-white hover:bg-gray-900 active:bg-gray-800 active:text-white/80',
}, },
outline: { outline: {
gray: 'border-gray-300 text-gray-700 hover:border-gray-400 active:bg-gray-100 active:text-gray-700/80', gray: 'border-gray-300 text-gray-200 hover:border-gray-400 active:bg-gray-100 active:text-gray-700/80',
}, },
} }

View File

@@ -3,6 +3,7 @@
import React from "react"; import React from "react";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { H2, P } from '@/components/Texts'; import { H2, P } from '@/components/Texts';
import { InfiniteMovingCards } from "@/components/magicui/infinite-moving-cards";
@@ -15,76 +16,58 @@ import DeepMind from '@/components/logos/DeepMind';
import Minimax from '@/components/logos/Minimax'; import Minimax from '@/components/logos/Minimax';
import Mistral from '@/components/logos/Mistral'; import Mistral from '@/components/logos/Mistral';
import Moonshot from '@/components/logos/Moonshot'; import Moonshot from '@/components/logos/Moonshot';
import AlibabaCloud from '@/components/logos/AlibabaCloud';
import TencentCloud from '@/components/logos/TencentCloud'; import TencentCloud from '@/components/logos/TencentCloud';
import OpenAI from '@/components/logos/OpenAI'; import OpenAI from '@/components/logos/OpenAI';
import XAI from '@/components/logos/XAI'; import XAI from '@/components/logos/XAI';
const row1 = [Ai21, Claude, BaiduCloud, ByteDance]; const logos = [
const row2 = [DeepSeek, DeepMind, Minimax, Mistral]; <Ai21 key="ai21" />,
const row3 = [Moonshot, AlibabaCloud]; <Claude key="claude" />,
const row4 = [TencentCloud, OpenAI, XAI]; <BaiduCloud key="baidu" />,
<ByteDance key="bytedance" />,
<DeepSeek key="deepseek" />,
<DeepMind key="deepmind" />,
<Minimax key="minimax" />,
<Mistral key="mistral" />,
<Moonshot key="moonshot" />,
<TencentCloud key="tencent" />,
<OpenAI key="openai" />,
<XAI key="xai" />,
];
const row1 = logos.slice(0, 6);
const row2 = logos.slice(6);
export function Companies() { export function Companies() {
return ( return (
<div id="companies" className="relative flex h-screen w-full overflow-hidden rounded-md bg-transparent antialiased md:items-center md:justify-center"> <div id="companies" className="relative bg-black flex flex-col items-center justify-center w-full overflow-hidden antialiased py-12 -top-20">
<div className="relative z-10 mx-auto w-full max-w-6xl p-4 py-12"> <div className="relative z-10 mx-auto w-full max-w-6xl p-4">
{/* Heading */} {/* Heading */}
<motion.div <motion.div
className="flex flex-col justify-center max-w-4xl items-center mb-6 mx-auto" className="flex flex-col justify-center max-w-4xl items-center mb-8 mx-auto"
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 1 }} transition={{ duration: 1 }}
> >
<H2 className="text-center pb-6"> <P className="text-gray-100 text-center">
Deploy the Worlds Leading AI Models Mycelium Cloud allows you to deploy and scale AI agents from top global providers on a decentralized, privacy-first infrastructure.
</H2>
<P className="pb-8 text-center" color="custom">
Deploy and scale AI from top global providers on a decentralized, privacy-first infrastructure. Mycelium Cloud lets you integrate state-of-the-art intelligence into your workflows with full control, sovereignty, and cost efficiency.
</P> </P>
</motion.div> </motion.div>
{/* Animated Line */}
<motion.div
className="h-[2px] bg-neutral-400 rounded-full mx-auto mb-12"
initial={{ width: 0 }}
animate={{ width: "100%" }}
transition={{ duration: 2, delay: 1 }}
/>
{/* Logos grid */} {/* Logos grid */}
<div className="flex flex-col items-center gap-y-10"> <div className="flex flex-col items-center gap-y-6 text-white">
{[row1, row2, row3, row4].map((row, rowIndex) => ( <InfiniteMovingCards
<motion.div items={row1}
key={rowIndex} direction="right"
className="flex flex-wrap justify-center items-center gap-x-8 sm:gap-x-10" speed="slow"
initial="hidden" />
animate="visible" <InfiniteMovingCards
variants={{ className=""
visible: { items={row2}
transition: { direction="left"
staggerChildren: 0.15, speed="slow"
delayChildren: 2.8 + rowIndex * 0.5, // Stagger rows />
},
},
}}
>
{row.map((Logo, i) => (
<motion.div
key={i}
className="flex justify-center"
variants={{
hidden: { opacity: 0, y: 30 },
visible: { opacity: 1, y: 0 },
}}
transition={{ duration: 0.6, ease: "easeOut" }}
>
<div className="text-[#2F3178]"><Logo /></div>
</motion.div>
))}
</motion.div>
))}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,14 +1,21 @@
import { useId } from 'react' import { useId } from 'react'
import clsx from 'clsx' import clsx from 'clsx'
const formClasses = const formClasses = {
'block w-full appearance-none rounded-lg border border-gray-200 bg-white py-[calc(--spacing(2)-1px)] px-[calc(--spacing(3)-1px)] text-gray-900 placeholder:text-gray-400 focus:border-cyan-500 focus:outline-hidden focus:ring-cyan-500 sm:text-sm' light:
'block w-full appearance-none rounded-lg border border-gray-200 bg-white py-[calc(--spacing(2)-1px)] px-[calc(--spacing(3)-1px)] text-gray-900 placeholder:text-gray-400 focus:border-cyan-500 focus:outline-none focus:ring-cyan-500 sm:text-sm',
dark:
'block w-full appearance-none rounded-lg border border-gray-600 bg-transparent py-[calc(--spacing(2)-1px)] px-[calc(--spacing(3)-1px)] text-white placeholder:text-gray-400 focus:border-cyan-500 focus:outline-none focus:ring-cyan-500 sm:text-sm',
}
function Label({ id, children }: { id: string; children: React.ReactNode }) { function Label({ id, children, variant = 'light' }: { id: string; children: React.ReactNode, variant?: 'light' | 'dark' }) {
return ( return (
<label <label
htmlFor={id} htmlFor={id}
className="mb-2 block text-sm font-semibold text-gray-900" className={clsx(
'mb-2 block text-sm font-semibold',
variant === 'light' ? 'text-gray-900' : 'text-white',
)}
> >
{children} {children}
</label> </label>
@@ -19,14 +26,15 @@ export function TextField({
label, label,
type = 'text', type = 'text',
className, className,
variant = 'light',
...props ...props
}: Omit<React.ComponentPropsWithoutRef<'input'>, 'id'> & { label?: string }) { }: Omit<React.ComponentPropsWithoutRef<'input'>, 'id'> & { label?: string; variant?: 'light' | 'dark' }) {
let id = useId() let id = useId()
return ( return (
<div className={className}> <div className={className}>
{label && <Label id={id}>{label}</Label>} {label && <Label id={id} variant={variant}>{label}</Label>}
<input id={id} type={type} {...props} className={formClasses} /> <input id={id} type={type} {...props} className={formClasses[variant]} />
</div> </div>
) )
} }

View File

@@ -22,11 +22,11 @@ function QrCodeBorder(props: React.ComponentPropsWithoutRef<'svg'>) {
export function Footer() { export function Footer() {
return ( return (
<footer id="footer" className="border-t border-gray-200"> <footer id="footer" className="border-t border-gray-800">
<Container> <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 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>
<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" /> <Logomark className="h-10 w-10 flex-none fill-cyan-500" />
<div className="ml-4"> <div className="ml-4">
<p className="text-base font-semibold">Project Mycelium</p> <p className="text-base font-semibold">Project Mycelium</p>
@@ -37,25 +37,25 @@ export function Footer() {
<NavLinks /> <NavLinks />
</nav> </nav>
</div> </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/50 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"> <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-700 transition-colors group-hover:stroke-cyan-500" />
<Image src={qrCode} alt="" unoptimized /> <Image src={qrCode} alt="" unoptimized />
</div> </div>
<div className="ml-8 lg:w-64"> <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="#"> <Link href="#">
<span className="absolute inset-0 sm:rounded-2xl" /> <span className="absolute inset-0 sm:rounded-2xl" />
Download the app Download the app
</Link> </Link>
</p> </p>
<p className="mt-1 text-sm text-gray-700"> <p className="mt-1 text-sm text-gray-400">
Scan the QR code to download the app from the App Store. Scan the QR code to download the app from the App Store.
</p> </p>
</div> </div>
</div> </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-800 pt-8 pb-12 md:flex-row-reverse md:justify-between md:pt-6">
<form className="flex w-full justify-center md:w-auto"> <form className="flex w-full justify-center md:w-auto">
<TextField <TextField
type="email" type="email"
@@ -63,6 +63,7 @@ export function Footer() {
placeholder="Email address" placeholder="Email address"
autoComplete="email" autoComplete="email"
required required
variant="dark"
className="w-60 min-w-0 shrink" className="w-60 min-w-0 shrink"
/> />
<Button type="submit" color="cyan" className="ml-4 flex-none"> <Button type="submit" color="cyan" className="ml-4 flex-none">
@@ -70,7 +71,7 @@ export function Footer() {
<span className="lg:hidden">Join newsletter</span> <span className="lg:hidden">Join newsletter</span>
</Button> </Button>
</form> </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">
&copy; Copyright ThreeFold {new Date().getFullYear()}. All rights reserved. &copy; Copyright ThreeFold {new Date().getFullYear()}. All rights reserved.
</p> </p>
</div> </div>

View File

@@ -2,63 +2,55 @@
import CountUp from "react-countup"; import CountUp from "react-countup";
import React from "react"; import React from "react";
import { Button } from "./Button"; import { Button } from "@/components/Button";
export function GridStats() { export function GridStats() {
return ( return (
<div id="grid-stats" className="py-24 bg-transparent relative"> <div
<div className="mx-auto max-w-2xl px-6 lg:max-w-7xl lg:px-8"> id="grid-stats"
<div className="grid grid-cols-1 gap-8 lg:grid-cols-3"> className="py-24 relative -top-20 "
{/* Column 1: Title & NODES */} style={{
backgroundImage: "url(/images/benefits.webp)",
backgroundSize: "cover",
backgroundPosition: "center",
}}
>
<div className="mx-auto max-w-2xl px-6 lg:max-w-7xl lg:px-4">
<div className="grid grid-cols-1 gap-12 lg:grid-cols-3">
{/* Column 1: Title & Description */}
<div className="flex flex-col space-y-10"> <div className="flex flex-col space-y-10">
{/* Title + Description */}
<div> <div>
<h2 className="text-2xl font-semibold tracking-tight leading-tight text-black lg:text-4xl"> <h2 className="text-2xl font-semibold tracking-tight leading-tight text-white lg:text-4xl">
Powered by a Global Community Robust Infrastructure for your Intellegence Needs
</h2> </h2>
<p className="mt-4 sm:mt-6 text-sm font-light text-pretty text-black lg:text-base"> <p className="mt-4 sm:mt-6 text-sm font-light text-pretty text-white lg:text-base">
ThreeFolds groundbreaking technology enables anyone individuals, organizations, and communities to deploy their own Internet infrastructure. Mycelium's groundbreaking technology provides dedicated, performance-validated GPUs for your AI workloads.
</p> </p>
<Button className="mt-8" variant="outline" href="https://threefold.io/build" >Explore TFGrid →</Button> <Button className="mt-8" variant="outline" href="https://threefold.io/build" >Explore TFGrid →</Button>
</div> </div>
</div> </div>
{/* Column 2: CORES (staggered) + SSD */} {/* Column 2: StatCards */}
<div className="flex flex-col space-y-10"> <div className="flex flex-col space-y-10">
<StatCard <StatCard
label="CORES" label="Dedicated Hosting"
description="A globally distributed mesh of CPU cores powering decentralized applications, AI workloads, and edge computing — without central bottlenecks." description="Run LLMs, VLMs, and diffusion models on single-tenant GPUs with private endpoints. Bring your own weights or deploy from a curated library of open models. Enjoy full control with flexible hourly pricing. Perfect for always-on inference or workloads exceeding 100K tokens per minute."
value={<CountUp end={54_958} duration={2.5} separator="," />}
note="Total Central Processing Unit Cores available on the grid."
/> />
<StatCard <StatCard
label="SSD CAPACITY" label="Data Sovereignty"
description="A distributed network of storage capacity — ready to support Web3, AI, and edge computing workloads around the world." description="Keep your sensitive data fully under your control. Mycelium nodes run on trusted infrastructure you own or choose, ensuring that no third party can access, train on, or monetize your data. This makes Mycelium ideal for enterprises in regulated industries that demand compliance and privacy."
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> </div>
{/* Column 3: StatCards */}
{/* Column 3: nodes countries */}
<div className="flex flex-col space-y-10 justify-start"> <div className="flex flex-col space-y-10 justify-start">
<StatCard <StatCard
label="NODES" label="Seamless Scalability"
description="A computer server 100% dedicated to the network. It is a building block of the ThreeFold Grid, providing compute, storage, and network resources." description="Scale from a single agent to hundreds without re-architecting. Myceliums decentralized infrastructure dynamically allocates compute, storage, and bandwidth across the network, so your AI workloads remain fast and resilient even under heavy demand."
value={<CountUp end={1778} duration={2.5} separator="," />}
note="The total number of nodes on the grid."
/> />
<StatCard <StatCard
label="COUNTRIES" label="Composable Agent Ecosystem"
description="The number of countries where at least one node is connected and operational on the grid." description="Mix and match agents for every part of your workflow: data ingestion, cleaning, orchestration, analysis, and reporting. Build an intelligent automation stack that evolves with your business needs, integrating easily with existing tools and APIs."
value={<CountUp end={51} duration={2.5} separator="," />}
note="The total number of countries with active nodes."
/> />
</div> </div>
</div> </div>
@@ -71,21 +63,15 @@ export function GridStats() {
function StatCard({ function StatCard({
label, label,
description, description,
value,
unit,
note,
className = "", className = "",
}: { }: {
label: string; label: string;
description: string; description: string;
value: React.ReactNode;
unit?: string;
note: string;
className?: string; className?: string;
}) { }) {
return ( return (
<div <div
className={`relative flex flex-col overflow-hidden rounded-3xl bg-white shadow-md shadow-gray-900/5 p-8 transition-all duration-300 ease-out hover:scale-105 ${className}`} className={`relative flex flex-col overflow-hidden rounded-3xl bg-gray-900/75 shadow-md shadow-gray-900/5 p-8 transition-all duration-300 ease-out hover:scale-105 ${className}`}
style={{ style={{
filter: 'brightness(1)', filter: 'brightness(1)',
}} }}
@@ -96,22 +82,10 @@ function StatCard({
e.currentTarget.style.filter = 'brightness(1)'; e.currentTarget.style.filter = 'brightness(1)';
}} }}
> >
<h3 className="text-lg font-semibold text-gradient-neutral-vertical" style={{ textShadow: '0 0 12px rgba(255, 255, 255, 0.4), 0 0 24px rgba(255, 255, 255, 0.2)' }}>{label}</h3> <h3 className="text-lg font-semibold text-white" style={{ textShadow: '0 0 12px rgba(255, 255, 255, 0.4), 0 0 24px rgba(255, 255, 255, 0.2)' }}>{label}</h3>
<p className="mt-2 text-sm font-light text-pretty text-black lg:text-base"> <p className="mt-2 text-sm font-light text-pretty text-white lg:text-base">
{description} {description}
</p> </p>
<div className="mt-8 flex items-center space-x-3">
<span className="text-gradient-neutral-vertical text-3xl"></span>
<div className="text-5xl font-semibold tracking-tight text-black tabular-nums">
{value}
{unit && (
<span className="ml-2 text-lg font-normal text-gray-800">{unit}</span>
)}
</div>
</div>
<p className="mt-2 text-sm text-gray-800 uppercase tracking-wider">
{note}
</p>
</div> </div>
); );
} }

View File

@@ -18,51 +18,23 @@ export function HomeHero() {
const [mobileMenuOpen, setMobileMenuOpen] = useState(false) const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
return ( return (
<div className="relative h-screen -top-15"> <div className="relative h-screen -top-20">
<motion.div <div className="absolute inset-0 -z-10">
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 2 }}
className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[700px] h-[700px] -z-10 rounded-full overflow-hidden"
>
<video <video
autoPlay autoPlay
loop loop
muted muted
playsInline playsInline
className="h-full w-full object-cover" className="absolute inset-0 w-full h-full object-cover"
> >
<source src="/videos/mycelium.mp4" type="video/mp4" /> <source src="/videos/mycelium.mp4" type="video/mp4" />
</video> </video>
</motion.div> <div className="absolute inset-0 bg-black/60" />
<div className="relative isolate px-6 lg:px-8">
<div
aria-hidden="true"
className="absolute inset-x-0 -top-40 -z-10 transform-gpu overflow-hidden blur-3xl lg:-top-80"
>
<div
style={{
clipPath:
'polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 85.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 27.5% 76.7%, 0.1% 64.9%, 17.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 74.1% 44.1%)',
}}
className="relative left-[calc(50%-11rem)] aspect-[1155/678] w-[36.125rem] -translate-x-1/2 rotate-[30deg] bg-gradient-to-tr from-[#a4caf6] to-[#aaa4fa] opacity-15 sm:left-[calc(50%-30rem)] sm:w-[72.1875rem]"
/>
</div>
<div
aria-hidden="true"
className="absolute inset-x-0 bottom-10 -z-10 transform-gpu overflow-hidden blur-3xl lg:bottom-40"
>
<div
style={{
clipPath:
'polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 85.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 27.5% 76.7%, 0.1% 64.9%, 17.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 74.1% 44.1%)',
}}
className="relative bottom-[calc(50%+3rem)] aspect-[1155/678] w-[36.125rem] -translate-x-1/2 bg-gradient-to-tr from-[#93c5fd] to-[#9089fc] opacity-15 sm:left-[calc(50%+30rem)] sm:w-[72.1875rem]"
/>
</div> </div>
<div className="relative px-6 lg:px-8">
<div className="relative -top-15 mx-auto max-w-8xl h-screen flex items-center justify-center"> <div className="relative -top-15 mx-auto max-w-8xl h-screen flex items-center justify-center">
<div className="text-center max-w-5xl"> <div className="text-center max-w-5xl">
<H1> <H1 color="light">
<TypeAnimation <TypeAnimation
sequence={[ sequence={[
'Decentralized Autonomous Agentic Cloud.', 'Decentralized Autonomous Agentic Cloud.',
@@ -79,7 +51,7 @@ export function HomeHero() {
animate={{ opacity: 1 }} animate={{ opacity: 1 }}
transition={{ duration: 1, delay: 1 }} transition={{ duration: 1, delay: 1 }}
> >
<PL className="absolute bottom-0 left-0 max-w-xl text-left" color="custom"> <PL className="absolute bottom-0 left-0 max-w-xl text-left text-gray-100" color="light">
Mycelium's advancements in Agentic infrastructure supports private, secure and autonomous Agents that connect, learn and grow with you. Mycelium's advancements in Agentic infrastructure supports private, secure and autonomous Agents that connect, learn and grow with you.
</PL> </PL>
</motion.div> </motion.div>

File diff suppressed because one or more lines are too long

View File

@@ -21,7 +21,7 @@ export function NavLinks() {
<Link <Link
key={label} key={label}
href={href} href={href}
className="relative -mx-3 -my-2 rounded-lg px-3 py-2 text-sm text-[#2F3178] transition-colors delay-150 hover:text-[#2F3178] hover:delay-0" className="relative -mx-3 -my-2 rounded-lg px-3 py-2 text-sm text-white transition-colors delay-150 hover:text-gray-300 hover:delay-0"
onMouseEnter={() => { onMouseEnter={() => {
if (timeoutRef.current) { if (timeoutRef.current) {
window.clearTimeout(timeoutRef.current) window.clearTimeout(timeoutRef.current)
@@ -37,7 +37,7 @@ export function NavLinks() {
<AnimatePresence> <AnimatePresence>
{hoveredIndex === index && ( {hoveredIndex === index && (
<motion.span <motion.span
className="absolute inset-0 rounded-lg bg-gray-100" className="absolute inset-0 rounded-lg bg-white/10"
layoutId="hoverBackground" layoutId="hoverBackground"
initial={{ opacity: 0 }} initial={{ opacity: 0 }}
animate={{ opacity: 1, transition: { duration: 0.15 } }} animate={{ opacity: 1, transition: { duration: 0.15 } }}

View File

@@ -30,18 +30,19 @@ export function Steps() {
const isInView = useInView(ref, { once: true }); const isInView = useInView(ref, { once: true });
return ( return (
<section id="benefits" ref={ref} className="bg-white pt-0 pb-24 dark:bg-gray-900"> <section id="benefits" ref={ref} className="relative bg-cover bg-center py-32 -top-20 text-white" style={{ backgroundImage: "url('/images/deployment.webp')" }}>
<div className="mx-auto max-w-7xl px-6 lg:px-0"> <div className="absolute inset-0 bg-black/70" />
<div className="relative px-6 lg:px-6">
<motion.div <motion.div
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
animate={isInView ? { opacity: 1, y: 0 } : { opacity: 0, y: 20 }} animate={isInView ? { opacity: 1, y: 0 } : { opacity: 0, y: 20 }}
transition={{ duration: 0.8, delay: 0.1 }} transition={{ duration: 0.8, delay: 0.1 }}
className="mx-auto max-w-5xl lg:mx-0" className="mx-auto max-w-5xl lg:mx-0"
> >
<H2 className="text-3xl font-medium tracking-tight"> <H2 className="text-3xl font-medium tracking-tight" color="light">
Deploy Scalable LLMs and AI Agents in Seconds Deploy Scalable LLMs and AI Agents in Seconds
</H2> </H2>
<P className="mt-6 text-lg" color="custom"> <P className="mt-6 text-lg" color="light">
Launch and scale intelligence on your own terms. Mycelium Cloud makes it simple to deploy models, integrate knowledge, and run everything on a network you control. Launch and scale intelligence on your own terms. Mycelium Cloud makes it simple to deploy models, integrate knowledge, and run everything on a network you control.
</P> </P>
</motion.div> </motion.div>
@@ -57,11 +58,11 @@ export function Steps() {
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
animate={isInView ? { opacity: 1, y: 0 } : { opacity: 0, y: 20 }} animate={isInView ? { opacity: 1, y: 0 } : { opacity: 0, y: 20 }}
transition={{ duration: 0.5, delay: 0.3 + index * 0.2 }} transition={{ duration: 0.5, delay: 0.3 + index * 0.2 }}
className="rounded-2xl border border-gray-200 p-8 dark:border-gray-700" className="rounded-2xl border border-white/20 bg-black/20 p-8 backdrop-blur-sm transition-all duration-300 ease-in-out hover:scale-105 hover:border-white/40 hover:bg-black/40"
> >
<feature.icon className="h-8 w-8 mb-4 text-[#2F3178]" /> <feature.icon className="h-8 w-8 mb-4 text-white" />
<CT as="span" className="font-semibold">{feature.name}</CT> <CT as="span" className="font-semibold" color="light">{feature.name}</CT>
<CP className="mt-2 text-sm" color="custom">{feature.description}</CP> <CP className="mt-2 text-sm" color="light">{feature.description}</CP>
</motion.li> </motion.li>
))} ))}
</motion.ul> </motion.ul>

View File

@@ -7,6 +7,7 @@ const colorVariants = {
primary: 'text-[#2F3178]', primary: 'text-[#2F3178]',
secondary: 'text-gray-600', secondary: 'text-gray-600',
custom: 'text-[#1c1c49]', custom: 'text-[#1c1c49]',
light: 'text-white',
} as const } as const
type TextOwnProps = { type TextOwnProps = {
@@ -51,10 +52,10 @@ const createTextComponent = <DefaultElement extends React.ElementType>(
// Exports // Exports
export const H1 = createTextComponent('h1', 'text-5xl font-medium tracking-tight text-balance lg:text-8xl') export const H1 = createTextComponent('h1', 'text-5xl font-medium tracking-tight text-balance lg:text-8xl')
export const PL = createTextComponent('p', 'text-2xl font-medium text-pretty leading-[1.2] lg:text-3xl') export const PL = createTextComponent('p', 'text-2xl font-medium text-pretty leading-[1.2] lg:text-3xl')
export const H2 = createTextComponent('h2', 'text-4xl font-medium text-pretty lg:text-5xl') export const H2 = createTextComponent('h2', 'text-3xl font-medium text-pretty lg:text-4xl')
export const P = createTextComponent('p', 'text-xl font-normal text-pretty lg:text-2xl') export const P = createTextComponent('p', 'text-lg font-normal text-pretty lg:text-xl')
export const H3 = createTextComponent('h3', 'text-3xl lg:text-4xl font-medium') export const H3 = createTextComponent('h3', 'text-2xl lg:text-3xl font-medium')
export const H4 = createTextComponent('h4', 'text-2xl lg:text-3xl font-semibold leading-tight') export const H4 = createTextComponent('h4', 'text-xl lg:text-2xl font-semibold leading-tight')
export const CT = createTextComponent('span', 'text-base lg:text-xl font-semibold text-center') export const CT = createTextComponent('span', 'text-lg lg:text-xl font-semibold text-center')
export const CP = createTextComponent('p', 'text-sm lg:text-base leading-relaxed font-light') export const CP = createTextComponent('p', 'text-sm lg:text-base leading-relaxed font-light')
export const NL = createTextComponent('span', 'text-lg font-semibold leading-6') export const NL = createTextComponent('span', 'text-lg font-semibold leading-6')

View File

@@ -86,18 +86,18 @@ function Review({
return ( return (
<figure <figure
className={clsx( 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/50 p-6 opacity-0 shadow-md shadow-gray-900/5',
className, className,
)} )}
style={{ animationDelay }} style={{ animationDelay }}
{...props} {...props}
> >
<blockquote className="text-gray-900"> <blockquote className="text-white">
{React.createElement(getReviewIcon(title), { className: "h-6 w-6 text-[#2F3178] mb-2" })} {React.createElement(getReviewIcon(title), { className: "h-6 w-6 text-[#2F3178] mb-2" })}
<CT color="primary" className="mt-4 text-lg/6 font-semibold"> <CT color="light" className="mt-4 text-lg/6 font-semibold">
{title} {title}
</CT> </CT>
<CP color="custom" className="mt-3 text-sm">{body}</CP> <CP color="light" className="mt-3 text-sm">{body}</CP>
</blockquote> </blockquote>
</figure> </figure>
@@ -176,7 +176,7 @@ function ReviewGrid() {
return ( return (
<div <div
ref={containerRef} ref={containerRef}
className="relative -mx-4 mt-0 grid h-196 max-h-[150vh] grid-cols-1 items-start gap-8 overflow-hidden px-4 sm:mt-20 md:grid-cols-2 lg:grid-cols-3" className="relative -mx-4 mt-0 grid h-196 max-h-[150vh] grid-cols-1 items-start gap-8 overflow-hidden px-4 md:grid-cols-2 lg:grid-cols-3"
> >
{isInView && ( {isInView && (
<> <>
@@ -206,8 +206,8 @@ function ReviewGrid() {
/> />
</> </>
)} )}
<div className="pointer-events-none absolute inset-x-0 top-0 h-32 bg-linear-to-b from-white" /> <div className="pointer-events-none absolute inset-x-0 top-0 h-32 bg-linear-to-b from-black" />
<div className="pointer-events-none absolute inset-x-0 bottom-0 h-32 bg-linear-to-t from-white" /> <div className="pointer-events-none absolute inset-x-0 bottom-0 h-32 bg-linear-to-t from-black" />
</div> </div>
) )
} }
@@ -221,7 +221,7 @@ export function UseCases() {
id="usecases" id="usecases"
ref={ref} ref={ref}
aria-labelledby="usecases-title" aria-labelledby="usecases-title"
className="py-12" className="bg-black py-12"
> >
<Container className=''> <Container className=''>
<motion.div <motion.div
@@ -232,12 +232,12 @@ export function UseCases() {
> >
<H2 <H2
id="usecases-title" id="usecases-title"
color="primary" color="light"
className="text-center" className="text-center"
> >
Coming Soon: The Future of Mycelium Coming Soon: The Future of Mycelium
</H2> </H2>
<P className="mt-6 text-center" color="custom"> <P className="mt-6 text-center" color="light">
Mycelium Cloud is evolving to bring even more powerful decentralized features, designed to enhance your experience and expand possibilities. Be the first to explore what's coming next by staying connected with our latest updates. Mycelium Cloud is evolving to bring even more powerful decentralized features, designed to enhance your experience and expand possibilities. Be the first to explore what's coming next by staying connected with our latest updates.
</P> </P>
</motion.div> </motion.div>

View File

@@ -0,0 +1,23 @@
import { Globe } from "@/components/ui/globe"
import { motion } from "framer-motion"
import { H2, H3, H4, P } from "./Texts"
export function WorldMap() {
return (
<div className="relative h-screen max-w-full overflow-hidden bg-black -top-20">
<div className="absolute top-0 left-0 px-6 py-24 z-10 max-w-lg">
<H4 className="" color="light">
Mycelium Network is Live.
</H4>
<P className="mt-6 text-base text-pretty leading-relaxed font-light" color="light">
Mycelium Cloud's advancement technology enables anyone to deploy their own Internet infrastructure, anywhere.
</P>
</div>
<div className="absolute inset-0 flex items-center justify-center">
<Globe className="top-28" />
</div>
<div className="pointer-events-none absolute inset-0 h-full bg-[radial-gradient(circle_at_50%_200%,rgba(0,0,0,0.2),rgba(255,255,255,0))]" />
</div>
);
}

View File

@@ -1,3 +1,3 @@
import { Ai21 } from '@lobehub/icons'; import { Ai21 } from '@lobehub/icons';
export default () => <Ai21.Brand size={40} />; export default () => <Ai21.Brand size={30} />;

View File

@@ -1,3 +1,3 @@
import { AlibabaCloud } from '@lobehub/icons'; import { AlibabaCloud } from '@lobehub/icons';
export default () => <AlibabaCloud.Text size={40} />; export default () => <AlibabaCloud.Text size={30} />;

View File

@@ -1,3 +1,3 @@
import { BaiduCloud } from '@lobehub/icons'; import { BaiduCloud } from '@lobehub/icons';
export default () => <BaiduCloud.Combine size={40} />; export default () => <BaiduCloud.Combine size={30} />;

View File

@@ -1,3 +1,3 @@
import { ByteDance } from '@lobehub/icons'; import { ByteDance } from '@lobehub/icons';
export default () => <ByteDance.Text size={40} />; export default () => <ByteDance.Text size={30} />;

View File

@@ -1,3 +1,3 @@
import { Claude } from '@lobehub/icons'; import { Claude } from '@lobehub/icons';
export default () => <Claude.Combine size={40} />; export default () => <Claude.Combine size={30} />;

View File

@@ -1,3 +1,3 @@
import { DeepMind } from '@lobehub/icons'; import { DeepMind } from '@lobehub/icons';
export default () => <DeepMind.Combine size={40} />; export default () => <DeepMind.Combine size={30} />;

View File

@@ -1,3 +1,3 @@
import { DeepSeek } from '@lobehub/icons'; import { DeepSeek } from '@lobehub/icons';
export default () => <DeepSeek.Combine size={40} />; export default () => <DeepSeek.Combine size={30} />;

View File

@@ -1,3 +1,3 @@
import { Minimax } from '@lobehub/icons'; import { Minimax } from '@lobehub/icons';
export default () => <Minimax.Combine size={40} />; export default () => <Minimax.Combine size={30} />;

View File

@@ -1,3 +1,3 @@
import { Mistral } from '@lobehub/icons'; import { Mistral } from '@lobehub/icons';
export default () => <Mistral.Combine size={40} />; export default () => <Mistral.Combine size={30} />;

View File

@@ -1,3 +1,3 @@
import { Moonshot } from '@lobehub/icons'; import { Moonshot } from '@lobehub/icons';
export default () => <Moonshot.Combine size={40} />; export default () => <Moonshot.Combine size={30} />;

View File

@@ -1,3 +1,3 @@
import { OpenAI } from '@lobehub/icons'; import { OpenAI } from '@lobehub/icons';
export default () => <OpenAI.Combine size={40} />; export default () => <OpenAI.Combine size={30} />;

View File

@@ -1,3 +1,3 @@
import { TencentCloud } from '@lobehub/icons'; import { TencentCloud } from '@lobehub/icons';
export default () => <TencentCloud.Combine size={40} />; export default () => <TencentCloud.Combine size={30} />;

View File

@@ -1,3 +1,3 @@
import { XAI } from '@lobehub/icons'; import { XAI } from '@lobehub/icons';
export default () => <XAI.Text size={40} />; export default () => <XAI.Text size={30} />;

View File

@@ -0,0 +1,96 @@
"use client";
import { cn } from "@/lib/utils";
import React, { useEffect, useState } from "react";
export const InfiniteMovingCards = ({
items,
direction = "left",
speed = "fast",
pauseOnHover = true,
className,
}: {
items: React.ReactNode[];
direction?: "left" | "right";
speed?: "fast" | "normal" | "slow";
pauseOnHover?: boolean;
className?: string;
}) => {
const containerRef = React.useRef<HTMLDivElement>(null);
const scrollerRef = React.useRef<HTMLUListElement>(null);
useEffect(() => {
addAnimation();
}, []);
const [start, setStart] = useState(false);
function addAnimation() {
if (containerRef.current && scrollerRef.current) {
const scrollerContent = Array.from(scrollerRef.current.children);
scrollerContent.forEach((item) => {
const duplicatedItem = item.cloneNode(true);
if (scrollerRef.current) {
scrollerRef.current.appendChild(duplicatedItem);
}
});
getDirection();
getSpeed();
setStart(true);
}
}
const getDirection = () => {
if (containerRef.current) {
if (direction === "left") {
containerRef.current.style.setProperty(
"--animation-direction",
"forwards"
);
} else {
containerRef.current.style.setProperty(
"--animation-direction",
"reverse"
);
}
}
};
const getSpeed = () => {
if (containerRef.current) {
if (speed === "fast") {
containerRef.current.style.setProperty("--animation-duration", "20s");
} else if (speed === "normal") {
containerRef.current.style.setProperty("--animation-duration", "40s");
} else {
containerRef.current.style.setProperty("--animation-duration", "80s");
}
}
};
return (
<div
ref={containerRef}
className={cn(
"scroller relative z-20 max-w-7xl overflow-hidden [mask-image:linear-gradient(to_right,transparent,white_20%,white_80%,transparent)]",
className
)}
>
<ul
ref={scrollerRef}
className={cn(
" flex min-w-full shrink-0 gap-4 py-0 w-max flex-nowrap",
start && "animate-scroll ",
pauseOnHover && "hover:[animation-play-state:paused]"
)}
>
{items.map((item, idx) => (
<li
className="w-[160px] max-w-full relative flex-shrink-0 flex items-center justify-center px-8 py-0 md:w-[180px]"
key={idx}
>
{item}
</li>
))}
</ul>
</div>
);
};

View File

@@ -10,12 +10,12 @@ const baseStyles = {
const variantStyles = { const variantStyles = {
solid: { solid: {
primary: 'bg-[#2F3178] text-white hover:bg-[#2F3178]/90 active:bg-[#2F3178]/80', primary: 'bg-[#005eff] text-white hover:bg-[#005eff]/90 active:bg-[#005eff]/80',
white: 'bg-white text-black hover:bg-white/90 active:bg-white/90 active:text-gray-400', white: 'bg-white text-black hover:bg-white/90 active:bg-white/90 active:text-gray-400',
gray: 'bg-gray-800 text-white hover:bg-gray-900 active:bg-gray-800 active:text-white/80', gray: 'bg-gray-800 text-white hover:bg-gray-900 active:bg-gray-800 active:text-white/80',
}, },
outline: { outline: {
primary: 'border-[#2F3178] text-[#2F3178] hover:border-[#2F3178]/80 hover:text-[#2F3178]/80 active:bg-gray-100 active:text-[#2F3178]/70', primary: 'border-[#005eff] text-[#005eff] hover:border-[#005eff]/80 hover:text-[#005eff]/80 active:bg-gray-100 active:text-[#005eff]/70',
gray: 'border-gray-300 text-gray-700 hover:border-gray-400 active:bg-gray-100 active:text-gray-700/80', gray: 'border-gray-300 text-gray-700 hover:border-gray-400 active:bg-gray-100 active:text-gray-700/80',
}, },
} }

View File

@@ -13,7 +13,7 @@ export function ScrollDown() {
return ( return (
<button <button
onClick={scrollToNext} onClick={scrollToNext}
className="fixed bottom-8 right-8 z-50 flex items-center gap-x-2 text-2xl font-medium text-[#1c1c49] lg:text-3xl animate-blink" className="fixed bottom-8 right-8 z-50 flex items-center gap-x-2 text-2xl font-medium text-white lg:text-3xl animate-blink"
> >
<span>scroll</span> <span>scroll</span>
<ChevronDoubleDownIcon className="h-6 w-6" /> <ChevronDoubleDownIcon className="h-6 w-6" />

128
src/components/ui/globe.tsx Normal file
View File

@@ -0,0 +1,128 @@
"use client";
import createGlobe, { COBEOptions } from "cobe";
import { useMotionValue, useSpring } from "motion/react";
import { useEffect, useRef } from "react";
import { cn } from "@/lib/utils";
const MOVEMENT_DAMPING = 1400;
const GLOBE_CONFIG: COBEOptions = {
width: 800,
height: 800,
onRender: () => {},
devicePixelRatio: 2,
phi: 0,
theta: 0.3,
dark: 0,
diffuse: 0.4,
mapSamples: 16000,
mapBrightness: 1.2,
baseColor: [1, 1, 1],
markerColor: [1 / 255, 94 / 255, 255 / 255],
glowColor: [1, 1, 1],
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={cn(
"absolute inset-0 mx-auto aspect-[1/1] w-full max-w-[600px]",
className,
)}
>
<canvas
className={cn(
"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>
);
}

View File

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

View File

@@ -1,8 +1,17 @@
@import 'tailwindcss'; @import 'tailwindcss';
@import "tw-animate-css";
@custom-variant dark (&:is(.dark *));
@plugin '@tailwindcss/forms'; @plugin '@tailwindcss/forms';
@theme { @theme {
@keyframes scroll {
to {
transform: translate(calc(-50% - 0.5rem));
}
}
--text-*: initial; --text-*: initial;
--text-xs: 0.75rem; --text-xs: 0.75rem;
--text-xs--line-height: 1rem; --text-xs--line-height: 1rem;
@@ -101,6 +110,10 @@
} }
@layer utilities { @layer utilities {
.animate-scroll {
animation: scroll var(--animation-duration, 40s) var(--animation-direction, forwards) linear infinite;
}
.animate-blink { .animate-blink {
animation: blink 2s infinite; animation: blink 2s infinite;
} }
@@ -112,10 +125,132 @@
@theme inline { @theme inline {
--animate-marquee: marquee var(--marquee-duration) linear infinite; --animate-marquee: marquee var(--marquee-duration) linear infinite;
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar: var(--sidebar);
--color-chart-5: var(--chart-5);
--color-chart-4: var(--chart-4);
--color-chart-3: var(--chart-3);
--color-chart-2: var(--chart-2);
--color-chart-1: var(--chart-1);
--color-ring: var(--ring);
--color-input: var(--input);
--color-border: var(--border);
--color-destructive: var(--destructive);
--color-accent-foreground: var(--accent-foreground);
--color-accent: var(--accent);
--color-muted-foreground: var(--muted-foreground);
--color-muted: var(--muted);
--color-secondary-foreground: var(--secondary-foreground);
--color-secondary: var(--secondary);
--color-primary-foreground: var(--primary-foreground);
--color-primary: var(--primary);
--color-popover-foreground: var(--popover-foreground);
--color-popover: var(--popover);
--color-card-foreground: var(--card-foreground);
--color-card: var(--card);
--color-foreground: var(--foreground);
--color-background: var(--background);
@keyframes marquee { @keyframes marquee {
100% { 100% {
transform: translateY(-50%); transform: translateY(-50%);
} }
} }
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px)
}
:root {
--radius: 0.625rem;
--background: oklch(1 0 0);
--foreground: oklch(0.13 0.028 261.692);
--card: oklch(1 0 0);
--card-foreground: oklch(0.13 0.028 261.692);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.13 0.028 261.692);
--primary: oklch(0.21 0.034 264.665);
--primary-foreground: oklch(0.985 0.002 247.839);
--secondary: oklch(0.967 0.003 264.542);
--secondary-foreground: oklch(0.21 0.034 264.665);
--muted: oklch(0.967 0.003 264.542);
--muted-foreground: oklch(0.551 0.027 264.364);
--accent: oklch(0.967 0.003 264.542);
--accent-foreground: oklch(0.21 0.034 264.665);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.928 0.006 264.531);
--input: oklch(0.928 0.006 264.531);
--ring: oklch(0.707 0.022 261.325);
--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.002 247.839);
--sidebar-foreground: oklch(0.13 0.028 261.692);
--sidebar-primary: oklch(0.21 0.034 264.665);
--sidebar-primary-foreground: oklch(0.985 0.002 247.839);
--sidebar-accent: oklch(0.967 0.003 264.542);
--sidebar-accent-foreground: oklch(0.21 0.034 264.665);
--sidebar-border: oklch(0.928 0.006 264.531);
--sidebar-ring: oklch(0.707 0.022 261.325);
}
.dark {
--background: oklch(0.13 0.028 261.692);
--foreground: oklch(0.985 0.002 247.839);
--card: oklch(0.21 0.034 264.665);
--card-foreground: oklch(0.985 0.002 247.839);
--popover: oklch(0.21 0.034 264.665);
--popover-foreground: oklch(0.985 0.002 247.839);
--primary: oklch(0.928 0.006 264.531);
--primary-foreground: oklch(0.21 0.034 264.665);
--secondary: oklch(0.278 0.033 256.848);
--secondary-foreground: oklch(0.985 0.002 247.839);
--muted: oklch(0.278 0.033 256.848);
--muted-foreground: oklch(0.707 0.022 261.325);
--accent: oklch(0.278 0.033 256.848);
--accent-foreground: oklch(0.985 0.002 247.839);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.551 0.027 264.364);
--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.21 0.034 264.665);
--sidebar-foreground: oklch(0.985 0.002 247.839);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0.002 247.839);
--sidebar-accent: oklch(0.278 0.033 256.848);
--sidebar-accent-foreground: oklch(0.985 0.002 247.839);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.551 0.027 264.364);
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
} }