added dockerfile, style changes
This commit is contained in:
parent
fc8fb230e0
commit
57accaca11
@ -1,11 +0,0 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"shadcn": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "shadcn@canary", "registry:mcp"],
|
||||
"env": {
|
||||
"REGISTRY_URL": "https://animate-ui.com/r/registry.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
23
.env.local.example
Normal file
23
.env.local.example
Normal file
@ -0,0 +1,23 @@
|
||||
# Environment variables for Fortura Data Solutions website
|
||||
|
||||
# Next.js environment
|
||||
NODE_ENV=development
|
||||
|
||||
# API endpoints (if applicable)
|
||||
# NEXT_PUBLIC_API_URL=http://localhost:3001/api
|
||||
|
||||
# Analytics (if self-hosted)
|
||||
# NEXT_PUBLIC_PLAUSIBLE_DOMAIN=your-domain.com
|
||||
# NEXT_PUBLIC_PLAUSIBLE_API_HOST=https://plausible.your-domain.com
|
||||
|
||||
# Contact form submission endpoint
|
||||
# CONTACT_FORM_ENDPOINT=https://n8n.your-domain.com/webhook/contact-form
|
||||
|
||||
# Calendly integration (if applicable)
|
||||
# CALENDLY_URL=https://calendly.com/your-calendly-url
|
||||
|
||||
# Email service (if applicable)
|
||||
# NEXT_PUBLIC_EMAIL_SERVICE_ENDPOINT=https://api.email-service.com/send
|
||||
|
||||
# Feature flags
|
||||
# NEXT_PUBLIC_FEATURE_FLAG_NEW_DESIGN=true
|
||||
0
061cecebb592
Normal file
0
061cecebb592
Normal file
0
46db0b326091
Normal file
0
46db0b326091
Normal file
0
4cf371dfcf97
Normal file
0
4cf371dfcf97
Normal file
0
631b88c73ff0
Normal file
0
631b88c73ff0
Normal file
0
7cdef5a33192
Normal file
0
7cdef5a33192
Normal file
0
8fccc2e8a28d
Normal file
0
8fccc2e8a28d
Normal file
348
CONTRIBUTING.md
348
CONTRIBUTING.md
@ -1,348 +0,0 @@
|
||||
# Contributing to Animate UI
|
||||
|
||||
Thank you for your interest in **contributing to Animate UI**! Your support is highly appreciated, and we look forward to your contributions. This guide will help you understand the project structure and provide detailed instructions for adding a new component or effect to Animate UI.
|
||||
|
||||
**Note:** You only need to modify a few files to add a new component, and it should take you around 10 minutes to complete.
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Fork and Clone the Repository
|
||||
|
||||
#### 1. Fork the Repository
|
||||
|
||||
Click [here](https://github.com/animate-ui/animate-ui/fork) to fork the repository.
|
||||
|
||||
#### 2. Clone your Fork to Your Local Machine
|
||||
|
||||
```bash
|
||||
git clone https://github.com/<YOUR_USERNAME>/animate-ui.git
|
||||
```
|
||||
|
||||
#### 3. Navigate to the Project Directory
|
||||
|
||||
```bash
|
||||
cd animate-ui
|
||||
```
|
||||
|
||||
#### 4. Create a New Branch for Your Changes
|
||||
|
||||
```bash
|
||||
git checkout -b my-branch
|
||||
```
|
||||
|
||||
#### 5. Install Dependencies
|
||||
|
||||
```bash
|
||||
pnpm i
|
||||
```
|
||||
|
||||
#### 6. Run the Project
|
||||
|
||||
```bash
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
## Edit a Component
|
||||
|
||||
If you need to modify a component to correct or improve it, you must :
|
||||
|
||||
- add a screenshot (photo or video as appropriate) of before and after the modification
|
||||
- clearly explain why you made the modification
|
||||
|
||||
### Edit the code
|
||||
|
||||
Edit the component in the `registry` folder. Don't forget to adapt the demo and documentation if necessary.
|
||||
|
||||
You shouldn't change your behavior completely unless there's a good reason.
|
||||
|
||||
### Build the Registry
|
||||
|
||||
To update the registry, run the following command:
|
||||
|
||||
```bash
|
||||
pnpm registry:build
|
||||
```
|
||||
|
||||
## Adding a New Component
|
||||
|
||||
The addition of a new component must comply with certain rules:
|
||||
|
||||
- The component must be animated in some way (css, motion, ...).
|
||||
- You can't just copy/paste component code from other libraries. You can be inspired by a component, but it must have added value. For example, I took Shadcn's components and animated them. So I didn't copy and paste the component, I added something to it.
|
||||
- If you take inspiration from a component (CodePen, another library, etc.), remember to add the “Credits” section to your documentation. It's important to respect the work of other developers.
|
||||
|
||||
To submit your component, please include a demo video in the MR. Once the component has been submitted, it must be validated by @imskyleen.
|
||||
|
||||
To **add a new component to Animate UI**, you will need to update several files. Follow these steps:
|
||||
|
||||
### Create the Component
|
||||
|
||||
#### Basics
|
||||
|
||||
Create your main component in `apps/www/registry/[category]/my-component/index.tsx`.
|
||||
|
||||
```tsx title="my-component/index.tsx"
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
|
||||
type MyComponentProps = {
|
||||
myProps: string;
|
||||
} & React.ComponentProps<'div'>;
|
||||
|
||||
function MyComponent({ myProps, ...props }) {
|
||||
return <div {...props}>{/* Your component */}</div>;
|
||||
}
|
||||
|
||||
export { MyComponent, type MyComponentProps };
|
||||
```
|
||||
|
||||
#### Registry item
|
||||
|
||||
Create a `apps/www/registry/[category]/my-component/registry-item.json` file to export your component :
|
||||
|
||||
```json title="my-component/registry-item.json"
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
||||
"name": "my-component",
|
||||
"type": "registry:ui",
|
||||
"title": "My Component",
|
||||
"description": "My Component Description",
|
||||
"dependencies": [...],
|
||||
"devDependencies": [...],
|
||||
"files": [
|
||||
{
|
||||
"path": "registry/[category]/my-component/index.tsx",
|
||||
"type": "registry:ui",
|
||||
"target": "components/animate-ui/demo/[category]/my-component.tsx"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Create a demo
|
||||
|
||||
#### Basics
|
||||
|
||||
Create your demo in `apps/www/registry/demo/[category]/my-component/index.tsx`.
|
||||
|
||||
```tsx title="my-component/index.tsx"
|
||||
'use client';
|
||||
|
||||
import {
|
||||
MyComponent,
|
||||
type MyComponentProps,
|
||||
} from '@/registry/[category]/my-component';
|
||||
|
||||
type MyComponentDemoProps = {
|
||||
myProps: string;
|
||||
} & MyComponentProps;
|
||||
|
||||
export const MyComponentDemo = ({ myProps }) => {
|
||||
return <MyComponent myProps={myProps} />;
|
||||
};
|
||||
```
|
||||
|
||||
#### Registry item
|
||||
|
||||
```json title="my-component/registry-item.json"
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
||||
"name": "my-component-demo",
|
||||
"type": "registry:ui",
|
||||
"title": "My Component Deo",
|
||||
"description": "Demo showing my component",
|
||||
"registryDependencies": ["https://animate-ui.com/r/my-component"],
|
||||
"files": [
|
||||
{
|
||||
"path": "registry/demo/[category]/my-component/index.tsx",
|
||||
"type": "registry:ui",
|
||||
"target": "components/[category]/demo/my-component.tsx"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### Add a Tweakpane
|
||||
|
||||
You can add a Tweakpane allowing users to play with your demo props.
|
||||
|
||||
Your demo must accept the props you want in your tweakpane :
|
||||
|
||||
```tsx title="my-component-demo/index.tsx"
|
||||
import { MyComponent } from '@/registry/[category]/my-component';
|
||||
|
||||
type MyComponentDemoProps = {
|
||||
props1: number;
|
||||
props2: number;
|
||||
props3: string;
|
||||
props4: string;
|
||||
props5: boolean;
|
||||
};
|
||||
|
||||
export function MyComponentDemo({
|
||||
props1,
|
||||
props2,
|
||||
props3,
|
||||
props4,
|
||||
props5,
|
||||
}: MyComponentDemoProps) {
|
||||
return <MyComponent />;
|
||||
}
|
||||
```
|
||||
|
||||
You must then specify the demo props information in your demo's `registry-item.json` file:
|
||||
|
||||
```json title="my-component-demo/registry-item.json"
|
||||
{
|
||||
...
|
||||
"meta": {
|
||||
"demoProps": {
|
||||
"MyComponent": {
|
||||
"props1": { "value": 700, "min": 0, "max": 2000, "step": 100 },
|
||||
"props2": { "value": 0 },
|
||||
"props3": { "value": "foo" },
|
||||
"props4": {
|
||||
"value": "center",
|
||||
"options": {
|
||||
"start": "start",
|
||||
"center": "center",
|
||||
"end": "end"
|
||||
}
|
||||
},
|
||||
"props5": { "value": true }
|
||||
}
|
||||
}
|
||||
},
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
**You need to run `pnpm registry:build` to see the updated tweakpane in the demo.**
|
||||
|
||||
#### How to use `demoProps`
|
||||
|
||||
##### Number
|
||||
|
||||
Simple number input:
|
||||
|
||||
```json
|
||||
"myNumber": { "value": 10 }
|
||||
```
|
||||
|
||||
Slider:
|
||||
|
||||
```json
|
||||
"myNumber": { "value": 10, "min": 0, "max": 100, "step": 1 }
|
||||
```
|
||||
|
||||
Select:
|
||||
|
||||
```json
|
||||
"myNumber": {
|
||||
"value": 10,
|
||||
"options": {
|
||||
"Big": 30,
|
||||
"Medium": 20,
|
||||
"Small": 10
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
##### String
|
||||
|
||||
Simple text input:
|
||||
|
||||
```json
|
||||
"myString": { "value": "Hello World" }
|
||||
```
|
||||
|
||||
Select:
|
||||
|
||||
```json
|
||||
"myNumber": {
|
||||
"value": "small",
|
||||
"options": {
|
||||
"Big": "big",
|
||||
"Medium": "medium",
|
||||
"Small": "small"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
##### Boolean
|
||||
|
||||
```json
|
||||
"myBoolean": { "value": true }
|
||||
```
|
||||
|
||||
### Update the Documentation Sidebar
|
||||
|
||||
Add your component to the documentation sidebar by updating the file `content/docs/meta.json`.
|
||||
|
||||
```json title="meta.json"
|
||||
{
|
||||
"title": "Animate UI",
|
||||
"root": true,
|
||||
"pages": [
|
||||
...,
|
||||
"[category]/my-component"
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Create the Component Documentation
|
||||
|
||||
Create an MDX file to document your component in `content/docs/[category]/my-component.mdx`.
|
||||
|
||||
```mdx
|
||||
---
|
||||
title: My Component
|
||||
description: Description for the new component
|
||||
author:
|
||||
name: your name
|
||||
url: https://link-to-your-profile.com
|
||||
new: true
|
||||
---
|
||||
|
||||
<ComponentPreview name="my-component-demo" />
|
||||
|
||||
## Installation
|
||||
|
||||
<ComponentInstallation name="my-component" />
|
||||
|
||||
## Usage
|
||||
|
||||
[Basic usage of the component]
|
||||
|
||||
## Props
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
myProps: {
|
||||
description: 'Description for my props',
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
## Credits
|
||||
|
||||
- Credits to [you](https://link-to-your-profile.com) for creating the component
|
||||
```
|
||||
|
||||
### Build the Registry
|
||||
|
||||
To update the registry, run the following command:
|
||||
|
||||
```bash
|
||||
pnpm registry:build
|
||||
```
|
||||
|
||||
## Ask for Help
|
||||
|
||||
If you need any assistance or have questions, please feel free to open a [GitHub issue](https://github.com/animate-ui/animate-ui/issues/new). We are here to help!
|
||||
|
||||
Thank you again for your contribution to Animate UI! We look forward to seeing your improvements and new components.
|
||||
29
Dockerfile
Normal file
29
Dockerfile
Normal file
@ -0,0 +1,29 @@
|
||||
# Use Node.js 20 as the base image
|
||||
FROM node:20-alpine
|
||||
|
||||
# Set the working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Install pnpm globally
|
||||
RUN npm install -g pnpm
|
||||
|
||||
# Copy package.json and pnpm-lock.yaml files
|
||||
COPY package.json pnpm-lock.yaml ./
|
||||
|
||||
# Copy turbo.json
|
||||
COPY turbo.json ./
|
||||
|
||||
# Install dependencies
|
||||
RUN pnpm install --frozen-lockfile
|
||||
|
||||
# Copy the rest of the application code
|
||||
COPY . .
|
||||
|
||||
# Build the application
|
||||
RUN pnpm build
|
||||
|
||||
# Expose port 3000
|
||||
EXPOSE 3000
|
||||
|
||||
# Start the application
|
||||
CMD ["pnpm", "start"]
|
||||
144
README.md
144
README.md
@ -30,16 +30,148 @@ We built our platform for high-demand environments like VFX studios, where petab
|
||||
|
||||
This repository contains the code for the Fortura Data Solutions marketing website. It's built with Next.js, TypeScript, and Tailwind CSS.
|
||||
|
||||
For information on how to set up the development environment, run the site locally, or contribute, please see our [contributing guide](CONTRIBUTING.md).
|
||||
### Prerequisites
|
||||
|
||||
- Node.js >= 20
|
||||
- pnpm >= 8
|
||||
- Docker (optional, for containerized deployment)
|
||||
|
||||
### Environment Variables
|
||||
|
||||
To run this project, you'll need to set up the following environment variables:
|
||||
|
||||
```bash
|
||||
# Create a .env.local file in the root directory
|
||||
cp .env.local.example .env.local
|
||||
```
|
||||
|
||||
Then edit the `.env.local` file to include your configuration:
|
||||
|
||||
```env
|
||||
# Next.js environment
|
||||
NODE_ENV=development
|
||||
|
||||
# API endpoints (if applicable)
|
||||
# NEXT_PUBLIC_API_URL=http://localhost:3001/api
|
||||
|
||||
# Analytics (if self-hosted)
|
||||
# NEXT_PUBLIC_PLAUSIBLE_DOMAIN=your-domain.com
|
||||
# NEXT_PUBLIC_PLAUSIBLE_API_HOST=https://plausible.your-domain.com
|
||||
|
||||
# Contact form submission endpoint
|
||||
# CONTACT_FORM_ENDPOINT=https://n8n.your-domain.com/webhook/contact-form
|
||||
|
||||
# Calendly integration (if applicable)
|
||||
# CALENDLY_URL=https://calendly.com/your-calendly-url
|
||||
```
|
||||
|
||||
### Development
|
||||
|
||||
To run the development server:
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
pnpm install
|
||||
|
||||
# Run the development server
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
|
||||
### Docker Deployment
|
||||
|
||||
To build and run the application using Docker:
|
||||
|
||||
1. Build the Docker image:
|
||||
```bash
|
||||
docker build -t fortura-website -f apps/www/Dockerfile .
|
||||
```
|
||||
|
||||
2. Run the container:
|
||||
```bash
|
||||
docker run -p 3000:3000 --env-file .env.local fortura-website
|
||||
```
|
||||
|
||||
Alternatively, use docker-compose:
|
||||
```bash
|
||||
docker-compose up --build
|
||||
```
|
||||
|
||||
The application will be available at [http://localhost:3000](http://localhost:3000).
|
||||
|
||||
### Production Build
|
||||
|
||||
To create a production build:
|
||||
|
||||
```bash
|
||||
# Build the application
|
||||
pnpm build
|
||||
|
||||
# Start the production server
|
||||
pnpm start
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### TS2742 from Fumadocs generated files (Next.js 15 type-check)
|
||||
|
||||
TypeScript can flag the generated Fumadocs output in `apps/www/.source/index.ts` because the inferred type of `docs` references private internals from `fumadocs-mdx/dist/...`. Next 15 enforces strict type portability and fails with TS2742.
|
||||
|
||||
Fast unblock (recommended and safe): exclude generated files from type-check.
|
||||
|
||||
1) Ensure `apps/www/tsconfig.json` excludes the generated folder and relaxes lib checks:
|
||||
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"exclude": [
|
||||
".source",
|
||||
".source/**/*"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
2) Clean and rebuild the app:
|
||||
|
||||
```bash
|
||||
rm -rf apps/www/.source
|
||||
pnpm -C apps/www build
|
||||
```
|
||||
|
||||
Proper fix (long-term): align Fumadocs + Zod and regenerate.
|
||||
|
||||
1) Upgrade deps (workspace-wide):
|
||||
|
||||
```bash
|
||||
pnpm -w up fumadocs-mdx@latest fumadocs-core@latest fumadocs-ui@latest @types/mdx@latest
|
||||
pnpm -w up zod@^4
|
||||
```
|
||||
|
||||
2) Regenerate the `.source` output:
|
||||
|
||||
```bash
|
||||
rm -rf apps/www/.source
|
||||
pnpm -C apps/www build
|
||||
```
|
||||
|
||||
3) If the generator still emits a generic that triggers TS2742, enforce a portable annotation in `apps/www/.source/index.ts` (temporary):
|
||||
|
||||
```ts
|
||||
import { _runtime } from 'fumadocs-mdx';
|
||||
type DocsData = ReturnType<typeof _runtime.docs>;
|
||||
export const docs: DocsData = _runtime.docs(/* generated arrays */);
|
||||
// or fallback: export const docs: unknown = _runtime.docs(/* generated arrays */);
|
||||
```
|
||||
|
||||
Note: Keeping `.source` excluded from type-checks is fine even after upgrades; it also speeds up CI.
|
||||
|
||||
## Documentation
|
||||
|
||||
Detailed documentation for Fortura's services, migration guides, and operational procedures is available to clients on our internal portal.
|
||||
|
||||
## Contributing
|
||||
|
||||
Visit our [contributing guide](CONTRIBUTING.md) to learn how to contribute to the website's codebase.
|
||||
|
||||
## License
|
||||
|
||||
This website's code is licensed under a proprietary license. See [LICENSE.md](LICENSE.md) for details.
|
||||
This website's code is licensed under a proprietary license. See [LICENSE.md](LICENSE.md) for details.
|
||||
|
||||
0
animate-ui@0.0.1
Normal file
0
animate-ui@0.0.1
Normal file
13
apps/www/.source-typed.ts
Normal file
13
apps/www/.source-typed.ts
Normal file
@ -0,0 +1,13 @@
|
||||
// Typed facade for the generated .source/index.ts
|
||||
// Keep this file portable and avoid referencing internal fumadocs types.
|
||||
|
||||
// Minimal surface used by the app (extend if you need more methods)
|
||||
export type DocsApi = {
|
||||
toFumadocsSource(): unknown;
|
||||
};
|
||||
|
||||
// Defer loading the generated module to runtime and avoid importing its types
|
||||
export const docs: DocsApi = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
toFumadocsSource: () => require('./.source/index').docs.toFumadocsSource(),
|
||||
};
|
||||
26
apps/www/Dockerfile
Normal file
26
apps/www/Dockerfile
Normal file
@ -0,0 +1,26 @@
|
||||
# Use Node.js 20 as the base image
|
||||
FROM node:20-alpine
|
||||
|
||||
# Set the working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Install pnpm globally
|
||||
RUN npm install -g pnpm
|
||||
|
||||
# Copy package.json and pnpm-lock.yaml files
|
||||
COPY package.json pnpm-lock.yaml ./
|
||||
|
||||
# Install dependencies
|
||||
RUN pnpm install --frozen-lockfile
|
||||
|
||||
# Copy the rest of the application code
|
||||
COPY . .
|
||||
|
||||
# Build the application
|
||||
RUN pnpm build
|
||||
|
||||
# Expose port 3000
|
||||
EXPOSE 3000
|
||||
|
||||
# Start the application
|
||||
CMD ["pnpm", "start"]
|
||||
@ -6,348 +6,256 @@ import * as React from 'react';
|
||||
// Import layout components from our new shared `ui` library
|
||||
import { Navbar } from '@workspace/ui/components/layout';
|
||||
import { Footer } from '@workspace/ui/components/layout';
|
||||
import { Container, Section, Grid } from '@workspace/ui/components/layout';
|
||||
import { Container } from '@workspace/ui/components/layout';
|
||||
import { AnimatedSection } from '@workspace/ui/components/animate-ui/section';
|
||||
// import { ScrollProgressBar } from '@workspace/ui/components/animate-ui/scroll-progress';
|
||||
// import { Collapsible, CollapsibleTrigger, CollapsibleContent } from '@workspace/ui/components/animate-ui/collapsible';
|
||||
import { motion, useScroll, useTransform } from 'motion/react';
|
||||
// import { ParallaxLayers } from '@workspace/ui/components/animate-ui/parallax-layers';
|
||||
|
||||
// Import the useTone hook to access the global tone setting
|
||||
import { useTone } from '@workspace/ui/hooks/use-tone';
|
||||
|
||||
// Import Lucide icons for the value proposition section
|
||||
import { DollarSignIcon, WrenchIcon, MapIcon, LockIcon, CloudOffIcon, ZapIcon } from 'lucide-react';
|
||||
import { PlaceholderIcon } from '@workspace/ui/components/icons';
|
||||
import { WrenchIcon, LockIcon, CloudOffIcon, ZapIcon } from 'lucide-react';
|
||||
|
||||
// Import UI components from our shared library
|
||||
import { Button } from '@workspace/ui/components/ui';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@workspace/ui/components/ui';
|
||||
import Image from 'next/image';
|
||||
|
||||
// Import new content components
|
||||
import { MetricsKPI } from '@workspace/ui/components/content';
|
||||
import { LogosMarquee } from '@workspace/ui/components/content';
|
||||
import { Steps } from '@workspace/ui/components/content';
|
||||
import { FeatureCard, CaseStudyCard } from '@workspace/ui/components/content/cards';
|
||||
// import { Steps } from '@workspace/ui/components/content';
|
||||
import { TrustSection } from '@workspace/ui/components/content';
|
||||
import { FAQAccordion } from '@workspace/ui/components/interactive';
|
||||
// import { FAQAccordion } from '@workspace/ui/components/interactive';
|
||||
import { FinalCTASection } from '@workspace/ui/components/content';
|
||||
import { CostReductionChart } from '@workspace/ui/components/content/cost-reduction-chart';
|
||||
|
||||
export default function HomePage() {
|
||||
const { tone } = useTone(); // Access the current tone
|
||||
|
||||
// Content definitions based on tone
|
||||
const heroHeadline = tone === 'direct'
|
||||
? "Own your infrastructure. Slash your bill."
|
||||
: "Cut waste, keep performance.";
|
||||
const heroSubcopyLine1 = tone === 'direct'
|
||||
? "Your hosting provider is fucking you. Cloud rent bleeds $25k–$500k/mo."
|
||||
: "Cloud rent is bleeding your budget dry.";
|
||||
const heroSubcopyLine2 = tone === 'direct'
|
||||
? "Own the hardware. Keep the performance. Kill the waste."
|
||||
: "Own your stack. Colocate it. Keep the performance, ditch the waste.";
|
||||
const heroHeadline =
|
||||
tone === 'direct'
|
||||
? 'Own your infrastructure. Slash your bill.'
|
||||
: 'Cut waste, keep performance.';
|
||||
const heroSubcopyLine1 =
|
||||
tone === 'direct'
|
||||
? 'Your hosting provider is fucking you. Cloud rent bleeds $25k–$500k/mo.'
|
||||
: 'Cloud rent is bleeding your budget dry.';
|
||||
const heroSubcopyLine2 =
|
||||
tone === 'direct'
|
||||
? 'Own the hardware. Keep the performance. Kill the waste.'
|
||||
: 'Own your stack. Colocate it. Keep the performance, ditch the waste.';
|
||||
|
||||
const valuePropositions = tone === 'direct' ? [
|
||||
// Core value propositions based on core selling points
|
||||
const coreValuePropositions = [
|
||||
{
|
||||
icon: <DollarSignIcon className="size-6" />,
|
||||
title: "Stop getting scammed.",
|
||||
description: "Cloud rent bleeds $25k–$500k/mo. Own the hardware. Keep the performance. Kill the waste."
|
||||
title: 'Stop getting scammed',
|
||||
description:
|
||||
'Cloud providers are bleeding you dry. We help you cut costs by up to 65% by moving away from endless rental fees to a one-time hardware purchase plus low colocation costs.',
|
||||
},
|
||||
{
|
||||
icon: <WrenchIcon className="size-6" />,
|
||||
title: "Build on what works.",
|
||||
description: "Keep your stack. We wire it together and automate the boring."
|
||||
title: 'Keep your existing stack',
|
||||
description:
|
||||
"We don't force you to replace your tools. Instead, we integrate what works and automate the boring parts, so your team can focus on what they do best.",
|
||||
},
|
||||
{
|
||||
icon: <MapIcon className="size-6" />,
|
||||
title: "Pave desire paths.",
|
||||
description: "We embed with your team, shape the system to how they actually work."
|
||||
}
|
||||
] : [
|
||||
{
|
||||
icon: <DollarSignIcon className="size-6" />,
|
||||
title: "Cut waste, keep performance.",
|
||||
description: "Move from renting to owning. Dramatically reduce monthly costs without sacrificing speed or reliability."
|
||||
title: 'Work with your actual workflows',
|
||||
description:
|
||||
'We embed with your team, map your real processes, and build automation around how you actually work—not how consultants think you should work.',
|
||||
},
|
||||
{
|
||||
icon: <WrenchIcon className="size-6" />,
|
||||
title: "Integrate, don't replace.",
|
||||
description: "We work with your existing tools and workflows. No disruptive rip-and-replace projects."
|
||||
},
|
||||
{
|
||||
icon: <MapIcon className="size-6" />,
|
||||
title: "Build around real workflows.",
|
||||
description: "We map your actual processes, then build automation that fits like a glove."
|
||||
}
|
||||
];
|
||||
|
||||
// Mock data for LogosMarquee
|
||||
const logos = Array(6).fill(null).map((_, i) => ({
|
||||
name: `Client ${i + 1}`,
|
||||
icon: <PlaceholderIcon className="size-8 md:size-10" />
|
||||
}));
|
||||
|
||||
// Mock data for Process Steps
|
||||
const processSteps = [
|
||||
// Benefits of owned infrastructure
|
||||
const benefits = [
|
||||
{
|
||||
title: "Discovery",
|
||||
description: "We deep-dive into your current infrastructure, workloads, and pain points.",
|
||||
youDo: "Share access to cloud billing, architecture docs, and team interviews.",
|
||||
weDo: "Analyze costs, identify waste, and map technical dependencies."
|
||||
title: 'Ownership',
|
||||
description:
|
||||
'Own your hardware and data. No more vendor lock-in or unpredictable pricing.',
|
||||
},
|
||||
{
|
||||
title: "Shadow & Map",
|
||||
description: "We observe how your teams actually work and map desire paths.",
|
||||
youDo: "Participate in workflow observations and workshops.",
|
||||
weDo: "Document real workflows, identify automation opportunities, and draft the future state."
|
||||
title: 'Performance',
|
||||
description:
|
||||
'Direct-attached storage and local networks outperform virtualized cloud infrastructure.',
|
||||
},
|
||||
{
|
||||
title: "Architect",
|
||||
description: "We design a tailored, owned infrastructure solution.",
|
||||
youDo: "Review and approve the proposed architecture and migration plan.",
|
||||
weDo: "Specify hardware, select software stack, and design for performance and cost."
|
||||
title: 'Privacy',
|
||||
description:
|
||||
'Your data never leaves your control. AI models run locally with zero data leakage.',
|
||||
},
|
||||
{
|
||||
title: "Pilot",
|
||||
description: "We build and test a critical component in the new environment.",
|
||||
youDo: "Provide feedback on performance, usability, and integration.",
|
||||
weDo: "Deploy, test, and iterate on the pilot component with your team."
|
||||
title: 'Cost Savings',
|
||||
description:
|
||||
"Break-even in under 12 months. After that, it's all savings.",
|
||||
},
|
||||
{
|
||||
title: "Migrate",
|
||||
description: "We execute the planned migration with zero downtime.",
|
||||
youDo: "Validate functionality and performance post-migration.",
|
||||
weDo: "Cutover workloads, decommission old systems, and optimize the new stack."
|
||||
},
|
||||
{
|
||||
title: "Operate",
|
||||
description: "We manage the infrastructure, you focus on your core business.",
|
||||
youDo: "Use the system and provide ongoing feedback for improvements.",
|
||||
weDo: "Monitor, maintain, upgrade, and support the infrastructure 24/7."
|
||||
}
|
||||
];
|
||||
|
||||
// Mock data for Case Studies
|
||||
const caseStudies = [
|
||||
{
|
||||
title: "VFX Studio Migration",
|
||||
description: "Migrated a major VFX studio from cloud rendering to a colocated HPC cluster, reducing costs by 65%.",
|
||||
kpi1: { value: "65%", label: "Cost ↓" },
|
||||
kpi2: { value: "2x", label: "Performance ↑" },
|
||||
ctaText: "See build",
|
||||
ctaHref: "/case-studies/vfx-studio"
|
||||
},
|
||||
{
|
||||
title: "Legal Document Management",
|
||||
description: "Consolidated scattered legal tech stack into a secure, private Nextcloud instance with AI search.",
|
||||
kpi1: { value: "$120k/mo", label: "Saved" },
|
||||
kpi2: { value: "0", label: "Egress Fees" },
|
||||
ctaText: "See build",
|
||||
ctaHref: "/case-studies/legal-firm"
|
||||
},
|
||||
{
|
||||
title: "Research Data Pipeline",
|
||||
description: "Built a local ML training environment for a research institute, eliminating data transfer bottlenecks.",
|
||||
kpi1: { value: "90%", label: "Time-to-Train ↓" },
|
||||
kpi2: { value: "Break-even: 8mo", label: "" },
|
||||
ctaText: "See build",
|
||||
ctaHref: "/case-studies/research-institute"
|
||||
}
|
||||
];
|
||||
|
||||
// Mock data for Trust Section
|
||||
// Trust factors
|
||||
const trustItems = [
|
||||
{
|
||||
icon: <LockIcon className="size-5" />,
|
||||
text: "Your data. Your hardware. Your rules."
|
||||
},
|
||||
{
|
||||
icon: <CloudOffIcon className="size-5" />,
|
||||
text: "Colocated. Monitored. Upgradable without downtime."
|
||||
text: 'Your data. Your hardware. Your rules.',
|
||||
},
|
||||
{
|
||||
icon: <ZapIcon className="size-5" />,
|
||||
text: "AI-native, privacy-first. No third-party model training."
|
||||
}
|
||||
];
|
||||
|
||||
// Mock data for FAQ
|
||||
// (This is just for homepage preview; full FAQ is on /faq page)
|
||||
const faqItems = [
|
||||
{
|
||||
question: "What uptime can I expect?",
|
||||
answer: "We design for 99.9%+ uptime through redundant hardware, proactive monitoring, and colocation in Tier III+ facilities."
|
||||
text: 'AI-native, privacy-first. No third-party AI.',
|
||||
},
|
||||
{
|
||||
question: "How is support handled?",
|
||||
answer: "You get direct access to our engineering team via Slack, email, or phone. We provide 24/7 monitoring and alerting."
|
||||
}
|
||||
icon: <CloudOffIcon className="size-5" />,
|
||||
text: 'No license fees. You own it, forever.',
|
||||
},
|
||||
{
|
||||
icon: <WrenchIcon className="size-5" />,
|
||||
text: 'Ultimate freedom. Any tool, any stack, reshaped to work together for your unique use-case.',
|
||||
},
|
||||
];
|
||||
|
||||
// Hero right panel scroll-linked fade
|
||||
const heroRef = React.useRef<HTMLDivElement | null>(null);
|
||||
const { scrollYProgress: heroProgress } = useScroll({
|
||||
target: heroRef,
|
||||
offset: ['start end', 'end start'],
|
||||
});
|
||||
const heroPanelOpacity = useTransform(heroProgress, [0, 1], [1, 0.96]);
|
||||
const heroPanelY = useTransform(heroProgress, [0, 1], [0, 8]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col min-h-screen">
|
||||
<Navbar />
|
||||
|
||||
{/* Hero Section */}
|
||||
<Section className="flex flex-col items-center text-center py-20 md:py-32">
|
||||
<Container maxWidth="3xl">
|
||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-bold tracking-tight">
|
||||
{heroHeadline}
|
||||
</h1>
|
||||
<p className="mt-6 text-lg md:text-xl text-muted-foreground max-w-2xl">
|
||||
{heroSubcopyLine1}
|
||||
</p>
|
||||
<p className="mt-2 text-lg md:text-xl text-muted-foreground max-w-2xl">
|
||||
{heroSubcopyLine2}
|
||||
</p>
|
||||
<div className="mt-10 flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<Button size="lg" variant="default" asChild>
|
||||
<a href="/contact">Book Architecture Call</a>
|
||||
</Button>
|
||||
<Button size="lg" variant="outline" asChild>
|
||||
<a href="/pricing">Run My Numbers</a>
|
||||
</Button>
|
||||
</div>
|
||||
{/* KPI Metrics */}
|
||||
<div className="mt-16 grid grid-cols-1 sm:grid-cols-3 gap-8 max-w-2xl mx-auto">
|
||||
<MetricsKPI value="$350/mo" label="after year 1" />
|
||||
<MetricsKPI value="65%" label="avg. cost ↓" />
|
||||
<MetricsKPI value="12mo" label="typical break-even" />
|
||||
</div>
|
||||
</Container>
|
||||
</Section>
|
||||
|
||||
{/* Value Proposition Section */}
|
||||
<Section background="muted">
|
||||
<Container>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
{valuePropositions.map((item, index) => (
|
||||
<Card key={index} className="flex flex-col">
|
||||
<CardHeader>
|
||||
<div className="mb-2 text-primary">
|
||||
{item.icon}
|
||||
</div>
|
||||
<CardTitle>{item.title}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="flex-grow">
|
||||
<CardDescription>{item.description}</CardDescription>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</Container>
|
||||
</Section>
|
||||
|
||||
{/* Proof Strip / Logos Marquee */}
|
||||
<Section>
|
||||
<Container>
|
||||
<h2 className="text-2xl font-bold text-center mb-12">Trusted by forward-thinking teams</h2>
|
||||
<LogosMarquee logos={logos} />
|
||||
</Container>
|
||||
</Section>
|
||||
|
||||
{/* Process Snapshot */}
|
||||
<Section background="muted">
|
||||
<Container>
|
||||
<Steps
|
||||
steps={processSteps.slice(0, 3)} // Show only first 3 steps on homepage
|
||||
title="Our 6-Step Process"
|
||||
ctaText="See Full Process"
|
||||
ctaHref="/process"
|
||||
/>
|
||||
</Container>
|
||||
</Section>
|
||||
|
||||
{/* Case Study Rail */}
|
||||
<Section>
|
||||
<Container>
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-2xl font-bold">Proven Results</h2>
|
||||
<p className="mt-2 text-muted-foreground">
|
||||
Real outcomes from real migrations.
|
||||
</p>
|
||||
</div>
|
||||
<Grid columns={{ initial: 1, md: 3 }} gap="6">
|
||||
{caseStudies.map((study, index) => (
|
||||
<CaseStudyCard
|
||||
key={index}
|
||||
title={study.title}
|
||||
description={study.description}
|
||||
kpi1={study.kpi1}
|
||||
kpi2={study.kpi2}
|
||||
ctaText={study.ctaText}
|
||||
ctaHref={study.ctaHref}
|
||||
/>
|
||||
))}
|
||||
</Grid>
|
||||
</Container>
|
||||
</Section>
|
||||
|
||||
{/* Pricing Preview / Own vs Rent */}
|
||||
<Section background="muted">
|
||||
<Container>
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-2xl font-bold">Own vs Rent</h2>
|
||||
<p className="mt-2 text-muted-foreground max-w-2xl mx-auto">
|
||||
Cloud is a rental agreement with infinite term. Owned hardware + colocation breaks even in under 12 months.
|
||||
</p>
|
||||
</div>
|
||||
<div className="max-w-4xl mx-auto bg-card border rounded-xl p-6 md:p-8">
|
||||
<h3 className="text-xl font-semibold mb-4">Why Owned Infrastructure Wins</h3>
|
||||
<ul className="space-y-2 text-muted-foreground">
|
||||
<li className="flex items-start">
|
||||
<span className="mr-2 text-primary">•</span>
|
||||
<span><span className="font-medium">Capex:</span> You buy the hardware once. Depreciate over 3-5 years.</span>
|
||||
</li>
|
||||
<li className="flex items-start">
|
||||
<span className="mr-2 text-primary">•</span>
|
||||
<span><span className="font-medium">Low Opex:</span> Colocation is ~$200/server/mo. Managed ops is ~$500/server/mo.</span>
|
||||
</li>
|
||||
<li className="flex items-start">
|
||||
<span className="mr-2 text-primary">•</span>
|
||||
<span><span className="font-medium">No Egress Fees:</span> Move data freely within your network.</span>
|
||||
</li>
|
||||
<li className="flex items-start">
|
||||
<span className="mr-2 text-primary">•</span>
|
||||
<span><span className="font-medium">Performance:</span> Direct-attached storage and local networks are faster than VPCs.</span>
|
||||
</li>
|
||||
<li className="flex items-start">
|
||||
<span className="mr-2 text-primary">•</span>
|
||||
<span><span className="font-medium">No Vendor Lock-in:</span> Standard hardware and open-source software.</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div className="mt-6 p-4 bg-muted rounded-lg">
|
||||
<p className="text-center font-medium">
|
||||
Break-even is typically < 12 months. After that, it's pure savings.
|
||||
{/* Hero Section (banded, two-column, grid background) */}
|
||||
<AnimatedSection className="bg-grid-dark border-b border-muted">
|
||||
<Container maxWidth="10xl">
|
||||
<div
|
||||
ref={heroRef}
|
||||
className="grid grid-cols-1 md:grid-cols-2 gap-16 items-center py-20 md:py-28"
|
||||
>
|
||||
<div className="text-left">
|
||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-bold leading-tight tracking-tight text-balance">
|
||||
{heroHeadline}
|
||||
</h1>
|
||||
<p className="mt-6 text-lg md:text-xl text-muted-foreground max-w-xl">
|
||||
{heroSubcopyLine1}
|
||||
</p>
|
||||
<p className="mt-3 text-lg md:text-xl text-muted-foreground max-w-xl">
|
||||
{heroSubcopyLine2}
|
||||
</p>
|
||||
<div className="mt-10 flex flex-col sm:flex-row gap-4">
|
||||
<Button size="lg" variant="default" asChild>
|
||||
<a href="/contact">Book Architecture Call</a>
|
||||
</Button>
|
||||
<Button size="lg" variant="outline" asChild>
|
||||
<a href="/pricing">Run My Numbers</a>
|
||||
</Button>
|
||||
</div>
|
||||
{/* KPI Metrics */}
|
||||
<div className="mt-14 grid grid-cols-3 gap-6 max-w-xl">
|
||||
<MetricsKPI value="$350/mo" label="after year 1" />
|
||||
<MetricsKPI value="65%" label="avg. cost ↓" />
|
||||
<MetricsKPI value="12mo" label="break-even" />
|
||||
</div>
|
||||
</div>
|
||||
<motion.div
|
||||
className="relative h-[260px] md:h-[360px] lg:h-[420px] ring-1 ring-border/50"
|
||||
style={{ opacity: heroPanelOpacity, y: heroPanelY }}
|
||||
>
|
||||
<div className="absolute inset-0 rounded-sm border bg-card/50 backdrop-blur-sm" />
|
||||
{/* Optional decorative hero art (drop /hero/home-hero-art.webp in public) */}
|
||||
<Image
|
||||
src="/hero/home-hero-art.webp"
|
||||
alt=""
|
||||
aria-hidden
|
||||
fill
|
||||
sizes="(max-width: 768px) 100vw, 50vw"
|
||||
className="object-cover rounded-sm opacity-35"
|
||||
priority={false}
|
||||
/>
|
||||
<div className="absolute inset-0 bg-stripes-dark rounded-sm opacity-40" />
|
||||
</motion.div>
|
||||
</div>
|
||||
</Container>
|
||||
</Section>
|
||||
</AnimatedSection>
|
||||
|
||||
{/* Core Value Propositions - Vertical List */}
|
||||
<AnimatedSection className="py-20 border-b border-muted/80">
|
||||
<Container maxWidth="10xl">
|
||||
<div className="space-y-16">
|
||||
<h2 className="text-4xl font-bold">Our Approach</h2>
|
||||
{coreValuePropositions.map((item, index) => (
|
||||
<a
|
||||
key={index}
|
||||
href="#"
|
||||
className="group block border-b border-muted/80 pb-8 transition-all duration-300 hover:border-foreground/70 last:border-0"
|
||||
>
|
||||
<div className="flex justify-between items-start">
|
||||
<div>
|
||||
<div className="text-4xl font-light text-muted-foreground mb-4">
|
||||
{index < 9 ? `0${index + 1}` : index + 1}
|
||||
</div>
|
||||
<h3 className="text-4xl md:text-5xl font-bold transition-colors duration-300 group-hover:text-foreground/90">
|
||||
{item.title}
|
||||
<span className="ml-3 inline-block opacity-0 -translate-x-1 group-hover:opacity-100 group-hover:translate-x-0 transition">
|
||||
→
|
||||
</span>
|
||||
</h3>
|
||||
<p className="mt-6 text-xl text-muted-foreground max-w-3xl transition-colors duration-300 group-hover:text-foreground/80">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</Container>
|
||||
</AnimatedSection>
|
||||
|
||||
{/* Cost Reduction Chart */}
|
||||
<AnimatedSection className="py-20 bg-muted/30 border-b border-muted/80">
|
||||
<Container maxWidth="10xl">
|
||||
<CostReductionChart />
|
||||
</Container>
|
||||
</AnimatedSection>
|
||||
|
||||
{/* Benefits Section */}
|
||||
<AnimatedSection className="py-20 border-b border-muted">
|
||||
<Container maxWidth="10xl">
|
||||
<div className="text-center mb-20">
|
||||
<h2 className="text-4xl font-bold">Why Owned Infrastructure?</h2>
|
||||
<p className="mt-6 text-xl text-muted-foreground max-w-3xl mx-auto">
|
||||
The benefits of moving from cloud rental to hardware ownership
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-16">
|
||||
{benefits.map((benefit, index) => (
|
||||
<div key={index} className="space-y-6">
|
||||
<div className="text-5xl font-light text-muted-foreground">
|
||||
{index < 9 ? `0${index + 1}` : index + 1}
|
||||
</div>
|
||||
<h3 className="text-3xl font-bold">{benefit.title}</h3>
|
||||
<p className="text-xl text-muted-foreground">
|
||||
{benefit.description}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Container>
|
||||
</AnimatedSection>
|
||||
|
||||
{/* Trust Section */}
|
||||
<Section>
|
||||
<Container>
|
||||
<TrustSection items={trustItems} title="Your Data, Your Rules" />
|
||||
<AnimatedSection className="py-20 border-b border-muted">
|
||||
<Container maxWidth="10xl">
|
||||
<TrustSection
|
||||
items={trustItems}
|
||||
title="Your Data, Your Rules"
|
||||
description="We move your business off the cloud to custom servers you own and control. No more paying rent forever—colocate your hardware in vetted, local data centers for low ongoing costs with full ownership."
|
||||
/>
|
||||
</Container>
|
||||
</Section>
|
||||
|
||||
{/* FAQ Accordion Preview */}
|
||||
<Section background="muted">
|
||||
<Container>
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-2xl font-bold">Common Questions</h2>
|
||||
<p className="mt-2 text-muted-foreground">
|
||||
Answers to key concerns about migrating and self-hosting.
|
||||
</p>
|
||||
</div>
|
||||
<FAQAccordion />
|
||||
<div className="mt-8 text-center">
|
||||
<Button variant="link" asChild>
|
||||
<a href="/faq">See All FAQs</a>
|
||||
</Button>
|
||||
</div>
|
||||
</Container>
|
||||
</Section>
|
||||
</AnimatedSection>
|
||||
|
||||
{/* Final CTA */}
|
||||
<Section>
|
||||
<Container>
|
||||
<AnimatedSection className="py-20 border-t border-muted">
|
||||
<Container maxWidth="10xl">
|
||||
<FinalCTASection
|
||||
title="Ready to cut your cloud bill?"
|
||||
description="Book a free architecture call to see how much you can save."
|
||||
@ -357,7 +265,7 @@ export default function HomePage() {
|
||||
secondaryCtaHref="/download-blueprint" // Placeholder
|
||||
/>
|
||||
</Container>
|
||||
</Section>
|
||||
</AnimatedSection>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
|
||||
@ -3,127 +3,166 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import { ExternalLinkIcon, AwardIcon, UsersIcon, ZapIcon } from 'lucide-react';
|
||||
import { AwardIcon, UsersIcon, ZapIcon } from 'lucide-react';
|
||||
|
||||
import { Navbar } from '@workspace/ui/components/layout';
|
||||
import { Footer } from '@workspace/ui/components/layout';
|
||||
import { Container, Section, Grid } from '@workspace/ui/components/layout';
|
||||
import { Container } from '@workspace/ui/components/layout';
|
||||
import { AnimatedSection } from '@workspace/ui/components/animate-ui/section';
|
||||
import { Callout } from '@workspace/ui/components/content';
|
||||
import { Badge } from '@workspace/ui/components/content';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@workspace/ui/components/ui';
|
||||
import { Button } from '@workspace/ui/components/ui';
|
||||
import { FinalCTASection } from '@workspace/ui/components/content';
|
||||
import Image from 'next/image';
|
||||
|
||||
export default function AboutPage() {
|
||||
return (
|
||||
<div className="flex flex-col min-h-screen">
|
||||
<Navbar />
|
||||
<Section className="flex-grow">
|
||||
<Container>
|
||||
<div className="text-center mb-12">
|
||||
<h1 className="text-3xl md:text-4xl font-bold">About Fortura</h1>
|
||||
<p className="mt-4 text-muted-foreground max-w-2xl mx-auto">
|
||||
We built it for VFX first. That's why it works for everyone else.
|
||||
|
||||
{/* Hero Section */}
|
||||
<AnimatedSection className="border-b border-muted">
|
||||
<Container maxWidth="10xl">
|
||||
<div className="text-center py-20 md:py-28">
|
||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-bold">
|
||||
About Fortura
|
||||
</h1>
|
||||
<p className="mt-6 text-xl text-muted-foreground max-w-3xl mx-auto">
|
||||
We built it for VFX first. That's why it works for everyone else.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Section>
|
||||
<Container>
|
||||
<div className="flex flex-col md:flex-row gap-8 items-center">
|
||||
<div className="flex-1">
|
||||
<h2 className="text-2xl font-bold mb-4">Our Origin Story</h2>
|
||||
<p className="text-muted-foreground mb-4">
|
||||
Fortura was born in the high-stakes world of Visual Effects (VFX) production. VFX studios operate on razor-thin margins, require massive computational power, and deal with enormous datasets (petabytes) that need to move fast.
|
||||
</p>
|
||||
<p className="text-muted-foreground mb-4">
|
||||
We saw firsthand how the cloud billing model was financially unsustainable for these studios. They were hemorrhaging money on egress fees, rent for virtual machines that sat idle half the time, and paying premium prices for "enterprise" support that often lagged behind community forums.
|
||||
</p>
|
||||
<p className="text-muted-foreground">
|
||||
So, we built a better way. A way to own the hardware, colocate it for low-cost power and bandwidth, and automate the hell out of operations so a small team could manage massive infrastructure.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex-1 flex justify-center">
|
||||
{/* Placeholder for an image or graphic */}
|
||||
<div className="bg-muted border rounded-xl w-full h-64 flex items-center justify-center">
|
||||
<span className="text-muted-foreground">VFX Studio Image/Graphic</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</Section>
|
||||
|
||||
<Section background="muted">
|
||||
<Container>
|
||||
<h2 className="text-2xl font-bold mb-6 text-center">Why That Matters</h2>
|
||||
<Grid columns={{ initial: 1, md: 3 }} gap="6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<AwardIcon className="size-8 text-primary mb-2" />
|
||||
<CardTitle>Petabyte Scale</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<CardDescription>
|
||||
We cut our teeth on systems handling hundreds of terabytes to petabytes of data. This means we know how to design for scale, performance, and cost-efficiency from the ground up.
|
||||
</CardDescription>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<UsersIcon className="size-8 text-primary mb-2" />
|
||||
<CardTitle>Low Budgets</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<CardDescription>
|
||||
VFX studios have tight budgets. Every dollar spent on infrastructure is a dollar not spent on creative talent or compute rendering the next shot. This discipline forces us to find the absolute cheapest, most efficient solutions.
|
||||
</CardDescription>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<ZapIcon className="size-8 text-primary mb-2" />
|
||||
<CardTitle>Real-Time Demands</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<CardDescription>
|
||||
In VFX, deadlines are absolute. A render farm that's down is a studio that's losing money. This means we obsess over uptime, redundancy, and zero-downtime upgrades. What works for 24/7 VFX pipelines works for any business-critical application.
|
||||
</CardDescription>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
</Container>
|
||||
</Section>
|
||||
|
||||
<Section>
|
||||
<Container>
|
||||
<h2 className="text-2xl font-bold mb-6">Our Mission</h2>
|
||||
<p className="text-muted-foreground mb-4 max-w-3xl">
|
||||
Our mission is to fundamentally change how enterprises think about infrastructure. The cloud model of "rent forever" is a bad deal for anyone with significant scale or data. It's a wealth transfer from innovative businesses to cloud providers.
|
||||
</p>
|
||||
<p className="text-muted-foreground mb-6 max-w-3xl">
|
||||
We show you how to own your infrastructure, colocate it for performance and cost, and operate it with a tiny fraction of the overhead of a cloud provider. You keep the performance, you keep the data, and you keep the savings.
|
||||
</p>
|
||||
<Callout variant="default">
|
||||
<span className="font-semibold">"Your hosting provider is fucking you."</span> This isn't just a tagline; it's a technical reality. We're here to help you take control.
|
||||
</Callout>
|
||||
</Container>
|
||||
</Section>
|
||||
|
||||
<Section background="muted" className="text-center">
|
||||
<Container>
|
||||
<h2 className="text-2xl font-bold mb-4">Ready to take control?</h2>
|
||||
<p className="mb-6 text-muted-foreground max-w-2xl mx-auto">
|
||||
Book a free architecture call to see how much you can save by moving from renting to owning.
|
||||
</p>
|
||||
<Button asChild size="lg">
|
||||
<a href="/contact">
|
||||
Book Architecture Call
|
||||
<ExternalLinkIcon className="ml-2 size-4" />
|
||||
</a>
|
||||
</Button>
|
||||
</Container>
|
||||
</Section>
|
||||
</Container>
|
||||
</Section>
|
||||
</AnimatedSection>
|
||||
|
||||
{/* Origin Story */}
|
||||
<AnimatedSection className="py-20 border-b border-muted">
|
||||
<Container maxWidth="10xl">
|
||||
<div className="flex flex-col lg:flex-row gap-16 items-center">
|
||||
<div className="flex-1">
|
||||
<h2 className="text-4xl font-bold mb-8">Our Origin Story</h2>
|
||||
<div className="space-y-6">
|
||||
<p className="text-xl text-muted-foreground">
|
||||
Fortura was born in the high-stakes world of Visual Effects
|
||||
(VFX) production. VFX studios operate on razor-thin margins,
|
||||
require massive computational power, and deal with enormous
|
||||
datasets (petabytes) that need to move fast.
|
||||
</p>
|
||||
<p className="text-xl text-muted-foreground">
|
||||
We saw firsthand how the cloud billing model was financially
|
||||
unsustainable for these studios. They were hemorrhaging money
|
||||
on egress fees, rent for virtual machines that sat idle half
|
||||
the time, and paying premium prices for "enterprise" support
|
||||
that often lagged behind community forums.
|
||||
</p>
|
||||
<p className="text-xl text-muted-foreground">
|
||||
So, we built a better way. A way to own the hardware, colocate
|
||||
it for low-cost power and bandwidth, and automate the hell out
|
||||
of operations so a small team could manage massive
|
||||
infrastructure.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 flex justify-center">
|
||||
<div className="relative w-full h-80 rounded-xl overflow-hidden border">
|
||||
<Image
|
||||
src="/about/about-vfx-studio.jpg"
|
||||
alt="VFX studio render and review environment"
|
||||
fill
|
||||
sizes="(max-width: 1024px) 100vw, 50vw"
|
||||
className="object-cover"
|
||||
priority={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</AnimatedSection>
|
||||
|
||||
{/* Why That Matters */}
|
||||
<AnimatedSection className="py-20 border-b border-muted">
|
||||
<Container maxWidth="10xl">
|
||||
<h2 className="text-4xl font-bold mb-16 text-center">
|
||||
Why That Matters
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
<div className="space-y-4 p-8 rounded-lg border border-muted bg-background transition-all duration-300 hover:border-foreground/20">
|
||||
<div className="flex items-center justify-center w-12 h-12 rounded-full bg-muted/50 text-primary mb-4">
|
||||
<AwardIcon className="size-6" />
|
||||
</div>
|
||||
<h3 className="text-2xl font-bold">Petabyte Scale</h3>
|
||||
<p className="text-muted-foreground">
|
||||
We cut our teeth on systems handling hundreds of terabytes to
|
||||
petabytes of data. This means we know how to design for scale,
|
||||
performance, and cost-efficiency from the ground up.
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-4 p-8 rounded-lg border border-muted bg-background transition-all duration-300 hover:border-foreground/20">
|
||||
<div className="flex items-center justify-center w-12 h-12 rounded-full bg-muted/50 text-primary mb-4">
|
||||
<UsersIcon className="size-6" />
|
||||
</div>
|
||||
<h3 className="text-2xl font-bold">Low Budgets</h3>
|
||||
<p className="text-muted-foreground">
|
||||
VFX studios have tight budgets. Every dollar spent on
|
||||
infrastructure is a dollar not spent on creative talent or
|
||||
compute rendering the next shot. This discipline forces us to
|
||||
find the absolute cheapest, most efficient solutions.
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-4 p-8 rounded-lg border border-muted bg-background transition-all duration-300 hover:border-foreground/20">
|
||||
<div className="flex items-center justify-center w-12 h-12 rounded-full bg-muted/50 text-primary mb-4">
|
||||
<ZapIcon className="size-6" />
|
||||
</div>
|
||||
<h3 className="text-2xl font-bold">Real-Time Demands</h3>
|
||||
<p className="text-muted-foreground">
|
||||
In VFX, deadlines are absolute. A render farm that's down is a
|
||||
studio that's losing money. This means we obsess over uptime,
|
||||
redundancy, and zero-downtime upgrades. What works for 24/7 VFX
|
||||
pipelines works for any business-critical application.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</AnimatedSection>
|
||||
|
||||
{/* Our Mission */}
|
||||
<AnimatedSection className="py-20 border-b border-muted">
|
||||
<Container maxWidth="10xl">
|
||||
<h2 className="text-4xl font-bold mb-16">Our Mission</h2>
|
||||
<div className="space-y-8 max-w-4xl">
|
||||
<p className="text-xl text-muted-foreground">
|
||||
Our mission is to fundamentally change how enterprises think about
|
||||
infrastructure. The cloud model of "rent forever" is a bad deal
|
||||
for anyone with significant scale or data. It's a wealth transfer
|
||||
from innovative businesses to cloud providers.
|
||||
</p>
|
||||
<p className="text-xl text-muted-foreground">
|
||||
We show you how to own your infrastructure, colocate it for
|
||||
performance and cost, and operate it with a tiny fraction of the
|
||||
overhead of a cloud provider. You keep the performance, you keep
|
||||
the data, and you keep the savings.
|
||||
</p>
|
||||
<Callout variant="default" className="text-xl p-8">
|
||||
<span className="font-semibold">
|
||||
"Your hosting provider is fucking you."
|
||||
</span>{' '}
|
||||
This isn't just a tagline; it's a technical reality. We're here to
|
||||
help you take control.
|
||||
</Callout>
|
||||
</div>
|
||||
</Container>
|
||||
</AnimatedSection>
|
||||
|
||||
{/* Final CTA */}
|
||||
<AnimatedSection className="py-20 border-t border-muted">
|
||||
<Container maxWidth="10xl">
|
||||
<FinalCTASection
|
||||
title="Ready to take control?"
|
||||
description="Book a free architecture call to see how much you can save by moving from renting to owning."
|
||||
primaryCtaText="Book Architecture Call"
|
||||
primaryCtaHref="/contact"
|
||||
/>
|
||||
</Container>
|
||||
</AnimatedSection>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { source } from '@/lib/source';
|
||||
import { createFromSource } from 'fumadocs-core/search/server';
|
||||
|
||||
export const { GET } = createFromSource(source);
|
||||
// Fast unblock: relax types here to avoid coupling to full LoaderOutput shape
|
||||
export const { GET } = createFromSource(source as any);
|
||||
|
||||
89
apps/www/app/case-studies/page.tsx
Normal file
89
apps/www/app/case-studies/page.tsx
Normal file
@ -0,0 +1,89 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
|
||||
import { Container } from "@workspace/ui/components/layout";
|
||||
import { Navbar, Footer } from "@workspace/ui/components/layout";
|
||||
import { AnimatedSection } from '@workspace/ui/components/animate-ui/section';
|
||||
|
||||
export default function CaseStudiesPage() {
|
||||
const caseStudies = [
|
||||
{
|
||||
title: "VFX Studio Migration",
|
||||
description:
|
||||
"Migrated a major VFX studio from cloud rendering to a colocated HPC cluster, reducing costs by 65%.",
|
||||
kpi1: { value: "65%", label: "Cost ↓" },
|
||||
kpi2: { value: "2x", label: "Performance ↑" },
|
||||
ctaText: "See build",
|
||||
ctaHref: "/case-studies/vfx-studio",
|
||||
},
|
||||
{
|
||||
title: "Legal Document Management",
|
||||
description:
|
||||
"Consolidated scattered legal tech stack into a secure, private Nextcloud instance with AI search.",
|
||||
kpi1: { value: "$120k/mo", label: "Saved" },
|
||||
kpi2: { value: "0", label: "Egress Fees" },
|
||||
ctaText: "See build",
|
||||
ctaHref: "/case-studies/legal-firm",
|
||||
},
|
||||
{
|
||||
title: "Research Data Pipeline",
|
||||
description:
|
||||
"Built a local ML training environment for a research institute, eliminating data transfer bottlenecks.",
|
||||
kpi1: { value: "90%", label: "Time-to-Train ↓" },
|
||||
kpi2: { value: "Break-even: 8mo", label: "" },
|
||||
ctaText: "See build",
|
||||
ctaHref: "/case-studies/research-institute",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="flex flex-col min-h-screen">
|
||||
<Navbar />
|
||||
|
||||
{/* Hero Section */}
|
||||
<AnimatedSection className="border-b border-muted">
|
||||
<Container maxWidth="10xl">
|
||||
<div className="py-20 md:py-28">
|
||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-bold">Case Studies</h1>
|
||||
<p className="mt-6 text-xl text-muted-foreground max-w-3xl">
|
||||
Context → constraints → architecture → migration → outcomes. Real numbers, real stacks.
|
||||
</p>
|
||||
</div>
|
||||
</Container>
|
||||
</AnimatedSection>
|
||||
|
||||
{/* Case Studies */}
|
||||
<AnimatedSection className="py-20 border-b border-muted">
|
||||
<Container maxWidth="10xl">
|
||||
<div className="space-y-8 max-w-4xl">
|
||||
{caseStudies.map((study, index) => (
|
||||
<a
|
||||
key={index}
|
||||
href={study.ctaHref}
|
||||
className="group block w-full border-b border-muted pb-8 transition-all duration-300 hover:border-foreground/70"
|
||||
>
|
||||
<div className="flex flex-col md:flex-row md:items-center md:justify-between gap-6">
|
||||
<div>
|
||||
<div className="text-4xl font-light text-muted-foreground mb-2">
|
||||
{index < 9 ? `0${index + 1}` : index + 1}
|
||||
</div>
|
||||
<h2 className="text-3xl font-bold">{study.title}</h2>
|
||||
<p className="text-xl text-muted-foreground mt-3 max-w-3xl">{study.description}</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-8 shrink-0">
|
||||
<div className="text-lg"><span className="font-semibold">{study.kpi1.value}</span> <span className="text-muted-foreground">{study.kpi1.label}</span></div>
|
||||
<div className="text-lg"><span className="font-semibold">{study.kpi2.value}</span> <span className="text-muted-foreground">{study.kpi2.label}</span></div>
|
||||
<span className="text-primary text-lg">See build →</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</Container>
|
||||
</AnimatedSection>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -7,8 +7,8 @@ import { ExternalLinkIcon } from 'lucide-react';
|
||||
|
||||
import { Navbar } from '@workspace/ui/components/layout';
|
||||
import { Footer } from '@workspace/ui/components/layout';
|
||||
import { Container, Section } from '@workspace/ui/components/layout';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@workspace/ui/components/ui';
|
||||
import { Container } from '@workspace/ui/components/layout';
|
||||
import { AnimatedSection } from '@workspace/ui/components/animate-ui/section';
|
||||
import { Button } from '@workspace/ui/components/ui';
|
||||
import { Input } from '@workspace/ui/components/ui';
|
||||
import { Textarea } from '@workspace/ui/components/ui';
|
||||
@ -54,122 +54,122 @@ export default function ContactPage() {
|
||||
return (
|
||||
<div className="flex flex-col min-h-screen">
|
||||
<Navbar />
|
||||
<Section className="flex-grow">
|
||||
<Container>
|
||||
<div className="text-center mb-12">
|
||||
<h1 className="text-3xl md:text-4xl font-bold">Contact Us</h1>
|
||||
<p className="mt-4 text-muted-foreground max-w-2xl mx-auto">
|
||||
|
||||
{/* Hero Section */}
|
||||
<AnimatedSection className="border-b border-muted">
|
||||
<Container maxWidth="10xl">
|
||||
<div className="text-center py-20 md:py-28">
|
||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-bold">Contact Us</h1>
|
||||
<p className="mt-6 text-xl text-muted-foreground max-w-3xl mx-auto">
|
||||
Book a free architecture call, request a cost estimate, or ask a question.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8 max-w-6xl mx-auto">
|
||||
</Container>
|
||||
</AnimatedSection>
|
||||
|
||||
<AnimatedSection className="py-20 border-b border-muted">
|
||||
<Container maxWidth="10xl">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-16 max-w-6xl mx-auto">
|
||||
{/* Contact Form */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Send us a message</CardTitle>
|
||||
<CardDescription>
|
||||
Fill out the form and we'll get back to you as soon as possible.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{submitSuccess ? (
|
||||
<div className="bg-green-500/10 border border-green-500/30 rounded-md p-4 text-green-500">
|
||||
<p className="font-semibold">Message Sent!</p>
|
||||
<p>Thank you for reaching out. We'll get back to you soon.</p>
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-3xl font-bold">Send us a message</h2>
|
||||
<p className="text-xl text-muted-foreground">
|
||||
Fill out the form and we'll get back to you as soon as possible.
|
||||
</p>
|
||||
|
||||
{submitSuccess ? (
|
||||
<div className="bg-green-500/10 border border-green-500/30 rounded-md p-6 text-green-500">
|
||||
<p className="font-semibold text-xl">Message Sent!</p>
|
||||
<p className="mt-2">Thank you for reaching out. We'll get back to you soon.</p>
|
||||
</div>
|
||||
) : (
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
<div>
|
||||
<Label htmlFor="name" className="text-lg">Name *</Label>
|
||||
<Input
|
||||
id="name"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
required
|
||||
className="mt-2 p-4 text-lg"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div>
|
||||
<Label htmlFor="name">Name *</Label>
|
||||
<Input
|
||||
id="name"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="email">Email *</Label>
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="company">Company</Label>
|
||||
<Input
|
||||
id="company"
|
||||
value={company}
|
||||
onChange={(e) => setCompany(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="message">Message *</Label>
|
||||
<Textarea
|
||||
id="message"
|
||||
rows={5}
|
||||
value={message}
|
||||
onChange={(e) => setMessage(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
{submitError && (
|
||||
<div className="text-red-500 text-sm">{submitError}</div>
|
||||
)}
|
||||
<Button type="submit" disabled={isSubmitting} className="w-full">
|
||||
{isSubmitting ? 'Sending...' : 'Send Message'}
|
||||
</Button>
|
||||
</form>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div>
|
||||
<Label htmlFor="email" className="text-lg">Email *</Label>
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
required
|
||||
className="mt-2 p-4 text-lg"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="company" className="text-lg">Company</Label>
|
||||
<Input
|
||||
id="company"
|
||||
value={company}
|
||||
onChange={(e) => setCompany(e.target.value)}
|
||||
className="mt-2 p-4 text-lg"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="message" className="text-lg">Message *</Label>
|
||||
<Textarea
|
||||
id="message"
|
||||
rows={5}
|
||||
value={message}
|
||||
onChange={(e) => setMessage(e.target.value)}
|
||||
required
|
||||
className="mt-2 p-4 text-lg"
|
||||
/>
|
||||
</div>
|
||||
{submitError && (
|
||||
<div className="text-red-500 text-lg">{submitError}</div>
|
||||
)}
|
||||
<Button type="submit" disabled={isSubmitting} size="lg" className="w-full mt-4">
|
||||
{isSubmitting ? 'Sending...' : 'Send Message'}
|
||||
</Button>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Calendly Embed and Info */}
|
||||
<div>
|
||||
<Card className="mb-6">
|
||||
<CardHeader>
|
||||
<CardTitle>Book an Architecture Call</CardTitle>
|
||||
<CardDescription>
|
||||
30-minute consultation to discuss your infrastructure and potential savings.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="mb-4 text-muted-foreground">
|
||||
During this call, we'll review your current setup, identify cost-cutting opportunities, and outline a migration path.
|
||||
</p>
|
||||
{/* Calendly Inline Embed */}
|
||||
<div className="bg-muted rounded-lg p-4 text-center">
|
||||
<p className="text-muted-foreground">Calendly Embed Placeholder</p>
|
||||
<p className="text-sm mt-2">(This would be a real Calendly widget)</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="space-y-8">
|
||||
<div className="space-y-6 p-8 rounded-lg border border-muted bg-background">
|
||||
<h2 className="text-3xl font-bold">Book an Architecture Call</h2>
|
||||
<p className="text-xl text-muted-foreground">
|
||||
30-minute consultation to discuss your infrastructure and potential savings.
|
||||
</p>
|
||||
<p className="text-muted-foreground">
|
||||
During this call, we'll review your current setup, identify cost-cutting opportunities, and outline a migration path.
|
||||
</p>
|
||||
{/* Calendly Inline Embed */}
|
||||
<div className="bg-muted rounded-lg p-8 text-center">
|
||||
<p className="text-muted-foreground text-xl">Calendly Embed Placeholder</p>
|
||||
<p className="text-sm mt-4">(This would be a real Calendly widget)</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Security Inquiries</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="mb-4 text-muted-foreground">
|
||||
For sensitive security-related questions, please use our dedicated channel.
|
||||
</p>
|
||||
<Button asChild variant="outline" className="w-full">
|
||||
<a href="mailto:security@fortura.ai">
|
||||
security@fortura.ai
|
||||
<ExternalLinkIcon className="ml-2 size-4" />
|
||||
</a>
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="space-y-6 p-8 rounded-lg border border-muted bg-background">
|
||||
<h2 className="text-3xl font-bold">Security Inquiries</h2>
|
||||
<p className="text-muted-foreground">
|
||||
For sensitive security-related questions, please use our dedicated channel.
|
||||
</p>
|
||||
<Button asChild variant="outline" size="lg" className="w-full">
|
||||
<a href="mailto:security@fortura.ai">
|
||||
security@fortura.ai
|
||||
<ExternalLinkIcon className="ml-2 size-5" />
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</Section>
|
||||
</AnimatedSection>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,7 +32,7 @@ export default async function Page(props: {
|
||||
|
||||
return (
|
||||
<DocsPage
|
||||
toc={page.data.toc}
|
||||
toc={page.data.toc as any}
|
||||
full={page.data.full}
|
||||
footer={{ component: <Footer /> }}
|
||||
tableOfContent={{ style: 'clerk' }}
|
||||
|
||||
@ -17,7 +17,7 @@ export default function Layout({ children }: { children: ReactNode }) {
|
||||
type: 'icon',
|
||||
},
|
||||
]}
|
||||
tree={source.pageTree}
|
||||
tree={source.pageTree as any}
|
||||
themeSwitch={{
|
||||
component: <ThemeSwitcher />,
|
||||
}}
|
||||
|
||||
@ -6,25 +6,35 @@ import * as React from 'react';
|
||||
|
||||
import { Navbar } from '@workspace/ui/components/layout';
|
||||
import { Footer } from '@workspace/ui/components/layout';
|
||||
import { Container, Section } from '@workspace/ui/components/layout';
|
||||
import { Container } from '@workspace/ui/components/layout';
|
||||
import { AnimatedSection } from '@workspace/ui/components/animate-ui/section';
|
||||
import { FAQAccordion } from '@workspace/ui/components/interactive';
|
||||
|
||||
export default function FAQPage() {
|
||||
return (
|
||||
<div className="flex flex-col min-h-screen">
|
||||
<Navbar />
|
||||
<Section className="flex-grow">
|
||||
<Container>
|
||||
<div className="text-center mb-12">
|
||||
<h1 className="text-3xl md:text-4xl font-bold">Frequently Asked Questions</h1>
|
||||
<p className="mt-4 text-muted-foreground max-w-2xl mx-auto">
|
||||
Answers to common questions about Fortura's approach, process, and services.
|
||||
|
||||
{/* Hero Section */}
|
||||
<AnimatedSection className="border-b border-muted">
|
||||
<Container maxWidth="10xl">
|
||||
<div className="py-20 md:py-28">
|
||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-bold">Frequently Asked Questions</h1>
|
||||
<p className="mt-6 text-xl text-muted-foreground max-w-3xl">
|
||||
Answers to common questions about our approach, process, and services.
|
||||
</p>
|
||||
</div>
|
||||
</Container>
|
||||
</AnimatedSection>
|
||||
|
||||
{/* FAQ Section */}
|
||||
<AnimatedSection className="py-20 border-b border-muted">
|
||||
<Container maxWidth="10xl">
|
||||
<FAQAccordion />
|
||||
</Container>
|
||||
</Section>
|
||||
</AnimatedSection>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ import type { Metadata } from 'next';
|
||||
|
||||
import '@workspace/ui/globals.css';
|
||||
import { Providers } from './providers';
|
||||
import { ParallaxLayers } from '@workspace/ui/components/animate-ui/parallax-layers';
|
||||
// import { jsonLd } from '@/lib/json-ld'; // Commented out as it's Animate UI specific
|
||||
|
||||
// --- Update metadata for Fortura ---
|
||||
@ -46,6 +47,7 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
{/* Assuming RootProvider handles dark mode, we can nest our providers inside */}
|
||||
<RootProvider theme={{ defaultTheme: 'dark' }}>
|
||||
<Providers>
|
||||
<ParallaxLayers />
|
||||
{children}
|
||||
</Providers>
|
||||
</RootProvider>
|
||||
|
||||
@ -6,25 +6,112 @@ import * as React from 'react';
|
||||
|
||||
import { Navbar } from '@workspace/ui/components/layout';
|
||||
import { Footer } from '@workspace/ui/components/layout';
|
||||
import { Container, Section } from '@workspace/ui/components/layout';
|
||||
import { Container } from '@workspace/ui/components/layout';
|
||||
import { AnimatedSection } from '@workspace/ui/components/animate-ui/section';
|
||||
import { CostCalculator } from '@workspace/ui/components/interactive';
|
||||
import { MetricsKPI } from '@workspace/ui/components/content';
|
||||
import { CostReductionChart } from '@workspace/ui/components/content/cost-reduction-chart';
|
||||
import { Badge } from '@workspace/ui/components/content';
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from '@workspace/ui/components/ui';
|
||||
import Image from 'next/image';
|
||||
|
||||
export default function PricingPage() {
|
||||
return (
|
||||
<div className="flex flex-col min-h-screen">
|
||||
<Navbar />
|
||||
<Section className="flex-grow">
|
||||
<Container>
|
||||
<div className="text-center mb-12">
|
||||
<h1 className="text-3xl md:text-4xl font-bold">Pricing</h1>
|
||||
<p className="mt-4 text-muted-foreground max-w-2xl mx-auto">
|
||||
Calculate your Total Cost of Ownership (TCO) and see how much you can save by moving from renting to owning your infrastructure.
|
||||
|
||||
{/* Hero Section – soft gradient band distinct from homepage/solutions */}
|
||||
<AnimatedSection className="bg-gradient-to-b from-muted/30 to-transparent border-b border-muted">
|
||||
<Container maxWidth="10xl">
|
||||
<div className="relative py-20 md:py-28 text-center">
|
||||
{/* Optional decorative pricing art (drop /hero/pricing-hero-art.webp in public) */}
|
||||
<Image
|
||||
src="/hero/pricing-hero-art.webp"
|
||||
alt=""
|
||||
aria-hidden
|
||||
fill
|
||||
sizes="100vw"
|
||||
className="object-cover opacity-20 -z-10 rounded-sm"
|
||||
priority={false}
|
||||
/>
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<Badge variant="outline">TCO Focused</Badge>
|
||||
<Badge variant="outline">CapEx + OpEx</Badge>
|
||||
<Badge variant="outline">Transparent Assumptions</Badge>
|
||||
</div>
|
||||
<h1 className="mt-6 text-4xl md:text-5xl lg:text-6xl font-bold">
|
||||
Pricing & ROI
|
||||
</h1>
|
||||
<p className="mt-6 text-xl text-muted-foreground max-w-3xl mx-auto">
|
||||
Model your total cost of ownership and break-even timeline when
|
||||
you own the hardware and colocate it—no more cloud rent.
|
||||
</p>
|
||||
{/* KPI strip */}
|
||||
<div className="mt-12 grid grid-cols-3 gap-6 max-w-xl mx-auto">
|
||||
<MetricsKPI value="12mo" label="typical break-even" />
|
||||
<MetricsKPI value="65%" label="avg. monthly savings" />
|
||||
<MetricsKPI value="$350/mo" label="after year one" />
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</AnimatedSection>
|
||||
|
||||
{/* Cost Calculator – bordered panel with subtle header */}
|
||||
<AnimatedSection className="py-20 border-b border-muted">
|
||||
<Container maxWidth="10xl">
|
||||
<Card className="border-muted">
|
||||
<CardHeader className="border-b border-muted bg-card/50">
|
||||
<CardTitle className="text-2xl">Your Estimate</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-8">
|
||||
<CostCalculator />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Container>
|
||||
</AnimatedSection>
|
||||
|
||||
{/* Cost Curve – visual summary to complement calculator */}
|
||||
<AnimatedSection className="py-20 bg-muted/30 border-y border-muted">
|
||||
<Container maxWidth="10xl">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center">
|
||||
<div>
|
||||
<h2 className="text-3xl font-bold">Cost curve over time</h2>
|
||||
<p className="mt-4 text-muted-foreground max-w-prose">
|
||||
Upfront spend pays for itself quickly. After break-even, ongoing
|
||||
costs are mostly power, space, and amortized hardware—all
|
||||
predictable and under your control.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<CostReductionChart />
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</AnimatedSection>
|
||||
|
||||
{/* Notes & Assumptions – concise context for numbers */}
|
||||
<AnimatedSection className="py-16 border-b border-muted">
|
||||
<Container maxWidth="10xl">
|
||||
<div className="max-w-3xl mx-auto text-sm text-muted-foreground space-y-2">
|
||||
<p>
|
||||
Assumptions: commodity servers, 36–48 month amortization, market
|
||||
colo rates, and conservative utilization.
|
||||
</p>
|
||||
<p>
|
||||
Exact results depend on workload profile, redundancy requirements,
|
||||
and growth assumptions. We’ll tailor the model during your
|
||||
architecture call.
|
||||
</p>
|
||||
</div>
|
||||
<CostCalculator />
|
||||
</Container>
|
||||
</Section>
|
||||
</AnimatedSection>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,99 +3,239 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import { MoveUpRightIcon } from 'lucide-react';
|
||||
|
||||
import { Navbar } from '@workspace/ui/components/layout';
|
||||
import { Footer } from '@workspace/ui/components/layout';
|
||||
import { Container, Section, Grid } from '@workspace/ui/components/layout';
|
||||
import { Container } from '@workspace/ui/components/layout';
|
||||
import { AnimatedSection } from '@workspace/ui/components/animate-ui/section';
|
||||
import { Steps } from '@workspace/ui/components/content';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@workspace/ui/components/ui';
|
||||
import { Button } from '@workspace/ui/components/ui';
|
||||
import { FinalCTASection } from '@workspace/ui/components/content';
|
||||
import { Badge } from '@workspace/ui/components/content';
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from '@workspace/ui/components/ui';
|
||||
import Image from 'next/image';
|
||||
|
||||
// Mock data for Process Steps (full 6 steps)
|
||||
const PROCESS_STEPS = [
|
||||
{
|
||||
title: "Discovery",
|
||||
description: "We deep-dive into your current infrastructure, workloads, and pain points.",
|
||||
youDo: "Share access to cloud billing, architecture docs, and team interviews.",
|
||||
weDo: "Analyze costs, identify waste, and map technical dependencies."
|
||||
title: 'Discovery',
|
||||
description:
|
||||
'We deep-dive into your current infrastructure, workloads, and pain points.',
|
||||
youDo:
|
||||
'Share access to cloud billing, architecture docs, and team interviews.',
|
||||
weDo: 'Analyze costs, identify waste, and map technical dependencies.',
|
||||
},
|
||||
{
|
||||
title: "Shadow & Map",
|
||||
description: "We observe how your teams actually work and map desire paths.",
|
||||
youDo: "Participate in workflow observations and workshops.",
|
||||
weDo: "Document real workflows, identify automation opportunities, and draft the future state."
|
||||
title: 'Shadow & Map',
|
||||
description:
|
||||
'We observe how your teams actually work and map desire paths.',
|
||||
youDo: 'Participate in workflow observations and workshops.',
|
||||
weDo: 'Document real workflows, identify automation opportunities, and draft the future state.',
|
||||
},
|
||||
{
|
||||
title: "Architect",
|
||||
description: "We design a tailored, owned infrastructure solution.",
|
||||
youDo: "Review and approve the proposed architecture and migration plan.",
|
||||
weDo: "Specify hardware, select software stack, and design for performance and cost."
|
||||
title: 'Architect',
|
||||
description: 'We design a tailored, owned infrastructure solution.',
|
||||
youDo: 'Review and approve the proposed architecture and migration plan.',
|
||||
weDo: 'Specify hardware, select software stack, and design for performance and cost.',
|
||||
},
|
||||
{
|
||||
title: "Pilot",
|
||||
description: "We build and test a critical component in the new environment.",
|
||||
youDo: "Provide feedback on performance, usability, and integration.",
|
||||
weDo: "Deploy, test, and iterate on the pilot component with your team."
|
||||
title: 'Pilot',
|
||||
description:
|
||||
'We build and test a critical component in the new environment.',
|
||||
youDo: 'Provide feedback on performance, usability, and integration.',
|
||||
weDo: 'Deploy, test, and iterate on the pilot component with your team.',
|
||||
},
|
||||
{
|
||||
title: "Migrate",
|
||||
description: "We execute the planned migration with zero downtime.",
|
||||
youDo: "Validate functionality and performance post-migration.",
|
||||
weDo: "Cutover workloads, decommission old systems, and optimize the new stack."
|
||||
title: 'Migrate',
|
||||
description: 'We execute the planned migration with zero downtime.',
|
||||
youDo: 'Validate functionality and performance post-migration.',
|
||||
weDo: 'Cutover workloads, decommission old systems, and optimize the new stack.',
|
||||
},
|
||||
{
|
||||
title: "Operate",
|
||||
description: "We manage the infrastructure, you focus on your core business.",
|
||||
youDo: "Use the system and provide ongoing feedback for improvements.",
|
||||
weDo: "Monitor, maintain, upgrade, and support the infrastructure 24/7."
|
||||
}
|
||||
title: 'Operate',
|
||||
description:
|
||||
'We manage the infrastructure, you focus on your core business.',
|
||||
youDo: 'Use the system and provide ongoing feedback for improvements.',
|
||||
weDo: 'Monitor, maintain, upgrade, and support the infrastructure 24/7.',
|
||||
},
|
||||
];
|
||||
|
||||
export default function ProcessPage() {
|
||||
return (
|
||||
<div className="flex flex-col min-h-screen">
|
||||
<Navbar />
|
||||
<Section className="flex-grow">
|
||||
<Container>
|
||||
<div className="text-center mb-12">
|
||||
<h1 className="text-3xl md:text-4xl font-bold">Our Process</h1>
|
||||
<p className="mt-4 text-muted-foreground max-w-2xl mx-auto">
|
||||
A proven 6-step approach to migrate from cloud rent to owned infrastructure with minimal retraining and maximum savings.
|
||||
|
||||
{/* Hero Section – distinct gradient band to differentiate from Solutions */}
|
||||
<AnimatedSection className="bg-gradient-to-b from-muted/30 to-transparent border-b border-muted">
|
||||
<Container maxWidth="10xl">
|
||||
<div className="py-20 md:py-28">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Badge variant="outline">Desire Paths</Badge>
|
||||
<Badge variant="outline">Zero-Downtime Migration</Badge>
|
||||
<Badge variant="outline">SRE-Managed</Badge>
|
||||
</div>
|
||||
<h1 className="mt-6 text-4xl md:text-5xl lg:text-6xl font-bold">
|
||||
Our Process
|
||||
</h1>
|
||||
<p className="mt-6 text-xl text-muted-foreground max-w-3xl">
|
||||
A six-step path from audit to steady-state operations—minimizing
|
||||
retraining, maximizing savings.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Steps steps={PROCESS_STEPS} />
|
||||
|
||||
{/* Embedding Card */}
|
||||
<Section background="muted" className="mt-16">
|
||||
<Container>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center">
|
||||
<span>Embedding</span>
|
||||
<MoveUpRightIcon className="ml-2 size-5 text-primary" />
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<CardDescription>
|
||||
We sit with your teams, map real workflows, then automate the boring.
|
||||
</CardDescription>
|
||||
<p className="mt-2 text-sm">
|
||||
Our "Desire Paths" methodology ensures the system we build fits how your team actually works, not how a generic cloud platform prescribes. This minimizes retraining and maximizes adoption.
|
||||
</p>
|
||||
<div className="mt-4">
|
||||
<Button variant="outline" asChild>
|
||||
<a href="/contact">Book Discovery Call</a>
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Container>
|
||||
</Section>
|
||||
</Container>
|
||||
</Section>
|
||||
</AnimatedSection>
|
||||
|
||||
{/* Process Diagram (optional image placeholder) */}
|
||||
<AnimatedSection className="py-16 border-b border-muted">
|
||||
<Container maxWidth="10xl">
|
||||
<Card className="border-muted">
|
||||
<CardHeader>
|
||||
<CardTitle>Process diagram</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="relative w-full h-64 md:h-80 lg:h-96">
|
||||
<Image
|
||||
src="/process/process-diagram.webp"
|
||||
alt="Six-step migration process diagram"
|
||||
fill
|
||||
sizes="100vw"
|
||||
className="object-cover rounded-sm"
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Container>
|
||||
</AnimatedSection>
|
||||
|
||||
{/* Process Steps with phase legend */}
|
||||
<AnimatedSection className="py-20 border-b border-muted">
|
||||
<Container maxWidth="10xl">
|
||||
{/* Phase legend */}
|
||||
<div className="mb-10 grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div className="rounded-sm border border-muted p-4 bg-background/50">
|
||||
<div className="text-xs tracking-wide text-muted-foreground">
|
||||
Phase 1
|
||||
</div>
|
||||
<div className="mt-1 font-semibold">Discover & Map</div>
|
||||
<div className="mt-2 text-sm text-muted-foreground">
|
||||
Steps 1–2
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-sm border border-muted p-4 bg-background/50">
|
||||
<div className="text-xs tracking-wide text-muted-foreground">
|
||||
Phase 2
|
||||
</div>
|
||||
<div className="mt-1 font-semibold">Design & Pilot</div>
|
||||
<div className="mt-2 text-sm text-muted-foreground">
|
||||
Steps 3–4
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-sm border border-muted p-4 bg-background/50">
|
||||
<div className="text-xs tracking-wide text-muted-foreground">
|
||||
Phase 3
|
||||
</div>
|
||||
<div className="mt-1 font-semibold">Migrate & Operate</div>
|
||||
<div className="mt-2 text-sm text-muted-foreground">
|
||||
Steps 5–6
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Steps steps={PROCESS_STEPS} />
|
||||
</Container>
|
||||
</AnimatedSection>
|
||||
|
||||
{/* Deliverables */}
|
||||
<AnimatedSection className="py-20 border-b border-muted">
|
||||
<Container maxWidth="10xl">
|
||||
<h2 className="text-3xl font-bold mb-8">Key Deliverables</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Architecture Blueprint</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="text-muted-foreground">
|
||||
Hardware spec, network plan, security model, and service layout
|
||||
mapped to your workloads.
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Migration Plan</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="text-muted-foreground">
|
||||
Sequenced cutover with rollback points, data move playbooks, and
|
||||
testing gates.
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Runbook & SLOs</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="text-muted-foreground">
|
||||
Operability docs, monitoring dashboards, on-call rotations, and
|
||||
measurable service objectives.
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</Container>
|
||||
</AnimatedSection>
|
||||
|
||||
{/* Engagement Model */}
|
||||
<AnimatedSection className="py-20 bg-muted/30 border-y border-muted">
|
||||
<Container maxWidth="10xl">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<Card className="border-muted">
|
||||
<CardHeader>
|
||||
<CardTitle>Embedded With You</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="text-muted-foreground space-y-2">
|
||||
<p>
|
||||
Weekly working sessions, async updates, and direct
|
||||
engineer-to-engineer channels.
|
||||
</p>
|
||||
<p>
|
||||
We map desire paths, automate toil, and keep your stack
|
||||
familiar.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="border-muted">
|
||||
<CardHeader>
|
||||
<CardTitle>Support & Ops</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="text-muted-foreground space-y-2">
|
||||
<p>
|
||||
24/7 monitoring, upgrades, and incident response handled by
|
||||
our SREs.
|
||||
</p>
|
||||
<p>
|
||||
You own the hardware and data; we keep it healthy and fast.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</Container>
|
||||
</AnimatedSection>
|
||||
|
||||
{/* Final CTA */}
|
||||
<AnimatedSection className="py-20 border-b border-muted">
|
||||
<Container maxWidth="10xl">
|
||||
<FinalCTASection
|
||||
title="Ready to kick off discovery?"
|
||||
description="We’ll audit your spend, map your workflows, and design the fastest path to break-even."
|
||||
primaryCtaText="Book Discovery Call"
|
||||
primaryCtaHref="/contact"
|
||||
secondaryCtaText="Download Blueprint"
|
||||
secondaryCtaHref="/download-blueprint"
|
||||
/>
|
||||
</Container>
|
||||
</AnimatedSection>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,143 +1,170 @@
|
||||
// app/solutions/[industry]/page.tsx
|
||||
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import Link from 'next/link';
|
||||
import { notFound } from 'next/navigation';
|
||||
import { MoveUpRightIcon, DownloadIcon } from 'lucide-react';
|
||||
|
||||
import { Navbar } from '@workspace/ui/components/layout';
|
||||
import { Footer } from '@workspace/ui/components/layout';
|
||||
import { Container, Section, Grid } from '@workspace/ui/components/layout';
|
||||
import { FeatureGrid } from '@workspace/ui/components/content';
|
||||
import { Callout } from '@workspace/ui/components/content';
|
||||
import { Badge } from '@workspace/ui/components/content';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@workspace/ui/components/ui';
|
||||
import { FeatureGrid } from '@workspace/ui/components/content/feature-grid';
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from '@workspace/ui/components/ui';
|
||||
import { Button } from '@workspace/ui/components/ui';
|
||||
import { PlaceholderIcon } from '@workspace/ui/components/icons'; // Using placeholder for now
|
||||
import Image from 'next/image';
|
||||
|
||||
// Types and mock data for industries and their specific details
|
||||
type ProposedItem = { icon: React.ReactNode; title: string; description: string };
|
||||
type Outcome = { metric: string; description: string };
|
||||
type Industry = {
|
||||
name: string;
|
||||
title: string;
|
||||
description: string;
|
||||
painPoints: string[];
|
||||
proposedStack: ProposedItem[];
|
||||
migrationPlan: string[];
|
||||
opsModel: string;
|
||||
outcomes: Outcome[];
|
||||
};
|
||||
|
||||
// Mock data for industries and their specific details
|
||||
// In a real app, this would likely come from a CMS or database
|
||||
const INDUSTRY_DATA: Record<string, any> = {
|
||||
const INDUSTRY_DATA: Record<string, Industry> = {
|
||||
technology: {
|
||||
name: "Technology",
|
||||
title: "Cloud Alternatives for Technology Companies",
|
||||
description: "From SaaS startups to enterprise software, we help you scale efficiently without the cloud tax.",
|
||||
name: 'Technology',
|
||||
title: 'Cloud Alternatives for Technology Companies',
|
||||
description:
|
||||
'From SaaS startups to enterprise software, we help you scale efficiently without the cloud tax.',
|
||||
painPoints: [
|
||||
"High and unpredictable cloud bills that scale with user growth.",
|
||||
"Vendor lock-in making it hard to switch providers or adopt hybrid models.",
|
||||
"Complexity and overhead of managing cloud-native architectures.",
|
||||
"Need for high performance and low latency for user-facing applications."
|
||||
'High and unpredictable cloud bills that scale with user growth.',
|
||||
'Vendor lock-in making it hard to switch providers or adopt hybrid models.',
|
||||
'Complexity and overhead of managing cloud-native architectures.',
|
||||
'Need for high performance and low latency for user-facing applications.',
|
||||
],
|
||||
proposedStack: [
|
||||
{
|
||||
icon: <PlaceholderIcon className="size-5" />,
|
||||
title: "Kubernetes (K3s/RKE2)",
|
||||
description: "Lightweight, certified Kubernetes for efficient container orchestration."
|
||||
title: 'Kubernetes (K3s/RKE2)',
|
||||
description:
|
||||
'Lightweight, certified Kubernetes for efficient container orchestration.',
|
||||
},
|
||||
{
|
||||
icon: <PlaceholderIcon className="size-5" />,
|
||||
title: "PostgreSQL/MySQL",
|
||||
description: "Self-hosted, high-availability databases with point-in-time recovery."
|
||||
title: 'PostgreSQL/MySQL',
|
||||
description:
|
||||
'Self-hosted, high-availability databases with point-in-time recovery.',
|
||||
},
|
||||
{
|
||||
icon: <PlaceholderIcon className="size-5" />,
|
||||
title: "MinIO",
|
||||
description: "S3-compatible object storage for unstructured data."
|
||||
title: 'MinIO',
|
||||
description: 'S3-compatible object storage for unstructured data.',
|
||||
},
|
||||
{
|
||||
icon: <PlaceholderIcon className="size-5" />,
|
||||
title: "Nextcloud",
|
||||
description: "Private file sync, sharing, and collaboration platform."
|
||||
}
|
||||
title: 'Nextcloud',
|
||||
description: 'Private file sync, sharing, and collaboration platform.',
|
||||
},
|
||||
],
|
||||
migrationPlan: [
|
||||
"Audit current cloud spend and architecture.",
|
||||
"Design a colocated Kubernetes cluster tailored to your workloads.",
|
||||
"Migrate stateful services with zero downtime using blue-green deployments.",
|
||||
"Implement CI/CD pipelines for automated testing and deployment.",
|
||||
"Optimize for cost and performance post-migration."
|
||||
'Audit current cloud spend and architecture.',
|
||||
'Design a colocated Kubernetes cluster tailored to your workloads.',
|
||||
'Migrate stateful services with zero downtime using blue-green deployments.',
|
||||
'Implement CI/CD pipelines for automated testing and deployment.',
|
||||
'Optimize for cost and performance post-migration.',
|
||||
],
|
||||
opsModel: "We provide 24/7 monitoring, managed upgrades, and direct access to our SREs. You retain control over application configuration and scaling decisions.",
|
||||
opsModel:
|
||||
'We provide 24/7 monitoring, managed upgrades, and direct access to our SREs. You retain control over application configuration and scaling decisions.',
|
||||
outcomes: [
|
||||
{
|
||||
metric: "60%",
|
||||
description: "Reduction in monthly infrastructure costs."
|
||||
metric: '60%',
|
||||
description: 'Reduction in monthly infrastructure costs.',
|
||||
},
|
||||
{
|
||||
metric: "Sub-10ms",
|
||||
description: "Improved database query latency."
|
||||
metric: 'Sub-10ms',
|
||||
description: 'Improved database query latency.',
|
||||
},
|
||||
{
|
||||
metric: "Zero",
|
||||
description: "Vendor lock-in with portable, open-source tools."
|
||||
}
|
||||
]
|
||||
metric: 'Zero',
|
||||
description: 'Vendor lock-in with portable, open-source tools.',
|
||||
},
|
||||
],
|
||||
},
|
||||
legal: {
|
||||
name: "Legal",
|
||||
title: "Secure Infrastructure for Legal Firms",
|
||||
description: "Secure, private document management and collaboration environments with strict access controls.",
|
||||
name: 'Legal',
|
||||
title: 'Secure Infrastructure for Legal Firms',
|
||||
description:
|
||||
'Secure, private document management and collaboration environments with strict access controls.',
|
||||
painPoints: [
|
||||
"Stringent data privacy regulations (GDPR, CCPA) requiring data residency and control.",
|
||||
"High cost of secure cloud storage and collaboration tools.",
|
||||
"Risk of data breaches from third-party SaaS providers.",
|
||||
"Need for comprehensive audit trails and access logging."
|
||||
'Stringent data privacy regulations (GDPR, CCPA) requiring data residency and control.',
|
||||
'High cost of secure cloud storage and collaboration tools.',
|
||||
'Risk of data breaches from third-party SaaS providers.',
|
||||
'Need for comprehensive audit trails and access logging.',
|
||||
],
|
||||
proposedStack: [
|
||||
{
|
||||
icon: <PlaceholderIcon className="size-5" />,
|
||||
title: "Nextcloud",
|
||||
description: "Enterprise-grade file sync, sharing, and collaboration with full audit capabilities."
|
||||
title: 'Nextcloud',
|
||||
description:
|
||||
'Enterprise-grade file sync, sharing, and collaboration with full audit capabilities.',
|
||||
},
|
||||
{
|
||||
icon: <PlaceholderIcon className="size-5" />,
|
||||
title: "OnlyOffice",
|
||||
description: "Private, self-hosted office suite for document editing and collaboration."
|
||||
title: 'OnlyOffice',
|
||||
description:
|
||||
'Private, self-hosted office suite for document editing and collaboration.',
|
||||
},
|
||||
{
|
||||
icon: <PlaceholderIcon className="size-5" />,
|
||||
title: "Keycloak",
|
||||
description: "Centralized identity management with SAML/OIDC integration for existing directories."
|
||||
title: 'Keycloak',
|
||||
description:
|
||||
'Centralized identity management with SAML/OIDC integration for existing directories.',
|
||||
},
|
||||
{
|
||||
icon: <PlaceholderIcon className="size-5" />,
|
||||
title: "OpenSearch",
|
||||
description: "Self-hosted search and analytics engine for document discovery."
|
||||
}
|
||||
title: 'OpenSearch',
|
||||
description:
|
||||
'Self-hosted search and analytics engine for document discovery.',
|
||||
},
|
||||
],
|
||||
migrationPlan: [
|
||||
"Inventory and classify all documents and data flows.",
|
||||
"Implement a secure, private Nextcloud instance with custom compliance policies.",
|
||||
"Migrate existing document repositories with full version history.",
|
||||
"Integrate with existing case management and time-tracking systems.",
|
||||
"Train staff on new tools and security protocols."
|
||||
'Inventory and classify all documents and data flows.',
|
||||
'Implement a secure, private Nextcloud instance with custom compliance policies.',
|
||||
'Migrate existing document repositories with full version history.',
|
||||
'Integrate with existing case management and time-tracking systems.',
|
||||
'Train staff on new tools and security protocols.',
|
||||
],
|
||||
opsModel: "We manage the underlying infrastructure, security patches, and backups. Your team manages user accounts, permissions, and document workflows.",
|
||||
opsModel:
|
||||
'We manage the underlying infrastructure, security patches, and backups. Your team manages user accounts, permissions, and document workflows.',
|
||||
outcomes: [
|
||||
{
|
||||
metric: "100%",
|
||||
description: "Data sovereignty and compliance with legal regulations."
|
||||
metric: '100%',
|
||||
description: 'Data sovereignty and compliance with legal regulations.',
|
||||
},
|
||||
{
|
||||
metric: "$50k/year",
|
||||
description: "Savings vs. proprietary legal tech SaaS bundles."
|
||||
metric: '$50k/year',
|
||||
description: 'Savings vs. proprietary legal tech SaaS bundles.',
|
||||
},
|
||||
{
|
||||
metric: "Zero",
|
||||
description: "Third-party access to sensitive client data."
|
||||
}
|
||||
]
|
||||
metric: 'Zero',
|
||||
description: 'Third-party access to sensitive client data.',
|
||||
},
|
||||
],
|
||||
},
|
||||
// Add more industries here...
|
||||
};
|
||||
|
||||
interface SolutionPageProps {
|
||||
params: { industry: string };
|
||||
}
|
||||
|
||||
export default function SolutionPage({ params }: SolutionPageProps) {
|
||||
const { industry } = params;
|
||||
export default async function SolutionPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ industry: string }>;
|
||||
}) {
|
||||
const { industry } = await params;
|
||||
const solution = INDUSTRY_DATA[industry];
|
||||
|
||||
if (!solution) {
|
||||
@ -149,19 +176,33 @@ export default function SolutionPage({ params }: SolutionPageProps) {
|
||||
<Navbar />
|
||||
<Section className="flex-grow">
|
||||
<Container>
|
||||
{/* Optional industry banner (drop /solutions/[industry]-hero.webp in public) */}
|
||||
<div className="mb-8 rounded-sm border overflow-hidden bg-muted">
|
||||
<div className="relative h-48 md:h-64 lg:h-72">
|
||||
<Image
|
||||
src={`/solutions/${industry}-hero.webp`}
|
||||
alt=""
|
||||
aria-hidden
|
||||
fill
|
||||
sizes="100vw"
|
||||
className="object-cover"
|
||||
priority={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-8">
|
||||
<Button variant="link" asChild className="px-0">
|
||||
<a href="/solutions">← All Solutions</a>
|
||||
<Link href="/solutions">← All Solutions</Link>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="text-center mb-12">
|
||||
<h1 className="text-3xl md:text-4xl font-bold">{solution.title}</h1>
|
||||
<p className="mt-4 text-muted-foreground max-w-2xl mx-auto">
|
||||
{solution.description}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
<Section>
|
||||
<Container>
|
||||
<h2 className="text-2xl font-bold mb-6">Key Pain Points</h2>
|
||||
@ -175,14 +216,17 @@ export default function SolutionPage({ params }: SolutionPageProps) {
|
||||
</ul>
|
||||
</Container>
|
||||
</Section>
|
||||
|
||||
|
||||
<Section background="muted">
|
||||
<Container>
|
||||
<h2 className="text-2xl font-bold mb-6">Proposed Stack</h2>
|
||||
<FeatureGrid features={solution.proposedStack} columns={{ initial: 1, sm: 2 }} />
|
||||
<FeatureGrid
|
||||
features={solution.proposedStack}
|
||||
columns={{ initial: 1, sm: 2 }}
|
||||
/>
|
||||
</Container>
|
||||
</Section>
|
||||
|
||||
|
||||
<Section>
|
||||
<Container>
|
||||
<h2 className="text-2xl font-bold mb-6">Migration Plan</h2>
|
||||
@ -198,39 +242,47 @@ export default function SolutionPage({ params }: SolutionPageProps) {
|
||||
</ol>
|
||||
</Container>
|
||||
</Section>
|
||||
|
||||
|
||||
<Section background="muted">
|
||||
<Container>
|
||||
<h2 className="text-2xl font-bold mb-6">Operations Model</h2>
|
||||
<p className="text-muted-foreground">
|
||||
{solution.opsModel}
|
||||
</p>
|
||||
<p className="text-muted-foreground">{solution.opsModel}</p>
|
||||
</Container>
|
||||
</Section>
|
||||
|
||||
|
||||
<Section>
|
||||
<Container>
|
||||
<h2 className="text-2xl font-bold mb-6">Expected Outcomes</h2>
|
||||
<Grid columns={{ initial: 1, sm: 3 }} gap="6">
|
||||
{solution.outcomes.map((outcome: { metric: string; description: string }, index: number) => (
|
||||
<Card key={index}>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-3xl font-bold">{outcome.metric}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<CardDescription>{outcome.description}</CardDescription>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
{solution.outcomes.map(
|
||||
(
|
||||
outcome: { metric: string; description: string },
|
||||
index: number,
|
||||
) => (
|
||||
<Card key={index}>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-3xl font-bold">
|
||||
{outcome.metric}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<CardDescription>{outcome.description}</CardDescription>
|
||||
</CardContent>
|
||||
</Card>
|
||||
),
|
||||
)}
|
||||
</Grid>
|
||||
</Container>
|
||||
</Section>
|
||||
|
||||
|
||||
<Section background="muted" className="text-center">
|
||||
<Container>
|
||||
<h2 className="text-2xl font-bold mb-4">Ready to transform your infrastructure?</h2>
|
||||
<h2 className="text-2xl font-bold mb-4">
|
||||
Ready to transform your infrastructure?
|
||||
</h2>
|
||||
<p className="mb-6 text-muted-foreground max-w-2xl mx-auto">
|
||||
Download our industry-specific blueprint or book a free architecture call.
|
||||
Download our industry-specific blueprint or book a free
|
||||
architecture call.
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-3 justify-center">
|
||||
<Button asChild>
|
||||
@ -253,4 +305,4 @@ export default function SolutionPage({ params }: SolutionPageProps) {
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,77 +3,145 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import { ExternalLinkIcon } from 'lucide-react';
|
||||
|
||||
import { Navbar } from '@workspace/ui/components/layout';
|
||||
import { Footer } from '@workspace/ui/components/layout';
|
||||
import { Container, Section, Grid } from '@workspace/ui/components/layout';
|
||||
import { FeatureGrid } from '@workspace/ui/components/content';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@workspace/ui/components/ui';
|
||||
import { Container } from '@workspace/ui/components/layout';
|
||||
import { AnimatedSection } from '@workspace/ui/components/animate-ui/section';
|
||||
import { Button } from '@workspace/ui/components/ui';
|
||||
import { FinalCTASection } from '@workspace/ui/components/content';
|
||||
import { PlaceholderIcon } from '@workspace/ui/components/icons'; // Using placeholder for now
|
||||
import Image from 'next/image';
|
||||
|
||||
// Mock data for Industries
|
||||
const INDUSTRIES = [
|
||||
{
|
||||
icon: <PlaceholderIcon className="size-6" />,
|
||||
title: "Technology",
|
||||
description: "From SaaS startups to enterprise software, we help you scale efficiently without the cloud tax."
|
||||
title: 'Technology',
|
||||
description:
|
||||
'From SaaS startups to enterprise software, we help you scale efficiently without the cloud tax.',
|
||||
},
|
||||
{
|
||||
icon: <PlaceholderIcon className="size-6" />,
|
||||
title: "Legal",
|
||||
description: "Secure, private document management and collaboration environments with strict access controls."
|
||||
title: 'Legal',
|
||||
description:
|
||||
'Secure, private document management and collaboration environments with strict access controls.',
|
||||
},
|
||||
{
|
||||
icon: <PlaceholderIcon className="size-6" />,
|
||||
title: "Medical",
|
||||
description: "HIPAA-compliant infrastructure for storing, processing, and analyzing sensitive patient data."
|
||||
title: 'Medical',
|
||||
description:
|
||||
'HIPAA-compliant infrastructure for storing, processing, and analyzing sensitive patient data.',
|
||||
},
|
||||
{
|
||||
icon: <PlaceholderIcon className="size-6" />,
|
||||
title: "Media",
|
||||
description: "High-bandwidth, low-latency solutions for content creation, storage, and distribution."
|
||||
title: 'Media',
|
||||
description:
|
||||
'High-bandwidth, low-latency solutions for content creation, storage, and distribution.',
|
||||
},
|
||||
{
|
||||
icon: <PlaceholderIcon className="size-6" />,
|
||||
title: "Research",
|
||||
description: "Powerful compute and storage for data-intensive scientific research and simulations."
|
||||
}
|
||||
title: 'Research',
|
||||
description:
|
||||
'Powerful compute and storage for data-intensive scientific research and simulations.',
|
||||
},
|
||||
];
|
||||
|
||||
export default function SolutionsPage() {
|
||||
return (
|
||||
<div className="flex flex-col min-h-screen">
|
||||
<Navbar />
|
||||
<Section className="flex-grow">
|
||||
<Container>
|
||||
<div className="text-center mb-12">
|
||||
<h1 className="text-3xl md:text-4xl font-bold">Solutions by Industry</h1>
|
||||
<p className="mt-4 text-muted-foreground max-w-2xl mx-auto">
|
||||
We tailor our approach to the unique challenges and requirements of your vertical.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<FeatureGrid features={INDUSTRIES} columns={{ initial: 1, sm: 2, md: 3 }} />
|
||||
|
||||
<Section background="muted" className="text-center mt-16">
|
||||
<Container>
|
||||
<h2 className="text-2xl font-bold mb-4">Don't see your industry?</h2>
|
||||
<p className="mb-6 text-muted-foreground max-w-2xl mx-auto">
|
||||
Our core principles apply broadly. Contact us to discuss how we can adapt our approach for your specific use case.
|
||||
|
||||
{/* Hero Section: banded with grid background to mirror homepage */}
|
||||
<AnimatedSection className="bg-grid-dark border-b border-muted">
|
||||
<Container maxWidth="10xl">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-16 items-center py-20 md:py-28">
|
||||
<div className="text-left">
|
||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-bold leading-tight">
|
||||
Solutions by Industry
|
||||
</h1>
|
||||
<p className="mt-6 text-lg md:text-xl text-muted-foreground max-w-xl">
|
||||
Practical, high-impact stacks mapped to the realities of your
|
||||
sector.
|
||||
</p>
|
||||
<Button asChild>
|
||||
<a href="/contact">
|
||||
Contact Us
|
||||
<ExternalLinkIcon className="ml-2 size-4" />
|
||||
</a>
|
||||
</Button>
|
||||
</Container>
|
||||
</Section>
|
||||
<div className="mt-10 flex flex-col sm:flex-row gap-4">
|
||||
<Button size="lg" variant="default" asChild>
|
||||
<a href="#industries">Explore Industries</a>
|
||||
</Button>
|
||||
<Button size="lg" variant="outline" asChild>
|
||||
<a href="/contact">Talk To Us</a>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{/* Right visual panel for rhythm with homepage hero */}
|
||||
<div className="relative h-[220px] md:h-[300px] lg:h-[360px]">
|
||||
<div className="absolute inset-0 rounded-sm border bg-card/60 backdrop-blur-sm" />
|
||||
{/* Optional decorative solutions art (drop /hero/solutions-hero-art.webp in public) */}
|
||||
<Image
|
||||
src="/hero/solutions-hero-art.webp"
|
||||
alt=""
|
||||
aria-hidden
|
||||
fill
|
||||
sizes="(max-width: 768px) 100vw, 50vw"
|
||||
className="object-cover rounded-sm opacity-35"
|
||||
priority={false}
|
||||
/>
|
||||
<div className="absolute inset-0 bg-stripes-dark rounded-sm opacity-50" />
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</Section>
|
||||
</AnimatedSection>
|
||||
|
||||
{/* Industries */}
|
||||
<AnimatedSection id="industries" className="py-20 border-b border-muted">
|
||||
<Container maxWidth="10xl">
|
||||
<div className="space-y-10 max-w-5xl">
|
||||
{INDUSTRIES.map((item, index) => {
|
||||
const href = `/solutions/${item.title.toLowerCase()}`;
|
||||
return (
|
||||
<a
|
||||
key={item.title}
|
||||
href={href}
|
||||
className="group flex items-start gap-8 border-b border-muted pb-10 transition-all duration-300 hover:border-foreground/70 last:border-0"
|
||||
>
|
||||
<div className="text-4xl font-light text-muted-foreground mt-1">
|
||||
{index < 9 ? `0${index + 1}` : index + 1}
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="flex items-baseline gap-4">
|
||||
<h2 className="text-4xl md:text-5xl font-bold transition-colors duration-300 group-hover:text-foreground/90">
|
||||
{item.title}
|
||||
</h2>
|
||||
<span className="text-primary text-sm tracking-wide opacity-80 group-hover:opacity-100">
|
||||
Explore →
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-lg md:text-xl text-muted-foreground mt-4 max-w-3xl transition-colors duration-300 group-hover:text-foreground/80">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Container>
|
||||
</AnimatedSection>
|
||||
|
||||
{/* CTA Section: reuse shared FinalCTASection for consistency */}
|
||||
<AnimatedSection className="py-20 border-b border-muted">
|
||||
<Container maxWidth="10xl">
|
||||
<FinalCTASection
|
||||
title="Don't see your industry?"
|
||||
description="Same principles apply broadly. We’ll map your stack to your operations and constraints."
|
||||
primaryCtaText="Talk To Us"
|
||||
primaryCtaHref="/contact"
|
||||
secondaryCtaText="Download Blueprint"
|
||||
secondaryCtaHref="/download-blueprint"
|
||||
/>
|
||||
</Container>
|
||||
</AnimatedSection>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,11 +7,10 @@ import { ExternalLinkIcon } from 'lucide-react';
|
||||
|
||||
import { Navbar } from '@workspace/ui/components/layout';
|
||||
import { Footer } from '@workspace/ui/components/layout';
|
||||
import { Container, Section, Grid } from '@workspace/ui/components/layout';
|
||||
import { FeatureGrid } from '@workspace/ui/components/content';
|
||||
import { Container } from '@workspace/ui/components/layout';
|
||||
import { AnimatedSection } from '@workspace/ui/components/animate-ui/section';
|
||||
import { Callout } from '@workspace/ui/components/content';
|
||||
import { Badge } from '@workspace/ui/components/content';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@workspace/ui/components/ui';
|
||||
import { Button } from '@workspace/ui/components/ui';
|
||||
import { PlaceholderIcon } from '@workspace/ui/components/icons'; // Using placeholder for now
|
||||
|
||||
@ -106,66 +105,120 @@ export default function StackPage() {
|
||||
return (
|
||||
<div className="flex flex-col min-h-screen">
|
||||
<Navbar />
|
||||
<Section className="flex-grow">
|
||||
<Container>
|
||||
<div className="text-center mb-12">
|
||||
<h1 className="text-3xl md:text-4xl font-bold">Our Stack</h1>
|
||||
<p className="mt-4 text-muted-foreground max-w-2xl mx-auto">
|
||||
We build on proven, open-source foundations. No vendor lock-in, no proprietary black boxes.
|
||||
|
||||
{/* Hero Section */}
|
||||
<AnimatedSection className="border-b border-muted">
|
||||
<Container maxWidth="10xl">
|
||||
<div className="py-20 md:py-28">
|
||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-bold">Our Stack</h1>
|
||||
<p className="mt-6 text-xl text-muted-foreground max-w-3xl">
|
||||
Proven, open-source foundations. No lock-in. No black boxes.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Section>
|
||||
<Container>
|
||||
<h2 className="text-2xl font-bold mb-6">Core Frameworks</h2>
|
||||
<FeatureGrid features={CORE_FRAMEWORKS} columns={{ initial: 1, sm: 2, md: 3 }} />
|
||||
</Container>
|
||||
</Section>
|
||||
|
||||
<Section background="muted">
|
||||
<Container>
|
||||
<div className="flex flex-wrap items-center justify-between gap-4 mb-6">
|
||||
<h2 className="text-2xl font-bold">AI-Native</h2>
|
||||
<Badge variant="info">Private & Secure</Badge>
|
||||
</div>
|
||||
<p className="mb-6 text-muted-foreground max-w-3xl">
|
||||
Leverage AI on your terms. All inference happens on hardware you control, over data you own. Zero leakage to third parties.
|
||||
</p>
|
||||
<FeatureGrid features={AI_NATIVE_STACK} columns={{ initial: 1, md: 3 }} />
|
||||
|
||||
<Callout variant="info" className="mt-8">
|
||||
<span className="font-semibold">AI Compliance:</span> Our local AI stack ensures strict adherence to data privacy regulations (GDPR, HIPAA) and eliminates risks associated with sending sensitive data to external LLM providers.
|
||||
</Callout>
|
||||
</Container>
|
||||
</Section>
|
||||
|
||||
<Section>
|
||||
<Container>
|
||||
<h2 className="text-2xl font-bold mb-6">Integrations</h2>
|
||||
<p className="mb-6 text-muted-foreground max-w-3xl">
|
||||
We wire your new stack into your existing ecosystem. Connect to SSO, directories, project management, communication tools, and more.
|
||||
</p>
|
||||
<FeatureGrid features={INTEGRATIONS} columns={{ initial: 1, sm: 2, md: 3 }} />
|
||||
</Container>
|
||||
</Section>
|
||||
|
||||
<Section background="muted" className="text-center">
|
||||
<Container>
|
||||
<h2 className="text-2xl font-bold mb-4">Want to see a specific tool?</h2>
|
||||
<p className="mb-6 text-muted-foreground max-w-2xl mx-auto">
|
||||
Our stack is flexible. If you have a specific open-source tool in mind, we can likely integrate it.
|
||||
</p>
|
||||
<Button asChild>
|
||||
<a href="/contact">
|
||||
Discuss Your Stack
|
||||
<ExternalLinkIcon className="ml-2 size-4" />
|
||||
</a>
|
||||
</Button>
|
||||
</Container>
|
||||
</Section>
|
||||
</Container>
|
||||
</Section>
|
||||
</AnimatedSection>
|
||||
|
||||
{/* Core Frameworks */}
|
||||
<AnimatedSection className="py-20 border-b border-muted">
|
||||
<Container maxWidth="10xl">
|
||||
<h2 className="text-4xl font-bold mb-16">Core Frameworks</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||
{CORE_FRAMEWORKS.map((f, index) => (
|
||||
<div key={f.title} className="flex items-start gap-6 p-8 rounded-lg border border-muted bg-background transition-all duration-300 hover:border-foreground/20">
|
||||
<div className="flex items-center justify-center w-12 h-12 rounded-full bg-muted/50 text-primary">
|
||||
{f.icon}
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-4xl font-light text-muted-foreground mb-2">
|
||||
{index < 9 ? `0${index + 1}` : index + 1}
|
||||
</div>
|
||||
<h3 className="text-2xl font-bold">{f.title}</h3>
|
||||
<p className="text-muted-foreground mt-2">{f.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Container>
|
||||
</AnimatedSection>
|
||||
|
||||
{/* AI-Native Stack */}
|
||||
<AnimatedSection className="py-20 border-b border-muted bg-muted/30">
|
||||
<Container maxWidth="10xl">
|
||||
<div className="flex flex-wrap items-center justify-between gap-6 mb-16">
|
||||
<h2 className="text-4xl font-bold">AI-Native</h2>
|
||||
<Badge variant="info" className="text-lg py-2 px-4">Private & Secure</Badge>
|
||||
</div>
|
||||
<p className="text-xl text-muted-foreground max-w-4xl mb-16">
|
||||
Leverage AI on your terms. All inference happens on hardware you control, over data you own. Zero leakage to third parties.
|
||||
</p>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
{AI_NATIVE_STACK.map((f, index) => (
|
||||
<div key={f.title} className="flex items-start gap-6 p-8 rounded-lg border border-muted bg-background transition-all duration-300 hover:border-foreground/20">
|
||||
<div className="flex items-center justify-center w-12 h-12 rounded-full bg-muted/50 text-primary">
|
||||
{f.icon}
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-4xl font-light text-muted-foreground mb-2">
|
||||
{index < 9 ? `0${index + 1}` : index + 1}
|
||||
</div>
|
||||
<h3 className="text-2xl font-bold">{f.title}</h3>
|
||||
<p className="text-muted-foreground mt-2">{f.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<Callout variant="info" className="mt-16 p-8 text-lg">
|
||||
<span className="font-semibold">AI Compliance:</span> Our local AI stack ensures strict adherence to data privacy regulations (GDPR, HIPAA) and eliminates risks associated with sending sensitive data to external LLM providers.
|
||||
</Callout>
|
||||
</Container>
|
||||
</AnimatedSection>
|
||||
|
||||
{/* Integrations */}
|
||||
<AnimatedSection className="py-20 border-b border-muted">
|
||||
<Container maxWidth="10xl">
|
||||
<h2 className="text-4xl font-bold mb-16">Integrations</h2>
|
||||
<p className="text-xl text-muted-foreground max-w-4xl mb-16">
|
||||
We wire your new stack into your existing ecosystem. Connect to SSO, directories, project management, communication tools, and more.
|
||||
</p>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||
{INTEGRATIONS.map((f, index) => (
|
||||
<div key={f.title} className="flex items-start gap-6 p-8 rounded-lg border border-muted bg-background transition-all duration-300 hover:border-foreground/20">
|
||||
<div className="flex items-center justify-center w-12 h-12 rounded-full bg-muted/50 text-primary">
|
||||
{f.icon}
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-4xl font-light text-muted-foreground mb-2">
|
||||
{index < 9 ? `0${index + 1}` : index + 1}
|
||||
</div>
|
||||
<h3 className="text-2xl font-bold">{f.title}</h3>
|
||||
<p className="text-muted-foreground mt-2">{f.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Container>
|
||||
</AnimatedSection>
|
||||
|
||||
{/* CTA Section */}
|
||||
<AnimatedSection className="py-20 border-b border-muted">
|
||||
<Container maxWidth="10xl">
|
||||
<div className="max-w-4xl mx-auto text-center">
|
||||
<h2 className="text-4xl font-bold mb-6">Want to see a specific tool?</h2>
|
||||
<p className="text-xl text-muted-foreground mb-8 max-w-3xl mx-auto">
|
||||
Our stack is flexible. If you have a specific open-source tool in mind, we can likely integrate it.
|
||||
</p>
|
||||
<Button asChild size="lg">
|
||||
<a href="/contact">
|
||||
Discuss Your Stack
|
||||
<ExternalLinkIcon className="ml-2 size-5" />
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
</Container>
|
||||
</AnimatedSection>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,10 +1,22 @@
|
||||
import { docs } from '@/.source';
|
||||
import { loader } from 'fumadocs-core/source';
|
||||
import { createElement } from 'react';
|
||||
import { createElement, type ComponentType } from 'react';
|
||||
import { icons } from 'lucide-react';
|
||||
import { attachFile } from './attach-file';
|
||||
import { attachSeparator } from './attach-separator';
|
||||
|
||||
// Loosen types to avoid strict coupling to fumadocs internal types at build time
|
||||
type PageShape = {
|
||||
data: {
|
||||
body: ComponentType<any>;
|
||||
toc?: unknown;
|
||||
full?: boolean;
|
||||
title: string;
|
||||
description?: string;
|
||||
author?: { name: string; url?: string } | undefined;
|
||||
};
|
||||
};
|
||||
|
||||
export const source = loader({
|
||||
baseUrl: '/docs',
|
||||
source: docs.toFumadocsSource(),
|
||||
@ -19,4 +31,10 @@ export const source = loader({
|
||||
|
||||
if (icon in icons) return createElement(icons[icon as keyof typeof icons]);
|
||||
},
|
||||
});
|
||||
}) as unknown as {
|
||||
getPage: (slug?: string[]) => PageShape | undefined;
|
||||
// Minimal shape used by sitemap: url + file.path
|
||||
getPages: () => Array<{ url: string; file: { path: string } }>;
|
||||
generateParams: () => Promise<any> | any;
|
||||
pageTree: unknown;
|
||||
};
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
"homepage": "https://animate-ui.com",
|
||||
"author": "Skyleen",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "next build",
|
||||
"dev": "next dev",
|
||||
@ -49,7 +50,7 @@
|
||||
"rimraf": "^6.0.1",
|
||||
"shadcn": "2.4.0-canary.13",
|
||||
"shiki": "^3.2.1",
|
||||
"zod": "^3.24.2"
|
||||
"zod": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mdx": "^2.0.13",
|
||||
|
||||
@ -18,7 +18,7 @@
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"paths": {
|
||||
"@/.source": ["./.source/index.ts"],
|
||||
"@/.source": ["./.source-typed.ts"],
|
||||
"@/*": ["./*"],
|
||||
"@workspace/ui/*": ["../../packages/ui/src/*"]
|
||||
},
|
||||
@ -29,5 +29,9 @@
|
||||
]
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
".source",
|
||||
".source/**/*"
|
||||
]
|
||||
}
|
||||
|
||||
0
b4b7bd5fcfe1
Normal file
0
b4b7bd5fcfe1
Normal file
19
docker-compose.yml
Normal file
19
docker-compose.yml
Normal file
@ -0,0 +1,19 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
web:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: apps/www/Dockerfile
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
# Add other environment variables as needed
|
||||
# - NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}
|
||||
# - DATABASE_URL=${DATABASE_URL}
|
||||
env_file:
|
||||
- .env.local
|
||||
volumes:
|
||||
- ./.next:/app/.next
|
||||
restart: unless-stopped
|
||||
28
package.json
28
package.json
@ -17,18 +17,38 @@
|
||||
"@commitlint/config-conventional": "^19.8.0",
|
||||
"@eslint/js": "^9.28.0",
|
||||
"@tailwindcss/postcss": "^4.1.4",
|
||||
"@types/node": "22.13.8",
|
||||
"@types/react": "^19.0.10",
|
||||
"@types/react-dom": "^19.0.4",
|
||||
"@types/node": "24.3.0",
|
||||
"@types/react": "^19.1.10",
|
||||
"@types/react-dom": "^19.1.7",
|
||||
"@workspace/eslint-config": "workspace:*",
|
||||
"@workspace/typescript-config": "workspace:*",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"eslint": "^9.20.1",
|
||||
"fumadocs-core": "^15.6.12",
|
||||
"fumadocs-mdx": "^11.7.5",
|
||||
"fumadocs-ui": "^15.6.12",
|
||||
"husky": "^9.1.7",
|
||||
"motion": "^12.23.12",
|
||||
"next": "^15.5.0",
|
||||
"postcss": "^8.5.3",
|
||||
"prettier": "^3.5.3",
|
||||
"radix-ui": "^1.4.3",
|
||||
"react": "^19.1.1",
|
||||
"react-day-picker": "^9.9.0",
|
||||
"react-dom": "^19.1.1",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"tailwindcss": "^4.1.4",
|
||||
"turbo": "^2.4.2",
|
||||
"typescript": "5.7.3"
|
||||
"tw-animate-css": "^1.3.7",
|
||||
"typescript": "5.9.2",
|
||||
"zod": "^4.0.17"
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"zod": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"packageManager": "pnpm@10.4.1",
|
||||
"engines": {
|
||||
|
||||
@ -23,7 +23,7 @@
|
||||
"react-dom": "^19.1.0",
|
||||
"tailwind-merge": "^3.0.1",
|
||||
"tw-animate-css": "^1.2.4",
|
||||
"zod": "^3.24.2"
|
||||
"zod": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@turbo/gen": "^2.4.2"
|
||||
|
||||
41
packages/ui/src/components/animate-ui/parallax-layers.tsx
Normal file
41
packages/ui/src/components/animate-ui/parallax-layers.tsx
Normal file
@ -0,0 +1,41 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { motion, useScroll, useTransform } from "motion/react";
|
||||
|
||||
export function ParallaxLayers() {
|
||||
const { scrollY } = useScroll();
|
||||
const ySlow = useTransform(scrollY, [0, 800], [0, 20]);
|
||||
const yMed = useTransform(scrollY, [0, 800], [0, 40]);
|
||||
const yFast = useTransform(scrollY, [0, 800], [0, 60]);
|
||||
|
||||
return (
|
||||
<div aria-hidden className="pointer-events-none fixed inset-0 -z-10">
|
||||
{/* Layer 1: subtle grid */}
|
||||
<motion.div
|
||||
className="absolute inset-0 opacity-[0.06] bg-grid-dark"
|
||||
style={{ y: ySlow }}
|
||||
/>
|
||||
{/* Layer 2: diagonal stripes */}
|
||||
<motion.div
|
||||
className="absolute inset-0 opacity-[0.05] bg-stripes-dark"
|
||||
style={{ y: yMed }}
|
||||
/>
|
||||
{/* Layer 3: soft radial vignette */}
|
||||
<motion.div
|
||||
className="absolute inset-0"
|
||||
style={{ y: yFast }}
|
||||
>
|
||||
<div
|
||||
className="absolute inset-0"
|
||||
style={{
|
||||
background:
|
||||
"radial-gradient(60% 50% at 50% 10%, color-mix(in oklch, var(--color-foreground), transparent 96%) 0%, transparent 100%)",
|
||||
opacity: 0.08,
|
||||
}}
|
||||
/>
|
||||
</motion.div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
19
packages/ui/src/components/animate-ui/scroll-progress.tsx
Normal file
19
packages/ui/src/components/animate-ui/scroll-progress.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { motion, useScroll } from "motion/react";
|
||||
|
||||
export function ScrollProgressBar({ className = "" }: { className?: string }) {
|
||||
const { scrollYProgress } = useScroll();
|
||||
return (
|
||||
<motion.div
|
||||
aria-hidden
|
||||
className={`fixed left-0 right-0 top-20 h-px z-[60] ${className}`}
|
||||
>
|
||||
<motion.div
|
||||
className="h-full origin-left bg-primary/30"
|
||||
style={{ scaleX: scrollYProgress }}
|
||||
/>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
27
packages/ui/src/components/animate-ui/section.tsx
Normal file
27
packages/ui/src/components/animate-ui/section.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { motion } from "motion/react";
|
||||
|
||||
import { Section } from "@workspace/ui/components/layout/layout-utils";
|
||||
|
||||
type AnimatedSectionProps = React.ComponentProps<typeof Section> & {
|
||||
/** Delay in seconds for staggered entrance */
|
||||
delay?: number;
|
||||
};
|
||||
|
||||
export function AnimatedSection({ children, className, delay = 0, ...props }: AnimatedSectionProps) {
|
||||
return (
|
||||
<Section className={className} {...props}>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: "-10%" }}
|
||||
transition={{ duration: 0.22, ease: [0.2, 0.8, 0.2, 1], delay }}
|
||||
>
|
||||
{children}
|
||||
</motion.div>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
86
packages/ui/src/components/content/cost-reduction-chart.tsx
Normal file
86
packages/ui/src/components/content/cost-reduction-chart.tsx
Normal file
@ -0,0 +1,86 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
|
||||
// Import recharts components
|
||||
import {
|
||||
BarChart,
|
||||
Bar,
|
||||
XAxis,
|
||||
YAxis,
|
||||
CartesianGrid,
|
||||
ResponsiveContainer,
|
||||
Cell
|
||||
} from 'recharts';
|
||||
|
||||
export function CostReductionChart() {
|
||||
// Data for the chart
|
||||
const data = [
|
||||
{ name: 'Traditional Cloud', cost: 100000 },
|
||||
{ name: 'Our Solution', cost: 10000 },
|
||||
];
|
||||
|
||||
// Colors that align with the site's aesthetic
|
||||
// Using muted foreground and primary colors
|
||||
const COLORS = ['oklch(0.556 0 0)', 'oklch(0.340 0 0)']; // muted-foreground and primary
|
||||
|
||||
return (
|
||||
<div className="w-full py-8">
|
||||
<h2 className="text-4xl font-bold mb-16">Cost Reduction</h2>
|
||||
|
||||
<div className="h-64 md:h-80">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<BarChart
|
||||
data={data}
|
||||
margin={{
|
||||
top: 20,
|
||||
right: 30,
|
||||
left: 20,
|
||||
bottom: 5,
|
||||
}}
|
||||
layout="vertical"
|
||||
>
|
||||
<CartesianGrid strokeDasharray="3 3" horizontal={true} vertical={false} strokeOpacity={0.3} />
|
||||
<XAxis
|
||||
type="number"
|
||||
tickFormatter={(value) => `${value.toLocaleString()}`}
|
||||
domain={[0, 120000]}
|
||||
tick={{ fontSize: 12 }}
|
||||
/>
|
||||
<YAxis
|
||||
type="category"
|
||||
dataKey="name"
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
width={120}
|
||||
tick={{ fontSize: 12 }}
|
||||
/>
|
||||
<Bar
|
||||
dataKey="cost"
|
||||
name="Monthly Cost"
|
||||
radius={[0, 4, 4, 0]}
|
||||
barSize={40}
|
||||
>
|
||||
{data.map((entry, index) => (
|
||||
<Cell
|
||||
key={`cell-${index}`}
|
||||
fill={COLORS[index]}
|
||||
fillOpacity={0.8}
|
||||
/>
|
||||
))}
|
||||
</Bar>
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 text-center">
|
||||
<div className="text-2xl font-bold" style={{ color: 'oklch(0.205 0 0)' }}>
|
||||
99.65% ↓
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground mt-2">
|
||||
*Based on typical workloads. Your savings may vary.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -25,24 +25,26 @@ export function FinalCTASection({
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col items-center justify-center text-center p-8 md:p-12 rounded-lg border bg-card",
|
||||
"flex flex-col items-center justify-center text-center py-20 px-6 rounded-lg w-full",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<h2 className="text-2xl font-bold">{title}</h2>
|
||||
<p className="mt-2 text-muted-foreground max-w-xl">
|
||||
{description}
|
||||
</p>
|
||||
<div className="mt-6 flex flex-col sm:flex-row gap-3">
|
||||
<Button asChild size="lg">
|
||||
<a href={primaryCtaHref}>{primaryCtaText}</a>
|
||||
</Button>
|
||||
{secondaryCtaText && secondaryCtaHref && (
|
||||
<Button asChild variant="outline" size="lg">
|
||||
<a href={secondaryCtaHref}>{secondaryCtaText}</a>
|
||||
<div className="max-w-3xl">
|
||||
<h2 className="text-4xl font-bold mb-16">{title}</h2>
|
||||
<p className="text-xl text-muted-foreground mb-10 max-w-2xl mx-auto">
|
||||
{description}
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-6 justify-center">
|
||||
<Button asChild size="lg" className="px-10 py-6 text-lg">
|
||||
<a href={primaryCtaHref}>{primaryCtaText}</a>
|
||||
</Button>
|
||||
)}
|
||||
{secondaryCtaText && secondaryCtaHref && (
|
||||
<Button asChild variant="outline" size="lg" className="px-10 py-6 text-lg">
|
||||
<a href={secondaryCtaHref}>{secondaryCtaText}</a>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -7,4 +7,5 @@ export * from './callout';
|
||||
export * from './badge';
|
||||
export * from './trust-section';
|
||||
export * from './final-cta-section';
|
||||
export * from './toast'; // Add Toast export
|
||||
export * from './toast'; // Add Toast export
|
||||
export * from './cost-reduction-chart';
|
||||
@ -2,7 +2,7 @@ import * as React from 'react';
|
||||
import { motion } from 'motion/react';
|
||||
|
||||
import { cn } from '@workspace/ui/lib/utils';
|
||||
import { Button } from '@workspace/ui/components/ui/button'; // Assuming Button exists
|
||||
import { Button } from '@workspace/ui/components/ui/button';
|
||||
|
||||
// Define types for steps
|
||||
interface StepItem {
|
||||
@ -22,45 +22,37 @@ export interface StepsProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
|
||||
export function Steps({ steps, title, ctaText, ctaHref, className, ...props }: StepsProps) {
|
||||
return (
|
||||
<div className={cn("w-full", className)} {...props}>
|
||||
{title && <h2 className="text-2xl font-bold mb-8">{title}</h2>}
|
||||
<div className="relative">
|
||||
{/* Vertical line */}
|
||||
<div className="absolute left-4 top-0 h-full w-0.5 bg-border transform -translate-x-1/2 md:left-8 md:w-1"></div>
|
||||
|
||||
<ol className="relative space-y-12 pl-10 md:pl-20">
|
||||
<div className={cn('w-full', className)} {...props}>
|
||||
{title && <h2 className="text-2xl font-bold mb-8 text-center">{title}</h2>}
|
||||
|
||||
{/* Desktop / Wide: horizontal timeline */}
|
||||
<div className="relative hidden md:block">
|
||||
{/* Horizontal line */}
|
||||
<div className="absolute left-0 right-0 top-5 h-px bg-border" />
|
||||
|
||||
<ol className="relative flex items-start justify-between gap-6">
|
||||
{steps.map((step, index) => (
|
||||
<motion.li
|
||||
key={index}
|
||||
className="relative"
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
viewport={{ once: true, margin: "-100px" }}
|
||||
transition={{ duration: 0.5, delay: index * 0.1 }}
|
||||
className="relative flex-1 min-w-0"
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: '-10%' }}
|
||||
transition={{ duration: 0.22, delay: index * 0.05, ease: [0.2, 0.8, 0.2, 1] }}
|
||||
>
|
||||
{/* Step number circle */}
|
||||
<div className="absolute left-[-34px] top-0 flex h-8 w-8 items-center justify-center rounded-full bg-primary text-primary-foreground md:left-[-52px] md:h-12 md:w-12">
|
||||
<span className="text-sm font-bold md:text-base">{index + 1}</span>
|
||||
<div className="relative z-10 mx-auto w-10 h-10 rounded-full bg-primary text-primary-foreground flex items-center justify-center font-bold">
|
||||
<span className="text-sm">{index + 1}</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold">{step.title}</h3>
|
||||
<p className="mt-1 text-muted-foreground">{step.description}</p>
|
||||
|
||||
{/* Optional bullets */}
|
||||
<div className="mt-3 text-center px-2">
|
||||
<h3 className="text-sm font-semibold truncate">{step.title}</h3>
|
||||
<p className="mt-1 text-xs text-muted-foreground line-clamp-3">{step.description}</p>
|
||||
{(step.youDo || step.weDo) && (
|
||||
<ul className="mt-3 space-y-1 text-sm">
|
||||
<ul className="mt-2 space-y-1 text-xs text-left mx-auto max-w-xs">
|
||||
{step.youDo && (
|
||||
<li className="flex items-start">
|
||||
<span className="mr-2 text-primary">•</span>
|
||||
<span><span className="font-medium">You:</span> {step.youDo}</span>
|
||||
</li>
|
||||
<li className="flex items-start"><span className="mr-2 text-primary">•</span><span><span className="font-medium">You:</span> {step.youDo}</span></li>
|
||||
)}
|
||||
{step.weDo && (
|
||||
<li className="flex items-start">
|
||||
<span className="mr-2 text-primary">•</span>
|
||||
<span><span className="font-medium">We:</span> {step.weDo}</span>
|
||||
</li>
|
||||
<li className="flex items-start"><span className="mr-2 text-primary">•</span><span><span className="font-medium">We:</span> {step.weDo}</span></li>
|
||||
)}
|
||||
</ul>
|
||||
)}
|
||||
@ -69,10 +61,43 @@ export function Steps({ steps, title, ctaText, ctaHref, className, ...props }: S
|
||||
))}
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
{/* CTA Button */}
|
||||
|
||||
{/* Mobile: centered stacked items */}
|
||||
<div className="md:hidden">
|
||||
<ol className="space-y-8">
|
||||
{steps.map((step, index) => (
|
||||
<motion.li
|
||||
key={index}
|
||||
className="relative"
|
||||
initial={{ opacity: 0, y: 8 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: '-10%' }}
|
||||
transition={{ duration: 0.22, delay: index * 0.05, ease: [0.2, 0.8, 0.2, 1] }}
|
||||
>
|
||||
<div className="mx-auto w-9 h-9 rounded-full bg-primary text-primary-foreground flex items-center justify-center font-bold">
|
||||
<span className="text-xs">{index + 1}</span>
|
||||
</div>
|
||||
<div className="mt-3 text-center px-1">
|
||||
<h3 className="text-base font-semibold">{step.title}</h3>
|
||||
<p className="mt-1 text-sm text-muted-foreground">{step.description}</p>
|
||||
{(step.youDo || step.weDo) && (
|
||||
<ul className="mt-2 space-y-1 text-sm text-left mx-auto max-w-sm">
|
||||
{step.youDo && (
|
||||
<li className="flex items-start"><span className="mr-2 text-primary">•</span><span><span className="font-medium">You:</span> {step.youDo}</span></li>
|
||||
)}
|
||||
{step.weDo && (
|
||||
<li className="flex items-start"><span className="mr-2 text-primary">•</span><span><span className="font-medium">We:</span> {step.weDo}</span></li>
|
||||
)}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
</motion.li>
|
||||
))}
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
{ctaText && ctaHref && (
|
||||
<div className="mt-12 flex justify-center">
|
||||
<div className="mt-10 flex justify-center">
|
||||
<Button asChild>
|
||||
<a href={ctaHref}>{ctaText}</a>
|
||||
</Button>
|
||||
@ -80,4 +105,4 @@ export function Steps({ steps, title, ctaText, ctaHref, className, ...props }: S
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,22 +11,33 @@ interface TrustItem {
|
||||
export interface TrustSectionProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
items: TrustItem[];
|
||||
title?: string; // Optional title for the section
|
||||
description?: string; // Optional description for the section
|
||||
}
|
||||
|
||||
export function TrustSection({ items, title, className, ...props }: TrustSectionProps) {
|
||||
export function TrustSection({ items, title, description, className, ...props }: TrustSectionProps) {
|
||||
return (
|
||||
<div className={cn("w-full", className)} {...props}>
|
||||
{title && <h2 className="text-2xl font-bold mb-8 text-center">{title}</h2>}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{items.map((item, index) => (
|
||||
<div key={index} className="flex items-start space-x-3 p-4 rounded-lg border bg-card">
|
||||
<div className="mt-0.5 text-primary">
|
||||
{item.icon}
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">{item.text}</p>
|
||||
<div className={cn('w-full', className)} {...props}>
|
||||
<div className="max-w-6xl mx-auto">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-16 items-start">
|
||||
<div className="space-y-6">
|
||||
{title && <h2 className="text-4xl font-bold">{title}</h2>}
|
||||
{description && <p className="text-xl text-muted-foreground">{description}</p>}
|
||||
</div>
|
||||
))}
|
||||
<div className="grid grid-cols-2 gap-6">
|
||||
{items.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex flex-col items-center text-center space-y-4 p-6 rounded-lg border border-muted bg-background transition-all duration-300 hover:border-foreground/20"
|
||||
>
|
||||
<div className="flex items-center justify-center w-12 h-12 rounded-full bg-muted/50 text-primary">
|
||||
{item.icon}
|
||||
</div>
|
||||
<p className="text-base text-muted-foreground">{item.text}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,7 +50,7 @@ const SOCIAL_LINKS = [
|
||||
export function Footer() {
|
||||
return (
|
||||
<footer className="border-t bg-background/80 backdrop-blur-sm">
|
||||
<div className="container py-12 md:py-16 px-4 md:px-6">
|
||||
<div className="w-full py-14 md:py-20 px-4 md:px-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-8">
|
||||
{/* Branding and Description */}
|
||||
<div className="lg:col-span-1">
|
||||
@ -120,4 +120,4 @@ export function Footer() {
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,17 +3,27 @@ import * as React from 'react';
|
||||
import { cn } from '@workspace/ui/lib/utils';
|
||||
|
||||
// Container component for consistent horizontal padding and optional max-width centering
|
||||
type MaxWidth = '3xl' | '4xl' | '5xl' | '6xl' | '7xl' | '10xl' | 'full';
|
||||
|
||||
const maxWidthClassMap: Record<MaxWidth, string> = {
|
||||
'3xl': 'max-w-3xl',
|
||||
'4xl': 'max-w-4xl',
|
||||
'5xl': 'max-w-5xl',
|
||||
'6xl': 'max-w-6xl',
|
||||
'7xl': 'max-w-7xl',
|
||||
'10xl': 'max-w-8xl',
|
||||
'full': 'max-w-none',
|
||||
};
|
||||
|
||||
export function Container({
|
||||
children,
|
||||
className,
|
||||
maxWidth = '7xl', // Tailwind max-width class prefix (e.g., '7xl' for max-w-7xl)
|
||||
maxWidth = 'full',
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement> & { maxWidth?: string }) {
|
||||
}: React.HTMLAttributes<HTMLDivElement> & { maxWidth?: MaxWidth }) {
|
||||
const maxW = maxWidthClassMap[maxWidth] ?? maxWidthClassMap['7xl'];
|
||||
return (
|
||||
<div
|
||||
className={cn('w-full mx-auto px-4 md:px-6', maxWidth && `max-w-${maxWidth}`, className)}
|
||||
{...props}
|
||||
>
|
||||
<div className={cn('w-full mx-auto px-6 md:px-8', maxW, className)} {...props}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
@ -36,11 +46,12 @@ export function Section({
|
||||
const backgroundClass = background === 'muted' ? 'bg-muted' : background === 'card' ? 'bg-card' : '';
|
||||
const borderClass = border ? 'border-t' : '';
|
||||
|
||||
// More generous spacing for a cleaner aesthetic
|
||||
const paddingClasses = {
|
||||
'default': 'py-12 md:py-20',
|
||||
'sm': 'py-6 md:py-10',
|
||||
'lg': 'py-16 md:py-24',
|
||||
'xl': 'py-20 md:py-32',
|
||||
'default': 'py-16 md:py-20',
|
||||
'sm': 'py-8 md:py-12',
|
||||
'lg': 'py-20 md:py-28',
|
||||
'xl': 'py-28 md:py-40',
|
||||
'none': '',
|
||||
};
|
||||
const paddingClass = paddingClasses[padding] || paddingClasses['default'];
|
||||
@ -95,4 +106,4 @@ export function Grid({
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
import * as React from 'react';
|
||||
import Link from 'next/link';
|
||||
import Image from 'next/image';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { motion, AnimatePresence } from 'motion/react';
|
||||
import { MenuIcon, XIcon } from 'lucide-react';
|
||||
@ -15,15 +16,14 @@ import { cn } from '@workspace/ui/lib/utils';
|
||||
// import { Switch } from '@workspace/ui/components/ui/switch'; // Assuming you have a Switch component
|
||||
|
||||
const NAV_ITEMS = [
|
||||
{ name: 'Home', href: '/' },
|
||||
{ name: 'Solutions', href: '/solutions' },
|
||||
{ name: 'Case Studies', href: '/case-studies' },
|
||||
{ name: 'Stack', href: '/stack' },
|
||||
{ name: 'Process', href: '/process' },
|
||||
{ name: 'Pricing', href: '/pricing' },
|
||||
{ name: 'HOME', href: '/' },
|
||||
{ name: 'SOLUTIONS', href: '/solutions' },
|
||||
{ name: 'CASE STUDIES', href: '/case-studies' },
|
||||
{ name: 'STACK', href: '/stack' },
|
||||
{ name: 'PROCESS', href: '/process' },
|
||||
{ name: 'PRICING', href: '/pricing' },
|
||||
{ name: 'FAQ', href: '/faq' },
|
||||
{ name: 'About', href: '/about' },
|
||||
{ name: 'Contact', href: '/contact' },
|
||||
{ name: 'ABOUT', href: '/about' },
|
||||
];
|
||||
|
||||
export function Navbar() {
|
||||
@ -39,98 +39,97 @@ export function Navbar() {
|
||||
};
|
||||
|
||||
return (
|
||||
<header className="sticky top-0 z-50 w-full border-b bg-background/80 backdrop-blur-sm">
|
||||
<div className="container flex h-16 items-center justify-between px-4 md:px-6">
|
||||
{/* Logo / Brand */}
|
||||
<Link href="/" className="flex items-center space-x-2" aria-label="Fortura Data Solutions">
|
||||
{/* Replace with your actual logo component or image */}
|
||||
<span className="font-bold text-xl">Fortura</span>
|
||||
</Link>
|
||||
|
||||
{/* Desktop Navigation */}
|
||||
<nav className="hidden md:flex items-center gap-6">
|
||||
{NAV_ITEMS.map((item) => (
|
||||
<Link
|
||||
key={item.name}
|
||||
href={item.href}
|
||||
className={cn(
|
||||
'text-sm font-medium transition-colors hover:text-primary',
|
||||
pathname === item.href ? 'text-primary' : 'text-muted-foreground'
|
||||
)}
|
||||
aria-current={pathname === item.href ? 'page' : undefined}
|
||||
>
|
||||
{item.name}
|
||||
<header className="sticky top-4 z-50 w-full bg-transparent">
|
||||
<div className="px-4 md:px-6">
|
||||
{/* Floating pill nav */}
|
||||
<div className="mx-auto max-w-6xl border rounded-full bg-background/80 backdrop-blur supports-[backdrop-filter]:bg-background/60 shadow-md">
|
||||
<div className="flex h-16 items-center gap-4 px-4 md:px-6">
|
||||
{/* Logo / Brand */}
|
||||
<Link href="/" className="flex items-center gap-3" aria-label="Fortura Data Solutions">
|
||||
<Image src="/logo-dark.png" alt="Fortura logo" width={36} height={36} className="hidden dark:block" />
|
||||
<Image src="/logo-light.png" alt="Fortura logo" width={36} height={36} className="block dark:hidden" />
|
||||
<span className="font-bold text-sm tracking-wider uppercase">Fortura</span>
|
||||
</Link>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
{/* Desktop CTA (Optional, e.g., Book Call Button) */}
|
||||
<div className="hidden md:block">
|
||||
<Button size="sm" variant="default" asChild>
|
||||
<Link href="/contact">Book Architecture Call</Link>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Mobile Menu Trigger */}
|
||||
<Sheet open={isOpen} onOpenChange={setIsOpen}>
|
||||
<SheetTrigger asChild>
|
||||
<Button variant="ghost" size="icon" className="md:hidden" aria-label="Toggle navigation menu">
|
||||
{isOpen ? <XIcon className="size-6" /> : <MenuIcon className="size-6" />}
|
||||
</Button>
|
||||
</SheetTrigger>
|
||||
<SheetContent side="right" className="w-[300px] sm:w-[400px]">
|
||||
<div className="flex flex-col h-full">
|
||||
{/* Mobile Logo / Close Button Area */}
|
||||
<div className="flex items-center justify-between pt-4 px-2">
|
||||
<Link href="/" className="flex items-center space-x-2" onClick={handleLinkClick} aria-label="Fortura Data Solutions">
|
||||
<span className="font-bold text-xl">Fortura</span>
|
||||
{/* Desktop Navigation (center) */}
|
||||
<nav className="hidden md:flex items-center gap-8 mx-auto">
|
||||
{NAV_ITEMS.map((item) => (
|
||||
<Link
|
||||
key={item.name}
|
||||
href={item.href}
|
||||
className={cn(
|
||||
'text-[12px] tracking-wider transition-colors pb-0.5 border-b border-transparent hover:text-primary/90 hover:border-primary/30',
|
||||
pathname === item.href ? 'text-primary border-primary/80' : 'text-muted-foreground'
|
||||
)}
|
||||
aria-current={pathname === item.href ? 'page' : undefined}
|
||||
>
|
||||
{item.name}
|
||||
</Link>
|
||||
<SheetTrigger asChild>
|
||||
<Button variant="ghost" size="icon" aria-label="Close menu">
|
||||
<XIcon className="size-6" />
|
||||
</Button>
|
||||
</SheetTrigger>
|
||||
</div>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
{/* Mobile Navigation Links */}
|
||||
<nav className="flex flex-col mt-8 px-2">
|
||||
{NAV_ITEMS.map((item) => (
|
||||
<Link
|
||||
key={item.name}
|
||||
href={item.href}
|
||||
onClick={handleLinkClick}
|
||||
className={cn(
|
||||
'py-3 text-base font-medium transition-colors hover:text-primary border-b',
|
||||
pathname === item.href ? 'text-primary' : 'text-muted-foreground'
|
||||
)}
|
||||
aria-current={pathname === item.href ? 'page' : undefined}
|
||||
>
|
||||
{item.name}
|
||||
</Link>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
{/* Mobile CTA */}
|
||||
<div className="mt-auto p-4">
|
||||
<Button className="w-full" variant="default" asChild>
|
||||
<Link href="/contact" onClick={handleLinkClick}>Book Architecture Call</Link>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Theme Toggle (if implemented)
|
||||
<div className="flex items-center justify-between p-4 border-t">
|
||||
<span className="text-sm font-medium">Dark Mode</span>
|
||||
<Switch
|
||||
checked={isDarkMode}
|
||||
onCheckedChange={() => setTheme(isDarkMode ? 'light' : 'dark')}
|
||||
aria-label="Toggle dark mode"
|
||||
/>
|
||||
</div>
|
||||
*/}
|
||||
{/* Desktop CTA (far right) */}
|
||||
<div className="hidden md:block ml-auto">
|
||||
<Link href="/contact" className="text-[12px] tracking-wider text-primary hover:underline">
|
||||
BOOK CALL
|
||||
</Link>
|
||||
</div>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
|
||||
{/* Mobile Menu Trigger */}
|
||||
<Sheet open={isOpen} onOpenChange={setIsOpen}>
|
||||
<SheetTrigger asChild>
|
||||
<Button variant="ghost" size="icon" className="md:hidden" aria-label="Toggle navigation menu">
|
||||
{isOpen ? <XIcon className="size-6" /> : <MenuIcon className="size-6" />}
|
||||
</Button>
|
||||
</SheetTrigger>
|
||||
<SheetContent side="right" className="w-[300px] sm:w-[400px]">
|
||||
<div className="flex flex-col h-full">
|
||||
{/* Mobile Logo / Close Button Area */}
|
||||
<div className="flex items-center justify-between pt-4 px-2">
|
||||
<Link href="/" className="flex items-center space-x-2" onClick={handleLinkClick} aria-label="Fortura Data Solutions">
|
||||
<span className="font-bold text-xl">Fortura</span>
|
||||
</Link>
|
||||
<SheetTrigger asChild>
|
||||
<Button variant="ghost" size="icon" aria-label="Close menu">
|
||||
<XIcon className="size-6" />
|
||||
</Button>
|
||||
</SheetTrigger>
|
||||
</div>
|
||||
|
||||
{/* Mobile Navigation Links */}
|
||||
<nav className="flex flex-col mt-8 px-2">
|
||||
{NAV_ITEMS.map((item) => (
|
||||
<Link
|
||||
key={item.name}
|
||||
href={item.href}
|
||||
onClick={handleLinkClick}
|
||||
className={cn(
|
||||
'py-3 text-sm tracking-wider transition-colors hover:text-primary border-b border-border/60',
|
||||
pathname === item.href ? 'text-primary' : 'text-muted-foreground'
|
||||
)}
|
||||
aria-current={pathname === item.href ? 'page' : undefined}
|
||||
>
|
||||
{item.name}
|
||||
</Link>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
{/* Mobile CTA */}
|
||||
<div className="mt-auto p-4">
|
||||
<Link
|
||||
href="/contact"
|
||||
onClick={handleLinkClick}
|
||||
className="block w-full text-center text-sm tracking-wider text-primary hover:underline"
|
||||
>
|
||||
BOOK CALL
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -61,7 +61,8 @@
|
||||
|
||||
--chart-5: oklch(0.769 0.188 70.08);
|
||||
|
||||
--radius: 0.625rem;
|
||||
/* Industrial vibe: tight 2px radius */
|
||||
--radius: 2px;
|
||||
|
||||
--sidebar: oklch(0.985 0 0);
|
||||
|
||||
@ -226,6 +227,50 @@
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
}
|
||||
|
||||
/* Subtle background patterns inspired by reference sites */
|
||||
.bg-stripes-dark {
|
||||
background-image: repeating-linear-gradient(
|
||||
135deg,
|
||||
color-mix(in oklch, var(--color-background), var(--color-foreground) 6%) 0,
|
||||
color-mix(in oklch, var(--color-background), var(--color-foreground) 6%) 2px,
|
||||
transparent 2px,
|
||||
transparent 12px
|
||||
);
|
||||
background-size: 16px 16px;
|
||||
}
|
||||
|
||||
.bg-grid-dark {
|
||||
background-image:
|
||||
radial-gradient(circle at 1px 1px,
|
||||
color-mix(in oklch, var(--color-foreground), transparent 92%) 1px,
|
||||
transparent 0);
|
||||
background-size: 20px 20px;
|
||||
}
|
||||
|
||||
/* Keyword highlighting */
|
||||
.keyword {
|
||||
background-color: oklch(0.9 0 0); /* Light gray background */
|
||||
padding: 0.125rem 0.25rem;
|
||||
border-radius: var(--radius-sm);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.dark .keyword {
|
||||
background-color: oklch(0.3 0 0); /* Darker gray for dark mode */
|
||||
}
|
||||
|
||||
/* Platform list styling */
|
||||
.group:hover .text-muted-foreground {
|
||||
color: oklch(0.7 0 0); /* Slightly darker on hover */
|
||||
}
|
||||
|
||||
.dark .group:hover .text-muted-foreground {
|
||||
color: oklch(0.8 0 0); /* Slightly lighter on hover */
|
||||
}
|
||||
|
||||
/* Minimal, theme-aware scrollbar styling */
|
||||
/* Remove custom scrollbar to restore native */
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
@ -253,4 +298,67 @@
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
/* Generous spacing for a cleaner aesthetic */
|
||||
.space-y-6 > * + * {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.space-y-8 > * + * {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.space-y-12 > * + * {
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
.space-y-16 > * + * {
|
||||
margin-top: 4rem;
|
||||
}
|
||||
|
||||
.space-y-20 > * + * {
|
||||
margin-top: 5rem;
|
||||
}
|
||||
|
||||
.space-y-24 > * + * {
|
||||
margin-top: 6rem;
|
||||
}
|
||||
|
||||
/* Section padding adjustments for more generous spacing */
|
||||
.py-16 {
|
||||
padding-top: 4rem;
|
||||
padding-bottom: 4rem;
|
||||
}
|
||||
|
||||
.py-20 {
|
||||
padding-top: 5rem;
|
||||
padding-bottom: 5rem;
|
||||
}
|
||||
|
||||
.py-24 {
|
||||
padding-top: 6rem;
|
||||
padding-bottom: 6rem;
|
||||
}
|
||||
|
||||
.py-28 {
|
||||
padding-top: 7rem;
|
||||
padding-bottom: 7rem;
|
||||
}
|
||||
|
||||
/* Generous gap utilities */
|
||||
.gap-12 {
|
||||
gap: 3rem;
|
||||
}
|
||||
|
||||
.gap-16 {
|
||||
gap: 4rem;
|
||||
}
|
||||
|
||||
.gap-20 {
|
||||
gap: 5rem;
|
||||
}
|
||||
|
||||
.gap-24 {
|
||||
gap: 6rem;
|
||||
}
|
||||
}
|
||||
|
||||
318
pnpm-lock.yaml
generated
318
pnpm-lock.yaml
generated
@ -4,6 +4,9 @@ settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
overrides:
|
||||
zod: ^4.0.0
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
@ -17,7 +20,7 @@ importers:
|
||||
devDependencies:
|
||||
'@commitlint/cli':
|
||||
specifier: ^19.8.0
|
||||
version: 19.8.1(@types/node@22.13.8)(typescript@5.7.3)
|
||||
version: 19.8.1(@types/node@24.3.0)(typescript@5.9.2)
|
||||
'@commitlint/config-conventional':
|
||||
specifier: ^19.8.0
|
||||
version: 19.8.1
|
||||
@ -28,13 +31,13 @@ importers:
|
||||
specifier: ^4.1.4
|
||||
version: 4.1.12
|
||||
'@types/node':
|
||||
specifier: 22.13.8
|
||||
version: 22.13.8
|
||||
specifier: 24.3.0
|
||||
version: 24.3.0
|
||||
'@types/react':
|
||||
specifier: ^19.0.10
|
||||
specifier: ^19.1.10
|
||||
version: 19.1.10
|
||||
'@types/react-dom':
|
||||
specifier: ^19.0.4
|
||||
specifier: ^19.1.7
|
||||
version: 19.1.7(@types/react@19.1.10)
|
||||
'@workspace/eslint-config':
|
||||
specifier: workspace:*
|
||||
@ -42,27 +45,72 @@ importers:
|
||||
'@workspace/typescript-config':
|
||||
specifier: workspace:*
|
||||
version: link:packages/typescript-config
|
||||
class-variance-authority:
|
||||
specifier: ^0.7.1
|
||||
version: 0.7.1
|
||||
clsx:
|
||||
specifier: ^2.1.1
|
||||
version: 2.1.1
|
||||
date-fns:
|
||||
specifier: ^4.1.0
|
||||
version: 4.1.0
|
||||
eslint:
|
||||
specifier: ^9.20.1
|
||||
version: 9.33.0(jiti@2.5.1)
|
||||
fumadocs-core:
|
||||
specifier: ^15.6.12
|
||||
version: 15.6.12(@types/react@19.1.10)(next@15.5.0(@babel/core@7.28.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
||||
fumadocs-mdx:
|
||||
specifier: ^11.7.5
|
||||
version: 11.7.5(acorn@8.15.0)(fumadocs-core@15.6.12(@types/react@19.1.10)(next@15.5.0(@babel/core@7.28.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(next@15.5.0(@babel/core@7.28.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)
|
||||
fumadocs-ui:
|
||||
specifier: ^15.6.12
|
||||
version: 15.6.12(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(next@15.5.0(@babel/core@7.28.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(tailwindcss@4.1.12)
|
||||
husky:
|
||||
specifier: ^9.1.7
|
||||
version: 9.1.7
|
||||
motion:
|
||||
specifier: ^12.23.12
|
||||
version: 12.23.12(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
||||
next:
|
||||
specifier: ^15.5.0
|
||||
version: 15.5.0(@babel/core@7.28.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
||||
postcss:
|
||||
specifier: ^8.5.3
|
||||
version: 8.5.6
|
||||
prettier:
|
||||
specifier: ^3.5.3
|
||||
version: 3.6.2
|
||||
radix-ui:
|
||||
specifier: ^1.4.3
|
||||
version: 1.4.3(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
||||
react:
|
||||
specifier: ^19.1.1
|
||||
version: 19.1.1
|
||||
react-day-picker:
|
||||
specifier: ^9.9.0
|
||||
version: 9.9.0(react@19.1.1)
|
||||
react-dom:
|
||||
specifier: ^19.1.1
|
||||
version: 19.1.1(react@19.1.1)
|
||||
tailwind-merge:
|
||||
specifier: ^3.3.1
|
||||
version: 3.3.1
|
||||
tailwindcss:
|
||||
specifier: ^4.1.4
|
||||
version: 4.1.12
|
||||
turbo:
|
||||
specifier: ^2.4.2
|
||||
version: 2.5.6
|
||||
tw-animate-css:
|
||||
specifier: ^1.3.7
|
||||
version: 1.3.7
|
||||
typescript:
|
||||
specifier: 5.7.3
|
||||
version: 5.7.3
|
||||
specifier: 5.9.2
|
||||
version: 5.9.2
|
||||
zod:
|
||||
specifier: ^4.0.0
|
||||
version: 4.0.17
|
||||
|
||||
apps/www:
|
||||
dependencies:
|
||||
@ -86,7 +134,7 @@ importers:
|
||||
version: 4.1.0
|
||||
eslint-config-next:
|
||||
specifier: ^15.3.3
|
||||
version: 15.5.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.7.3)
|
||||
version: 15.5.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)
|
||||
fumadocs-core:
|
||||
specifier: ^15.2.14
|
||||
version: 15.6.12(@types/react@19.1.10)(next@15.5.0(@babel/core@7.28.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
||||
@ -134,13 +182,13 @@ importers:
|
||||
version: 6.0.1
|
||||
shadcn:
|
||||
specifier: 2.4.0-canary.13
|
||||
version: 2.4.0-canary.13(@types/node@22.13.8)(typescript@5.7.3)
|
||||
version: 2.4.0-canary.13(@types/node@24.3.0)(typescript@5.9.2)
|
||||
shiki:
|
||||
specifier: ^3.2.1
|
||||
version: 3.11.0
|
||||
zod:
|
||||
specifier: ^3.24.2
|
||||
version: 3.25.76
|
||||
specifier: ^4.0.0
|
||||
version: 4.0.17
|
||||
devDependencies:
|
||||
'@types/mdx':
|
||||
specifier: ^2.0.13
|
||||
@ -203,7 +251,7 @@ importers:
|
||||
version: 4.1.0
|
||||
eslint-config-next:
|
||||
specifier: ^15.3.3
|
||||
version: 15.5.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.7.3)
|
||||
version: 15.5.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)
|
||||
fumadocs-core:
|
||||
specifier: ^15.2.6
|
||||
version: 15.6.12(@types/react@19.1.10)(next@15.5.0(@babel/core@7.28.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
||||
@ -241,12 +289,12 @@ importers:
|
||||
specifier: ^1.2.4
|
||||
version: 1.3.7
|
||||
zod:
|
||||
specifier: ^3.24.2
|
||||
version: 3.25.76
|
||||
specifier: ^4.0.0
|
||||
version: 4.0.17
|
||||
devDependencies:
|
||||
'@turbo/gen':
|
||||
specifier: ^2.4.2
|
||||
version: 2.5.6(@types/node@22.13.8)(typescript@5.7.3)
|
||||
version: 2.5.6(@types/node@24.3.0)(typescript@5.9.2)
|
||||
|
||||
packages:
|
||||
|
||||
@ -490,6 +538,9 @@ packages:
|
||||
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
'@date-fns/tz@1.4.1':
|
||||
resolution: {integrity: sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA==}
|
||||
|
||||
'@emnapi/core@1.4.5':
|
||||
resolution: {integrity: sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q==}
|
||||
|
||||
@ -2079,6 +2130,9 @@ packages:
|
||||
'@types/node@22.13.8':
|
||||
resolution: {integrity: sha512-G3EfaZS+iOGYWLLRCEAXdWK9my08oHNZ+FHluRiggIYJPOXzhOiDgpVCUHaUvyIC5/fj7C/p637jdzC666AOKQ==}
|
||||
|
||||
'@types/node@24.3.0':
|
||||
resolution: {integrity: sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==}
|
||||
|
||||
'@types/raf@3.4.3':
|
||||
resolution: {integrity: sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==}
|
||||
|
||||
@ -2767,6 +2821,9 @@ packages:
|
||||
resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
date-fns-jalali@4.1.0-0:
|
||||
resolution: {integrity: sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==}
|
||||
|
||||
date-fns@4.1.0:
|
||||
resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==}
|
||||
|
||||
@ -4659,6 +4716,12 @@ packages:
|
||||
date-fns: ^2.28.0 || ^3.0.0
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
|
||||
react-day-picker@9.9.0:
|
||||
resolution: {integrity: sha512-NtkJbuX6cl/VaGNb3sVVhmMA6LSMnL5G3xNL+61IyoZj0mUZFWTg4hmj7PHjIQ8MXN9dHWhUHFoJWG6y60DKSg==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
react: '>=16.8.0'
|
||||
|
||||
react-dom@19.1.1:
|
||||
resolution: {integrity: sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==}
|
||||
peerDependencies:
|
||||
@ -5348,6 +5411,11 @@ packages:
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
|
||||
typescript@5.9.2:
|
||||
resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==}
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
|
||||
uglify-js@3.19.3:
|
||||
resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==}
|
||||
engines: {node: '>=0.8.0'}
|
||||
@ -5360,6 +5428,9 @@ packages:
|
||||
undici-types@6.20.0:
|
||||
resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==}
|
||||
|
||||
undici-types@7.10.0:
|
||||
resolution: {integrity: sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==}
|
||||
|
||||
unicorn-magic@0.1.0:
|
||||
resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==}
|
||||
engines: {node: '>=18'}
|
||||
@ -5549,9 +5620,6 @@ packages:
|
||||
resolution: {integrity: sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
zod@3.25.76:
|
||||
resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==}
|
||||
|
||||
zod@4.0.17:
|
||||
resolution: {integrity: sha512-1PHjlYRevNxxdy2JZ8JcNAw7rX8V9P1AKkP+x/xZfxB0K5FYfuV+Ug6P/6NVSR2jHQ+FzDDoDHS04nYUsOIyLQ==}
|
||||
|
||||
@ -5779,11 +5847,11 @@ snapshots:
|
||||
'@types/tough-cookie': 4.0.5
|
||||
tough-cookie: 4.1.4
|
||||
|
||||
'@commitlint/cli@19.8.1(@types/node@22.13.8)(typescript@5.7.3)':
|
||||
'@commitlint/cli@19.8.1(@types/node@24.3.0)(typescript@5.9.2)':
|
||||
dependencies:
|
||||
'@commitlint/format': 19.8.1
|
||||
'@commitlint/lint': 19.8.1
|
||||
'@commitlint/load': 19.8.1(@types/node@22.13.8)(typescript@5.7.3)
|
||||
'@commitlint/load': 19.8.1(@types/node@24.3.0)(typescript@5.9.2)
|
||||
'@commitlint/read': 19.8.1
|
||||
'@commitlint/types': 19.8.1
|
||||
tinyexec: 1.0.1
|
||||
@ -5830,15 +5898,15 @@ snapshots:
|
||||
'@commitlint/rules': 19.8.1
|
||||
'@commitlint/types': 19.8.1
|
||||
|
||||
'@commitlint/load@19.8.1(@types/node@22.13.8)(typescript@5.7.3)':
|
||||
'@commitlint/load@19.8.1(@types/node@24.3.0)(typescript@5.9.2)':
|
||||
dependencies:
|
||||
'@commitlint/config-validator': 19.8.1
|
||||
'@commitlint/execute-rule': 19.8.1
|
||||
'@commitlint/resolve-extends': 19.8.1
|
||||
'@commitlint/types': 19.8.1
|
||||
chalk: 5.6.0
|
||||
cosmiconfig: 9.0.0(typescript@5.7.3)
|
||||
cosmiconfig-typescript-loader: 6.1.0(@types/node@22.13.8)(cosmiconfig@9.0.0(typescript@5.7.3))(typescript@5.7.3)
|
||||
cosmiconfig: 9.0.0(typescript@5.9.2)
|
||||
cosmiconfig-typescript-loader: 6.1.0(@types/node@24.3.0)(cosmiconfig@9.0.0(typescript@5.9.2))(typescript@5.9.2)
|
||||
lodash.isplainobject: 4.0.6
|
||||
lodash.merge: 4.6.2
|
||||
lodash.uniq: 4.5.0
|
||||
@ -5893,6 +5961,8 @@ snapshots:
|
||||
dependencies:
|
||||
'@jridgewell/trace-mapping': 0.3.9
|
||||
|
||||
'@date-fns/tz@1.4.1': {}
|
||||
|
||||
'@emnapi/core@1.4.5':
|
||||
dependencies:
|
||||
'@emnapi/wasi-threads': 1.0.4
|
||||
@ -6199,17 +6269,17 @@ snapshots:
|
||||
'@img/sharp-win32-x64@0.34.3':
|
||||
optional: true
|
||||
|
||||
'@inquirer/confirm@5.1.15(@types/node@22.13.8)':
|
||||
'@inquirer/confirm@5.1.15(@types/node@24.3.0)':
|
||||
dependencies:
|
||||
'@inquirer/core': 10.1.15(@types/node@22.13.8)
|
||||
'@inquirer/type': 3.0.8(@types/node@22.13.8)
|
||||
'@inquirer/core': 10.1.15(@types/node@24.3.0)
|
||||
'@inquirer/type': 3.0.8(@types/node@24.3.0)
|
||||
optionalDependencies:
|
||||
'@types/node': 22.13.8
|
||||
'@types/node': 24.3.0
|
||||
|
||||
'@inquirer/core@10.1.15(@types/node@22.13.8)':
|
||||
'@inquirer/core@10.1.15(@types/node@24.3.0)':
|
||||
dependencies:
|
||||
'@inquirer/figures': 1.0.13
|
||||
'@inquirer/type': 3.0.8(@types/node@22.13.8)
|
||||
'@inquirer/type': 3.0.8(@types/node@24.3.0)
|
||||
ansi-escapes: 4.3.2
|
||||
cli-width: 4.1.0
|
||||
mute-stream: 2.0.0
|
||||
@ -6217,20 +6287,20 @@ snapshots:
|
||||
wrap-ansi: 6.2.0
|
||||
yoctocolors-cjs: 2.1.2
|
||||
optionalDependencies:
|
||||
'@types/node': 22.13.8
|
||||
'@types/node': 24.3.0
|
||||
|
||||
'@inquirer/external-editor@1.0.1(@types/node@22.13.8)':
|
||||
'@inquirer/external-editor@1.0.1(@types/node@24.3.0)':
|
||||
dependencies:
|
||||
chardet: 2.1.0
|
||||
iconv-lite: 0.6.3
|
||||
optionalDependencies:
|
||||
'@types/node': 22.13.8
|
||||
'@types/node': 24.3.0
|
||||
|
||||
'@inquirer/figures@1.0.13': {}
|
||||
|
||||
'@inquirer/type@3.0.8(@types/node@22.13.8)':
|
||||
'@inquirer/type@3.0.8(@types/node@24.3.0)':
|
||||
optionalDependencies:
|
||||
'@types/node': 22.13.8
|
||||
'@types/node': 24.3.0
|
||||
|
||||
'@internationalized/date@3.8.2':
|
||||
dependencies:
|
||||
@ -7416,17 +7486,17 @@ snapshots:
|
||||
|
||||
'@tsconfig/node16@1.0.4': {}
|
||||
|
||||
'@turbo/gen@2.5.6(@types/node@22.13.8)(typescript@5.7.3)':
|
||||
'@turbo/gen@2.5.6(@types/node@24.3.0)(typescript@5.9.2)':
|
||||
dependencies:
|
||||
'@turbo/workspaces': 2.5.6(@types/node@22.13.8)
|
||||
'@turbo/workspaces': 2.5.6(@types/node@24.3.0)
|
||||
commander: 10.0.1
|
||||
fs-extra: 10.1.0
|
||||
inquirer: 8.2.7(@types/node@22.13.8)
|
||||
inquirer: 8.2.7(@types/node@24.3.0)
|
||||
minimatch: 9.0.5
|
||||
node-plop: 0.26.3
|
||||
picocolors: 1.0.1
|
||||
proxy-agent: 6.5.0
|
||||
ts-node: 10.9.2(@types/node@22.13.8)(typescript@5.7.3)
|
||||
ts-node: 10.9.2(@types/node@24.3.0)(typescript@5.9.2)
|
||||
update-check: 1.5.4
|
||||
validate-npm-package-name: 5.0.1
|
||||
transitivePeerDependencies:
|
||||
@ -7436,14 +7506,14 @@ snapshots:
|
||||
- supports-color
|
||||
- typescript
|
||||
|
||||
'@turbo/workspaces@2.5.6(@types/node@22.13.8)':
|
||||
'@turbo/workspaces@2.5.6(@types/node@24.3.0)':
|
||||
dependencies:
|
||||
commander: 10.0.1
|
||||
execa: 5.1.1
|
||||
fast-glob: 3.3.3
|
||||
fs-extra: 10.1.0
|
||||
gradient-string: 2.0.2
|
||||
inquirer: 8.2.7(@types/node@22.13.8)
|
||||
inquirer: 8.2.7(@types/node@24.3.0)
|
||||
js-yaml: 4.1.0
|
||||
ora: 4.1.1
|
||||
picocolors: 1.0.1
|
||||
@ -7459,7 +7529,7 @@ snapshots:
|
||||
|
||||
'@types/conventional-commits-parser@5.0.1':
|
||||
dependencies:
|
||||
'@types/node': 22.13.8
|
||||
'@types/node': 24.3.0
|
||||
|
||||
'@types/cookie@0.6.0': {}
|
||||
|
||||
@ -7531,6 +7601,10 @@ snapshots:
|
||||
dependencies:
|
||||
undici-types: 6.20.0
|
||||
|
||||
'@types/node@24.3.0':
|
||||
dependencies:
|
||||
undici-types: 7.10.0
|
||||
|
||||
'@types/raf@3.4.3':
|
||||
optional: true
|
||||
|
||||
@ -7578,6 +7652,23 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/eslint-plugin@8.40.0(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)':
|
||||
dependencies:
|
||||
'@eslint-community/regexpp': 4.12.1
|
||||
'@typescript-eslint/parser': 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)
|
||||
'@typescript-eslint/scope-manager': 8.40.0
|
||||
'@typescript-eslint/type-utils': 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)
|
||||
'@typescript-eslint/utils': 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)
|
||||
'@typescript-eslint/visitor-keys': 8.40.0
|
||||
eslint: 9.33.0(jiti@2.5.1)
|
||||
graphemer: 1.4.0
|
||||
ignore: 7.0.5
|
||||
natural-compare: 1.4.0
|
||||
ts-api-utils: 2.1.0(typescript@5.9.2)
|
||||
typescript: 5.9.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.7.3)':
|
||||
dependencies:
|
||||
'@typescript-eslint/scope-manager': 8.40.0
|
||||
@ -7590,6 +7681,18 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)':
|
||||
dependencies:
|
||||
'@typescript-eslint/scope-manager': 8.40.0
|
||||
'@typescript-eslint/types': 8.40.0
|
||||
'@typescript-eslint/typescript-estree': 8.40.0(typescript@5.9.2)
|
||||
'@typescript-eslint/visitor-keys': 8.40.0
|
||||
debug: 4.4.1
|
||||
eslint: 9.33.0(jiti@2.5.1)
|
||||
typescript: 5.9.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/project-service@8.40.0(typescript@5.7.3)':
|
||||
dependencies:
|
||||
'@typescript-eslint/tsconfig-utils': 8.40.0(typescript@5.7.3)
|
||||
@ -7599,6 +7702,15 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/project-service@8.40.0(typescript@5.9.2)':
|
||||
dependencies:
|
||||
'@typescript-eslint/tsconfig-utils': 8.40.0(typescript@5.9.2)
|
||||
'@typescript-eslint/types': 8.40.0
|
||||
debug: 4.4.1
|
||||
typescript: 5.9.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/scope-manager@8.40.0':
|
||||
dependencies:
|
||||
'@typescript-eslint/types': 8.40.0
|
||||
@ -7608,6 +7720,10 @@ snapshots:
|
||||
dependencies:
|
||||
typescript: 5.7.3
|
||||
|
||||
'@typescript-eslint/tsconfig-utils@8.40.0(typescript@5.9.2)':
|
||||
dependencies:
|
||||
typescript: 5.9.2
|
||||
|
||||
'@typescript-eslint/type-utils@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.7.3)':
|
||||
dependencies:
|
||||
'@typescript-eslint/types': 8.40.0
|
||||
@ -7620,6 +7736,18 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/type-utils@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)':
|
||||
dependencies:
|
||||
'@typescript-eslint/types': 8.40.0
|
||||
'@typescript-eslint/typescript-estree': 8.40.0(typescript@5.9.2)
|
||||
'@typescript-eslint/utils': 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)
|
||||
debug: 4.4.1
|
||||
eslint: 9.33.0(jiti@2.5.1)
|
||||
ts-api-utils: 2.1.0(typescript@5.9.2)
|
||||
typescript: 5.9.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/types@8.40.0': {}
|
||||
|
||||
'@typescript-eslint/typescript-estree@8.40.0(typescript@5.7.3)':
|
||||
@ -7638,6 +7766,22 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/typescript-estree@8.40.0(typescript@5.9.2)':
|
||||
dependencies:
|
||||
'@typescript-eslint/project-service': 8.40.0(typescript@5.9.2)
|
||||
'@typescript-eslint/tsconfig-utils': 8.40.0(typescript@5.9.2)
|
||||
'@typescript-eslint/types': 8.40.0
|
||||
'@typescript-eslint/visitor-keys': 8.40.0
|
||||
debug: 4.4.1
|
||||
fast-glob: 3.3.3
|
||||
is-glob: 4.0.3
|
||||
minimatch: 9.0.5
|
||||
semver: 7.7.2
|
||||
ts-api-utils: 2.1.0(typescript@5.9.2)
|
||||
typescript: 5.9.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/utils@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.7.3)':
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.7.0(eslint@9.33.0(jiti@2.5.1))
|
||||
@ -7649,6 +7793,17 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/utils@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)':
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.7.0(eslint@9.33.0(jiti@2.5.1))
|
||||
'@typescript-eslint/scope-manager': 8.40.0
|
||||
'@typescript-eslint/types': 8.40.0
|
||||
'@typescript-eslint/typescript-estree': 8.40.0(typescript@5.9.2)
|
||||
eslint: 9.33.0(jiti@2.5.1)
|
||||
typescript: 5.9.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/visitor-keys@8.40.0':
|
||||
dependencies:
|
||||
'@typescript-eslint/types': 8.40.0
|
||||
@ -8127,30 +8282,30 @@ snapshots:
|
||||
core-js@3.45.0:
|
||||
optional: true
|
||||
|
||||
cosmiconfig-typescript-loader@6.1.0(@types/node@22.13.8)(cosmiconfig@9.0.0(typescript@5.7.3))(typescript@5.7.3):
|
||||
cosmiconfig-typescript-loader@6.1.0(@types/node@24.3.0)(cosmiconfig@9.0.0(typescript@5.9.2))(typescript@5.9.2):
|
||||
dependencies:
|
||||
'@types/node': 22.13.8
|
||||
cosmiconfig: 9.0.0(typescript@5.7.3)
|
||||
'@types/node': 24.3.0
|
||||
cosmiconfig: 9.0.0(typescript@5.9.2)
|
||||
jiti: 2.5.1
|
||||
typescript: 5.7.3
|
||||
typescript: 5.9.2
|
||||
|
||||
cosmiconfig@8.3.6(typescript@5.7.3):
|
||||
cosmiconfig@8.3.6(typescript@5.9.2):
|
||||
dependencies:
|
||||
import-fresh: 3.3.1
|
||||
js-yaml: 4.1.0
|
||||
parse-json: 5.2.0
|
||||
path-type: 4.0.0
|
||||
optionalDependencies:
|
||||
typescript: 5.7.3
|
||||
typescript: 5.9.2
|
||||
|
||||
cosmiconfig@9.0.0(typescript@5.7.3):
|
||||
cosmiconfig@9.0.0(typescript@5.9.2):
|
||||
dependencies:
|
||||
env-paths: 2.2.1
|
||||
import-fresh: 3.3.1
|
||||
js-yaml: 4.1.0
|
||||
parse-json: 5.2.0
|
||||
optionalDependencies:
|
||||
typescript: 5.7.3
|
||||
typescript: 5.9.2
|
||||
|
||||
create-require@1.1.1: {}
|
||||
|
||||
@ -8232,6 +8387,8 @@ snapshots:
|
||||
es-errors: 1.3.0
|
||||
is-data-view: 1.0.2
|
||||
|
||||
date-fns-jalali@4.1.0-0: {}
|
||||
|
||||
date-fns@4.1.0: {}
|
||||
|
||||
debug@3.2.7:
|
||||
@ -8515,21 +8672,21 @@ snapshots:
|
||||
optionalDependencies:
|
||||
source-map: 0.6.1
|
||||
|
||||
eslint-config-next@15.5.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.7.3):
|
||||
eslint-config-next@15.5.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2):
|
||||
dependencies:
|
||||
'@next/eslint-plugin-next': 15.5.0
|
||||
'@rushstack/eslint-patch': 1.12.0
|
||||
'@typescript-eslint/eslint-plugin': 8.40.0(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.7.3))(eslint@9.33.0(jiti@2.5.1))(typescript@5.7.3)
|
||||
'@typescript-eslint/parser': 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.7.3)
|
||||
'@typescript-eslint/eslint-plugin': 8.40.0(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)
|
||||
'@typescript-eslint/parser': 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)
|
||||
eslint: 9.33.0(jiti@2.5.1)
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.33.0(jiti@2.5.1))
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.7.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.33.0(jiti@2.5.1))
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.33.0(jiti@2.5.1))
|
||||
eslint-plugin-jsx-a11y: 6.10.2(eslint@9.33.0(jiti@2.5.1))
|
||||
eslint-plugin-react: 7.37.5(eslint@9.33.0(jiti@2.5.1))
|
||||
eslint-plugin-react-hooks: 5.2.0(eslint@9.33.0(jiti@2.5.1))
|
||||
optionalDependencies:
|
||||
typescript: 5.7.3
|
||||
typescript: 5.9.2
|
||||
transitivePeerDependencies:
|
||||
- eslint-import-resolver-webpack
|
||||
- eslint-plugin-import-x
|
||||
@ -8558,22 +8715,22 @@ snapshots:
|
||||
tinyglobby: 0.2.14
|
||||
unrs-resolver: 1.11.1
|
||||
optionalDependencies:
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.7.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.33.0(jiti@2.5.1))
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.33.0(jiti@2.5.1))
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-module-utils@2.12.1(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.33.0(jiti@2.5.1)):
|
||||
eslint-module-utils@2.12.1(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.33.0(jiti@2.5.1)):
|
||||
dependencies:
|
||||
debug: 3.2.7
|
||||
optionalDependencies:
|
||||
'@typescript-eslint/parser': 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.7.3)
|
||||
'@typescript-eslint/parser': 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)
|
||||
eslint: 9.33.0(jiti@2.5.1)
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.33.0(jiti@2.5.1))
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.7.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.33.0(jiti@2.5.1)):
|
||||
eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.33.0(jiti@2.5.1)):
|
||||
dependencies:
|
||||
'@rtsao/scc': 1.1.0
|
||||
array-includes: 3.1.9
|
||||
@ -8584,7 +8741,7 @@ snapshots:
|
||||
doctrine: 2.1.0
|
||||
eslint: 9.33.0(jiti@2.5.1)
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.33.0(jiti@2.5.1))
|
||||
eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.33.0(jiti@2.5.1))
|
||||
hasown: 2.0.2
|
||||
is-core-module: 2.16.1
|
||||
is-glob: 4.0.3
|
||||
@ -8596,7 +8753,7 @@ snapshots:
|
||||
string.prototype.trimend: 1.0.9
|
||||
tsconfig-paths: 3.15.0
|
||||
optionalDependencies:
|
||||
'@typescript-eslint/parser': 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.7.3)
|
||||
'@typescript-eslint/parser': 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)
|
||||
transitivePeerDependencies:
|
||||
- eslint-import-resolver-typescript
|
||||
- eslint-import-resolver-webpack
|
||||
@ -9321,9 +9478,9 @@ snapshots:
|
||||
strip-ansi: 6.0.1
|
||||
through: 2.3.8
|
||||
|
||||
inquirer@8.2.7(@types/node@22.13.8):
|
||||
inquirer@8.2.7(@types/node@24.3.0):
|
||||
dependencies:
|
||||
'@inquirer/external-editor': 1.0.1(@types/node@22.13.8)
|
||||
'@inquirer/external-editor': 1.0.1(@types/node@24.3.0)
|
||||
ansi-escapes: 4.3.2
|
||||
chalk: 4.1.2
|
||||
cli-cursor: 3.1.0
|
||||
@ -10233,12 +10390,12 @@ snapshots:
|
||||
|
||||
ms@2.1.3: {}
|
||||
|
||||
msw@2.10.5(@types/node@22.13.8)(typescript@5.7.3):
|
||||
msw@2.10.5(@types/node@24.3.0)(typescript@5.9.2):
|
||||
dependencies:
|
||||
'@bundled-es-modules/cookie': 2.0.1
|
||||
'@bundled-es-modules/statuses': 1.0.1
|
||||
'@bundled-es-modules/tough-cookie': 0.1.6
|
||||
'@inquirer/confirm': 5.1.15(@types/node@22.13.8)
|
||||
'@inquirer/confirm': 5.1.15(@types/node@24.3.0)
|
||||
'@mswjs/interceptors': 0.39.6
|
||||
'@open-draft/deferred-promise': 2.2.0
|
||||
'@open-draft/until': 2.1.0
|
||||
@ -10254,7 +10411,7 @@ snapshots:
|
||||
type-fest: 4.41.0
|
||||
yargs: 17.7.2
|
||||
optionalDependencies:
|
||||
typescript: 5.7.3
|
||||
typescript: 5.9.2
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
|
||||
@ -10707,6 +10864,13 @@ snapshots:
|
||||
date-fns: 4.1.0
|
||||
react: 19.1.1
|
||||
|
||||
react-day-picker@9.9.0(react@19.1.1):
|
||||
dependencies:
|
||||
'@date-fns/tz': 1.4.1
|
||||
date-fns: 4.1.0
|
||||
date-fns-jalali: 4.1.0-0
|
||||
react: 19.1.1
|
||||
|
||||
react-dom@19.1.1(react@19.1.1):
|
||||
dependencies:
|
||||
react: 19.1.1
|
||||
@ -11065,14 +11229,14 @@ snapshots:
|
||||
es-errors: 1.3.0
|
||||
es-object-atoms: 1.1.1
|
||||
|
||||
shadcn@2.4.0-canary.13(@types/node@22.13.8)(typescript@5.7.3):
|
||||
shadcn@2.4.0-canary.13(@types/node@24.3.0)(typescript@5.9.2):
|
||||
dependencies:
|
||||
'@antfu/ni': 23.3.1
|
||||
'@babel/core': 7.28.3
|
||||
'@babel/parser': 7.28.3
|
||||
'@babel/plugin-transform-typescript': 7.28.0(@babel/core@7.28.3)
|
||||
commander: 10.0.1
|
||||
cosmiconfig: 8.3.6(typescript@5.7.3)
|
||||
cosmiconfig: 8.3.6(typescript@5.9.2)
|
||||
deepmerge: 4.3.1
|
||||
diff: 5.2.0
|
||||
execa: 7.2.0
|
||||
@ -11080,7 +11244,7 @@ snapshots:
|
||||
fs-extra: 11.3.1
|
||||
https-proxy-agent: 6.2.1
|
||||
kleur: 4.1.5
|
||||
msw: 2.10.5(@types/node@22.13.8)(typescript@5.7.3)
|
||||
msw: 2.10.5(@types/node@24.3.0)(typescript@5.9.2)
|
||||
node-fetch: 3.3.2
|
||||
ora: 6.3.1
|
||||
postcss: 8.5.6
|
||||
@ -11089,7 +11253,7 @@ snapshots:
|
||||
stringify-object: 5.0.0
|
||||
ts-morph: 18.0.0
|
||||
tsconfig-paths: 4.2.0
|
||||
zod: 3.25.76
|
||||
zod: 4.0.17
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- supports-color
|
||||
@ -11427,26 +11591,30 @@ snapshots:
|
||||
dependencies:
|
||||
typescript: 5.7.3
|
||||
|
||||
ts-api-utils@2.1.0(typescript@5.9.2):
|
||||
dependencies:
|
||||
typescript: 5.9.2
|
||||
|
||||
ts-morph@18.0.0:
|
||||
dependencies:
|
||||
'@ts-morph/common': 0.19.0
|
||||
code-block-writer: 12.0.0
|
||||
|
||||
ts-node@10.9.2(@types/node@22.13.8)(typescript@5.7.3):
|
||||
ts-node@10.9.2(@types/node@24.3.0)(typescript@5.9.2):
|
||||
dependencies:
|
||||
'@cspotcode/source-map-support': 0.8.1
|
||||
'@tsconfig/node10': 1.0.11
|
||||
'@tsconfig/node12': 1.0.11
|
||||
'@tsconfig/node14': 1.0.3
|
||||
'@tsconfig/node16': 1.0.4
|
||||
'@types/node': 22.13.8
|
||||
'@types/node': 24.3.0
|
||||
acorn: 8.15.0
|
||||
acorn-walk: 8.3.4
|
||||
arg: 4.1.3
|
||||
create-require: 1.1.1
|
||||
diff: 4.0.2
|
||||
make-error: 1.3.6
|
||||
typescript: 5.7.3
|
||||
typescript: 5.9.2
|
||||
v8-compile-cache-lib: 3.0.1
|
||||
yn: 3.1.1
|
||||
|
||||
@ -11557,6 +11725,8 @@ snapshots:
|
||||
|
||||
typescript@5.7.3: {}
|
||||
|
||||
typescript@5.9.2: {}
|
||||
|
||||
uglify-js@3.19.3:
|
||||
optional: true
|
||||
|
||||
@ -11569,6 +11739,8 @@ snapshots:
|
||||
|
||||
undici-types@6.20.0: {}
|
||||
|
||||
undici-types@7.10.0: {}
|
||||
|
||||
unicorn-magic@0.1.0: {}
|
||||
|
||||
unified@11.0.5:
|
||||
@ -11819,8 +11991,6 @@ snapshots:
|
||||
|
||||
yoctocolors-cjs@2.1.2: {}
|
||||
|
||||
zod@3.25.76: {}
|
||||
|
||||
zod@4.0.17: {}
|
||||
|
||||
zwitch@2.0.4: {}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user