From 4ec0f386f13b9e56eb73241d6471c35414e11b1e Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Mon, 16 Feb 2026 17:40:34 -0700 Subject: [PATCH] Initial commit: MycoStack.xyz website Epic single-page scrolling site merging Commons Stack ethos with mycelial design principles. Features scroll-driven dark-to-light color transitions, procedural mycelial canvas animation, anastomosis SVG animation, and an interactive network map of connected domains. Co-Authored-By: Claude Opus 4.6 --- .gitignore | 37 + Dockerfile | 14 + app/globals.css | 249 +++++++ app/layout.tsx | 54 ++ app/page.tsx | 27 + components/anastomosis-section.tsx | 239 +++++++ components/compost-section.tsx | 72 ++ components/emergence-section.tsx | 96 +++ components/footer.tsx | 57 ++ components/hero-section.tsx | 57 ++ components/mycelial-canvas.tsx | 208 ++++++ components/mycelium-section.tsx | 119 ++++ components/network-map-section.tsx | 237 +++++++ components/scroll-provider.tsx | 58 ++ components/undernet-section.tsx | 117 ++++ docker-compose.yml | 32 + hooks/use-scroll-progress.ts | 33 + hooks/use-section-reveal.ts | 30 + lib/utils.ts | 6 + next.config.mjs | 9 + nginx.conf | 23 + package.json | 31 + pnpm-lock.yaml | 1011 ++++++++++++++++++++++++++++ postcss.config.mjs | 7 + tsconfig.json | 23 + 25 files changed, 2846 insertions(+) create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 app/globals.css create mode 100644 app/layout.tsx create mode 100644 app/page.tsx create mode 100644 components/anastomosis-section.tsx create mode 100644 components/compost-section.tsx create mode 100644 components/emergence-section.tsx create mode 100644 components/footer.tsx create mode 100644 components/hero-section.tsx create mode 100644 components/mycelial-canvas.tsx create mode 100644 components/mycelium-section.tsx create mode 100644 components/network-map-section.tsx create mode 100644 components/scroll-provider.tsx create mode 100644 components/undernet-section.tsx create mode 100644 docker-compose.yml create mode 100644 hooks/use-scroll-progress.ts create mode 100644 hooks/use-section-reveal.ts create mode 100644 lib/utils.ts create mode 100644 next.config.mjs create mode 100644 nginx.conf create mode 100644 package.json create mode 100644 pnpm-lock.yaml create mode 100644 postcss.config.mjs create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..abf07ab --- /dev/null +++ b/.gitignore @@ -0,0 +1,37 @@ +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files +.env*.local +.env + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..15a506c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,14 @@ +FROM node:20-alpine AS builder +WORKDIR /app +RUN corepack enable && corepack prepare pnpm@latest --activate +COPY package.json pnpm-lock.yaml* ./ +RUN pnpm install --frozen-lockfile +COPY . . +ENV NEXT_TELEMETRY_DISABLED=1 +RUN pnpm build + +FROM nginx:alpine +COPY nginx.conf /etc/nginx/conf.d/default.conf +COPY --from=builder /app/out /usr/share/nginx/html +EXPOSE 80 +CMD ["nginx", "-g", "daemon off;"] diff --git a/app/globals.css b/app/globals.css new file mode 100644 index 0000000..2f61025 --- /dev/null +++ b/app/globals.css @@ -0,0 +1,249 @@ +@import "tailwindcss"; +@import "tw-animate-css"; + +@custom-variant dark (&:is(.dark *)); + +:root { + /* Scroll-driven dynamic colors (set by JS) */ + --scroll-bg: oklch(0.08 0.02 30); + --scroll-fg: oklch(0.85 0.03 80); + --scroll-accent: oklch(0.45 0.12 60); + --scroll-depth: 0; + + /* Static palette tokens */ + --soil-black: oklch(0.08 0.02 30); + --earth-brown: oklch(0.12 0.04 40); + --compost-amber: oklch(0.55 0.15 45); + --forest-deep: oklch(0.16 0.05 150); + --mycelium-green: oklch(0.55 0.18 155); + --mycelium-glow: oklch(0.72 0.20 135); + --undernet-dark: oklch(0.22 0.04 200); + --undernet-teal: oklch(0.50 0.14 220); + --emergence-gold: oklch(0.60 0.16 90); + --canopy-light: oklch(0.94 0.02 110); + --mycopunk-orange: oklch(0.60 0.16 55); + --mycopunk-warm: oklch(0.50 0.13 40); + + /* shadcn-compatible tokens */ + --background: var(--scroll-bg); + --foreground: var(--scroll-fg); + --primary: var(--scroll-accent); + --primary-foreground: oklch(0.98 0.01 80); + --muted: oklch(0.20 0.02 30); + --muted-foreground: oklch(0.60 0.02 80); + --border: oklch(0.25 0.03 30); + --input: oklch(0.25 0.03 30); + --ring: var(--scroll-accent); + --radius: 0.5rem; +} + +@theme inline { + --font-sans: var(--font-geist-sans), system-ui, sans-serif; + --font-serif: var(--font-crimson-pro), Georgia, serif; + --font-mono: var(--font-geist-mono), monospace; + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); + --color-soil-black: var(--soil-black); + --color-earth-brown: var(--earth-brown); + --color-compost-amber: var(--compost-amber); + --color-forest-deep: var(--forest-deep); + --color-mycelium-green: var(--mycelium-green); + --color-mycelium-glow: var(--mycelium-glow); + --color-undernet-dark: var(--undernet-dark); + --color-undernet-teal: var(--undernet-teal); + --color-emergence-gold: var(--emergence-gold); + --color-canopy-light: var(--canopy-light); + --color-mycopunk-orange: var(--mycopunk-orange); + --color-mycopunk-warm: var(--mycopunk-warm); + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + + body { + background-color: var(--scroll-bg, oklch(0.08 0.02 30)); + color: var(--scroll-fg, oklch(0.85 0.03 80)); + transition: background-color 0.05s linear, color 0.05s linear; + overflow-x: hidden; + } + + html { + scroll-behavior: smooth; + } +} + +/* Section scroll-reveal */ +.section-reveal { + opacity: 0; + transform: translateY(40px); + transition: opacity 0.9s cubic-bezier(0.16, 1, 0.3, 1), + transform 0.9s cubic-bezier(0.16, 1, 0.3, 1); +} + +.section-reveal.visible { + opacity: 1; + transform: translateY(0); +} + +/* Noise texture overlay */ +.noise-overlay { + position: relative; +} + +.noise-overlay::after { + content: ""; + position: absolute; + inset: 0; + background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E"); + opacity: 0.03; + pointer-events: none; + mix-blend-mode: overlay; + z-index: 1; +} + +/* Hero title emergence animation */ +@keyframes emerge-letter { + from { + opacity: 0; + transform: translateY(24px); + filter: blur(6px); + } + to { + opacity: 1; + transform: translateY(0); + filter: blur(0); + } +} + +.emerge-letter { + animation: emerge-letter 1s cubic-bezier(0.16, 1, 0.3, 1) forwards; + opacity: 0; +} + +/* Scroll indicator pulse */ +@keyframes scroll-hint { + 0%, + 100% { + opacity: 0.3; + transform: translateY(0); + } + 50% { + opacity: 0.7; + transform: translateY(10px); + } +} + +.scroll-hint { + animation: scroll-hint 2.5s ease-in-out infinite; +} + +/* Mycelial divider between sections */ +.mycelial-divider { + width: 2px; + height: 80px; + margin: 0 auto; + background: linear-gradient( + to bottom, + transparent, + var(--scroll-accent), + transparent + ); + opacity: 0.35; +} + +/* Anastomosis SVG line drawing */ +.draw-line { + stroke-dasharray: 600; + stroke-dashoffset: 600; +} + +.draw-line.animate { + animation: draw-path 2.5s cubic-bezier(0.16, 1, 0.3, 1) forwards; +} + +@keyframes draw-path { + to { + stroke-dashoffset: 0; + } +} + +/* Merge pulse */ +@keyframes merge-pulse { + 0%, + 40% { + opacity: 0; + r: 0; + } + 60% { + opacity: 1; + r: 10; + } + 100% { + opacity: 0.7; + r: 7; + } +} + +.merge-pulse { + animation: merge-pulse 3s cubic-bezier(0.16, 1, 0.3, 1) forwards; + animation-delay: 2s; +} + +/* Network node hover glow */ +.network-node { + transition: all 0.3s ease; +} + +.network-node:hover { + filter: drop-shadow(0 0 12px var(--scroll-accent)); + transform: scale(1.05); +} + +/* Terminal cursor blink */ +@keyframes blink { + 0%, + 100% { + opacity: 1; + } + 50% { + opacity: 0; + } +} + +.cursor-blink { + animation: blink 1s step-end infinite; +} + +/* Card glass effect */ +.glass-card { + background: oklch(0.5 0 0 / 0.08); + backdrop-filter: blur(8px); + border: 1px solid oklch(0.5 0 0 / 0.12); + border-radius: var(--radius-lg); +} + +/* Link style for domain easter eggs */ +a.domain-link { + text-decoration: underline; + text-decoration-style: dotted; + text-underline-offset: 3px; + transition: all 0.2s ease; +} + +a.domain-link:hover { + text-decoration-style: solid; + color: var(--scroll-accent); +} diff --git a/app/layout.tsx b/app/layout.tsx new file mode 100644 index 0000000..b2e7b5c --- /dev/null +++ b/app/layout.tsx @@ -0,0 +1,54 @@ +import type { Metadata } from "next" +import { GeistSans } from "geist/font/sans" +import { GeistMono } from "geist/font/mono" +import { Crimson_Pro } from "next/font/google" +import { ScrollProvider } from "@/components/scroll-provider" +import "./globals.css" + +const crimsonPro = Crimson_Pro({ + weight: ["400", "500", "600", "700"], + subsets: ["latin"], + variable: "--font-crimson-pro", + display: "swap", +}) + +export const metadata: Metadata = { + title: "MycoStack — Technology-Augmented Commons", + description: + "A reboot of the Commons Stack, merging technology with mycelial principles for regenerative systems. Growing from beneath the surface.", + metadataBase: new URL("https://mycostack.xyz"), + icons: { + icon: "data:image/svg+xml,🍄", + }, + openGraph: { + title: "MycoStack — Technology-Augmented Commons", + description: + "Growing regenerative systems from beneath the surface. Commons governance meets mycelial design.", + url: "https://mycostack.xyz", + siteName: "MycoStack", + locale: "en_US", + type: "website", + }, + twitter: { + card: "summary_large_image", + title: "MycoStack — Technology-Augmented Commons", + description: + "Growing regenerative systems from beneath the surface.", + }, +} + +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + + {children} + + + ) +} diff --git a/app/page.tsx b/app/page.tsx new file mode 100644 index 0000000..6e96765 --- /dev/null +++ b/app/page.tsx @@ -0,0 +1,27 @@ +import { MycelialCanvas } from "@/components/mycelial-canvas" +import { HeroSection } from "@/components/hero-section" +import { CompostSection } from "@/components/compost-section" +import { MyceliumSection } from "@/components/mycelium-section" +import { UndernetSection } from "@/components/undernet-section" +import { AnastomosisSection } from "@/components/anastomosis-section" +import { EmergenceSection } from "@/components/emergence-section" +import { NetworkMapSection } from "@/components/network-map-section" +import { Footer } from "@/components/footer" + +export default function Home() { + return ( +
+ +
+ + + + + + + +
+
+
+ ) +} diff --git a/components/anastomosis-section.tsx b/components/anastomosis-section.tsx new file mode 100644 index 0000000..9cbb9de --- /dev/null +++ b/components/anastomosis-section.tsx @@ -0,0 +1,239 @@ +"use client" + +import { useEffect, useRef } from "react" +import { useSectionReveal } from "@/hooks/use-section-reveal" + +export function AnastomosisSection() { + const sectionRef = useSectionReveal() + const svgRef = useRef(null) + + useEffect(() => { + const svg = svgRef.current + if (!svg) return + + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + svg.querySelectorAll(".draw-line").forEach((el) => { + el.classList.add("animate") + }) + svg.querySelector(".merge-dot")?.classList.add("merge-pulse") + } + }) + }, + { threshold: 0.3 } + ) + + observer.observe(svg) + return () => observer.disconnect() + }, []) + + return ( +
+
+ +
+
+

+ Anastomosis +

+

+ /uh-nas-tuh-MOH-sis/ +

+

+ When separate mycelial networks discover each other and merge, + forming new connections. The moment distinct systems recognize their + shared purpose and become one. +

+
+ + {/* SVG Animation: Two networks merging */} +
+ + {/* Left network */} + + {/* Main trunk */} + + {/* Branch up */} + + {/* Branch down */} + + {/* Sub-branch */} + + {/* Small nodes */} + + + + + + + {/* Right network */} + + {/* Main trunk */} + + {/* Branch up */} + + {/* Branch down */} + + {/* Sub-branch */} + + {/* Small nodes */} + + + + + + + {/* Merge connections (appear last) */} + + + + + + + + + + {/* Center merge point */} + + +
+ +
+

+ We are the connections between movements. Commons Stack, MycoFi, the + Undernet — separate networks finding each other, merging, growing + stronger together. The boundaries between projects dissolve. What + remains is the shared mycelium. +

+

+ A space that belongs to its communities, not its platforms. Find{" "} + + (you)rSpace.online + {" "} + and start anastomosing. +

+
+
+
+ ) +} diff --git a/components/compost-section.tsx b/components/compost-section.tsx new file mode 100644 index 0000000..adf68a0 --- /dev/null +++ b/components/compost-section.tsx @@ -0,0 +1,72 @@ +"use client" + +import { useSectionReveal } from "@/hooks/use-section-reveal" + +const CARDS = [ + { + title: "Break Down", + body: "Old systems don't disappear — they decompose. Capitalism's waste becomes the substrate for what grows next. Every collapsing institution releases nutrients back into the commons.", + }, + { + title: "Transform", + body: "Mycelium turns death into life. We turn extractive protocols into regenerative ones. The same energy that powered exploitation can power mutual aid — if we know how to compost it.", + }, + { + title: "Nourish", + body: "What's composted feeds what's growing. Every broken system contains the nutrients for its successor. The question isn't whether the old world will decompose — it's what we grow in its place.", + }, +] + +export function CompostSection() { + const sectionRef = useSectionReveal() + + return ( +
+
+ +
+
+

+ The Compost Layer +

+

+ Decomposing extractive systems into nutrients for regeneration +

+
+ +
+ {CARDS.map((card, i) => ( +
+
+

+ {card.title} +

+

{card.body}

+
+ ))} +
+ +

+ This is{" "} + + compost capitalism + {" "} + — the art of breaking down what no longer serves, so that what comes + next can thrive. +

+
+
+ ) +} diff --git a/components/emergence-section.tsx b/components/emergence-section.tsx new file mode 100644 index 0000000..f10e208 --- /dev/null +++ b/components/emergence-section.tsx @@ -0,0 +1,96 @@ +"use client" + +import { useSectionReveal } from "@/hooks/use-section-reveal" + +const CARDS = [ + { + title: "Regenerative Economics", + body: "New currencies that decompose when hoarded. Mutual credit that flows like nutrients through soil. Quadratic funding that amplifies the grassroots. Economics that serves life instead of extracting from it.", + }, + { + title: "Sovereign Technology", + body: "Community-owned servers. Open protocols. Software that serves its users. Hardware you can repair. Networks no corporation can capture. The tools of liberation, maintained by the communities that depend on them.", + }, + { + title: "Living Commons", + body: "Knowledge, tools, and infrastructure that belong to everyone. Not static archives but living, growing resources — tended by communities, enriched by participation, and freely shared across the mycelial web.", + }, +] + +export function EmergenceSection() { + const sectionRef = useSectionReveal() + + return ( +
+
+ +
+
+

+ Emergence +

+

+ What grows underground eventually breaks the surface. +

+
+ + {/* Quote block */} +
+

+ “The post-capitalist future is not a utopian fantasy. It is + already growing, underground, in the networks we are building + today.” +

+
+ +
+ {CARDS.map((card, i) => ( +
+
+

+ {card.title} +

+

{card.body}

+
+ ))} +
+ +
+

+ Building the{" "} + + post-appitalist.app + + lication layer for a regenerative economy. Tools that serve + communities, not shareholders. +

+

+ Sometimes the best way to see the future is to change your + perspective. Stop{" "} + + trippinballs.lol + {" "} + and start building. +

+
+
+
+ ) +} diff --git a/components/footer.tsx b/components/footer.tsx new file mode 100644 index 0000000..938877d --- /dev/null +++ b/components/footer.tsx @@ -0,0 +1,57 @@ +"use client" + +const LINKS = [ + { name: "MycoFi", url: "https://mycofi.earth" }, + { name: "Mycopunk", url: "https://mycopunk.xyz" }, + { name: "Undernet", url: "https://undernet.earth" }, + { name: "Psilo-Cyber", url: "https://psilo-cyber.net" }, + { name: "Compost Capitalism", url: "https://compostcapitalism.xyz" }, + { name: "Post-Appitalism", url: "https://post-appitalist.app" }, + { name: "(You)rSpace", url: "https://yourspace.online" }, + { name: "Trippin Balls", url: "https://trippinballs.lol" }, +] + +export function Footer() { + return ( +
+
+ {/* Network links */} +
+ {LINKS.map((link) => ( + + {link.name} + + ))} +
+ + {/* Tagline */} +
+

+ Built with compost and code. +

+

+ MycoStack — technology-augmented commons growing from beneath +

+

+ + CC BY-SA 4.0 + + {" "}—{" "} + the more we share, the more we have +

+
+
+
+ ) +} diff --git a/components/hero-section.tsx b/components/hero-section.tsx new file mode 100644 index 0000000..04fef1e --- /dev/null +++ b/components/hero-section.tsx @@ -0,0 +1,57 @@ +"use client" + +import { useSectionReveal } from "@/hooks/use-section-reveal" +import { ChevronDown } from "lucide-react" + +const TITLE = "MycoStack" + +export function HeroSection() { + const sectionRef = useSectionReveal() + + return ( +
+
+ {/* Title with staggered emergence */} +

+ {TITLE.split("").map((letter, i) => ( + + {letter} + + ))} +

+ + {/* Subtitle */} +

+ Technology-augmented commons. +
+ Growing from beneath. +

+ + {/* Terminal tagline */} +
+ > composting capitalism, + growing alternatives + _ +
+
+ + {/* Scroll indicator */} +
+ +
+
+ ) +} diff --git a/components/mycelial-canvas.tsx b/components/mycelial-canvas.tsx new file mode 100644 index 0000000..f777333 --- /dev/null +++ b/components/mycelial-canvas.tsx @@ -0,0 +1,208 @@ +"use client" + +import { useEffect, useRef } from "react" + +interface Hypha { + x: number + y: number + angle: number + speed: number + age: number + maxAge: number + parentX: number + parentY: number + depth: number + branchCount: number +} + +export function MycelialCanvas() { + const canvasRef = useRef(null) + const hyphaeRef = useRef([]) + const frameRef = useRef(0) + const trailCanvasRef = useRef(null) + + useEffect(() => { + const canvas = canvasRef.current + if (!canvas) return + + const ctx = canvas.getContext("2d") + if (!ctx) return + + // Create offscreen trail canvas for persistence + const trailCanvas = document.createElement("canvas") + const trailCtx = trailCanvas.getContext("2d")! + trailCanvasRef.current = trailCanvas + + const resize = () => { + const dpr = Math.min(window.devicePixelRatio || 1, 2) + const w = window.innerWidth + const h = window.innerHeight + canvas.width = w * dpr + canvas.height = h * dpr + canvas.style.width = `${w}px` + canvas.style.height = `${h}px` + ctx.scale(dpr, dpr) + + trailCanvas.width = w * dpr + trailCanvas.height = h * dpr + trailCtx.scale(dpr, dpr) + } + resize() + window.addEventListener("resize", resize) + + const w = () => canvas.width / (Math.min(window.devicePixelRatio || 1, 2)) + const h = () => canvas.height / (Math.min(window.devicePixelRatio || 1, 2)) + + const createHypha = ( + x: number, + y: number, + angle: number, + depth: number + ): Hypha => ({ + x, + y, + angle, + speed: 1.2 + Math.random() * 0.8 - depth * 0.15, + age: 0, + maxAge: 200 + Math.random() * 150 - depth * 20, + parentX: x, + parentY: y, + depth, + branchCount: 0, + }) + + // Seed initial hyphae from bottom + const seedCount = Math.max(3, Math.floor(w() / 250)) + for (let i = 0; i < seedCount; i++) { + const x = + (w() / (seedCount + 1)) * (i + 1) + (Math.random() - 0.5) * 80 + hyphaeRef.current.push( + createHypha( + x, + h() + 10, + -Math.PI / 2 + (Math.random() - 0.5) * 0.5, + 0 + ) + ) + } + + const getAccentColor = () => { + return ( + getComputedStyle(document.documentElement) + .getPropertyValue("--scroll-accent") + .trim() || "oklch(0.55 0.18 155)" + ) + } + + let lastSeed = 0 + + const animate = () => { + const width = w() + const height = h() + + // Clear main canvas + ctx.clearRect(0, 0, width, height) + + // Fade the trail canvas slowly + trailCtx.fillStyle = "rgba(10, 8, 5, 0.008)" + trailCtx.fillRect(0, 0, width, height) + + const accent = getAccentColor() + const alive: Hypha[] = [] + + for (const hypha of hyphaeRef.current) { + hypha.age++ + if (hypha.age >= hypha.maxAge) continue + + // Save previous position + const prevX = hypha.x + const prevY = hypha.y + + // Random walk with upward bias + hypha.angle += (Math.random() - 0.5) * 0.12 + // Gentle gravitropism (slightly toward vertical) + hypha.angle += (-Math.PI / 2 - hypha.angle) * 0.003 + hypha.x += Math.cos(hypha.angle) * hypha.speed + hypha.y += Math.sin(hypha.angle) * hypha.speed + + // Boundary wrapping + if (hypha.x < -20) hypha.x = width + 20 + if (hypha.x > width + 20) hypha.x = -20 + + // Opacity based on age and depth + const ageRatio = hypha.age / hypha.maxAge + const opacity = Math.max( + 0.05, + (1 - ageRatio * 0.9) * (0.6 - hypha.depth * 0.08) + ) + const lineWidth = Math.max(0.3, 2.5 - hypha.depth * 0.4 - ageRatio) + + // Draw on trail canvas for persistence + trailCtx.strokeStyle = accent.replace(")", ` / ${opacity * 0.5})`) + trailCtx.lineWidth = lineWidth + trailCtx.lineCap = "round" + trailCtx.beginPath() + trailCtx.moveTo(prevX, prevY) + trailCtx.lineTo(hypha.x, hypha.y) + trailCtx.stroke() + + // Draw tip glow on main canvas + if (ageRatio < 0.7) { + ctx.fillStyle = accent.replace(")", ` / ${opacity * 0.4})`) + ctx.beginPath() + ctx.arc(hypha.x, hypha.y, lineWidth + 1, 0, Math.PI * 2) + ctx.fill() + } + + // Branching + if ( + hypha.age > 25 && + hypha.age < hypha.maxAge * 0.7 && + hypha.branchCount < 3 && + hypha.depth < 5 && + Math.random() > 0.975 + ) { + const branchAngle = + hypha.angle + (Math.random() > 0.5 ? 1 : -1) * (0.4 + Math.random() * 0.5) + alive.push(createHypha(hypha.x, hypha.y, branchAngle, hypha.depth + 1)) + hypha.branchCount++ + } + + alive.push(hypha) + } + + // Draw trail canvas behind + ctx.drawImage(trailCanvas, 0, 0, width, height) + + // Cap and manage + hyphaeRef.current = alive.length > 500 ? alive.slice(-400) : alive + + // Periodically seed new roots + const now = Date.now() + if (now - lastSeed > 3000 && alive.length < 350) { + lastSeed = now + const x = Math.random() * width + hyphaeRef.current.push( + createHypha(x, height + 10, -Math.PI / 2 + (Math.random() - 0.5) * 0.6, 0) + ) + } + + frameRef.current = requestAnimationFrame(animate) + } + + animate() + + return () => { + window.removeEventListener("resize", resize) + cancelAnimationFrame(frameRef.current) + } + }, []) + + return ( + + ) +} diff --git a/components/mycelium-section.tsx b/components/mycelium-section.tsx new file mode 100644 index 0000000..271d63f --- /dev/null +++ b/components/mycelium-section.tsx @@ -0,0 +1,119 @@ +"use client" + +import { useSectionReveal } from "@/hooks/use-section-reveal" + +const PRINCIPLES = [ + { + title: "Nutrient Cycling", + body: "Resources flow where they're needed, not where they're hoarded. Mycelial currencies route value like fungi route nutrients — sensing scarcity, bridging gaps, feeding the weak to strengthen the whole. This is the economics of the forest floor.", + icon: ( + + + + + + + + + + ), + }, + { + title: "Mutual Aid", + body: "Every node strengthens the network. Every network strengthens each node. In a mycelial system, there are no freeloaders and no extractors — only participants in a web of reciprocal support. This is the peer-for-peer protocol.", + icon: ( + + + + + + + + ), + }, + { + title: "Distributed Intelligence", + body: "No central brain. No single point of failure. Intelligence emerges from connection, from the ten thousand chemical conversations happening simultaneously across the network. The wisdom is in the web, not the node.", + icon: ( + + + + + + + + + ), + }, +] + +export function MyceliumSection() { + const sectionRef = useSectionReveal() + + return ( +
+
+ +
+
+

+ The Mycelial Network +

+

+ Three principles from the forest floor, applied to human systems +

+
+ + {PRINCIPLES.map((principle, i) => ( +
+
+ {principle.icon} +
+
+

+ {principle.title} +

+

+ {principle.body} +

+
+ {i < PRINCIPLES.length - 1 && ( +
+ )} +
+ ))} + + {/* Connecting line between principles - visual only */} +
+

+ These are the protocols of{" "} + + mycofi.earth + + . The economics of interconnection, first practiced by fungi four + hundred million years before capitalism. Read more about the + philosophy at{" "} + + mycopunk.xyz + + . +

+
+
+
+ ) +} diff --git a/components/network-map-section.tsx b/components/network-map-section.tsx new file mode 100644 index 0000000..5735493 --- /dev/null +++ b/components/network-map-section.tsx @@ -0,0 +1,237 @@ +"use client" + +import { useState } from "react" +import { useSectionReveal } from "@/hooks/use-section-reveal" +import { ExternalLink } from "lucide-react" + +interface NetworkNode { + name: string + domain: string + description: string + x: number + y: number + primary?: boolean +} + +const NODES: NetworkNode[] = [ + { + name: "MycoStack", + domain: "mycostack.xyz", + description: "Technology-augmented commons", + x: 50, + y: 45, + primary: true, + }, + { + name: "MycoFi", + domain: "mycofi.earth", + description: "Mycoeconomics & regenerative currencies", + x: 25, + y: 20, + }, + { + name: "Mycopunk", + domain: "mycopunk.xyz", + description: "Building from beneath the surface", + x: 75, + y: 15, + }, + { + name: "Compost Capitalism", + domain: "compostcapitalism.xyz", + description: "Decomposing extractive systems", + x: 12, + y: 50, + }, + { + name: "The Undernet", + domain: "undernet.earth", + description: "Community-owned infrastructure", + x: 88, + y: 45, + }, + { + name: "Post-Appitalism", + domain: "post-appitalist.app", + description: "Tools beyond extractive platforms", + x: 22, + y: 78, + }, + { + name: "(You)rSpace", + domain: "yourspace.online", + description: "Spaces that belong to communities", + x: 60, + y: 80, + }, + { + name: "Psilo-Cyber", + domain: "psilo-cyber.net", + description: "Encrypted mesh networks", + x: 82, + y: 72, + }, + { + name: "Trippin Balls", + domain: "trippinballs.lol", + description: "Expand your perspective", + x: 42, + y: 88, + }, +] + +const CONNECTIONS: [string, string][] = [ + ["mycostack.xyz", "mycofi.earth"], + ["mycostack.xyz", "undernet.earth"], + ["mycostack.xyz", "compostcapitalism.xyz"], + ["mycostack.xyz", "yourspace.online"], + ["mycofi.earth", "mycopunk.xyz"], + ["undernet.earth", "psilo-cyber.net"], + ["compostcapitalism.xyz", "post-appitalist.app"], + ["post-appitalist.app", "yourspace.online"], + ["yourspace.online", "trippinballs.lol"], + ["mycopunk.xyz", "psilo-cyber.net"], + ["mycofi.earth", "post-appitalist.app"], + ["mycopunk.xyz", "undernet.earth"], +] + +function getNode(domain: string) { + return NODES.find((n) => n.domain === domain) +} + +export function NetworkMapSection() { + const sectionRef = useSectionReveal() + const [hovered, setHovered] = useState(null) + + const isConnected = (domain: string) => { + if (!hovered) return false + return CONNECTIONS.some( + ([a, b]) => + (a === hovered && b === domain) || (b === hovered && a === domain) + ) + } + + const getLineOpacity = (a: string, b: string) => { + if (!hovered) return 0.15 + if ( + (a === hovered || b === hovered) && + (isConnected(a) || isConnected(b) || a === hovered || b === hovered) + ) + return 0.5 + return 0.06 + } + + return ( +
+
+ +
+
+

+ The Network of Networks +

+

+ Every node strengthens the whole. Every connection multiplies + possibility. +

+
+ + {/* Desktop: SVG network map */} +
+
+ + {/* Connection lines */} + {CONNECTIONS.map(([a, b]) => { + const nodeA = getNode(a) + const nodeB = getNode(b) + if (!nodeA || !nodeB) return null + return ( + + ) + })} + + + {/* Node cards */} + {NODES.map((node) => { + const isActive = hovered === node.domain + const connected = isConnected(node.domain) + const dimmed = hovered && !isActive && !connected + + return ( + setHovered(node.domain)} + onMouseLeave={() => setHovered(null)} + > +
+
+ {node.name} +
+
+ {node.domain} + +
+ {isActive && ( +
+ {node.description} +
+ )} +
+
+ ) + })} +
+
+ + {/* Mobile: Simple card list */} + +
+
+ ) +} diff --git a/components/scroll-provider.tsx b/components/scroll-provider.tsx new file mode 100644 index 0000000..979824d --- /dev/null +++ b/components/scroll-provider.tsx @@ -0,0 +1,58 @@ +"use client" + +import { useScrollProgress } from "@/hooks/use-scroll-progress" +import { useEffect } from "react" + +// Color stops: [scrollPos, bg-L, bg-C, bg-H, fg-L, fg-C, fg-H, accent-L, accent-C, accent-H] +const COLOR_STOPS: number[][] = [ + [0.0, 0.08, 0.02, 30, 0.85, 0.03, 80, 0.45, 0.12, 60], + [0.15, 0.12, 0.04, 40, 0.82, 0.04, 80, 0.55, 0.15, 45], + [0.3, 0.16, 0.05, 150, 0.88, 0.05, 145, 0.55, 0.18, 155], + [0.5, 0.22, 0.04, 200, 0.9, 0.03, 200, 0.5, 0.14, 220], + [0.65, 0.45, 0.06, 140, 0.15, 0.03, 150, 0.65, 0.18, 145], + [0.8, 0.85, 0.04, 110, 0.15, 0.04, 100, 0.6, 0.16, 90], + [0.92, 0.94, 0.02, 110, 0.12, 0.03, 100, 0.55, 0.18, 155], +] + +function lerp(a: number, b: number, t: number) { + return a + (b - a) * t +} + +function lerpHue(a: number, b: number, t: number) { + let diff = b - a + if (diff > 180) diff -= 360 + if (diff < -180) diff += 360 + return ((a + diff * t) % 360 + 360) % 360 +} + +function interpolateColors(progress: number) { + let i = 0 + for (; i < COLOR_STOPS.length - 1; i++) { + if (progress <= COLOR_STOPS[i + 1][0]) break + } + const stop = COLOR_STOPS[i] + const next = COLOR_STOPS[Math.min(i + 1, COLOR_STOPS.length - 1)] + const range = next[0] - stop[0] + const t = range > 0 ? Math.min(1, Math.max(0, (progress - stop[0]) / range)) : 0 + + return { + bg: `oklch(${lerp(stop[1], next[1], t).toFixed(3)} ${lerp(stop[2], next[2], t).toFixed(3)} ${lerpHue(stop[3], next[3], t).toFixed(1)})`, + fg: `oklch(${lerp(stop[4], next[4], t).toFixed(3)} ${lerp(stop[5], next[5], t).toFixed(3)} ${lerpHue(stop[6], next[6], t).toFixed(1)})`, + accent: `oklch(${lerp(stop[7], next[7], t).toFixed(3)} ${lerp(stop[8], next[8], t).toFixed(3)} ${lerpHue(stop[9], next[9], t).toFixed(1)})`, + } +} + +export function ScrollProvider({ children }: { children: React.ReactNode }) { + const progress = useScrollProgress() + + useEffect(() => { + const { bg, fg, accent } = interpolateColors(progress) + const root = document.documentElement + root.style.setProperty("--scroll-bg", bg) + root.style.setProperty("--scroll-fg", fg) + root.style.setProperty("--scroll-accent", accent) + root.style.setProperty("--scroll-depth", String(progress)) + }, [progress]) + + return <>{children} +} diff --git a/components/undernet-section.tsx b/components/undernet-section.tsx new file mode 100644 index 0000000..3c85749 --- /dev/null +++ b/components/undernet-section.tsx @@ -0,0 +1,117 @@ +"use client" + +import { useSectionReveal } from "@/hooks/use-section-reveal" + +const ASCII_NETWORK = ` [node-01] ──── [node-02] + │ │ + │ ┌─────────┘ + │ │ + [node-03] ──── [node-04] + │ │ + └──────┬───────┘ + │ + [mesh-05] + │ + ┌──────┴───────┐ + │ │ + [node-06] ──── [node-07]` + +export function UndernetSection() { + const sectionRef = useSectionReveal() + + return ( +
+
+ +
+
+

+ The Undernet +

+
+ > connecting nodes... + building resilience... + _ +
+
+ +
+ {/* Description */} +
+

+ Community-Owned Infrastructure +

+
+

+ Beneath the extractive platforms, a different kind of + infrastructure is growing. Self-provisioned. Privacy-first. + Data sovereign. Locally resilient. +

+

+ Community servers. Encrypted mesh networks. Open protocols that + no corporation can shut down. Hardware owned by the people who + depend on it. Software that serves its users instead of + surveilling them. +

+

+ This is the{" "} + + undernet.earth + {" "} + — the network beneath the network. Where the{" "} + + psilo-cyber.net + + work grows, encrypted and entangled, through the substrate of + the old world. +

+
+ +
+
+ protocol: fog computing +
+
+ governance: mycological + consensus +
+
+ ownership: community +
+
+ surveillance:{" "} + none +
+
+
+ + {/* ASCII Network Diagram */} +
+
+ {ASCII_NETWORK} +
+

+ // every node is sovereign +
+ // every connection is encrypted +
// the network has no center +

+
+
+
+
+ ) +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..929e5a6 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,32 @@ +services: + mycostack-website: + build: . + container_name: mycostack-website + restart: unless-stopped + security_opt: + - no-new-privileges:true + cap_drop: + - ALL + cap_add: + - NET_BIND_SERVICE + - CHOWN + - SETGID + - SETUID + - DAC_OVERRIDE + read_only: true + tmpfs: + - /tmp + - /var/cache/nginx + - /var/run + networks: + - traefik-public + labels: + - "traefik.enable=true" + - "traefik.http.routers.mycostack.rule=Host(`mycostack.xyz`) || Host(`www.mycostack.xyz`)" + - "traefik.http.routers.mycostack.entrypoints=web" + - "traefik.http.services.mycostack.loadbalancer.server.port=80" + - "traefik.docker.network=traefik-public" + +networks: + traefik-public: + external: true diff --git a/hooks/use-scroll-progress.ts b/hooks/use-scroll-progress.ts new file mode 100644 index 0000000..c01bdf9 --- /dev/null +++ b/hooks/use-scroll-progress.ts @@ -0,0 +1,33 @@ +"use client" + +import { useState, useEffect, useCallback } from "react" + +export function useScrollProgress() { + const [progress, setProgress] = useState(0) + + const handleScroll = useCallback(() => { + const scrollTop = window.scrollY + const docHeight = + document.documentElement.scrollHeight - window.innerHeight + const scrollPercent = docHeight > 0 ? scrollTop / docHeight : 0 + setProgress(Math.min(1, Math.max(0, scrollPercent))) + }, []) + + useEffect(() => { + let ticking = false + const onScroll = () => { + if (!ticking) { + requestAnimationFrame(() => { + handleScroll() + ticking = false + }) + ticking = true + } + } + window.addEventListener("scroll", onScroll, { passive: true }) + handleScroll() + return () => window.removeEventListener("scroll", onScroll) + }, [handleScroll]) + + return progress +} diff --git a/hooks/use-section-reveal.ts b/hooks/use-section-reveal.ts new file mode 100644 index 0000000..583ecb0 --- /dev/null +++ b/hooks/use-section-reveal.ts @@ -0,0 +1,30 @@ +"use client" + +import { useEffect, useRef } from "react" + +export function useSectionReveal() { + const ref = useRef(null) + + useEffect(() => { + const el = ref.current + if (!el) return + + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + entry.target.classList.add("visible") + } + }) + }, + { threshold: 0.15, rootMargin: "0px 0px -50px 0px" } + ) + + const revealElements = el.querySelectorAll(".section-reveal") + revealElements.forEach((child) => observer.observe(child)) + + return () => observer.disconnect() + }, []) + + return ref +} diff --git a/lib/utils.ts b/lib/utils.ts new file mode 100644 index 0000000..d084cca --- /dev/null +++ b/lib/utils.ts @@ -0,0 +1,6 @@ +import { type ClassValue, clsx } from "clsx" +import { twMerge } from "tailwind-merge" + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} diff --git a/next.config.mjs b/next.config.mjs new file mode 100644 index 0000000..5070720 --- /dev/null +++ b/next.config.mjs @@ -0,0 +1,9 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + output: 'export', + eslint: { ignoreDuringBuilds: true }, + typescript: { ignoreBuildErrors: true }, + images: { unoptimized: true }, +} + +export default nextConfig diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..c7fee4e --- /dev/null +++ b/nginx.conf @@ -0,0 +1,23 @@ +server { + listen 80; + server_name _; + root /usr/share/nginx/html; + index index.html; + + location / { + try_files $uri $uri/ $uri.html =404; + } + + error_page 404 /404.html; + + location /_next/static/ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + gzip on; + gzip_vary on; + gzip_proxied any; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml text/javascript image/svg+xml; + gzip_min_length 1000; +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..f1dcc06 --- /dev/null +++ b/package.json @@ -0,0 +1,31 @@ +{ + "name": "mycostack-website", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev --turbopack", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "geist": "^1.3.1", + "lucide-react": "^0.454.0", + "next": "^15.3.3", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "tailwind-merge": "^2.5.5" + }, + "devDependencies": { + "@tailwindcss/postcss": "^4.1.9", + "@types/node": "^22.15.0", + "@types/react": "^19.1.0", + "@types/react-dom": "^19.1.0", + "postcss": "^8.5.4", + "tailwindcss": "^4.1.9", + "tw-animate-css": "^1.3.3", + "typescript": "^5.8.3" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..3ef99d2 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,1011 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + class-variance-authority: + specifier: ^0.7.1 + version: 0.7.1 + clsx: + specifier: ^2.1.1 + version: 2.1.1 + geist: + specifier: ^1.3.1 + version: 1.7.0(next@15.5.12(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) + lucide-react: + specifier: ^0.454.0 + version: 0.454.0(react@19.2.4) + next: + specifier: ^15.3.3 + version: 15.5.12(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react: + specifier: ^19.1.0 + version: 19.2.4 + react-dom: + specifier: ^19.1.0 + version: 19.2.4(react@19.2.4) + tailwind-merge: + specifier: ^2.5.5 + version: 2.6.1 + devDependencies: + '@tailwindcss/postcss': + specifier: ^4.1.9 + version: 4.1.18 + '@types/node': + specifier: ^22.15.0 + version: 22.19.11 + '@types/react': + specifier: ^19.1.0 + version: 19.2.14 + '@types/react-dom': + specifier: ^19.1.0 + version: 19.2.3(@types/react@19.2.14) + postcss: + specifier: ^8.5.4 + version: 8.5.6 + tailwindcss: + specifier: ^4.1.9 + version: 4.1.18 + tw-animate-css: + specifier: ^1.3.3 + version: 1.4.0 + typescript: + specifier: ^5.8.3 + version: 5.9.3 + +packages: + + '@alloc/quick-lru@5.2.0': + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + + '@emnapi/runtime@1.8.1': + resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==} + + '@img/colour@1.0.0': + resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==} + engines: {node: '>=18'} + + '@img/sharp-darwin-arm64@0.34.5': + resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.34.5': + resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.2.4': + resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.2.4': + resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.2.4': + resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linux-arm@1.2.4': + resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} + cpu: [arm] + os: [linux] + + '@img/sharp-libvips-linux-ppc64@1.2.4': + resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} + cpu: [ppc64] + os: [linux] + + '@img/sharp-libvips-linux-riscv64@1.2.4': + resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} + cpu: [riscv64] + os: [linux] + + '@img/sharp-libvips-linux-s390x@1.2.4': + resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} + cpu: [s390x] + os: [linux] + + '@img/sharp-libvips-linux-x64@1.2.4': + resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} + cpu: [x64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} + cpu: [x64] + os: [linux] + + '@img/sharp-linux-arm64@0.34.5': + resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linux-arm@0.34.5': + resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + + '@img/sharp-linux-ppc64@0.34.5': + resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ppc64] + os: [linux] + + '@img/sharp-linux-riscv64@0.34.5': + resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [riscv64] + os: [linux] + + '@img/sharp-linux-s390x@0.34.5': + resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + + '@img/sharp-linux-x64@0.34.5': + resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-linuxmusl-arm64@0.34.5': + resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linuxmusl-x64@0.34.5': + resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-wasm32@0.34.5': + resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.34.5': + resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.34.5': + resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.34.5': + resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@next/env@15.5.12': + resolution: {integrity: sha512-pUvdJN1on574wQHjaBfNGDt9Mz5utDSZFsIIQkMzPgNS8ZvT4H2mwOrOIClwsQOb6EGx5M76/CZr6G8i6pSpLg==} + + '@next/swc-darwin-arm64@15.5.12': + resolution: {integrity: sha512-RnRjBtH8S8eXCpUNkQ+543DUc7ys8y15VxmFU9HRqlo9BG3CcBUiwNtF8SNoi2xvGCVJq1vl2yYq+3oISBS0Zg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@next/swc-darwin-x64@15.5.12': + resolution: {integrity: sha512-nqa9/7iQlboF1EFtNhWxQA0rQstmYRSBGxSM6g3GxvxHxcoeqVXfGNr9stJOme674m2V7r4E3+jEhhGvSQhJRA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@next/swc-linux-arm64-gnu@15.5.12': + resolution: {integrity: sha512-dCzAjqhDHwmoB2M4eYfVKqXs99QdQxNQVpftvP1eGVppamXh/OkDAwV737Zr0KPXEqRUMN4uCjh6mjO+XtF3Mw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@next/swc-linux-arm64-musl@15.5.12': + resolution: {integrity: sha512-+fpGWvQiITgf7PUtbWY1H7qUSnBZsPPLyyq03QuAKpVoTy/QUx1JptEDTQMVvQhvizCEuNLEeghrQUyXQOekuw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@next/swc-linux-x64-gnu@15.5.12': + resolution: {integrity: sha512-jSLvgdRRL/hrFAPqEjJf1fFguC719kmcptjNVDJl26BnJIpjL3KH5h6mzR4mAweociLQaqvt4UyzfbFjgAdDcw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@next/swc-linux-x64-musl@15.5.12': + resolution: {integrity: sha512-/uaF0WfmYqQgLfPmN6BvULwxY0dufI2mlN2JbOKqqceZh1G4hjREyi7pg03zjfyS6eqNemHAZPSoP84x17vo6w==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@next/swc-win32-arm64-msvc@15.5.12': + resolution: {integrity: sha512-xhsL1OvQSfGmlL5RbOmU+FV120urrgFpYLq+6U8C6KIym32gZT6XF/SDE92jKzzlPWskkbjOKCpqk5m4i8PEfg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@next/swc-win32-x64-msvc@15.5.12': + resolution: {integrity: sha512-Z1Dh6lhFkxvBDH1FoW6OU/L6prYwPSlwjLiZkExIAh8fbP6iI/M7iGTQAJPYJ9YFlWobCZ1PHbchFhFYb2ADkw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@swc/helpers@0.5.15': + resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} + + '@tailwindcss/node@4.1.18': + resolution: {integrity: sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==} + + '@tailwindcss/oxide-android-arm64@4.1.18': + resolution: {integrity: sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.1.18': + resolution: {integrity: sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.1.18': + resolution: {integrity: sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.1.18': + resolution: {integrity: sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18': + resolution: {integrity: sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.18': + resolution: {integrity: sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-musl@4.1.18': + resolution: {integrity: sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-gnu@4.1.18': + resolution: {integrity: sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-musl@4.1.18': + resolution: {integrity: sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-wasm32-wasi@4.1.18': + resolution: {integrity: sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.18': + resolution: {integrity: sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.1.18': + resolution: {integrity: sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.1.18': + resolution: {integrity: sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==} + engines: {node: '>= 10'} + + '@tailwindcss/postcss@4.1.18': + resolution: {integrity: sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g==} + + '@types/node@22.19.11': + resolution: {integrity: sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w==} + + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react@19.2.14': + resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} + + caniuse-lite@1.0.30001770: + resolution: {integrity: sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw==} + + class-variance-authority@0.7.1: + resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + + client-only@0.0.1: + resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + enhanced-resolve@5.19.0: + resolution: {integrity: sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==} + engines: {node: '>=10.13.0'} + + geist@1.7.0: + resolution: {integrity: sha512-ZaoiZwkSf0DwwB1ncdLKp+ggAldqxl5L1+SXaNIBGkPAqcu+xjVJLxlf3/S8vLt9UHx1xu5fz3lbzKCj5iOVdQ==} + peerDependencies: + next: '>=13.2.0' + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + hasBin: true + + lightningcss-android-arm64@1.30.2: + resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.30.2: + resolution: {integrity: sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.30.2: + resolution: {integrity: sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.30.2: + resolution: {integrity: sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.30.2: + resolution: {integrity: sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.30.2: + resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.30.2: + resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.30.2: + resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.30.2: + resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.30.2: + resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.30.2: + resolution: {integrity: sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.30.2: + resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==} + engines: {node: '>= 12.0.0'} + + lucide-react@0.454.0: + resolution: {integrity: sha512-hw7zMDwykCLnEzgncEEjHeA6+45aeEzRYuKHuyRSOPkhko+J3ySGjGIzu+mmMfDFG1vazHepMaYFYHbTFAZAAQ==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + next@15.5.12: + resolution: {integrity: sha512-Fi/wQ4Etlrn60rz78bebG1i1SR20QxvV8tVp6iJspjLUSHcZoeUXCt+vmWoEcza85ElZzExK/jJ/F6SvtGktjA==} + engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.1.0 + '@playwright/test': ^1.51.1 + babel-plugin-react-compiler: '*' + react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + sass: ^1.3.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@playwright/test': + optional: true + babel-plugin-react-compiler: + optional: true + sass: + optional: true + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + postcss@8.4.31: + resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} + engines: {node: ^10 || ^12 || >=14} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + react-dom@19.2.4: + resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==} + peerDependencies: + react: ^19.2.4 + + react@19.2.4: + resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==} + engines: {node: '>=0.10.0'} + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + + sharp@0.34.5: + resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + styled-jsx@5.1.6: + resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} + engines: {node: '>= 12.0.0'} + peerDependencies: + '@babel/core': '*' + babel-plugin-macros: '*' + react: '>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0' + peerDependenciesMeta: + '@babel/core': + optional: true + babel-plugin-macros: + optional: true + + tailwind-merge@2.6.1: + resolution: {integrity: sha512-Oo6tHdpZsGpkKG88HJ8RR1rg/RdnEkQEfMoEk2x1XRI3F1AxeU+ijRXpiVUF4UbLfcxxRGw6TbUINKYdWVsQTQ==} + + tailwindcss@4.1.18: + resolution: {integrity: sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==} + + tapable@2.3.0: + resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} + engines: {node: '>=6'} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tw-animate-css@1.4.0: + resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + +snapshots: + + '@alloc/quick-lru@5.2.0': {} + + '@emnapi/runtime@1.8.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@img/colour@1.0.0': + optional: true + + '@img/sharp-darwin-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.2.4 + optional: true + + '@img/sharp-darwin-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.2.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.2.4': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-riscv64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-s390x@1.2.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + optional: true + + '@img/sharp-linux-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.2.4 + optional: true + + '@img/sharp-linux-arm@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.2.4 + optional: true + + '@img/sharp-linux-ppc64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.2.4 + optional: true + + '@img/sharp-linux-riscv64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.2.4 + optional: true + + '@img/sharp-linux-s390x@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.2.4 + optional: true + + '@img/sharp-linux-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + optional: true + + '@img/sharp-wasm32@0.34.5': + dependencies: + '@emnapi/runtime': 1.8.1 + optional: true + + '@img/sharp-win32-arm64@0.34.5': + optional: true + + '@img/sharp-win32-ia32@0.34.5': + optional: true + + '@img/sharp-win32-x64@0.34.5': + optional: true + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@next/env@15.5.12': {} + + '@next/swc-darwin-arm64@15.5.12': + optional: true + + '@next/swc-darwin-x64@15.5.12': + optional: true + + '@next/swc-linux-arm64-gnu@15.5.12': + optional: true + + '@next/swc-linux-arm64-musl@15.5.12': + optional: true + + '@next/swc-linux-x64-gnu@15.5.12': + optional: true + + '@next/swc-linux-x64-musl@15.5.12': + optional: true + + '@next/swc-win32-arm64-msvc@15.5.12': + optional: true + + '@next/swc-win32-x64-msvc@15.5.12': + optional: true + + '@swc/helpers@0.5.15': + dependencies: + tslib: 2.8.1 + + '@tailwindcss/node@4.1.18': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.19.0 + jiti: 2.6.1 + lightningcss: 1.30.2 + magic-string: 0.30.21 + source-map-js: 1.2.1 + tailwindcss: 4.1.18 + + '@tailwindcss/oxide-android-arm64@4.1.18': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.1.18': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.1.18': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.1.18': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.18': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.1.18': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.1.18': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.1.18': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.1.18': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.18': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.1.18': + optional: true + + '@tailwindcss/oxide@4.1.18': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.1.18 + '@tailwindcss/oxide-darwin-arm64': 4.1.18 + '@tailwindcss/oxide-darwin-x64': 4.1.18 + '@tailwindcss/oxide-freebsd-x64': 4.1.18 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.18 + '@tailwindcss/oxide-linux-arm64-gnu': 4.1.18 + '@tailwindcss/oxide-linux-arm64-musl': 4.1.18 + '@tailwindcss/oxide-linux-x64-gnu': 4.1.18 + '@tailwindcss/oxide-linux-x64-musl': 4.1.18 + '@tailwindcss/oxide-wasm32-wasi': 4.1.18 + '@tailwindcss/oxide-win32-arm64-msvc': 4.1.18 + '@tailwindcss/oxide-win32-x64-msvc': 4.1.18 + + '@tailwindcss/postcss@4.1.18': + dependencies: + '@alloc/quick-lru': 5.2.0 + '@tailwindcss/node': 4.1.18 + '@tailwindcss/oxide': 4.1.18 + postcss: 8.5.6 + tailwindcss: 4.1.18 + + '@types/node@22.19.11': + dependencies: + undici-types: 6.21.0 + + '@types/react-dom@19.2.3(@types/react@19.2.14)': + dependencies: + '@types/react': 19.2.14 + + '@types/react@19.2.14': + dependencies: + csstype: 3.2.3 + + caniuse-lite@1.0.30001770: {} + + class-variance-authority@0.7.1: + dependencies: + clsx: 2.1.1 + + client-only@0.0.1: {} + + clsx@2.1.1: {} + + csstype@3.2.3: {} + + detect-libc@2.1.2: {} + + enhanced-resolve@5.19.0: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.0 + + geist@1.7.0(next@15.5.12(react-dom@19.2.4(react@19.2.4))(react@19.2.4)): + dependencies: + next: 15.5.12(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + + graceful-fs@4.2.11: {} + + jiti@2.6.1: {} + + lightningcss-android-arm64@1.30.2: + optional: true + + lightningcss-darwin-arm64@1.30.2: + optional: true + + lightningcss-darwin-x64@1.30.2: + optional: true + + lightningcss-freebsd-x64@1.30.2: + optional: true + + lightningcss-linux-arm-gnueabihf@1.30.2: + optional: true + + lightningcss-linux-arm64-gnu@1.30.2: + optional: true + + lightningcss-linux-arm64-musl@1.30.2: + optional: true + + lightningcss-linux-x64-gnu@1.30.2: + optional: true + + lightningcss-linux-x64-musl@1.30.2: + optional: true + + lightningcss-win32-arm64-msvc@1.30.2: + optional: true + + lightningcss-win32-x64-msvc@1.30.2: + optional: true + + lightningcss@1.30.2: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.30.2 + lightningcss-darwin-arm64: 1.30.2 + lightningcss-darwin-x64: 1.30.2 + lightningcss-freebsd-x64: 1.30.2 + lightningcss-linux-arm-gnueabihf: 1.30.2 + lightningcss-linux-arm64-gnu: 1.30.2 + lightningcss-linux-arm64-musl: 1.30.2 + lightningcss-linux-x64-gnu: 1.30.2 + lightningcss-linux-x64-musl: 1.30.2 + lightningcss-win32-arm64-msvc: 1.30.2 + lightningcss-win32-x64-msvc: 1.30.2 + + lucide-react@0.454.0(react@19.2.4): + dependencies: + react: 19.2.4 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + nanoid@3.3.11: {} + + next@15.5.12(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + dependencies: + '@next/env': 15.5.12 + '@swc/helpers': 0.5.15 + caniuse-lite: 1.0.30001770 + postcss: 8.4.31 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + styled-jsx: 5.1.6(react@19.2.4) + optionalDependencies: + '@next/swc-darwin-arm64': 15.5.12 + '@next/swc-darwin-x64': 15.5.12 + '@next/swc-linux-arm64-gnu': 15.5.12 + '@next/swc-linux-arm64-musl': 15.5.12 + '@next/swc-linux-x64-gnu': 15.5.12 + '@next/swc-linux-x64-musl': 15.5.12 + '@next/swc-win32-arm64-msvc': 15.5.12 + '@next/swc-win32-x64-msvc': 15.5.12 + sharp: 0.34.5 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + + picocolors@1.1.1: {} + + postcss@8.4.31: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + react-dom@19.2.4(react@19.2.4): + dependencies: + react: 19.2.4 + scheduler: 0.27.0 + + react@19.2.4: {} + + scheduler@0.27.0: {} + + semver@7.7.4: + optional: true + + sharp@0.34.5: + dependencies: + '@img/colour': 1.0.0 + detect-libc: 2.1.2 + semver: 7.7.4 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.5 + '@img/sharp-darwin-x64': 0.34.5 + '@img/sharp-libvips-darwin-arm64': 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.2.4 + '@img/sharp-libvips-linux-arm': 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.2.4 + '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.2.4 + '@img/sharp-libvips-linux-x64': 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + '@img/sharp-linux-arm': 0.34.5 + '@img/sharp-linux-arm64': 0.34.5 + '@img/sharp-linux-ppc64': 0.34.5 + '@img/sharp-linux-riscv64': 0.34.5 + '@img/sharp-linux-s390x': 0.34.5 + '@img/sharp-linux-x64': 0.34.5 + '@img/sharp-linuxmusl-arm64': 0.34.5 + '@img/sharp-linuxmusl-x64': 0.34.5 + '@img/sharp-wasm32': 0.34.5 + '@img/sharp-win32-arm64': 0.34.5 + '@img/sharp-win32-ia32': 0.34.5 + '@img/sharp-win32-x64': 0.34.5 + optional: true + + source-map-js@1.2.1: {} + + styled-jsx@5.1.6(react@19.2.4): + dependencies: + client-only: 0.0.1 + react: 19.2.4 + + tailwind-merge@2.6.1: {} + + tailwindcss@4.1.18: {} + + tapable@2.3.0: {} + + tslib@2.8.1: {} + + tw-animate-css@1.4.0: {} + + typescript@5.9.3: {} + + undici-types@6.21.0: {} diff --git a/postcss.config.mjs b/postcss.config.mjs new file mode 100644 index 0000000..2f8795a --- /dev/null +++ b/postcss.config.mjs @@ -0,0 +1,7 @@ +const config = { + plugins: { + "@tailwindcss/postcss": {}, + }, +} + +export default config diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..eea7ea5 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2017", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [{ "name": "next" }], + "paths": { + "@/*": ["./*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +}