Compare commits

19 Commits

Author SHA1 Message Date
76e0bdb7be style: update border colors from gray-200 to gray-300 and standardize cyan color usage 2025-10-22 15:47:52 +02:00
85c041ab49 style: update feature card styling with cyan hover effects and consistent padding 2025-10-22 15:47:41 +02:00
0231c4835c style: update stat cards with consistent hover effects and modern styling 2025-10-22 15:35:35 +02:00
4efc563aa9 feat: add Eyebrow component to section headers across landing page components 2025-10-22 15:27:47 +02:00
d70c2b6874 chore: switch from Mulish to Inter font family 2025-10-22 15:18:39 +02:00
db4c2d8ea0 Merge branch 'main' into development_new 2025-10-22 13:16:44 +02:00
28bef26fbc refactor: remove GetStarted section and consolidate with CallToAction component 2025-10-22 11:38:05 +02:00
73fe0e7c8e feat: add GetStarted section and rename deploy section to how-it-works 2025-10-22 11:34:16 +02:00
c59c09eee8 refactor: replace mailerlite popup with direct mailto link for waitlist signup 2025-10-22 11:32:04 +02:00
593201ae10 feat: add dotted glow background and enhance stack section animations 2025-10-22 11:30:26 +02:00
b46df781f8 feat: add hide-on-scroll header and redesign hero with video background 2025-10-21 16:45:54 +02:00
f44662829d feat: implement light theme version of stack section with hover animations 2025-10-20 17:08:28 +02:00
a663c32f53 feat: add decorative gradient overlays to hero section background 2025-10-20 16:56:00 +02:00
2f3dea92a2 feat: implement light theme hero section with cloud background and updated header 2025-10-20 16:20:51 +02:00
75b660d81e Merge branch 'main' into development 2025-10-20 13:56:07 +02:00
3f86c7e87f docs: add development guide and dependencies section to README 2025-10-20 13:55:50 +02:00
1c2c274848 Update README.md 2025-10-15 10:37:16 +00:00
1577eb6c6c Update README.md 2025-10-15 09:56:06 +00:00
f4519ec8bf Update README.md 2025-10-15 09:55:00 +00:00
41 changed files with 1277 additions and 118 deletions

View File

@@ -1,9 +1,11 @@
# Mycelium Cloud Website # Project Mycelium Website
- **Main Branch:** [https://project.mycelium.tf/](https://project.mycelium.tf/)
- **Dev Branch:** [https://www2.project.mycelium.tf/](https://www2.project.mycelium.tf/)
- **Repository:** [https://git.ourworld.tf/ourworld_web/www_project_mycelium/](https://git.ourworld.tf/ourworld_web/www_project_mycelium/) - **Repository:** [https://git.ourworld.tf/ourworld_web/www_project_mycelium/](https://git.ourworld.tf/ourworld_web/www_project_mycelium/)
- **Main Branch (Production):** [https://project.mycelium.tf/](https://project.mycelium.tf/)
- **Dev Branch (Staging):** [https://www2.project.mycelium.tf/](https://www2.project.mycelium.tf/)
--- ---
## About ## About
@@ -20,11 +22,19 @@ This is the official website for Mycelium Cloud, built using Next.js and Tailwin
--- ---
## Dependencies
- **UI**: [@headlessui/react](https://headlessui.com/)
- **Animation**: [framer-motion](https://www.framer.com/motion/)
- **Utilities**: [clsx](https://github.com/lukeed/clsx), [use-debounce](https://github.com/xnimorz/use-debounce)
---
## File Structure ## File Structure
- **Pages**: To edit the content of a specific page, navigate to `src/app/(main)/`. - **Pages**: To edit the content of a specific page, navigate to `src/app/(main)/`.
- **Components**: Reusable components are located in `src/components/`. - **Components**: Reusable components are located in `src/components/`.
- **Images and Videos**: Add or modify images and videos in the `public/` directory. Images are in `public/images/` and videos are in `public/videos/`. - **Images**: Add or modify images in the `public/images/` directory.
- **CSS**: Global styles can be found in `src/styles/tailwind.css`. Most styling is done using Tailwind CSS utility classes directly in the `.tsx` files. - **CSS**: Global styles can be found in `src/styles/tailwind.css`. Most styling is done using Tailwind CSS utility classes directly in the `.tsx` files.
--- ---
@@ -60,6 +70,57 @@ Follow these steps to get the project running locally:
--- ---
## Development Guide
This project follows a modular, component-based architecture. Pages are assembled by combining reusable components into a single layout.
### Homepage Structure
The homepage (`src/app/(main)/page.tsx`) is composed of the following components:
- `HomeHero`
- `WorldMap`
- `StackSectionPreview`
- `Steps`
- `Companies`
- `ClickableGallery`
- `BentoReviews`
- `CallToAction`
To edit a specific section of the homepage, navigate to `src/components/` and modify the corresponding component file.
### Base Layout
The main layout for the application is defined in `src/components/Layout.tsx`. This file includes the `Header` and `Footer` and wraps the primary content of each page.
### Creating a New Page
To create a new page, follow these steps:
1. **Create a Folder**: Inside the `src/app/(main)/` directory, create a new folder with the desired URL slug for your page (e.g., `new-page`).
2. **Create the Page File**: Inside the new folder, create a `page.tsx` file.
3. **Add Page Content**: Compose your page by importing and using the reusable components from `src/components/`. For example:
```tsx
import { HomeHero } from '@/components/HomeHero'
import { Faqs } from '@/components/Faqs'
export default function NewPage() {
return (
<>
<HomeHero />
<Faqs />
</>
)
}
```
The new page will be accessible at `http://localhost:3000/new-page`.
---
## Contributing ## Contributing
- **Never update the `main` branch directly.** All changes must be reviewed and merged by the team through a pull request. - **Never update the `main` branch directly.** All changes must be reviewed and merged by the team through a pull request.
@@ -72,7 +133,9 @@ Follow these steps to get the project running locally:
To report an issue, please use the following link and provide the requested information: To report an issue, please use the following link and provide the requested information:
- **Issue Tracker**: [https://git.ourworld.tf/tfgrid_internal/circle_tfgrid_ops/issues](https://git.ourworld.tf/tfgrid_internal/circle_tfgrid_ops/issues) - **Issue Tracker**: [git.ourworld.tf/ourworld_web/HOME/issues/new](https://git.ourworld.tf/ourworld_web/HOME/issues/new) and tag **OW Website & Wiki Project 2025**
- See the current web rpoject on [OW Website & Wiki Project 2025](https://git.ourworld.tf/ourworld_web/-/projects/153)
When reporting an issue, please include: When reporting an issue, please include:

View File

@@ -19,6 +19,7 @@
"hooks": "@/hooks" "hooks": "@/hooks"
}, },
"registries": { "registries": {
"@magicui": "https://magicui.design/r/{name}.json" "@magicui": "https://magicui.design/r/{name}.json",
"@aceternity": "https://ui.aceternity.com/registry/{name}.json"
} }
} }

BIN
public/images/cloud.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

BIN
public/images/mchip.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
public/videos/cloud.mp4 Normal file

Binary file not shown.

BIN
public/videos/cloud2.mp4 Normal file

Binary file not shown.

View File

@@ -1 +0,0 @@
export { Layout as default } from '@/components/Layout'

View File

@@ -3,9 +3,11 @@ import { Faqs } from '@/components/Faqs'
import { UseCases } from '@/components/UseCases' import { UseCases } from '@/components/UseCases'
import { Steps } from '@/components/Steps' import { Steps } from '@/components/Steps'
import { HomeHero } from '@/components/HomeHero' import { HomeHero } from '@/components/HomeHero'
import { HomeHeroLight } from '@/components/HomeHeroLight'
import { HomeHeroLight2 } from '@/components/HomeHeroLight2'
import { HomeAbout } from '@/components/HomeAbout' import { HomeAbout } from '@/components/HomeAbout'
import { ClickableGallery } from '@/components/ClickableGallery' import { ClickableGalleryLight } from '@/components/ClickableGalleryLight'
import { StackSectionPreview } from '@/components/StackSection' import { StackSectionLight } from '@/components/StackSectionLight'
import { Companies } from '@/components/Companies' import { Companies } from '@/components/Companies'
import { CallToAction } from '@/components/CallToAction' import { CallToAction } from '@/components/CallToAction'
import { ScrollDown } from '@/components/ui/ScrollDown' import { ScrollDown } from '@/components/ui/ScrollDown'
@@ -19,27 +21,27 @@ export default function Home() {
return ( return (
<> <>
<section id="home-hero"> <section id="home-hero">
<HomeHero /> <HomeHeroLight2 />
</section> </section>
<section id="network"> <section id="network">
<WorldMap /> <WorldMap />
</section> </section>
<section id="technologies"> <section id="technologies">
<StackSectionPreview /> <StackSectionLight />
</section> </section>
<section id="deploy"> <section id="how-it-works">
<Steps /> <Steps />
</section> </section>
<section id="llms"> <section id="llms">
<Companies /> <Companies />
</section> </section>
<section id="clickable-gallery"> <section id="clickable-gallery">
<ClickableGallery /> <ClickableGalleryLight />
</section> </section>
<section id="bento-reviews"> <section id="bento-reviews">
<BentoReviews /> <BentoReviews />
</section> </section>
<section id="call-to-action"> <section id="get-started">
<CallToAction /> <CallToAction />
</section> </section>
</> </>

View File

@@ -1,14 +1,14 @@
import { type Metadata } from 'next' import { type Metadata } from 'next'
import Script from 'next/script' import Script from 'next/script'
import { Mulish } from 'next/font/google' import { Inter } from 'next/font/google'
import clsx from 'clsx' import clsx from 'clsx'
import '@/styles/tailwind.css' import '@/styles/tailwind.css'
const mulish = Mulish({ const inter = Inter({
subsets: ['latin'], subsets: ['latin'],
display: 'swap', display: 'swap',
variable: '--font-mulish', variable: '--font-inter',
}) })
export const metadata: Metadata = { export const metadata: Metadata = {
@@ -26,7 +26,7 @@ export default function RootLayout({
children: React.ReactNode children: React.ReactNode
}) { }) {
return ( return (
<html lang="en" className={clsx('antialiased', mulish.variable)}> <html lang="en" className={clsx('antialiased', inter.variable)}>
<head> <head>
{/* MailerLite Universal */} {/* MailerLite Universal */}
<Script id="mailerlite-universal" strategy="afterInteractive"> <Script id="mailerlite-universal" strategy="afterInteractive">

View File

@@ -57,7 +57,7 @@ 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-gray-300 p-8 dark:border-gray-700"
> >
<feature.icon className="h-8 w-8 mb-4 text-[#2F3178]" /> <feature.icon className="h-8 w-8 mb-4 text-[#2F3178]" />
<CT as="span" className="font-semibold">{feature.name}</CT> <CT as="span" className="font-semibold">{feature.name}</CT>

View File

@@ -1,7 +1,7 @@
"use client"; "use client";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { H2, P } from "@/components/Texts"; import { SectionHeader, P, Eyebrow } from "@/components/Texts";
import React, { useState, useEffect, useRef } from "react"; import React, { useState, useEffect, useRef } from "react";
import { BentoGrid, MotionBentoGridItem } from "@/components/ui/bento-grid"; import { BentoGrid, MotionBentoGridItem } from "@/components/ui/bento-grid";
import { AnimatePresence, motion } from "framer-motion"; import { AnimatePresence, motion } from "framer-motion";
@@ -95,7 +95,8 @@ export function BentoReviews() {
<div className="relative isolate py-24 bg-black text-center w-full lg:px-0 px-4"> <div className="relative isolate py-24 bg-black text-center w-full lg:px-0 px-4">
<FadeIn transition={{ duration: 0.8, delay: 0.1 }}> <FadeIn transition={{ duration: 0.8, delay: 0.1 }}>
<div className="mx-auto max-w-5xl "> <div className="mx-auto max-w-5xl ">
<H2 className="text-center">Augmented Intelligence Fabric</H2> <Eyebrow color="accent">Components</Eyebrow>
<SectionHeader className="text-center">Augmented Intelligence Fabric</SectionHeader>
</div> </div>
</FadeIn> </FadeIn>
<FadeIn transition={{ duration: 0.8, delay: 0.2 }}> <FadeIn transition={{ duration: 0.8, delay: 0.2 }}>

View File

@@ -10,7 +10,7 @@ const baseStyles = {
const variantStyles = { const variantStyles = {
solid: { solid: {
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', cyan: 'relative overflow-hidden bg-cyan-500 text-white before:absolute before:inset-0 active:before:bg-transparent hover:before:bg-white/10 active:bg-cyan-600 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',

View File

@@ -1,16 +1,16 @@
"use client" "use client"
import { H2, P } from '@/components/Texts' import { SectionHeader, P } from '@/components/Texts'
export function CallTo() { export function CallTo() {
return ( return (
<div className="relative isolate overflow-hidden max-w-5xl mx-auto py-24"> <div className="relative isolate overflow-hidden max-w-5xl mx-auto py-24">
<div className="relative isolate overflow-hidden bg-gray-50/10 px-6 py-24 text-center shadow-md shadow-gray-900/5 sm:rounded-3xl sm:px-16 border border-gray-200"> <div className="relative isolate overflow-hidden bg-gray-50/10 px-6 py-24 text-center shadow-md shadow-gray-900/5 sm:rounded-3xl sm:px-16 border border-gray-300">
<div className="mx-auto max-w-4xl text-center"> <div className="mx-auto max-w-4xl text-center">
<H2 color="primary"> <SectionHeader color="primary">
Are you Ready? Are you Ready?
</H2> </SectionHeader>
<P color="custom" className="mt-8 max-w-3xl"> <P className="mt-8 max-w-3xl">
Why hand out your intelligence to centralized giants when you can build your own? Why hand out your intelligence to centralized giants when you can build your own?
</P> </P>
<div className="mt-10 flex items-center justify-center gap-x-6"> <div className="mt-10 flex items-center justify-center gap-x-6">
@@ -23,12 +23,7 @@ export function CallTo() {
Book a Meeting Book a Meeting
</a> </a>
<a <a
href="javascript:;" href="mailto:info@ourworld.tf"
onClick={() => {
if (typeof window !== 'undefined' && (window as any).ml_account) {
(window as any).ml_account('webforms', '6108375', 'l9m8g1', 'show')
}
}}
className="text-sm/6 font-semibold text-[#2F3178] hover:text-[#2F3178]/80" className="text-sm/6 font-semibold text-[#2F3178] hover:text-[#2F3178]/80"
> >
Join the Waitlist <span aria-hidden="true"></span> Join the Waitlist <span aria-hidden="true"></span>

View File

@@ -4,6 +4,7 @@ import { CircleBackground } from '@/components/CircleBackground'
import { Container } from '@/components/Container' import { Container } from '@/components/Container'
import { Button } from '@/components/Button' import { Button } from '@/components/Button'
import { FadeIn } from '@/components/FadeIn' import { FadeIn } from '@/components/FadeIn'
import { Eyebrow } from '@/components/Texts'
export function CallToAction() { export function CallToAction() {
return ( return (
@@ -27,6 +28,7 @@ export function CallToAction() {
<Container className="relative z-20"> <Container className="relative z-20">
<FadeIn> <FadeIn>
<div className="mx-auto max-w-md text-center"> <div className="mx-auto max-w-md text-center">
<Eyebrow color="accent"></Eyebrow>
<h2 className="text-3xl font-medium tracking-tight text-white sm:text-4xl"> <h2 className="text-3xl font-medium tracking-tight text-white sm:text-4xl">
Decentralized AI Agents that are Truly Yours Decentralized AI Agents that are Truly Yours
</h2> </h2>
@@ -47,11 +49,7 @@ export function CallToAction() {
<Button <Button
variant="outline" variant="outline"
color="white" color="white"
onClick={() => { href="mailto:info@ourworld.tf"
if (typeof window !== 'undefined' && (window as any).ml_account) {
(window as any).ml_account('webforms', '6108375', 'l9m8g1', 'show')
}
}}
> >
Join the Waitlist Join the Waitlist
</Button> </Button>

View File

@@ -6,7 +6,7 @@ import Image from 'next/image'
import { motion, AnimatePresence } from 'framer-motion' import { motion, AnimatePresence } from 'framer-motion'
import { wrap } from 'popmotion' import { wrap } from 'popmotion'
import { Button } from '@/components/Button'; import { Button } from '@/components/Button';
import { H2, P, CT } from '@/components/Texts'; import { SectionHeader, P, CT } from '@/components/Texts';
import { TypeAnimation } from 'react-type-animation' import { TypeAnimation } from 'react-type-animation'
import { FadeIn } from './FadeIn'; import { FadeIn } from './FadeIn';
@@ -53,7 +53,7 @@ export function ClickableGallery() {
<div className="relative isolate pt-8 pb-0 bg-transparent text-center w-full"> <div className="relative isolate pt-8 pb-0 bg-transparent text-center w-full">
<FadeIn transition={{ duration: 0.8, delay: 0.1 }}> <FadeIn transition={{ duration: 0.8, delay: 0.1 }}>
<div className="mx-auto max-w-5xl lg:mt-12"> <div className="mx-auto max-w-5xl lg:mt-12">
<H2 className="text-center">Agents with Endless Possibilities.</H2> <SectionHeader className="text-center">Agents with Endless Possibilities.</SectionHeader>
</div> </div>
</FadeIn> </FadeIn>
<FadeIn transition={{ duration: 0.8, delay: 0.2 }}> <FadeIn transition={{ duration: 0.8, delay: 0.2 }}>

View File

@@ -0,0 +1,180 @@
'use client'
import { useEffect, useMemo, useState } from 'react'
import { useResponsiveCarousel } from '@/hooks/useResponsiveCarousel';
import Image from 'next/image'
import { motion, AnimatePresence } from 'framer-motion'
import { wrap } from 'popmotion'
import { Button } from '@/components/Button';
import { SectionHeader, P, CT, Eyebrow } from '@/components/Texts';
import { TypeAnimation } from 'react-type-animation'
import { FadeIn } from './FadeIn';
const galleryItems = [
{ text: 'Navigate and interact with any web interface', image: '/images/gallery/interface.jpg', width: 448, height: 277 },
{ text: 'Process documents across all formats', image: '/images/gallery/docs.jpg', width: 448, height: 277 },
{ text: 'Execute multi-step workflows autonomously', image: '/images/gallery/flow.jpg', width: 448, height: 277 },
{ text: 'Manage calendars, emails, and tasks', image: '/images/gallery/calendar.jpg', width: 448, height: 277 },
{ text: 'Perform deep semantic search across all data sources', image: '/images/gallery/data.jpg', width: 448, height: 277 },
{ text: 'Identify patterns in complex datasets', image: '/images/gallery/datasets.jpg', width: 448, height: 277 },
{ text: 'Provide real-time market intelligence', image: '/images/gallery/market.jpg', width: 448, height: 277 },
{ text: 'Generate and debug code in multiple languages', image: '/images/gallery/code.jpg', width: 448, height: 277 },
{ text: 'Create consistent branded content', image: '/images/gallery/branding.jpg', width: 448, height: 277 },
{ text: 'Translate and localize materials', image: '/images/gallery/translate.jpg', width: 448, height: 277 },
{ text: 'Transform and migrate data structures', image: '/images/gallery/structure.jpg', width: 448, height: 277 },
]
// 🔧 Carousel Config
const VISIBLE = 4;
const AUTOPLAY_MS = 3200;
export function ClickableGalleryLight() {
const [active, setActive] = useState(0);
const [hovering, setHovering] = useState(false);
const { GAP, ROT_Y, DEPTH, SCALE_DROP } = useResponsiveCarousel();
// autoplay
useEffect(() => {
if (hovering) return
const id = setInterval(() => setActive((i) => wrap(0, galleryItems.length, i + 1)), AUTOPLAY_MS)
return () => clearInterval(id)
}, [hovering])
const indices = useMemo(
() => [...Array(VISIBLE * 2 + 1)].map((_, i) => wrap(0, galleryItems.length, active + i - VISIBLE)),
[active]
)
const next = () => setActive((i) => wrap(0, galleryItems.length, i + 1))
const prev = () => setActive((i) => wrap(0, galleryItems.length, i - 1))
return (
<div className="bg-[#FAFAFA]">
<div className="relative isolate pt-8 pb-0 text-center w-full">
<FadeIn transition={{ duration: 0.8, delay: 0.1 }}>
<div className="mx-auto max-w-5xl lg:mt-12">
<Eyebrow color="accent">Use Cases</Eyebrow>
<SectionHeader className="text-center" color="dark">Agents with Endless Possibilities.</SectionHeader>
</div>
</FadeIn>
<FadeIn transition={{ duration: 0.8, delay: 0.2 }}>
<div className="mx-auto max-w-4xl mt-6 lg:px-0 px-4">
<P className="text-center" color="dark">
Your private agent coordinates a team of specialists that spin up on demand, collaborate across your world, and deliver end-to-end results.
Many agents, one intelligenceyours.
</P>
</div>
</FadeIn>
</div>
<FadeIn transition={{ duration: 1, delay: 0.4 }}>
<section
className="relative w-full flex items-center justify-center overflow-hidden -mt-8 pt-0 pb-0"
onMouseEnter={() => setHovering(true)}
onMouseLeave={() => setHovering(false)}
>
<div className="relative w-full max-w-[1800px] h-[300px] md:h-[500px]" style={{ perspective: '1600px' }}>
<div className="absolute inset-0" style={{ transformStyle: 'preserve-3d' }}>
<AnimatePresence initial={false}>
{indices.map((idx, i) => {
const distance = i - VISIBLE;
const item = galleryItems[idx];
const x = distance * GAP;
const z = -Math.abs(distance) * DEPTH;
const r = distance * ROT_Y;
const s = 1 - Math.abs(distance) * SCALE_DROP;
const o = distance === 0 ? 1 : 0.80;
const zIndex = 100 - Math.abs(distance);
return (
<motion.div
key={`${idx}-${i}`}
className={`absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 will-change-transform overflow-hidden ${distance === 0 ? 'rounded-xl' : ''}`}
initial={{ opacity: 0 }}
animate={{
transform: `translateX(${x}px) translateZ(${z}px) rotateY(${r}deg) scale(${s})`,
zIndex,
opacity: o,
boxShadow: distance === 0 ? '0 0 20px 5px rgba(0, 0, 0, 0.1)' : 'none',
}}
exit={{ opacity: 0 }}
transition={{ type: 'spring', stiffness: 220, damping: 26 }}
onClick={() => setActive(idx)}
>
<div className="relative bg-gray-100 flex items-center justify-center">
<Image
src={item.image}
alt={item.text}
width={item.width}
height={item.height}
className="object-contain"
priority={i === VISIBLE}
/>
</div>
</motion.div>
);
})}
</AnimatePresence>
</div>
</div>
{/* Arrows */}
<div className="absolute inset-y-0 left-8 hidden md:flex items-center z-50">
<button
onClick={prev}
className="bg-white/50 rounded-full p-2 shadow-lg backdrop-blur-md text-black"
aria-label="Previous"
>
<svg className="size-8" viewBox="0 0 24 24" fill="none" dangerouslySetInnerHTML={{ __html: '<path d="M15 19L8 12l7-7" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>' }} />
</button>
</div>
<div className="absolute inset-y-0 right-8 hidden md:flex items-center z-50">
<button
onClick={next}
className="bg-white/50 rounded-full p-2 shadow-lg backdrop-blur-md text-black"
aria-label="Next"
>
<svg className="size-8" viewBox="0 0 24 24" fill="none" dangerouslySetInnerHTML={{ __html: '<path d="M9 5l7 7-7 7" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>' }} />
</button>
</div>
{/* Foreground pill (Desktop) */}
<div className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 z-[60] hidden md:block">
<div className="flex items-center justify-between w-[1040px] gap-6 rounded-2xl bg-gray-100/80 shadow-[0_8px_40px_rgba(0,0,0,0.15)] px-12 backdrop-blur">
<CT as="h4" className="max-w-[820px] h-[72px] flex items-center" color="dark">
<TypeAnimation
key={active}
sequence={[galleryItems[active].text]}
wrapper="span"
speed={50}
repeat={0}
/>
</CT>
<Button href="#" color="cyan" className="text-sm px-4 py-2 lg:text-base whitespace-nowrap">
Start
</Button>
</div>
</div>
</section>
{/* Text box (Mobile) */}
<div className="md:hidden w-full px-4 -mt-12 mb-16">
<div className="flex flex-row items-center justify-between w-full gap-x-4 rounded-2xl bg-gray-100/80 p-4 backdrop-blur-md">
<CT as="h4" className="w-full text-left h-[72px] leading-tight flex items-center" color="dark">
<TypeAnimation
key={active}
sequence={[galleryItems[active].text]}
wrapper="span"
speed={50}
repeat={0}
/>
</CT>
<Button href="#" color="cyan" className="text-xs px-3 py-1.5 whitespace-nowrap">
Start
</Button>
</div>
</div>
</FadeIn>
</div>
);
}

View File

@@ -2,7 +2,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 { P, Eyebrow } from '@/components/Texts';
import { InfiniteMovingCards } from "@/components/magicui/infinite-moving-cards"; import { InfiniteMovingCards } from "@/components/magicui/infinite-moving-cards";
@@ -50,6 +50,7 @@ export function Companies() {
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 1 }} transition={{ duration: 1 }}
> >
<Eyebrow color="accent"></Eyebrow>
<P className="hidden min-xl:text-gray-100 text-center mb-6"> <P className="hidden min-xl:text-gray-100 text-center mb-6">
Mycelium Cloud allows you to deploy and scale AI agents from top global providers on a decentralized, privacy-first infrastructure. Mycelium Cloud allows you to deploy and scale AI agents from top global providers on a decentralized, privacy-first infrastructure.
</P> </P>

View File

@@ -1,18 +1,18 @@
'use client' 'use client'
import CountUp from 'react-countup' import CountUp from 'react-countup'
import { H2 } from './Texts' import { SectionHeader } from './Texts'
interface CountUpNumberProps { interface CountUpNumberProps {
end: number end: number
className?: string className?: string
color?: 'light' | 'primary' | 'secondary' | 'custom' color?: 'light' | 'primary' | 'secondary'
} }
export function CountUpNumber({ end, className, color }: CountUpNumberProps) { export function CountUpNumber({ end, className, color }: CountUpNumberProps) {
return ( return (
<H2 color={color} className={className}> <SectionHeader color={color} className={className}>
<CountUp end={end} duration={2.75} separator="," /> <CountUp end={end} duration={2.75} separator="," />
</H2> </SectionHeader>
) )
} }

View File

@@ -62,7 +62,7 @@ export function Faqs() {
id="faqs" id="faqs"
ref={ref} ref={ref}
aria-labelledby="faqs-title" aria-labelledby="faqs-title"
className="border-t border-gray-200 py-20 sm:py-32 relative overflow-hidden" className="border-t border-gray-300 py-20 sm:py-32 relative overflow-hidden"
> >
<motion.div <motion.div
initial={{ opacity: 0 }} initial={{ opacity: 0 }}

View File

@@ -3,7 +3,7 @@ import clsx from 'clsx'
const formClasses = { const formClasses = {
light: 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', 'block w-full appearance-none rounded-lg border border-gray-300 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: 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', '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',
} }

View File

@@ -1,6 +1,6 @@
"use client"; "use client";
import { H2, P, CP } from "@/components/Texts"; import { SectionHeader, P, CP } from "@/components/Texts";
import { Button } from "@/components/Button"; import { Button } from "@/components/Button";
export function GetStarted() { export function GetStarted() {
@@ -36,7 +36,7 @@ export function GetStarted() {
}} }}
> >
<div className="max-w-8xl mx-auto px-4 text-left mb-12"> <div className="max-w-8xl mx-auto px-4 text-left mb-12">
<H2>Get Started</H2> <SectionHeader>Get Started</SectionHeader>
<P>Explore the documentation, code, and support channels to start building with Mycelium Cloud.</P> <P>Explore the documentation, code, and support channels to start building with Mycelium Cloud.</P>
</div> </div>

View File

@@ -3,7 +3,7 @@
import CountUp from "react-countup"; import CountUp from "react-countup";
import React from "react"; import React from "react";
import { Button } from "@/components/Button"; import { Button } from "@/components/Button";
import { H2, P } from "@/components/Texts"; import { SectionHeader, P } from "@/components/Texts";
export function GridStats() { export function GridStats() {
return ( return (
@@ -21,9 +21,9 @@ export function GridStats() {
{/* Column 1: Title & Description */} {/* Column 1: Title & Description */}
<div className="flex flex-col space-y-10"> <div className="flex flex-col space-y-10">
<div> <div>
<H2 color="light"> <SectionHeader color="light">
Robust Infrastructure for your Intellegence Needs Robust Infrastructure for your Intellegence Needs
</H2> </SectionHeader>
<P color="light" className="mt-6"> <P color="light" className="mt-6">
Mycelium's groundbreaking technology provides dedicated, performance-validated GPUs for your AI workloads. Mycelium's groundbreaking technology provides dedicated, performance-validated GPUs for your AI workloads.
</P> </P>

View File

@@ -72,7 +72,7 @@ export function Header() {
{({ open }) => ( {({ open }) => (
<> <>
<PopoverButton <PopoverButton
className="relative z-10 -m-2 inline-flex items-center rounded-lg stroke-white p-2 hover:bg-gray-200/50 hover:stroke-gray-400 focus:not-data-focus:outline-hidden active:stroke-white" className="relative z-10 -m-2 inline-flex items-center rounded-lg stroke-white p-2 hover:bg-gray-300/50 hover:stroke-gray-400 focus:not-data-focus:outline-hidden active:stroke-white"
aria-label="Toggle site navigation" aria-label="Toggle site navigation"
> >
{({ open }) => {({ open }) =>
@@ -113,11 +113,7 @@ export function Header() {
<Button <Button
variant="outline" variant="outline"
color="white" color="white"
onClick={() => { href="mailto:info@ourworld.tf"
if (typeof window !== 'undefined' && (window as any).ml_account) {
(window as any).ml_account('webforms', '6108375', 'l9m8g1', 'show')
}
}}
> >
Join the Waitlist Join the Waitlist
</Button> </Button>
@@ -134,11 +130,7 @@ export function Header() {
<Button <Button
variant="outline" variant="outline"
color="white" color="white"
onClick={() => { href="mailto:info@ourworld.tf"
if (typeof window !== 'undefined' && (window as any).ml_account) {
(window as any).ml_account('webforms', '6108375', 'l9m8g1', 'show')
}
}}
> >
Join the Waitlist Join the Waitlist
</Button> </Button>

View File

@@ -0,0 +1,98 @@
'use client'
import { useEffect, useRef, useState } from 'react'
import Link from 'next/link'
import { AnimatePresence, motion } from 'framer-motion'
import clsx from 'clsx'
function NavLinks() {
let [hoveredIndex, setHoveredIndex] = useState<number | null>(null)
let timeoutRef = useRef<number | null>(null)
return (
<div className="flex items-center gap-x-5">
<div className="flex items-center gap-x-5 border-l border-white/10 pl-5">
{[
['Technologies', '/#technologies'],
['Network', '/#network'],
['How it Works', '/#how-it-works'],
['Get Started', '/#get-started'],
['Contact', '/#contact'],
].map(([label, href], index) => (
<Link
key={label}
href={href}
className={clsx(
'relative rounded-lg px-3 py-2 text-sm text-black transition-colors delay-150 hover:text-gray-700 hover:delay-0',
)}
onMouseEnter={() => {
if (timeoutRef.current) {
window.clearTimeout(timeoutRef.current)
}
setHoveredIndex(index)
}}
onMouseLeave={() => {
timeoutRef.current = window.setTimeout(() => {
setHoveredIndex(null)
}, 200)
}}
>
<AnimatePresence>
{hoveredIndex === index && (
<motion.span
className="absolute inset-0 rounded-lg bg-white/10"
layoutId="hoverBackground"
initial={{ opacity: 0 }}
animate={{ opacity: 1, transition: { duration: 0.15 } }}
exit={{
opacity: 0,
transition: { duration: 0.15 },
}}
/>
)}
</AnimatePresence>
<span className="relative z-10">{label}</span>
</Link>
))}
</div>
</div>
)
}
export function HeaderLight() {
const [isVisible, setIsVisible] = useState(true);
const [lastScrollY, setLastScrollY] = useState(0);
const controlHeader = () => {
if (typeof window !== 'undefined') {
if (window.scrollY > lastScrollY && window.scrollY > 100) { // Hides when scrolling down past 100px
setIsVisible(false);
} else { // Shows when scrolling up
setIsVisible(true);
}
setLastScrollY(window.scrollY);
}
};
useEffect(() => {
if (typeof window !== 'undefined') {
window.addEventListener('scroll', controlHeader);
return () => {
window.removeEventListener('scroll', controlHeader);
};
}
}, [lastScrollY]);
return (
<motion.header
className="fixed top-4 left-0 right-0 z-50 flex justify-center"
initial={{ y: 0, opacity: 1 }}
animate={{ y: isVisible ? 0 : -100, opacity: isVisible ? 1 : 0 }}
transition={{ duration: 0.3, ease: 'easeInOut' }}
>
<div className="rounded-full bg-gray-50/90 px-5 py-3 shadow-lg ring-1 ring-white/10 backdrop-blur-sm">
<NavLinks />
</div>
</motion.header>
)
}

View File

@@ -8,7 +8,7 @@ import { Bars3Icon, XMarkIcon, ChevronDoubleDownIcon } from '@heroicons/react/24
import Image from 'next/image' import Image from 'next/image'
import diamondSvg from '@/images/diamond.svg' import diamondSvg from '@/images/diamond.svg'
import { Container } from '@/components/Container'; import { Container } from '@/components/Container';
import { H2, P, H3 } from '@/components/Texts'; import { SectionHeader, P, H3 } from '@/components/Texts';
import { Candy } from '@/components/Candy' import { Candy } from '@/components/Candy'
const navigation = [ const navigation = [
@@ -65,10 +65,10 @@ export function HomeAbout() {
transition={{ duration: 1, delay: 0.2 }} transition={{ duration: 1, delay: 0.2 }}
className="absolute top-24 left-0 max-w-xl text-left" className="absolute top-24 left-0 max-w-xl text-left"
> >
<H2> <SectionHeader>
Enterprise AI Agents Enterprise AI Agents
That Never Sleep. That Never Sleep.
</H2> </SectionHeader>
</motion.div> </motion.div>
<motion.div <motion.div
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
@@ -76,7 +76,7 @@ export function HomeAbout() {
transition={{ duration: 1, delay: 0.4 }} transition={{ duration: 1, delay: 0.4 }}
className="absolute top-54 left-0 max-w-xl text-left" className="absolute top-54 left-0 max-w-xl text-left"
> >
<P color="custom"> <P>
With Mycelium Cloud, you can deploy purpose-built AI agents to automate any workflow. Keep complete control of your data while scaling from simple tasks to complex decision-making. With Mycelium Cloud, you can deploy purpose-built AI agents to automate any workflow. Keep complete control of your data while scaling from simple tasks to complex decision-making.
</P> </P>
</motion.div> </motion.div>

View File

@@ -5,7 +5,7 @@ import { motion } from 'framer-motion'
import { TypeAnimation } from 'react-type-animation' import { TypeAnimation } from 'react-type-animation'
import { Dialog, DialogPanel } from '@headlessui/react' import { Dialog, DialogPanel } from '@headlessui/react'
import { Bars3Icon, XMarkIcon, ChevronDoubleDownIcon } from '@heroicons/react/24/outline' import { Bars3Icon, XMarkIcon, ChevronDoubleDownIcon } from '@heroicons/react/24/outline'
import { H1, H2, PL } from '@/components/Texts' import { H1, H2, P } from '@/components/Texts'
const navigation = [ const navigation = [
{ name: 'Product', href: '#' }, { name: 'Product', href: '#' },
@@ -54,9 +54,9 @@ export function HomeHero() {
transition={{ duration: 1, delay: 1 }} transition={{ duration: 1, delay: 1 }}
className="mt-12" className="mt-12"
> >
<PL className="mx-auto max-w-4xl text-center text-gray-100" color="light"> <P className="mx-auto max-w-4xl text-center " color="secondary">
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> </P>
</motion.div> </motion.div>
</div> </div>
</div> </div>

View File

@@ -0,0 +1,55 @@
'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, H2, PL } from '@/components/Texts'
import { ChevronRightIcon } from '@heroicons/react/20/solid'
const navigation = [
{ name: 'Product', href: '#' },
{ name: 'Features', href: '#' },
{ name: 'Marketplace', href: '#' },
{ name: 'Company', href: '#' },
]
export function HomeHeroLight() {
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
return (
<div
className="relative isolate overflow-hidden bg-white"
style={{
backgroundImage: 'url(/images/cloud.png)',
backgroundSize: 'cover',
backgroundPosition: 'center',
}}
>
<div className="mx-auto max-w-7xl px-6 pt-10 pb-24 sm:pb-32 lg:flex lg:px-8 lg:py-40">
<div className="mx-auto max-w-2xl shrink-0 lg:mx-0 lg:pt-8">
<h1 className="mt-10 text-5xl font-semibold tracking-tight text-pretty text-gray-900 sm:text-7xl">
Decentralized Autonomous Agentic Cloud.
</h1>
<p className="mt-8 text-lg font-medium text-pretty text-gray-500 sm:text-xl/8">
Mycelium's advancements in Agentic infrastructure supports private, secure and autonomous Agents that connect, learn and grow with you.
</p>
<div className="mt-10 flex items-center gap-x-6">
<a
href="#"
className="rounded-md bg-indigo-600 px-3.5 py-2.5 text-sm font-semibold text-white shadow-xs hover:bg-indigo-500 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
>
Get started
</a>
<a href="#" className="text-sm/6 font-semibold text-gray-900">
Learn more <span aria-hidden="true"></span>
</a>
</div>
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,60 @@
'use client'
import { useRef, useEffect } from 'react'
import { H1, H2, P, H5, Eyebrow } from '@/components/Texts'
export function HomeHeroLight2() {
const videoRef = useRef<HTMLVideoElement>(null)
useEffect(() => {
if (videoRef.current) videoRef.current.playbackRate = 0.4
}, [])
return (
<section className="relative h-screen overflow-hidden">
{/* Background video */}
<video
ref={videoRef}
src="/videos/cloud.mp4"
autoPlay
loop
muted
playsInline
className="absolute inset-0 h-full w-full object-cover z-[-10]"
/>
{/* Global soft wash + blur */}
<div className="absolute inset-0 bg-white opacity-30 backdrop-blur-md z-0" />
{/* Center “halo” for text legibility */}
<div
className="absolute inset-0 z-0"
style={{
background:
'radial-gradient(ellipse at center, rgba(255,255,255,0.96) 0%, rgba(255,255,255,0.88) 15%, rgba(255,255,255,0.72) 35%, rgba(255,255,255,0.08) 75%)'
}}
/>
{/* Content */}
<div className="relative z-10 h-full flex items-center justify-center">
<div className="mx-auto max-w-4xl text-center px-6 lg:px-8">
<Eyebrow color="accent"></Eyebrow>
<H1
className="pt-6"
style={{ textShadow: '0 2px 8px rgba(0,0,0,0.08)' }}
>
Decentralized Autonomous Agentic Cloud.
</H1>
<H5
className="mt-8"
style={{ textShadow: '0 1px 4px rgba(0,0,0,0.06)' }}
>
Mycelium&apos;s advancements in Agentic infrastructure support private, secure, and
autonomous Agents that connect, learn, and grow with you.
</H5>
</div>
</div>
</section>
)
}

View File

@@ -1,10 +1,11 @@
import { Footer } from '@/components/Footer' import { Footer } from '@/components/Footer'
import { Header } from '@/components/Header' import { HeaderLight } from '@/components/HeaderLight'
export function Layout({ children }: { children: React.ReactNode }) { export function Layout({ children }: { children: React.ReactNode }) {
return ( return (
<> <>
<Header /> <HeaderLight />
{children}
<main className="flex-auto">{children}</main> <main className="flex-auto">{children}</main>
<Footer /> <Footer />
</> </>

View File

@@ -5,7 +5,7 @@ import Link from 'next/link'
import { AnimatePresence, motion } from 'framer-motion' import { AnimatePresence, motion } from 'framer-motion'
import clsx from 'clsx' import clsx from 'clsx'
export function NavLinks({ className }: { className?: string }) { export function NavLinks({ className, theme = 'dark' }: { className?: string, theme?: 'dark' | 'light' }) {
let [hoveredIndex, setHoveredIndex] = useState<number | null>(null) let [hoveredIndex, setHoveredIndex] = useState<number | null>(null)
let timeoutRef = useRef<number | null>(null) let timeoutRef = useRef<number | null>(null)
@@ -33,7 +33,10 @@ export function NavLinks({ className }: { className?: string }) {
key={label} key={label}
href={href} href={href}
className={clsx( className={clsx(
'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', 'relative -mx-3 -my-2 rounded-lg px-3 py-2 text-sm transition-colors delay-150 hover:delay-0',
theme === 'dark'
? 'text-white hover:text-gray-300'
: 'text-gray-900 hover:text-gray-700',
className, className,
)} )}
onClick={(e) => handleClick(e, href)} onClick={(e) => handleClick(e, href)}
@@ -52,7 +55,10 @@ export function NavLinks({ className }: { className?: string }) {
<AnimatePresence> <AnimatePresence>
{hoveredIndex === index && ( {hoveredIndex === index && (
<motion.span <motion.span
className="absolute inset-0 rounded-lg bg-white/10" className={clsx(
'absolute inset-0 rounded-lg',
theme === 'dark' ? 'bg-white/10' : 'bg-gray-900/5',
)}
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

@@ -0,0 +1,75 @@
"use client";
import { motion } from "framer-motion";
import { StackedCubesLight } from "@/components/ui/StackedCubesLight";
import { H2, P, SectionHeader, Eyebrow } from "@/components/Texts";
import { FadeIn } from "./FadeIn";
import { DottedGlowBackground } from '@/components/ui/dotted-glow-background';
export function StackSectionLight() {
return (
<section className="relative w-full overflow-hidden py-24 lg:py-40">
{/* === Background Layer === */}
<div className="absolute inset-0 -z-10 bg-[#FAFAFA]">
{/* Dotted Glow Background */}
<DottedGlowBackground
gap={15}
radius={2}
color="rgba(0,0,0,0.4)"
glowColor="rgba(0,170,255,0.85)"
opacity={0.2}
/>
{/* Faint 3D grid floor */}
<div className="absolute inset-0 flex items-end justify-center overflow-hidden">
<div className="w-[200vw] h-[200vh] bg-[linear-gradient(to_right,rgba(0,0,0,0.03)_1px,transparent_1px),linear-gradient(to_bottom,rgba(0,0,0,0.03)_1px,transparent_1px)] bg-[size:60px_60px] [transform:perspective(800px)_rotateX(70deg)] origin-bottom opacity-50" />
</div>
</div>
{/* === Content === */}
<div className="relative mx-auto max-w-7xl px-6 lg:px-8 grid grid-cols-1 lg:grid-cols-3 gap-16 items-center">
{/* Left Column - Text */}
<div className="text-center lg:text-left">
<FadeIn>
<Eyebrow color="accent">Technology Layers</Eyebrow>
<SectionHeader color="dark" className="text-4xl sm:text-5xl font-semibold">
The Mycelium Stack
</SectionHeader>
</FadeIn>
<FadeIn>
<P color="dark" className="mt-6 text-lg leading-relaxed text-gray-600">
Built with Mycelium technology, our AI infrastructure ensures
unbreakable networks, complete data sovereignty, ultra-secure
agent-human communication, and unhackable data storage systems.
</P>
</FadeIn>
</div>
{/* Right Column - Animated Stack */}
<div className="lg:col-span-2 flex items-center justify-center lg:justify-start relative">
<motion.div
initial={{ y: 30, opacity: 0 }}
whileInView={{ y: 0, opacity: 1 }}
transition={{ duration: 1.2, ease: "easeOut" }}
viewport={{ once: true }}
>
<motion.div
animate={{
y: [0, -10, 0],
rotateZ: [0, 0.5, -0.5, 0],
}}
transition={{
duration: 6,
repeat: Infinity,
ease: "easeInOut",
}}
className="relative"
>
<StackedCubesLight />
</motion.div>
</motion.div>
</div>
</div>
</section>
);
}

View File

@@ -2,7 +2,7 @@
import React, { useRef } from 'react' import React, { useRef } from 'react'
import { motion, useInView } from 'framer-motion' import { motion, useInView } from 'framer-motion'
import { H2, P, CT, CP } from '@/components/Texts' import { SectionHeader, P, CT, CP, Eyebrow } from '@/components/Texts'
import { TbCircleNumber1Filled, TbCircleNumber2Filled, TbCircleNumber3Filled } from 'react-icons/tb' import { TbCircleNumber1Filled, TbCircleNumber2Filled, TbCircleNumber3Filled } from 'react-icons/tb'
const features = [ const features = [
@@ -30,7 +30,7 @@ export function Steps() {
const isInView = useInView(ref, { once: true }); const isInView = useInView(ref, { once: true });
return ( return (
<section id="benefits" ref={ref} className="relative pt-12 pb-4 px-4 lg:px-12 text-white"> <section id="benefits" ref={ref} className="relative pt-12 lg:pt-24 pb-4 px-4 lg:px-12 text-white">
<div className="relative px-6 lg:px-12"> <div className="relative px-6 lg:px-12">
<motion.div <motion.div
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
@@ -38,9 +38,10 @@ export function Steps() {
transition={{ duration: 0.8, delay: 0.1 }} transition={{ duration: 0.8, delay: 0.1 }}
className="mx-auto max-w-5xl text-center" className="mx-auto max-w-5xl text-center"
> >
<H2 className="text-3xl font-medium tracking-tight" color="light"> <Eyebrow color="accent">Get Started</Eyebrow>
<SectionHeader 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> </SectionHeader>
<P className="mt-6" color="light"> <P className="mt-6" 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>
@@ -57,7 +58,7 @@ 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-white/20 bg-black/30 lg:py-8 lg:px-8 py-6 px-6 backdrop-blur-sm transition-all duration-300 ease-in-out hover:scale-105 hover:border-white/40 hover:bg-black/40" className="rounded-2xl border border-gray-300 p-8 transition-all duration-300 ease-in-out hover:scale-105 hover:border-cyan-500 hover:shadow-lg hover:shadow-cyan-500/20 bg-white/5 backdrop-blur-md"
> >
<feature.icon className="h-8 w-8 mb-4 text-white" /> <feature.icon className="h-8 w-8 mb-4 text-white" />
<CT as="span" className="font-semibold" color="light">{feature.name}</CT> <CT as="span" className="font-semibold" color="light">{feature.name}</CT>

View File

@@ -4,10 +4,14 @@ import React from 'react'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
const colorVariants = { const colorVariants = {
primary: 'text-[#fffff]', primary: 'text-gray-900',
secondary: 'text-gray-200', secondary: 'text-gray-600',
custom: 'text-[#015eff]', light: 'text-gray-50',
light: '[#fcfcfc]', accent: 'text-cyan-500',
white: 'text-white',
dark: 'text-gray-950',
tertiary: 'text-gray-700',
lightSecondary: 'text-gray-300',
} as const } as const
type TextOwnProps = { type TextOwnProps = {
@@ -15,17 +19,19 @@ type TextOwnProps = {
className?: string className?: string
} }
// Polymorphic helpers (no forwardRef needed) // Polymorphic helpers
type PolymorphicProps<E extends React.ElementType, P> = type PolymorphicProps<E extends React.ElementType, P> = P & {
P & { as?: E } & as?: E
Omit<React.ComponentPropsWithoutRef<E>, keyof P | 'as'> } & Omit<React.ComponentPropsWithoutRef<E>, keyof P | 'as'>
const createTextComponent = <DefaultElement extends React.ElementType>( const createTextComponent = <DefaultElement extends React.ElementType>(
defaultElement: DefaultElement, defaultElement: DefaultElement,
defaultClassName: string defaultClassName: string
) => { ) => {
type Props<E extends React.ElementType = DefaultElement> = type Props<E extends React.ElementType = DefaultElement> = PolymorphicProps<
PolymorphicProps<E, TextOwnProps> E,
TextOwnProps
>
function Text<E extends React.ElementType = DefaultElement>({ function Text<E extends React.ElementType = DefaultElement>({
as, as,
@@ -38,24 +44,106 @@ const createTextComponent = <DefaultElement extends React.ElementType>(
return ( return (
<Tag <Tag
className={cn(defaultClassName, colorVariants[color], className)} className={cn(defaultClassName, colorVariants[color], className)}
{...(props as object)} {...props}
> >
{children} {children}
</Tag> </Tag>
) )
} }
;(Text as any).displayName = `Text(${typeof defaultElement === 'string' ? defaultElement : 'Component'})` ;(Text as any).displayName = `Text(${typeof defaultElement === 'string' ? defaultElement : 'Component'
})`
return Text return Text
} }
// Exports // Exports based on your tailwind.css and the example
export const H1 = createTextComponent('h1', 'text-5xl font-medium tracking-tight text-balance lg:text-8xl') export const H1 = createTextComponent(
export const PL = createTextComponent('p', 'text-2xl font-medium text-pretty leading-[1.2] lg:text-3xl') 'h1',
export const H2 = createTextComponent('h2', 'text-3xl font-medium text-pretty lg:text-4xl') 'text-6xl lg:text-7xl font-medium leading-tight tracking-tight'
export const P = createTextComponent('p', 'text-lg font-normal text-pretty leading-snug lg:text-xl lg:leading-normal') )
export const H3 = createTextComponent('h3', 'text-2xl lg:text-3xl font-medium') export const H2 = createTextComponent(
export const H4 = createTextComponent('h4', 'text-xl lg:text-2xl font-semibold leading-[1.15]') 'h2',
'text-4xl lg:text-6xl font-medium leading-tight tracking-tight'
)
export const H3 = createTextComponent(
'h3',
'text-3xl lg:text-5xl font-medium leading-tight tracking-tight'
)
export const H4 = createTextComponent(
'h4',
'text-2xl lg:text-4xl font-medium leading-snug tracking-tight'
)
export const P = createTextComponent(
'p',
'text-base lg:text-lg leading-relaxed'
)
export const Small = createTextComponent(
'small',
'text-sm font-medium leading-normal tracking-normal'
)
export const Subtle = createTextComponent(
'p',
'text-sm leading-normal tracking-normal text-gray-500'
)
export const H5 = createTextComponent(
'h5',
'text-lg lg:text-xl font-medium leading-snug tracking-tight'
)
export const Eyebrow = createTextComponent(
'h2',
'text-base/7 font-semibold tracking-wide'
)
export const SectionHeader = createTextComponent(
'p',
'text-3xl lg:text-4xl font-medium leading-tight tracking-tight'
)
export const CardEyebrow = createTextComponent(
'h3',
'text-sm/4 font-semibold tracking-wide'
)
export const CardTitle = createTextComponent(
'p',
'text-lg font-medium leading-snug tracking-tight'
)
export const CardDescription = createTextComponent(
'p',
'text-sm/6 leading-normal tracking-normal'
)
export const FeatureTitle = createTextComponent(
'h3',
'text-lg font-semibold leading-snug tracking-tight'
)
export const FeatureDescription = createTextComponent(
'p',
'text-sm leading-normal tracking-normal'
)
export const MobileFeatureTitle = createTextComponent(
'h3',
'text-sm font-semibold sm:text-lg leading-snug tracking-tight'
)
export const SecondaryFeatureTitle = createTextComponent(
'h3',
'text-base font-semibold leading-snug tracking-tight'
)
export const Question = createTextComponent(
'h3',
'text-lg/6 font-semibold tracking-tight'
)
export const Answer = createTextComponent(
'p',
'mt-4 text-sm leading-normal tracking-normal'
)
export const PageHeader = createTextComponent(
'h2',
'text-5xl lg:text-6xl font-medium leading-tight tracking-tight'
)
export const DownloadCardTitle = createTextComponent(
'dt',
'text-base/7 font-semibold tracking-wide'
)
export const DownloadCardDescription = createTextComponent(
'dd',
'text-base/7 leading-normal tracking-normal'
)
export const CT = createTextComponent('span', 'text-lg 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-[1.525] font-light') export const CP = createTextComponent('p', 'text-sm lg:text-base leading-[1.525] font-light')
export const NL = createTextComponent('span', 'text-lg font-semibold leading-[1.23]')

View File

@@ -14,7 +14,7 @@ import {
} from '@heroicons/react/24/solid' } from '@heroicons/react/24/solid'
import { Container } from '@/components/Container' import { Container } from '@/components/Container'
import { H2, P, CT, CP } from '@/components/Texts' import { SectionHeader, P, CT, CP } from '@/components/Texts'
import { motion, useInView } from 'framer-motion' import { motion, useInView } from 'framer-motion'
interface Review { interface Review {
@@ -147,9 +147,9 @@ export function UseCases() {
transition={{ duration: 0.8, delay: 0.1 }} transition={{ duration: 0.8, delay: 0.1 }}
className="flex flex-col items-start justify-start pt-10 lg:pr-12" className="flex flex-col items-start justify-start pt-10 lg:pr-12"
> >
<H2 id="usecases-title" color="light" className="text-left"> <SectionHeader id="usecases-title" color="light" className="text-left">
Augmented Intelligence Fabric Augmented Intelligence Fabric
</H2> </SectionHeader>
<P className="mt-4 text-left" color="light"> <P className="mt-4 text-left" color="light">
The sovereign substrate for autonomous AI. The sovereign substrate for autonomous AI.
Stateless, geo-aware, end-to-end encryptedand verifiable from intent to execution. Stateless, geo-aware, end-to-end encryptedand verifiable from intent to execution.

View File

@@ -2,7 +2,7 @@
import { Globe } from "@/components/ui/globe" import { Globe } from "@/components/ui/globe"
import { motion } from "framer-motion" import { motion } from "framer-motion"
import { H2, P, CT, CP } from "@/components/Texts" import { H2, P, CT, CP, SectionHeader, Eyebrow } from "@/components/Texts"
import { CountUpNumber } from './CountUpNumber' import { CountUpNumber } from './CountUpNumber'
export function WorldMap() { export function WorldMap() {
@@ -31,8 +31,9 @@ export function WorldMap() {
transition={{ duration: 0.5 }} transition={{ duration: 0.5 }}
className="max-w-xl" className="max-w-xl"
> >
<H2 color="light">Mycelium Network is Live.</H2> <Eyebrow color="accent">Network</Eyebrow>
<P className="hidden mt-4 text-base leading-relaxed font-light" color="light"> <SectionHeader color="light">Mycelium Network is Live.</SectionHeader>
<P className=" mt-4 text-base leading-relaxed" color="light">
Mycelium Cloud's advancement technology enables anyone to deploy Mycelium Cloud's advancement technology enables anyone to deploy
their own Internet infrastructure, anywhere. their own Internet infrastructure, anywhere.
</P> </P>
@@ -58,8 +59,7 @@ export function WorldMap() {
initial={{ opacity: 0, x: -20 }} initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }} animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.5, delay: 0.4 }} transition={{ duration: 0.5, delay: 0.4 }}
whileHover={{ scale: 1.05 }} className="lg:absolute lg:top-12 lg:-left-12 w-80 rounded-2xl border border-gray-300 p-8 transition-all duration-300 ease-in-out hover:scale-105 hover:border-cyan-500 hover:shadow-lg hover:shadow-cyan-500/20 bg-white/5 backdrop-blur-md"
className="lg:absolute lg:top-12 lg:-left-12 rounded-xl bg-white/5 backdrop-blur-md border border-white/10 px-4 lg:py-8 py-6 shadow-md w-80"
> >
<CT color="light" className="uppercase tracking-wide">CORES</CT> <CT color="light" className="uppercase tracking-wide">CORES</CT>
<CountUpNumber end={54958} color="light" className="mt-2 text-3xl font-bold" /> <CountUpNumber end={54958} color="light" className="mt-2 text-3xl font-bold" />
@@ -72,8 +72,7 @@ export function WorldMap() {
initial={{ opacity: 0, x: 20 }} initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }} animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.5, delay: 0.5 }} transition={{ duration: 0.5, delay: 0.5 }}
whileHover={{ scale: 1.05 }} className="lg:absolute lg:-top-10 lg:right-0 w-80 rounded-2xl border border-gray-300 p-8 transition-all duration-300 ease-in-out hover:scale-105 hover:border-cyan-500 hover:shadow-lg hover:shadow-cyan-500/20 bg-white/5 backdrop-blur-md"
className="lg:absolute lg:-top-10 lg:right-0 rounded-xl bg-white/5 backdrop-blur-md border border-white/10 px-4 lg:py-8 py-6 shadow-md w-80"
> >
<CT color="light" className="uppercase tracking-wide">NODES</CT> <CT color="light" className="uppercase tracking-wide">NODES</CT>
<CountUpNumber end={1493} color="light" className="mt-2 text-3xl font-bold" /> <CountUpNumber end={1493} color="light" className="mt-2 text-3xl font-bold" />
@@ -86,8 +85,7 @@ export function WorldMap() {
initial={{ opacity: 0, x: -20 }} initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }} animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.5, delay: 0.6 }} transition={{ duration: 0.5, delay: 0.6 }}
whileHover={{ scale: 1.05 }} className="lg:absolute lg:bottom-28 lg:-left-12 w-80 rounded-2xl border border-gray-300 p-8 transition-all duration-300 ease-in-out hover:scale-105 hover:border-cyan-500 hover:shadow-lg hover:shadow-cyan-500/20 bg-white/5 backdrop-blur-md"
className="lg:absolute lg:bottom-28 lg:-left-12 rounded-xl bg-white/5 backdrop-blur-md border border-white/10 px-4 lg:py-8 py-6 shadow-md w-80"
> >
<CT color="light" className="uppercase tracking-wide">SSD CAPACITY</CT> <CT color="light" className="uppercase tracking-wide">SSD CAPACITY</CT>
<CountUpNumber end={5388956} color="light" className="mt-2 text-3xl font-bold" /> <CountUpNumber end={5388956} color="light" className="mt-2 text-3xl font-bold" />
@@ -100,8 +98,7 @@ export function WorldMap() {
initial={{ opacity: 0, x: 20 }} initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }} animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.5, delay: 0.7 }} transition={{ duration: 0.5, delay: 0.7 }}
whileHover={{ scale: 1.05 }} className="lg:absolute lg:top-47 lg:right-0 w-80 rounded-2xl border border-gray-300 p-8 transition-all duration-300 ease-in-out hover:scale-105 hover:border-cyan-500 hover:shadow-lg hover:shadow-cyan-500/20 bg-white/5 backdrop-blur-md"
className="lg:absolute lg:top-44 lg:right-0 rounded-xl bg-white/5 backdrop-blur-md border border-white/10 px-4 lg:py-8 py-6 shadow-md w-80"
> >
<CT color="light" className="uppercase tracking-wide">COUNTRIES</CT> <CT color="light" className="uppercase tracking-wide">COUNTRIES</CT>
<CountUpNumber end={44} color="light" className="mt-2 text-3xl font-bold" /> <CountUpNumber end={44} color="light" className="mt-2 text-3xl font-bold" />

View File

@@ -0,0 +1,131 @@
"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;
onClick: () => 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 stopColor="#E5E7EB" />
<stop offset="1" stopColor="#9CA3AF" />
</linearGradient>
</defs>
</svg>
);
export function CubeLight({ title, descriptionTitle, description, isActive, index, onHover, onLeave, onClick }: CubeProps) {
return (
<div className="relative flex flex-col items-center">
<motion.div
className="relative cursor-pointer"
onMouseEnter={onHover}
onMouseLeave={onLeave}
onClick={onClick}
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-80"
style={{
filter: isActive ? 'brightness(1.1) drop-shadow(0 0 15px rgba(0, 0, 0, 0.2))' : 'brightness(1)',
}}
/>
{/* Title overlay */}
<div className="absolute inset-0 flex items-center justify-center">
<h3
className="text-black text-sm lg:text-base font-medium text-center px-4 drop-shadow-sm"
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="black"
strokeWidth="1"
opacity="0.6"
/>
</svg>
{/* Description text */}
<div className="ml-32 w-80">
<h4 className="text-black text-base font-semibold mb-2">
{descriptionTitle}
</h4>
<p className="text-gray-800 text-sm leading-relaxed font-light">
{description}
</p>
</div>
</motion.div>
)}
{/* Description for Mobile - Below cube */}
</motion.div>
</div>
);
}

View File

@@ -0,0 +1,95 @@
"use client";
import { useState } from "react";
import { motion } from "framer-motion";
import { CubeLight } from "@/components/ui/CubeLight"
const stackData = [
{
id: "agent",
title: "Agent Layer",
descriptionTitle: "Your sovereign agent with private memory and permissioned data access—always under your control.",
description:
"Choose from a wide library of open-source LLMs, paired with built-in semantic search and retrieval.\nIt coordinates across people, apps, and other agents to plan, create, and execute.\nIt operates inside a compliant legal & financial sandbox, ready for real-world transactions and operations.\nMore than just an assistant—an intelligent partner that learns and does your way.",
position: "top",
},
{
id: "network",
title: "Network Layer",
descriptionTitle: "A global, end-to-end encrypted overlay that simply doesnt break.",
description:
"Shortest-path routing moves your traffic the fastest way, every time.\nInstant discovery with integrated DNS, semantic search, and indexing.\nA distributed CDN and edge delivery keep content available and tamper-resistant worldwide.\nBuilt-in tool services and secure coding sandboxes—seamless on phones, desktops, and edge.",
position: "middle",
},
{
id: "cloud",
title: "Cloud Layer",
descriptionTitle: "An autonomous, stateless OS that enforces pre-deterministic deployments you define.",
description:
"Workloads are cryptographically bound to your private key—location and access are yours.\nNo cloud vendor or middleman in the path: end-to-end ownership and isolation by default.\nGeo-aware placement delivers locality, compliance, and ultra-low latency where it matters.\nEncrypted, erasure-coded storage, decentralized compute and GPU on demand—including LLMs.",
position: "bottom",
},
];
export function StackedCubesLight() {
const [active, setActive] = useState<string | null>("agent");
const [selectedForMobile, setSelectedForMobile] = useState<string | null>("agent");
const handleCubeClick = (id: string) => {
setSelectedForMobile(prev => (prev === id ? null : id));
};
const selectedMobileLayer = stackData.find(layer => layer.id === selectedForMobile);
return (
<div className="flex flex-col items-center">
<div
className="relative w-full flex items-center justify-center lg:justify-center min-h-[450px] lg:min-h-[400px]"
onMouseLeave={() => setActive("agent")}
>
<motion.div
className="relative lg:pl-0 pl-6 h-[300px] lg:h-[400px] w-64 sm:w-80 lg:w-96 scale-120 lg:scale-100"
animate={{ y: ["-8px", "8px"] }}
transition={{
duration: 4,
repeat: Infinity,
repeatType: "reverse",
ease: "easeInOut",
}}
>
{stackData.map((layer, index) => (
<div
key={layer.id}
className="absolute"
style={{
top: `calc(${index * 30}% - ${index * 10}px)`,
zIndex: active === layer.id ? 20 : 10 - index,
}}
>
<CubeLight
title={layer.title}
descriptionTitle={layer.descriptionTitle}
description={layer.description}
isActive={active === layer.id}
index={index}
onHover={() => setActive(layer.id)}
onLeave={() => {}}
onClick={() => handleCubeClick(layer.id)}
/>
</div>
))}
</motion.div>
</div>
{selectedMobileLayer && (
<div className="lg:hidden w-full max-w-md p-6 -mt-8 bg-gray-200/50 rounded-lg">
<h4 className="text-black text-lg font-semibold mb-2 text-center">
{selectedMobileLayer.descriptionTitle}
</h4>
<p className="text-gray-700 text-sm leading-relaxed text-center">
{selectedMobileLayer.description}
</p>
</div>
)}
</div>
);
}

View File

@@ -0,0 +1,308 @@
"use client";
import React, { useEffect, useRef, useState } from "react";
type DottedGlowBackgroundProps = {
className?: string;
/** distance between dot centers in pixels */
gap?: number;
/** base radius of each dot in CSS px */
radius?: number;
/** dot color (will pulse by alpha) */
color?: string;
/** optional dot color for dark mode */
darkColor?: string;
/** shadow/glow color for bright dots */
glowColor?: string;
/** optional glow color for dark mode */
darkGlowColor?: string;
/** optional CSS variable name for light dot color (e.g. --color-zinc-900) */
colorLightVar?: string;
/** optional CSS variable name for dark dot color (e.g. --color-zinc-100) */
colorDarkVar?: string;
/** optional CSS variable name for light glow color */
glowColorLightVar?: string;
/** optional CSS variable name for dark glow color */
glowColorDarkVar?: string;
/** global opacity for the whole layer */
opacity?: number;
/** background radial fade opacity (0 = transparent background) */
backgroundOpacity?: number;
/** minimum per-dot speed in rad/s */
speedMin?: number;
/** maximum per-dot speed in rad/s */
speedMax?: number;
/** global speed multiplier for all dots */
speedScale?: number;
};
/**
* Canvas-based dotted background that randomly glows and dims.
* - Uses a stable grid of dots.
* - Each dot gets its own phase + speed producing organic shimmering.
* - Handles high-DPI and resizes via ResizeObserver.
*/
export function DottedGlowBackground({
className,
gap = 12,
radius = 2,
color = "rgba(0,0,0,0.7)",
darkColor,
glowColor = "rgba(0, 170, 255, 0.85)",
darkGlowColor,
colorLightVar,
colorDarkVar,
glowColorLightVar,
glowColorDarkVar,
opacity = 0.6,
backgroundOpacity = 0,
speedMin = 0.4,
speedMax = 1.3,
speedScale = 1,
}: DottedGlowBackgroundProps) {
const canvasRef = useRef<HTMLCanvasElement | null>(null);
const containerRef = useRef<HTMLDivElement | null>(null);
const [resolvedColor, setResolvedColor] = useState<string>(color);
const [resolvedGlowColor, setResolvedGlowColor] = useState<string>(glowColor);
// Resolve CSS variable value from the container or root
const resolveCssVariable = (
el: Element,
variableName?: string,
): string | null => {
if (!variableName) return null;
const normalized = variableName.startsWith("--")
? variableName
: `--${variableName}`;
const fromEl = getComputedStyle(el as Element)
.getPropertyValue(normalized)
.trim();
if (fromEl) return fromEl;
const root = document.documentElement;
const fromRoot = getComputedStyle(root).getPropertyValue(normalized).trim();
return fromRoot || null;
};
const detectDarkMode = (): boolean => {
const root = document.documentElement;
if (root.classList.contains("dark")) return true;
if (root.classList.contains("light")) return false;
return (
window.matchMedia &&
window.matchMedia("(prefers-color-scheme: dark)").matches
);
};
// Keep resolved colors in sync with theme changes and prop updates
useEffect(() => {
const container = containerRef.current ?? document.documentElement;
const compute = () => {
const isDark = detectDarkMode();
let nextColor: string = color;
let nextGlow: string = glowColor;
if (isDark) {
const varDot = resolveCssVariable(container, colorDarkVar);
const varGlow = resolveCssVariable(container, glowColorDarkVar);
nextColor = varDot || darkColor || nextColor;
nextGlow = varGlow || darkGlowColor || nextGlow;
} else {
const varDot = resolveCssVariable(container, colorLightVar);
const varGlow = resolveCssVariable(container, glowColorLightVar);
nextColor = varDot || nextColor;
nextGlow = varGlow || nextGlow;
}
setResolvedColor(nextColor);
setResolvedGlowColor(nextGlow);
};
compute();
const mql = window.matchMedia
? window.matchMedia("(prefers-color-scheme: dark)")
: null;
const handleMql = () => compute();
mql?.addEventListener?.("change", handleMql);
const mo = new MutationObserver(() => compute());
mo.observe(document.documentElement, {
attributes: true,
attributeFilter: ["class", "style"],
});
return () => {
mql?.removeEventListener?.("change", handleMql);
mo.disconnect();
};
}, [
color,
darkColor,
glowColor,
darkGlowColor,
colorLightVar,
colorDarkVar,
glowColorLightVar,
glowColorDarkVar,
]);
useEffect(() => {
const el = canvasRef.current;
const container = containerRef.current;
if (!el || !container) return;
const ctx = el.getContext("2d");
if (!ctx) return;
let raf = 0;
let stopped = false;
const dpr = Math.max(1, window.devicePixelRatio || 1);
const resize = () => {
const { width, height } = container.getBoundingClientRect();
el.width = Math.max(1, Math.floor(width * dpr));
el.height = Math.max(1, Math.floor(height * dpr));
el.style.width = `${Math.floor(width)}px`;
el.style.height = `${Math.floor(height)}px`;
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
};
const ro = new ResizeObserver(resize);
ro.observe(container);
resize();
// Precompute dot metadata for a medium-sized grid and regenerate on resize
let dots: { x: number; y: number; phase: number; speed: number }[] = [];
const regenDots = () => {
dots = [];
const { width, height } = container.getBoundingClientRect();
const cols = Math.ceil(width / gap) + 2;
const rows = Math.ceil(height / gap) + 2;
const min = Math.min(speedMin, speedMax);
const max = Math.max(speedMin, speedMax);
for (let i = -1; i < cols; i++) {
for (let j = -1; j < rows; j++) {
const x = i * gap + (j % 2 === 0 ? 0 : gap * 0.5); // offset every other row
const y = j * gap;
// Randomize phase and speed slightly per dot
const phase = Math.random() * Math.PI * 2;
const span = Math.max(max - min, 0);
const speed = min + Math.random() * span; // configurable rad/s
dots.push({ x, y, phase, speed });
}
}
};
const regenThrottled = () => {
regenDots();
};
regenDots();
let last = performance.now();
const draw = (now: number) => {
if (stopped) return;
const dt = (now - last) / 1000; // seconds
last = now;
const { width, height } = container.getBoundingClientRect();
ctx.clearRect(0, 0, el.width, el.height);
ctx.globalAlpha = opacity;
// optional subtle background fade for depth (defaults to 0 = transparent)
if (backgroundOpacity > 0) {
const grad = ctx.createRadialGradient(
width * 0.5,
height * 0.4,
Math.min(width, height) * 0.1,
width * 0.5,
height * 0.5,
Math.max(width, height) * 0.7,
);
grad.addColorStop(0, "rgba(0,0,0,0)");
grad.addColorStop(
1,
`rgba(0,0,0,${Math.min(Math.max(backgroundOpacity, 0), 1)})`,
);
ctx.fillStyle = grad as unknown as CanvasGradient;
ctx.fillRect(0, 0, width, height);
}
// animate dots
ctx.save();
ctx.fillStyle = resolvedColor;
const time = (now / 1000) * Math.max(speedScale, 0);
for (let i = 0; i < dots.length; i++) {
const d = dots[i];
// Linear triangle wave 0..1..0 for linear glow/dim
const mod = (time * d.speed + d.phase) % 2;
const lin = mod < 1 ? mod : 2 - mod; // 0..1..0
const a = 0.25 + 0.55 * lin; // 0.25..0.8 linearly
// draw glow when bright
if (a > 0.6) {
const glow = (a - 0.6) / 0.4; // 0..1
ctx.shadowColor = resolvedGlowColor;
ctx.shadowBlur = 6 * glow;
} else {
ctx.shadowColor = "transparent";
ctx.shadowBlur = 0;
}
ctx.globalAlpha = a * opacity;
ctx.beginPath();
ctx.arc(d.x, d.y, radius, 0, Math.PI * 2);
ctx.fill();
}
ctx.restore();
raf = requestAnimationFrame(draw);
};
const handleResize = () => {
resize();
regenThrottled();
};
window.addEventListener("resize", handleResize);
raf = requestAnimationFrame(draw);
return () => {
stopped = true;
cancelAnimationFrame(raf);
window.removeEventListener("resize", handleResize);
ro.disconnect();
};
}, [
gap,
radius,
resolvedColor,
resolvedGlowColor,
opacity,
backgroundOpacity,
speedMin,
speedMax,
speedScale,
]);
return (
<div
ref={containerRef}
className={className}
style={{ position: "absolute", inset: 0 }}
>
<canvas
ref={canvasRef}
style={{ display: "block", width: "100%", height: "100%" }}
/>
</div>
);
}
export default DottedGlowBackground;

View File

@@ -20,8 +20,9 @@ const GLOBE_CONFIG: COBEOptions = {
mapSamples: 16000, mapSamples: 16000,
mapBrightness: 1.1, mapBrightness: 1.1,
baseColor: [0.8, 0.8, 0.8], // sleek dark gray globe baseColor: [0.8, 0.8, 0.8], // sleek dark gray globe
markerColor: [0.3, 0.6, 1], // soft, elegant blue markerColor: [0.02, 0.71, 0.83], // cyan-500
glowColor: [0.8, 0.8, 0.85], // subtle glow glowColor: [0.8, 0.8, 0.85], // grey
markers: [ markers: [
// --- Core Global Markers --- // --- Core Global Markers ---
{ location: [14.5995, 120.9842], size: 0.03 }, // Manila { location: [14.5995, 120.9842], size: 0.03 }, // Manila

View File

@@ -64,7 +64,7 @@
--color-gray-900: oklch(0.205 0 0); --color-gray-900: oklch(0.205 0 0);
--color-gray-950: oklch(0.145 0 0); --color-gray-950: oklch(0.145 0 0);
--font-sans: var(--font-mulish); --font-sans: var(--font-inter);
--container-2xl: 40rem; --container-2xl: 40rem;

View File

@@ -24,5 +24,16 @@
} }
}, },
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"] "exclude": ["node_modules"],
"extend": {
"animation": {
"pulse-slow": "pulse 6s ease-in-out infinite"
},
"keyframes": {
"pulse": {
"0%, 100%": { "opacity": "1" },
"50%": { "opacity": "0.6" }
}
}
}
} }