Compare commits
95 Commits
main
...
39b748cdac
| Author | SHA1 | Date | |
|---|---|---|---|
| 39b748cdac | |||
| 6d186c2304 | |||
| 681a7606d5 | |||
| 0e8de7e7fe | |||
| 3919b72b0c | |||
| f796ec1218 | |||
| 5af349ad4a | |||
| 6485e1e3ce | |||
| e7b33b75c9 | |||
| 2e22ed9683 | |||
| 15e81cb5cd | |||
| 0e6ecedf85 | |||
| 75dd7dfbc5 | |||
| d8ce04252a | |||
| c14c0e92d4 | |||
| 9730129506 | |||
| 8b4e0defb9 | |||
| 15cc1f70e3 | |||
| ae3e78f75a | |||
| 7ee6da68fe | |||
| 6e2ea7c87c | |||
| 5f0c749f94 | |||
| b3836062a3 | |||
| b1c59a9b5a | |||
| db92f1271e | |||
| 6dc318704a | |||
| 6304f8fc1d | |||
| d7769a5ecd | |||
| 005d8c35d4 | |||
| a73608ce6c | |||
| ef7dc12bc2 | |||
| 2d856a5858 | |||
| 8fac6f8edd | |||
| 560ec7dcd0 | |||
| aaf02d0a7c | |||
| b767bdbcb4 | |||
| c25f6aaba6 | |||
| 2bd3026bac | |||
| 88d6a90f60 | |||
| 8d9f02a846 | |||
| e16456bb94 | |||
| 388654fcbd | |||
| 865252274c | |||
| 8b892c9432 | |||
| 7f9023c631 | |||
| f3456eb470 | |||
| 46272e939d | |||
| 0daabe56f5 | |||
| 85afc05c35 | |||
| 866e1df042 | |||
| 28ea2ab49d | |||
| 1f11ca5319 | |||
| 3e99e85e48 | |||
| b39694ea24 | |||
| 363b822d8b | |||
| 00802c7064 | |||
| e45ca7c86f | |||
| 664897ee19 | |||
| 91d249f097 | |||
| 4ba88257eb | |||
| 4c4a0c5dd1 | |||
| 8817272932 | |||
| 6a882371f0 | |||
| 4e8e714f37 | |||
| 01c3c226a9 | |||
| c7371ec21b | |||
| c15b110afe | |||
| 3564b5cb0f | |||
| 51ef8dffb5 | |||
| 9d8f1a6919 | |||
| 0f93199aa4 | |||
| 57fa97cc70 | |||
| fa7c524b18 | |||
| f1c388cbab | |||
| ea3ee4d455 | |||
| 24f6da37ed | |||
| c861f15492 | |||
| bfe3c1e4bd | |||
| 26ae2f156a | |||
| 33c940c604 | |||
| 0837a8313c | |||
| b311cb22a4 | |||
| b987f1a072 | |||
| dd4eba2215 | |||
| ea1ef853f1 | |||
| 5b05dababb | |||
| ab5ac43793 | |||
| fb9250c365 | |||
| 04d08a4265 | |||
| 07f76639d2 | |||
| a59e09e8ed | |||
| 9f81561a86 | |||
| c0cbe7e6bf | |||
|
|
6d6cbd115a | ||
|
|
3c9823bf80 |
252
README.md
@@ -1,93 +1,195 @@
|
||||
# Project Mycelium Website
|
||||
|
||||
A multi-page website for Project Mycelium built with React, Vite, TypeScript, and Tailwind CSS.
|
||||
All in one project mycelium website
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- **Framework**: React 19
|
||||
- **Build Tool**: Vite 7
|
||||
- **Repository:** [https://git.ourworld.tf/ourworld_web/www_projectmycelium_com/](https://git.ourworld.tf/ourworld_web/www_projectmycelium_com/)
|
||||
|
||||
|
||||
- **Main Branch (Production):** [https://www.projectmycelium.com/](https://www.projectmycelium.com)
|
||||
- **Dev Branch (Staging):** [https://www2.projectmycelium.com/](https://www2.projectmycelium.com/)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
## About
|
||||
|
||||
|
||||
This is the official website for Mycelium , built using Vite.js and Tailwind CSS.
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Technologies
|
||||
|
||||
|
||||
- **Framework**: Vite.js (React)
|
||||
- **Language**: TypeScript
|
||||
- **Styling**: Tailwind CSS 4
|
||||
- **Routing**: React Router DOM 7
|
||||
- **Animations**: Framer Motion 11
|
||||
- **Styling**: Tailwind CSS
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
src/
|
||||
├── components/ # Shared components
|
||||
│ ├── AnimatedSection.tsx
|
||||
│ ├── Button.tsx
|
||||
│ ├── CircleBackground.tsx
|
||||
│ ├── Container.tsx
|
||||
│ ├── Header.tsx
|
||||
│ ├── Layout.tsx
|
||||
│ └── Logo.tsx
|
||||
├── pages/ # Page-specific components
|
||||
│ ├── home/ # Home page blocks
|
||||
│ ├── cloud/ # Cloud page blocks
|
||||
│ ├── network/ # Network page blocks
|
||||
│ │ ├── animations/ # SVG animations
|
||||
│ │ ├── Hero.tsx
|
||||
│ │ ├── About.tsx
|
||||
│ │ ├── Features.tsx
|
||||
│ │ ├── PrimaryFeatures.tsx
|
||||
│ │ ├── SecondaryFeatures.tsx
|
||||
│ │ ├── CallToAction.tsx
|
||||
│ │ └── NetworkPage.tsx
|
||||
│ └── agents/ # Agents page blocks
|
||||
├── styles/
|
||||
│ └── tailwind.css # Tailwind configuration
|
||||
└── App.tsx # Main app with routing
|
||||
```
|
||||
---
|
||||
|
||||
## Pages
|
||||
|
||||
- **Home** (`/`) - Main landing page with placeholder content
|
||||
- **Cloud** (`/cloud`) - Mycelium Cloud information (placeholder)
|
||||
- **Network** (`/network`) - Full Network page with:
|
||||
- Hero section
|
||||
- Our Mission (About)
|
||||
- Core Components (Features with animations)
|
||||
- How It Works
|
||||
- Roadmap
|
||||
- Call to Action
|
||||
- **Agents** (`/agents`) - Mycelium Agents information (placeholder)
|
||||
## Dependencies
|
||||
|
||||
## Development
|
||||
- **@emailjs/browser**: ^4.4.1
|
||||
- **@headlessui/react**: ^2.2.9
|
||||
- **@heroicons/react**: ^2.2.0
|
||||
- **@lobehub/icons**: ^1.97.2
|
||||
- **@radix-ui/react-icons**: ^1.3.2
|
||||
- **@react-three/drei**: ^9.89.2
|
||||
- **@react-three/fiber**: ^8.15.12
|
||||
- **@tabler/icons-react**: ^3.35.0
|
||||
- **@tailwindcss/forms**: ^0.5.10
|
||||
- **@types/node**: ^20.19.23
|
||||
- **@types/react**: ^18.3.26
|
||||
- **@types/react-dom**: ^18.3.7
|
||||
- **@types/react-router-dom**: ^5.3.3
|
||||
- **class-variance-authority**: ^0.7.1
|
||||
- **clsx**: ^2.1.1
|
||||
- **cobe**: ^0.6.5
|
||||
- **dotted-map**: ^2.2.3
|
||||
- **framer-motion**: ^10.18.0
|
||||
- **lucide-react**: ^0.544.0
|
||||
- **motion**: ^12.23.24
|
||||
- **popmotion**: ^11.0.5
|
||||
- **react**: ^18.3.1
|
||||
- **react-countup**: ^6.5.3
|
||||
- **react-dom**: ^18.3.1
|
||||
- **react-icons**: ^5.5.0
|
||||
- **react-router-dom**: ^7.9.4
|
||||
- **react-type-animation**: ^3.2.0
|
||||
- **tailwind-merge**: ^3.3.1
|
||||
- **tailwindcss**: ^4.1.15
|
||||
- **three**: ^0.151.0
|
||||
- **typescript**: ^5.9.3
|
||||
- **use-debounce**: ^10.0.6
|
||||
|
||||
Install dependencies:
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
### Dev Dependencies
|
||||
|
||||
Run development server:
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
- **@tailwindcss/postcss**: ^4.1.15
|
||||
- **@types/three**: ^0.151.0
|
||||
- **@vitejs/plugin-react**: ^5.0.4
|
||||
- **autoprefixer**: ^10.4.20
|
||||
- **eslint**: ^8.57.1
|
||||
- **prettier**: ^3.6.2
|
||||
- **prettier-plugin-tailwindcss**: ^0.6.14
|
||||
- **sharp**: ^0.33.1
|
||||
- **tw-animate-css**: ^1.4.0
|
||||
- **vite**: ^7.1.7
|
||||
|
||||
Build for production:
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
---
|
||||
|
||||
Preview production build:
|
||||
```bash
|
||||
npm run preview
|
||||
```
|
||||
|
||||
## Styling
|
||||
## File Structure
|
||||
|
||||
The project follows the same styling conventions as the source Mycelium website:
|
||||
- **Font**: Inter (Google Fonts)
|
||||
- **Colors**: Custom gray scale and cyan accent
|
||||
- **Animations**: Framer Motion with reduced motion support
|
||||
- **Design**: Tailwind CSS with custom theme extensions
|
||||
|
||||
## Migration Notes
|
||||
- **Pages**: `src/pages`
|
||||
- **Components**: `src/components`
|
||||
- **Images**: `public/images`, `src/images`
|
||||
- **CSS**: `src/index.css`, `src/App.css`
|
||||
|
||||
This website consolidates multiple one-pager sites into a single multi-page application. The Network page contains fully migrated content from the original www_mycelium_net repository, including:
|
||||
- All animated SVG components
|
||||
- Responsive layouts
|
||||
- Hover effects and transitions
|
||||
- Accessibility features
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Branding
|
||||
|
||||
|
||||
- **Font**: 'Mulish', 'Neuton'
|
||||
- **Logos**: `public/images/logo.svg`, `public/images/logomark.svg`
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Get Started
|
||||
|
||||
|
||||
Follow these steps to get the project running locally:
|
||||
|
||||
|
||||
1. **Install Dependencies**:
|
||||
|
||||
`npm install`
|
||||
|
||||
2. **Build the Project**:
|
||||
|
||||
`npm run build`
|
||||
|
||||
3. **Start the Development Server**:
|
||||
|
||||
`npm run dev`
|
||||
|
||||
---
|
||||
|
||||
|
||||
## 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 is composed of components from `src/components` and pages are defined in `src/pages`.
|
||||
|
||||
To edit a specific section of the homepage, navigate to `src/components/` and modify the corresponding component file.
|
||||
|
||||
|
||||
### Base Layout
|
||||
|
||||
The base layout for all pages is defined in `src/components/Layout.tsx`.
|
||||
|
||||
|
||||
### Creating a New Page
|
||||
|
||||
To create a new page, add a new route in `src/App.tsx` and create a corresponding component in `src/pages`.
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Contributing
|
||||
|
||||
|
||||
- **Never update the `main` branch directly.** All changes must be reviewed and merged by the team through a pull request.
|
||||
- **Always work on the `development` branch.** Create a feature branch from `development` and submit your pull request to `development`.
|
||||
- **Request a review.** After submitting your pull request, ask the team to review and accept it into the `main` branch.
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Report an Error
|
||||
|
||||
|
||||
To report an issue, please use the following link and provide the requested information:
|
||||
|
||||
|
||||
- **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:
|
||||
|
||||
|
||||
- **URL**: The page where the error occurred.
|
||||
- **Repo**: The repository you are working with.
|
||||
- **Branch**: The specific branch you are on.
|
||||
- **Problem**: A detailed description of the problem.
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Questions
|
||||
|
||||
|
||||
If you have any questions, you can reach out to [sashaastiadi](https://git.ourworld.tf/sashaastiadi).
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<meta name="description" content="Project Mycelium's technology enables anyone to deploy their own Internet infrastructure, anywhere." />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Mulish:wght@400;500;700&display=swap" rel="stylesheet" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 1.1 MiB |
BIN
public/images/ChatGPT Image Oct 31, 2025, 01_46_54 AM.png
Normal file
|
After Width: | Height: | Size: 497 KiB |
|
Before Width: | Height: | Size: 303 KiB |
|
Before Width: | Height: | Size: 992 KiB |
|
Before Width: | Height: | Size: 802 KiB |
BIN
public/images/agenthero1.webp
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
BIN
public/images/agents.png
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
BIN
public/images/agentshero2.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 5.6 KiB |
BIN
public/images/bento-01-integrations.png
Normal file
|
After Width: | Height: | Size: 431 KiB |
BIN
public/images/bento-agent.jpg
Normal file
|
After Width: | Height: | Size: 95 KiB |
BIN
public/images/bento-cloud.jpg
Normal file
|
After Width: | Height: | Size: 85 KiB |
BIN
public/images/bento-compute.png
Normal file
|
After Width: | Height: | Size: 895 KiB |
BIN
public/images/bento-gpu.jpg
Normal file
|
After Width: | Height: | Size: 179 KiB |
BIN
public/images/bento-network.png
Normal file
|
After Width: | Height: | Size: 151 KiB |
BIN
public/images/bento-network.webp
Normal file
|
After Width: | Height: | Size: 114 KiB |
BIN
public/images/bento-storage.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
public/images/bg.jpg
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
public/images/bg2.jpg
Normal file
|
After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 4.8 MiB |
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 723 KiB |
BIN
public/images/cloud/billing.jpg
Normal file
|
After Width: | Height: | Size: 878 KiB |
BIN
public/images/cloud/billing.png
Normal file
|
After Width: | Height: | Size: 778 KiB |
BIN
public/images/cloud/kubeconfig.jpg
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
public/images/cloud/kubeconfig.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
public/images/cloud/reserve.jpg
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
BIN
public/images/cloud/reserve.png
Normal file
|
After Width: | Height: | Size: 2.0 MiB |
|
Before Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 757 KiB |
BIN
public/images/cloudhero.webp
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
public/images/cloudhero2.webp
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
public/images/cloudhero3.webp
Normal file
|
After Width: | Height: | Size: 938 KiB |
BIN
public/images/cloudhero4.webp
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
public/images/cloudhosting.png
Normal file
|
After Width: | Height: | Size: 2.0 MiB |
BIN
public/images/cloudhosting.webp
Normal file
|
After Width: | Height: | Size: 1.8 MiB |
BIN
public/images/computehero.webp
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
BIN
public/images/computehero11.webp
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 110 KiB |
|
Before Width: | Height: | Size: 190 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 6.7 KiB |
BIN
public/images/gpuhero2.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
public/images/hero11.webp
Normal file
|
After Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 1.7 MiB |
BIN
public/images/k82.png
Normal file
|
After Width: | Height: | Size: 910 KiB |
BIN
public/images/logo/cryptpad.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
public/images/logo/gitea.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
public/images/logo/lifekit.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
public/images/logo/matrix.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
public/images/logo/nextcloud.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
public/images/logo/stalwart.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 126 KiB |
|
Before Width: | Height: | Size: 835 KiB |
BIN
public/images/pages/agent.png
Normal file
|
After Width: | Height: | Size: 107 KiB |
BIN
public/images/pages/agent.webp
Normal file
|
After Width: | Height: | Size: 239 KiB |
BIN
public/images/pages/cloud.png
Normal file
|
After Width: | Height: | Size: 87 KiB |
BIN
public/images/pages/cloud.webp
Normal file
|
After Width: | Height: | Size: 232 KiB |
BIN
public/images/pages/compute.png
Normal file
|
After Width: | Height: | Size: 114 KiB |
BIN
public/images/pages/compute.webp
Normal file
|
After Width: | Height: | Size: 216 KiB |
BIN
public/images/pages/gpu.png
Normal file
|
After Width: | Height: | Size: 121 KiB |
BIN
public/images/pages/gpu.webp
Normal file
|
After Width: | Height: | Size: 268 KiB |
BIN
public/images/pages/gpuhero2.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
public/images/pages/network.png
Normal file
|
After Width: | Height: | Size: 80 KiB |
BIN
public/images/pages/network.webp
Normal file
|
After Width: | Height: | Size: 178 KiB |
|
After Width: | Height: | Size: 6.1 MiB |
BIN
public/images/pages/storage.png
Normal file
|
After Width: | Height: | Size: 135 KiB |
BIN
public/images/pages/storage.webp
Normal file
|
After Width: | Height: | Size: 271 KiB |
BIN
public/images/pages/storage1.webp
Normal file
|
After Width: | Height: | Size: 230 KiB |
BIN
public/images/storage.png
Normal file
|
After Width: | Height: | Size: 1.5 MiB |
BIN
public/images/storage2.png
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
public/images/testpic.png
Normal file
|
After Width: | Height: | Size: 87 KiB |
BIN
public/videos/mhero.mp4
Normal file
24
src/App.tsx
@@ -1,17 +1,20 @@
|
||||
import { BrowserRouter, Routes, Route } from 'react-router-dom'
|
||||
import { Layout } from './components/Layout'
|
||||
import HomePage from './pages/home/HomePage'
|
||||
import CloudPage from './pages/cloud/CloudPage'
|
||||
import NetworkPage from './pages/network/NetworkPage'
|
||||
import AgentsPage from './pages/agents/AgentsPage'
|
||||
import DownloadPage from './pages/download/DownloadPage'
|
||||
import ComputePage from './pages/compute/ComputePage'
|
||||
import StoragePage from './pages/storage/StoragePage'
|
||||
import GpuPage from './pages/gpu/GpuPage'
|
||||
import { BrowserRouter, Routes, Route } from 'react-router-dom';
|
||||
import { Layout } from './components/Layout';
|
||||
import { lazy, Suspense } from 'react';
|
||||
|
||||
const HomePage = lazy(() => import('./pages/home/HomePage'));
|
||||
const CloudPage = lazy(() => import('./pages/cloud/CloudPage'));
|
||||
const NetworkPage = lazy(() => import('./pages/network/NetworkPage'));
|
||||
const AgentsPage = lazy(() => import('./pages/agents/AgentsPage'));
|
||||
const DownloadPage = lazy(() => import('./pages/download/DownloadPage'));
|
||||
const ComputePage = lazy(() => import('./pages/compute/ComputePage'));
|
||||
const StoragePage = lazy(() => import('./pages/storage/StoragePage'));
|
||||
const GpuPage = lazy(() => import('./pages/gpu/GpuPage'));
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<Routes>
|
||||
<Route path="/" element={<Layout />}>
|
||||
<Route index element={<HomePage />} />
|
||||
@@ -24,6 +27,7 @@ function App() {
|
||||
<Route path="gpu" element={<GpuPage />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
</Suspense>
|
||||
</BrowserRouter>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { motion } from 'framer-motion'
|
||||
import { forwardRef } from 'react'
|
||||
|
||||
type AnimatedSectionProps = {
|
||||
children: React.ReactNode
|
||||
@@ -6,9 +7,11 @@ type AnimatedSectionProps = {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function AnimatedSection({ children, id, className }: AnimatedSectionProps) {
|
||||
export const AnimatedSection = forwardRef<HTMLElement, AnimatedSectionProps>(
|
||||
({ children, id, className }, ref) => {
|
||||
return (
|
||||
<motion.section
|
||||
ref={ref}
|
||||
id={id}
|
||||
className={className}
|
||||
initial={{ opacity: 0, y: 40 }}
|
||||
@@ -19,4 +22,5 @@ export function AnimatedSection({ children, id, className }: AnimatedSectionProp
|
||||
{children}
|
||||
</motion.section>
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@@ -3,9 +3,9 @@ import clsx from 'clsx'
|
||||
|
||||
const baseStyles = {
|
||||
solid:
|
||||
'inline-flex justify-center rounded-full py-2 px-4 text-sm font-semibold transition-colors',
|
||||
'inline-flex justify-center rounded-full py-2 px-5 text-base font-semibold transition-colors',
|
||||
outline:
|
||||
'inline-flex justify-center rounded-full border py-[calc(--spacing(2)-1px)] px-[calc(--spacing(4)-1px)] text-sm transition-colors',
|
||||
'inline-flex justify-center bg-transparent rounded-full border py-[calc(--spacing(2)-1px)] px-[calc(--spacing(5)-1px)] text-base transition-colors',
|
||||
}
|
||||
|
||||
const variantStyles = {
|
||||
@@ -17,7 +17,7 @@ const variantStyles = {
|
||||
green: 'bg-green-500 text-white hover:bg-green-600',
|
||||
},
|
||||
outline: {
|
||||
cyan: 'border-cyan-500 text-cyan-500 hover:bg-cyan-50',
|
||||
cyan: 'border-cyan-500 text-cyan-500',
|
||||
gray: 'border-gray-300 text-gray-700 hover:border-cyan-500 active:border-cyan-500',
|
||||
white: 'border-gray-300 text-white hover:border-cyan-500 active:border-cyan-500',
|
||||
},
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Container } from './Container'
|
||||
|
||||
export function Footer() {
|
||||
return (
|
||||
<footer className="border-t border-gray-200">
|
||||
<footer className="">
|
||||
<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-8">
|
||||
<div>
|
||||
|
||||
@@ -1,15 +1,34 @@
|
||||
import { Link } from 'react-router-dom'
|
||||
import { Link, useLocation } from 'react-router-dom'
|
||||
import { Dropdown } from './ui/Dropdown'
|
||||
import { ChevronDownIcon } from '@heroicons/react/20/solid'
|
||||
import { Container } from './Container'
|
||||
import { Button } from './Button'
|
||||
import pmyceliumLogo from '../images/logos/logo_1.png'
|
||||
|
||||
|
||||
const cloudNavItems = [
|
||||
{ name: 'Cloud', href: '/cloud' },
|
||||
{ name: 'Compute', href: '/compute' },
|
||||
{ name: 'Storage', href: '/storage' },
|
||||
{ name: 'GPU', href: '/gpu' },
|
||||
]
|
||||
|
||||
export function Header() {
|
||||
const location = useLocation()
|
||||
|
||||
const getCurrentPageName = () => {
|
||||
const currentPath = location.pathname;
|
||||
if (currentPath.startsWith('/compute')) return 'Compute';
|
||||
if (currentPath.startsWith('/storage')) return 'Storage';
|
||||
if (currentPath.startsWith('/gpu')) return 'GPU';
|
||||
if (currentPath.startsWith('/cloud')) return 'Cloud';
|
||||
return 'Cloud';
|
||||
};
|
||||
|
||||
return (
|
||||
<header>
|
||||
<nav>
|
||||
<Container className="relative z-50 flex justify-between py-4">
|
||||
<header className="bg-white">
|
||||
<nav className="border-b border-gray-200">
|
||||
<Container className="flex bg-transparent justify-between py-4">
|
||||
<div className="relative z-10 flex items-center gap-16">
|
||||
<Link to="/" aria-label="Home">
|
||||
<img src={pmyceliumLogo} alt="Mycelium" className="h-8 w-auto" />
|
||||
@@ -18,16 +37,11 @@ export function Header() {
|
||||
<Dropdown
|
||||
buttonContent={
|
||||
<>
|
||||
Cloud
|
||||
{getCurrentPageName()}
|
||||
<ChevronDownIcon className="h-5 w-5" aria-hidden="true" />
|
||||
</>
|
||||
}
|
||||
items={[
|
||||
{ name: 'Cloud', href: '/cloud' },
|
||||
{ name: 'Compute', href: '/compute' },
|
||||
{ name: 'Storage', href: '/storage' },
|
||||
{ name: 'GPU', href: '/gpu' },
|
||||
]}
|
||||
items={cloudNavItems}
|
||||
/>
|
||||
<Link
|
||||
to="/network"
|
||||
|
||||
@@ -5,12 +5,14 @@ import { Header } from './Header'
|
||||
export function Layout() {
|
||||
|
||||
return (
|
||||
<div className="bg-white antialiased" style={{ fontFamily: 'var(--font-inter)' }}>
|
||||
<div className="bg-[#fdfdfd] antialiased relative" style={{ fontFamily: 'var(--font-inter)' }}>
|
||||
<div className="relative z-10">
|
||||
<Header />
|
||||
<main className="">
|
||||
<main>
|
||||
<Outlet />
|
||||
</main>
|
||||
<Footer />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ const colorVariants = {
|
||||
secondary: 'text-gray-600',
|
||||
light: 'text-gray-50',
|
||||
accent: 'text-cyan-500',
|
||||
cyan: 'text-cyan-50',
|
||||
white: 'text-white',
|
||||
dark: 'text-gray-950',
|
||||
tertiary: 'text-gray-700',
|
||||
@@ -33,7 +34,8 @@ type PolymorphicProps<E extends React.ElementType, P> = P & {
|
||||
|
||||
const createTextComponent = <DefaultElement extends React.ElementType>(
|
||||
defaultElement: DefaultElement,
|
||||
defaultClassName: string
|
||||
defaultClassName: string,
|
||||
defaultProps: Omit<TextOwnProps, 'className' | 'children'> = {}
|
||||
) => {
|
||||
type Props<E extends React.ElementType = DefaultElement> = PolymorphicProps<
|
||||
E,
|
||||
@@ -41,9 +43,9 @@ const createTextComponent = <DefaultElement extends React.ElementType>(
|
||||
>
|
||||
|
||||
function Text<E extends React.ElementType = DefaultElement>({
|
||||
font = 'sans',
|
||||
font = defaultProps.font || 'sans',
|
||||
as,
|
||||
color = 'primary',
|
||||
color = defaultProps.color || 'primary',
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
@@ -72,7 +74,7 @@ const createTextComponent = <DefaultElement extends React.ElementType>(
|
||||
// Exports based on your tailwind.css and the example
|
||||
export const H1 = createTextComponent(
|
||||
'h1',
|
||||
'text-6xl lg:text-7xl font-medium leading-tight tracking-tight'
|
||||
'text-5xl lg:text-7xl font-medium leading-tight tracking-tight'
|
||||
)
|
||||
export const H2 = createTextComponent(
|
||||
'h2',
|
||||
@@ -88,7 +90,7 @@ export const H4 = createTextComponent(
|
||||
)
|
||||
export const P = createTextComponent(
|
||||
'p',
|
||||
'text-base lg:text-lg leading-relaxed'
|
||||
'text-base lg:text-xl leading-relaxed'
|
||||
)
|
||||
export const Small = createTextComponent(
|
||||
'small',
|
||||
@@ -100,11 +102,12 @@ export const Subtle = createTextComponent(
|
||||
)
|
||||
export const H5 = createTextComponent(
|
||||
'h5',
|
||||
'text-xl lg:text-2xl font-light leading-snug tracking-normal'
|
||||
'text-xl lg:text-2xl font-light lg:leading-snug leading-[0.85] tracking-normal'
|
||||
)
|
||||
export const Eyebrow = createTextComponent(
|
||||
'h2',
|
||||
'text-base/7 font-semibold tracking-wide'
|
||||
'text-base/7 font-semibold tracking-[0.18em] uppercase',
|
||||
{ color: 'accent' }
|
||||
)
|
||||
export const SectionHeader = createTextComponent(
|
||||
'p',
|
||||
@@ -128,7 +131,7 @@ export const FeatureTitle = createTextComponent(
|
||||
)
|
||||
export const FeatureDescription = createTextComponent(
|
||||
'p',
|
||||
'text-sm leading-normal tracking-normal'
|
||||
'lg:text-base text-sm leading-normal tracking-normal'
|
||||
)
|
||||
export const MobileFeatureTitle = createTextComponent(
|
||||
'h3',
|
||||
@@ -160,4 +163,4 @@ export const DownloadCardDescription = createTextComponent(
|
||||
)
|
||||
|
||||
export const CT = createTextComponent('span', 'text-lg lg:text-xl font-semibold')
|
||||
export const CP = createTextComponent('p', 'text-sm lg:text-sm tracking-wide leading-[1.525] font-light')
|
||||
export const CP = createTextComponent('p', 'text-sm lg:text-base tracking-wide leading-tight font-light')
|
||||
|
||||
@@ -59,7 +59,7 @@ export default function FeaturesSectionDemo() {
|
||||
},
|
||||
];
|
||||
return (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 relative z-10 py-10 max-w-7xl mx-auto">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 relative z-10 py-8 max-w-7xl mx-auto">
|
||||
{features.map((feature, index) => (
|
||||
<Feature key={feature.title} {...feature} index={index} />
|
||||
))}
|
||||
@@ -81,7 +81,7 @@ const Feature = ({
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col lg:border-r py-10 relative group/feature dark:border-neutral-800",
|
||||
"flex flex-col lg:border-r py-8 relative group/feature dark:border-neutral-800",
|
||||
(index === 0 || index === 4) && "lg:border-l dark:border-neutral-800",
|
||||
index < 4 && "lg:border-b dark:border-neutral-800"
|
||||
)}
|
||||
|
||||
@@ -40,7 +40,7 @@ export default function FeaturesSectionDemo() {
|
||||
},
|
||||
];
|
||||
return (
|
||||
<div className="relative z-20 py-10 lg:py-40 max-w-7xl mx-auto">
|
||||
<div className="relative z-20 py-8 lg:py-40 max-w-7xl mx-auto">
|
||||
<div className="px-8">
|
||||
<h4 className="text-3xl lg:text-5xl lg:leading-tight max-w-5xl mx-auto text-center tracking-tight font-medium text-black dark:text-white">
|
||||
Packed with thousands of features
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { ReactNode } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ArrowRightIcon } from "@radix-ui/react-icons";
|
||||
import { ArrowRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const BentoGrid = ({
|
||||
@@ -64,12 +63,13 @@ const BentoCard = ({
|
||||
"pointer-events-none absolute bottom-0 flex w-full translate-y-10 transform-gpu flex-row items-center p-4 opacity-0 transition-all duration-300 group-hover:translate-y-0 group-hover:opacity-100",
|
||||
)}
|
||||
>
|
||||
<Button variant="ghost" asChild size="sm" className="pointer-events-auto">
|
||||
<a href={href}>
|
||||
<a
|
||||
href={href}
|
||||
className="pointer-events-auto inline-flex items-center gap-2 text-sm font-medium text-neutral-600 transition-colors hover:text-sky-700"
|
||||
>
|
||||
{cta}
|
||||
<ArrowRightIcon className="ml-2 h-4 w-4" />
|
||||
<ArrowRight className="h-4 w-4" />
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -26,7 +26,7 @@ const CubeSvg: React.FC<React.SVGProps<SVGSVGElement> & { index: number }> = ({
|
||||
<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"
|
||||
stroke="rgba(59, 130, 246, 0.25)"
|
||||
stroke="rgba(107, 114, 128, 0.3)"
|
||||
strokeWidth="0.5"
|
||||
/>
|
||||
<defs>
|
||||
@@ -79,8 +79,8 @@ export function CubeLight({
|
||||
<div
|
||||
className={`absolute inset-0 blur-3xl rounded-2xl transition-all duration-500 ${
|
||||
isActive
|
||||
? "bg-blue-400/40 opacity-70"
|
||||
: "bg-blue-200/20 opacity-40"
|
||||
? "bg-cyan-400/20 opacity-30"
|
||||
: "bg-cyan-200/20 opacity-40"
|
||||
}`}
|
||||
/>
|
||||
|
||||
@@ -90,8 +90,8 @@ export function CubeLight({
|
||||
className="w-48 sm:w-64 lg:w-80 h-auto relative"
|
||||
style={{
|
||||
filter: isActive
|
||||
? "drop-shadow(0 0 25px rgba(59, 130, 246, 0.4)) brightness(1.1)"
|
||||
: "drop-shadow(0 0 10px rgba(59, 130, 246, 0.15)) brightness(1)",
|
||||
? "drop-shadow(0 0 15px rgba(34, 211, 238, 0.25)) brightness(1.05)"
|
||||
: "drop-shadow(0 0 10px rgba(34, 211, 238, 0.15)) brightness(1)",
|
||||
transition: "all 0.4s ease",
|
||||
}}
|
||||
/>
|
||||
@@ -99,10 +99,10 @@ export function CubeLight({
|
||||
{/* Title overlay */}
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<h3
|
||||
className="text-blue-900 text-sm lg:text-base font-medium text-center px-4"
|
||||
className="text-cyan-900 text-sm lg:text-base font-medium text-center px-4"
|
||||
style={{
|
||||
textShadow:
|
||||
"0 0 15px rgba(255,255,255,0.8), 0 0 25px rgba(59, 130, 246, 0.5)",
|
||||
"0 0 15px rgba(255,255,255,0.8), 0 0 25px rgba(34, 211, 238, 0.5)",
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
@@ -131,7 +131,7 @@ export function CubeLight({
|
||||
y1="1"
|
||||
x2="120"
|
||||
y2="1"
|
||||
stroke="rgba(59, 130, 246, 0.6)"
|
||||
stroke="rgba(34, 211, 238, 0.6)"
|
||||
strokeWidth="1"
|
||||
opacity="0.8"
|
||||
/>
|
||||
|
||||
@@ -34,7 +34,7 @@ export function Dropdown({ buttonContent, items }: DropdownProps) {
|
||||
<div className="py-1">
|
||||
{items.map((item) => (
|
||||
<Menu.Item key={item.href}>
|
||||
{({ active }) => (
|
||||
{({ active }: { active: boolean }) => (
|
||||
<Link
|
||||
to={item.href}
|
||||
className={`
|
||||
|
||||
@@ -3,13 +3,10 @@
|
||||
import { Canvas, useFrame } from "@react-three/fiber";
|
||||
import { Line, OrbitControls, useTexture } from "@react-three/drei";
|
||||
import { useMemo, useRef } from "react";
|
||||
|
||||
type RotatableGroup = {
|
||||
rotation: { y: number };
|
||||
};
|
||||
import type { Group } from "three";
|
||||
|
||||
function Globe() {
|
||||
const groupRef = useRef<RotatableGroup | null>(null);
|
||||
const groupRef = useRef<Group>(null);
|
||||
const cloudTexture = useTexture("/images/cloud1.png");
|
||||
|
||||
// Rotate the globe slowly
|
||||
@@ -78,7 +75,7 @@ function Globe() {
|
||||
|
||||
export function HalfGlobe() {
|
||||
return (
|
||||
<div className="w-full h-[500px] bg-white flex items-center justify-center">
|
||||
<div key="half-globe" className="w-full h-[500px] bg-white flex items-center justify-center">
|
||||
<Canvas camera={{ position: [0, 1.5, 4], fov: 45 }}>
|
||||
<ambientLight intensity={0.8} />
|
||||
<Globe />
|
||||
|
||||
@@ -35,8 +35,8 @@ const stackData = [
|
||||
];
|
||||
|
||||
export function StackedCubesLight() {
|
||||
const [active, setActive] = useState<string | null>("agent");
|
||||
const [selectedForMobile, setSelectedForMobile] = useState<string | null>("agent");
|
||||
const [active, setActive] = useState<string | null>("network");
|
||||
const [selectedForMobile, setSelectedForMobile] = useState<string | null>("network");
|
||||
|
||||
const handleCubeClick = (id: string) => {
|
||||
setSelectedForMobile((prev) => (prev === id ? null : id));
|
||||
@@ -49,10 +49,10 @@ export function StackedCubesLight() {
|
||||
return (
|
||||
<div className="flex flex-col items-center relative">
|
||||
{/* ✨ Ambient cyan-white gradient background */}
|
||||
<div className="absolute inset-0 bg-gradient-to-b from-white via-cyan-50/40 to-white blur-3xl opacity-70 pointer-events-none" />
|
||||
<div className="absolute inset-0 bg-gradient-to-b from-white via-cyan-50/30 to-white blur-3xl opacity-70 pointer-events-none" />
|
||||
<div
|
||||
className="relative w-full flex items-center justify-center lg:justify-center min-h-[450px] lg:min-h-[400px]"
|
||||
onMouseLeave={() => setActive("agent")}
|
||||
onMouseLeave={() => setActive("network")}
|
||||
>
|
||||
<motion.div
|
||||
className="relative lg:pl-0 pl-6 h-[300px] lg:h-[400px] w-64 sm:w-80 lg:w-96"
|
||||
@@ -77,8 +77,8 @@ export function StackedCubesLight() {
|
||||
<div
|
||||
className={`absolute inset-0 blur-2xl rounded-3xl transition-all duration-500 ${
|
||||
active === layer.id
|
||||
? "bg-cyan-300/40 opacity-70"
|
||||
: "bg-cyan-200/20 opacity-40"
|
||||
? "bg-cyan-300/20 opacity-20"
|
||||
: "bg-cyan-200/20 opacity-20"
|
||||
}`}
|
||||
/>
|
||||
<CubeLight
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Link } from "react-router-dom";
|
||||
import { motion } from "motion/react";
|
||||
import { DarkCard } from "./cards";
|
||||
|
||||
interface CarouselProps {
|
||||
items: JSX.Element[];
|
||||
@@ -61,7 +62,7 @@ export const Carousel = ({ items, initialScroll = 0 }: CarouselProps) => {
|
||||
return (
|
||||
<div className="relative w-full">
|
||||
<div
|
||||
className="flex w-full overflow-x-scroll overscroll-x-auto scroll-smooth py-10 [scrollbar-width:none] md:py-20"
|
||||
className="flex w-full overflow-x-scroll overscroll-x-auto scroll-smooth py-8 [scrollbar-width:none] md:py-20"
|
||||
ref={carouselRef}
|
||||
onScroll={checkScrollability}
|
||||
>
|
||||
@@ -73,7 +74,7 @@ export const Carousel = ({ items, initialScroll = 0 }: CarouselProps) => {
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-row justify-start gap-4 pl-4",
|
||||
"flex flex-row justify-start gap-6 pl-4",
|
||||
"mx-auto max-w-7xl", // remove max-w-4xl if you want the carousel to span the full width of its container
|
||||
)}
|
||||
>
|
||||
@@ -127,42 +128,44 @@ export const Card = ({
|
||||
card: Card;
|
||||
layout?: boolean;
|
||||
}) => {
|
||||
|
||||
return (
|
||||
<Link to={card.link}>
|
||||
<DarkCard className="p-0 rounded-3xl">
|
||||
<motion.div
|
||||
layoutId={layout ? `card-${card.title}` : undefined}
|
||||
className="relative z-10 flex h-60 w-56 flex-col items-start justify-start overflow-hidden rounded-3xl md:h-120 md:w-96 hover:scale-105 transition-transform duration-200"
|
||||
style={{
|
||||
backgroundImage: `url(${card.bg})`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center',
|
||||
}}
|
||||
className="relative z-10 flex h-104 w-80 flex-col justify-between overflow-hidden rounded-3xl p-8 md:h-120 md:w-96"
|
||||
>
|
||||
<div className="pointer-events-none absolute inset-x-0 top-0 z-30 h-full bg-gradient-to-b from-black/50 via-transparent to-transparent" />
|
||||
<div className="relative z-40 p-8 w-full">
|
||||
<div className="flex h-2/5 flex-col justify-center py-4 px-4">
|
||||
<motion.p
|
||||
layoutId={layout ? `category-${card.category}` : undefined}
|
||||
className="text-left font-sans text-sm font-medium text-white md:text-base"
|
||||
className="text-left font-sans font-semibold text-cyan-500 uppercase tracking-wider text-xs md:text-sm"
|
||||
>
|
||||
{card.category}
|
||||
</motion.p>
|
||||
<motion.p
|
||||
layoutId={layout ? `title-${card.title}` : undefined}
|
||||
className="mt-2 max-w-xs text-left font-sans text-xl font-semibold [text-wrap:balance] text-white md:text-3xl"
|
||||
className="mt-1 max-w-xs text-left font-sans text-xl font-semibold text-white [text-wrap:balance] lg:text-2xl"
|
||||
>
|
||||
{card.title}
|
||||
</motion.p>
|
||||
<div className="flex flex-row justify-between items-center w-full mt-4">
|
||||
<motion.p className="max-w-xs text-left font-sans text-sm text-neutral-300">
|
||||
<div className="mt-2 flex w-full flex-row items-center justify-between md:mt-4">
|
||||
<motion.p className="max-w-xs text-left font-sans lg:text-xl text-lg text-neutral-300 lg:leading-normal leading-tight">
|
||||
{card.description}
|
||||
</motion.p>
|
||||
<div className="h-8 w-8 bg-[#212121] rounded-full flex items-center justify-center text-[#858585] shrink-0 hover:bg-[#262626] hover:text-white active:bg-[#262626] active:text-white transition-colors duration-200">
|
||||
<IconChevronRight className="h-6 w-6" />
|
||||
<div className="flex h-6 w-6 ml-2 shrink-0 items-center justify-center rounded-full bg-[#212121] text-[#858585] transition-colors duration-200 hover:bg-[#262626] hover:text-white md:h-8 md:w-8">
|
||||
<IconChevronRight className="h-4 w-4 md:h-6 md:w-6 " />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative flex h-3/5 w-full items-end justify-end bg-transparent">
|
||||
<img
|
||||
src={card.src}
|
||||
alt={card.title}
|
||||
className="h-full w-full origin-bottom-right scale-75 object-contain mb-10"
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
</DarkCard>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
19
src/components/ui/cards.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import * as React from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const DarkCard = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"border border-white/10 bg-white/3 p-8 backdrop-blur-sm transition hover:-translate-y-1 hover:border-cyan-300/50 rounded-xl hover:bg-white/6 hover:scale-105 hover:shadow-lg hover:shadow-cyan-500/15",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
DarkCard.displayName = "DarkCard";
|
||||
|
||||
export { DarkCard };
|
||||
@@ -33,7 +33,7 @@ export const LayoutTextFlip = ({
|
||||
|
||||
<motion.span
|
||||
layout
|
||||
className="relative w-fit overflow-hidden px-2 py-2 font-medium italic tracking-tight"
|
||||
className="relative w-fit overflow-hidden px-2 py-2 font-medium tracking-tight"
|
||||
>
|
||||
<AnimatePresence mode="popLayout">
|
||||
<motion.span
|
||||
|
||||
BIN
src/images/6890d07f23cb642ec7274aee_platform-image.webp
Normal file
|
After Width: | Height: | Size: 106 KiB |
BIN
src/images/ChatGPT Image Nov 6, 2025, 09_01_36 PM.png
Normal file
|
After Width: | Height: | Size: 2.0 MiB |
BIN
src/images/bento-compute.png
Normal file
|
After Width: | Height: | Size: 1002 KiB |
BIN
src/images/bento-gpu.jpg
Normal file
|
After Width: | Height: | Size: 179 KiB |