Compare commits
25 Commits
343b7ae22e
...
developmen
Author | SHA1 | Date | |
---|---|---|---|
9a4f347ee8 | |||
017fc41d2b | |||
ab14a5a8e5 | |||
41d4c3a054 | |||
a362985d4c | |||
1d66fd60a4 | |||
43d995bbc2 | |||
0dedde3592 | |||
d5b9303d94 | |||
3a240177c4 | |||
0479b7330a | |||
a78bc67ed3 | |||
a035500c34 | |||
2b5c502724 | |||
a462afc8b2 | |||
bf78cde2d8 | |||
cde6c90033 | |||
f5ab743987 | |||
45364a7452 | |||
b7f25d712f | |||
2fdcb3697d | |||
204625b9a8 | |||
02557fcb82 | |||
9277bc7105 | |||
3f6ffbe4ea |
86
README.md
@@ -1,35 +1,83 @@
|
||||
# Mycelium
|
||||
# Mycelium Cloud Website
|
||||
|
||||
Mycelium is a [Tailwind Plus](https://tailwindcss.com/plus) site template built using [Tailwind CSS](https://tailwindcss.com) and [Next.js](https://nextjs.org).
|
||||
This is the official website for Mycelium Cloud, built using Next.js and Tailwind CSS.
|
||||
|
||||
## Getting started
|
||||
## Getting Started
|
||||
|
||||
To get started with this template, first install the npm dependencies:
|
||||
Follow these instructions to get a local copy up and running for development and testing purposes.
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
### Prerequisites
|
||||
|
||||
Next, run the development server:
|
||||
Make sure you have Node.js and npm installed on your machine. You can download them from [nodejs.org](https://nodejs.org/).
|
||||
|
||||
### Installation
|
||||
|
||||
1. Clone the repository to your local machine.
|
||||
2. Install the NPM packages:
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
### Running the Application
|
||||
|
||||
To run the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Finally, open [http://localhost:3000](http://localhost:3000) in your browser to view the website.
|
||||
Open [http://localhost:3000](http://localhost:3000) in your browser to see the result.
|
||||
|
||||
## Customizing
|
||||
## Git Workflow
|
||||
|
||||
You can start editing this template by modifying the files in the `/src` folder. The site will auto-update as you edit these files.
|
||||
We follow a branching model to ensure code quality and a stable production environment. All new work should be done on a feature branch.
|
||||
|
||||
1. **Switch to the `development` branch** and make sure it's up to date:
|
||||
```bash
|
||||
git checkout development
|
||||
git pull origin development
|
||||
```
|
||||
|
||||
2. **Create a new feature branch** for your changes:
|
||||
```bash
|
||||
git checkout -b your-feature-name
|
||||
```
|
||||
|
||||
3. **Make your changes and commit them**.
|
||||
|
||||
4. **Push your feature branch** to the remote repository:
|
||||
```bash
|
||||
git push origin your-feature-name
|
||||
```
|
||||
|
||||
5. **Create a Pull Request** on GitHub from your feature branch to the `development` branch.
|
||||
|
||||
6. After the pull request is reviewed and merged, the changes will be on the `development` branch. To deploy to production, the `development` branch will be merged into `main`.
|
||||
|
||||
## Project Structure
|
||||
|
||||
Here is an overview of the key directories in the project:
|
||||
|
||||
- `src/app/(main)/page.tsx`
|
||||
This is the main entry point for the homepage.
|
||||
|
||||
- `src/components/`
|
||||
This directory contains all the reusable React components used throughout the site. The main components rendered on the homepage (`src/app/(main)/page.tsx`) are:
|
||||
- `HomeHero.tsx`
|
||||
- `StackSection.tsx` (as `StackSectionPreview`)
|
||||
- `BentoReviews.tsx`
|
||||
- `WorldMap.tsx`
|
||||
- `Steps.tsx`
|
||||
- `Companies.tsx`
|
||||
- `ClickableGallery.tsx`
|
||||
- `CallToAction.tsx`
|
||||
|
||||
- `public/images/`
|
||||
All static images are stored here. You can find logos, gallery images, and other visual assets in this folder.
|
||||
|
||||
- `public/videos/`
|
||||
This folder contains video assets used on the site.
|
||||
|
||||
## License
|
||||
|
||||
This site template is a commercial product and is licensed under the [Tailwind Plus license](https://tailwindcss.com/plus/license).
|
||||
|
||||
## Learn more
|
||||
|
||||
To learn more about the technologies used in this site template, see the following resources:
|
||||
|
||||
- [Tailwind CSS](https://tailwindcss.com/docs) - the official Tailwind CSS documentation
|
||||
- [Next.js](https://nextjs.org/docs) - the official Next.js documentation
|
||||
- [Headless UI](https://headlessui.dev) - the official Headless UI documentation
|
||||
|
1
next-env.d.ts
vendored
@@ -1,5 +1,6 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
/// <reference types="next/navigation-types/compat/navigation" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
|
||||
|
27
package-lock.json
generated
@@ -11,6 +11,7 @@
|
||||
"@headlessui/react": "^2.1.0",
|
||||
"@heroicons/react": "^2.2.0",
|
||||
"@lobehub/icons": "^1.97.2",
|
||||
"@tabler/icons-react": "^3.35.0",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"@types/node": "^20.10.8",
|
||||
"@types/react": "^18.2.55",
|
||||
@@ -2976,6 +2977,32 @@
|
||||
"tslib": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tabler/icons": {
|
||||
"version": "3.35.0",
|
||||
"resolved": "https://registry.npmjs.org/@tabler/icons/-/icons-3.35.0.tgz",
|
||||
"integrity": "sha512-yYXe+gJ56xlZFiXwV9zVoe3FWCGuZ/D7/G4ZIlDtGxSx5CGQK110wrnT29gUj52kEZoxqF7oURTk97GQxELOFQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/codecalm"
|
||||
}
|
||||
},
|
||||
"node_modules/@tabler/icons-react": {
|
||||
"version": "3.35.0",
|
||||
"resolved": "https://registry.npmjs.org/@tabler/icons-react/-/icons-react-3.35.0.tgz",
|
||||
"integrity": "sha512-XG7t2DYf3DyHT5jxFNp5xyLVbL4hMJYJhiSdHADzAjLRYfL7AnjlRfiHDHeXxkb2N103rEIvTsBRazxXtAUz2g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tabler/icons": "3.35.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/codecalm"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">= 16"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/forms": {
|
||||
"version": "0.5.10",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.10.tgz",
|
||||
|
@@ -13,6 +13,7 @@
|
||||
"@headlessui/react": "^2.1.0",
|
||||
"@heroicons/react": "^2.2.0",
|
||||
"@lobehub/icons": "^1.97.2",
|
||||
"@tabler/icons-react": "^3.35.0",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"@types/node": "^20.10.8",
|
||||
"@types/react": "^18.2.55",
|
||||
|
BIN
public/images/gallery/branding.jpg
Normal file
After Width: | Height: | Size: 101 KiB |
BIN
public/images/gallery/calendar.jpg
Normal file
After Width: | Height: | Size: 118 KiB |
BIN
public/images/gallery/code.jpg
Normal file
After Width: | Height: | Size: 114 KiB |
BIN
public/images/gallery/data.jpg
Normal file
After Width: | Height: | Size: 177 KiB |
BIN
public/images/gallery/datasets.jpg
Normal file
After Width: | Height: | Size: 128 KiB |
BIN
public/images/gallery/docs.jpg
Normal file
After Width: | Height: | Size: 84 KiB |
BIN
public/images/gallery/flow.jpg
Normal file
After Width: | Height: | Size: 54 KiB |
BIN
public/images/gallery/interface.jpg
Normal file
After Width: | Height: | Size: 110 KiB |
BIN
public/images/gallery/market.jpg
Normal file
After Width: | Height: | Size: 120 KiB |
BIN
public/images/gallery/structure.jpg
Normal file
After Width: | Height: | Size: 109 KiB |
BIN
public/images/gallery/translate.jpg
Normal file
After Width: | Height: | Size: 78 KiB |
BIN
public/images/placeholder.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
public/videos/agent.mp4
Normal file
BIN
public/videos/deterministic.mp4
Normal file
BIN
public/videos/fungistor.mp4
Normal file
BIN
public/videos/herodb.mp4
Normal file
BIN
public/videos/mesh.mp4
Normal file
BIN
public/videos/sandbox.mp4
Normal file
BIN
public/videos/universal.mp4
Normal file
@@ -13,6 +13,7 @@ import { ScrollUp } from '@/components/ui/ScrollUp'
|
||||
import { GridStats } from '@/components/GridStats'
|
||||
import { WorldMap } from '@/components/WorldMap'
|
||||
import { GetStarted } from '@/components/GetStarted'
|
||||
import { BentoReviews } from '@/components/BentoReviews'
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
@@ -20,9 +21,12 @@ export default function Home() {
|
||||
<section id="home-hero">
|
||||
<HomeHero />
|
||||
</section>
|
||||
<section id="about">
|
||||
<section id="technologies">
|
||||
<StackSectionPreview />
|
||||
</section>
|
||||
<section id="bento-reviews">
|
||||
<BentoReviews />
|
||||
</section>
|
||||
<section id="network">
|
||||
<WorldMap />
|
||||
</section>
|
||||
@@ -32,15 +36,9 @@ export default function Home() {
|
||||
<section id="llms">
|
||||
<Companies />
|
||||
</section>
|
||||
<section id="features">
|
||||
<UseCases />
|
||||
</section>
|
||||
<section id="clickable-gallery">
|
||||
<ClickableGallery />
|
||||
</section>
|
||||
<section id="get-started">
|
||||
<GetStarted />
|
||||
</section>
|
||||
<section id="call-to-action">
|
||||
<CallToAction />
|
||||
</section>
|
||||
|
172
src/components/BentoReviews.tsx
Normal file
@@ -0,0 +1,172 @@
|
||||
"use client";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { H2, P } from "@/components/Texts";
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import { BentoGrid, MotionBentoGridItem } from "@/components/ui/bento-grid";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { FadeIn } from "./FadeIn";
|
||||
|
||||
const items = [
|
||||
{
|
||||
title: 'FungiStor',
|
||||
subtitle: 'Long-Term AI Memory',
|
||||
description: 'Quantum-safe permanent storage preserving AI knowledge forever. Zero-knowledge architecture with mathematical dispersal ensures immortality.',
|
||||
video: "/videos/fungistor.mp4",
|
||||
},
|
||||
{
|
||||
title: 'HeroDB',
|
||||
subtitle: 'Active AI Memory',
|
||||
description: 'High-performance datastore for AI working memory. Multi-modal indexing enables vector search with global accessibility.',
|
||||
video: "/videos/herodb.mp4",
|
||||
},
|
||||
{
|
||||
title: 'MOS Sandboxes',
|
||||
subtitle: 'Secure Agent Workspaces',
|
||||
description: 'Lightweight isolated environments deploying globally in five seconds. Hardware-level isolation ensures maximum security for agents.',
|
||||
video: "/videos/sandbox.mp4",
|
||||
},
|
||||
{
|
||||
title: 'Mycelium Mesh',
|
||||
subtitle: 'Secure Communication Network',
|
||||
description: 'Peer-to-peer overlay network with end-to-end encryption. Self-healing shortest-path routing creates resilient agentic communication.',
|
||||
video: "/videos/mesh.mp4",
|
||||
},
|
||||
{
|
||||
title: 'Deterministic Deployment',
|
||||
subtitle: 'Verifiable Code Execution',
|
||||
description: 'Cryptographic guarantee system ensuring deployed code matches specifications. Prevents supply-chain attacks with immutable trails.',
|
||||
video: "/videos/deterministic.mp4",
|
||||
},
|
||||
{
|
||||
title: 'Agent Coordination',
|
||||
subtitle: 'Sovereign Workflow Management',
|
||||
description: 'User-centric orchestration where HERO agents coordinate worker fleets. Planetary-scale coordination with instant spawning.',
|
||||
video: "/videos/agent.mp4",
|
||||
},
|
||||
{
|
||||
title: 'Universal Interface Layer',
|
||||
subtitle: 'AI Service Gateway',
|
||||
description: 'Unified broker connecting agents to LLMs, APIs, and services. Integrated micropayments simplify development.',
|
||||
video: "/videos/universal.mp4",
|
||||
},
|
||||
|
||||
];
|
||||
|
||||
export function BentoReviews() {
|
||||
const [activeIndex, setActiveIndex] = useState(0);
|
||||
const [isPaused, setIsPaused] = useState(false);
|
||||
const intervalRef = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isPaused) {
|
||||
intervalRef.current = setInterval(() => {
|
||||
setActiveIndex((prevIndex) => (prevIndex + 1) % items.length);
|
||||
}, 3000);
|
||||
} else {
|
||||
if (intervalRef.current) {
|
||||
clearInterval(intervalRef.current);
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (intervalRef.current) {
|
||||
clearInterval(intervalRef.current);
|
||||
}
|
||||
};
|
||||
}, [isPaused]);
|
||||
|
||||
const handleCardTap = () => {
|
||||
setIsPaused(true);
|
||||
};
|
||||
|
||||
const handlePrev = () => {
|
||||
setActiveIndex((prevIndex) => (prevIndex - 1 + items.length) % items.length);
|
||||
setIsPaused(true);
|
||||
};
|
||||
|
||||
const handleNext = () => {
|
||||
setActiveIndex((prevIndex) => (prevIndex + 1) % items.length);
|
||||
setIsPaused(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="relative isolate pt-24 pb-12 bg-black text-center w-full lg:px-0 px-4">
|
||||
<FadeIn transition={{ duration: 0.8, delay: 0.1 }}>
|
||||
<div className="mx-auto max-w-5xl ">
|
||||
<H2 className="text-center">Mycelium Technologies</H2>
|
||||
</div>
|
||||
</FadeIn>
|
||||
<FadeIn transition={{ duration: 0.8, delay: 0.2 }}>
|
||||
<div className="mx-auto max-w-4xl mt-6 mb-8">
|
||||
<P className="text-center" color="primary">
|
||||
A robust infrastructure layer for autonomous AI agents, our technology stack
|
||||
delivers a secure, efficient, and intuitive platform for deploying and managing AI agents at scale.
|
||||
</P>
|
||||
</div>
|
||||
</FadeIn>
|
||||
</div>
|
||||
|
||||
{/* Desktop Grid */}
|
||||
<div className="hidden lg:block">
|
||||
<BentoGrid className="max-w-8xl lg:px-12 px-4 pb-12 lg:grid-cols-3">
|
||||
{items.map((item, i) => (
|
||||
<MotionBentoGridItem
|
||||
key={i}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: false, margin: '0px 0px -100px 0px' }}
|
||||
transition={{ duration: 0.8, delay: 0.3 + i * 0.1 }}
|
||||
className={cn(i === 3 || i === 6 ? "md:col-span-2" : "")}
|
||||
rowHeight={i >= 3 ? "h-[22rem]" : ""}
|
||||
title={item.title}
|
||||
subtitle={item.subtitle}
|
||||
description={item.description}
|
||||
video={item.video}
|
||||
/>
|
||||
))}
|
||||
</BentoGrid>
|
||||
</div>
|
||||
|
||||
{/* Mobile Carousel */}
|
||||
<div className="lg:hidden block px-4 pb-12">
|
||||
<div className="relative h-[24rem] w-full overflow-hidden">
|
||||
<div className="absolute inset-0" onTouchStart={handleCardTap} />
|
||||
<AnimatePresence initial={false}>
|
||||
<motion.div
|
||||
key={activeIndex}
|
||||
className="absolute h-full w-full"
|
||||
initial={{ x: "100%", opacity: 0 }}
|
||||
animate={{ x: 0, opacity: 1 }}
|
||||
exit={{ x: "-100%", opacity: 0 }}
|
||||
transition={{ type: "spring", stiffness: 300, damping: 30 }}
|
||||
>
|
||||
<MotionBentoGridItem
|
||||
className="h-full"
|
||||
title={items[activeIndex].title}
|
||||
subtitle={items[activeIndex].subtitle}
|
||||
description={items[activeIndex].description}
|
||||
video={items[activeIndex].video}
|
||||
/>
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
<button
|
||||
onClick={handlePrev}
|
||||
className="absolute left-2 top-[58%] -translate-y-1/2 rounded-full bg-black/50 p-2 text-white z-10"
|
||||
>
|
||||
<ChevronLeft className="h-6 w-6" />
|
||||
</button>
|
||||
<button
|
||||
onClick={handleNext}
|
||||
className="absolute right-2 top-[58%] -translate-y-1/2 rounded-full bg-black/50 p-2 text-white z-10"
|
||||
>
|
||||
<ChevronRight className="h-6 w-6" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@@ -17,7 +17,7 @@ const variantStyles = {
|
||||
},
|
||||
outline: {
|
||||
gray: 'border-gray-300 text-gray-700 hover:border-gray-400 active:bg-gray-100 active:text-gray-700/80',
|
||||
white: 'border-gray-300 text-white hover:border-gray-400 active:bg-gray-100 active:text-white',
|
||||
white: 'border-gray-300 text-white hover:border-gray-400 hover:text-gray-300 active:bg-gray-100 active:text-gray-800',
|
||||
},
|
||||
}
|
||||
|
||||
|
@@ -13,12 +13,14 @@ export function CallTo() {
|
||||
</P>
|
||||
<div className="mt-10 flex items-center justify-center gap-x-6">
|
||||
<a
|
||||
href="#"
|
||||
href="mailto:info@ourworld.tf"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="rounded-md bg-[#2F3178] px-3.5 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-[#2F3178]/80 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#2F3178]"
|
||||
>
|
||||
Book a Meeting
|
||||
</a>
|
||||
<a href="#" className="text-sm/6 font-semibold text-[#2F3178] hover:text-[#2F3178]/80">
|
||||
<a href="mailto:info@ourworld.tf" target="_blank" rel="noopener noreferrer" className="text-sm/6 font-semibold text-[#2F3178] hover:text-[#2F3178]/80">
|
||||
Join the Waitlist <span aria-hidden="true">→</span>
|
||||
</a>
|
||||
</div>
|
||||
|
@@ -1,12 +1,13 @@
|
||||
import { CircleBackground } from '@/components/CircleBackground'
|
||||
import { Container } from '@/components/Container'
|
||||
import { Button } from '@/components/Button'
|
||||
import { FadeIn } from '@/components/FadeIn'
|
||||
|
||||
export function CallToAction() {
|
||||
return (
|
||||
<section
|
||||
id="get-free-shares-today"
|
||||
className="relative overflow-hidden py-20 sm:py-28"
|
||||
className="relative overflow-hidden py-20 sm:py-28 border-t border-gray-800"
|
||||
>
|
||||
<video
|
||||
autoPlay
|
||||
@@ -18,11 +19,12 @@ export function CallToAction() {
|
||||
<source src="/videos/cta.mp4" type="video/mp4" />
|
||||
</video>
|
||||
<div className="absolute top-0 left-0 w-full h-full bg-black opacity-40 z-10"></div>
|
||||
<div className="absolute left-20 top-1/2 -translate-y-1/2 sm:left-1/2 sm:-translate-x-1/2 z-20">
|
||||
<div className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 sm:left-1/2 sm:-translate-x-1/2 z-20">
|
||||
<CircleBackground color="#fff" className="animate-spin-slower" />
|
||||
</div>
|
||||
<Container className="relative z-20">
|
||||
<div className="mx-auto max-w-md sm:text-center">
|
||||
<FadeIn>
|
||||
<div className="mx-auto max-w-md text-center">
|
||||
<h2 className="text-3xl font-medium tracking-tight text-white sm:text-4xl">
|
||||
Decentralized AI Agents that are Truly Yours
|
||||
</h2>
|
||||
@@ -31,14 +33,21 @@ export function CallToAction() {
|
||||
build your own. Ready to own your intelligence?
|
||||
</p>
|
||||
<div className="mt-8 flex justify-center gap-x-6">
|
||||
<Button variant="solid" color="cyan" href="#">
|
||||
<Button
|
||||
variant="solid"
|
||||
color="cyan"
|
||||
href="mailto:info@ourworld.tf"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Book a Meeting
|
||||
</Button>
|
||||
<Button href="#" variant="outline" color="white">
|
||||
<Button href="mailto:info@ourworld.tf" target="_blank" rel="noopener noreferrer" variant="outline" color="white">
|
||||
Join the Waitlist
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</FadeIn>
|
||||
</Container>
|
||||
</section>
|
||||
)
|
||||
|
@@ -1,41 +1,37 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useMemo, useState, useRef } from 'react'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { useResponsiveCarousel } from '@/hooks/useResponsiveCarousel';
|
||||
import Image from 'next/image'
|
||||
import { motion, AnimatePresence, useInView } from 'framer-motion'
|
||||
import { motion, AnimatePresence } from 'framer-motion'
|
||||
import { wrap } from 'popmotion'
|
||||
import { Button } from '@/components/Button';
|
||||
import { H2, P, H4 } from '@/components/Texts';
|
||||
import { H2, P, CT } 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/interface.png' },
|
||||
{ text: 'Process documents across all formats', image: '/images/docs.png' },
|
||||
{ text: 'Execute multi-step workflows autonomously', image: '/images/flow.png' },
|
||||
{ text: 'Manage calendars, emails, and tasks', image: '/images/calendar.png' },
|
||||
{ text: 'Perform deep semantic search across all data sources', image: '/images/data.png' },
|
||||
{ text: 'Identify patterns in complex datasets', image: '/images/datasets.png' },
|
||||
{ text: 'Provide real-time market intelligence', image: '/images/market.png' },
|
||||
{ text: 'Generate and debug code in multiple languages', image: '/images/code.png' },
|
||||
{ text: 'Create consistent branded content', image: '/images/branding.png' },
|
||||
{ text: 'Translate and localize materials', image: '/images/translate.png' },
|
||||
{ text: 'Transform and migrate data structures', image: '/images/structure.png' },
|
||||
{ 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 CARD_SIZE = 360 // square size on desktop
|
||||
const GAP = 300 // spacing for larger cards
|
||||
const ROT_Y = 18
|
||||
const DEPTH = 210
|
||||
const SCALE_DROP = 0.12
|
||||
const AUTOPLAY_MS = 3200
|
||||
const VISIBLE = 4;
|
||||
const AUTOPLAY_MS = 3200;
|
||||
|
||||
export function ClickableGallery() {
|
||||
const [active, setActive] = useState(0)
|
||||
const [hovering, setHovering] = useState(false)
|
||||
const ref = useRef(null);
|
||||
const isInView = useInView(ref, { once: true });
|
||||
const [active, setActive] = useState(0);
|
||||
const [hovering, setHovering] = useState(false);
|
||||
const { GAP, ROT_Y, DEPTH, SCALE_DROP } = useResponsiveCarousel();
|
||||
|
||||
// autoplay
|
||||
useEffect(() => {
|
||||
@@ -53,113 +49,131 @@ export function ClickableGallery() {
|
||||
const prev = () => setActive((i) => wrap(0, galleryItems.length, i - 1))
|
||||
|
||||
return (
|
||||
<div ref={ref}>
|
||||
<div className="py-24 relative isolate pt-24 pb-0 bg-black text-center w-full">
|
||||
<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">
|
||||
<H2 className="text-center mt-12">One Agent, Endless Possibilities.</H2>
|
||||
</motion.div>
|
||||
<motion.div initial={{ opacity: 0, y: 20 }} animate={isInView ? { opacity: 1, y: 0 } : { opacity: 0, y: 20 }} transition={{ duration: 0.8, delay: 0.2 }} className="mx-auto max-w-4xl mt-6">
|
||||
<P className="text-center" color="primary">
|
||||
The future isn’t about more tools. It’s about one intelligent partner that can do it all. This is your gateway to creativity, automation, and discovery.
|
||||
</P>
|
||||
</motion.div>
|
||||
</div>
|
||||
<motion.section
|
||||
initial={{ opacity: 0 }}
|
||||
animate={isInView ? { opacity: 1 } : { opacity: 0 }}
|
||||
transition={{ duration: 1, delay: 0.4 }}
|
||||
className="relative w-full flex items-center justify-center overflow-hidden bg-black pt-0 pb-32"
|
||||
onMouseEnter={() => setHovering(true)}
|
||||
onMouseLeave={() => setHovering(false)}
|
||||
>
|
||||
|
||||
|
||||
<div className="relative w-full max-w-[1800px] 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.90
|
||||
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"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{
|
||||
transform: `translateX(${x}px) translateZ(${z}px) rotateY(${r}deg) scale(${s})`,
|
||||
zIndex,
|
||||
opacity: o,
|
||||
}}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ type: 'spring', stiffness: 220, damping: 26 }}
|
||||
onClick={() => setActive(idx)}
|
||||
>
|
||||
{/* Square container, keeps image ratio inside */}
|
||||
<div
|
||||
className="relative rounded-2xl overflow-hidden bg-white flex items-center justify-center"
|
||||
style={{ width: CARD_SIZE, height: CARD_SIZE }}
|
||||
>
|
||||
<Image
|
||||
src={item.image}
|
||||
alt={item.text}
|
||||
fill
|
||||
className="object-contain rounded-2xl text-black"
|
||||
priority={i === VISIBLE}
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
)
|
||||
})}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
|
||||
{/* Arrows */}
|
||||
<div className="absolute inset-y-0 left-8 flex items-center z-50">
|
||||
<button
|
||||
onClick={prev}
|
||||
className="bg-white/70 hover:bg-white rounded-full p-3 shadow-lg backdrop-blur-md"
|
||||
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 flex items-center z-50">
|
||||
<button
|
||||
onClick={next}
|
||||
className="bg-white/70 hover:bg-white rounded-full p-3 shadow-lg backdrop-blur-md"
|
||||
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 */}
|
||||
<div className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 z-[60]">
|
||||
<div className="flex items-center justify-between w-[1040px] gap-6 rounded-3xl bg-white/95 shadow-[0_8px_40px_rgba(0,0,0,0.15)] px-12 py-6 backdrop-blur">
|
||||
<H4 as="h4" className="max-w-[820px] h-[72px] text-black">
|
||||
<TypeAnimation
|
||||
key={active}
|
||||
sequence={[galleryItems[active].text]}
|
||||
wrapper="span"
|
||||
speed={50}
|
||||
repeat={0}
|
||||
/>
|
||||
</H4>
|
||||
<Button href="#" color="cyan" className="text-sm px-4 py-2 lg:text-base">
|
||||
Start
|
||||
</Button>
|
||||
<div>
|
||||
<div className="relative isolate pt-8 pb-0 bg-transparent text-center w-full">
|
||||
<FadeIn transition={{ duration: 0.8, delay: 0.1 }}>
|
||||
<div className="mx-auto max-w-5xl">
|
||||
<H2 className="text-center">One Agent, Endless Possibilities.</H2>
|
||||
</div>
|
||||
</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="primary">
|
||||
The future isn’t about more tools. It’s about one intelligent partner that can do it all. This is your gateway to creativity, automation, and discovery.
|
||||
</P>
|
||||
</div>
|
||||
</FadeIn>
|
||||
</div>
|
||||
</motion.section>
|
||||
<FadeIn transition={{ duration: 1, delay: 0.4 }}>
|
||||
<section
|
||||
className="relative w-full flex items-center justify-center overflow-hidden bg-transparent -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(255, 255, 255, 0.2)' : 'none',
|
||||
}}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ type: 'spring', stiffness: 220, damping: 26 }}
|
||||
onClick={() => setActive(idx)}
|
||||
>
|
||||
<div className="relative bg-black flex items-center justify-center">
|
||||
<Image
|
||||
src={item.image}
|
||||
alt={item.text}
|
||||
width={item.width}
|
||||
height={item.height}
|
||||
className="object-contain text-white"
|
||||
priority={i === VISIBLE}
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
})}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Arrows */}
|
||||
{/* Arrows */}
|
||||
<div className="absolute inset-y-0 left-8 hidden md:flex items-center z-50">
|
||||
<button
|
||||
onClick={prev}
|
||||
className="bg-transparent rounded-full p-2 shadow-lg backdrop-blur-md"
|
||||
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-transparent rounded-full p-2 shadow-lg backdrop-blur-md"
|
||||
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-black/30 shadow-[0_8px_40px_rgba(0,0,0,0.15)] px-12 backdrop-blur">
|
||||
<CT as="h4" className="max-w-[820px] h-[72px] text-white flex items-center">
|
||||
<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-white/10 bg-opacity-80 p-4 backdrop-blur-md">
|
||||
<CT as="h4" className="w-full text-left h-[72px] text-white leading-tight flex items-center">
|
||||
<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>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@@ -40,7 +40,7 @@ const row2 = logos.slice(6);
|
||||
|
||||
export function Companies() {
|
||||
return (
|
||||
<div id="companies" className="relative bg-black flex flex-col items-center justify-center w-full overflow-hidden antialiased py-12 -top-20">
|
||||
<div id="companies" className="relative bg-black flex flex-col items-center justify-center w-full overflow-hidden antialiased lg:py-12 py-4 -top-20">
|
||||
<div className="relative z-10 mx-auto w-full max-w-6xl p-4">
|
||||
|
||||
{/* Heading */}
|
||||
|
28
src/components/FadeIn.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
'use client'
|
||||
|
||||
import { motion, Transition } from 'framer-motion'
|
||||
import React from 'react'
|
||||
import { useMediaQuery } from '@/hooks/useMediaQuery'
|
||||
|
||||
type FadeInProps = {
|
||||
children: React.ReactNode
|
||||
transition?: Transition
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function FadeIn({ children, transition, className }: FadeInProps) {
|
||||
const isMobile = useMediaQuery('(max-width: 768px)')
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
className={className}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: false, margin: isMobile ? '0px 0px -50px 0px' : '0px 0px -100px 0px' }}
|
||||
transition={transition || { duration: 0.5 }}
|
||||
>
|
||||
{children}
|
||||
</motion.div>
|
||||
)
|
||||
}
|
||||
|
@@ -6,6 +6,7 @@ import { Container } from '@/components/Container'
|
||||
import { TextField } from '@/components/Fields'
|
||||
import { Logomark } from '@/components/Logo'
|
||||
import { NavLinks } from '@/components/NavLinks'
|
||||
import { FadeIn } from '@/components/FadeIn'
|
||||
import qrCode from '@/images/qr-code.svg'
|
||||
|
||||
function QrCodeBorder(props: React.ComponentPropsWithoutRef<'svg'>) {
|
||||
@@ -23,8 +24,9 @@ function QrCodeBorder(props: React.ComponentPropsWithoutRef<'svg'>) {
|
||||
export function Footer() {
|
||||
return (
|
||||
<footer id="footer" className="border-t border-gray-800">
|
||||
<FadeIn>
|
||||
<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-12 pb-6 lg:flex-row lg:items-center lg:py-12">
|
||||
<div>
|
||||
<div className="flex items-center text-white">
|
||||
<Logomark className="h-10 w-10 flex-none fill-cyan-500" />
|
||||
@@ -56,7 +58,7 @@ export function Footer() {
|
||||
</div>
|
||||
</div>
|
||||
<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
|
||||
type="email"
|
||||
aria-label="Email address"
|
||||
@@ -70,12 +72,13 @@ export function Footer() {
|
||||
<span className="hidden lg:inline">Join our newsletter</span>
|
||||
<span className="lg:hidden">Join newsletter</span>
|
||||
</Button>
|
||||
</form>
|
||||
<p className="mt-6 text-sm text-gray-400 md:mt-0">
|
||||
© Copyright ThreeFold {new Date().getFullYear()}. All rights reserved.
|
||||
</form> */}
|
||||
<p className="mt-6 text-sm text-gray-400 md:mt-0 ">
|
||||
© Copyright OurWorld Holdings, {new Date().getFullYear()}. All rights reserved.
|
||||
</p>
|
||||
</div>
|
||||
</Container>
|
||||
</FadeIn>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
|
@@ -48,7 +48,7 @@ function MobileNavLink(
|
||||
return (
|
||||
<PopoverButton
|
||||
as={Link}
|
||||
className="block text-base/7 tracking-tight text-[#2F3178]"
|
||||
className="block text-base/7 tracking-tight text-white"
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
@@ -72,7 +72,7 @@ export function Header() {
|
||||
{({ open }) => (
|
||||
<>
|
||||
<PopoverButton
|
||||
className="relative z-10 -m-2 inline-flex items-center rounded-lg stroke-gray-900 p-2 hover:bg-gray-200/50 hover:stroke-gray-600 focus:not-data-focus:outline-hidden active:stroke-gray-900"
|
||||
className="relative z-10 -m-2 inline-flex items-center rounded-lg stroke-white p-2 hover:bg-gray-200/50 hover:stroke-gray-400 focus:not-data-focus:outline-hidden active:stroke-white"
|
||||
aria-label="Toggle site navigation"
|
||||
>
|
||||
{({ open }) =>
|
||||
@@ -104,25 +104,13 @@ export function Header() {
|
||||
y: -32,
|
||||
transition: { duration: 0.2 },
|
||||
}}
|
||||
className="absolute inset-x-0 top-0 z-0 origin-top rounded-b-2xl bg-white px-6 pt-32 pb-6 shadow-2xl shadow-gray-900/20"
|
||||
className="absolute inset-x-0 top-0 z-0 origin-top rounded-b-2xl bg-gray-900 px-6 pt-32 pb-6 shadow-2xl shadow-gray-900/20"
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<MobileNavLink href="/#about">
|
||||
About
|
||||
</MobileNavLink>
|
||||
<MobileNavLink href="/#benefits">
|
||||
Benefits
|
||||
</MobileNavLink>
|
||||
<MobileNavLink href="/#features">
|
||||
Features
|
||||
</MobileNavLink>
|
||||
<MobileNavLink href="/#usecases">
|
||||
Use Cases
|
||||
</MobileNavLink>
|
||||
<MobileNavLink href="/#faqs">FAQs</MobileNavLink>
|
||||
<NavLinks className="block text-base/7 tracking-tight" />
|
||||
</div>
|
||||
<div className="mt-8 flex flex-col gap-4">
|
||||
<Button href="https://docs.ourworld.tf/mycelium_cloud/docs/" variant="outline" color="white">
|
||||
<Button href="https://docs.ourworld.tf/mycelium_cloud/docs/" variant="outline" color="white" target="_blank" rel="noopener noreferrer">
|
||||
Docs
|
||||
</Button>
|
||||
<Button href="https://www.mycelium.threefold.io/download/" color="cyan">Get Started</Button>
|
||||
@@ -135,7 +123,7 @@ export function Header() {
|
||||
)}
|
||||
</Popover>
|
||||
<div className="flex items-center gap-6 max-lg:hidden">
|
||||
<Button href="https://docs.ourworld.tf/mycelium_cloud/docs/" variant="outline">
|
||||
<Button href="https://docs.ourworld.tf/mycelium_cloud/docs/" variant="outline" color="white" target="_blank" rel="noopener noreferrer">
|
||||
Docs
|
||||
</Button>
|
||||
<Button href="https://www.mycelium.threefold.io/download/" color="cyan">Get Started</Button>
|
||||
|
@@ -32,26 +32,43 @@ export function HomeHero() {
|
||||
<div className="absolute inset-0 bg-black/60" />
|
||||
</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="text-center max-w-5xl">
|
||||
<H1 color="light">
|
||||
<TypeAnimation
|
||||
sequence={[
|
||||
'Decentralized Autonomous Agentic Cloud.',
|
||||
1000,
|
||||
]}
|
||||
wrapper="span"
|
||||
speed={50}
|
||||
repeat={0}
|
||||
/>
|
||||
</H1>
|
||||
<div className="relative -top-15 mx-auto flex h-screen max-w-8xl items-center justify-center">
|
||||
<div className="text-center">
|
||||
<div className="max-w-5xl">
|
||||
<H1 color="light">
|
||||
<TypeAnimation
|
||||
sequence={[
|
||||
'Decentralized Autonomous Agentic Cloud.',
|
||||
1000,
|
||||
]}
|
||||
wrapper="span"
|
||||
speed={50}
|
||||
repeat={0}
|
||||
/>
|
||||
</H1>
|
||||
</div>
|
||||
|
||||
{/* Mobile-only PL */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 1, delay: 1 }}
|
||||
className="lg:hidden"
|
||||
>
|
||||
<PL className="mt-8 max-w-xl text-center text-gray-100" color="light">
|
||||
Mycelium's advancements in Agentic infrastructure supports private, secure and autonomous Agents that connect, learn and grow with you.
|
||||
</PL>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* Desktop-only PL */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 1, delay: 1 }}
|
||||
className="hidden lg:block"
|
||||
>
|
||||
<PL className="absolute bottom-0 left-0 max-w-xl text-left text-gray-100" color="light">
|
||||
<PL className="absolute -bottom-8 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.
|
||||
</PL>
|
||||
</motion.div>
|
||||
|
@@ -3,8 +3,9 @@
|
||||
import { useRef, useState } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { AnimatePresence, motion } from 'framer-motion'
|
||||
import clsx from 'clsx'
|
||||
|
||||
export function NavLinks() {
|
||||
export function NavLinks({ className }: { className?: string }) {
|
||||
let [hoveredIndex, setHoveredIndex] = useState<number | null>(null)
|
||||
let timeoutRef = useRef<number | null>(null)
|
||||
|
||||
@@ -23,17 +24,18 @@ export function NavLinks() {
|
||||
};
|
||||
|
||||
return [
|
||||
['About', '/#about'],
|
||||
['Technologies', '/#technologies'],
|
||||
['Network', '/#network'],
|
||||
['Deploy', '/#deploy'],
|
||||
['LLMs', '/#llms'],
|
||||
['Features', '/#features'],
|
||||
['How it Works', '/#how-it-works'],
|
||||
['Get Started', '/#get-started'],
|
||||
].map(([label, href], index) => (
|
||||
<Link
|
||||
key={label}
|
||||
href={href}
|
||||
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"
|
||||
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',
|
||||
className,
|
||||
)}
|
||||
onClick={(e) => handleClick(e, href)}
|
||||
onMouseEnter={() => {
|
||||
if (timeoutRef.current) {
|
||||
|
@@ -1,63 +1,34 @@
|
||||
"use client";
|
||||
|
||||
import { StackedCubes } from "@/components/ui/StackedCubes";
|
||||
import { Button } from "@/components/Button";
|
||||
import { motion, useInView } from 'framer-motion';
|
||||
import { H2, P } from '@/components/Texts';
|
||||
import { useRef } from "react";
|
||||
import { FadeIn } from "./FadeIn";
|
||||
|
||||
export function StackSectionPreview() {
|
||||
const ref = useRef(null);
|
||||
const isInView = useInView(ref);
|
||||
|
||||
return (
|
||||
<section ref={ref} className="w-full bg-transparent lg:px-0 py-24 px-6 relative">
|
||||
{/* Gradient Blob Component */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={isInView ? { opacity: 0.4 } : { opacity: 0 }}
|
||||
transition={{ duration: 1, delay: 0.1 }}
|
||||
className="absolute w-[400px] h-[200px] bg-gradient-to-br from-[#505050] to-[#7e7e7e] rounded-full blur-[150px] bottom-[200px] left-[-150px] z-0"
|
||||
/>
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={isInView ? { opacity: 0.5 } : { opacity: 0 }}
|
||||
transition={{ duration: 1, delay: 0.15 }}
|
||||
className="absolute w-[200px] h-[100px] bg-gradient-to-br from-[#505050] to-[#7e7e7e] rounded-full blur-[150px] top-[200px] right-[-150px] z-0"
|
||||
/>
|
||||
<section className="w-full bg-transparent lg:px-0 py-12 lg:py-24 px-6 relative">
|
||||
<div className="mx-auto max-w-7xl">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-4 lg:gap-16 items-center lg:items-start">
|
||||
{/* Left Column - Text (1/3 width) */}
|
||||
<div className="text-left lg:text-left lg:col-span-1 order-1 lg:order-1">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={isInView ? { opacity: 1, y: 0 } : { opacity: 0, y: 30 }}
|
||||
transition={{ duration: 0.8, delay: 0.2 }}
|
||||
>
|
||||
<div className="text-center lg:text-left lg:col-span-1 order-1 lg:order-1">
|
||||
<FadeIn>
|
||||
<H2 className="">
|
||||
The Mycelium Stack
|
||||
</H2>
|
||||
</motion.div>
|
||||
</FadeIn>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={isInView ? { opacity: 1, y: 0 } : { opacity: 0, y: 30 }}
|
||||
transition={{ duration: 0.8, delay: 0.6 }}
|
||||
>
|
||||
<FadeIn>
|
||||
<P className="mx-auto mt-8 max-w-3xl" color="light">
|
||||
Built with Mycelium technology, our AI infrastructure ensures unbreakable networks, complete data sovereignty, ultra-secure agent-human communication, and unhackable data storage systems.
|
||||
</P>
|
||||
</motion.div>
|
||||
</FadeIn>
|
||||
</div>
|
||||
{/* Right Column - Stacked Cubes (2/3 width) */}
|
||||
<div className="lg:col-span-2 flex items-center justify-center lg:justify-start order-2 lg:order-2">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={isInView ? { opacity: 1, y: 0 } : { opacity: 0, y: 30 }}
|
||||
transition={{ duration: 0.8, delay: 0.3 }}
|
||||
>
|
||||
<div className="lg:col-span-2 flex items-center justify-center lg:justify-start order-2 lg:order-2 mt-8 lg:mt-0">
|
||||
<FadeIn>
|
||||
<StackedCubes />
|
||||
</motion.div>
|
||||
</FadeIn>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -42,7 +42,7 @@ export function Steps() {
|
||||
<H2 className="text-3xl font-medium tracking-tight" color="light">
|
||||
Deploy Scalable LLMs and AI Agents in Seconds
|
||||
</H2>
|
||||
<P className="mt-6 text-lg" 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.
|
||||
</P>
|
||||
</motion.div>
|
||||
@@ -50,7 +50,7 @@ export function Steps() {
|
||||
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"
|
||||
className="mx-auto lg:mt-12 mt-8 grid max-w-2xl grid-cols-1 gap-x-8 gap-y-8 text-base/7 sm:grid-cols-2 sm:gap-y-16 lg:mx-0 lg:max-w-none lg:grid-cols-3"
|
||||
>
|
||||
{features.map((feature, index) => (
|
||||
<motion.li
|
||||
@@ -58,7 +58,7 @@ export function Steps() {
|
||||
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-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"
|
||||
className="rounded-2xl border border-white/20 bg-black/20 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"
|
||||
>
|
||||
<feature.icon className="h-8 w-8 mb-4 text-white" />
|
||||
<CT as="span" className="font-semibold" color="light">{feature.name}</CT>
|
||||
|
@@ -7,7 +7,7 @@ const colorVariants = {
|
||||
primary: 'text-[#fffff]',
|
||||
secondary: 'text-gray-200',
|
||||
custom: 'text-[#015eff]',
|
||||
light: 'text-white',
|
||||
light: '[#fcfcfc]',
|
||||
} as const
|
||||
|
||||
type TextOwnProps = {
|
||||
@@ -53,9 +53,9 @@ const createTextComponent = <DefaultElement extends React.ElementType>(
|
||||
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 H2 = createTextComponent('h2', 'text-3xl font-medium text-pretty lg:text-4xl')
|
||||
export const P = createTextComponent('p', 'text-lg font-normal text-pretty lg:text-xl')
|
||||
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 H4 = createTextComponent('h4', 'text-xl lg:text-2xl font-semibold leading-tight')
|
||||
export const H4 = createTextComponent('h4', 'text-xl lg:text-2xl font-semibold leading-[1.15]')
|
||||
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 NL = createTextComponent('span', 'text-lg font-semibold leading-6')
|
||||
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]')
|
||||
|
@@ -7,7 +7,7 @@ import { CountUpNumber } from './CountUpNumber'
|
||||
|
||||
export function WorldMap() {
|
||||
return (
|
||||
<div className="relative min-h-screen w-full overflow-hidden -top-20 flex flex-col">
|
||||
<div className="relative min-h-screen w-full overflow-hidden top-0 flex py-12 flex-col">
|
||||
{/* Background video */}
|
||||
<video
|
||||
autoPlay
|
||||
@@ -48,18 +48,18 @@ export function WorldMap() {
|
||||
className="flex-1 flex items-center justify-center"
|
||||
>
|
||||
<div className="relative w-[450px] h-[450px] md:w-[600px] md:h-[600px]">
|
||||
<Globe className="absolute inset-0 w-full h-full -left-24" />
|
||||
<Globe className="absolute inset-0 w-full h-full left-0 lg:-left-24" />
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Cards Right Column */}
|
||||
<div className="relative flex-1">
|
||||
<div className="relative flex-1 lg:h-auto h-[700px] flex flex-col lg:block items-center gap-y-4 mt-8 lg:mt-0">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.5, delay: 0.4 }}
|
||||
whileHover={{ scale: 1.05 }}
|
||||
className="absolute top-12 -left-12 rounded-2xl bg-white/5 backdrop-blur-md border border-white/10 px-4 py-8 shadow-md w-80"
|
||||
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>
|
||||
<CountUpNumber end={54958} color="light" className="mt-2 text-3xl font-bold" />
|
||||
@@ -73,7 +73,7 @@ export function WorldMap() {
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.5, delay: 0.5 }}
|
||||
whileHover={{ scale: 1.05 }}
|
||||
className="absolute -top-10 right-0 rounded-2xl bg-white/5 backdrop-blur-md border border-white/10 px-4 py-8 shadow-md w-80"
|
||||
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>
|
||||
<CountUpNumber end={1493} color="light" className="mt-2 text-3xl font-bold" />
|
||||
@@ -87,12 +87,12 @@ export function WorldMap() {
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.5, delay: 0.6 }}
|
||||
whileHover={{ scale: 1.05 }}
|
||||
className="absolute bottom-28 -left-12 rounded-2xl bg-white/5 backdrop-blur-md border border-white/10 px-4 py-8 shadow-md w-80"
|
||||
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>
|
||||
<CountUpNumber end={5388956} color="light" className="mt-2 text-3xl font-bold" />
|
||||
<CP color="light" className="mt-2 text-sm">
|
||||
Total amount of storage (SSD, HDD, & RAM) on the grid.
|
||||
Total GB amount of storage (SSD, HDD, & RAM) on the grid.
|
||||
</CP>
|
||||
</motion.div>
|
||||
|
||||
@@ -101,7 +101,7 @@ export function WorldMap() {
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.5, delay: 0.7 }}
|
||||
whileHover={{ scale: 1.05 }}
|
||||
className="absolute top-44 right-0 rounded-2xl bg-white/5 backdrop-blur-md border border-white/10 px-4 py-8 shadow-md w-80"
|
||||
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>
|
||||
<CountUpNumber end={44} color="light" className="mt-2 text-3xl font-bold" />
|
||||
|
@@ -11,6 +11,7 @@ interface CubeProps {
|
||||
index: number;
|
||||
onHover: () => void;
|
||||
onLeave: () => void;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
const CubeSvg: React.FC<React.SVGProps<SVGSVGElement> & { index: number }> = ({ index, ...props }) => (
|
||||
@@ -42,13 +43,14 @@ const CubeSvg: React.FC<React.SVGProps<SVGSVGElement> & { index: number }> = ({
|
||||
</svg>
|
||||
);
|
||||
|
||||
export function Cube({ title, descriptionTitle, description, isActive, index, onHover, onLeave }: CubeProps) {
|
||||
export function Cube({ 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,
|
||||
}}
|
||||
@@ -123,25 +125,7 @@ export function Cube({ title, descriptionTitle, description, isActive, index, on
|
||||
)}
|
||||
|
||||
{/* Description for Mobile - Below cube */}
|
||||
{isActive && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: 10 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
className="lg:hidden absolute top-full left-1/2 -translate-x-1/2 mt-8 z-50"
|
||||
>
|
||||
<div className="w-64 sm:w-80 px-4">
|
||||
<h4 className="text-white text-base font-semibold mb-2 text-center">
|
||||
{descriptionTitle}
|
||||
</h4>
|
||||
<p className="text-white text-sm leading-relaxed font-light text-center">
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@@ -33,14 +33,22 @@ const stackData = [
|
||||
|
||||
export function StackedCubes() {
|
||||
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-start min-h-[600px] sm:min-h-[700px] lg:min-h-[600px]"
|
||||
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 ml-0 sm:ml-4 lg:ml-8 h-[600px] w-96"
|
||||
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,
|
||||
@@ -54,7 +62,7 @@ export function StackedCubes() {
|
||||
key={layer.id}
|
||||
className="absolute"
|
||||
style={{
|
||||
top: `${index * 140}px`,
|
||||
top: `calc(${index * 30}% - ${index * 10}px)`,
|
||||
zIndex: active === layer.id ? 20 : 10 - index,
|
||||
}}
|
||||
>
|
||||
@@ -66,10 +74,22 @@ export function StackedCubes() {
|
||||
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-800/50 rounded-lg">
|
||||
<h4 className="text-white text-lg font-semibold mb-2 text-center">
|
||||
{selectedMobileLayer.descriptionTitle}
|
||||
</h4>
|
||||
<p className="text-gray-300 text-sm leading-relaxed text-center">
|
||||
{selectedMobileLayer.description}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
79
src/components/ui/bento-grid.tsx
Normal file
@@ -0,0 +1,79 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import { CT, CP } from "@/components/Texts";
|
||||
import Image from 'next/image';
|
||||
import React from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
export const BentoGrid = ({
|
||||
className,
|
||||
children,
|
||||
}: {
|
||||
className?: string;
|
||||
children?: React.ReactNode;
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"mx-4 grid max-w-6xl grid-cols-1 gap-4 lg:grid-cols-3",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface BentoGridItemProps {
|
||||
className?: string;
|
||||
title?: string | React.ReactNode;
|
||||
subtitle?: string | React.ReactNode;
|
||||
description?: string | React.ReactNode;
|
||||
img?: string;
|
||||
video?: string;
|
||||
rowHeight?: string;
|
||||
}
|
||||
|
||||
export const BentoGridItem = React.forwardRef<HTMLDivElement, BentoGridItemProps>(
|
||||
({ className, title, subtitle, description, img, video, rowHeight }, ref) => {
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"group/bento shadow-input row-span-1 flex flex-col justify-between rounded-xl border border-black bg-black/10 backdrop-blur-md transition-all duration-300 ease-in-out hover:scale-105 hover:border-black hover:bg-black/40",
|
||||
rowHeight ? rowHeight : "h-full",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div className="relative w-full h-[65%] min-h-[6rem] bg-transparent overflow-hidden">
|
||||
{video ? (
|
||||
<video
|
||||
src={video}
|
||||
autoPlay
|
||||
loop
|
||||
muted
|
||||
playsInline
|
||||
className="w-full h-full object-cover opacity-90 group-hover/bento:opacity-100 transition-opacity duration-300"
|
||||
/>
|
||||
) : img ? (
|
||||
<Image
|
||||
src={img}
|
||||
alt={title as string}
|
||||
width={300}
|
||||
height={300}
|
||||
className="w-full h-full object-cover opacity-90 group-hover/bento:opacity-100 transition-opacity duration-300"
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="p-4 transition bg-white/5 hover:bg-white/7 backdrop-blur-md duration-200 group-hover/bento:translate-x-2 ">
|
||||
<CT>{title}</CT>
|
||||
<CP className="font-medium">{subtitle}</CP>
|
||||
<CP className="mt-2">{description}</CP>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
BentoGridItem.displayName = "BentoGridItem";
|
||||
|
||||
export const MotionBentoGridItem = motion(BentoGridItem);
|
21
src/hooks/useMediaQuery.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
|
||||
export function useMediaQuery(query: string) {
|
||||
const [matches, setMatches] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
const media = window.matchMedia(query)
|
||||
if (media.matches !== matches) {
|
||||
setMatches(media.matches)
|
||||
}
|
||||
const listener = () => {
|
||||
setMatches(media.matches)
|
||||
}
|
||||
media.addEventListener('change', listener)
|
||||
return () => media.removeEventListener('change', listener)
|
||||
}, [matches, query])
|
||||
|
||||
return matches
|
||||
}
|
39
src/hooks/useResponsiveCarousel.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
// 🔧 Carousel Config
|
||||
const desktopConfig = {
|
||||
GAP: 300,
|
||||
ROT_Y: 18,
|
||||
DEPTH: 210,
|
||||
SCALE_DROP: 0.12,
|
||||
};
|
||||
|
||||
const mobileConfig = {
|
||||
GAP: 110, // Smaller gap for mobile
|
||||
ROT_Y: 0, // Flatter view on mobile
|
||||
DEPTH: 150, // Less depth
|
||||
SCALE_DROP: 0.1, // Less aggressive scaling
|
||||
};
|
||||
|
||||
export const useResponsiveCarousel = () => {
|
||||
const [config, setConfig] = useState(desktopConfig);
|
||||
|
||||
useEffect(() => {
|
||||
const checkScreenSize = () => {
|
||||
if (window.innerWidth < 768) {
|
||||
setConfig(mobileConfig);
|
||||
} else {
|
||||
setConfig(desktopConfig);
|
||||
}
|
||||
};
|
||||
|
||||
checkScreenSize();
|
||||
window.addEventListener('resize', checkScreenSize);
|
||||
|
||||
return () => window.removeEventListener('resize', checkScreenSize);
|
||||
}, []);
|
||||
|
||||
return config;
|
||||
};
|
13
src/pages/_document.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Html, Head, Main, NextScript } from 'next/document'
|
||||
|
||||
export default function Document() {
|
||||
return (
|
||||
<Html lang="en">
|
||||
<Head />
|
||||
<body>
|
||||
<Main />
|
||||
<NextScript />
|
||||
</body>
|
||||
</Html>
|
||||
)
|
||||
}
|