diff --git a/packages/website/package.json b/packages/website/package.json
new file mode 100644
index 000000000..3ed56f66f
--- /dev/null
+++ b/packages/website/package.json
@@ -0,0 +1,33 @@
+{
+ "name": "@paseo/website",
+ "version": "1.0.0",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview",
+ "typecheck": "tsc --noEmit",
+ "deploy": "npm run build && wrangler deploy"
+ },
+ "dependencies": {
+ "@cloudflare/vite-plugin": "^1.20.3",
+ "@cloudflare/workers-types": "^4.20260114.0",
+ "@tanstack/react-router": "^1.120.3",
+ "@tanstack/react-start": "^1.120.3",
+ "react": "^19.1.0",
+ "react-dom": "^19.1.0",
+ "wrangler": "^4.59.1"
+ },
+ "devDependencies": {
+ "@tailwindcss/vite": "^4.1.8",
+ "@types/node": "^22.15.29",
+ "@types/react": "^19.1.6",
+ "@types/react-dom": "^19.1.5",
+ "@vitejs/plugin-react": "^4.5.1",
+ "tailwindcss": "^4.1.8",
+ "typescript": "^5.9.3",
+ "vite": "^7.0.0",
+ "vite-tsconfig-paths": "^5.1.4"
+ }
+}
diff --git a/packages/website/public/favicon.ico b/packages/website/public/favicon.ico
new file mode 100644
index 000000000..170789c6b
Binary files /dev/null and b/packages/website/public/favicon.ico differ
diff --git a/packages/website/public/favicon.svg b/packages/website/public/favicon.svg
new file mode 100644
index 000000000..5d1dedaef
--- /dev/null
+++ b/packages/website/public/favicon.svg
@@ -0,0 +1,11 @@
+
diff --git a/packages/website/public/hero-bg.jpg b/packages/website/public/hero-bg.jpg
new file mode 100644
index 000000000..d366ec223
Binary files /dev/null and b/packages/website/public/hero-bg.jpg differ
diff --git a/packages/website/public/logo.svg b/packages/website/public/logo.svg
new file mode 100644
index 000000000..b8ca9396c
--- /dev/null
+++ b/packages/website/public/logo.svg
@@ -0,0 +1,3 @@
+
diff --git a/packages/website/src/components/butterfly.tsx b/packages/website/src/components/butterfly.tsx
new file mode 100644
index 000000000..afd31f8fa
--- /dev/null
+++ b/packages/website/src/components/butterfly.tsx
@@ -0,0 +1,353 @@
+import { createContext, useContext, useEffect, useRef, useState } from 'react'
+
+// --- Vector math utilities ---
+
+interface Vec2 {
+ x: number
+ y: number
+}
+
+function vec2(x: number, y: number): Vec2 {
+ return { x, y }
+}
+
+function add(a: Vec2, b: Vec2): Vec2 {
+ return { x: a.x + b.x, y: a.y + b.y }
+}
+
+function sub(a: Vec2, b: Vec2): Vec2 {
+ return { x: a.x - b.x, y: a.y - b.y }
+}
+
+function scale(v: Vec2, s: number): Vec2 {
+ return { x: v.x * s, y: v.y * s }
+}
+
+function length(v: Vec2): number {
+ return Math.sqrt(v.x * v.x + v.y * v.y)
+}
+
+function normalize(v: Vec2): Vec2 {
+ const len = length(v)
+ if (len === 0) return { x: 0, y: 0 }
+ return { x: v.x / len, y: v.y / len }
+}
+
+function lerpAngle(a: number, b: number, t: number): number {
+ // Handle angle wrapping for smooth interpolation
+ let diff = b - a
+ while (diff > 180) diff -= 360
+ while (diff < -180) diff += 360
+ return a + diff * t
+}
+
+// --- Cursor context ---
+
+const CursorContext = createContext(null)
+
+export function CursorFieldProvider({ children }: { children: React.ReactNode }) {
+ const [cursor, setCursor] = useState(null)
+
+ useEffect(() => {
+ function handleMouseMove(e: MouseEvent) {
+ setCursor({ x: e.clientX, y: e.clientY })
+ }
+
+ function handleMouseLeave() {
+ setCursor(null)
+ }
+
+ window.addEventListener('mousemove', handleMouseMove)
+ window.addEventListener('mouseleave', handleMouseLeave)
+
+ return () => {
+ window.removeEventListener('mousemove', handleMouseMove)
+ window.removeEventListener('mouseleave', handleMouseLeave)
+ }
+ }, [])
+
+ return {children}
+}
+
+// --- Physics simulation state ---
+
+interface ButterflyState {
+ position: Vec2 // Current offset from origin
+ velocity: Vec2 // Current velocity
+ heading: number // Current facing angle in degrees (0 = down, positive = clockwise)
+ targetHeading: number // Target heading to smoothly rotate towards
+}
+
+// Physics constants
+const WANDER_STRENGTH = 0.15 // How strongly butterflies wander
+const WANDER_RADIUS = 40 // Max wander distance from origin
+const RETURN_STRENGTH = 0.02 // How strongly butterflies return to origin
+const REPULSION_RADIUS = 150 // Cursor repulsion detection radius
+const REPULSION_STRENGTH = 2.5 // How strongly cursor repels
+const MAX_SPEED = 1.5 // Maximum velocity
+const FRICTION = 0.98 // Velocity damping per frame
+const HEADING_SMOOTHING = 0.08 // How quickly heading catches up to velocity direction
+
+// --- Butterfly visual component ---
+
+interface ButterflyVisualProps {
+ size?: number
+ color?: string
+ delay?: number
+ duration?: number
+ direction?: 'left' | 'right'
+ heading?: number
+}
+
+function ButterflyVisual({
+ size = 40,
+ color = '#e8a87c',
+ delay = 0,
+ duration = 0.5,
+ direction = 'right',
+ heading = 0,
+}: ButterflyVisualProps) {
+ const wingStyle = {
+ animationDelay: `${delay}s`,
+ animationDuration: `${duration}s`,
+ }
+
+ const scaleX = direction === 'left' ? -1 : 1
+
+ // Base butterfly has rotate(30deg), so it's already looking 30° down from horizontal.
+ // To keep it in the bottom half (never looking up), rotateZ range is -30° to +90°
+ // -30° = horizontal, 0° = 30° down (base), +90° = 120° down (nearly straight down)
+ const clampedHeading = Math.max(-30, Math.min(90, heading))
+
+ return (
+
+
+
+ )
+}
+
+function darken(hex: string, amount: number): string {
+ const num = parseInt(hex.replace('#', ''), 16)
+ const r = Math.max(0, (num >> 16) - amount)
+ const g = Math.max(0, ((num >> 8) & 0x00ff) - amount)
+ const b = Math.max(0, (num & 0x0000ff) - amount)
+ return `#${((r << 16) | (g << 8) | b).toString(16).padStart(6, '0')}`
+}
+
+// --- Main FloatingButterfly component with physics ---
+
+interface FloatingButterflyProps {
+ style?: React.CSSProperties
+ size?: number
+ color?: string
+ delay?: number
+ duration?: number
+ direction?: 'left' | 'right'
+}
+
+export function FloatingButterfly({ style, size, color, delay, duration, direction }: FloatingButterflyProps) {
+ // Initialize state lazily to avoid hydration mismatch
+ const initialStateRef = useRef<{ x: number; y: number; heading: number } | null>(null)
+ if (initialStateRef.current === null && typeof window !== 'undefined') {
+ // Random starting position within wander radius
+ const angle = Math.random() * Math.PI * 2
+ const dist = Math.random() * WANDER_RADIUS * 0.5
+ initialStateRef.current = {
+ x: Math.cos(angle) * dist,
+ y: Math.sin(angle) * dist,
+ heading: Math.random() * 40 - 10,
+ }
+ }
+
+ const [mounted, setMounted] = useState(false)
+ const [renderState, setRenderState] = useState({ x: 0, y: 0, heading: 0 })
+
+ const stateRef = useRef(null)
+ const elementRef = useRef(null)
+ const rafRef = useRef(null)
+ const wanderAngleRef = useRef(Math.random() * Math.PI * 2)
+ const lastTimeRef = useRef(null)
+
+ const cursor = useContext(CursorContext)
+ const cursorRef = useRef(null)
+
+ useEffect(() => {
+ // Initialize physics state on mount with pre-computed random values
+ const init = initialStateRef.current || { x: 0, y: 0, heading: 0 }
+ stateRef.current = {
+ position: vec2(init.x, init.y),
+ velocity: vec2(0, 0),
+ heading: init.heading,
+ targetHeading: init.heading,
+ }
+ // Set initial render state to match physics state
+ setRenderState({ x: init.x, y: init.y, heading: init.heading })
+ setMounted(true)
+ }, [])
+
+ // Keep cursor ref in sync
+ useEffect(() => {
+ cursorRef.current = cursor
+ }, [cursor])
+
+ useEffect(() => {
+ if (!mounted) return
+
+ function simulate(timestamp: number) {
+ const state = stateRef.current
+ if (!state) return
+
+ // Skip first frame to let things settle, then start tracking time
+ if (lastTimeRef.current === null) {
+ lastTimeRef.current = timestamp
+ rafRef.current = requestAnimationFrame(simulate)
+ return
+ }
+
+ // Calculate delta time (cap at 50ms to avoid huge jumps)
+ const dt = Math.min((timestamp - lastTimeRef.current) / 16.67, 3)
+ lastTimeRef.current = timestamp
+
+ let acceleration = vec2(0, 0)
+
+ // 1. Wander behavior - gentle random movement
+ wanderAngleRef.current += (Math.random() - 0.5) * 0.3 * dt
+ const wanderForce = vec2(
+ Math.cos(wanderAngleRef.current) * WANDER_STRENGTH,
+ Math.sin(wanderAngleRef.current) * WANDER_STRENGTH
+ )
+ acceleration = add(acceleration, wanderForce)
+
+ // 2. Return to origin - soft spring force
+ const distFromOrigin = length(state.position)
+ if (distFromOrigin > WANDER_RADIUS * 0.5) {
+ const returnForce = scale(normalize(scale(state.position, -1)), RETURN_STRENGTH * (distFromOrigin / WANDER_RADIUS))
+ acceleration = add(acceleration, returnForce)
+ }
+
+ // 3. Cursor repulsion
+ const currentCursor = cursorRef.current
+ if (currentCursor && elementRef.current) {
+ const rect = elementRef.current.getBoundingClientRect()
+ const worldPos = vec2(rect.left + rect.width / 2 + state.position.x, rect.top + rect.height / 2 + state.position.y)
+
+ const toCursor = sub(worldPos, currentCursor)
+ const distToCursor = length(toCursor)
+
+ if (distToCursor < REPULSION_RADIUS && distToCursor > 0) {
+ // Inverse square falloff for more natural feel
+ const strength = REPULSION_STRENGTH * Math.pow(1 - distToCursor / REPULSION_RADIUS, 2)
+ const repulsionForce = scale(normalize(toCursor), strength)
+ acceleration = add(acceleration, repulsionForce)
+ }
+ }
+
+ // 4. Update velocity with acceleration
+ state.velocity = add(state.velocity, scale(acceleration, dt))
+
+ // 5. Apply friction
+ state.velocity = scale(state.velocity, Math.pow(FRICTION, dt))
+
+ // 6. Clamp velocity to max speed
+ const speed = length(state.velocity)
+ if (speed > MAX_SPEED) {
+ state.velocity = scale(normalize(state.velocity), MAX_SPEED)
+ }
+
+ // 7. Update position
+ state.position = add(state.position, scale(state.velocity, dt))
+
+ // 8. Soft clamp position to wander radius
+ const posLen = length(state.position)
+ if (posLen > WANDER_RADIUS) {
+ state.position = scale(normalize(state.position), WANDER_RADIUS)
+ // Dampen velocity when hitting boundary
+ state.velocity = scale(state.velocity, 0.8)
+ }
+
+ // 9. Calculate target heading from velocity direction
+ if (speed > 0.1) {
+ // Convert velocity to angle: 0 = moving down screen (+Y), positive = clockwise
+ const rawHeading = Math.atan2(state.velocity.x, state.velocity.y) * (180 / Math.PI)
+ // Clamp to valid range: -30° to +90° (relative to base 30° rotation)
+ state.targetHeading = Math.max(-30, Math.min(90, rawHeading))
+ }
+
+ // 10. Smoothly interpolate heading towards target (already clamped)
+ state.heading = lerpAngle(state.heading, state.targetHeading, HEADING_SMOOTHING * dt)
+
+ // Update render state
+ setRenderState({
+ x: state.position.x,
+ y: state.position.y,
+ heading: state.heading,
+ })
+
+ rafRef.current = requestAnimationFrame(simulate)
+ }
+
+ rafRef.current = requestAnimationFrame(simulate)
+
+ return () => {
+ if (rafRef.current) {
+ cancelAnimationFrame(rafRef.current)
+ }
+ }
+ }, [mounted])
+
+ if (!mounted) return null
+
+ return (
+
+
+
+ )
+}
diff --git a/packages/website/src/routeTree.gen.ts b/packages/website/src/routeTree.gen.ts
new file mode 100644
index 000000000..f1c69c7ab
--- /dev/null
+++ b/packages/website/src/routeTree.gen.ts
@@ -0,0 +1,129 @@
+/* eslint-disable */
+
+// @ts-nocheck
+
+// noinspection JSUnusedGlobalSymbols
+
+// This file was automatically generated by TanStack Router.
+// You should NOT make any changes in this file as it will be overwritten.
+// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
+
+import { Route as rootRouteImport } from './routes/__root'
+import { Route as DocsRouteImport } from './routes/docs'
+import { Route as IndexRouteImport } from './routes/index'
+import { Route as DocsIndexRouteImport } from './routes/docs/index'
+import { Route as DocsBestPracticesRouteImport } from './routes/docs/best-practices'
+
+const DocsRoute = DocsRouteImport.update({
+ id: '/docs',
+ path: '/docs',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const IndexRoute = IndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const DocsIndexRoute = DocsIndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => DocsRoute,
+} as any)
+const DocsBestPracticesRoute = DocsBestPracticesRouteImport.update({
+ id: '/best-practices',
+ path: '/best-practices',
+ getParentRoute: () => DocsRoute,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/': typeof IndexRoute
+ '/docs': typeof DocsRouteWithChildren
+ '/docs/best-practices': typeof DocsBestPracticesRoute
+ '/docs/': typeof DocsIndexRoute
+}
+export interface FileRoutesByTo {
+ '/': typeof IndexRoute
+ '/docs/best-practices': typeof DocsBestPracticesRoute
+ '/docs': typeof DocsIndexRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/': typeof IndexRoute
+ '/docs': typeof DocsRouteWithChildren
+ '/docs/best-practices': typeof DocsBestPracticesRoute
+ '/docs/': typeof DocsIndexRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/' | '/docs' | '/docs/best-practices' | '/docs/'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/' | '/docs/best-practices' | '/docs'
+ id: '__root__' | '/' | '/docs' | '/docs/best-practices' | '/docs/'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ IndexRoute: typeof IndexRoute
+ DocsRoute: typeof DocsRouteWithChildren
+}
+
+declare module '@tanstack/react-router' {
+ interface FileRoutesByPath {
+ '/docs': {
+ id: '/docs'
+ path: '/docs'
+ fullPath: '/docs'
+ preLoaderRoute: typeof DocsRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/': {
+ id: '/'
+ path: '/'
+ fullPath: '/'
+ preLoaderRoute: typeof IndexRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/docs/': {
+ id: '/docs/'
+ path: '/'
+ fullPath: '/docs/'
+ preLoaderRoute: typeof DocsIndexRouteImport
+ parentRoute: typeof DocsRoute
+ }
+ '/docs/best-practices': {
+ id: '/docs/best-practices'
+ path: '/best-practices'
+ fullPath: '/docs/best-practices'
+ preLoaderRoute: typeof DocsBestPracticesRouteImport
+ parentRoute: typeof DocsRoute
+ }
+ }
+}
+
+interface DocsRouteChildren {
+ DocsBestPracticesRoute: typeof DocsBestPracticesRoute
+ DocsIndexRoute: typeof DocsIndexRoute
+}
+
+const DocsRouteChildren: DocsRouteChildren = {
+ DocsBestPracticesRoute: DocsBestPracticesRoute,
+ DocsIndexRoute: DocsIndexRoute,
+}
+
+const DocsRouteWithChildren = DocsRoute._addFileChildren(DocsRouteChildren)
+
+const rootRouteChildren: RootRouteChildren = {
+ IndexRoute: IndexRoute,
+ DocsRoute: DocsRouteWithChildren,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
+
+import type { getRouter } from './router.tsx'
+import type { createStart } from '@tanstack/react-start'
+declare module '@tanstack/react-start' {
+ interface Register {
+ ssr: true
+ router: Awaited>
+ }
+}
diff --git a/packages/website/src/router.tsx b/packages/website/src/router.tsx
new file mode 100644
index 000000000..12ebb735e
--- /dev/null
+++ b/packages/website/src/router.tsx
@@ -0,0 +1,15 @@
+import { createRouter } from '@tanstack/react-router'
+import { routeTree } from './routeTree.gen'
+
+export function getRouter() {
+ return createRouter({
+ routeTree,
+ scrollRestoration: true,
+ })
+}
+
+declare module '@tanstack/react-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
diff --git a/packages/website/src/routes/__root.tsx b/packages/website/src/routes/__root.tsx
new file mode 100644
index 000000000..84158a814
--- /dev/null
+++ b/packages/website/src/routes/__root.tsx
@@ -0,0 +1,51 @@
+import type { ReactNode } from 'react'
+import {
+ Outlet,
+ createRootRoute,
+ HeadContent,
+ Scripts,
+} from '@tanstack/react-router'
+
+export const Route = createRootRoute({
+ head: () => ({
+ meta: [
+ { charSet: 'utf-8' },
+ { name: 'viewport', content: 'width=device-width, initial-scale=1' },
+ { name: 'theme-color', content: '#0a0a0a' },
+ ],
+ links: [
+ { rel: 'icon', href: '/favicon.ico', sizes: '48x48' },
+ { rel: 'icon', href: '/favicon.svg', type: 'image/svg+xml' },
+ { rel: 'apple-touch-icon', href: '/favicon.svg' },
+ { rel: 'preconnect', href: 'https://fonts.googleapis.com' },
+ { rel: 'preconnect', href: 'https://fonts.gstatic.com', crossOrigin: 'anonymous' },
+ {
+ rel: 'stylesheet',
+ href: 'https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;500;600&display=swap',
+ },
+ ],
+ }),
+ component: RootComponent,
+})
+
+function RootComponent() {
+ return (
+
+
+
+ )
+}
+
+function RootDocument({ children }: Readonly<{ children: ReactNode }>) {
+ return (
+
+
+
+
+
+ {children}
+
+
+
+ )
+}
diff --git a/packages/website/src/styles.css b/packages/website/src/styles.css
new file mode 100644
index 000000000..7bf137a67
--- /dev/null
+++ b/packages/website/src/styles.css
@@ -0,0 +1,111 @@
+@import 'tailwindcss';
+
+@theme {
+ --font-serif: 'Playfair Display', serif;
+
+ --color-background: #0a0a0a;
+ --color-foreground: #fafafa;
+ --color-muted: #262626;
+ --color-muted-foreground: #a1a1aa;
+ --color-card: #171717;
+ --color-border: #27272a;
+ --color-primary: #3b82f6;
+ --color-primary-foreground: #ffffff;
+ --color-secondary: #27272a;
+ --color-secondary-foreground: #fafafa;
+
+ --animate-flutter-left: flutter-left 0.6s ease-in-out infinite;
+ --animate-flutter-right: flutter-right 0.6s ease-in-out infinite;
+ --animate-float: float 8s ease-in-out infinite;
+ --animate-flutter-body: flutter-body 0.5s ease-in-out infinite;
+}
+
+@keyframes flutter-left {
+ 0%,
+ 100% {
+ transform: rotateY(0deg) rotateZ(0deg);
+ }
+ 50% {
+ transform: rotateY(50deg) rotateZ(-5deg);
+ }
+}
+
+@keyframes flutter-right {
+ 0%,
+ 100% {
+ transform: rotateY(0deg) rotateZ(0deg);
+ }
+ 50% {
+ transform: rotateY(-50deg) rotateZ(5deg);
+ }
+}
+
+@keyframes flutter-body {
+ 0%,
+ 100% {
+ transform: translateY(0) rotate(0deg);
+ }
+ 50% {
+ transform: translateY(-2px) rotate(-3deg);
+ }
+}
+
+@keyframes float-1 {
+ 0% {
+ transform: translate(0, 0) rotate(0deg);
+ }
+ 20% {
+ transform: translate(12px, -18px) rotate(5deg);
+ }
+ 40% {
+ transform: translate(-8px, -30px) rotate(-3deg);
+ }
+ 60% {
+ transform: translate(-15px, -12px) rotate(2deg);
+ }
+ 80% {
+ transform: translate(5px, 8px) rotate(-4deg);
+ }
+ 100% {
+ transform: translate(0, 0) rotate(0deg);
+ }
+}
+
+@keyframes float-2 {
+ 0% {
+ transform: translate(0, 0) rotate(0deg);
+ }
+ 15% {
+ transform: translate(-10px, -12px) rotate(-4deg);
+ }
+ 35% {
+ transform: translate(8px, -25px) rotate(3deg);
+ }
+ 55% {
+ transform: translate(15px, -8px) rotate(-2deg);
+ }
+ 75% {
+ transform: translate(-5px, 10px) rotate(5deg);
+ }
+ 100% {
+ transform: translate(0, 0) rotate(0deg);
+ }
+}
+
+@keyframes float-3 {
+ 0% {
+ transform: translate(0, 0) rotate(0deg);
+ }
+ 25% {
+ transform: translate(18px, -10px) rotate(3deg);
+ }
+ 50% {
+ transform: translate(5px, -35px) rotate(-5deg);
+ }
+ 75% {
+ transform: translate(-12px, -15px) rotate(4deg);
+ }
+ 100% {
+ transform: translate(0, 0) rotate(0deg);
+ }
+}
diff --git a/packages/website/tsconfig.json b/packages/website/tsconfig.json
new file mode 100644
index 000000000..532ad144d
--- /dev/null
+++ b/packages/website/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ "jsx": "react-jsx",
+ "moduleResolution": "Bundler",
+ "module": "ESNext",
+ "target": "ES2022",
+ "skipLibCheck": true,
+ "strict": true,
+ "strictNullChecks": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "baseUrl": ".",
+ "paths": {
+ "~/*": ["./src/*"]
+ }
+ },
+ "include": ["src/**/*", "vite.config.ts"]
+}
diff --git a/packages/website/vite.config.ts b/packages/website/vite.config.ts
new file mode 100644
index 000000000..176c7c543
--- /dev/null
+++ b/packages/website/vite.config.ts
@@ -0,0 +1,19 @@
+import { defineConfig } from 'vite'
+import tsConfigPaths from 'vite-tsconfig-paths'
+import { tanstackStart } from '@tanstack/react-start/plugin/vite'
+import { cloudflare } from '@cloudflare/vite-plugin'
+import react from '@vitejs/plugin-react'
+import tailwindcss from '@tailwindcss/vite'
+
+export default defineConfig({
+ server: {
+ port: 8082,
+ },
+ plugins: [
+ cloudflare({ viteEnvironment: { name: 'ssr' } }),
+ tsConfigPaths(),
+ tanstackStart(),
+ react(),
+ tailwindcss(),
+ ],
+})
diff --git a/packages/website/wrangler.toml b/packages/website/wrangler.toml
new file mode 100644
index 000000000..ee8aa4236
--- /dev/null
+++ b/packages/website/wrangler.toml
@@ -0,0 +1,13 @@
+name = "paseo-website"
+account_id = "10ed39a1dbf316e30abd0c409bed40d6"
+compatibility_date = "2024-12-01"
+compatibility_flags = ["nodejs_compat"]
+main = "@tanstack/react-start/server-entry"
+
+routes = [
+ { pattern = "paseo.sh", custom_domain = true },
+ { pattern = "www.paseo.sh", custom_domain = true }
+]
+
+[observability]
+enabled = true