Add website package and assets

This commit is contained in:
Mohamed Boudra
2026-01-14 18:37:41 +07:00
parent 358b9aded9
commit 51ed99b8a8
13 changed files with 758 additions and 0 deletions

View File

@@ -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"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,11 @@
<svg width="700" height="700" viewBox="0 0 700 700" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_634_358)">
<rect width="700" height="700" rx="100" fill="#20744A"/>
<path d="M291.495 91.399C333.897 104.892 379.155 135.075 416.229 173.191C453.389 211.394 484.429 259.725 495.708 311.251C497.555 319.693 498.865 328.216 499.586 336.776C509.755 326.554 519.867 317.815 529.89 311.547C540.647 304.821 553.808 299.297 568.641 299.785C584.29 300.299 597.395 307.326 607.747 317.632C632.173 341.947 629.612 372.898 619.872 397.936C610.185 422.833 591.557 447.826 572.732 469.124C553.591 490.78 532.713 510.308 516.779 524.318C508.775 531.355 501.936 537.073 497.07 541.052C494.635 543.043 492.689 544.603 491.334 545.679C490.657 546.217 490.126 546.635 489.756 546.926C489.571 547.071 489.425 547.184 489.321 547.265C489.269 547.305 489.227 547.338 489.196 547.362C489.181 547.374 489.168 547.385 489.157 547.393C489.153 547.397 489.147 547.401 489.144 547.403C489.134 547.4 488.837 547.06 473.001 528.499L489.135 547.411C478.157 555.911 462.033 554.334 453.122 543.89C444.213 533.448 445.887 518.094 456.861 509.592C456.863 509.591 456.865 509.588 456.869 509.586C456.88 509.577 456.902 509.561 456.933 509.536C456.997 509.487 457.101 509.404 457.245 509.292C457.533 509.066 457.979 508.715 458.569 508.247C459.749 507.31 461.506 505.901 463.742 504.073C468.216 500.414 474.589 495.088 482.073 488.508C497.114 475.284 516.315 457.282 533.578 437.75C551.157 417.862 565.26 398.01 571.859 381.048C578.403 364.227 575.681 356.302 570.724 351.367C568.928 349.579 567.744 348.902 567.267 348.676C566.888 348.496 566.811 348.52 566.804 348.52C566.605 348.513 563.971 348.537 557.953 352.3C545.161 360.299 528.815 377.492 506.807 403.867C494.927 418.106 481.871 434.435 467.547 451.957C463.709 457.28 459.503 462.538 454.91 467.717L454.702 467.549C420.808 508.347 380.37 553.856 332.335 593.848C301.853 619.226 262.656 622.597 228.642 614.743C194.834 606.936 162.658 587.448 142.217 561.686C108.054 518.631 100.57 469.801 108.223 427.836C115.56 387.606 137.391 351.005 166.502 331.557C161.248 315.813 156.813 299.49 153.519 283.013C142.593 228.368 143.239 167.031 174.28 119.619C186.922 100.31 205.846 89.1535 227.387 85.2773C248.1 81.5504 270.278 84.648 291.495 91.399ZM378.642 206.356C345.773 172.563 307.463 147.917 275.208 137.654C259.096 132.527 246.171 131.514 236.828 133.195C228.314 134.727 222.227 138.497 217.721 145.38C196.712 177.468 193.858 224.004 203.82 273.827C206.532 287.394 210.127 300.834 214.345 313.817C236.45 310.276 260.156 311.463 281.22 317.11C319.621 327.403 357.501 355.419 357.501 405.654C357.501 435.255 339.111 465.136 307.278 473.815C273.211 483.103 238.854 464.822 213.105 427.541C203.716 413.947 194.443 397.766 185.947 379.89C174.028 392.223 163.08 411.953 158.673 436.118C153.128 466.518 158.514 501.286 183.085 532.253C195.993 548.522 217.742 562.031 240.771 567.349C263.594 572.619 284.147 569.24 298.664 557.154C349.383 514.927 390.709 466.547 426.366 422.952C448.879 390.86 453.195 356.06 445.578 321.265C436.703 280.718 411.425 240.06 378.642 206.356ZM306.296 405.722C306.296 384.769 292.223 370.736 267.284 364.051C256.012 361.03 244.156 360.087 233.095 360.771C240.361 375.935 248.168 389.513 255.897 400.704C275.647 429.298 289.989 427.822 293.247 426.934C298.737 425.437 306.296 418.161 306.296 405.722Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_634_358">
<rect width="700" height="700" rx="100" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 MiB

View File

@@ -0,0 +1,3 @@
<svg width="32" height="32" viewBox="0 0 700 700" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M291.495 91.399C333.897 104.892 379.155 135.075 416.229 173.191C453.389 211.394 484.429 259.725 495.708 311.251C497.555 319.693 498.865 328.216 499.586 336.776C509.755 326.554 519.867 317.815 529.89 311.547C540.647 304.821 553.808 299.297 568.641 299.785C584.29 300.299 597.395 307.326 607.747 317.632C632.173 341.947 629.612 372.898 619.872 397.936C610.185 422.833 591.557 447.826 572.732 469.124C553.591 490.78 532.713 510.308 516.779 524.318C508.775 531.355 501.936 537.073 497.07 541.052C494.635 543.043 492.689 544.603 491.334 545.679C490.657 546.217 490.126 546.635 489.756 546.926C489.571 547.071 489.425 547.184 489.321 547.265C489.269 547.305 489.227 547.338 489.196 547.362C489.181 547.374 489.168 547.385 489.157 547.393C489.153 547.397 489.147 547.401 489.144 547.403C489.134 547.4 488.837 547.06 473.001 528.499L489.135 547.411C478.157 555.911 462.033 554.334 453.122 543.89C444.213 533.448 445.887 518.094 456.861 509.592C456.863 509.591 456.865 509.588 456.869 509.586C456.88 509.577 456.902 509.561 456.933 509.536C456.997 509.487 457.101 509.404 457.245 509.292C457.533 509.066 457.979 508.715 458.569 508.247C459.749 507.31 461.506 505.901 463.742 504.073C468.216 500.414 474.589 495.088 482.073 488.508C497.114 475.284 516.315 457.282 533.578 437.75C551.157 417.862 565.26 398.01 571.859 381.048C578.403 364.227 575.681 356.302 570.724 351.367C568.928 349.579 567.744 348.902 567.267 348.676C566.888 348.496 566.811 348.52 566.804 348.52C566.605 348.513 563.971 348.537 557.953 352.3C545.161 360.299 528.815 377.492 506.807 403.867C494.927 418.106 481.871 434.435 467.547 451.957C463.709 457.28 459.503 462.538 454.91 467.717L454.702 467.549C420.808 508.347 380.37 553.856 332.335 593.848C301.853 619.226 262.656 622.597 228.642 614.743C194.834 606.936 162.658 587.448 142.217 561.686C108.054 518.631 100.57 469.801 108.223 427.836C115.56 387.606 137.391 351.005 166.502 331.557C161.248 315.813 156.813 299.49 153.519 283.013C142.593 228.368 143.239 167.031 174.28 119.619C186.922 100.31 205.846 89.1535 227.387 85.2773C248.1 81.5504 270.278 84.648 291.495 91.399ZM378.642 206.356C345.773 172.563 307.463 147.917 275.208 137.654C259.096 132.527 246.171 131.514 236.828 133.195C228.314 134.727 222.227 138.497 217.721 145.38C196.712 177.468 193.858 224.004 203.82 273.827C206.532 287.394 210.127 300.834 214.345 313.817C236.45 310.276 260.156 311.463 281.22 317.11C319.621 327.403 357.501 355.419 357.501 405.654C357.501 435.255 339.111 465.136 307.278 473.815C273.211 483.103 238.854 464.822 213.105 427.541C203.716 413.947 194.443 397.766 185.947 379.89C174.028 392.223 163.08 411.953 158.673 436.118C153.128 466.518 158.514 501.286 183.085 532.253C195.993 548.522 217.742 562.031 240.771 567.349C263.594 572.619 284.147 569.24 298.664 557.154C349.383 514.927 390.709 466.547 426.366 422.952C448.879 390.86 453.195 356.06 445.578 321.265C436.703 280.718 411.425 240.06 378.642 206.356ZM306.296 405.722C306.296 384.769 292.223 370.736 267.284 364.051C256.012 361.03 244.156 360.087 233.095 360.771C240.361 375.935 248.168 389.513 255.897 400.704C275.647 429.298 289.989 427.822 293.247 426.934C298.737 425.437 306.296 418.161 306.296 405.722Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@@ -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<Vec2 | null>(null)
export function CursorFieldProvider({ children }: { children: React.ReactNode }) {
const [cursor, setCursor] = useState<Vec2 | null>(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 <CursorContext.Provider value={cursor}>{children}</CursorContext.Provider>
}
// --- 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 (
<div
className="animate-flutter-body"
style={{
animationDelay: `${delay}s`,
animationDuration: `${duration}s`,
}}
>
<svg
viewBox="0 0 50 40"
width={size}
height={size * 0.8}
style={{
filter: 'url(#painterly)',
transform: `scaleX(${scaleX}) rotateY(25deg) rotate(30deg) rotateZ(${clampedHeading}deg)`,
transition: 'transform 0.3s ease-out',
}}
>
<defs>
<filter id="painterly" x="-20%" y="-20%" width="140%" height="140%">
<feTurbulence type="fractalNoise" baseFrequency="0.03" numOctaves="2" result="noise" />
<feDisplacementMap in="SourceGraphic" in2="noise" scale="2" xChannelSelector="R" yChannelSelector="G" />
</filter>
</defs>
{/* Far side wings (darker, behind, slightly rotated) */}
<g
className="animate-flutter-left origin-[38px_20px]"
style={{ ...wingStyle, animationDelay: `${delay + 0.08}s` }}
transform="translate(6, 0) rotate(8, 38, 20)"
>
<path d="M38 20 Q20 2 8 10 Q0 18 10 28 Q25 32 38 22" fill={darken(color, 50)} />
<path d="M38 22 Q25 30 18 38 Q28 42 36 34 Q40 28 38 24" fill={darken(color, 50)} />
</g>
{/* Near side wings (main color, front) */}
<g className="animate-flutter-left origin-[38px_20px]" style={wingStyle}>
<path d="M38 20 Q20 2 8 10 Q0 18 10 28 Q25 32 38 22" fill={color} />
</g>
<g className="animate-flutter-left origin-[38px_20px]" style={{ ...wingStyle, animationDelay: `${delay + 0.05}s` }}>
<path d="M38 22 Q25 30 18 38 Q28 42 36 34 Q40 28 38 24" fill={color} />
</g>
</svg>
</div>
)
}
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<ButterflyState | null>(null)
const elementRef = useRef<HTMLDivElement>(null)
const rafRef = useRef<number | null>(null)
const wanderAngleRef = useRef(Math.random() * Math.PI * 2)
const lastTimeRef = useRef<number | null>(null)
const cursor = useContext(CursorContext)
const cursorRef = useRef<Vec2 | null>(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 (
<div
ref={elementRef}
className="absolute pointer-events-none"
style={{
...style,
transform: `translate(${renderState.x}px, ${renderState.y}px)`,
}}
>
<ButterflyVisual size={size} color={color} delay={delay} duration={duration} direction={direction} heading={renderState.heading} />
</div>
)
}

View File

@@ -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<FileRouteTypes>()
import type { getRouter } from './router.tsx'
import type { createStart } from '@tanstack/react-start'
declare module '@tanstack/react-start' {
interface Register {
ssr: true
router: Awaited<ReturnType<typeof getRouter>>
}
}

View File

@@ -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<typeof getRouter>
}
}

View File

@@ -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 (
<RootDocument>
<Outlet />
</RootDocument>
)
}
function RootDocument({ children }: Readonly<{ children: ReactNode }>) {
return (
<html lang="en">
<head>
<HeadContent />
</head>
<body className="antialiased bg-background text-foreground">
{children}
<Scripts />
</body>
</html>
)
}

View File

@@ -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);
}
}

View File

@@ -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"]
}

View File

@@ -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(),
],
})

View File

@@ -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