diff --git a/www/app/(home)/ThemeToggle.tsx b/www/app/(home)/ThemeToggle.tsx index 2dd97157..0b59ebc8 100644 --- a/www/app/(home)/ThemeToggle.tsx +++ b/www/app/(home)/ThemeToggle.tsx @@ -1,11 +1,9 @@ 'use client'; import { useEffect, useState } from 'react'; -// Import useTheme from fumadocs, NOT from 'next-themes' directly. fumadocs -// bundles its own next-themes copy inside RootProvider; a direct import -// resolves to a second module instance whose context is empty, so setTheme() -// silently no-ops. fumadocs re-exports the matching hook for this reason. -import { useTheme } from 'fumadocs-ui/provider/base'; +// The landing has its own next-themes provider (see layout.tsx), so the hook +// comes from next-themes directly. +import { useTheme } from 'next-themes'; // The landing's own theme control. // diff --git a/www/app/(home)/Veil.tsx b/www/app/(home)/Veil.tsx index b944aaaf..5688a1dd 100644 --- a/www/app/(home)/Veil.tsx +++ b/www/app/(home)/Veil.tsx @@ -1,9 +1,6 @@ 'use client'; -// Import useTheme from fumadocs, NOT from 'next-themes' directly. fumadocs -// bundles its own next-themes copy inside RootProvider; a direct import -// resolves to a second module instance whose context is empty. -import { useTheme } from 'fumadocs-ui/provider/base'; +import { useTheme } from 'next-themes'; import dynamic from 'next/dynamic'; import { useEffect, useState } from 'react'; diff --git a/www/app/(home)/Wordmark.tsx b/www/app/(home)/Wordmark.tsx index a551c228..0c75b5f4 100644 --- a/www/app/(home)/Wordmark.tsx +++ b/www/app/(home)/Wordmark.tsx @@ -1,11 +1,13 @@ 'use client'; import { useEffect, useState } from 'react'; -// Import useTheme from fumadocs, NOT from 'next-themes' directly. fumadocs -// bundles its own next-themes copy inside RootProvider; a direct import -// resolves to a second module instance whose context is empty. -import { useTheme } from 'fumadocs-ui/provider/base'; -import { ParticleText } from './ParticleText'; +import { useTheme } from 'next-themes'; +import dynamic from 'next/dynamic'; +import { LazyIsland } from './islands'; + +// The particle field is the last thing on a page twelve thousand pixels tall. +// Its code loads when the footer comes within a screen, not before. +const ParticleText = dynamic(() => import('./ParticleText').then((m) => m.ParticleText), { ssr: false }); // The wordmark that closes the page. // @@ -39,7 +41,7 @@ export function Wordmark() { const tuning = mounted && resolvedTheme === 'light' ? LIGHT : DARK; return ( - + ); } diff --git a/www/app/(home)/dashboard-scene.tsx b/www/app/(home)/dashboard-scene.tsx new file mode 100644 index 00000000..c433026c --- /dev/null +++ b/www/app/(home)/dashboard-scene.tsx @@ -0,0 +1,17 @@ +'use client'; + +import { BrowserFrame } from './BrowserFrame'; +import { DashboardShowcase } from './DashboardShowcase'; +import { MacbookScroll } from './MacbookScroll'; + +// The dashboard showcase and its frame, in one module so the island's single +// dynamic import brings all of it and none of it lands in the page bundle. +export function DashboardScene() { + return ( + + + + + + ); +} diff --git a/www/app/(home)/home.css b/www/app/(home)/home.css index c72cb1b1..a765d9eb 100644 --- a/www/app/(home)/home.css +++ b/www/app/(home)/home.css @@ -828,3 +828,14 @@ html:not(.dark) .hp-veil canvas { } /* No light .hp-veil-scrim override. The scrim derives from --background, which is already white here. */ + +/* Scroll-mounted islands (islands.tsx). Each reserves the height its content + takes at that breakpoint, measured on the built page, so mounting later + moves nothing below it. The dashboard scene is a landscape mock that + stacks on phones; the beam diagram is the reverse. */ +.island--dashboard { min-height: 550px; } +.island--beam { min-height: 556px; } +@media (min-width: 1024px) { + .island--dashboard { min-height: 689px; } + .island--beam { min-height: 258px; } +} diff --git a/www/app/(home)/islands.tsx b/www/app/(home)/islands.tsx new file mode 100644 index 00000000..4635b476 --- /dev/null +++ b/www/app/(home)/islands.tsx @@ -0,0 +1,75 @@ +'use client'; + +import dynamic from 'next/dynamic'; +import { useEffect, useRef, useState, type CSSProperties, type ReactNode } from 'react'; + +// Scroll-mounted islands for the landing page's visual showcases. +// +// These components carry no copy, only a mock UI or a diagram, so nothing is +// lost by leaving them out of the server-rendered HTML. What is gained: their +// JavaScript (framer-style animation, the dashboard mock, the beam diagram, +// the particle wordmark) is neither downloaded nor hydrated until the reader +// scrolls within a screen of them. Everything with text on this page stays +// server-rendered and hydrates as before. +// +// Each island reserves its height up front so mounting later moves nothing +// (CLS). The sizes are the components' natural heights at the widths the +// page is laid out for; see the notes on each. + +export function LazyIsland({ + children, + style, + className, + rootMargin = '600px 0px', +}: { + children: ReactNode; + style?: CSSProperties; + className?: string; + rootMargin?: string; +}) { + const ref = useRef(null); + const [near, setNear] = useState(false); + useEffect(() => { + const el = ref.current; + if (!el || near) return; + if (typeof IntersectionObserver === 'undefined') { + setNear(true); + return; + } + const io = new IntersectionObserver( + ([entry]) => { + if (entry.isIntersecting) { + setNear(true); + io.disconnect(); + } + }, + { rootMargin }, + ); + io.observe(el); + return () => io.disconnect(); + }, [near, rootMargin]); + return ( +
+ {near ? children : null} +
+ ); +} + +const DashboardScene = dynamic(() => import('./dashboard-scene').then((m) => m.DashboardScene), { ssr: false }); +const BeamDiagram = dynamic(() => import('./BeamDiagram').then((m) => m.BeamDiagram), { ssr: false }); + +export function DashboardIsland() { + return ( + + + + ); +} + +export function BeamIsland() { + return ( + + + + ); +} diff --git a/www/app/(home)/landing-base.css b/www/app/(home)/landing-base.css new file mode 100644 index 00000000..65c38c32 --- /dev/null +++ b/www/app/(home)/landing-base.css @@ -0,0 +1,225 @@ +/* The landing page's base sheet. + * + * The landing has its own root layout and does not load global.css (Tailwind, + * the fumadocs theme and the docs chrome, ~95 KB before compression, of which + * the landing used two utilities). What it does need from there: Tailwind's + * preflight resets, which home.css and landing.css were written on top of, + * and the design tokens, which are copied below so both sheets resolve the + * same values. Edit the tokens in global.css first, then mirror here. */ + +@import 'tailwindcss/preflight.css'; + +:root { + --radius: 0.625rem; + + /* ------------------------------------------------------------------ + Neutral greyscale, matching payloadcms.com. + + Their whole palette is achromatic: 51 colour tokens, every base step + R=G=B, no brand hue anywhere. The only chromatic values on the site are + semantic status colours. All the warmth in their hero comes from the + background video, not from the theme. + + We keep the neutral surfaces and spend the colour on --primary instead, + since we have no hero video to carry it. The tokens that used to hold + that video's measured palette (--wave-*, --field-*, and the three + opacity values beside them) are gone: nothing referenced them, in CSS + or in TSX, after the hero they belonged to was replaced. + + Lightness steps below are read off their base scale: + 255 -> 100% 221 -> 88.4% 154 -> 66% 47 -> 25% + 245 -> 96.5% 208 -> 84% 101 -> 47.3% 20 -> 11.5% + 235 -> 93% 181 -> 75% 74 -> 36.8% 7 -> 4.5% + ------------------------------------------------------------------ */ + + /* shadcn layer — light */ + --background: oklch(100% 0 0); + --foreground: oklch(18.7% 0 0); + --card: oklch(100% 0 0); + --card-foreground: oklch(18.7% 0 0); + --popover: oklch(100% 0 0); + --popover-foreground: oklch(18.7% 0 0); + /* Ink, not a hue. --primary drives buttons, active states, the marked phrase + and the focus ring, and here it is simply the darkest neutral, inverting to + the lightest one on the dark theme. The interface stays monochrome. + + Swapping this for a chromatic value is the single edit that colours the + whole site, because everything downstream reads it. */ + --primary: oklch(18.7% 0 0); + --primary-foreground: oklch(100% 0 0); + --secondary: oklch(96.5% 0 0); + --secondary-foreground: oklch(25% 0 0); + --muted: oklch(96.5% 0 0); + --muted-foreground: oklch(47.3% 0 0); + --accent: oklch(93% 0 0); + --accent-foreground: oklch(31% 0 0); + /* Destructive keeps its hue. Payload keeps chroma for status too: a neutral + palette is a brand decision, not a reason to make errors unreadable. */ + --destructive: oklch(58% 0.21 25); + --border: oklch(88.4% 0 0); + --input: oklch(88.4% 0 0); + --ring: oklch(47.3% 0 0); + + /* fumadocs layer. + * + * Aliases, not copies. These were the same sixteen values typed a second + * time, with nothing tying the two sets together, so editing one and not the + * other was silent and the docs drifted away from everything else. + * + * Declared once, here, and NOT repeated under .dark. Each one resolves + * through a shadcn token that .dark already redefines, so the docs follow + * the theme with no second block. A second block is the copy that drifts. */ + --color-fd-background: var(--background); + --color-fd-foreground: var(--foreground); + --color-fd-card: var(--card); + --color-fd-card-foreground: var(--card-foreground); + --color-fd-popover: var(--popover); + --color-fd-popover-foreground: var(--popover-foreground); + --color-fd-muted: var(--muted); + --color-fd-muted-foreground: var(--muted-foreground); + --color-fd-secondary: var(--secondary); + --color-fd-secondary-foreground: var(--secondary-foreground); + --color-fd-border: var(--border); + --color-fd-primary: var(--primary); + --color-fd-primary-foreground: var(--primary-foreground); + --color-fd-ring: var(--ring); + --color-fd-accent: var(--accent); + --color-fd-accent-foreground: var(--accent-foreground); + + /* ------------------------------------------------------------------ + The semantic steps shadcn does not provide. + + shadcn gives one --border and one --card. The landing uses four edge + weights and three translucent panel washes, so those extra steps are + derived here instead of being hardcoded in home.css. That file used to + hold them as literals, which is why it needed a whole second copy of + itself for light mode. + ------------------------------------------------------------------ */ + + /* Edge weights. These were rgba(black, a) on white and rgba(white, a) on + black, which is exactly a foreground mix, so they carry over without loss. + The alphas differ per theme on purpose: a black hairline on white reads + heavier than a white one on black at the same alpha. */ + --rule-1: color-mix(in srgb, var(--foreground) 8%, transparent); + --rule-2: color-mix(in srgb, var(--foreground) 13%, transparent); + --rule-3: color-mix(in srgb, var(--foreground) 30%, transparent); + --rule-frame: color-mix(in srgb, var(--foreground) 14%, transparent); + --rule-grid: color-mix(in srgb, var(--foreground) 9%, transparent); + + /* Panel washes. Translucent because they sit over the animated canvas. */ + --wash-1: color-mix(in srgb, var(--card) 72%, transparent); + --wash-2: color-mix(in srgb, var(--card) 86%, transparent); + --wash-3: color-mix(in srgb, var(--card) 92%, transparent); + + /* A shadow is an absence of light, not a colour from the palette, so this + stays black in both themes and only its strength changes. Deriving it from + --foreground would make it white on the dark theme, which is a glow. */ + --shadow-page: 0 30px 70px rgb(0 0 0 / 0.1); + + /* Syntax colours. Code cannot go monochrome the way the interface can: a + single-colour listing reads as a quote block rather than as code. So this + is the one part of the theme that stays chromatic while --primary is ink. + + Six hues at one lightness and one chroma, so only the hue varies. Holding L + and C is what keeps them distinguishable from each other and readable on + the page at the same time. + + --chroma-h is a real anchor rather than a reading of --primary's hue, and + that is deliberate. An achromatic --primary has no meaningful hue: the + browser serialises it with a floating-point crumb in the b channel, which + came out as roughly 112 degrees rather than 0. Anchoring a palette on that + is anchoring it on rounding noise. Two knobs are worth the honesty. + + At 25 the ramp runs red, orange, yellow-green, green, blue, magenta, which + is close to how most editors colour code already. Move this one number to + rotate the whole palette; if --primary is ever given a hue and the code + should follow it, set this to the same value. */ + --chroma-h: 25; + /* The one fixed colour in the brand mark: the reconciled cell. See components/brand-mark.tsx. */ + --brand-violet: #6d4bd4; + --chroma-l: 0.48; + --chroma-c: 0.15; + --chroma-1: oklch(var(--chroma-l) var(--chroma-c) calc(var(--chroma-h) + 0)); + --chroma-2: oklch(var(--chroma-l) var(--chroma-c) calc(var(--chroma-h) + 40)); + --chroma-3: oklch(var(--chroma-l) var(--chroma-c) calc(var(--chroma-h) + 80)); + --chroma-4: oklch(var(--chroma-l) var(--chroma-c) calc(var(--chroma-h) + 130)); + --chroma-5: oklch(var(--chroma-l) var(--chroma-c) calc(var(--chroma-h) + 200)); + --chroma-6: oklch(var(--chroma-l) var(--chroma-c) calc(var(--chroma-h) + 300)); +} + +.dark { + /* shadcn layer — dark */ + --background: oklch(4.5% 0 0); + --foreground: oklch(100% 0 0); + --card: oklch(11.5% 0 0); + --card-foreground: oklch(100% 0 0); + --popover: oklch(11.5% 0 0); + --popover-foreground: oklch(100% 0 0); + /* The same ink, inverted. Black primary on a black page would be invisible, + so the dark theme takes the lightest neutral and puts the dark one on top. */ + --primary: oklch(100% 0 0); + --primary-foreground: oklch(4.5% 0 0); + --secondary: oklch(18.7% 0 0); + --secondary-foreground: oklch(93% 0 0); + --muted: oklch(18.7% 0 0); + --muted-foreground: oklch(66% 0 0); + --accent: oklch(25% 0 0); + --brand-violet: #a78bfa; + --accent-foreground: oklch(84% 0 0); + --destructive: oklch(70% 0.19 22); + --border: oklch(25% 0 0); + --input: oklch(25% 0 0); + --ring: oklch(66% 0 0); + + /* No --color-fd-* here on purpose. They alias the shadcn tokens in :root, + and every one of those is redefined above, so the docs are already dark. + Repeating them here is what made the two sets drift. + + The --rule-*, --wash-* and --chroma-* steps are not repeated either: they + are built from --foreground, --card and --primary, all redefined above. + Only the values that genuinely differ by theme are listed below. */ + + /* Below about 7% a white hairline composites to #0e0e0e on black and reads + as nothing, so the dark ground needs its own alphas rather than the ones + tuned for paper. */ + --rule-1: color-mix(in srgb, var(--foreground) 7.5%, transparent); + --rule-2: color-mix(in srgb, var(--foreground) 12%, transparent); + --rule-3: color-mix(in srgb, var(--foreground) 28%, transparent); + --rule-frame: color-mix(in srgb, var(--foreground) 16%, transparent); + --rule-grid: color-mix(in srgb, var(--foreground) 10%, transparent); + + --wash-1: color-mix(in srgb, var(--card) 66%, transparent); + --wash-2: color-mix(in srgb, var(--card) 72%, transparent); + --wash-3: color-mix(in srgb, var(--card) 86%, transparent); + + /* A black page can carry a heavy shadow because it has nowhere to go. White + cannot: at 0.8 the screenshots look like they are floating off the sheet. */ + --shadow-page: 0 30px 70px rgb(0 0 0 / 0.8); + + /* Lifted for the dark ground. Same hues, read against black instead of white. */ + --chroma-l: 0.78; + --chroma-c: 0.13; +} + +html { + scrollbar-gutter: stable; +} + +/* No page-level horizontal scroll (clip, never hidden — preserves sticky). */ +html, +body { + overflow-x: clip; +} + +/* The one utility the landing used from Tailwind. */ +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; +} diff --git a/www/app/(home)/layout.tsx b/www/app/(home)/layout.tsx index 7955f1e7..7f1c3614 100644 --- a/www/app/(home)/layout.tsx +++ b/www/app/(home)/layout.tsx @@ -1,35 +1,37 @@ import type { Metadata } from 'next'; import type { ReactNode } from 'react'; -import { Inter, JetBrains_Mono } from 'next/font/google'; +import { Inter, JetBrains_Mono, Outfit } from 'next/font/google'; +import Script from 'next/script'; +import { ThemeProvider } from 'next-themes'; +import './landing-base.css'; import './home.css'; import { Veil } from './Veil'; import { GridLines } from './Grid'; import { Header } from './Header'; import { Footer } from './Footer'; -// The landing. +// The landing, as its own root layout. +// +// The rest of the site sits under app/(site) with the fumadocs shell: its +// provider, its global stylesheet (Tailwind plus the docs theme), its search +// dialog. The landing used two utilities from that sheet and none of the +// rest, but paid for all of it on every visit: ~95 KB of CSS before +// compression and the search bundle in the critical path. Two root layouts +// let the landing ship only what it draws. Same design tokens (mirrored in +// landing-base.css), same theme storage key, so the toggle here and the one +// in the docs stay in step. // // Marketing chrome of its own rather than the shared Navbar and Footer: a // compact hairline header, a four-column footer under a giant wordmark, and -// three grid lines on the quarter points of the content column. The docs keep -// the shared shell, so the two deliberately differ. +// three grid lines on the quarter points of the content column. // // Behind all of it, PixelBlast: an fbm field thresholded against an 8x8 Bayer // matrix, so the backdrop is a grid of hard on/off cells rather than a // gradient. It has a texture, which suits a page whose own structure is three -// hairlines. Clicking anywhere sends a ring through it. -// -// The canvas is fixed to the viewport rather than sized to the document. One -// screen of GL covers a page twelve thousand pixels tall, and the field holds -// still while the page scrolls over it. -// -// Everything above it is translucent by a few points in home.css, which lets -// the violet through the sections instead of only between them. -// -// Follows the site light/dark toggle, shared with the docs through the single -// RootProvider in the root layout. Dark is the set this was drawn in; light is -// the same composition on white, with the backdrop dropped back and its scrim -// inverted. See the light block in home.css. +// hairlines. Clicking anywhere sends a ring through it. The canvas is fixed to +// the viewport rather than sized to the document, and everything above it is +// translucent by a few points in home.css, which lets the violet through the +// sections instead of only between them. const inter = Inter({ subsets: ['latin'], weight: ['400', '500', '600', '700'], @@ -40,8 +42,17 @@ const mono = JetBrains_Mono({ weight: ['400', '500'], variable: '--font-jetbrains-mono', }); +// Display headings below the fold (landing.css --font-display). Outfit stands +// in for Gilroy; see the note in app/(site)/layout.tsx. +const outfit = Outfit({ + subsets: ['latin'], + variable: '--font-outfit', +}); export const metadata: Metadata = { + metadataBase: new URL('https://concile.dev'), + alternates: { canonical: './' }, + verification: { google: 'kiKhCzpdz32bLp_YjGbYMjPcRlpWIlyHkIpXq_zGP9I' }, /* Title and description carry the same words as the H1, because a visitor who searched one of them should land on a page that repeats it back. "Realtime" rather than "reactive": reactive is Convex's brand term, realtime is what @@ -53,18 +64,35 @@ export const metadata: Metadata = { export default function HomeLayout({ children }: { children: ReactNode }) { return ( -
- -
- -
-
{children}
-
-
-
+ + + {/* Same settings as fumadocs' RootProvider uses in the (site) layout, so + the class on and the "theme" key in storage mean the same + thing on both sides. */} + +
+ +
+ +
+
{children}
+
+
+
+
+ {/* Microsoft Clarity, production only, loaded after hydration. The + element id must not be "clarity": browsers expose every id as a + window global and the loader reads window.clarity. */} + {process.env.NODE_ENV === 'production' ? ( + + ) : null} + + ); } diff --git a/www/app/(home)/page.tsx b/www/app/(home)/page.tsx index 7780797a..2b460512 100644 --- a/www/app/(home)/page.tsx +++ b/www/app/(home)/page.tsx @@ -2,10 +2,10 @@ import Link from 'next/link'; import './landing.css'; import { STACK_LOGOS } from './stack-logos'; import { Hero } from './Hero'; +import { BeamIsland, DashboardIsland } from './islands'; import { Reveal } from './Reveal'; import { SpotlightGrid } from './SpotlightGrid'; import { StatNumber } from './StatNumber'; -import { BeamDiagram } from './BeamDiagram'; import { Steps } from './Steps'; import { FeatureCards } from './FeatureCards'; import { Components } from './Components'; @@ -13,9 +13,6 @@ import { Pillars } from './Pillars'; import { ComparisonTable } from './ComparisonTable'; import { Faq } from './Faq'; import { CopyCommand } from './CopyCommand'; -import { DashboardShowcase } from './DashboardShowcase'; -import { BrowserFrame } from './BrowserFrame'; -import { MacbookScroll } from './MacbookScroll'; export default function HomePage() { return ( @@ -81,11 +78,7 @@ export default function HomePage() { We include a built-in dashboard that runs on the exact same origin as your app. It uses live subscriptions, meaning you see writes land in the database immediately. We didn't even build a refresh button, because you'll never need it.

- - - - - + {/* ---------------- REACTIVITY DIAGRAM ---------------- */} @@ -102,7 +95,7 @@ export default function HomePage() { Read about reactivity - + {/* ---------------- CAPABILITIES (live mini-visuals) ---------------- */} diff --git a/www/app/(content)/brand/page.tsx b/www/app/(site)/(content)/brand/page.tsx similarity index 100% rename from www/app/(content)/brand/page.tsx rename to www/app/(site)/(content)/brand/page.tsx diff --git a/www/app/(content)/layout.tsx b/www/app/(site)/(content)/layout.tsx similarity index 100% rename from www/app/(content)/layout.tsx rename to www/app/(site)/(content)/layout.tsx diff --git a/www/app/api/search/route.ts b/www/app/(site)/api/search/route.ts similarity index 100% rename from www/app/api/search/route.ts rename to www/app/(site)/api/search/route.ts diff --git a/www/app/blog/[slug]/page.tsx b/www/app/(site)/blog/[slug]/page.tsx similarity index 100% rename from www/app/blog/[slug]/page.tsx rename to www/app/(site)/blog/[slug]/page.tsx diff --git a/www/app/blog/layout.tsx b/www/app/(site)/blog/layout.tsx similarity index 100% rename from www/app/blog/layout.tsx rename to www/app/(site)/blog/layout.tsx diff --git a/www/app/blog/page.tsx b/www/app/(site)/blog/page.tsx similarity index 100% rename from www/app/blog/page.tsx rename to www/app/(site)/blog/page.tsx diff --git a/www/app/compare/[slug]/page.tsx b/www/app/(site)/compare/[slug]/page.tsx similarity index 100% rename from www/app/compare/[slug]/page.tsx rename to www/app/(site)/compare/[slug]/page.tsx diff --git a/www/app/compare/page.tsx b/www/app/(site)/compare/page.tsx similarity index 100% rename from www/app/compare/page.tsx rename to www/app/(site)/compare/page.tsx diff --git a/www/app/docs/[[...slug]]/page.tsx b/www/app/(site)/docs/[[...slug]]/page.tsx similarity index 100% rename from www/app/docs/[[...slug]]/page.tsx rename to www/app/(site)/docs/[[...slug]]/page.tsx diff --git a/www/app/docs/layout.tsx b/www/app/(site)/docs/layout.tsx similarity index 100% rename from www/app/docs/layout.tsx rename to www/app/(site)/docs/layout.tsx diff --git a/www/app/layout.tsx b/www/app/(site)/layout.tsx similarity index 99% rename from www/app/layout.tsx rename to www/app/(site)/layout.tsx index 67d5b556..a04a787c 100644 --- a/www/app/layout.tsx +++ b/www/app/(site)/layout.tsx @@ -1,5 +1,5 @@ import { RootProvider } from 'fumadocs-ui/provider/next'; -import './global.css'; +import '../global.css'; import type { Metadata } from 'next'; import { Inter, Outfit } from 'next/font/google'; import Script from 'next/script'; diff --git a/www/app/llms-full.txt/route.ts b/www/app/(site)/llms-full.txt/route.ts similarity index 100% rename from www/app/llms-full.txt/route.ts rename to www/app/(site)/llms-full.txt/route.ts diff --git a/www/app/llms.mdx/docs/[[...slug]]/route.ts b/www/app/(site)/llms.mdx/docs/[[...slug]]/route.ts similarity index 100% rename from www/app/llms.mdx/docs/[[...slug]]/route.ts rename to www/app/(site)/llms.mdx/docs/[[...slug]]/route.ts diff --git a/www/app/llms.txt/route.ts b/www/app/(site)/llms.txt/route.ts similarity index 100% rename from www/app/llms.txt/route.ts rename to www/app/(site)/llms.txt/route.ts diff --git a/www/app/og/docs/[...slug]/route.tsx b/www/app/(site)/og/docs/[...slug]/route.tsx similarity index 100% rename from www/app/og/docs/[...slug]/route.tsx rename to www/app/(site)/og/docs/[...slug]/route.tsx diff --git a/www/bun.lock b/www/bun.lock index a9db44c9..005555f2 100644 --- a/www/bun.lock +++ b/www/bun.lock @@ -15,6 +15,7 @@ "lucide-react": "^1.24.0", "motion": "^13.3.0", "next": "16.2.10", + "next-themes": "^0.4.6", "ogl": "^1.0.11", "react": "^19.2.7", "react-dom": "^19.2.7", diff --git a/www/package.json b/www/package.json index f6357727..efda72c9 100644 --- a/www/package.json +++ b/www/package.json @@ -21,6 +21,7 @@ "lucide-react": "^1.24.0", "motion": "^13.3.0", "next": "16.2.10", + "next-themes": "^0.4.6", "ogl": "^1.0.11", "react": "^19.2.7", "react-dom": "^19.2.7",