svforge 1.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,7 +10,7 @@ bunx sv create my-app --template minimal --types ts --add svforge --install bun
10
10
  cd my-app && bun dev
11
11
 
12
12
  # Fullstack template (auth + DB + admin)
13
- bunx sv create my-app --template minimal --types ts --add 'svforge=template:fullstack' --install bun
13
+ bunx sv create my-app --template minimal --types ts --add 'svforge=template:dashboard' --install bun
14
14
  cd my-app && cp .env.example .env && bunx drizzle-kit push && bun dev
15
15
  ```
16
16
 
@@ -18,7 +18,7 @@ Or step by step:
18
18
  ```bash
19
19
  bunx sv create my-app --template minimal --types ts
20
20
  cd my-app
21
- bunx sv add svforge # prompts: base or fullstack
21
+ bunx sv add svforge # prompts: base or dashboard
22
22
  ```
23
23
 
24
24
  ## What you get — Base
@@ -10,8 +10,8 @@ declare const _default: sv0.Addon<{
10
10
  readonly value: "base";
11
11
  readonly label: "Base — UI kit + layouts + forms (landing, portfolio, marketing…)";
12
12
  }, {
13
- readonly value: "fullstack";
14
- readonly label: "Full Stack — base + admin dashboard + auth + DB";
13
+ readonly value: "dashboard";
14
+ readonly label: "Dashboard — base + admin dashboard + auth + DB";
15
15
  }];
16
16
  };
17
17
  }, "svelteforge">;
package/dist/index.js CHANGED
@@ -3,79 +3,85 @@ import { defineAddon, defineAddonOptions } from "sv";
3
3
  const baseFiles = {
4
4
  "/routes/layout.css": "@import 'tailwindcss';\n@import '@skeletonlabs/skeleton';\n@import '@skeletonlabs/skeleton-svelte';\n@import '@fontsource-variable/inter';\n@import '@fontsource-variable/space-grotesk';\n@import '@fontsource-variable/manrope';\n@import '@fontsource-variable/fira-code';\n@import '../lib/styles/index.css';\n\n@plugin '@tailwindcss/forms';\n@plugin '@tailwindcss/typography';\n\n@custom-variant dark (&:where([data-mode=dark], [data-mode=dark] *));\n\nh1, h2, h3, h4, h5, h6 {\n font-family: var(--font-heading);\n}\n\ncode, pre {\n font-family: var(--font-code);\n}\n",
5
5
  "/routes/+layout.svelte": "<script lang=\"ts\">\n import './layout.css';\n import { Navbar } from '$lib/components/layout';\n import { Footer } from '$lib/components/layout';\n\n let { children } = $props();\n<\/script>\n\n<Navbar links={[{ href: '/demo-ui', label: 'Components' }]} />\n\n{@render children()}\n\n<Footer links={[{ href: '/demo-ui', label: 'Components' }]} />\n",
6
- "/routes/+page.svelte": "<script lang=\"ts\">\n import { Button, Card } from '$lib/components/ui';\n<\/script>\n\n<svelte:head>\n <title>SvelteForge</title>\n</svelte:head>\n\n<main class=\"max-w-container mx-auto px-element py-section\">\n <section class=\"text-center space-y-6 py-section\">\n <h1 class=\"text-5xl font-heading font-bold\">\n Welcome to <span class=\"text-primary-500\">SvelteForge</span>\n </h1>\n <p class=\"text-xl text-surface-500 max-w-2xl mx-auto\">\n A modern SvelteKit starter kit built on Skeleton UI with Tailwind CSS.\n </p>\n <div class=\"flex justify-center gap-4\">\n <Button href=\"/demo-ui\" size=\"lg\">View Components</Button>\n <Button variant=\"outlined\" size=\"lg\" href=\"https://github.com/ludoloops/svelteforge\">GitHub</Button>\n </div>\n </section>\n\n <section class=\"grid grid-cols-1 md:grid-cols-3 gap-group mt-section\">\n <Card variant=\"elevated\">\n <h3 class=\"font-heading font-bold text-lg mb-2\">Modern Stack</h3>\n <p class=\"text-surface-500\">SvelteKit + Tailwind v4 + Skeleton UI v4. Always up to date.</p>\n </Card>\n <Card variant=\"elevated\">\n <h3 class=\"font-heading font-bold text-lg mb-2\">Customizable</h3>\n <p class=\"text-surface-500\">Full theme system with oklch colors. Make it yours.</p>\n </Card>\n <Card variant=\"elevated\">\n <h3 class=\"font-heading font-bold text-lg mb-2\">Production Ready</h3>\n <p class=\"text-surface-500\">Prettier, ESLint, TypeScript, responsive. Ship with confidence.</p>\n </Card>\n </section>\n</main>\n",
7
- "/routes/demo-ui/+page.svelte": "<script lang=\"ts\">\n import {\n Accordion, Alert, Avatar, Badge, Breadcrumb, Button, Card,\n Checkbox, Input, Select, Table, Tabs, Textarea, ThemeToggle, Toggle\n } from '$lib/components/ui';\n import { Navbar } from '$lib/components/layout';\n import { Footer } from '$lib/components/layout';\n\n let loading = $state(false);\n\n function simulateLoading() {\n loading = true;\n setTimeout(() => (loading = false), 2000);\n }\n<\/script>\n\n<svelte:head>\n <title>SvelteForge — Component Demo</title>\n</svelte:head>\n\n<Navbar links={[{ href: '/demo-ui', label: 'Demo' }]} />\n\n<main class=\"max-w-container mx-auto px-element py-section space-y-section\">\n <h1 class=\"text-4xl font-heading font-bold\">Component Demo</h1>\n <p class=\"text-surface-500\">All SvelteForge components with variants. Theme changes apply instantly.</p>\n\n <!-- Buttons -->\n <section class=\"space-y-4\">\n <h2 class=\"text-2xl font-heading border-b border-surface-200-800 pb-2\">Button</h2>\n <h3 class=\"text-lg font-heading\">Colors (Filled)</h3>\n <div class=\"flex flex-wrap gap-3\">\n <Button>Primary</Button>\n <Button color=\"secondary\">Secondary</Button>\n <Button color=\"tertiary\">Tertiary</Button>\n <Button color=\"success\">Success</Button>\n <Button color=\"warning\">Warning</Button>\n <Button color=\"error\">Error</Button>\n <Button color=\"surface\">Surface</Button>\n </div>\n <h3 class=\"text-lg font-heading\">Variants</h3>\n <div class=\"flex flex-wrap gap-3\">\n <Button variant=\"filled\">Filled</Button>\n <Button variant=\"outlined\">Outlined</Button>\n <Button variant=\"tonal\">Tonal</Button>\n <Button variant=\"ghost\">Ghost</Button>\n </div>\n <h3 class=\"text-lg font-heading\">Sizes</h3>\n <div class=\"flex flex-wrap gap-3 items-center\">\n <Button size=\"sm\">Small</Button>\n <Button size=\"md\">Medium</Button>\n <Button size=\"lg\">Large</Button>\n </div>\n <h3 class=\"text-lg font-heading\">States</h3>\n <div class=\"flex flex-wrap gap-3\">\n <Button loading={loading} onclick={simulateLoading}>\n {loading ? 'Loading...' : 'Click to Load'}\n </Button>\n <Button href=\"/\">Link Button</Button>\n <Button disabled>Disabled</Button>\n </div>\n </section>\n\n <!-- Cards -->\n <section class=\"space-y-4\">\n <h2 class=\"text-2xl font-heading border-b border-surface-200-800 pb-2\">Card</h2>\n <div class=\"grid grid-cols-1 md:grid-cols-3 gap-group\">\n <Card>\n <p>Flat card with some content.</p>\n </Card>\n <Card variant=\"elevated\">\n <p>Elevated card with shadow.</p>\n </Card>\n <Card variant=\"outlined\">\n <p>Outlined card with ring border.</p>\n </Card>\n </div>\n <Card>\n {#snippet header()}\n <h3 class=\"font-heading font-bold\">Card with Header & Footer</h3>\n {/snippet}\n <p>Content goes here.</p>\n {#snippet footer()}\n <div class=\"flex justify-end gap-2\">\n <Button variant=\"ghost\" size=\"sm\">Cancel</Button>\n <Button size=\"sm\">Save</Button>\n </div>\n {/snippet}\n </Card>\n </section>\n\n <!-- Badges -->\n <section class=\"space-y-4\">\n <h2 class=\"text-2xl font-heading border-b border-surface-200-800 pb-2\">Badge</h2>\n <h3 class=\"text-lg font-heading\">Filled</h3>\n <div class=\"flex flex-wrap gap-2\">\n <Badge>Primary</Badge>\n <Badge color=\"secondary\">Secondary</Badge>\n <Badge color=\"tertiary\">Tertiary</Badge>\n <Badge color=\"success\">Success</Badge>\n <Badge color=\"warning\">Warning</Badge>\n <Badge color=\"error\">Error</Badge>\n <Badge color=\"surface\">Surface</Badge>\n </div>\n <h3 class=\"text-lg font-heading\">Outlined</h3>\n <div class=\"flex flex-wrap gap-2\">\n <Badge variant=\"outlined\">Primary</Badge>\n <Badge variant=\"outlined\" color=\"secondary\">Secondary</Badge>\n <Badge variant=\"outlined\" color=\"success\">Success</Badge>\n <Badge variant=\"outlined\" color=\"warning\">Warning</Badge>\n <Badge variant=\"outlined\" color=\"error\">Error</Badge>\n <Badge variant=\"outlined\" color=\"surface\">Surface</Badge>\n </div>\n <h3 class=\"text-lg font-heading\">Tonal</h3>\n <div class=\"flex flex-wrap gap-2\">\n <Badge variant=\"tonal\">Primary</Badge>\n <Badge variant=\"tonal\" color=\"secondary\">Secondary</Badge>\n <Badge variant=\"tonal\" color=\"success\">Success</Badge>\n <Badge variant=\"tonal\" color=\"error\">Error</Badge>\n </div>\n <h3 class=\"text-lg font-heading\">Sizes</h3>\n <div class=\"flex flex-wrap gap-2 items-center\">\n <Badge size=\"sm\">Small</Badge>\n <Badge size=\"md\">Medium</Badge>\n <Badge size=\"lg\">Large</Badge>\n </div>\n </section>\n\n <!-- Avatar -->\n <section class=\"space-y-4\">\n <h2 class=\"text-2xl font-heading border-b border-surface-200-800 pb-2\">Avatar</h2>\n <div class=\"flex items-center gap-3\">\n <Avatar size=\"sm\" src=\"https://api.dicebear.com/9.x/avataaars/svg?seed=1\" alt=\"User 1\" />\n <Avatar size=\"md\" src=\"https://api.dicebear.com/9.x/avataaars/svg?seed=2\" alt=\"User 2\" />\n <Avatar size=\"lg\" src=\"https://api.dicebear.com/9.x/avataaars/svg?seed=3\" alt=\"User 3\" />\n </div>\n </section>\n\n <!-- Alerts -->\n <section class=\"space-y-4\">\n <h2 class=\"text-2xl font-heading border-b border-surface-200-800 pb-2\">Alert</h2>\n <div class=\"space-y-3\">\n <Alert variant=\"info\">This is an informational message.</Alert>\n <Alert variant=\"success\">Operation completed successfully!</Alert>\n <Alert variant=\"warning\">Please review before continuing.</Alert>\n <Alert variant=\"error\">Something went wrong. Please try again.</Alert>\n </div>\n </section>\n\n <!-- Form -->\n <section class=\"space-y-4\">\n <h2 class=\"text-2xl font-heading border-b border-surface-200-800 pb-2\">Form</h2>\n <Card>\n <div class=\"space-y-4\">\n <Input label=\"Name\" placeholder=\"Enter your name\" />\n <Input label=\"Email\" type=\"email\" placeholder=\"you@example.com\" error=\"Invalid email\" />\n <Textarea label=\"Message\" placeholder=\"Write something...\" rows={3} />\n <Select\n label=\"Category\"\n options={[\n { value: '', label: 'Select...' },\n { value: 'design', label: 'Design' },\n { value: 'dev', label: 'Development' },\n { value: 'marketing', label: 'Marketing' }\n ]}\n />\n <div class=\"flex flex-col gap-3\">\n <Checkbox label=\"I agree to the terms\" />\n <Toggle label=\"Enable notifications\" />\n </div>\n <Button>Submit</Button>\n </div>\n </Card>\n </section>\n\n <!-- Accordion -->\n <section class=\"space-y-4\">\n <h2 class=\"text-2xl font-heading border-b border-surface-200-800 pb-2\">Accordion</h2>\n <Accordion items={[\n { title: 'What is SvelteForge?', content: 'A SvelteKit starter kit built on Skeleton UI.' },\n { title: 'Is it open source?', content: 'Yes, fully open source on GitHub.' },\n { title: 'Can I customize the theme?', content: 'Absolutely. All colors and spacing use CSS variables.' }\n ]} />\n </section>\n\n <!-- Tabs -->\n <section class=\"space-y-4\">\n <h2 class=\"text-2xl font-heading border-b border-surface-200-800 pb-2\">Tabs</h2>\n <Tabs tabs={[\n { label: 'Overview', content: 'This is the overview tab with general information.' },\n { label: 'Features', content: 'A list of features and capabilities.' },\n { label: 'Pricing', content: 'Free and open source. No pricing page needed!' }\n ]} />\n </section>\n\n <!-- Table -->\n <section class=\"space-y-4\">\n <h2 class=\"text-2xl font-heading border-b border-surface-200-800 pb-2\">Table</h2>\n <Table\n columns={[\n { key: 'name', label: 'Name' },\n { key: 'role', label: 'Role' },\n { key: 'status', label: 'Status' }\n ]}\n rows={[\n { name: 'Alice', role: 'Developer', status: 'Active' },\n { name: 'Bob', role: 'Designer', status: 'Away' },\n { name: 'Charlie', role: 'PM', status: 'Active' }\n ]}\n />\n </section>\n\n <!-- Breadcrumb -->\n <section class=\"space-y-4\">\n <h2 class=\"text-2xl font-heading border-b border-surface-200-800 pb-2\">Breadcrumb</h2>\n <Breadcrumb items={[\n { href: '/', label: 'Home' },\n { href: '/demo-ui', label: 'Components' },\n { label: 'Breadcrumb' }\n ]} />\n </section>\n\n <!-- Theme -->\n <section class=\"space-y-4\">\n <h2 class=\"text-2xl font-heading border-b border-surface-200-800 pb-2\">Theme</h2>\n <p class=\"text-surface-500\">Click to toggle light/dark mode:</p>\n <ThemeToggle />\n </section>\n</main>\n\n<Footer links={[{ href: '/demo-ui', label: 'Components' }]} />\n",
6
+ "/routes/+page.svelte": "<script lang=\"ts\">\n import { Button, Card } from '$lib/components/svforge/ui';\n<\/script>\n\n<svelte:head>\n <title>SvelteForge</title>\n</svelte:head>\n\n<main class=\"max-w-container mx-auto px-element py-section\">\n <section class=\"text-center space-y-6 py-section\">\n <h1 class=\"text-5xl font-heading font-bold\">\n Welcome to <span class=\"text-primary-500\">SvelteForge</span>\n </h1>\n <p class=\"text-xl text-surface-500 max-w-2xl mx-auto\">\n A modern SvelteKit starter kit built on Skeleton UI with Tailwind CSS.\n </p>\n <div class=\"flex justify-center gap-4\">\n <Button href=\"/demo-ui\" size=\"lg\">View Components</Button>\n <Button variant=\"outlined\" size=\"lg\" href=\"https://github.com/ludoloops/svelteforge\">GitHub</Button>\n </div>\n </section>\n\n <section class=\"grid grid-cols-1 md:grid-cols-3 gap-group mt-section\">\n <Card variant=\"elevated\">\n <h3 class=\"font-heading font-bold text-lg mb-2\">Modern Stack</h3>\n <p class=\"text-surface-500\">SvelteKit + Tailwind v4 + Skeleton UI v4. Always up to date.</p>\n </Card>\n <Card variant=\"elevated\">\n <h3 class=\"font-heading font-bold text-lg mb-2\">Customizable</h3>\n <p class=\"text-surface-500\">Full theme system with oklch colors. Make it yours.</p>\n </Card>\n <Card variant=\"elevated\">\n <h3 class=\"font-heading font-bold text-lg mb-2\">Production Ready</h3>\n <p class=\"text-surface-500\">Prettier, ESLint, TypeScript, responsive. Ship with confidence.</p>\n </Card>\n </section>\n</main>\n",
7
+ "/routes/demo-ui/+page.svelte": "<script lang=\"ts\">\n import {\n Accordion, Alert, Avatar, Badge, Breadcrumb, Button, Card,\n Checkbox, Input, Select, Table, Tabs, Textarea, ThemeToggle, Toggle\n } from '$lib/components/svforge/ui';\n import { Navbar } from '$lib/components/layout';\n import { Footer } from '$lib/components/layout';\n\n let loading = $state(false);\n\n function simulateLoading() {\n loading = true;\n setTimeout(() => (loading = false), 2000);\n }\n<\/script>\n\n<svelte:head>\n <title>SvelteForge — Component Demo</title>\n</svelte:head>\n\n<Navbar links={[{ href: '/demo-ui', label: 'Demo' }]} />\n\n<main class=\"max-w-container mx-auto px-element py-section space-y-section\">\n <h1 class=\"text-4xl font-heading font-bold\">Component Demo</h1>\n <p class=\"text-surface-500\">All SvelteForge components with variants. Theme changes apply instantly.</p>\n\n <!-- Buttons -->\n <section class=\"space-y-4\">\n <h2 class=\"text-2xl font-heading border-b border-surface-200-800 pb-2\">Button</h2>\n <h3 class=\"text-lg font-heading\">Colors (Filled)</h3>\n <div class=\"flex flex-wrap gap-3\">\n <Button>Primary</Button>\n <Button color=\"secondary\">Secondary</Button>\n <Button color=\"tertiary\">Tertiary</Button>\n <Button color=\"success\">Success</Button>\n <Button color=\"warning\">Warning</Button>\n <Button color=\"error\">Error</Button>\n <Button color=\"surface\">Surface</Button>\n </div>\n <h3 class=\"text-lg font-heading\">Variants</h3>\n <div class=\"flex flex-wrap gap-3\">\n <Button variant=\"filled\">Filled</Button>\n <Button variant=\"outlined\">Outlined</Button>\n <Button variant=\"tonal\">Tonal</Button>\n <Button variant=\"ghost\">Ghost</Button>\n </div>\n <h3 class=\"text-lg font-heading\">Sizes</h3>\n <div class=\"flex flex-wrap gap-3 items-center\">\n <Button size=\"sm\">Small</Button>\n <Button size=\"md\">Medium</Button>\n <Button size=\"lg\">Large</Button>\n </div>\n <h3 class=\"text-lg font-heading\">States</h3>\n <div class=\"flex flex-wrap gap-3\">\n <Button loading={loading} onclick={simulateLoading}>\n {loading ? 'Loading...' : 'Click to Load'}\n </Button>\n <Button href=\"/\">Link Button</Button>\n <Button disabled>Disabled</Button>\n </div>\n </section>\n\n <!-- Cards -->\n <section class=\"space-y-4\">\n <h2 class=\"text-2xl font-heading border-b border-surface-200-800 pb-2\">Card</h2>\n <div class=\"grid grid-cols-1 md:grid-cols-3 gap-group\">\n <Card>\n <p>Flat card with some content.</p>\n </Card>\n <Card variant=\"elevated\">\n <p>Elevated card with shadow.</p>\n </Card>\n <Card variant=\"outlined\">\n <p>Outlined card with ring border.</p>\n </Card>\n </div>\n <Card>\n {#snippet header()}\n <h3 class=\"font-heading font-bold\">Card with Header & Footer</h3>\n {/snippet}\n <p>Content goes here.</p>\n {#snippet footer()}\n <div class=\"flex justify-end gap-2\">\n <Button variant=\"ghost\" size=\"sm\">Cancel</Button>\n <Button size=\"sm\">Save</Button>\n </div>\n {/snippet}\n </Card>\n </section>\n\n <!-- Badges -->\n <section class=\"space-y-4\">\n <h2 class=\"text-2xl font-heading border-b border-surface-200-800 pb-2\">Badge</h2>\n <h3 class=\"text-lg font-heading\">Filled</h3>\n <div class=\"flex flex-wrap gap-2\">\n <Badge>Primary</Badge>\n <Badge color=\"secondary\">Secondary</Badge>\n <Badge color=\"tertiary\">Tertiary</Badge>\n <Badge color=\"success\">Success</Badge>\n <Badge color=\"warning\">Warning</Badge>\n <Badge color=\"error\">Error</Badge>\n <Badge color=\"surface\">Surface</Badge>\n </div>\n <h3 class=\"text-lg font-heading\">Outlined</h3>\n <div class=\"flex flex-wrap gap-2\">\n <Badge variant=\"outlined\">Primary</Badge>\n <Badge variant=\"outlined\" color=\"secondary\">Secondary</Badge>\n <Badge variant=\"outlined\" color=\"success\">Success</Badge>\n <Badge variant=\"outlined\" color=\"warning\">Warning</Badge>\n <Badge variant=\"outlined\" color=\"error\">Error</Badge>\n <Badge variant=\"outlined\" color=\"surface\">Surface</Badge>\n </div>\n <h3 class=\"text-lg font-heading\">Tonal</h3>\n <div class=\"flex flex-wrap gap-2\">\n <Badge variant=\"tonal\">Primary</Badge>\n <Badge variant=\"tonal\" color=\"secondary\">Secondary</Badge>\n <Badge variant=\"tonal\" color=\"success\">Success</Badge>\n <Badge variant=\"tonal\" color=\"error\">Error</Badge>\n </div>\n <h3 class=\"text-lg font-heading\">Sizes</h3>\n <div class=\"flex flex-wrap gap-2 items-center\">\n <Badge size=\"sm\">Small</Badge>\n <Badge size=\"md\">Medium</Badge>\n <Badge size=\"lg\">Large</Badge>\n </div>\n </section>\n\n <!-- Avatar -->\n <section class=\"space-y-4\">\n <h2 class=\"text-2xl font-heading border-b border-surface-200-800 pb-2\">Avatar</h2>\n <div class=\"flex items-center gap-3\">\n <Avatar size=\"sm\" src=\"https://api.dicebear.com/9.x/avataaars/svg?seed=1\" alt=\"User 1\" />\n <Avatar size=\"md\" src=\"https://api.dicebear.com/9.x/avataaars/svg?seed=2\" alt=\"User 2\" />\n <Avatar size=\"lg\" src=\"https://api.dicebear.com/9.x/avataaars/svg?seed=3\" alt=\"User 3\" />\n </div>\n </section>\n\n <!-- Alerts -->\n <section class=\"space-y-4\">\n <h2 class=\"text-2xl font-heading border-b border-surface-200-800 pb-2\">Alert</h2>\n <div class=\"space-y-3\">\n <Alert variant=\"info\">This is an informational message.</Alert>\n <Alert variant=\"success\">Operation completed successfully!</Alert>\n <Alert variant=\"warning\">Please review before continuing.</Alert>\n <Alert variant=\"error\">Something went wrong. Please try again.</Alert>\n </div>\n </section>\n\n <!-- Form -->\n <section class=\"space-y-4\">\n <h2 class=\"text-2xl font-heading border-b border-surface-200-800 pb-2\">Form</h2>\n <Card>\n <div class=\"space-y-4\">\n <Input label=\"Name\" placeholder=\"Enter your name\" />\n <Input label=\"Email\" type=\"email\" placeholder=\"you@example.com\" error=\"Invalid email\" />\n <Textarea label=\"Message\" placeholder=\"Write something...\" rows={3} />\n <Select\n label=\"Category\"\n options={[\n { value: '', label: 'Select...' },\n { value: 'design', label: 'Design' },\n { value: 'dev', label: 'Development' },\n { value: 'marketing', label: 'Marketing' }\n ]}\n />\n <div class=\"flex flex-col gap-3\">\n <Checkbox label=\"I agree to the terms\" />\n <Toggle label=\"Enable notifications\" />\n </div>\n <Button>Submit</Button>\n </div>\n </Card>\n </section>\n\n <!-- Accordion -->\n <section class=\"space-y-4\">\n <h2 class=\"text-2xl font-heading border-b border-surface-200-800 pb-2\">Accordion</h2>\n <Accordion items={[\n { title: 'What is SvelteForge?', content: 'A SvelteKit starter kit built on Skeleton UI.' },\n { title: 'Is it open source?', content: 'Yes, fully open source on GitHub.' },\n { title: 'Can I customize the theme?', content: 'Absolutely. All colors and spacing use CSS variables.' }\n ]} />\n </section>\n\n <!-- Tabs -->\n <section class=\"space-y-4\">\n <h2 class=\"text-2xl font-heading border-b border-surface-200-800 pb-2\">Tabs</h2>\n <Tabs tabs={[\n { label: 'Overview', content: 'This is the overview tab with general information.' },\n { label: 'Features', content: 'A list of features and capabilities.' },\n { label: 'Pricing', content: 'Free and open source. No pricing page needed!' }\n ]} />\n </section>\n\n <!-- Table -->\n <section class=\"space-y-4\">\n <h2 class=\"text-2xl font-heading border-b border-surface-200-800 pb-2\">Table</h2>\n <Table\n columns={[\n { key: 'name', label: 'Name' },\n { key: 'role', label: 'Role' },\n { key: 'status', label: 'Status' }\n ]}\n rows={[\n { name: 'Alice', role: 'Developer', status: 'Active' },\n { name: 'Bob', role: 'Designer', status: 'Away' },\n { name: 'Charlie', role: 'PM', status: 'Active' }\n ]}\n />\n </section>\n\n <!-- Breadcrumb -->\n <section class=\"space-y-4\">\n <h2 class=\"text-2xl font-heading border-b border-surface-200-800 pb-2\">Breadcrumb</h2>\n <Breadcrumb items={[\n { href: '/', label: 'Home' },\n { href: '/demo-ui', label: 'Components' },\n { label: 'Breadcrumb' }\n ]} />\n </section>\n\n <!-- Theme -->\n <section class=\"space-y-4\">\n <h2 class=\"text-2xl font-heading border-b border-surface-200-800 pb-2\">Theme</h2>\n <p class=\"text-surface-500\">Click to toggle light/dark mode:</p>\n <ThemeToggle />\n </section>\n</main>\n\n<Footer links={[{ href: '/demo-ui', label: 'Components' }]} />\n",
8
8
  "/lib/utils/cn.ts": "import { clsx } from 'clsx';\nimport type { ClassValue } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n",
9
9
  "/lib/index.ts": "// place files you want to import through the `$lib` alias in this folder.\n",
10
10
  "/lib/styles/tokens.css": "/* === Tailwind custom tokens === */\n@theme {\n --font-sans: 'Inter Variable', sans-serif;\n --font-heading: 'Space Grotesk Variable', sans-serif;\n --font-code: 'Fira Code Variable', monospace;\n\n --spacing-section: 2rem;\n --spacing-group: 1.5rem;\n --spacing-element: 1rem;\n\n --radius-card: 0.75rem;\n --radius-modal: 1rem;\n\n --width-modal: 28rem;\n --width-toast: 20rem;\n --width-container: 80rem;\n}\n",
11
11
  "/lib/styles/index.css": "@import './tokens.css';\n@import './svelteforge-theme.css';\n",
12
12
  "/lib/styles/svelteforge-theme.css": "/* === SvelteForge Theme === */\n[data-theme='svelteForge'] {\n --text-scaling: 1.067;\n --base-font-color: var(--color-surface-950);\n --base-font-color-dark: var(--color-surface-50);\n --base-font-family: 'Inter Variable', sans-serif;\n --heading-font-family: 'Space Grotesk Variable', sans-serif;\n --heading-font-weight: bold;\n --anchor-font-color: var(--color-primary-700);\n --anchor-font-color-dark: var(--color-primary-200);\n --anchor-text-decoration: none;\n --anchor-text-decoration-hover: underline;\n --body-background-color: var(--color-surface-50);\n --body-background-color-dark: var(--color-surface-950);\n --spacing: 0.25rem;\n --radius-base: 0.375rem;\n --radius-container: 0.75rem;\n\n /* Primary: Steel blue */\n --color-primary-50: oklch(94.82% 0.04 216.84deg);\n --color-primary-100: oklch(88.84% 0.07 215.45deg);\n --color-primary-200: oklch(83.12% 0.09 215.47deg);\n --color-primary-300: oklch(78.03% 0.11 216.31deg);\n --color-primary-400: oklch(73.14% 0.12 218.53deg);\n --color-primary-500: oklch(68.74% 0.13 222.38deg);\n --color-primary-600: oklch(63.96% 0.12 222.67deg);\n --color-primary-700: oklch(59.05% 0.11 222.32deg);\n --color-primary-800: oklch(53.82% 0.1 223.63deg);\n --color-primary-900: oklch(48.67% 0.09 223.32deg);\n --color-primary-950: oklch(43.44% 0.08 223.92deg);\n --color-primary-contrast-dark: oklch(20.02% 0 none);\n --color-primary-contrast-light: oklch(97.91% 0 none);\n --color-primary-contrast-50: var(--color-primary-contrast-dark);\n --color-primary-contrast-100: var(--color-primary-contrast-dark);\n --color-primary-contrast-200: var(--color-primary-contrast-dark);\n --color-primary-contrast-300: var(--color-primary-contrast-dark);\n --color-primary-contrast-400: var(--color-primary-contrast-dark);\n --color-primary-contrast-500: var(--color-primary-contrast-dark);\n --color-primary-contrast-600: var(--color-primary-contrast-dark);\n --color-primary-contrast-700: var(--color-primary-contrast-dark);\n --color-primary-contrast-800: var(--color-primary-contrast-light);\n --color-primary-contrast-900: var(--color-primary-contrast-light);\n --color-primary-contrast-950: var(--color-primary-contrast-light);\n\n /* Secondary: Burnt orange */\n --color-secondary-50: oklch(93.05% 0.05 71.66deg);\n --color-secondary-100: oklch(86.31% 0.07 50.93deg);\n --color-secondary-200: oklch(79.78% 0.1 39.88deg);\n --color-secondary-300: oklch(73.56% 0.12 32.86deg);\n --color-secondary-400: oklch(67.93% 0.14 30.33deg);\n --color-secondary-500: oklch(63.07% 0.17 29.44deg);\n --color-secondary-600: oklch(58.47% 0.15 27.5deg);\n --color-secondary-700: oklch(53.83% 0.13 24.55deg);\n --color-secondary-800: oklch(49.31% 0.11 20.41deg);\n --color-secondary-900: oklch(44.67% 0.09 12.99deg);\n --color-secondary-950: oklch(40.1% 0.07 0.14deg);\n --color-secondary-contrast-dark: oklch(97.91% 0 none);\n --color-secondary-contrast-light: oklch(20.02% 0 none);\n --color-secondary-contrast-50: var(--color-secondary-contrast-light);\n --color-secondary-contrast-100: var(--color-secondary-contrast-light);\n --color-secondary-contrast-200: var(--color-secondary-contrast-light);\n --color-secondary-contrast-300: var(--color-secondary-contrast-light);\n --color-secondary-contrast-400: var(--color-secondary-contrast-light);\n --color-secondary-contrast-500: var(--color-secondary-contrast-light);\n --color-secondary-contrast-600: var(--color-secondary-contrast-dark);\n --color-secondary-contrast-700: var(--color-secondary-contrast-dark);\n --color-secondary-contrast-800: var(--color-secondary-contrast-dark);\n --color-secondary-contrast-900: var(--color-secondary-contrast-dark);\n --color-secondary-contrast-950: var(--color-secondary-contrast-dark);\n\n /* Tertiary: Soft teal */\n --color-tertiary-50: oklch(98.31% 0.02 196.74deg);\n --color-tertiary-100: oklch(90.4% 0.03 193.59deg);\n --color-tertiary-200: oklch(82.32% 0.03 191.41deg);\n --color-tertiary-300: oklch(74.29% 0.04 187.78deg);\n --color-tertiary-400: oklch(65.86% 0.04 186.58deg);\n --color-tertiary-500: oklch(57.22% 0.05 185.36deg);\n --color-tertiary-600: oklch(52.72% 0.05 190.1deg);\n --color-tertiary-700: oklch(48.14% 0.04 195.76deg);\n --color-tertiary-800: oklch(43.13% 0.04 204.75deg);\n --color-tertiary-900: oklch(38.31% 0.04 212.77deg);\n --color-tertiary-950: oklch(33.36% 0.04 221.8deg);\n --color-tertiary-contrast-dark: var(--color-tertiary-950);\n --color-tertiary-contrast-light: var(--color-tertiary-50);\n --color-tertiary-contrast-50: var(--color-tertiary-contrast-dark);\n --color-tertiary-contrast-100: var(--color-tertiary-contrast-dark);\n --color-tertiary-contrast-200: var(--color-tertiary-contrast-dark);\n --color-tertiary-contrast-300: var(--color-tertiary-contrast-dark);\n --color-tertiary-contrast-400: var(--color-tertiary-contrast-dark);\n --color-tertiary-contrast-500: var(--color-tertiary-contrast-light);\n --color-tertiary-contrast-600: var(--color-tertiary-contrast-light);\n --color-tertiary-contrast-700: var(--color-tertiary-contrast-light);\n --color-tertiary-contrast-800: var(--color-tertiary-contrast-light);\n --color-tertiary-contrast-900: var(--color-tertiary-contrast-light);\n --color-tertiary-contrast-950: var(--color-tertiary-contrast-light);\n\n /* Success */\n --color-success-50: oklch(93.9% 0.09 179.09deg);\n --color-success-100: oklch(91.57% 0.1 178.72deg);\n --color-success-200: oklch(89.48% 0.11 177.05deg);\n --color-success-300: oklch(87.2% 0.12 176.72deg);\n --color-success-400: oklch(85.21% 0.12 175.14deg);\n --color-success-500: oklch(83.08% 0.13 174.56deg);\n --color-success-600: oklch(72.99% 0.11 174.61deg);\n --color-success-700: oklch(62.32% 0.1 176.14deg);\n --color-success-800: oklch(51.37% 0.08 176.77deg);\n --color-success-900: oklch(39.58% 0.06 180.47deg);\n --color-success-950: oklch(27.1% 0.04 184.49deg);\n --color-success-contrast-dark: var(--color-success-950);\n --color-success-contrast-light: var(--color-success-50);\n --color-success-contrast-50: var(--color-success-contrast-dark);\n --color-success-contrast-100: var(--color-success-contrast-dark);\n --color-success-contrast-200: var(--color-success-contrast-dark);\n --color-success-contrast-300: var(--color-success-contrast-dark);\n --color-success-contrast-400: var(--color-success-contrast-dark);\n --color-success-contrast-500: var(--color-success-contrast-dark);\n --color-success-contrast-600: var(--color-success-contrast-dark);\n --color-success-contrast-700: var(--color-success-contrast-dark);\n --color-success-contrast-800: var(--color-success-contrast-light);\n --color-success-contrast-900: var(--color-success-contrast-light);\n --color-success-contrast-950: var(--color-success-contrast-light);\n\n /* Warning */\n --color-warning-50: oklch(95.79% 0.05 88.07deg);\n --color-warning-100: oklch(92.94% 0.07 85.27deg);\n --color-warning-200: oklch(90.05% 0.09 81.8deg);\n --color-warning-300: oklch(87.31% 0.11 80.89deg);\n --color-warning-400: oklch(84.57% 0.13 78.29deg);\n --color-warning-500: oklch(82.02% 0.14 76.71deg);\n --color-warning-600: oklch(76.11% 0.14 72.37deg);\n --color-warning-700: oklch(70.14% 0.13 67.8deg);\n --color-warning-800: oklch(64% 0.13 62.98deg);\n --color-warning-900: oklch(57.93% 0.13 57.46deg);\n --color-warning-950: oklch(51.87% 0.13 51.28deg);\n --color-warning-contrast-dark: var(--color-warning-950);\n --color-warning-contrast-light: var(--color-warning-50);\n --color-warning-contrast-50: var(--color-warning-contrast-dark);\n --color-warning-contrast-100: var(--color-warning-contrast-dark);\n --color-warning-contrast-200: var(--color-warning-contrast-dark);\n --color-warning-contrast-300: var(--color-warning-contrast-dark);\n --color-warning-contrast-400: var(--color-warning-contrast-dark);\n --color-warning-contrast-500: var(--color-warning-contrast-dark);\n --color-warning-contrast-600: var(--color-warning-contrast-dark);\n --color-warning-contrast-700: var(--color-warning-contrast-light);\n --color-warning-contrast-800: var(--color-warning-contrast-light);\n --color-warning-contrast-900: var(--color-warning-contrast-light);\n --color-warning-contrast-950: var(--color-warning-contrast-light);\n\n /* Error */\n --color-error-50: oklch(89.99% 0.04 14.04deg);\n --color-error-100: oklch(83.6% 0.08 19.82deg);\n --color-error-200: oklch(77.52% 0.11 21.99deg);\n --color-error-300: oklch(72.26% 0.15 24.9deg);\n --color-error-400: oklch(67.55% 0.19 26.72deg);\n --color-error-500: oklch(64.06% 0.22 28.71deg);\n --color-error-600: oklch(59.71% 0.21 28.7deg);\n --color-error-700: oklch(55.17% 0.2 28.73deg);\n --color-error-800: oklch(50.88% 0.19 28.77deg);\n --color-error-900: oklch(46.35% 0.18 28.89deg);\n --color-error-950: oklch(42.03% 0.17 29.27deg);\n --color-error-contrast-dark: var(--color-error-950);\n --color-error-contrast-light: var(--color-error-50);\n --color-error-contrast-50: var(--color-error-contrast-dark);\n --color-error-contrast-100: var(--color-error-contrast-dark);\n --color-error-contrast-200: var(--color-error-contrast-dark);\n --color-error-contrast-300: var(--color-error-contrast-dark);\n --color-error-contrast-400: var(--color-error-contrast-dark);\n --color-error-contrast-500: var(--color-error-contrast-light);\n --color-error-contrast-600: var(--color-error-contrast-light);\n --color-error-contrast-700: var(--color-error-contrast-light);\n --color-error-contrast-800: var(--color-error-contrast-light);\n --color-error-contrast-900: var(--color-error-contrast-light);\n --color-error-contrast-950: var(--color-error-contrast-light);\n\n /* Surface: Subtle blue tint */\n --color-surface-50: oklch(96% 0.01 260deg);\n --color-surface-100: oklch(90% 0.01 260deg);\n --color-surface-200: oklch(78.47% 0.02 264.48deg);\n --color-surface-300: oklch(68.53% 0.03 265.52deg);\n --color-surface-400: oklch(58.21% 0.04 265.04deg);\n --color-surface-500: oklch(47.47% 0.05 264.53deg);\n --color-surface-600: oklch(42.05% 0.05 264.5deg);\n --color-surface-700: oklch(36.45% 0.05 264.44deg);\n --color-surface-800: oklch(30.96% 0.04 263.65deg);\n --color-surface-900: oklch(24.88% 0.04 263.42deg);\n --color-surface-950: oklch(18.45% 0.04 262.95deg);\n --color-surface-contrast-dark: var(--color-surface-950);\n --color-surface-contrast-light: var(--color-surface-50);\n --color-surface-contrast-50: var(--color-surface-contrast-dark);\n --color-surface-contrast-100: var(--color-surface-contrast-dark);\n --color-surface-contrast-200: var(--color-surface-contrast-dark);\n --color-surface-contrast-300: var(--color-surface-contrast-dark);\n --color-surface-contrast-400: var(--color-surface-contrast-dark);\n --color-surface-contrast-500: var(--color-surface-contrast-light);\n --color-surface-contrast-600: var(--color-surface-contrast-light);\n --color-surface-contrast-700: var(--color-surface-contrast-light);\n --color-surface-contrast-800: var(--color-surface-contrast-light);\n --color-surface-contrast-900: var(--color-surface-contrast-light);\n --color-surface-contrast-950: var(--color-surface-contrast-light);\n}\n",
13
- "/lib/components/ui/ThemeToggle.svelte": "<script lang=\"ts\">\n import Sun from 'phosphor-svelte/lib/Sun';\n import Moon from 'phosphor-svelte/lib/Moon';\n import { onMount } from 'svelte';\n\n interface Props {\n class?: string;\n }\n\n let { class: className = '' }: Props = $props();\n\n let isDark = $state(true);\n\n function applyMode(dark: boolean) {\n const mode = dark ? 'dark' : 'light';\n document.documentElement.setAttribute('data-mode', mode);\n document.documentElement.style.colorScheme = mode;\n }\n\n onMount(() => {\n const stored = localStorage.getItem('theme-mode');\n if (stored) {\n // User has a manual preference\n isDark = stored === 'dark';\n } else {\n // Follow system\n isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;\n }\n applyMode(isDark);\n });\n\n function toggle() {\n isDark = !isDark;\n applyMode(isDark);\n localStorage.setItem('theme-mode', isDark ? 'dark' : 'light');\n }\n<\/script>\n\n<button\n onclick={toggle}\n class=\"btn hover:preset-tonal-surface p-2 rounded-full {className}\"\n aria-label=\"Toggle theme\"\n>\n {#if isDark}\n <Moon size={18} />\n {:else}\n <Sun size={18} />\n {/if}\n</button>\n",
14
- "/lib/components/ui/index.ts": "export { default as Accordion } from './Accordion.svelte';\nexport { default as Alert } from './Alert.svelte';\nexport { default as Avatar } from './Avatar.svelte';\nexport { default as Badge } from './Badge.svelte';\nexport { default as Breadcrumb } from './Breadcrumb.svelte';\nexport { default as Button } from './Button.svelte';\nexport { default as Card } from './Card.svelte';\nexport { default as Checkbox } from './Checkbox.svelte';\nexport { default as Input } from './Input.svelte';\nexport { default as Select } from './Select.svelte';\nexport { default as Table } from './Table.svelte';\nexport { default as Tabs } from './Tabs.svelte';\nexport { default as Textarea } from './Textarea.svelte';\nexport { default as ThemeToggle } from './ThemeToggle.svelte';\nexport { default as Toggle } from './Toggle.svelte';\n",
15
- "/lib/components/ui/Breadcrumb.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import CaretRight from 'phosphor-svelte/lib/CaretRight';\n\n interface Crumb {\n href?: string;\n label: string;\n }\n\n interface Props {\n items: Crumb[];\n class?: string;\n }\n\n let { items, class: className = '' }: Props = $props();\n<\/script>\n\n<nav aria-label=\"Breadcrumb\" class={className}>\n <ol class=\"flex items-center gap-1 text-sm\">\n {#each items as item, i}\n <li class=\"flex items-center gap-1\">\n {#if i > 0}\n <CaretRight size={14} class=\"text-surface-400\" />\n {/if}\n {#if item.href}\n <a href={item.href} class=\"hover:text-primary-500 transition-colors\">\n {item.label}\n </a>\n {:else}\n <span class=\"text-surface-500\">{item.label}</span>\n {/if}\n </li>\n {/each}\n </ol>\n</nav>\n",
16
- "/lib/components/ui/Accordion.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import CaretDown from 'phosphor-svelte/lib/CaretDown';\n\n interface AccordionItem {\n title: string;\n content: string;\n }\n\n interface Props {\n items: AccordionItem[];\n class?: string;\n }\n\n let { items, class: className = '' }: Props = $props();\n let openIndex = $state<number | null>(null);\n\n function toggle(i: number) {\n openIndex = openIndex === i ? null : i;\n }\n<\/script>\n\n<div class={cn('divide-y divide-surface-200-800 rounded-card border border-surface-200-800', className)}>\n {#each items as item, i}\n <div>\n <button\n class=\"w-full flex items-center justify-between p-element hover:bg-surface-100-800 transition-colors\"\n onclick={() => toggle(i)}\n >\n <span class=\"font-medium\">{item.title}</span>\n <CaretDown\n size={18}\n class={cn('transition-transform', openIndex === i && 'rotate-180')}\n />\n </button>\n {#if openIndex === i}\n <div class=\"px-element pb-element text-surface-500-400\">\n {item.content}\n </div>\n {/if}\n </div>\n {/each}\n</div>\n",
17
- "/lib/components/ui/Badge.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import type { Snippet } from 'svelte';\n import type { HTMLAttributes } from 'svelte/elements';\n\n type Variant = 'filled' | 'outlined' | 'tonal';\n type Color = 'primary' | 'secondary' | 'tertiary' | 'success' | 'warning' | 'error' | 'surface';\n type Size = 'sm' | 'md' | 'lg';\n\n interface Props extends HTMLAttributes<HTMLSpanElement> {\n variant?: Variant;\n color?: Color;\n size?: Size;\n class?: string;\n children: Snippet;\n }\n\n let {\n variant = 'filled',\n color = 'primary',\n size = 'md',\n class: className = '',\n children,\n ...rest\n }: Props = $props();\n\n const presets = {\n filled: {\n primary: 'preset-filled-primary-400-600',\n secondary: 'preset-filled-secondary-400-600',\n tertiary: 'preset-filled-tertiary-400-600',\n success: 'preset-filled-success-400-600',\n warning: 'preset-filled-warning-400-600',\n error: 'preset-filled-error-400-600',\n surface: 'preset-filled-surface-400-600'\n },\n outlined: {\n primary: 'preset-outlined-primary-400-600',\n secondary: 'preset-outlined-secondary-400-600',\n tertiary: 'preset-outlined-tertiary-400-600',\n success: 'preset-outlined-success-400-600',\n warning: 'preset-outlined-warning-400-600',\n error: 'preset-outlined-error-400-600',\n surface: 'preset-outlined-surface-400-600'\n },\n tonal: {\n primary: 'preset-tonal-primary',\n secondary: 'preset-tonal-secondary',\n tertiary: 'preset-tonal-tertiary',\n success: 'preset-tonal-success',\n warning: 'preset-tonal-warning',\n error: 'preset-tonal-error',\n surface: 'preset-tonal-surface'\n }\n } as const;\n\n let sizeClass = $derived(\n size === 'sm' ? 'badge-sm' : size === 'lg' ? 'badge-lg' : 'badge-md'\n );\n\n let presetClass = $derived(presets[variant][color]);\n\n let classes = $derived(cn('badge', presetClass, sizeClass, className));\n<\/script>\n\n<span class={classes} {...rest}>\n {@render children()}\n</span>\n",
18
- "/lib/components/ui/Input.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import type { HTMLAttributes } from 'svelte/elements';\n\n interface Props extends HTMLAttributes<HTMLInputElement> {\n label?: string;\n error?: string;\n type?: string;\n placeholder?: string;\n value?: string;\n required?: boolean;\n name?: string;\n class?: string;\n }\n let { label, error, class: className, value = $bindable(''), ...rest }: Props = $props();\n let inputId = $derived(rest.id ?? `input-${Math.random().toString(36).slice(2, 9)}`);\n<\/script>\n\n<div class=\"w-full\">\n {#if label}\n <label class=\"label\" for={inputId}>\n {label}\n </label>\n {/if}\n <input\n id={inputId}\n class={cn('input', error && 'input-error', className)}\n bind:value\n {...rest}\n />\n {#if error}\n <p class=\"text-error-500 text-sm mt-1\">{error}</p>\n {/if}\n</div>\n",
19
- "/lib/components/ui/Checkbox.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import type { HTMLAttributes } from 'svelte/elements';\n\n interface Props extends HTMLAttributes<HTMLInputElement> {\n label?: string;\n checked?: boolean;\n class?: string;\n }\n\n let { label, class: className, checked = $bindable(false), ...rest }: Props = $props();\n<\/script>\n\n<label class={cn('flex items-center gap-2 cursor-pointer', className)}>\n <input type=\"checkbox\" class=\"checkbox\" bind:checked {...rest} />\n {#if label}\n <span>{label}</span>\n {/if}\n</label>\n",
20
- "/lib/components/ui/Select.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import type { HTMLAttributes } from 'svelte/elements';\n\n interface Props extends HTMLAttributes<HTMLSelectElement> {\n label?: string;\n error?: string;\n options: { value: string; label: string }[];\n value?: string;\n class?: string;\n }\n\n let { label, error, options, class: className, value = $bindable(''), ...rest }: Props = $props();\n<\/script>\n\n<div class=\"w-full\">\n {#if label}\n <label class=\"label\" for={rest.id}>\n {label}\n </label>\n {/if}\n <select class={cn('select', error && 'select-error', className)} bind:value {...rest}>\n {#each options as opt}\n <option value={opt.value}>{opt.label}</option>\n {/each}\n </select>\n {#if error}\n <p class=\"text-error-500 text-sm mt-1\">{error}</p>\n {/if}\n</div>\n",
21
- "/lib/components/ui/Alert.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import type { Snippet } from 'svelte';\n import type { HTMLAttributes } from 'svelte/elements';\n\n type Variant = 'info' | 'success' | 'warning' | 'error';\n\n interface Props extends HTMLAttributes<HTMLDivElement> {\n variant?: Variant;\n class?: string;\n children: Snippet;\n }\n\n let { variant = 'info', class: className = '', children, ...rest }: Props = $props();\n\n const presets: Record<Variant, string> = {\n info: 'preset-tonal-info',\n success: 'preset-tonal-success',\n warning: 'preset-tonal-warning',\n error: 'preset-tonal-error'\n };\n\n let classes = $derived(cn('alert', presets[variant], className));\n<\/script>\n\n<div class={classes} {...rest}>\n {@render children()}\n</div>\n",
22
- "/lib/components/ui/Tabs.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n\n interface Tab {\n label: string;\n content: string;\n }\n\n interface Props {\n tabs: Tab[];\n class?: string;\n }\n\n let { tabs, class: className = '' }: Props = $props();\n let activeTab = $state(0);\n<\/script>\n\n<div class={className}>\n <div class=\"flex border-b border-surface-200-800 gap-1\">\n {#each tabs as tab, i}\n <button\n class={cn(\n 'px-4 py-2 font-medium transition-colors rounded-t-card',\n activeTab === i\n ? 'text-primary-500 border-b-2 border-primary-500'\n : 'text-surface-500 hover:text-surface-900-100'\n )}\n onclick={() => (activeTab = i)}\n >\n {tab.label}\n </button>\n {/each}\n </div>\n <div class=\"p-element\">\n {tabs[activeTab].content}\n </div>\n</div>\n",
23
- "/lib/components/ui/Toggle.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n\n interface Props {\n label?: string;\n checked?: boolean;\n class?: string;\n }\n\n let { label, class: className, checked = $bindable(false) }: Props = $props();\n<\/script>\n\n<label class={cn('flex items-center gap-2 cursor-pointer', className)}>\n <input type=\"checkbox\" class=\"toggle\" bind:checked />\n {#if label}\n <span>{label}</span>\n {/if}\n</label>\n",
24
- "/lib/components/ui/Card.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import type { Snippet } from 'svelte';\n import type { HTMLAttributes } from 'svelte/elements';\n\n type Variant = 'flat' | 'elevated' | 'outlined';\n\n interface Props extends HTMLAttributes<HTMLDivElement> {\n variant?: Variant;\n class?: string;\n children: Snippet;\n header?: Snippet;\n footer?: Snippet;\n }\n\n let { variant = 'flat', class: className = '', children, header, footer, ...rest }: Props = $props();\n\n const variantClasses: Record<Variant, string> = {\n flat: 'card',\n elevated: 'card shadow-lg',\n outlined: 'card ring-1 ring-surface-200-800'\n };\n\n let classes = $derived(cn(variantClasses[variant], 'p-element rounded-card', className));\n<\/script>\n\n<div class={classes} {...rest}>\n {#if header}\n <div class=\"border-b border-surface-200-800 pb-3 mb-3\">\n {@render header()}\n </div>\n {/if}\n\n {@render children()}\n\n {#if footer}\n <div class=\"border-t border-surface-200-800 pt-3 mt-3\">\n {@render footer()}\n </div>\n {/if}\n</div>\n",
25
- "/lib/components/ui/Table.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import type { HTMLAttributes } from 'svelte/elements';\n\n interface Column {\n key: string;\n label: string;\n class?: string;\n }\n\n interface Props extends HTMLAttributes<HTMLDivElement> {\n columns: Column[];\n rows: Record<string, string>[];\n class?: string;\n }\n\n let { columns, rows, class: className = '', ...rest }: Props = $props();\n<\/script>\n\n<div class={cn('overflow-x-auto rounded-card border border-surface-200-800', className)} {...rest}>\n <table class=\"w-full\">\n <thead class=\"bg-surface-100-800\">\n <tr>\n {#each columns as col}\n <th class=\"px-element py-3 text-left text-sm font-semibold text-surface-500 {col.class}\">\n {col.label}\n </th>\n {/each}\n </tr>\n </thead>\n <tbody class=\"divide-y divide-surface-200-800\">\n {#each rows as row}\n <tr class=\"hover:bg-surface-50-900 transition-colors\">\n {#each columns as col}\n <td class=\"px-element py-3 text-sm {col.class}\">\n {row[col.key]}\n </td>\n {/each}\n </tr>\n {/each}\n </tbody>\n </table>\n</div>\n",
26
- "/lib/components/ui/Textarea.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import type { HTMLAttributes } from 'svelte/elements';\n\n interface Props extends HTMLAttributes<HTMLTextAreaElement> {\n label?: string;\n error?: string;\n placeholder?: string;\n rows?: number;\n value?: string;\n class?: string;\n }\n\n let { label, error, class: className, value = $bindable(''), ...rest }: Props = $props();\n let inputId = $derived(rest.id ?? `textarea-${Math.random().toString(36).slice(2, 9)}`);\n<\/script>\n\n<div class=\"w-full\">\n {#if label}\n <label class=\"label\" for={inputId}>\n {label}\n </label>\n {/if}\n <textarea\n id={inputId}\n class={cn('textarea', error && 'textarea-error', className)}\n bind:value\n {...rest}\n ></textarea>\n {#if error}\n <p class=\"text-error-500 text-sm mt-1\">{error}</p>\n {/if}\n</div>\n",
27
- "/lib/components/ui/Avatar.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n\n interface Props {\n src: string;\n alt?: string;\n size?: 'sm' | 'md' | 'lg';\n class?: string;\n }\n\n let { size = 'md', class: className = '', src, alt = '' }: Props = $props();\n\n const sizes: Record<string, string> = {\n sm: 'w-8 h-8',\n md: 'w-10 h-10',\n lg: 'w-14 h-14'\n };\n<\/script>\n\n<img\n {src}\n {alt}\n class={cn('rounded-full object-cover ring-2 ring-surface-200-800', sizes[size], className)}\n/>\n",
28
- "/lib/components/ui/Button.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import type { Snippet } from 'svelte';\n import type { HTMLAttributes } from 'svelte/elements';\n\n type Variant = 'filled' | 'outlined' | 'tonal' | 'ghost';\n type Color = 'primary' | 'secondary' | 'tertiary' | 'success' | 'warning' | 'error' | 'surface';\n type Size = 'sm' | 'md' | 'lg';\n\n interface Props extends HTMLAttributes<HTMLButtonElement> {\n variant?: Variant;\n color?: Color;\n size?: Size;\n href?: string;\n loading?: boolean;\n disabled?: boolean;\n type?: 'button' | 'submit' | 'reset';\n class?: string;\n children: Snippet;\n }\n\n let {\n variant = 'filled',\n color = 'primary',\n size = 'md',\n href,\n loading = false,\n disabled = false,\n type = 'button',\n class: className = '',\n children,\n ...rest\n }: Props = $props();\n\n const presets = {\n filled: {\n primary: 'preset-filled-primary-400-600',\n secondary: 'preset-filled-secondary-400-600',\n tertiary: 'preset-filled-tertiary-400-600',\n success: 'preset-filled-success-400-600',\n warning: 'preset-filled-warning-400-600',\n error: 'preset-filled-error-400-600',\n surface: 'preset-filled-surface-400-600'\n },\n outlined: {\n primary: 'preset-outlined-primary-400-600',\n secondary: 'preset-outlined-secondary-400-600',\n tertiary: 'preset-outlined-tertiary-400-600',\n success: 'preset-outlined-success-400-600',\n warning: 'preset-outlined-warning-400-600',\n error: 'preset-outlined-error-400-600',\n surface: 'preset-outlined-surface-400-600'\n },\n tonal: {\n primary: 'preset-tonal-primary',\n secondary: 'preset-tonal-secondary',\n tertiary: 'preset-tonal-tertiary',\n success: 'preset-tonal-success',\n warning: 'preset-tonal-warning',\n error: 'preset-tonal-error',\n surface: 'preset-tonal-surface'\n },\n ghost: {\n primary: 'hover:preset-tonal-primary',\n secondary: 'hover:preset-tonal-secondary',\n tertiary: 'hover:preset-tonal-tertiary',\n success: 'hover:preset-tonal-success',\n warning: 'hover:preset-tonal-warning',\n error: 'hover:preset-tonal-error',\n surface: 'hover:preset-tonal-surface'\n }\n } as const;\n\n let sizeClass = $derived(\n size === 'sm' ? 'btn-sm' : size === 'lg' ? 'btn-lg' : 'btn-md'\n );\n\n let presetClass = $derived(\n presets[variant]?.[color] ?? ''\n );\n\n let classes = $derived(cn('btn', presetClass, sizeClass, className));\n<\/script>\n\n{#if href}\n <a {href} class={classes}>\n {#if loading}\n <span\n class=\"mr-2 inline-block h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent\"\n ></span>\n {/if}\n {@render children()}\n </a>\n{:else}\n <button class={classes} disabled={disabled || loading} type={type} {...rest} aria-busy={loading}>\n {#if loading}\n <span\n class=\"mr-2 inline-block h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent\"\n ></span>\n {/if}\n {@render children()}\n </button>\n{/if}\n",
29
- "/lib/components/layout/Navbar.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import type { Snippet } from 'svelte';\n import type { HTMLAttributes } from 'svelte/elements';\n import ThemeToggle from '$lib/components/ui/ThemeToggle.svelte';\n import Menu from 'phosphor-svelte/lib/List';\n import X from 'phosphor-svelte/lib/X';\n\n interface Props extends HTMLAttributes<HTMLElement> {\n brand?: Snippet;\n links?: { href: string; label: string }[];\n class?: string;\n }\n\n let { brand, links = [], class: className, ...rest }: Props = $props();\n let mobileOpen = $state(false);\n<\/script>\n\n<nav class={cn('sticky top-0 z-50 bg-surface-50-950/80 backdrop-blur-md border-b border-surface-200-800', className)} {...rest}>\n <div class=\"max-w-container mx-auto flex items-center justify-between px-element py-3\">\n <!-- Brand -->\n <a href=\"/\" class=\"text-xl font-heading font-bold text-primary-500\">\n {#if brand}\n {@render brand()}\n {:else}\n SvelteForge\n {/if}\n </a>\n\n <!-- Desktop links -->\n <div class=\"hidden md:flex items-center gap-4\">\n {#each links as link}\n <a href={link.href} class=\"hover:text-primary-500 transition-colors\">\n {link.label}\n </a>\n {/each}\n <ThemeToggle />\n </div>\n\n <!-- Mobile toggle -->\n <button\n class=\"md:hidden btn hover:preset-tonal-surface p-2 rounded-full\"\n onclick={() => (mobileOpen = !mobileOpen)}\n aria-label=\"Toggle menu\"\n >\n {#if mobileOpen}\n <X size={20} />\n {:else}\n <Menu size={20} />\n {/if}\n </button>\n </div>\n\n <!-- Mobile menu -->\n {#if mobileOpen}\n <div class=\"md:hidden border-t border-surface-200-800 px-element py-3 flex flex-col gap-3\">\n {#each links as link}\n <a href={link.href} class=\"hover:text-primary-500 transition-colors\" onclick={() => (mobileOpen = false)}>\n {link.label}\n </a>\n {/each}\n <ThemeToggle />\n </div>\n {/if}\n</nav>\n",
30
- "/lib/components/layout/Footer.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import type { Snippet } from 'svelte';\n import type { HTMLAttributes } from 'svelte/elements';\n\n interface Props extends HTMLAttributes<HTMLElement> {\n links?: { href: string; label: string }[];\n class?: string;\n children?: Snippet;\n }\n\n let { links = [], class: className, children, ...rest }: Props = $props();\n<\/script>\n\n<footer class={cn('border-t border-surface-200-800 bg-surface-100-900', className)} {...rest}>\n <div class=\"max-w-container mx-auto px-element py-section\">\n <div class=\"flex flex-col md:flex-row justify-between gap-group\">\n {#if children}\n {@render children()}\n {/if}\n\n {#if links.length}\n <div class=\"flex flex-col gap-2\">\n {#each links as link}\n <a href={link.href} class=\"text-sm hover:text-primary-500 transition-colors\">\n {link.label}\n </a>\n {/each}\n </div>\n {/if}\n </div>\n\n <div class=\"mt-section pt-3 border-t border-surface-200-800 text-sm text-surface-500\">\n &copy; {new Date().getFullYear()} SvelteForge. All rights reserved.\n </div>\n </div>\n</footer>\n",
13
+ "/lib/components/layout/Navbar.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import type { Snippet } from 'svelte';\n import type { HTMLAttributes } from 'svelte/elements';\n import ThemeToggle from '$lib/components/svforge/ui/ThemeToggle.svelte';\n import Logo from '$lib/components/svforge/ui/Logo.svelte';\n import Menu from 'phosphor-svelte/lib/List';\n import X from 'phosphor-svelte/lib/X';\n\n interface Props extends HTMLAttributes<HTMLElement> {\n brand?: Snippet;\n links?: { href: string; label: string }[];\n class?: string;\n }\n\n let { brand, links = [], class: className, ...rest }: Props = $props();\n let mobileOpen = $state(false);\n<\/script>\n\n<nav class={cn('sticky top-0 z-50 bg-surface-50-950/80 backdrop-blur-md border-b border-surface-200-800', className)} {...rest}>\n <div class=\"max-w-container mx-auto flex items-center justify-between px-element py-3\">\n <!-- Brand -->\n <a href=\"/\" class=\"text-xl font-bold\">\n {#if brand}\n {@render brand()}\n {:else}\n <Logo />\n {/if}\n </a>\n\n <!-- Desktop links -->\n <div class=\"hidden md:flex items-center gap-4\">\n {#each links as link}\n <a href={link.href} class=\"hover:text-primary-500 transition-colors\">\n {link.label}\n </a>\n {/each}\n <ThemeToggle />\n </div>\n\n <!-- Mobile toggle -->\n <button\n class=\"md:hidden btn hover:preset-tonal-surface p-2 rounded-full\"\n onclick={() => (mobileOpen = !mobileOpen)}\n aria-label=\"Toggle menu\"\n >\n {#if mobileOpen}\n <X size={20} />\n {:else}\n <Menu size={20} />\n {/if}\n </button>\n </div>\n\n <!-- Mobile menu -->\n {#if mobileOpen}\n <div class=\"md:hidden border-t border-surface-200-800 px-element py-3 flex flex-col gap-3\">\n {#each links as link}\n <a href={link.href} class=\"hover:text-primary-500 transition-colors\" onclick={() => (mobileOpen = false)}>\n {link.label}\n </a>\n {/each}\n <ThemeToggle />\n </div>\n {/if}\n</nav>\n",
14
+ "/lib/components/layout/Footer.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import type { Snippet } from 'svelte';\n import type { HTMLAttributes } from 'svelte/elements';\n\n interface Props extends HTMLAttributes<HTMLElement> {\n links?: { href: string; label: string }[];\n class?: string;\n children?: Snippet;\n }\n\n let { links = [], class: className, children, ...rest }: Props = $props();\n<\/script>\n\n<footer class={cn('border-t border-surface-200-800 bg-surface-100-900', className)} {...rest}>\n <div class=\"max-w-container mx-auto px-element py-section\">\n <div class=\"flex flex-col md:flex-row justify-between gap-group\">\n {#if children}\n {@render children()}\n {/if}\n\n {#if links.length}\n <div class=\"flex flex-col gap-2\">\n {#each links as link}\n <a href={link.href} class=\"text-sm hover:text-primary-500 transition-colors\">\n {link.label}\n </a>\n {/each}\n </div>\n {/if}\n </div>\n\n <div class=\"mt-section pt-3 border-t border-surface-200-800 text-sm text-surface-500\">\n &copy; {new Date().getFullYear()} SVForge. All rights reserved.\n </div>\n </div>\n</footer>\n",
31
15
  "/lib/components/layout/index.ts": "export { default as Footer } from './Footer.svelte';\nexport { default as Navbar } from './Navbar.svelte';\n",
16
+ "/lib/components/svforge/ui/ThemeToggle.svelte": "<script lang=\"ts\">\n import Sun from 'phosphor-svelte/lib/Sun';\n import Moon from 'phosphor-svelte/lib/Moon';\n import { onMount } from 'svelte';\n\n interface Props {\n class?: string;\n }\n\n let { class: className = '' }: Props = $props();\n\n let isDark = $state(true);\n\n function applyMode(dark: boolean) {\n const mode = dark ? 'dark' : 'light';\n document.documentElement.setAttribute('data-mode', mode);\n document.documentElement.style.colorScheme = mode;\n }\n\n onMount(() => {\n const stored = localStorage.getItem('theme-mode');\n if (stored) {\n // User has a manual preference\n isDark = stored === 'dark';\n } else {\n // Follow system\n isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;\n }\n applyMode(isDark);\n });\n\n function toggle() {\n isDark = !isDark;\n applyMode(isDark);\n localStorage.setItem('theme-mode', isDark ? 'dark' : 'light');\n }\n<\/script>\n\n<button\n onclick={toggle}\n class=\"btn hover:preset-tonal-surface p-2 rounded-full {className}\"\n aria-label=\"Toggle theme\"\n>\n {#if isDark}\n <Moon size={18} />\n {:else}\n <Sun size={18} />\n {/if}\n</button>\n",
17
+ "/lib/components/svforge/ui/Seo.svelte": "<script lang=\"ts\">\n import { page } from '$app/stores';\n\n interface Props {\n title: string;\n description: string;\n image?: string;\n url?: string;\n type?: string;\n }\n\n let { title, description, image, url, type = 'website' }: Props = $props();\n\n const resolvedUrl = $derived(url ?? $page.url.href);\n const twitterCard = $derived(image ? 'summary_large_image' : 'summary');\n<\/script>\n\n<svelte:head>\n <title>{title}</title>\n <meta name=\"description\" content={description} />\n <meta property=\"og:title\" content={title} />\n <meta property=\"og:description\" content={description} />\n <meta property=\"og:type\" content={type} />\n <meta property=\"og:url\" content={resolvedUrl} />\n {#if image}\n <meta property=\"og:image\" content={image} />\n {/if}\n <meta name=\"twitter:card\" content={twitterCard} />\n <meta name=\"twitter:title\" content={title} />\n <meta name=\"twitter:description\" content={description} />\n {#if image}\n <meta name=\"twitter:image\" content={image} />\n {/if}\n</svelte:head>\n",
18
+ "/lib/components/svforge/ui/index.ts": "export { default as Accordion } from './Accordion.svelte';\nexport { default as Alert } from './Alert.svelte';\nexport { default as Avatar } from './Avatar.svelte';\nexport { default as Badge } from './Badge.svelte';\nexport { default as Breadcrumb } from './Breadcrumb.svelte';\nexport { default as Button } from './Button.svelte';\nexport { default as Card } from './Card.svelte';\nexport { default as Checkbox } from './Checkbox.svelte';\nexport { default as Input } from './Input.svelte';\nexport { default as Select } from './Select.svelte';\nexport { default as Table } from './Table.svelte';\nexport { default as Tabs } from './Tabs.svelte';\nexport { default as Textarea } from './Textarea.svelte';\nexport { default as ThemeToggle } from './ThemeToggle.svelte';\nexport { default as Toggle } from './Toggle.svelte';\nexport { default as Logo } from './Logo.svelte';\nexport { default as Seo } from './Seo.svelte';\nexport { generateSitemap } from './Sitemap';\n",
19
+ "/lib/components/svforge/ui/Breadcrumb.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import CaretRight from 'phosphor-svelte/lib/CaretRight';\n\n interface Crumb {\n href?: string;\n label: string;\n }\n\n interface Props {\n items: Crumb[];\n class?: string;\n }\n\n let { items, class: className = '' }: Props = $props();\n<\/script>\n\n<nav aria-label=\"Breadcrumb\" class={className}>\n <ol class=\"flex items-center gap-1 text-sm\">\n {#each items as item, i}\n <li class=\"flex items-center gap-1\">\n {#if i > 0}\n <CaretRight size={14} class=\"text-surface-400\" />\n {/if}\n {#if item.href}\n <a href={item.href} class=\"hover:text-primary-500 transition-colors\">\n {item.label}\n </a>\n {:else}\n <span class=\"text-surface-500\">{item.label}</span>\n {/if}\n </li>\n {/each}\n </ol>\n</nav>\n",
20
+ "/lib/components/svforge/ui/Sitemap.ts": "interface SitemapEntry {\n path: string;\n lastmod?: string;\n changefreq?: string;\n priority?: number;\n}\n\nexport function generateSitemap(baseUrl: string, routes: SitemapEntry[]): string {\n const entries = routes\n .map((route) => {\n const loc = `${baseUrl}${route.path}`;\n let xml = ` <url>\\n <loc>${loc}</loc>`;\n if (route.lastmod) xml += `\\n <lastmod>${route.lastmod}</lastmod>`;\n if (route.changefreq) xml += `\\n <changefreq>${route.changefreq}</changefreq>`;\n if (route.priority !== undefined) xml += `\\n <priority>${route.priority}</priority>`;\n xml += '\\n </url>';\n return xml;\n })\n .join('\\n');\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n${entries}\n</urlset>`;\n}\n",
21
+ "/lib/components/svforge/ui/Accordion.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import CaretDown from 'phosphor-svelte/lib/CaretDown';\n\n interface AccordionItem {\n title: string;\n content: string;\n }\n\n interface Props {\n items: AccordionItem[];\n class?: string;\n }\n\n let { items, class: className = '' }: Props = $props();\n let openIndex = $state<number | null>(null);\n\n function toggle(i: number) {\n openIndex = openIndex === i ? null : i;\n }\n<\/script>\n\n<div class={cn('divide-y divide-surface-200-800 rounded-card border border-surface-200-800', className)}>\n {#each items as item, i}\n <div>\n <button\n class=\"w-full flex items-center justify-between p-element hover:bg-surface-100-800 transition-colors\"\n onclick={() => toggle(i)}\n >\n <span class=\"font-medium\">{item.title}</span>\n <CaretDown\n size={18}\n class={cn('transition-transform', openIndex === i && 'rotate-180')}\n />\n </button>\n {#if openIndex === i}\n <div class=\"px-element pb-element text-surface-500-400\">\n {item.content}\n </div>\n {/if}\n </div>\n {/each}\n</div>\n",
22
+ "/lib/components/svforge/ui/Badge.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import type { Snippet } from 'svelte';\n import type { HTMLAttributes } from 'svelte/elements';\n\n type Variant = 'filled' | 'outlined' | 'tonal';\n type Color = 'primary' | 'secondary' | 'tertiary' | 'success' | 'warning' | 'error' | 'surface';\n type Size = 'sm' | 'md' | 'lg';\n\n interface Props extends HTMLAttributes<HTMLSpanElement> {\n variant?: Variant;\n color?: Color;\n size?: Size;\n class?: string;\n children: Snippet;\n }\n\n let {\n variant = 'filled',\n color = 'primary',\n size = 'md',\n class: className = '',\n children,\n ...rest\n }: Props = $props();\n\n const presets = {\n filled: {\n primary: 'preset-filled-primary-400-600',\n secondary: 'preset-filled-secondary-400-600',\n tertiary: 'preset-filled-tertiary-400-600',\n success: 'preset-filled-success-400-600',\n warning: 'preset-filled-warning-400-600',\n error: 'preset-filled-error-400-600',\n surface: 'preset-filled-surface-400-600'\n },\n outlined: {\n primary: 'preset-outlined-primary-400-600',\n secondary: 'preset-outlined-secondary-400-600',\n tertiary: 'preset-outlined-tertiary-400-600',\n success: 'preset-outlined-success-400-600',\n warning: 'preset-outlined-warning-400-600',\n error: 'preset-outlined-error-400-600',\n surface: 'preset-outlined-surface-400-600'\n },\n tonal: {\n primary: 'preset-tonal-primary',\n secondary: 'preset-tonal-secondary',\n tertiary: 'preset-tonal-tertiary',\n success: 'preset-tonal-success',\n warning: 'preset-tonal-warning',\n error: 'preset-tonal-error',\n surface: 'preset-tonal-surface'\n }\n } as const;\n\n let sizeClass = $derived(\n size === 'sm' ? 'badge-sm' : size === 'lg' ? 'badge-lg' : 'badge-md'\n );\n\n let presetClass = $derived(presets[variant][color]);\n\n let classes = $derived(cn('badge', presetClass, sizeClass, className));\n<\/script>\n\n<span class={classes} {...rest}>\n {@render children()}\n</span>\n",
23
+ "/lib/components/svforge/ui/Input.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import type { HTMLAttributes } from 'svelte/elements';\n\n interface Props extends HTMLAttributes<HTMLInputElement> {\n label?: string;\n error?: string;\n type?: string;\n placeholder?: string;\n value?: string;\n required?: boolean;\n name?: string;\n class?: string;\n }\n let { label, error, class: className, value = $bindable(''), ...rest }: Props = $props();\n let inputId = $derived(rest.id ?? `input-${Math.random().toString(36).slice(2, 9)}`);\n<\/script>\n\n<div class=\"w-full\">\n {#if label}\n <label class=\"label\" for={inputId}>\n {label}\n </label>\n {/if}\n <input\n id={inputId}\n class={cn('input', error && 'input-error', className)}\n bind:value\n {...rest}\n />\n {#if error}\n <p class=\"text-error-500 text-sm mt-1\">{error}</p>\n {/if}\n</div>\n",
24
+ "/lib/components/svforge/ui/Checkbox.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import type { HTMLAttributes } from 'svelte/elements';\n\n interface Props extends HTMLAttributes<HTMLInputElement> {\n label?: string;\n checked?: boolean;\n class?: string;\n }\n\n let { label, class: className, checked = $bindable(false), ...rest }: Props = $props();\n<\/script>\n\n<label class={cn('flex items-center gap-2 cursor-pointer', className)}>\n <input type=\"checkbox\" class=\"checkbox\" bind:checked {...rest} />\n {#if label}\n <span>{label}</span>\n {/if}\n</label>\n",
25
+ "/lib/components/svforge/ui/Logo.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n\n interface Props {\n class?: string;\n }\n\n let { class: className = '' }: Props = $props();\n<\/script>\n\n<span class={cn('logo text-xl font-extrabold', className)}>SVForge</span>\n\n<style>\n @keyframes metalFlow {\n 0% {\n background-position: 0% 50%;\n }\n 50% {\n background-position: 100% 50%;\n }\n 100% {\n background-position: 0% 50%;\n }\n }\n .logo {\n font-family: 'Space Grotesk Variable', sans-serif;\n color: transparent;\n background: linear-gradient(\n 45deg,\n var(--color-secondary-200-800),\n var(--color-secondary-500),\n var(--color-secondary-600-400),\n var(--color-secondary-200-800)\n );\n background-size: 200% 200%;\n -webkit-background-clip: text;\n background-clip: text;\n letter-spacing: 0.05em;\n animation: metalFlow 5s ease-in-out infinite;\n filter: drop-shadow(0 0 2px var(--color-secondary-200-800));\n }\n</style>\n",
26
+ "/lib/components/svforge/ui/Select.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import type { HTMLAttributes } from 'svelte/elements';\n\n interface Props extends HTMLAttributes<HTMLSelectElement> {\n label?: string;\n error?: string;\n options: { value: string; label: string }[];\n value?: string;\n class?: string;\n }\n\n let { label, error, options, class: className, value = $bindable(''), ...rest }: Props = $props();\n<\/script>\n\n<div class=\"w-full\">\n {#if label}\n <label class=\"label\" for={rest.id}>\n {label}\n </label>\n {/if}\n <select class={cn('select', error && 'select-error', className)} bind:value {...rest}>\n {#each options as opt}\n <option value={opt.value}>{opt.label}</option>\n {/each}\n </select>\n {#if error}\n <p class=\"text-error-500 text-sm mt-1\">{error}</p>\n {/if}\n</div>\n",
27
+ "/lib/components/svforge/ui/Alert.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import type { Snippet } from 'svelte';\n import type { HTMLAttributes } from 'svelte/elements';\n\n type Variant = 'info' | 'success' | 'warning' | 'error';\n\n interface Props extends HTMLAttributes<HTMLDivElement> {\n variant?: Variant;\n class?: string;\n children: Snippet;\n }\n\n let { variant = 'info', class: className = '', children, ...rest }: Props = $props();\n\n const presets: Record<Variant, string> = {\n info: 'preset-tonal-info',\n success: 'preset-tonal-success',\n warning: 'preset-tonal-warning',\n error: 'preset-tonal-error'\n };\n\n let classes = $derived(cn('alert', presets[variant], className));\n<\/script>\n\n<div class={classes} {...rest}>\n {@render children()}\n</div>\n",
28
+ "/lib/components/svforge/ui/Tabs.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n\n interface Tab {\n label: string;\n content: string;\n }\n\n interface Props {\n tabs: Tab[];\n class?: string;\n }\n\n let { tabs, class: className = '' }: Props = $props();\n let activeTab = $state(0);\n<\/script>\n\n<div class={className}>\n <div class=\"flex border-b border-surface-200-800 gap-1\">\n {#each tabs as tab, i}\n <button\n class={cn(\n 'px-4 py-2 font-medium transition-colors rounded-t-card',\n activeTab === i\n ? 'text-primary-500 border-b-2 border-primary-500'\n : 'text-surface-500 hover:text-surface-900-100'\n )}\n onclick={() => (activeTab = i)}\n >\n {tab.label}\n </button>\n {/each}\n </div>\n <div class=\"p-element\">\n {tabs[activeTab].content}\n </div>\n</div>\n",
29
+ "/lib/components/svforge/ui/Toggle.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n\n interface Props {\n label?: string;\n checked?: boolean;\n class?: string;\n }\n\n let { label, class: className, checked = $bindable(false) }: Props = $props();\n<\/script>\n\n<label class={cn('flex items-center gap-2 cursor-pointer', className)}>\n <input type=\"checkbox\" class=\"toggle\" bind:checked />\n {#if label}\n <span>{label}</span>\n {/if}\n</label>\n",
30
+ "/lib/components/svforge/ui/Card.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import type { Snippet } from 'svelte';\n import type { HTMLAttributes } from 'svelte/elements';\n\n type Variant = 'flat' | 'elevated' | 'outlined';\n\n interface Props extends HTMLAttributes<HTMLDivElement> {\n variant?: Variant;\n class?: string;\n children: Snippet;\n header?: Snippet;\n footer?: Snippet;\n }\n\n let { variant = 'flat', class: className = '', children, header, footer, ...rest }: Props = $props();\n\n const variantClasses: Record<Variant, string> = {\n flat: 'card',\n elevated: 'card shadow-lg',\n outlined: 'card ring-1 ring-surface-200-800'\n };\n\n let classes = $derived(cn(variantClasses[variant], 'p-element rounded-card', className));\n<\/script>\n\n<div class={classes} {...rest}>\n {#if header}\n <div class=\"border-b border-surface-200-800 pb-3 mb-3\">\n {@render header()}\n </div>\n {/if}\n\n {@render children()}\n\n {#if footer}\n <div class=\"border-t border-surface-200-800 pt-3 mt-3\">\n {@render footer()}\n </div>\n {/if}\n</div>\n",
31
+ "/lib/components/svforge/ui/Table.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import type { HTMLAttributes } from 'svelte/elements';\n\n interface Column {\n key: string;\n label: string;\n class?: string;\n }\n\n interface Props extends HTMLAttributes<HTMLDivElement> {\n columns: Column[];\n rows: Record<string, string>[];\n class?: string;\n }\n\n let { columns, rows, class: className = '', ...rest }: Props = $props();\n<\/script>\n\n<div class={cn('overflow-x-auto rounded-card border border-surface-200-800', className)} {...rest}>\n <table class=\"w-full\">\n <thead class=\"bg-surface-100-800\">\n <tr>\n {#each columns as col}\n <th class=\"px-element py-3 text-left text-sm font-semibold text-surface-500 {col.class}\">\n {col.label}\n </th>\n {/each}\n </tr>\n </thead>\n <tbody class=\"divide-y divide-surface-200-800\">\n {#each rows as row}\n <tr class=\"hover:bg-surface-50-900 transition-colors\">\n {#each columns as col}\n <td class=\"px-element py-3 text-sm {col.class}\">\n {row[col.key]}\n </td>\n {/each}\n </tr>\n {/each}\n </tbody>\n </table>\n</div>\n",
32
+ "/lib/components/svforge/ui/Textarea.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import type { HTMLAttributes } from 'svelte/elements';\n\n interface Props extends HTMLAttributes<HTMLTextAreaElement> {\n label?: string;\n error?: string;\n placeholder?: string;\n rows?: number;\n value?: string;\n class?: string;\n }\n\n let { label, error, class: className, value = $bindable(''), ...rest }: Props = $props();\n let inputId = $derived(rest.id ?? `textarea-${Math.random().toString(36).slice(2, 9)}`);\n<\/script>\n\n<div class=\"w-full\">\n {#if label}\n <label class=\"label\" for={inputId}>\n {label}\n </label>\n {/if}\n <textarea\n id={inputId}\n class={cn('textarea', error && 'textarea-error', className)}\n bind:value\n {...rest}\n ></textarea>\n {#if error}\n <p class=\"text-error-500 text-sm mt-1\">{error}</p>\n {/if}\n</div>\n",
33
+ "/lib/components/svforge/ui/Avatar.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n\n interface Props {\n src: string;\n alt?: string;\n size?: 'sm' | 'md' | 'lg';\n class?: string;\n }\n\n let { size = 'md', class: className = '', src, alt = '' }: Props = $props();\n\n const sizes: Record<string, string> = {\n sm: 'w-8 h-8',\n md: 'w-10 h-10',\n lg: 'w-14 h-14'\n };\n<\/script>\n\n<img\n {src}\n {alt}\n class={cn('rounded-full object-cover ring-2 ring-surface-200-800', sizes[size], className)}\n/>\n",
34
+ "/lib/components/svforge/ui/Button.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import type { Snippet } from 'svelte';\n import type { HTMLAttributes } from 'svelte/elements';\n\n type Variant = 'filled' | 'outlined' | 'tonal' | 'ghost';\n type Color = 'primary' | 'secondary' | 'tertiary' | 'success' | 'warning' | 'error' | 'surface';\n type Size = 'sm' | 'md' | 'lg';\n\n interface Props extends HTMLAttributes<HTMLButtonElement> {\n variant?: Variant;\n color?: Color;\n size?: Size;\n href?: string;\n loading?: boolean;\n disabled?: boolean;\n type?: 'button' | 'submit' | 'reset';\n class?: string;\n children: Snippet;\n }\n\n let {\n variant = 'filled',\n color = 'primary',\n size = 'md',\n href,\n loading = false,\n disabled = false,\n type = 'button',\n class: className = '',\n children,\n ...rest\n }: Props = $props();\n\n const presets = {\n filled: {\n primary: 'preset-filled-primary-400-600',\n secondary: 'preset-filled-secondary-400-600',\n tertiary: 'preset-filled-tertiary-400-600',\n success: 'preset-filled-success-400-600',\n warning: 'preset-filled-warning-400-600',\n error: 'preset-filled-error-400-600',\n surface: 'preset-filled-surface-400-600'\n },\n outlined: {\n primary: 'preset-outlined-primary-400-600',\n secondary: 'preset-outlined-secondary-400-600',\n tertiary: 'preset-outlined-tertiary-400-600',\n success: 'preset-outlined-success-400-600',\n warning: 'preset-outlined-warning-400-600',\n error: 'preset-outlined-error-400-600',\n surface: 'preset-outlined-surface-400-600'\n },\n tonal: {\n primary: 'preset-tonal-primary',\n secondary: 'preset-tonal-secondary',\n tertiary: 'preset-tonal-tertiary',\n success: 'preset-tonal-success',\n warning: 'preset-tonal-warning',\n error: 'preset-tonal-error',\n surface: 'preset-tonal-surface'\n },\n ghost: {\n primary: 'hover:preset-tonal-primary',\n secondary: 'hover:preset-tonal-secondary',\n tertiary: 'hover:preset-tonal-tertiary',\n success: 'hover:preset-tonal-success',\n warning: 'hover:preset-tonal-warning',\n error: 'hover:preset-tonal-error',\n surface: 'hover:preset-tonal-surface'\n }\n } as const;\n\n let sizeClass = $derived(\n size === 'sm' ? 'btn-sm' : size === 'lg' ? 'btn-lg' : 'btn-md'\n );\n\n let presetClass = $derived(\n presets[variant]?.[color] ?? ''\n );\n\n let classes = $derived(cn('btn', presetClass, sizeClass, className));\n<\/script>\n\n{#if href}\n <a {href} class={classes}>\n {#if loading}\n <span\n class=\"mr-2 inline-block h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent\"\n ></span>\n {/if}\n {@render children()}\n </a>\n{:else}\n <button class={classes} disabled={disabled || loading} type={type} {...rest} aria-busy={loading}>\n {#if loading}\n <span\n class=\"mr-2 inline-block h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent\"\n ></span>\n {/if}\n {@render children()}\n </button>\n{/if}\n",
32
35
  "/app.html": "<!doctype html>\n<html lang=\"en\" data-theme=\"svelteForge\">\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <meta name=\"text-scale\" content=\"scale\" />\n <link rel=\"icon\" href=\"%sveltekit.assets%/favicon.ico\" />\n %sveltekit.head%\n </head>\n <body data-sveltekit-preload-data=\"hover\">\n <div style=\"display: contents\">%sveltekit.body%</div>\n </body>\n</html>\n",
33
36
  "/app.d.ts": "// See https://svelte.dev/docs/kit/types#app.d.ts\n// for information about these interfaces\ndeclare global {\n namespace App {\n // interface Error {}\n // interface Locals {}\n // interface PageData {}\n // interface PageState {}\n // interface Platform {}\n }\n}\n\nexport {};\n"
34
37
  };
35
- const fullstackFiles = {
38
+ const dashboardFiles = {
36
39
  "/routes/layout.css": "@import 'tailwindcss';\n@import '@skeletonlabs/skeleton';\n@import '@skeletonlabs/skeleton-svelte';\n@import '@fontsource-variable/inter';\n@import '@fontsource-variable/space-grotesk';\n@import '@fontsource-variable/manrope';\n@import '@fontsource-variable/fira-code';\n@import '../lib/styles/index.css';\n\n@plugin '@tailwindcss/forms';\n@plugin '@tailwindcss/typography';\n\n@custom-variant dark (&:where([data-mode=dark], [data-mode=dark] *));\n\nh1, h2, h3, h4, h5, h6 {\n font-family: var(--font-heading);\n}\n\ncode, pre {\n font-family: var(--font-code);\n}\n",
37
40
  "/routes/+layout.svelte": "<script lang=\"ts\">\n import './layout.css';\n let { children } = $props();\n<\/script>\n\n{@render children()}\n",
38
- "/routes/+page.svelte": "<script lang=\"ts\">\n import { Button, Card } from '$lib/components/ui';\n<\/script>\n\n<svelte:head>\n <title>SvelteForge</title>\n</svelte:head>\n\n<main class=\"max-w-container mx-auto px-element py-section\">\n <section class=\"text-center space-y-6 py-section\">\n <h1 class=\"text-5xl font-heading font-bold\">\n Welcome to <span class=\"text-primary-500\">SvelteForge</span>\n </h1>\n <p class=\"text-xl text-surface-500 max-w-2xl mx-auto\">\n A modern SvelteKit starter kit built on Skeleton UI with Tailwind CSS.\n </p>\n <div class=\"flex justify-center gap-4\">\n <Button href=\"/demo-ui\" size=\"lg\">View Components</Button>\n <Button variant=\"outlined\" size=\"lg\" href=\"https://github.com/ludoloops/svelteforge\">GitHub</Button>\n </div>\n </section>\n\n <section class=\"grid grid-cols-1 md:grid-cols-3 gap-group mt-section\">\n <Card variant=\"elevated\">\n <h3 class=\"font-heading font-bold text-lg mb-2\">Modern Stack</h3>\n <p class=\"text-surface-500\">SvelteKit + Tailwind v4 + Skeleton UI v4. Always up to date.</p>\n </Card>\n <Card variant=\"elevated\">\n <h3 class=\"font-heading font-bold text-lg mb-2\">Customizable</h3>\n <p class=\"text-surface-500\">Full theme system with oklch colors. Make it yours.</p>\n </Card>\n <Card variant=\"elevated\">\n <h3 class=\"font-heading font-bold text-lg mb-2\">Production Ready</h3>\n <p class=\"text-surface-500\">Prettier, ESLint, TypeScript, responsive. Ship with confidence.</p>\n </Card>\n </section>\n</main>\n",
39
- "/routes/demo-ui/+page.svelte": "<script lang=\"ts\">\n import {\n Accordion, Alert, Avatar, Badge, Breadcrumb, Button, Card,\n Checkbox, Input, Select, Table, Tabs, Textarea, ThemeToggle, Toggle\n } from '$lib/components/ui';\n import { Navbar } from '$lib/components/layout';\n import { Footer } from '$lib/components/layout';\n\n let loading = $state(false);\n\n function simulateLoading() {\n loading = true;\n setTimeout(() => (loading = false), 2000);\n }\n<\/script>\n\n<svelte:head>\n <title>SvelteForge — Component Demo</title>\n</svelte:head>\n\n<Navbar links={[{ href: '/demo-ui', label: 'Demo' }]} />\n\n<main class=\"max-w-container mx-auto px-element py-section space-y-section\">\n <h1 class=\"text-4xl font-heading font-bold\">Component Demo</h1>\n <p class=\"text-surface-500\">All SvelteForge components with variants. Theme changes apply instantly.</p>\n\n <!-- Buttons -->\n <section class=\"space-y-4\">\n <h2 class=\"text-2xl font-heading border-b border-surface-200-800 pb-2\">Button</h2>\n <h3 class=\"text-lg font-heading\">Colors (Filled)</h3>\n <div class=\"flex flex-wrap gap-3\">\n <Button>Primary</Button>\n <Button color=\"secondary\">Secondary</Button>\n <Button color=\"tertiary\">Tertiary</Button>\n <Button color=\"success\">Success</Button>\n <Button color=\"warning\">Warning</Button>\n <Button color=\"error\">Error</Button>\n <Button color=\"surface\">Surface</Button>\n </div>\n <h3 class=\"text-lg font-heading\">Variants</h3>\n <div class=\"flex flex-wrap gap-3\">\n <Button variant=\"filled\">Filled</Button>\n <Button variant=\"outlined\">Outlined</Button>\n <Button variant=\"tonal\">Tonal</Button>\n <Button variant=\"ghost\">Ghost</Button>\n </div>\n <h3 class=\"text-lg font-heading\">Sizes</h3>\n <div class=\"flex flex-wrap gap-3 items-center\">\n <Button size=\"sm\">Small</Button>\n <Button size=\"md\">Medium</Button>\n <Button size=\"lg\">Large</Button>\n </div>\n <h3 class=\"text-lg font-heading\">States</h3>\n <div class=\"flex flex-wrap gap-3\">\n <Button loading={loading} onclick={simulateLoading}>\n {loading ? 'Loading...' : 'Click to Load'}\n </Button>\n <Button href=\"/\">Link Button</Button>\n <Button disabled>Disabled</Button>\n </div>\n </section>\n\n <!-- Cards -->\n <section class=\"space-y-4\">\n <h2 class=\"text-2xl font-heading border-b border-surface-200-800 pb-2\">Card</h2>\n <div class=\"grid grid-cols-1 md:grid-cols-3 gap-group\">\n <Card>\n <p>Flat card with some content.</p>\n </Card>\n <Card variant=\"elevated\">\n <p>Elevated card with shadow.</p>\n </Card>\n <Card variant=\"outlined\">\n <p>Outlined card with ring border.</p>\n </Card>\n </div>\n <Card>\n {#snippet header()}\n <h3 class=\"font-heading font-bold\">Card with Header & Footer</h3>\n {/snippet}\n <p>Content goes here.</p>\n {#snippet footer()}\n <div class=\"flex justify-end gap-2\">\n <Button variant=\"ghost\" size=\"sm\">Cancel</Button>\n <Button size=\"sm\">Save</Button>\n </div>\n {/snippet}\n </Card>\n </section>\n\n <!-- Badges -->\n <section class=\"space-y-4\">\n <h2 class=\"text-2xl font-heading border-b border-surface-200-800 pb-2\">Badge</h2>\n <h3 class=\"text-lg font-heading\">Filled</h3>\n <div class=\"flex flex-wrap gap-2\">\n <Badge>Primary</Badge>\n <Badge color=\"secondary\">Secondary</Badge>\n <Badge color=\"tertiary\">Tertiary</Badge>\n <Badge color=\"success\">Success</Badge>\n <Badge color=\"warning\">Warning</Badge>\n <Badge color=\"error\">Error</Badge>\n <Badge color=\"surface\">Surface</Badge>\n </div>\n <h3 class=\"text-lg font-heading\">Outlined</h3>\n <div class=\"flex flex-wrap gap-2\">\n <Badge variant=\"outlined\">Primary</Badge>\n <Badge variant=\"outlined\" color=\"secondary\">Secondary</Badge>\n <Badge variant=\"outlined\" color=\"success\">Success</Badge>\n <Badge variant=\"outlined\" color=\"warning\">Warning</Badge>\n <Badge variant=\"outlined\" color=\"error\">Error</Badge>\n <Badge variant=\"outlined\" color=\"surface\">Surface</Badge>\n </div>\n <h3 class=\"text-lg font-heading\">Tonal</h3>\n <div class=\"flex flex-wrap gap-2\">\n <Badge variant=\"tonal\">Primary</Badge>\n <Badge variant=\"tonal\" color=\"secondary\">Secondary</Badge>\n <Badge variant=\"tonal\" color=\"success\">Success</Badge>\n <Badge variant=\"tonal\" color=\"error\">Error</Badge>\n </div>\n <h3 class=\"text-lg font-heading\">Sizes</h3>\n <div class=\"flex flex-wrap gap-2 items-center\">\n <Badge size=\"sm\">Small</Badge>\n <Badge size=\"md\">Medium</Badge>\n <Badge size=\"lg\">Large</Badge>\n </div>\n </section>\n\n <!-- Avatar -->\n <section class=\"space-y-4\">\n <h2 class=\"text-2xl font-heading border-b border-surface-200-800 pb-2\">Avatar</h2>\n <div class=\"flex items-center gap-3\">\n <Avatar size=\"sm\" src=\"https://api.dicebear.com/9.x/avataaars/svg?seed=1\" alt=\"User 1\" />\n <Avatar size=\"md\" src=\"https://api.dicebear.com/9.x/avataaars/svg?seed=2\" alt=\"User 2\" />\n <Avatar size=\"lg\" src=\"https://api.dicebear.com/9.x/avataaars/svg?seed=3\" alt=\"User 3\" />\n </div>\n </section>\n\n <!-- Alerts -->\n <section class=\"space-y-4\">\n <h2 class=\"text-2xl font-heading border-b border-surface-200-800 pb-2\">Alert</h2>\n <div class=\"space-y-3\">\n <Alert variant=\"info\">This is an informational message.</Alert>\n <Alert variant=\"success\">Operation completed successfully!</Alert>\n <Alert variant=\"warning\">Please review before continuing.</Alert>\n <Alert variant=\"error\">Something went wrong. Please try again.</Alert>\n </div>\n </section>\n\n <!-- Form -->\n <section class=\"space-y-4\">\n <h2 class=\"text-2xl font-heading border-b border-surface-200-800 pb-2\">Form</h2>\n <Card>\n <div class=\"space-y-4\">\n <Input label=\"Name\" placeholder=\"Enter your name\" />\n <Input label=\"Email\" type=\"email\" placeholder=\"you@example.com\" error=\"Invalid email\" />\n <Textarea label=\"Message\" placeholder=\"Write something...\" rows={3} />\n <Select\n label=\"Category\"\n options={[\n { value: '', label: 'Select...' },\n { value: 'design', label: 'Design' },\n { value: 'dev', label: 'Development' },\n { value: 'marketing', label: 'Marketing' }\n ]}\n />\n <div class=\"flex flex-col gap-3\">\n <Checkbox label=\"I agree to the terms\" />\n <Toggle label=\"Enable notifications\" />\n </div>\n <Button>Submit</Button>\n </div>\n </Card>\n </section>\n\n <!-- Accordion -->\n <section class=\"space-y-4\">\n <h2 class=\"text-2xl font-heading border-b border-surface-200-800 pb-2\">Accordion</h2>\n <Accordion items={[\n { title: 'What is SvelteForge?', content: 'A SvelteKit starter kit built on Skeleton UI.' },\n { title: 'Is it open source?', content: 'Yes, fully open source on GitHub.' },\n { title: 'Can I customize the theme?', content: 'Absolutely. All colors and spacing use CSS variables.' }\n ]} />\n </section>\n\n <!-- Tabs -->\n <section class=\"space-y-4\">\n <h2 class=\"text-2xl font-heading border-b border-surface-200-800 pb-2\">Tabs</h2>\n <Tabs tabs={[\n { label: 'Overview', content: 'This is the overview tab with general information.' },\n { label: 'Features', content: 'A list of features and capabilities.' },\n { label: 'Pricing', content: 'Free and open source. No pricing page needed!' }\n ]} />\n </section>\n\n <!-- Table -->\n <section class=\"space-y-4\">\n <h2 class=\"text-2xl font-heading border-b border-surface-200-800 pb-2\">Table</h2>\n <Table\n columns={[\n { key: 'name', label: 'Name' },\n { key: 'role', label: 'Role' },\n { key: 'status', label: 'Status' }\n ]}\n rows={[\n { name: 'Alice', role: 'Developer', status: 'Active' },\n { name: 'Bob', role: 'Designer', status: 'Away' },\n { name: 'Charlie', role: 'PM', status: 'Active' }\n ]}\n />\n </section>\n\n <!-- Breadcrumb -->\n <section class=\"space-y-4\">\n <h2 class=\"text-2xl font-heading border-b border-surface-200-800 pb-2\">Breadcrumb</h2>\n <Breadcrumb items={[\n { href: '/', label: 'Home' },\n { href: '/demo-ui', label: 'Components' },\n { label: 'Breadcrumb' }\n ]} />\n </section>\n\n <!-- Theme -->\n <section class=\"space-y-4\">\n <h2 class=\"text-2xl font-heading border-b border-surface-200-800 pb-2\">Theme</h2>\n <p class=\"text-surface-500\">Click to toggle light/dark mode:</p>\n <ThemeToggle />\n </section>\n</main>\n\n<Footer links={[{ href: '/demo-ui', label: 'Components' }]} />\n",
41
+ "/routes/+page.svelte": "<script lang=\"ts\">\n import { Button, Card } from '$lib/components/svforge/ui';\n<\/script>\n\n<svelte:head>\n <title>SvelteForge</title>\n</svelte:head>\n\n<main class=\"max-w-container mx-auto px-element py-section\">\n <section class=\"text-center space-y-6 py-section\">\n <h1 class=\"text-5xl font-heading font-bold\">\n Welcome to <span class=\"text-primary-500\">SvelteForge</span>\n </h1>\n <p class=\"text-xl text-surface-500 max-w-2xl mx-auto\">\n A modern SvelteKit starter kit built on Skeleton UI with Tailwind CSS.\n </p>\n <div class=\"flex justify-center gap-4\">\n <Button href=\"/demo-ui\" size=\"lg\">View Components</Button>\n <Button variant=\"outlined\" size=\"lg\" href=\"https://github.com/ludoloops/svelteforge\">GitHub</Button>\n </div>\n </section>\n\n <section class=\"grid grid-cols-1 md:grid-cols-3 gap-group mt-section\">\n <Card variant=\"elevated\">\n <h3 class=\"font-heading font-bold text-lg mb-2\">Modern Stack</h3>\n <p class=\"text-surface-500\">SvelteKit + Tailwind v4 + Skeleton UI v4. Always up to date.</p>\n </Card>\n <Card variant=\"elevated\">\n <h3 class=\"font-heading font-bold text-lg mb-2\">Customizable</h3>\n <p class=\"text-surface-500\">Full theme system with oklch colors. Make it yours.</p>\n </Card>\n <Card variant=\"elevated\">\n <h3 class=\"font-heading font-bold text-lg mb-2\">Production Ready</h3>\n <p class=\"text-surface-500\">Prettier, ESLint, TypeScript, responsive. Ship with confidence.</p>\n </Card>\n </section>\n</main>\n",
42
+ "/routes/demo-ui/+page.svelte": "<script lang=\"ts\">\n import {\n Accordion, Alert, Avatar, Badge, Breadcrumb, Button, Card,\n Checkbox, Input, Select, Table, Tabs, Textarea, ThemeToggle, Toggle\n } from '$lib/components/svforge/ui';\n import { Navbar } from '$lib/components/layout';\n import { Footer } from '$lib/components/layout';\n\n let loading = $state(false);\n\n function simulateLoading() {\n loading = true;\n setTimeout(() => (loading = false), 2000);\n }\n<\/script>\n\n<svelte:head>\n <title>SvelteForge — Component Demo</title>\n</svelte:head>\n\n<Navbar links={[{ href: '/demo-ui', label: 'Demo' }]} />\n\n<main class=\"max-w-container mx-auto px-element py-section space-y-section\">\n <h1 class=\"text-4xl font-heading font-bold\">Component Demo</h1>\n <p class=\"text-surface-500\">All SvelteForge components with variants. Theme changes apply instantly.</p>\n\n <!-- Buttons -->\n <section class=\"space-y-4\">\n <h2 class=\"text-2xl font-heading border-b border-surface-200-800 pb-2\">Button</h2>\n <h3 class=\"text-lg font-heading\">Colors (Filled)</h3>\n <div class=\"flex flex-wrap gap-3\">\n <Button>Primary</Button>\n <Button color=\"secondary\">Secondary</Button>\n <Button color=\"tertiary\">Tertiary</Button>\n <Button color=\"success\">Success</Button>\n <Button color=\"warning\">Warning</Button>\n <Button color=\"error\">Error</Button>\n <Button color=\"surface\">Surface</Button>\n </div>\n <h3 class=\"text-lg font-heading\">Variants</h3>\n <div class=\"flex flex-wrap gap-3\">\n <Button variant=\"filled\">Filled</Button>\n <Button variant=\"outlined\">Outlined</Button>\n <Button variant=\"tonal\">Tonal</Button>\n <Button variant=\"ghost\">Ghost</Button>\n </div>\n <h3 class=\"text-lg font-heading\">Sizes</h3>\n <div class=\"flex flex-wrap gap-3 items-center\">\n <Button size=\"sm\">Small</Button>\n <Button size=\"md\">Medium</Button>\n <Button size=\"lg\">Large</Button>\n </div>\n <h3 class=\"text-lg font-heading\">States</h3>\n <div class=\"flex flex-wrap gap-3\">\n <Button loading={loading} onclick={simulateLoading}>\n {loading ? 'Loading...' : 'Click to Load'}\n </Button>\n <Button href=\"/\">Link Button</Button>\n <Button disabled>Disabled</Button>\n </div>\n </section>\n\n <!-- Cards -->\n <section class=\"space-y-4\">\n <h2 class=\"text-2xl font-heading border-b border-surface-200-800 pb-2\">Card</h2>\n <div class=\"grid grid-cols-1 md:grid-cols-3 gap-group\">\n <Card>\n <p>Flat card with some content.</p>\n </Card>\n <Card variant=\"elevated\">\n <p>Elevated card with shadow.</p>\n </Card>\n <Card variant=\"outlined\">\n <p>Outlined card with ring border.</p>\n </Card>\n </div>\n <Card>\n {#snippet header()}\n <h3 class=\"font-heading font-bold\">Card with Header & Footer</h3>\n {/snippet}\n <p>Content goes here.</p>\n {#snippet footer()}\n <div class=\"flex justify-end gap-2\">\n <Button variant=\"ghost\" size=\"sm\">Cancel</Button>\n <Button size=\"sm\">Save</Button>\n </div>\n {/snippet}\n </Card>\n </section>\n\n <!-- Badges -->\n <section class=\"space-y-4\">\n <h2 class=\"text-2xl font-heading border-b border-surface-200-800 pb-2\">Badge</h2>\n <h3 class=\"text-lg font-heading\">Filled</h3>\n <div class=\"flex flex-wrap gap-2\">\n <Badge>Primary</Badge>\n <Badge color=\"secondary\">Secondary</Badge>\n <Badge color=\"tertiary\">Tertiary</Badge>\n <Badge color=\"success\">Success</Badge>\n <Badge color=\"warning\">Warning</Badge>\n <Badge color=\"error\">Error</Badge>\n <Badge color=\"surface\">Surface</Badge>\n </div>\n <h3 class=\"text-lg font-heading\">Outlined</h3>\n <div class=\"flex flex-wrap gap-2\">\n <Badge variant=\"outlined\">Primary</Badge>\n <Badge variant=\"outlined\" color=\"secondary\">Secondary</Badge>\n <Badge variant=\"outlined\" color=\"success\">Success</Badge>\n <Badge variant=\"outlined\" color=\"warning\">Warning</Badge>\n <Badge variant=\"outlined\" color=\"error\">Error</Badge>\n <Badge variant=\"outlined\" color=\"surface\">Surface</Badge>\n </div>\n <h3 class=\"text-lg font-heading\">Tonal</h3>\n <div class=\"flex flex-wrap gap-2\">\n <Badge variant=\"tonal\">Primary</Badge>\n <Badge variant=\"tonal\" color=\"secondary\">Secondary</Badge>\n <Badge variant=\"tonal\" color=\"success\">Success</Badge>\n <Badge variant=\"tonal\" color=\"error\">Error</Badge>\n </div>\n <h3 class=\"text-lg font-heading\">Sizes</h3>\n <div class=\"flex flex-wrap gap-2 items-center\">\n <Badge size=\"sm\">Small</Badge>\n <Badge size=\"md\">Medium</Badge>\n <Badge size=\"lg\">Large</Badge>\n </div>\n </section>\n\n <!-- Avatar -->\n <section class=\"space-y-4\">\n <h2 class=\"text-2xl font-heading border-b border-surface-200-800 pb-2\">Avatar</h2>\n <div class=\"flex items-center gap-3\">\n <Avatar size=\"sm\" src=\"https://api.dicebear.com/9.x/avataaars/svg?seed=1\" alt=\"User 1\" />\n <Avatar size=\"md\" src=\"https://api.dicebear.com/9.x/avataaars/svg?seed=2\" alt=\"User 2\" />\n <Avatar size=\"lg\" src=\"https://api.dicebear.com/9.x/avataaars/svg?seed=3\" alt=\"User 3\" />\n </div>\n </section>\n\n <!-- Alerts -->\n <section class=\"space-y-4\">\n <h2 class=\"text-2xl font-heading border-b border-surface-200-800 pb-2\">Alert</h2>\n <div class=\"space-y-3\">\n <Alert variant=\"info\">This is an informational message.</Alert>\n <Alert variant=\"success\">Operation completed successfully!</Alert>\n <Alert variant=\"warning\">Please review before continuing.</Alert>\n <Alert variant=\"error\">Something went wrong. Please try again.</Alert>\n </div>\n </section>\n\n <!-- Form -->\n <section class=\"space-y-4\">\n <h2 class=\"text-2xl font-heading border-b border-surface-200-800 pb-2\">Form</h2>\n <Card>\n <div class=\"space-y-4\">\n <Input label=\"Name\" placeholder=\"Enter your name\" />\n <Input label=\"Email\" type=\"email\" placeholder=\"you@example.com\" error=\"Invalid email\" />\n <Textarea label=\"Message\" placeholder=\"Write something...\" rows={3} />\n <Select\n label=\"Category\"\n options={[\n { value: '', label: 'Select...' },\n { value: 'design', label: 'Design' },\n { value: 'dev', label: 'Development' },\n { value: 'marketing', label: 'Marketing' }\n ]}\n />\n <div class=\"flex flex-col gap-3\">\n <Checkbox label=\"I agree to the terms\" />\n <Toggle label=\"Enable notifications\" />\n </div>\n <Button>Submit</Button>\n </div>\n </Card>\n </section>\n\n <!-- Accordion -->\n <section class=\"space-y-4\">\n <h2 class=\"text-2xl font-heading border-b border-surface-200-800 pb-2\">Accordion</h2>\n <Accordion items={[\n { title: 'What is SvelteForge?', content: 'A SvelteKit starter kit built on Skeleton UI.' },\n { title: 'Is it open source?', content: 'Yes, fully open source on GitHub.' },\n { title: 'Can I customize the theme?', content: 'Absolutely. All colors and spacing use CSS variables.' }\n ]} />\n </section>\n\n <!-- Tabs -->\n <section class=\"space-y-4\">\n <h2 class=\"text-2xl font-heading border-b border-surface-200-800 pb-2\">Tabs</h2>\n <Tabs tabs={[\n { label: 'Overview', content: 'This is the overview tab with general information.' },\n { label: 'Features', content: 'A list of features and capabilities.' },\n { label: 'Pricing', content: 'Free and open source. No pricing page needed!' }\n ]} />\n </section>\n\n <!-- Table -->\n <section class=\"space-y-4\">\n <h2 class=\"text-2xl font-heading border-b border-surface-200-800 pb-2\">Table</h2>\n <Table\n columns={[\n { key: 'name', label: 'Name' },\n { key: 'role', label: 'Role' },\n { key: 'status', label: 'Status' }\n ]}\n rows={[\n { name: 'Alice', role: 'Developer', status: 'Active' },\n { name: 'Bob', role: 'Designer', status: 'Away' },\n { name: 'Charlie', role: 'PM', status: 'Active' }\n ]}\n />\n </section>\n\n <!-- Breadcrumb -->\n <section class=\"space-y-4\">\n <h2 class=\"text-2xl font-heading border-b border-surface-200-800 pb-2\">Breadcrumb</h2>\n <Breadcrumb items={[\n { href: '/', label: 'Home' },\n { href: '/demo-ui', label: 'Components' },\n { label: 'Breadcrumb' }\n ]} />\n </section>\n\n <!-- Theme -->\n <section class=\"space-y-4\">\n <h2 class=\"text-2xl font-heading border-b border-surface-200-800 pb-2\">Theme</h2>\n <p class=\"text-surface-500\">Click to toggle light/dark mode:</p>\n <ThemeToggle />\n </section>\n</main>\n\n<Footer links={[{ href: '/demo-ui', label: 'Components' }]} />\n",
40
43
  "/lib/utils/cn.ts": "import { clsx } from 'clsx';\nimport type { ClassValue } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n",
41
44
  "/lib/index.ts": "// place files you want to import through the `$lib` alias in this folder.\n",
42
45
  "/lib/styles/tokens.css": "/* === Tailwind custom tokens === */\n@theme {\n --font-sans: 'Inter Variable', sans-serif;\n --font-heading: 'Space Grotesk Variable', sans-serif;\n --font-code: 'Fira Code Variable', monospace;\n\n --spacing-section: 2rem;\n --spacing-group: 1.5rem;\n --spacing-element: 1rem;\n\n --radius-card: 0.75rem;\n --radius-modal: 1rem;\n\n --width-modal: 28rem;\n --width-toast: 20rem;\n --width-container: 80rem;\n}\n",
43
46
  "/lib/styles/index.css": "@import './tokens.css';\n@import './svelteforge-theme.css';\n",
44
47
  "/lib/styles/svelteforge-theme.css": "/* === SvelteForge Theme === */\n[data-theme='svelteForge'] {\n --text-scaling: 1.067;\n --base-font-color: var(--color-surface-950);\n --base-font-color-dark: var(--color-surface-50);\n --base-font-family: 'Inter Variable', sans-serif;\n --heading-font-family: 'Space Grotesk Variable', sans-serif;\n --heading-font-weight: bold;\n --anchor-font-color: var(--color-primary-700);\n --anchor-font-color-dark: var(--color-primary-200);\n --anchor-text-decoration: none;\n --anchor-text-decoration-hover: underline;\n --body-background-color: var(--color-surface-50);\n --body-background-color-dark: var(--color-surface-950);\n --spacing: 0.25rem;\n --radius-base: 0.375rem;\n --radius-container: 0.75rem;\n\n /* Primary: Steel blue */\n --color-primary-50: oklch(94.82% 0.04 216.84deg);\n --color-primary-100: oklch(88.84% 0.07 215.45deg);\n --color-primary-200: oklch(83.12% 0.09 215.47deg);\n --color-primary-300: oklch(78.03% 0.11 216.31deg);\n --color-primary-400: oklch(73.14% 0.12 218.53deg);\n --color-primary-500: oklch(68.74% 0.13 222.38deg);\n --color-primary-600: oklch(63.96% 0.12 222.67deg);\n --color-primary-700: oklch(59.05% 0.11 222.32deg);\n --color-primary-800: oklch(53.82% 0.1 223.63deg);\n --color-primary-900: oklch(48.67% 0.09 223.32deg);\n --color-primary-950: oklch(43.44% 0.08 223.92deg);\n --color-primary-contrast-dark: oklch(20.02% 0 none);\n --color-primary-contrast-light: oklch(97.91% 0 none);\n --color-primary-contrast-50: var(--color-primary-contrast-dark);\n --color-primary-contrast-100: var(--color-primary-contrast-dark);\n --color-primary-contrast-200: var(--color-primary-contrast-dark);\n --color-primary-contrast-300: var(--color-primary-contrast-dark);\n --color-primary-contrast-400: var(--color-primary-contrast-dark);\n --color-primary-contrast-500: var(--color-primary-contrast-dark);\n --color-primary-contrast-600: var(--color-primary-contrast-dark);\n --color-primary-contrast-700: var(--color-primary-contrast-dark);\n --color-primary-contrast-800: var(--color-primary-contrast-light);\n --color-primary-contrast-900: var(--color-primary-contrast-light);\n --color-primary-contrast-950: var(--color-primary-contrast-light);\n\n /* Secondary: Burnt orange */\n --color-secondary-50: oklch(93.05% 0.05 71.66deg);\n --color-secondary-100: oklch(86.31% 0.07 50.93deg);\n --color-secondary-200: oklch(79.78% 0.1 39.88deg);\n --color-secondary-300: oklch(73.56% 0.12 32.86deg);\n --color-secondary-400: oklch(67.93% 0.14 30.33deg);\n --color-secondary-500: oklch(63.07% 0.17 29.44deg);\n --color-secondary-600: oklch(58.47% 0.15 27.5deg);\n --color-secondary-700: oklch(53.83% 0.13 24.55deg);\n --color-secondary-800: oklch(49.31% 0.11 20.41deg);\n --color-secondary-900: oklch(44.67% 0.09 12.99deg);\n --color-secondary-950: oklch(40.1% 0.07 0.14deg);\n --color-secondary-contrast-dark: oklch(97.91% 0 none);\n --color-secondary-contrast-light: oklch(20.02% 0 none);\n --color-secondary-contrast-50: var(--color-secondary-contrast-light);\n --color-secondary-contrast-100: var(--color-secondary-contrast-light);\n --color-secondary-contrast-200: var(--color-secondary-contrast-light);\n --color-secondary-contrast-300: var(--color-secondary-contrast-light);\n --color-secondary-contrast-400: var(--color-secondary-contrast-light);\n --color-secondary-contrast-500: var(--color-secondary-contrast-light);\n --color-secondary-contrast-600: var(--color-secondary-contrast-dark);\n --color-secondary-contrast-700: var(--color-secondary-contrast-dark);\n --color-secondary-contrast-800: var(--color-secondary-contrast-dark);\n --color-secondary-contrast-900: var(--color-secondary-contrast-dark);\n --color-secondary-contrast-950: var(--color-secondary-contrast-dark);\n\n /* Tertiary: Soft teal */\n --color-tertiary-50: oklch(98.31% 0.02 196.74deg);\n --color-tertiary-100: oklch(90.4% 0.03 193.59deg);\n --color-tertiary-200: oklch(82.32% 0.03 191.41deg);\n --color-tertiary-300: oklch(74.29% 0.04 187.78deg);\n --color-tertiary-400: oklch(65.86% 0.04 186.58deg);\n --color-tertiary-500: oklch(57.22% 0.05 185.36deg);\n --color-tertiary-600: oklch(52.72% 0.05 190.1deg);\n --color-tertiary-700: oklch(48.14% 0.04 195.76deg);\n --color-tertiary-800: oklch(43.13% 0.04 204.75deg);\n --color-tertiary-900: oklch(38.31% 0.04 212.77deg);\n --color-tertiary-950: oklch(33.36% 0.04 221.8deg);\n --color-tertiary-contrast-dark: var(--color-tertiary-950);\n --color-tertiary-contrast-light: var(--color-tertiary-50);\n --color-tertiary-contrast-50: var(--color-tertiary-contrast-dark);\n --color-tertiary-contrast-100: var(--color-tertiary-contrast-dark);\n --color-tertiary-contrast-200: var(--color-tertiary-contrast-dark);\n --color-tertiary-contrast-300: var(--color-tertiary-contrast-dark);\n --color-tertiary-contrast-400: var(--color-tertiary-contrast-dark);\n --color-tertiary-contrast-500: var(--color-tertiary-contrast-light);\n --color-tertiary-contrast-600: var(--color-tertiary-contrast-light);\n --color-tertiary-contrast-700: var(--color-tertiary-contrast-light);\n --color-tertiary-contrast-800: var(--color-tertiary-contrast-light);\n --color-tertiary-contrast-900: var(--color-tertiary-contrast-light);\n --color-tertiary-contrast-950: var(--color-tertiary-contrast-light);\n\n /* Success */\n --color-success-50: oklch(93.9% 0.09 179.09deg);\n --color-success-100: oklch(91.57% 0.1 178.72deg);\n --color-success-200: oklch(89.48% 0.11 177.05deg);\n --color-success-300: oklch(87.2% 0.12 176.72deg);\n --color-success-400: oklch(85.21% 0.12 175.14deg);\n --color-success-500: oklch(83.08% 0.13 174.56deg);\n --color-success-600: oklch(72.99% 0.11 174.61deg);\n --color-success-700: oklch(62.32% 0.1 176.14deg);\n --color-success-800: oklch(51.37% 0.08 176.77deg);\n --color-success-900: oklch(39.58% 0.06 180.47deg);\n --color-success-950: oklch(27.1% 0.04 184.49deg);\n --color-success-contrast-dark: var(--color-success-950);\n --color-success-contrast-light: var(--color-success-50);\n --color-success-contrast-50: var(--color-success-contrast-dark);\n --color-success-contrast-100: var(--color-success-contrast-dark);\n --color-success-contrast-200: var(--color-success-contrast-dark);\n --color-success-contrast-300: var(--color-success-contrast-dark);\n --color-success-contrast-400: var(--color-success-contrast-dark);\n --color-success-contrast-500: var(--color-success-contrast-dark);\n --color-success-contrast-600: var(--color-success-contrast-dark);\n --color-success-contrast-700: var(--color-success-contrast-dark);\n --color-success-contrast-800: var(--color-success-contrast-light);\n --color-success-contrast-900: var(--color-success-contrast-light);\n --color-success-contrast-950: var(--color-success-contrast-light);\n\n /* Warning */\n --color-warning-50: oklch(95.79% 0.05 88.07deg);\n --color-warning-100: oklch(92.94% 0.07 85.27deg);\n --color-warning-200: oklch(90.05% 0.09 81.8deg);\n --color-warning-300: oklch(87.31% 0.11 80.89deg);\n --color-warning-400: oklch(84.57% 0.13 78.29deg);\n --color-warning-500: oklch(82.02% 0.14 76.71deg);\n --color-warning-600: oklch(76.11% 0.14 72.37deg);\n --color-warning-700: oklch(70.14% 0.13 67.8deg);\n --color-warning-800: oklch(64% 0.13 62.98deg);\n --color-warning-900: oklch(57.93% 0.13 57.46deg);\n --color-warning-950: oklch(51.87% 0.13 51.28deg);\n --color-warning-contrast-dark: var(--color-warning-950);\n --color-warning-contrast-light: var(--color-warning-50);\n --color-warning-contrast-50: var(--color-warning-contrast-dark);\n --color-warning-contrast-100: var(--color-warning-contrast-dark);\n --color-warning-contrast-200: var(--color-warning-contrast-dark);\n --color-warning-contrast-300: var(--color-warning-contrast-dark);\n --color-warning-contrast-400: var(--color-warning-contrast-dark);\n --color-warning-contrast-500: var(--color-warning-contrast-dark);\n --color-warning-contrast-600: var(--color-warning-contrast-dark);\n --color-warning-contrast-700: var(--color-warning-contrast-light);\n --color-warning-contrast-800: var(--color-warning-contrast-light);\n --color-warning-contrast-900: var(--color-warning-contrast-light);\n --color-warning-contrast-950: var(--color-warning-contrast-light);\n\n /* Error */\n --color-error-50: oklch(89.99% 0.04 14.04deg);\n --color-error-100: oklch(83.6% 0.08 19.82deg);\n --color-error-200: oklch(77.52% 0.11 21.99deg);\n --color-error-300: oklch(72.26% 0.15 24.9deg);\n --color-error-400: oklch(67.55% 0.19 26.72deg);\n --color-error-500: oklch(64.06% 0.22 28.71deg);\n --color-error-600: oklch(59.71% 0.21 28.7deg);\n --color-error-700: oklch(55.17% 0.2 28.73deg);\n --color-error-800: oklch(50.88% 0.19 28.77deg);\n --color-error-900: oklch(46.35% 0.18 28.89deg);\n --color-error-950: oklch(42.03% 0.17 29.27deg);\n --color-error-contrast-dark: var(--color-error-950);\n --color-error-contrast-light: var(--color-error-50);\n --color-error-contrast-50: var(--color-error-contrast-dark);\n --color-error-contrast-100: var(--color-error-contrast-dark);\n --color-error-contrast-200: var(--color-error-contrast-dark);\n --color-error-contrast-300: var(--color-error-contrast-dark);\n --color-error-contrast-400: var(--color-error-contrast-dark);\n --color-error-contrast-500: var(--color-error-contrast-light);\n --color-error-contrast-600: var(--color-error-contrast-light);\n --color-error-contrast-700: var(--color-error-contrast-light);\n --color-error-contrast-800: var(--color-error-contrast-light);\n --color-error-contrast-900: var(--color-error-contrast-light);\n --color-error-contrast-950: var(--color-error-contrast-light);\n\n /* Surface: Subtle blue tint */\n --color-surface-50: oklch(96% 0.01 260deg);\n --color-surface-100: oklch(90% 0.01 260deg);\n --color-surface-200: oklch(78.47% 0.02 264.48deg);\n --color-surface-300: oklch(68.53% 0.03 265.52deg);\n --color-surface-400: oklch(58.21% 0.04 265.04deg);\n --color-surface-500: oklch(47.47% 0.05 264.53deg);\n --color-surface-600: oklch(42.05% 0.05 264.5deg);\n --color-surface-700: oklch(36.45% 0.05 264.44deg);\n --color-surface-800: oklch(30.96% 0.04 263.65deg);\n --color-surface-900: oklch(24.88% 0.04 263.42deg);\n --color-surface-950: oklch(18.45% 0.04 262.95deg);\n --color-surface-contrast-dark: var(--color-surface-950);\n --color-surface-contrast-light: var(--color-surface-50);\n --color-surface-contrast-50: var(--color-surface-contrast-dark);\n --color-surface-contrast-100: var(--color-surface-contrast-dark);\n --color-surface-contrast-200: var(--color-surface-contrast-dark);\n --color-surface-contrast-300: var(--color-surface-contrast-dark);\n --color-surface-contrast-400: var(--color-surface-contrast-dark);\n --color-surface-contrast-500: var(--color-surface-contrast-light);\n --color-surface-contrast-600: var(--color-surface-contrast-light);\n --color-surface-contrast-700: var(--color-surface-contrast-light);\n --color-surface-contrast-800: var(--color-surface-contrast-light);\n --color-surface-contrast-900: var(--color-surface-contrast-light);\n --color-surface-contrast-950: var(--color-surface-contrast-light);\n}\n",
45
- "/lib/components/ui/ThemeToggle.svelte": "<script lang=\"ts\">\n import Sun from 'phosphor-svelte/lib/Sun';\n import Moon from 'phosphor-svelte/lib/Moon';\n import { onMount } from 'svelte';\n\n interface Props {\n class?: string;\n }\n\n let { class: className = '' }: Props = $props();\n\n let isDark = $state(true);\n\n function applyMode(dark: boolean) {\n const mode = dark ? 'dark' : 'light';\n document.documentElement.setAttribute('data-mode', mode);\n document.documentElement.style.colorScheme = mode;\n }\n\n onMount(() => {\n const stored = localStorage.getItem('theme-mode');\n if (stored) {\n // User has a manual preference\n isDark = stored === 'dark';\n } else {\n // Follow system\n isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;\n }\n applyMode(isDark);\n });\n\n function toggle() {\n isDark = !isDark;\n applyMode(isDark);\n localStorage.setItem('theme-mode', isDark ? 'dark' : 'light');\n }\n<\/script>\n\n<button\n onclick={toggle}\n class=\"btn hover:preset-tonal-surface p-2 rounded-full {className}\"\n aria-label=\"Toggle theme\"\n>\n {#if isDark}\n <Moon size={18} />\n {:else}\n <Sun size={18} />\n {/if}\n</button>\n",
46
- "/lib/components/ui/index.ts": "export { default as Accordion } from './Accordion.svelte';\nexport { default as Alert } from './Alert.svelte';\nexport { default as Avatar } from './Avatar.svelte';\nexport { default as Badge } from './Badge.svelte';\nexport { default as Breadcrumb } from './Breadcrumb.svelte';\nexport { default as Button } from './Button.svelte';\nexport { default as Card } from './Card.svelte';\nexport { default as Checkbox } from './Checkbox.svelte';\nexport { default as Input } from './Input.svelte';\nexport { default as Select } from './Select.svelte';\nexport { default as Table } from './Table.svelte';\nexport { default as Tabs } from './Tabs.svelte';\nexport { default as Textarea } from './Textarea.svelte';\nexport { default as ThemeToggle } from './ThemeToggle.svelte';\nexport { default as AvatarInitial } from './AvatarInitial.svelte';\nexport { default as Feedback } from './Feedback.svelte';\nexport { default as Toggle } from './Toggle.svelte';\n",
47
- "/lib/components/ui/Breadcrumb.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import CaretRight from 'phosphor-svelte/lib/CaretRight';\n\n interface Crumb {\n href?: string;\n label: string;\n }\n\n interface Props {\n items: Crumb[];\n class?: string;\n }\n\n let { items, class: className = '' }: Props = $props();\n<\/script>\n\n<nav aria-label=\"Breadcrumb\" class={className}>\n <ol class=\"flex items-center gap-1 text-sm\">\n {#each items as item, i}\n <li class=\"flex items-center gap-1\">\n {#if i > 0}\n <CaretRight size={14} class=\"text-surface-400\" />\n {/if}\n {#if item.href}\n <a href={item.href} class=\"hover:text-primary-500 transition-colors\">\n {item.label}\n </a>\n {:else}\n <span class=\"text-surface-500\">{item.label}</span>\n {/if}\n </li>\n {/each}\n </ol>\n</nav>\n",
48
- "/lib/components/ui/Accordion.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import CaretDown from 'phosphor-svelte/lib/CaretDown';\n\n interface AccordionItem {\n title: string;\n content: string;\n }\n\n interface Props {\n items: AccordionItem[];\n class?: string;\n }\n\n let { items, class: className = '' }: Props = $props();\n let openIndex = $state<number | null>(null);\n\n function toggle(i: number) {\n openIndex = openIndex === i ? null : i;\n }\n<\/script>\n\n<div class={cn('divide-y divide-surface-200-800 rounded-card border border-surface-200-800', className)}>\n {#each items as item, i}\n <div>\n <button\n class=\"w-full flex items-center justify-between p-element hover:bg-surface-100-800 transition-colors\"\n onclick={() => toggle(i)}\n >\n <span class=\"font-medium\">{item.title}</span>\n <CaretDown\n size={18}\n class={cn('transition-transform', openIndex === i && 'rotate-180')}\n />\n </button>\n {#if openIndex === i}\n <div class=\"px-element pb-element text-surface-500-400\">\n {item.content}\n </div>\n {/if}\n </div>\n {/each}\n</div>\n",
49
- "/lib/components/ui/Badge.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import type { Snippet } from 'svelte';\n import type { HTMLAttributes } from 'svelte/elements';\n\n type Variant = 'filled' | 'outlined' | 'tonal';\n type Color = 'primary' | 'secondary' | 'tertiary' | 'success' | 'warning' | 'error' | 'surface';\n type Size = 'sm' | 'md' | 'lg';\n\n interface Props extends HTMLAttributes<HTMLSpanElement> {\n variant?: Variant;\n color?: Color;\n size?: Size;\n class?: string;\n children: Snippet;\n }\n\n let {\n variant = 'filled',\n color = 'primary',\n size = 'md',\n class: className = '',\n children,\n ...rest\n }: Props = $props();\n\n const presets = {\n filled: {\n primary: 'preset-filled-primary-400-600',\n secondary: 'preset-filled-secondary-400-600',\n tertiary: 'preset-filled-tertiary-400-600',\n success: 'preset-filled-success-400-600',\n warning: 'preset-filled-warning-400-600',\n error: 'preset-filled-error-400-600',\n surface: 'preset-filled-surface-400-600'\n },\n outlined: {\n primary: 'preset-outlined-primary-400-600',\n secondary: 'preset-outlined-secondary-400-600',\n tertiary: 'preset-outlined-tertiary-400-600',\n success: 'preset-outlined-success-400-600',\n warning: 'preset-outlined-warning-400-600',\n error: 'preset-outlined-error-400-600',\n surface: 'preset-outlined-surface-400-600'\n },\n tonal: {\n primary: 'preset-tonal-primary',\n secondary: 'preset-tonal-secondary',\n tertiary: 'preset-tonal-tertiary',\n success: 'preset-tonal-success',\n warning: 'preset-tonal-warning',\n error: 'preset-tonal-error',\n surface: 'preset-tonal-surface'\n }\n } as const;\n\n let sizeClass = $derived(\n size === 'sm' ? 'badge-sm' : size === 'lg' ? 'badge-lg' : 'badge-md'\n );\n\n let presetClass = $derived(presets[variant][color]);\n\n let classes = $derived(cn('badge', presetClass, sizeClass, className));\n<\/script>\n\n<span class={classes} {...rest}>\n {@render children()}\n</span>\n",
50
- "/lib/components/ui/Input.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import type { HTMLAttributes } from 'svelte/elements';\n\n interface Props extends HTMLAttributes<HTMLInputElement> {\n label?: string;\n error?: string;\n type?: string;\n placeholder?: string;\n value?: string;\n required?: boolean;\n name?: string;\n class?: string;\n }\n let { label, error, class: className, value = $bindable(''), ...rest }: Props = $props();\n let inputId = $derived(rest.id ?? `input-${Math.random().toString(36).slice(2, 9)}`);\n<\/script>\n\n<div class=\"w-full\">\n {#if label}\n <label class=\"label\" for={inputId}>\n {label}\n </label>\n {/if}\n <input\n id={inputId}\n class={cn('input', error && 'input-error', className)}\n bind:value\n {...rest}\n />\n {#if error}\n <p class=\"text-error-500 text-sm mt-1\">{error}</p>\n {/if}\n</div>\n",
51
- "/lib/components/ui/Checkbox.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import type { HTMLAttributes } from 'svelte/elements';\n\n interface Props extends HTMLAttributes<HTMLInputElement> {\n label?: string;\n checked?: boolean;\n class?: string;\n }\n\n let { label, class: className, checked = $bindable(false), ...rest }: Props = $props();\n<\/script>\n\n<label class={cn('flex items-center gap-2 cursor-pointer', className)}>\n <input type=\"checkbox\" class=\"checkbox\" bind:checked {...rest} />\n {#if label}\n <span>{label}</span>\n {/if}\n</label>\n",
52
- "/lib/components/ui/Select.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import type { HTMLAttributes } from 'svelte/elements';\n\n interface Props extends HTMLAttributes<HTMLSelectElement> {\n label?: string;\n error?: string;\n options: { value: string; label: string }[];\n value?: string;\n class?: string;\n }\n\n let { label, error, options, class: className, value = $bindable(''), ...rest }: Props = $props();\n<\/script>\n\n<div class=\"w-full\">\n {#if label}\n <label class=\"label\" for={rest.id}>\n {label}\n </label>\n {/if}\n <select class={cn('select', error && 'select-error', className)} bind:value {...rest}>\n {#each options as opt}\n <option value={opt.value}>{opt.label}</option>\n {/each}\n </select>\n {#if error}\n <p class=\"text-error-500 text-sm mt-1\">{error}</p>\n {/if}\n</div>\n",
53
- "/lib/components/ui/Alert.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import type { Snippet } from 'svelte';\n import type { HTMLAttributes } from 'svelte/elements';\n\n type Variant = 'info' | 'success' | 'warning' | 'error';\n\n interface Props extends HTMLAttributes<HTMLDivElement> {\n variant?: Variant;\n class?: string;\n children: Snippet;\n }\n\n let { variant = 'info', class: className = '', children, ...rest }: Props = $props();\n\n const presets: Record<Variant, string> = {\n info: 'preset-tonal-info',\n success: 'preset-tonal-success',\n warning: 'preset-tonal-warning',\n error: 'preset-tonal-error'\n };\n\n let classes = $derived(cn('alert', presets[variant], className));\n<\/script>\n\n<div class={classes} {...rest}>\n {@render children()}\n</div>\n",
54
- "/lib/components/ui/Tabs.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n\n interface Tab {\n label: string;\n content: string;\n }\n\n interface Props {\n tabs: Tab[];\n class?: string;\n }\n\n let { tabs, class: className = '' }: Props = $props();\n let activeTab = $state(0);\n<\/script>\n\n<div class={className}>\n <div class=\"flex border-b border-surface-200-800 gap-1\">\n {#each tabs as tab, i}\n <button\n class={cn(\n 'px-4 py-2 font-medium transition-colors rounded-t-card',\n activeTab === i\n ? 'text-primary-500 border-b-2 border-primary-500'\n : 'text-surface-500 hover:text-surface-900-100'\n )}\n onclick={() => (activeTab = i)}\n >\n {tab.label}\n </button>\n {/each}\n </div>\n <div class=\"p-element\">\n {tabs[activeTab].content}\n </div>\n</div>\n",
55
- "/lib/components/ui/Toggle.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n\n interface Props {\n label?: string;\n checked?: boolean;\n class?: string;\n }\n\n let { label, class: className, checked = $bindable(false) }: Props = $props();\n<\/script>\n\n<label class={cn('flex items-center gap-2 cursor-pointer', className)}>\n <input type=\"checkbox\" class=\"toggle\" bind:checked />\n {#if label}\n <span>{label}</span>\n {/if}\n</label>\n",
56
- "/lib/components/ui/Card.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import type { Snippet } from 'svelte';\n import type { HTMLAttributes } from 'svelte/elements';\n\n type Variant = 'flat' | 'elevated' | 'outlined';\n\n interface Props extends HTMLAttributes<HTMLDivElement> {\n variant?: Variant;\n class?: string;\n children: Snippet;\n header?: Snippet;\n footer?: Snippet;\n }\n\n let { variant = 'flat', class: className = '', children, header, footer, ...rest }: Props = $props();\n\n const variantClasses: Record<Variant, string> = {\n flat: 'card',\n elevated: 'card shadow-lg',\n outlined: 'card ring-1 ring-surface-200-800'\n };\n\n let classes = $derived(cn(variantClasses[variant], 'p-element rounded-card', className));\n<\/script>\n\n<div class={classes} {...rest}>\n {#if header}\n <div class=\"border-b border-surface-200-800 pb-3 mb-3\">\n {@render header()}\n </div>\n {/if}\n\n {@render children()}\n\n {#if footer}\n <div class=\"border-t border-surface-200-800 pt-3 mt-3\">\n {@render footer()}\n </div>\n {/if}\n</div>\n",
57
- "/lib/components/ui/Table.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import type { HTMLAttributes } from 'svelte/elements';\n\n interface Column {\n key: string;\n label: string;\n class?: string;\n }\n\n interface Props extends HTMLAttributes<HTMLDivElement> {\n columns: Column[];\n rows: Record<string, string>[];\n class?: string;\n }\n\n let { columns, rows, class: className = '', ...rest }: Props = $props();\n<\/script>\n\n<div class={cn('overflow-x-auto rounded-card border border-surface-200-800', className)} {...rest}>\n <table class=\"w-full\">\n <thead class=\"bg-surface-100-800\">\n <tr>\n {#each columns as col}\n <th class=\"px-element py-3 text-left text-sm font-semibold text-surface-500 {col.class}\">\n {col.label}\n </th>\n {/each}\n </tr>\n </thead>\n <tbody class=\"divide-y divide-surface-200-800\">\n {#each rows as row}\n <tr class=\"hover:bg-surface-50-900 transition-colors\">\n {#each columns as col}\n <td class=\"px-element py-3 text-sm {col.class}\">\n {row[col.key]}\n </td>\n {/each}\n </tr>\n {/each}\n </tbody>\n </table>\n</div>\n",
58
- "/lib/components/ui/Textarea.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import type { HTMLAttributes } from 'svelte/elements';\n\n interface Props extends HTMLAttributes<HTMLTextAreaElement> {\n label?: string;\n error?: string;\n placeholder?: string;\n rows?: number;\n value?: string;\n class?: string;\n }\n\n let { label, error, class: className, value = $bindable(''), ...rest }: Props = $props();\n let inputId = $derived(rest.id ?? `textarea-${Math.random().toString(36).slice(2, 9)}`);\n<\/script>\n\n<div class=\"w-full\">\n {#if label}\n <label class=\"label\" for={inputId}>\n {label}\n </label>\n {/if}\n <textarea\n id={inputId}\n class={cn('textarea', error && 'textarea-error', className)}\n bind:value\n {...rest}\n ></textarea>\n {#if error}\n <p class=\"text-error-500 text-sm mt-1\">{error}</p>\n {/if}\n</div>\n",
59
- "/lib/components/ui/Avatar.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n\n interface Props {\n src: string;\n alt?: string;\n size?: 'sm' | 'md' | 'lg';\n class?: string;\n }\n\n let { size = 'md', class: className = '', src, alt = '' }: Props = $props();\n\n const sizes: Record<string, string> = {\n sm: 'w-8 h-8',\n md: 'w-10 h-10',\n lg: 'w-14 h-14'\n };\n<\/script>\n\n<img\n {src}\n {alt}\n class={cn('rounded-full object-cover ring-2 ring-surface-200-800', sizes[size], className)}\n/>\n",
60
- "/lib/components/ui/Button.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import type { Snippet } from 'svelte';\n import type { HTMLAttributes } from 'svelte/elements';\n\n type Variant = 'filled' | 'outlined' | 'tonal' | 'ghost';\n type Color = 'primary' | 'secondary' | 'tertiary' | 'success' | 'warning' | 'error' | 'surface';\n type Size = 'sm' | 'md' | 'lg';\n\n interface Props extends HTMLAttributes<HTMLButtonElement> {\n variant?: Variant;\n color?: Color;\n size?: Size;\n href?: string;\n loading?: boolean;\n disabled?: boolean;\n type?: 'button' | 'submit' | 'reset';\n class?: string;\n children: Snippet;\n }\n\n let {\n variant = 'filled',\n color = 'primary',\n size = 'md',\n href,\n loading = false,\n disabled = false,\n type = 'button',\n class: className = '',\n children,\n ...rest\n }: Props = $props();\n\n const presets = {\n filled: {\n primary: 'preset-filled-primary-400-600',\n secondary: 'preset-filled-secondary-400-600',\n tertiary: 'preset-filled-tertiary-400-600',\n success: 'preset-filled-success-400-600',\n warning: 'preset-filled-warning-400-600',\n error: 'preset-filled-error-400-600',\n surface: 'preset-filled-surface-400-600'\n },\n outlined: {\n primary: 'preset-outlined-primary-400-600',\n secondary: 'preset-outlined-secondary-400-600',\n tertiary: 'preset-outlined-tertiary-400-600',\n success: 'preset-outlined-success-400-600',\n warning: 'preset-outlined-warning-400-600',\n error: 'preset-outlined-error-400-600',\n surface: 'preset-outlined-surface-400-600'\n },\n tonal: {\n primary: 'preset-tonal-primary',\n secondary: 'preset-tonal-secondary',\n tertiary: 'preset-tonal-tertiary',\n success: 'preset-tonal-success',\n warning: 'preset-tonal-warning',\n error: 'preset-tonal-error',\n surface: 'preset-tonal-surface'\n },\n ghost: {\n primary: 'hover:preset-tonal-primary',\n secondary: 'hover:preset-tonal-secondary',\n tertiary: 'hover:preset-tonal-tertiary',\n success: 'hover:preset-tonal-success',\n warning: 'hover:preset-tonal-warning',\n error: 'hover:preset-tonal-error',\n surface: 'hover:preset-tonal-surface'\n }\n } as const;\n\n let sizeClass = $derived(\n size === 'sm' ? 'btn-sm' : size === 'lg' ? 'btn-lg' : 'btn-md'\n );\n\n let presetClass = $derived(\n presets[variant]?.[color] ?? ''\n );\n\n let classes = $derived(cn('btn', presetClass, sizeClass, className));\n<\/script>\n\n{#if href}\n <a {href} class={classes}>\n {#if loading}\n <span\n class=\"mr-2 inline-block h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent\"\n ></span>\n {/if}\n {@render children()}\n </a>\n{:else}\n <button class={classes} disabled={disabled || loading} type={type} {...rest} aria-busy={loading}>\n {#if loading}\n <span\n class=\"mr-2 inline-block h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent\"\n ></span>\n {/if}\n {@render children()}\n </button>\n{/if}\n",
61
- "/lib/components/layout/Navbar.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import type { Snippet } from 'svelte';\n import type { HTMLAttributes } from 'svelte/elements';\n import ThemeToggle from '$lib/components/ui/ThemeToggle.svelte';\n import Menu from 'phosphor-svelte/lib/List';\n import X from 'phosphor-svelte/lib/X';\n\n interface Props extends HTMLAttributes<HTMLElement> {\n brand?: Snippet;\n links?: { href: string; label: string }[];\n class?: string;\n }\n\n let { brand, links = [], class: className, ...rest }: Props = $props();\n let mobileOpen = $state(false);\n<\/script>\n\n<nav class={cn('sticky top-0 z-50 bg-surface-50-950/80 backdrop-blur-md border-b border-surface-200-800', className)} {...rest}>\n <div class=\"max-w-container mx-auto flex items-center justify-between px-element py-3\">\n <!-- Brand -->\n <a href=\"/\" class=\"text-xl font-heading font-bold text-primary-500\">\n {#if brand}\n {@render brand()}\n {:else}\n SvelteForge\n {/if}\n </a>\n\n <!-- Desktop links -->\n <div class=\"hidden md:flex items-center gap-4\">\n {#each links as link}\n <a href={link.href} class=\"hover:text-primary-500 transition-colors\">\n {link.label}\n </a>\n {/each}\n <ThemeToggle />\n </div>\n\n <!-- Mobile toggle -->\n <button\n class=\"md:hidden btn hover:preset-tonal-surface p-2 rounded-full\"\n onclick={() => (mobileOpen = !mobileOpen)}\n aria-label=\"Toggle menu\"\n >\n {#if mobileOpen}\n <X size={20} />\n {:else}\n <Menu size={20} />\n {/if}\n </button>\n </div>\n\n <!-- Mobile menu -->\n {#if mobileOpen}\n <div class=\"md:hidden border-t border-surface-200-800 px-element py-3 flex flex-col gap-3\">\n {#each links as link}\n <a href={link.href} class=\"hover:text-primary-500 transition-colors\" onclick={() => (mobileOpen = false)}>\n {link.label}\n </a>\n {/each}\n <ThemeToggle />\n </div>\n {/if}\n</nav>\n",
62
- "/lib/components/layout/Footer.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import type { Snippet } from 'svelte';\n import type { HTMLAttributes } from 'svelte/elements';\n\n interface Props extends HTMLAttributes<HTMLElement> {\n links?: { href: string; label: string }[];\n class?: string;\n children?: Snippet;\n }\n\n let { links = [], class: className, children, ...rest }: Props = $props();\n<\/script>\n\n<footer class={cn('border-t border-surface-200-800 bg-surface-100-900', className)} {...rest}>\n <div class=\"max-w-container mx-auto px-element py-section\">\n <div class=\"flex flex-col md:flex-row justify-between gap-group\">\n {#if children}\n {@render children()}\n {/if}\n\n {#if links.length}\n <div class=\"flex flex-col gap-2\">\n {#each links as link}\n <a href={link.href} class=\"text-sm hover:text-primary-500 transition-colors\">\n {link.label}\n </a>\n {/each}\n </div>\n {/if}\n </div>\n\n <div class=\"mt-section pt-3 border-t border-surface-200-800 text-sm text-surface-500\">\n &copy; {new Date().getFullYear()} SvelteForge. All rights reserved.\n </div>\n </div>\n</footer>\n",
48
+ "/lib/components/layout/Navbar.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import type { Snippet } from 'svelte';\n import type { HTMLAttributes } from 'svelte/elements';\n import ThemeToggle from '$lib/components/svforge/ui/ThemeToggle.svelte';\n import Logo from '$lib/components/svforge/ui/Logo.svelte';\n import Menu from 'phosphor-svelte/lib/List';\n import X from 'phosphor-svelte/lib/X';\n\n interface Props extends HTMLAttributes<HTMLElement> {\n brand?: Snippet;\n links?: { href: string; label: string }[];\n class?: string;\n }\n\n let { brand, links = [], class: className, ...rest }: Props = $props();\n let mobileOpen = $state(false);\n<\/script>\n\n<nav class={cn('sticky top-0 z-50 bg-surface-50-950/80 backdrop-blur-md border-b border-surface-200-800', className)} {...rest}>\n <div class=\"max-w-container mx-auto flex items-center justify-between px-element py-3\">\n <!-- Brand -->\n <a href=\"/\" class=\"text-xl font-bold\">\n {#if brand}\n {@render brand()}\n {:else}\n <Logo />\n {/if}\n </a>\n\n <!-- Desktop links -->\n <div class=\"hidden md:flex items-center gap-4\">\n {#each links as link}\n <a href={link.href} class=\"hover:text-primary-500 transition-colors\">\n {link.label}\n </a>\n {/each}\n <ThemeToggle />\n </div>\n\n <!-- Mobile toggle -->\n <button\n class=\"md:hidden btn hover:preset-tonal-surface p-2 rounded-full\"\n onclick={() => (mobileOpen = !mobileOpen)}\n aria-label=\"Toggle menu\"\n >\n {#if mobileOpen}\n <X size={20} />\n {:else}\n <Menu size={20} />\n {/if}\n </button>\n </div>\n\n <!-- Mobile menu -->\n {#if mobileOpen}\n <div class=\"md:hidden border-t border-surface-200-800 px-element py-3 flex flex-col gap-3\">\n {#each links as link}\n <a href={link.href} class=\"hover:text-primary-500 transition-colors\" onclick={() => (mobileOpen = false)}>\n {link.label}\n </a>\n {/each}\n <ThemeToggle />\n </div>\n {/if}\n</nav>\n",
49
+ "/lib/components/layout/Footer.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import type { Snippet } from 'svelte';\n import type { HTMLAttributes } from 'svelte/elements';\n\n interface Props extends HTMLAttributes<HTMLElement> {\n links?: { href: string; label: string }[];\n class?: string;\n children?: Snippet;\n }\n\n let { links = [], class: className, children, ...rest }: Props = $props();\n<\/script>\n\n<footer class={cn('border-t border-surface-200-800 bg-surface-100-900', className)} {...rest}>\n <div class=\"max-w-container mx-auto px-element py-section\">\n <div class=\"flex flex-col md:flex-row justify-between gap-group\">\n {#if children}\n {@render children()}\n {/if}\n\n {#if links.length}\n <div class=\"flex flex-col gap-2\">\n {#each links as link}\n <a href={link.href} class=\"text-sm hover:text-primary-500 transition-colors\">\n {link.label}\n </a>\n {/each}\n </div>\n {/if}\n </div>\n\n <div class=\"mt-section pt-3 border-t border-surface-200-800 text-sm text-surface-500\">\n &copy; {new Date().getFullYear()} SVForge. All rights reserved.\n </div>\n </div>\n</footer>\n",
63
50
  "/lib/components/layout/index.ts": "export { default as AdminLayout } from './AdminLayout.svelte';\nexport { default as Footer } from './Footer.svelte';\nexport { default as Navbar } from './Navbar.svelte';\n",
51
+ "/lib/components/svforge/ui/ThemeToggle.svelte": "<script lang=\"ts\">\n import Sun from 'phosphor-svelte/lib/Sun';\n import Moon from 'phosphor-svelte/lib/Moon';\n import { onMount } from 'svelte';\n\n interface Props {\n class?: string;\n }\n\n let { class: className = '' }: Props = $props();\n\n let isDark = $state(true);\n\n function applyMode(dark: boolean) {\n const mode = dark ? 'dark' : 'light';\n document.documentElement.setAttribute('data-mode', mode);\n document.documentElement.style.colorScheme = mode;\n }\n\n onMount(() => {\n const stored = localStorage.getItem('theme-mode');\n if (stored) {\n // User has a manual preference\n isDark = stored === 'dark';\n } else {\n // Follow system\n isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;\n }\n applyMode(isDark);\n });\n\n function toggle() {\n isDark = !isDark;\n applyMode(isDark);\n localStorage.setItem('theme-mode', isDark ? 'dark' : 'light');\n }\n<\/script>\n\n<button\n onclick={toggle}\n class=\"btn hover:preset-tonal-surface p-2 rounded-full {className}\"\n aria-label=\"Toggle theme\"\n>\n {#if isDark}\n <Moon size={18} />\n {:else}\n <Sun size={18} />\n {/if}\n</button>\n",
52
+ "/lib/components/svforge/ui/Seo.svelte": "<script lang=\"ts\">\n import { page } from '$app/stores';\n\n interface Props {\n title: string;\n description: string;\n image?: string;\n url?: string;\n type?: string;\n }\n\n let { title, description, image, url, type = 'website' }: Props = $props();\n\n const resolvedUrl = $derived(url ?? $page.url.href);\n const twitterCard = $derived(image ? 'summary_large_image' : 'summary');\n<\/script>\n\n<svelte:head>\n <title>{title}</title>\n <meta name=\"description\" content={description} />\n <meta property=\"og:title\" content={title} />\n <meta property=\"og:description\" content={description} />\n <meta property=\"og:type\" content={type} />\n <meta property=\"og:url\" content={resolvedUrl} />\n {#if image}\n <meta property=\"og:image\" content={image} />\n {/if}\n <meta name=\"twitter:card\" content={twitterCard} />\n <meta name=\"twitter:title\" content={title} />\n <meta name=\"twitter:description\" content={description} />\n {#if image}\n <meta name=\"twitter:image\" content={image} />\n {/if}\n</svelte:head>\n",
53
+ "/lib/components/svforge/ui/index.ts": "export { default as Accordion } from './Accordion.svelte';\nexport { default as Alert } from './Alert.svelte';\nexport { default as Avatar } from './Avatar.svelte';\nexport { default as Badge } from './Badge.svelte';\nexport { default as Breadcrumb } from './Breadcrumb.svelte';\nexport { default as Button } from './Button.svelte';\nexport { default as Card } from './Card.svelte';\nexport { default as Checkbox } from './Checkbox.svelte';\nexport { default as Input } from './Input.svelte';\nexport { default as Select } from './Select.svelte';\nexport { default as Table } from './Table.svelte';\nexport { default as Tabs } from './Tabs.svelte';\nexport { default as Textarea } from './Textarea.svelte';\nexport { default as ThemeToggle } from './ThemeToggle.svelte';\nexport { default as AvatarInitial } from './AvatarInitial.svelte';\nexport { default as Feedback } from './Feedback.svelte';\nexport { default as Toggle } from './Toggle.svelte';\n",
54
+ "/lib/components/svforge/ui/Breadcrumb.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import CaretRight from 'phosphor-svelte/lib/CaretRight';\n\n interface Crumb {\n href?: string;\n label: string;\n }\n\n interface Props {\n items: Crumb[];\n class?: string;\n }\n\n let { items, class: className = '' }: Props = $props();\n<\/script>\n\n<nav aria-label=\"Breadcrumb\" class={className}>\n <ol class=\"flex items-center gap-1 text-sm\">\n {#each items as item, i}\n <li class=\"flex items-center gap-1\">\n {#if i > 0}\n <CaretRight size={14} class=\"text-surface-400\" />\n {/if}\n {#if item.href}\n <a href={item.href} class=\"hover:text-primary-500 transition-colors\">\n {item.label}\n </a>\n {:else}\n <span class=\"text-surface-500\">{item.label}</span>\n {/if}\n </li>\n {/each}\n </ol>\n</nav>\n",
55
+ "/lib/components/svforge/ui/Sitemap.ts": "interface SitemapEntry {\n path: string;\n lastmod?: string;\n changefreq?: string;\n priority?: number;\n}\n\nexport function generateSitemap(baseUrl: string, routes: SitemapEntry[]): string {\n const entries = routes\n .map((route) => {\n const loc = `${baseUrl}${route.path}`;\n let xml = ` <url>\\n <loc>${loc}</loc>`;\n if (route.lastmod) xml += `\\n <lastmod>${route.lastmod}</lastmod>`;\n if (route.changefreq) xml += `\\n <changefreq>${route.changefreq}</changefreq>`;\n if (route.priority !== undefined) xml += `\\n <priority>${route.priority}</priority>`;\n xml += '\\n </url>';\n return xml;\n })\n .join('\\n');\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n${entries}\n</urlset>`;\n}\n",
56
+ "/lib/components/svforge/ui/Accordion.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import CaretDown from 'phosphor-svelte/lib/CaretDown';\n\n interface AccordionItem {\n title: string;\n content: string;\n }\n\n interface Props {\n items: AccordionItem[];\n class?: string;\n }\n\n let { items, class: className = '' }: Props = $props();\n let openIndex = $state<number | null>(null);\n\n function toggle(i: number) {\n openIndex = openIndex === i ? null : i;\n }\n<\/script>\n\n<div class={cn('divide-y divide-surface-200-800 rounded-card border border-surface-200-800', className)}>\n {#each items as item, i}\n <div>\n <button\n class=\"w-full flex items-center justify-between p-element hover:bg-surface-100-800 transition-colors\"\n onclick={() => toggle(i)}\n >\n <span class=\"font-medium\">{item.title}</span>\n <CaretDown\n size={18}\n class={cn('transition-transform', openIndex === i && 'rotate-180')}\n />\n </button>\n {#if openIndex === i}\n <div class=\"px-element pb-element text-surface-500-400\">\n {item.content}\n </div>\n {/if}\n </div>\n {/each}\n</div>\n",
57
+ "/lib/components/svforge/ui/Badge.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import type { Snippet } from 'svelte';\n import type { HTMLAttributes } from 'svelte/elements';\n\n type Variant = 'filled' | 'outlined' | 'tonal';\n type Color = 'primary' | 'secondary' | 'tertiary' | 'success' | 'warning' | 'error' | 'surface';\n type Size = 'sm' | 'md' | 'lg';\n\n interface Props extends HTMLAttributes<HTMLSpanElement> {\n variant?: Variant;\n color?: Color;\n size?: Size;\n class?: string;\n children: Snippet;\n }\n\n let {\n variant = 'filled',\n color = 'primary',\n size = 'md',\n class: className = '',\n children,\n ...rest\n }: Props = $props();\n\n const presets = {\n filled: {\n primary: 'preset-filled-primary-400-600',\n secondary: 'preset-filled-secondary-400-600',\n tertiary: 'preset-filled-tertiary-400-600',\n success: 'preset-filled-success-400-600',\n warning: 'preset-filled-warning-400-600',\n error: 'preset-filled-error-400-600',\n surface: 'preset-filled-surface-400-600'\n },\n outlined: {\n primary: 'preset-outlined-primary-400-600',\n secondary: 'preset-outlined-secondary-400-600',\n tertiary: 'preset-outlined-tertiary-400-600',\n success: 'preset-outlined-success-400-600',\n warning: 'preset-outlined-warning-400-600',\n error: 'preset-outlined-error-400-600',\n surface: 'preset-outlined-surface-400-600'\n },\n tonal: {\n primary: 'preset-tonal-primary',\n secondary: 'preset-tonal-secondary',\n tertiary: 'preset-tonal-tertiary',\n success: 'preset-tonal-success',\n warning: 'preset-tonal-warning',\n error: 'preset-tonal-error',\n surface: 'preset-tonal-surface'\n }\n } as const;\n\n let sizeClass = $derived(\n size === 'sm' ? 'badge-sm' : size === 'lg' ? 'badge-lg' : 'badge-md'\n );\n\n let presetClass = $derived(presets[variant][color]);\n\n let classes = $derived(cn('badge', presetClass, sizeClass, className));\n<\/script>\n\n<span class={classes} {...rest}>\n {@render children()}\n</span>\n",
58
+ "/lib/components/svforge/ui/Input.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import type { HTMLAttributes } from 'svelte/elements';\n\n interface Props extends HTMLAttributes<HTMLInputElement> {\n label?: string;\n error?: string;\n type?: string;\n placeholder?: string;\n value?: string;\n required?: boolean;\n name?: string;\n class?: string;\n }\n let { label, error, class: className, value = $bindable(''), ...rest }: Props = $props();\n let inputId = $derived(rest.id ?? `input-${Math.random().toString(36).slice(2, 9)}`);\n<\/script>\n\n<div class=\"w-full\">\n {#if label}\n <label class=\"label\" for={inputId}>\n {label}\n </label>\n {/if}\n <input\n id={inputId}\n class={cn('input', error && 'input-error', className)}\n bind:value\n {...rest}\n />\n {#if error}\n <p class=\"text-error-500 text-sm mt-1\">{error}</p>\n {/if}\n</div>\n",
59
+ "/lib/components/svforge/ui/Checkbox.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import type { HTMLAttributes } from 'svelte/elements';\n\n interface Props extends HTMLAttributes<HTMLInputElement> {\n label?: string;\n checked?: boolean;\n class?: string;\n }\n\n let { label, class: className, checked = $bindable(false), ...rest }: Props = $props();\n<\/script>\n\n<label class={cn('flex items-center gap-2 cursor-pointer', className)}>\n <input type=\"checkbox\" class=\"checkbox\" bind:checked {...rest} />\n {#if label}\n <span>{label}</span>\n {/if}\n</label>\n",
60
+ "/lib/components/svforge/ui/Logo.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n\n interface Props {\n class?: string;\n }\n\n let { class: className = '' }: Props = $props();\n<\/script>\n\n<span class={cn('logo text-xl font-extrabold', className)}>SVForge</span>\n\n<style>\n @keyframes metalFlow {\n 0% {\n background-position: 0% 50%;\n }\n 50% {\n background-position: 100% 50%;\n }\n 100% {\n background-position: 0% 50%;\n }\n }\n .logo {\n font-family: 'Space Grotesk Variable', sans-serif;\n color: transparent;\n background: linear-gradient(\n 45deg,\n var(--color-secondary-200-800),\n var(--color-secondary-500),\n var(--color-secondary-600-400),\n var(--color-secondary-200-800)\n );\n background-size: 200% 200%;\n -webkit-background-clip: text;\n background-clip: text;\n letter-spacing: 0.05em;\n animation: metalFlow 5s ease-in-out infinite;\n filter: drop-shadow(0 0 2px var(--color-secondary-200-800));\n }\n</style>\n",
61
+ "/lib/components/svforge/ui/Select.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import type { HTMLAttributes } from 'svelte/elements';\n\n interface Props extends HTMLAttributes<HTMLSelectElement> {\n label?: string;\n error?: string;\n options: { value: string; label: string }[];\n value?: string;\n class?: string;\n }\n\n let { label, error, options, class: className, value = $bindable(''), ...rest }: Props = $props();\n<\/script>\n\n<div class=\"w-full\">\n {#if label}\n <label class=\"label\" for={rest.id}>\n {label}\n </label>\n {/if}\n <select class={cn('select', error && 'select-error', className)} bind:value {...rest}>\n {#each options as opt}\n <option value={opt.value}>{opt.label}</option>\n {/each}\n </select>\n {#if error}\n <p class=\"text-error-500 text-sm mt-1\">{error}</p>\n {/if}\n</div>\n",
62
+ "/lib/components/svforge/ui/Alert.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import type { Snippet } from 'svelte';\n import type { HTMLAttributes } from 'svelte/elements';\n\n type Variant = 'info' | 'success' | 'warning' | 'error';\n\n interface Props extends HTMLAttributes<HTMLDivElement> {\n variant?: Variant;\n class?: string;\n children: Snippet;\n }\n\n let { variant = 'info', class: className = '', children, ...rest }: Props = $props();\n\n const presets: Record<Variant, string> = {\n info: 'preset-tonal-info',\n success: 'preset-tonal-success',\n warning: 'preset-tonal-warning',\n error: 'preset-tonal-error'\n };\n\n let classes = $derived(cn('alert', presets[variant], className));\n<\/script>\n\n<div class={classes} {...rest}>\n {@render children()}\n</div>\n",
63
+ "/lib/components/svforge/ui/Tabs.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n\n interface Tab {\n label: string;\n content: string;\n }\n\n interface Props {\n tabs: Tab[];\n class?: string;\n }\n\n let { tabs, class: className = '' }: Props = $props();\n let activeTab = $state(0);\n<\/script>\n\n<div class={className}>\n <div class=\"flex border-b border-surface-200-800 gap-1\">\n {#each tabs as tab, i}\n <button\n class={cn(\n 'px-4 py-2 font-medium transition-colors rounded-t-card',\n activeTab === i\n ? 'text-primary-500 border-b-2 border-primary-500'\n : 'text-surface-500 hover:text-surface-900-100'\n )}\n onclick={() => (activeTab = i)}\n >\n {tab.label}\n </button>\n {/each}\n </div>\n <div class=\"p-element\">\n {tabs[activeTab].content}\n </div>\n</div>\n",
64
+ "/lib/components/svforge/ui/Toggle.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n\n interface Props {\n label?: string;\n checked?: boolean;\n class?: string;\n }\n\n let { label, class: className, checked = $bindable(false) }: Props = $props();\n<\/script>\n\n<label class={cn('flex items-center gap-2 cursor-pointer', className)}>\n <input type=\"checkbox\" class=\"toggle\" bind:checked />\n {#if label}\n <span>{label}</span>\n {/if}\n</label>\n",
65
+ "/lib/components/svforge/ui/Card.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import type { Snippet } from 'svelte';\n import type { HTMLAttributes } from 'svelte/elements';\n\n type Variant = 'flat' | 'elevated' | 'outlined';\n\n interface Props extends HTMLAttributes<HTMLDivElement> {\n variant?: Variant;\n class?: string;\n children: Snippet;\n header?: Snippet;\n footer?: Snippet;\n }\n\n let { variant = 'flat', class: className = '', children, header, footer, ...rest }: Props = $props();\n\n const variantClasses: Record<Variant, string> = {\n flat: 'card',\n elevated: 'card shadow-lg',\n outlined: 'card ring-1 ring-surface-200-800'\n };\n\n let classes = $derived(cn(variantClasses[variant], 'p-element rounded-card', className));\n<\/script>\n\n<div class={classes} {...rest}>\n {#if header}\n <div class=\"border-b border-surface-200-800 pb-3 mb-3\">\n {@render header()}\n </div>\n {/if}\n\n {@render children()}\n\n {#if footer}\n <div class=\"border-t border-surface-200-800 pt-3 mt-3\">\n {@render footer()}\n </div>\n {/if}\n</div>\n",
66
+ "/lib/components/svforge/ui/Table.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import type { HTMLAttributes } from 'svelte/elements';\n\n interface Column {\n key: string;\n label: string;\n class?: string;\n }\n\n interface Props extends HTMLAttributes<HTMLDivElement> {\n columns: Column[];\n rows: Record<string, string>[];\n class?: string;\n }\n\n let { columns, rows, class: className = '', ...rest }: Props = $props();\n<\/script>\n\n<div class={cn('overflow-x-auto rounded-card border border-surface-200-800', className)} {...rest}>\n <table class=\"w-full\">\n <thead class=\"bg-surface-100-800\">\n <tr>\n {#each columns as col}\n <th class=\"px-element py-3 text-left text-sm font-semibold text-surface-500 {col.class}\">\n {col.label}\n </th>\n {/each}\n </tr>\n </thead>\n <tbody class=\"divide-y divide-surface-200-800\">\n {#each rows as row}\n <tr class=\"hover:bg-surface-50-900 transition-colors\">\n {#each columns as col}\n <td class=\"px-element py-3 text-sm {col.class}\">\n {row[col.key]}\n </td>\n {/each}\n </tr>\n {/each}\n </tbody>\n </table>\n</div>\n",
67
+ "/lib/components/svforge/ui/Textarea.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import type { HTMLAttributes } from 'svelte/elements';\n\n interface Props extends HTMLAttributes<HTMLTextAreaElement> {\n label?: string;\n error?: string;\n placeholder?: string;\n rows?: number;\n value?: string;\n class?: string;\n }\n\n let { label, error, class: className, value = $bindable(''), ...rest }: Props = $props();\n let inputId = $derived(rest.id ?? `textarea-${Math.random().toString(36).slice(2, 9)}`);\n<\/script>\n\n<div class=\"w-full\">\n {#if label}\n <label class=\"label\" for={inputId}>\n {label}\n </label>\n {/if}\n <textarea\n id={inputId}\n class={cn('textarea', error && 'textarea-error', className)}\n bind:value\n {...rest}\n ></textarea>\n {#if error}\n <p class=\"text-error-500 text-sm mt-1\">{error}</p>\n {/if}\n</div>\n",
68
+ "/lib/components/svforge/ui/Avatar.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n\n interface Props {\n src: string;\n alt?: string;\n size?: 'sm' | 'md' | 'lg';\n class?: string;\n }\n\n let { size = 'md', class: className = '', src, alt = '' }: Props = $props();\n\n const sizes: Record<string, string> = {\n sm: 'w-8 h-8',\n md: 'w-10 h-10',\n lg: 'w-14 h-14'\n };\n<\/script>\n\n<img\n {src}\n {alt}\n class={cn('rounded-full object-cover ring-2 ring-surface-200-800', sizes[size], className)}\n/>\n",
69
+ "/lib/components/svforge/ui/Button.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import type { Snippet } from 'svelte';\n import type { HTMLAttributes } from 'svelte/elements';\n\n type Variant = 'filled' | 'outlined' | 'tonal' | 'ghost';\n type Color = 'primary' | 'secondary' | 'tertiary' | 'success' | 'warning' | 'error' | 'surface';\n type Size = 'sm' | 'md' | 'lg';\n\n interface Props extends HTMLAttributes<HTMLButtonElement> {\n variant?: Variant;\n color?: Color;\n size?: Size;\n href?: string;\n loading?: boolean;\n disabled?: boolean;\n type?: 'button' | 'submit' | 'reset';\n class?: string;\n children: Snippet;\n }\n\n let {\n variant = 'filled',\n color = 'primary',\n size = 'md',\n href,\n loading = false,\n disabled = false,\n type = 'button',\n class: className = '',\n children,\n ...rest\n }: Props = $props();\n\n const presets = {\n filled: {\n primary: 'preset-filled-primary-400-600',\n secondary: 'preset-filled-secondary-400-600',\n tertiary: 'preset-filled-tertiary-400-600',\n success: 'preset-filled-success-400-600',\n warning: 'preset-filled-warning-400-600',\n error: 'preset-filled-error-400-600',\n surface: 'preset-filled-surface-400-600'\n },\n outlined: {\n primary: 'preset-outlined-primary-400-600',\n secondary: 'preset-outlined-secondary-400-600',\n tertiary: 'preset-outlined-tertiary-400-600',\n success: 'preset-outlined-success-400-600',\n warning: 'preset-outlined-warning-400-600',\n error: 'preset-outlined-error-400-600',\n surface: 'preset-outlined-surface-400-600'\n },\n tonal: {\n primary: 'preset-tonal-primary',\n secondary: 'preset-tonal-secondary',\n tertiary: 'preset-tonal-tertiary',\n success: 'preset-tonal-success',\n warning: 'preset-tonal-warning',\n error: 'preset-tonal-error',\n surface: 'preset-tonal-surface'\n },\n ghost: {\n primary: 'hover:preset-tonal-primary',\n secondary: 'hover:preset-tonal-secondary',\n tertiary: 'hover:preset-tonal-tertiary',\n success: 'hover:preset-tonal-success',\n warning: 'hover:preset-tonal-warning',\n error: 'hover:preset-tonal-error',\n surface: 'hover:preset-tonal-surface'\n }\n } as const;\n\n let sizeClass = $derived(\n size === 'sm' ? 'btn-sm' : size === 'lg' ? 'btn-lg' : 'btn-md'\n );\n\n let presetClass = $derived(\n presets[variant]?.[color] ?? ''\n );\n\n let classes = $derived(cn('btn', presetClass, sizeClass, className));\n<\/script>\n\n{#if href}\n <a {href} class={classes}>\n {#if loading}\n <span\n class=\"mr-2 inline-block h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent\"\n ></span>\n {/if}\n {@render children()}\n </a>\n{:else}\n <button class={classes} disabled={disabled || loading} type={type} {...rest} aria-busy={loading}>\n {#if loading}\n <span\n class=\"mr-2 inline-block h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent\"\n ></span>\n {/if}\n {@render children()}\n </button>\n{/if}\n",
64
70
  "/app.html": "<!doctype html>\n<html lang=\"en\" data-theme=\"svelteForge\">\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <meta name=\"text-scale\" content=\"scale\" />\n <link rel=\"icon\" href=\"%sveltekit.assets%/favicon.ico\" />\n %sveltekit.head%\n </head>\n <body data-sveltekit-preload-data=\"hover\">\n <div style=\"display: contents\">%sveltekit.body%</div>\n </body>\n</html>\n",
65
71
  "/app.d.ts": "import type { User, Session } from 'better-auth/minimal';\n\n// See https://svelte.dev/docs/kit/types#app.d.ts\n// for information about these interfaces\ndeclare global {\n namespace App {\n interface Locals { user?: User; session?: Session }\n\n // interface Error {}\n // interface PageData {}\n // interface PageState {}\n // interface Platform {}\n }\n}\n\nexport {};\n",
66
72
  "/routes/+page.server.ts": "import { redirect } from '@sveltejs/kit';\nimport type { PageServerLoad } from './$types';\n\nexport const load: PageServerLoad = async ({ locals }) => {\n if (locals.session) {\n throw redirect(302, '/admin');\n }\n throw redirect(302, '/login');\n};\n",
67
73
  "/routes/setup/+page.server.ts": "import { dev } from '$app/environment';\nimport { fail, redirect } from '@sveltejs/kit';\nimport { auth } from '$lib/server/auth';\nimport type { Actions } from './$types';\n\nexport const actions: Actions = {\n default: async ({ request }) => {\n if (!dev) {\n throw redirect(302, '/login');\n }\n\n const data = await request.formData();\n const name = data.get('name') as string;\n const email = data.get('email') as string;\n const password = data.get('password') as string;\n\n if (!name || !email || !password) {\n return fail(400, { error: 'All fields are required' });\n }\n\n if (password.length < 8) {\n return fail(400, { error: 'Password must be at least 8 characters' });\n }\n\n try {\n await auth.api.signUpEmail({\n body: { name, email, password }\n });\n } catch (e: unknown) {\n const message = e instanceof Error ? e.message : 'Failed to create admin';\n return fail(400, { error: message });\n }\n\n throw redirect(302, '/login');\n }\n};\n",
68
- "/routes/setup/+page.svelte": "<script lang=\"ts\">\n import { Button, Input, Card } from '$lib/components/ui';\n import ThemeToggle from '$lib/components/ui/ThemeToggle.svelte';\n import { enhance } from '$app/forms';\n import type { ActionData } from './$types';\n\n let { form }: { form: ActionData } = $props();\n<\/script>\n\n<svelte:head>\n <title>Setup — SvelteForge</title>\n</svelte:head>\n\n<main class=\"min-h-screen flex items-center justify-center p-4\">\n <div class=\"w-full max-w-sm space-y-6\">\n <div class=\"text-center space-y-2\">\n <h1 class=\"text-2xl font-heading font-bold\">Create Admin</h1>\n <p class=\"text-surface-500 text-sm\">First user becomes admin automatically.</p>\n </div>\n\n <Card>\n <form method=\"POST\" class=\"space-y-4\" use:enhance>\n {#if form?.error}\n <p class=\"text-error-500 text-sm\">{form.error}</p>\n {/if}\n\n <Input name=\"name\" label=\"Name\" placeholder=\"Admin\" required />\n <Input name=\"email\" label=\"Email\" type=\"email\" placeholder=\"admin@example.com\" required />\n <Input name=\"password\" label=\"Password\" type=\"password\" placeholder=\"Min. 8 characters\" required />\n <Button type=\"submit\" class=\"w-full\">Create & Continue</Button>\n </form>\n </Card>\n\n <div class=\"flex justify-center\">\n <ThemeToggle />\n </div>\n\n <p class=\"text-center text-xs text-surface-500\">\n This page is only available in development mode.\n </p>\n </div>\n</main>\n",
74
+ "/routes/setup/+page.svelte": "<script lang=\"ts\">\n import { Button, Input, Card } from '$lib/components/svforge/ui';\n import ThemeToggle from '$lib/components/svforge/ui/ThemeToggle.svelte';\n import { enhance } from '$app/forms';\n import type { ActionData } from './$types';\n\n let { form }: { form: ActionData } = $props();\n<\/script>\n\n<svelte:head>\n <title>Setup — SvelteForge</title>\n</svelte:head>\n\n<main class=\"min-h-screen flex items-center justify-center p-4\">\n <div class=\"w-full max-w-sm space-y-6\">\n <div class=\"text-center space-y-2\">\n <h1 class=\"text-2xl font-heading font-bold\">Create Admin</h1>\n <p class=\"text-surface-500 text-sm\">First user becomes admin automatically.</p>\n </div>\n\n <Card>\n <form method=\"POST\" class=\"space-y-4\" use:enhance>\n {#if form?.error}\n <p class=\"text-error-500 text-sm\">{form.error}</p>\n {/if}\n\n <Input name=\"name\" label=\"Name\" placeholder=\"Admin\" required />\n <Input name=\"email\" label=\"Email\" type=\"email\" placeholder=\"admin@example.com\" required />\n <Input name=\"password\" label=\"Password\" type=\"password\" placeholder=\"Min. 8 characters\" required />\n <Button type=\"submit\" class=\"w-full\">Create & Continue</Button>\n </form>\n </Card>\n\n <div class=\"flex justify-center\">\n <ThemeToggle />\n </div>\n\n <p class=\"text-center text-xs text-surface-500\">\n This page is only available in development mode.\n </p>\n </div>\n</main>\n",
69
75
  "/routes/login/+page.server.ts": "import { redirect } from '@sveltejs/kit';\nimport { auth } from '$lib/server/auth';\nimport { fail, type Actions } from '@sveltejs/kit';\nimport type { PageServerLoad } from './$types';\n\nexport const load: PageServerLoad = async ({ locals }) => {\n if (locals.session) {\n throw redirect(302, '/admin');\n }\n};\n\nexport const actions: Actions = {\n default: async ({ request }) => {\n const formData = await request.formData();\n const email = formData.get('email')?.toString();\n const password = formData.get('password')?.toString();\n\n if (!email || !password) {\n return fail(400, { message: 'Email and password are required' });\n }\n\n try {\n await auth.api.signInEmail({\n body: { email, password },\n headers: request.headers\n });\n return { success: true };\n } catch (e: any) {\n return fail(401, { message: e.message || 'Invalid credentials' });\n }\n }\n};\n",
70
- "/routes/login/+page.svelte": "<script lang=\"ts\">\n import { enhance } from '$app/forms';\n import { goto } from '$app/navigation';\n import { Button, Input, Card } from '$lib/components/ui';\n import ThemeToggle from '$lib/components/ui/ThemeToggle.svelte';\n import type { ActionData } from './$types';\n\n let { form }: { form: ActionData } = $props();\n let email = $state('');\n let password = $state('');\n let loading = $state(false);\n<\/script>\n\n<svelte:head>\n <title>Login — SvelteForge</title>\n</svelte:head>\n\n<main class=\"min-h-screen flex items-center justify-center bg-surface-50 dark:bg-surface-950 p-element\">\n <div class=\"w-full max-w-modal\">\n <div class=\"flex justify-end mb-4\">\n <ThemeToggle />\n </div>\n\n <Card variant=\"elevated\">\n <div class=\"text-center mb-6\">\n <h1 class=\"text-2xl font-heading font-bold\">Welcome back</h1>\n <p class=\"text-surface-500 mt-1\">Sign in to your account</p>\n </div>\n\n {#if form?.message}\n <div class=\"alert alert-error mb-4\">{form.message}</div>\n {/if}\n\n <form method=\"POST\" use:enhance={() => {\n loading = true;\n return async ({ update }) => {\n const result = await update({ reset: false });\n loading = false;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n if ((result as any)?.type === 'success') {\n const callbackURL = new URLSearchParams(window.location.search).get('callbackURL');\n goto(callbackURL || '/admin');\n }\n };\n }} class=\"space-y-4\">\n <Input label=\"Email\" type=\"email\" name=\"email\" bind:value={email} placeholder=\"you@example.com\" required />\n <Input label=\"Password\" type=\"password\" name=\"password\" bind:value={password} placeholder=\"••••••••\" required />\n <Button type=\"submit\" class=\"w-full\" disabled={loading}>\n {loading ? 'Signing in...' : 'Sign In'}\n </Button>\n </form>\n </Card>\n </div>\n</main>\n",
76
+ "/routes/login/+page.svelte": "<script lang=\"ts\">\n import { enhance } from '$app/forms';\n import { goto } from '$app/navigation';\n import { Button, Input, Card } from '$lib/components/svforge/ui';\n import ThemeToggle from '$lib/components/svforge/ui/ThemeToggle.svelte';\n import type { ActionData } from './$types';\n\n let { form }: { form: ActionData } = $props();\n let email = $state('');\n let password = $state('');\n let loading = $state(false);\n<\/script>\n\n<svelte:head>\n <title>Login — SvelteForge</title>\n</svelte:head>\n\n<main class=\"min-h-screen flex items-center justify-center bg-surface-50 dark:bg-surface-950 p-element\">\n <div class=\"w-full max-w-modal\">\n <div class=\"flex justify-end mb-4\">\n <ThemeToggle />\n </div>\n\n <Card variant=\"elevated\">\n <div class=\"text-center mb-6\">\n <h1 class=\"text-2xl font-heading font-bold\">Welcome back</h1>\n <p class=\"text-surface-500 mt-1\">Sign in to your account</p>\n </div>\n\n {#if form?.message}\n <div class=\"alert alert-error mb-4\">{form.message}</div>\n {/if}\n\n <form method=\"POST\" use:enhance={() => {\n loading = true;\n return async ({ update }) => {\n const result = await update({ reset: false });\n loading = false;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n if ((result as any)?.type === 'success') {\n const callbackURL = new URLSearchParams(window.location.search).get('callbackURL');\n goto(callbackURL || '/admin');\n }\n };\n }} class=\"space-y-4\">\n <Input label=\"Email\" type=\"email\" name=\"email\" bind:value={email} placeholder=\"you@example.com\" required />\n <Input label=\"Password\" type=\"password\" name=\"password\" bind:value={password} placeholder=\"••••••••\" required />\n <Button type=\"submit\" class=\"w-full\" disabled={loading}>\n {loading ? 'Signing in...' : 'Sign In'}\n </Button>\n </form>\n </Card>\n </div>\n</main>\n",
71
77
  "/routes/(app)/+layout.svelte": "<script lang=\"ts\">\n import { page } from '$app/stores';\n import { goto } from '$app/navigation';\n import AdminLayout from '$lib/components/layout/AdminLayout.svelte';\n import { authClient } from '$lib/client/auth';\n\n let { data, children } = $props();\n\n async function handleSignOut() {\n await authClient.signOut();\n goto('/login');\n }\n<\/script>\n\n<AdminLayout currentPath={$page.url.pathname} user={data.user} onSignOut={handleSignOut}>\n {@render children()}\n</AdminLayout>\n",
72
78
  "/routes/(app)/+layout.server.ts": "import { redirect } from '@sveltejs/kit';\nimport type { LayoutServerLoad } from './$types';\n\n// Auth guard — redirects to /login if no session.\n// This template uses single-admin pattern (first user is admin).\n// For multi-user, add a role column and check here.\nexport const load: LayoutServerLoad = async ({ locals, url }) => {\n if (!locals.session || !locals.user) {\n throw redirect(302, `/login?callbackURL=${encodeURIComponent(url.pathname)}`);\n }\n\n return {\n user: locals.user,\n session: locals.session\n };\n};\n",
73
79
  "/routes/(app)/admin/+page.server.ts": "import { redirect } from '@sveltejs/kit';\nimport { db } from '$lib/server/db';\nimport { user, session } from '$lib/server/db/schema';\nimport { sql, desc, asc } from 'drizzle-orm';\nimport { isAdmin } from '$lib/server/admin';\nimport type { PageServerLoad } from './$types';\n\nexport const load: PageServerLoad = async ({ locals }) => {\n if (!locals.user || !(await isAdmin(locals.user.id))) {\n throw redirect(302, '/login');\n }\n\n const [totalUsersResult] = await db.select({ count: sql<number>`count(*)` }).from(user);\n const [activeSessionsResult] = await db.select({ count: sql<number>`count(*)` }).from(session).where(sql`expires_at > ${Date.now()}`);\n const [newThisWeekResult] = await db.select({ count: sql<number>`count(*)` }).from(user).where(sql`created_at > ${Date.now() - 7 * 24 * 60 * 60 * 1000}`);\n\n const recentUsers = await db.select({\n id: user.id,\n name: user.name,\n email: user.email,\n emailVerified: user.emailVerified,\n createdAt: user.createdAt\n }).from(user).orderBy(desc(user.createdAt)).limit(5);\n\n return {\n user: locals.user,\n stats: {\n totalUsers: totalUsersResult?.count ?? 0,\n activeSessions: activeSessionsResult?.count ?? 0,\n newThisWeek: newThisWeekResult?.count ?? 0\n },\n recentUsers\n };\n};\n",
74
- "/routes/(app)/admin/+page.svelte": "<script lang=\"ts\">\n import { Card, Badge, Button, AvatarInitial } from '$lib/components/ui';\n import Users from 'phosphor-svelte/lib/Users';\n import ChartBar from 'phosphor-svelte/lib/ChartBar';\n import Clock from 'phosphor-svelte/lib/Clock';\n\n let { data } = $props();\n<\/script>\n\n<svelte:head>\n <title>Dashboard — SvelteForge</title>\n</svelte:head>\n\n<div class=\"space-y-section\">\n <div class=\"flex items-center justify-between\">\n <div>\n <h2 class=\"text-2xl font-heading font-bold\">Dashboard</h2>\n <p class=\"text-surface-500\">Welcome back, {data.user.name}</p>\n </div>\n <Button href=\"/admin/users\" size=\"sm\">\n <Users size={16} class=\"mr-1\" />\n Manage Users\n </Button>\n </div>\n\n <!-- Stats -->\n <div class=\"grid grid-cols-1 sm:grid-cols-3 gap-group\">\n <Card variant=\"elevated\">\n <div class=\"flex items-center gap-3\">\n <div class=\"p-3 rounded-card bg-primary-100 dark:bg-primary-900\">\n <Users size={24} class=\"text-primary-600 dark:text-primary-400\" />\n </div>\n <div>\n <p class=\"text-sm text-surface-500\">Total Users</p>\n <p class=\"text-2xl font-heading font-bold\">{data.stats.totalUsers}</p>\n </div>\n </div>\n </Card>\n <Card variant=\"elevated\">\n <div class=\"flex items-center gap-3\">\n <div class=\"p-3 rounded-card bg-success-100 dark:bg-success-900\">\n <Clock size={24} class=\"text-success-600 dark:text-success-400\" />\n </div>\n <div>\n <p class=\"text-sm text-surface-500\">Active Sessions</p>\n <p class=\"text-2xl font-heading font-bold\">{data.stats.activeSessions}</p>\n </div>\n </div>\n </Card>\n <Card variant=\"elevated\">\n <div class=\"flex items-center gap-3\">\n <div class=\"p-3 rounded-card bg-secondary-100 dark:bg-secondary-900\">\n <ChartBar size={24} class=\"text-secondary-600 dark:text-secondary-400\" />\n </div>\n <div>\n <p class=\"text-sm text-surface-500\">This Week</p>\n <p class=\"text-2xl font-heading font-bold\">{data.stats.newThisWeek}</p>\n </div>\n </div>\n </Card>\n </div>\n\n <!-- Recent Users -->\n <Card>\n {#snippet header()}\n <h3 class=\"font-heading font-bold\">Recent Users</h3>\n {/snippet}\n\n <div class=\"space-y-3\">\n {#each data.recentUsers as u}\n <div class=\"flex items-center justify-between py-2 border-b border-surface-100 dark:border-surface-800 last:border-0\">\n <div class=\"flex items-center gap-3\">\n <AvatarInitial name={u.name} />\n <div>\n <p class=\"font-medium text-sm\">{u.name}</p>\n <p class=\"text-xs text-surface-500\">{u.email}</p>\n </div>\n </div>\n <Badge color={u.emailVerified ? 'success' : 'warning'}>\n {u.emailVerified ? 'Verified' : 'Pending'}\n </Badge>\n </div>\n {/each}\n </div>\n </Card>\n</div>\n",
80
+ "/routes/(app)/admin/+page.svelte": "<script lang=\"ts\">\n import { Card, Badge, Button, AvatarInitial } from '$lib/components/svforge/ui';\n import Users from 'phosphor-svelte/lib/Users';\n import ChartBar from 'phosphor-svelte/lib/ChartBar';\n import Clock from 'phosphor-svelte/lib/Clock';\n\n let { data } = $props();\n<\/script>\n\n<svelte:head>\n <title>Dashboard — SvelteForge</title>\n</svelte:head>\n\n<div class=\"space-y-section\">\n <div class=\"flex items-center justify-between\">\n <div>\n <h2 class=\"text-2xl font-heading font-bold\">Dashboard</h2>\n <p class=\"text-surface-500\">Welcome back, {data.user.name}</p>\n </div>\n <Button href=\"/admin/users\" size=\"sm\">\n <Users size={16} class=\"mr-1\" />\n Manage Users\n </Button>\n </div>\n\n <!-- Stats -->\n <div class=\"grid grid-cols-1 sm:grid-cols-3 gap-group\">\n <Card variant=\"elevated\">\n <div class=\"flex items-center gap-3\">\n <div class=\"p-3 rounded-card bg-primary-100 dark:bg-primary-900\">\n <Users size={24} class=\"text-primary-600 dark:text-primary-400\" />\n </div>\n <div>\n <p class=\"text-sm text-surface-500\">Total Users</p>\n <p class=\"text-2xl font-heading font-bold\">{data.stats.totalUsers}</p>\n </div>\n </div>\n </Card>\n <Card variant=\"elevated\">\n <div class=\"flex items-center gap-3\">\n <div class=\"p-3 rounded-card bg-success-100 dark:bg-success-900\">\n <Clock size={24} class=\"text-success-600 dark:text-success-400\" />\n </div>\n <div>\n <p class=\"text-sm text-surface-500\">Active Sessions</p>\n <p class=\"text-2xl font-heading font-bold\">{data.stats.activeSessions}</p>\n </div>\n </div>\n </Card>\n <Card variant=\"elevated\">\n <div class=\"flex items-center gap-3\">\n <div class=\"p-3 rounded-card bg-secondary-100 dark:bg-secondary-900\">\n <ChartBar size={24} class=\"text-secondary-600 dark:text-secondary-400\" />\n </div>\n <div>\n <p class=\"text-sm text-surface-500\">This Week</p>\n <p class=\"text-2xl font-heading font-bold\">{data.stats.newThisWeek}</p>\n </div>\n </div>\n </Card>\n </div>\n\n <!-- Recent Users -->\n <Card>\n {#snippet header()}\n <h3 class=\"font-heading font-bold\">Recent Users</h3>\n {/snippet}\n\n <div class=\"space-y-3\">\n {#each data.recentUsers as u}\n <div class=\"flex items-center justify-between py-2 border-b border-surface-100 dark:border-surface-800 last:border-0\">\n <div class=\"flex items-center gap-3\">\n <AvatarInitial name={u.name} />\n <div>\n <p class=\"font-medium text-sm\">{u.name}</p>\n <p class=\"text-xs text-surface-500\">{u.email}</p>\n </div>\n </div>\n <Badge color={u.emailVerified ? 'success' : 'warning'}>\n {u.emailVerified ? 'Verified' : 'Pending'}\n </Badge>\n </div>\n {/each}\n </div>\n </Card>\n</div>\n",
75
81
  "/routes/(app)/admin/settings/+page.server.ts": "import { auth } from '$lib/server/auth';\nimport { fail, type Actions } from '@sveltejs/kit';\nimport type { PageServerLoad } from './$types';\n\nexport const load: PageServerLoad = async ({ locals }) => {\n return {\n user: locals.user!\n };\n};\n\nexport const actions: Actions = {\n changePassword: async ({ request }) => {\n const formData = await request.formData();\n const currentPassword = formData.get('currentPassword')?.toString();\n const newPassword = formData.get('newPassword')?.toString();\n\n if (!currentPassword || !newPassword) {\n return fail(400, { message: 'Both passwords are required' });\n }\n\n try {\n await auth.api.changePassword({\n headers: request.headers,\n body: { currentPassword, newPassword }\n });\n return { success: true };\n } catch (e: any) {\n return fail(400, { message: e.message || 'Failed to change password' });\n }\n }\n};\n",
76
- "/routes/(app)/admin/settings/+page.svelte": "<script lang=\"ts\">\n import { enhance } from '$app/forms';\n import { Card, Input, Button, AvatarInitial, Feedback } from '$lib/components/ui';\n import Lock from 'phosphor-svelte/lib/Lock';\n import type { ActionData } from './$types';\n\n let { data, form }: { data: any, form: ActionData } = $props();\n let currentPassword = $state('');\n let newPassword = $state('');\n let confirmPassword = $state('');\n\n // Client-side validation\n let validationError = $state('');\n\n $effect(() => {\n if (form?.message) validationError = '';\n });\n<\/script>\n\n<svelte:head>\n <title>Settings — SvelteForge Admin</title>\n</svelte:head>\n\n<div class=\"space-y-section max-w-2xl\">\n <h2 class=\"text-2xl font-heading font-bold\">Settings</h2>\n\n <!-- Profile info -->\n <Card>\n {#snippet header()}\n <h3 class=\"font-heading font-bold\">Profile</h3>\n {/snippet}\n <div class=\"space-y-3\">\n <div class=\"flex items-center gap-4\">\n <AvatarInitial name={data.user.name} size=\"lg\" />\n <div>\n <p class=\"font-medium\">{data.user.name}</p>\n <p class=\"text-sm text-surface-500\">{data.user.email}</p>\n </div>\n </div>\n </div>\n </Card>\n\n <!-- Change password -->\n <Card>\n {#snippet header()}\n <div class=\"flex items-center gap-2\">\n <Lock size={18} />\n <h3 class=\"font-heading font-bold\">Change Password</h3>\n </div>\n {/snippet}\n\n {#if form?.message}\n <Feedback type={form.success ? 'success' : 'error'} message={form.message} class=\"mb-4\" />\n {/if}\n {#if validationError}\n <div class=\"mb-4 p-3 rounded-card text-sm bg-error-100 dark:bg-error-900 text-error-700 dark:text-error-300\">\n {validationError}\n </div>\n {/if}\n\n <form method=\"POST\" action=\"?/changePassword\" use:enhance class=\"space-y-4\">\n <Input label=\"Current Password\" type=\"password\" name=\"currentPassword\" bind:value={currentPassword} required />\n <Input label=\"New Password\" type=\"password\" name=\"newPassword\" bind:value={newPassword} placeholder=\"Min 8 characters\" required />\n <Input label=\"Confirm New Password\" type=\"password\" name=\"confirmPassword\" bind:value={confirmPassword} required />\n <div class=\"flex justify-end\">\n <Button type=\"submit\">Update Password</Button>\n </div>\n </form>\n </Card>\n</div>\n",
82
+ "/routes/(app)/admin/settings/+page.svelte": "<script lang=\"ts\">\n import { enhance } from '$app/forms';\n import { Card, Input, Button, AvatarInitial, Feedback } from '$lib/components/svforge/ui';\n import Lock from 'phosphor-svelte/lib/Lock';\n import type { ActionData } from './$types';\n\n let { data, form }: { data: any, form: ActionData } = $props();\n let currentPassword = $state('');\n let newPassword = $state('');\n let confirmPassword = $state('');\n\n // Client-side validation\n let validationError = $state('');\n\n $effect(() => {\n if (form?.message) validationError = '';\n });\n<\/script>\n\n<svelte:head>\n <title>Settings — SvelteForge Admin</title>\n</svelte:head>\n\n<div class=\"space-y-section max-w-2xl\">\n <h2 class=\"text-2xl font-heading font-bold\">Settings</h2>\n\n <!-- Profile info -->\n <Card>\n {#snippet header()}\n <h3 class=\"font-heading font-bold\">Profile</h3>\n {/snippet}\n <div class=\"space-y-3\">\n <div class=\"flex items-center gap-4\">\n <AvatarInitial name={data.user.name} size=\"lg\" />\n <div>\n <p class=\"font-medium\">{data.user.name}</p>\n <p class=\"text-sm text-surface-500\">{data.user.email}</p>\n </div>\n </div>\n </div>\n </Card>\n\n <!-- Change password -->\n <Card>\n {#snippet header()}\n <div class=\"flex items-center gap-2\">\n <Lock size={18} />\n <h3 class=\"font-heading font-bold\">Change Password</h3>\n </div>\n {/snippet}\n\n {#if form?.message}\n <Feedback type={form.success ? 'success' : 'error'} message={form.message} class=\"mb-4\" />\n {/if}\n {#if validationError}\n <div class=\"mb-4 p-3 rounded-card text-sm bg-error-100 dark:bg-error-900 text-error-700 dark:text-error-300\">\n {validationError}\n </div>\n {/if}\n\n <form method=\"POST\" action=\"?/changePassword\" use:enhance class=\"space-y-4\">\n <Input label=\"Current Password\" type=\"password\" name=\"currentPassword\" bind:value={currentPassword} required />\n <Input label=\"New Password\" type=\"password\" name=\"newPassword\" bind:value={newPassword} placeholder=\"Min 8 characters\" required />\n <Input label=\"Confirm New Password\" type=\"password\" name=\"confirmPassword\" bind:value={confirmPassword} required />\n <div class=\"flex justify-end\">\n <Button type=\"submit\">Update Password</Button>\n </div>\n </form>\n </Card>\n</div>\n",
77
83
  "/routes/(app)/admin/users/+page.server.ts": "import { db } from '$lib/server/db';\nimport { user, account, session } from '$lib/server/db/schema';\nimport { desc, eq } from 'drizzle-orm';\nimport { fail, type Actions } from '@sveltejs/kit';\nimport { hashPassword } from 'better-auth/crypto';\nimport { isAdmin } from '$lib/server/admin';\nimport { redirect } from '@sveltejs/kit';\nimport type { PageServerLoad } from './$types';\n\nexport const load: PageServerLoad = async ({ locals }) => {\n if (!locals.user || !(await isAdmin(locals.user.id))) {\n throw redirect(302, '/login');\n }\n\n const users = await db.select({\n id: user.id,\n name: user.name,\n email: user.email,\n emailVerified: user.emailVerified,\n image: user.image,\n createdAt: user.createdAt\n }).from(user).orderBy(desc(user.createdAt));\n\n return { users };\n};\n\nexport const actions: Actions = {\n create: async ({ request }) => {\n const formData = await request.formData();\n const name = formData.get('name')?.toString()?.trim();\n const email = formData.get('email')?.toString()?.trim();\n const password = formData.get('password')?.toString();\n\n if (!name || !email || !password) {\n return fail(400, { message: 'Name, email and password are required' });\n }\n\n if (password.length < 8) {\n return fail(400, { message: 'Password must be at least 8 characters' });\n }\n\n // Check duplicate email\n const [existing] = await db.select({ id: user.id }).from(user).where(eq(user.email, email)).limit(1);\n if (existing) {\n return fail(400, { message: 'Email already exists' });\n }\n\n // Direct DB insert to avoid signUpEmail session hijack\n // (signUpEmail creates a session cookie that overwrites admin's session)\n try {\n const hashedPassword = await hashPassword(password);\n const userId = crypto.randomUUID();\n\n await db.insert(user).values({\n id: userId,\n name,\n email,\n emailVerified: false,\n createdAt: new Date(),\n updatedAt: new Date()\n });\n\n await db.insert(account).values({\n id: crypto.randomUUID(),\n userId,\n accountId: email,\n providerId: 'credential',\n password: hashedPassword,\n createdAt: new Date(),\n updatedAt: new Date()\n });\n } catch (e: any) {\n return fail(500, { message: e.message || 'Failed to create user' });\n }\n\n return { success: true, message: `User ${name} created` };\n },\n\n update: async ({ request }) => {\n const formData = await request.formData();\n const id = formData.get('id')?.toString();\n const name = formData.get('name')?.toString()?.trim();\n const email = formData.get('email')?.toString()?.trim();\n\n if (!id || !name || !email) {\n return fail(400, { message: 'ID, name and email are required' });\n }\n\n // Check email not taken by another user\n const [existing] = await db.select({ id: user.id }).from(user).where(eq(user.email, email)).limit(1);\n if (existing && existing.id !== id) {\n return fail(400, { message: 'Email already taken by another user' });\n }\n\n await db.update(user).set({\n name,\n email,\n updatedAt: new Date()\n }).where(eq(user.id, id));\n\n return { success: true, message: `User ${name} updated` };\n },\n\n delete: async ({ request, locals }) => {\n const formData = await request.formData();\n const id = formData.get('id')?.toString();\n\n if (!id) {\n return fail(400, { message: 'User ID is required' });\n }\n\n // Prevent self-delete\n if (id === locals.user?.id) {\n return fail(400, { message: 'You cannot delete your own account' });\n }\n\n // Delete sessions first (avoid orphan sessions)\n await db.delete(session).where(eq(session.userId, id));\n // Delete accounts\n await db.delete(account).where(eq(account.userId, id));\n // Delete user\n await db.delete(user).where(eq(user.id, id));\n\n return { success: true, message: 'User deleted' };\n },\n\n toggleVerify: async ({ request }) => {\n const formData = await request.formData();\n const id = formData.get('id')?.toString();\n const verified = formData.get('verified')?.toString() === 'true';\n\n if (!id) {\n return fail(400, { message: 'User ID is required' });\n }\n\n await db.update(user).set({\n emailVerified: !verified,\n updatedAt: new Date()\n }).where(eq(user.id, id));\n\n return { success: true, message: `Email ${!verified ? 'verified' : 'unverified'}` };\n }\n};\n",
78
- "/routes/(app)/admin/users/+page.svelte": "<script lang=\"ts\">\n import { invalidateAll } from '$app/navigation';\n import { page } from '$app/stores';\n import { Button, Badge, Input, Card, AvatarInitial, Feedback } from '$lib/components/ui';\n import UserPlus from 'phosphor-svelte/lib/UserPlus';\n import Trash from 'phosphor-svelte/lib/Trash';\n import Pencil from 'phosphor-svelte/lib/Pencil';\n import X from 'phosphor-svelte/lib/X';\n\n let { data } = $props();\n let currentUserId = $derived($page.data.user?.id);\n\n let search = $state('');\n let feedback = $state<{ type: 'success' | 'error'; message: string } | null>(null);\n\n // Modal state\n let modal = $state<'create' | 'edit' | 'delete' | null>(null);\n let editUser = $state<{ id: string; name: string; email: string } | null>(null);\n let deleteTarget = $state<{ id: string; name: string } | null>(null);\n\n // Form fields\n let formName = $state('');\n let formEmail = $state('');\n let formPassword = $state('');\n\n let filtered = $derived(\n data.users.filter((u: any) =>\n u.name.toLowerCase().includes(search.toLowerCase()) ||\n u.email.toLowerCase().includes(search.toLowerCase())\n )\n );\n\n function openCreate() {\n formName = '';\n formEmail = '';\n formPassword = '';\n modal = 'create';\n }\n\n function openEdit(u: any) {\n editUser = { id: u.id, name: u.name, email: u.email };\n formName = u.name;\n formEmail = u.email;\n modal = 'edit';\n }\n\n function openDelete(u: any) {\n deleteTarget = { id: u.id, name: u.name };\n modal = 'delete';\n }\n\n function closeModal() {\n modal = null;\n editUser = null;\n deleteTarget = null;\n }\n\n async function submitCreate() {\n const formData = new FormData();\n formData.set('name', formName);\n formData.set('email', formEmail);\n formData.set('password', formPassword);\n\n const res = await fetch('?/create', { method: 'POST', body: formData });\n const result = await res.json();\n if (result.type === 'success') {\n feedback = { type: 'success', message: result.message || 'User created' };\n closeModal();\n invalidate();\n } else {\n feedback = { type: 'error', message: result.message || 'Failed to create user' };\n }\n }\n\n async function submitEdit() {\n if (!editUser) return;\n const formData = new FormData();\n formData.set('id', editUser.id);\n formData.set('name', formName);\n formData.set('email', formEmail);\n\n const res = await fetch('?/update', { method: 'POST', body: formData });\n const result = await res.json();\n if (result.type === 'success') {\n feedback = { type: 'success', message: result.message || 'User updated' };\n closeModal();\n invalidate();\n } else {\n feedback = { type: 'error', message: result.message || 'Failed to update user' };\n }\n }\n\n async function submitDelete() {\n if (!deleteTarget) return;\n const formData = new FormData();\n formData.set('id', deleteTarget.id);\n\n const res = await fetch('?/delete', { method: 'POST', body: formData });\n const result = await res.json();\n if (result.type === 'success') {\n feedback = { type: 'success', message: result.message || 'User deleted' };\n closeModal();\n invalidate();\n } else {\n feedback = { type: 'error', message: result.message || 'Failed to delete user' };\n }\n }\n\n async function toggleVerify(u: any) {\n const formData = new FormData();\n formData.set('id', u.id);\n formData.set('verified', String(u.emailVerified));\n\n const res = await fetch('?/toggleVerify', { method: 'POST', body: formData });\n const result = await res.json();\n if (result.type === 'success') {\n feedback = { type: 'success', message: result.message };\n invalidate();\n }\n }\n\n async function invalidate() {\n await invalidateAll();\n }\n<\/script>\n\n<svelte:head>\n <title>Users — SvelteForge Admin</title>\n</svelte:head>\n\n<div class=\"space-y-group\">\n <div class=\"flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3\">\n <h2 class=\"text-2xl font-heading font-bold\">Users</h2>\n <Button onclick={openCreate}>\n <UserPlus size={16} class=\"mr-1\" />\n Add User\n </Button>\n </div>\n\n {#if feedback}\n <Feedback type={feedback.type} message={feedback.message} ondismiss={() => (feedback = null)} />\n {/if}\n\n <Input placeholder=\"Search users...\" bind:value={search} />\n\n <!-- Users table -->\n <div class=\"overflow-x-auto rounded-card border border-surface-200 dark:border-surface-800\">\n <table class=\"w-full text-sm\">\n <thead class=\"bg-surface-100 dark:bg-surface-900\">\n <tr>\n <th class=\"text-left px-4 py-3 font-medium\">Name</th>\n <th class=\"text-left px-4 py-3 font-medium hidden sm:table-cell\">Email</th>\n <th class=\"text-left px-4 py-3 font-medium\">Status</th>\n <th class=\"text-right px-4 py-3 font-medium\">Actions</th>\n </tr>\n </thead>\n <tbody class=\"divide-y divide-surface-100 dark:divide-surface-800\">\n {#each filtered as u}\n <tr class=\"hover:bg-surface-50 dark:hover:bg-surface-900 transition-colors\">\n <td class=\"px-4 py-3\">\n <div class=\"flex items-center gap-3\">\n <AvatarInitial name={u.name} size=\"sm\" />\n <div>\n <p class=\"font-medium\">{u.name}</p>\n <p class=\"text-xs text-surface-500 sm:hidden\">{u.email}</p>\n </div>\n </div>\n </td>\n <td class=\"px-4 py-3 hidden sm:table-cell text-surface-500\">{u.email}</td>\n <td class=\"px-4 py-3\">\n <button onclick={() => toggleVerify(u)}>\n <Badge color={u.emailVerified ? 'success' : 'warning'}>\n {u.emailVerified ? '✅ Verified' : '⏳ Pending'}\n </Badge>\n </button>\n </td>\n <td class=\"px-4 py-3\">\n <div class=\"flex items-center justify-end gap-1\">\n <button class=\"btn preset-ghost variant-surface p-2 rounded\" onclick={() => openEdit(u)} aria-label=\"Edit user\">\n <Pencil size={16} />\n </button>\n <button class=\"btn preset-ghost variant-error p-2 rounded\" onclick={() => openDelete(u)} disabled={u.id === currentUserId} aria-label=\"Delete user\">\n <Trash size={16} />\n </button>\n </div>\n </td>\n </tr>\n {/each}\n </tbody>\n </table>\n </div>\n\n {#if filtered.length === 0}\n <p class=\"text-center text-surface-500 py-section\">No users found</p>\n {/if}\n</div>\n\n<!-- Modal overlay -->\n{#if modal}\n <div class=\"fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-element\" onclick={closeModal}>\n <Card class=\"w-full max-w-modal\" onclick={(e: Event) => e.stopPropagation()}>\n <div class=\"flex items-center justify-between mb-4\">\n <h3 class=\"text-lg font-heading font-bold\">\n {modal === 'create' ? 'Add User' : modal === 'edit' ? 'Edit User' : 'Delete User'}\n </h3>\n <button class=\"btn preset-ghost variant-surface p-1 rounded\" onclick={closeModal} aria-label=\"Close\">\n <X size={18} />\n </button>\n </div>\n\n {#if modal === 'create' || modal === 'edit'}\n <form class=\"space-y-4\" onsubmit={(e) => { e.preventDefault(); modal === 'create' ? submitCreate() : submitEdit(); }}>\n <Input label=\"Name\" bind:value={formName} placeholder=\"John Doe\" required />\n <Input label=\"Email\" type=\"email\" bind:value={formEmail} placeholder=\"john@example.com\" required />\n {#if modal === 'create'}\n <Input label=\"Password\" type=\"password\" bind:value={formPassword} placeholder=\"Min 8 characters\" required />\n {/if}\n <div class=\"flex justify-end gap-2 pt-2\">\n <Button variant=\"ghost\" onclick={closeModal}>Cancel</Button>\n <Button type=\"submit\">{modal === 'create' ? 'Create' : 'Save'}</Button>\n </div>\n </form>\n {:else if modal === 'delete' && deleteTarget}\n <p class=\"text-surface-500 mb-4\">\n Are you sure you want to delete <strong>{deleteTarget.name}</strong>? This action cannot be undone.\n </p>\n <div class=\"flex justify-end gap-2\">\n <Button variant=\"ghost\" onclick={closeModal}>Cancel</Button>\n <Button color=\"error\" onclick={submitDelete}>Delete</Button>\n </div>\n {/if}\n </Card>\n </div>\n{/if}\n",
84
+ "/routes/(app)/admin/users/+page.svelte": "<script lang=\"ts\">\n import { invalidateAll } from '$app/navigation';\n import { page } from '$app/stores';\n import { Button, Badge, Input, Card, AvatarInitial, Feedback } from '$lib/components/svforge/ui';\n import UserPlus from 'phosphor-svelte/lib/UserPlus';\n import Trash from 'phosphor-svelte/lib/Trash';\n import Pencil from 'phosphor-svelte/lib/Pencil';\n import X from 'phosphor-svelte/lib/X';\n\n let { data } = $props();\n let currentUserId = $derived($page.data.user?.id);\n\n let search = $state('');\n let feedback = $state<{ type: 'success' | 'error'; message: string } | null>(null);\n\n // Modal state\n let modal = $state<'create' | 'edit' | 'delete' | null>(null);\n let editUser = $state<{ id: string; name: string; email: string } | null>(null);\n let deleteTarget = $state<{ id: string; name: string } | null>(null);\n\n // Form fields\n let formName = $state('');\n let formEmail = $state('');\n let formPassword = $state('');\n\n let filtered = $derived(\n data.users.filter((u: any) =>\n u.name.toLowerCase().includes(search.toLowerCase()) ||\n u.email.toLowerCase().includes(search.toLowerCase())\n )\n );\n\n function openCreate() {\n formName = '';\n formEmail = '';\n formPassword = '';\n modal = 'create';\n }\n\n function openEdit(u: any) {\n editUser = { id: u.id, name: u.name, email: u.email };\n formName = u.name;\n formEmail = u.email;\n modal = 'edit';\n }\n\n function openDelete(u: any) {\n deleteTarget = { id: u.id, name: u.name };\n modal = 'delete';\n }\n\n function closeModal() {\n modal = null;\n editUser = null;\n deleteTarget = null;\n }\n\n async function submitCreate() {\n const formData = new FormData();\n formData.set('name', formName);\n formData.set('email', formEmail);\n formData.set('password', formPassword);\n\n const res = await fetch('?/create', { method: 'POST', body: formData });\n const result = await res.json();\n if (result.type === 'success') {\n feedback = { type: 'success', message: result.message || 'User created' };\n closeModal();\n invalidate();\n } else {\n feedback = { type: 'error', message: result.message || 'Failed to create user' };\n }\n }\n\n async function submitEdit() {\n if (!editUser) return;\n const formData = new FormData();\n formData.set('id', editUser.id);\n formData.set('name', formName);\n formData.set('email', formEmail);\n\n const res = await fetch('?/update', { method: 'POST', body: formData });\n const result = await res.json();\n if (result.type === 'success') {\n feedback = { type: 'success', message: result.message || 'User updated' };\n closeModal();\n invalidate();\n } else {\n feedback = { type: 'error', message: result.message || 'Failed to update user' };\n }\n }\n\n async function submitDelete() {\n if (!deleteTarget) return;\n const formData = new FormData();\n formData.set('id', deleteTarget.id);\n\n const res = await fetch('?/delete', { method: 'POST', body: formData });\n const result = await res.json();\n if (result.type === 'success') {\n feedback = { type: 'success', message: result.message || 'User deleted' };\n closeModal();\n invalidate();\n } else {\n feedback = { type: 'error', message: result.message || 'Failed to delete user' };\n }\n }\n\n async function toggleVerify(u: any) {\n const formData = new FormData();\n formData.set('id', u.id);\n formData.set('verified', String(u.emailVerified));\n\n const res = await fetch('?/toggleVerify', { method: 'POST', body: formData });\n const result = await res.json();\n if (result.type === 'success') {\n feedback = { type: 'success', message: result.message };\n invalidate();\n }\n }\n\n async function invalidate() {\n await invalidateAll();\n }\n<\/script>\n\n<svelte:head>\n <title>Users — SvelteForge Admin</title>\n</svelte:head>\n\n<div class=\"space-y-group\">\n <div class=\"flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3\">\n <h2 class=\"text-2xl font-heading font-bold\">Users</h2>\n <Button onclick={openCreate}>\n <UserPlus size={16} class=\"mr-1\" />\n Add User\n </Button>\n </div>\n\n {#if feedback}\n <Feedback type={feedback.type} message={feedback.message} ondismiss={() => (feedback = null)} />\n {/if}\n\n <Input placeholder=\"Search users...\" bind:value={search} />\n\n <!-- Users table -->\n <div class=\"overflow-x-auto rounded-card border border-surface-200 dark:border-surface-800\">\n <table class=\"w-full text-sm\">\n <thead class=\"bg-surface-100 dark:bg-surface-900\">\n <tr>\n <th class=\"text-left px-4 py-3 font-medium\">Name</th>\n <th class=\"text-left px-4 py-3 font-medium hidden sm:table-cell\">Email</th>\n <th class=\"text-left px-4 py-3 font-medium\">Status</th>\n <th class=\"text-right px-4 py-3 font-medium\">Actions</th>\n </tr>\n </thead>\n <tbody class=\"divide-y divide-surface-100 dark:divide-surface-800\">\n {#each filtered as u}\n <tr class=\"hover:bg-surface-50 dark:hover:bg-surface-900 transition-colors\">\n <td class=\"px-4 py-3\">\n <div class=\"flex items-center gap-3\">\n <AvatarInitial name={u.name} size=\"sm\" />\n <div>\n <p class=\"font-medium\">{u.name}</p>\n <p class=\"text-xs text-surface-500 sm:hidden\">{u.email}</p>\n </div>\n </div>\n </td>\n <td class=\"px-4 py-3 hidden sm:table-cell text-surface-500\">{u.email}</td>\n <td class=\"px-4 py-3\">\n <button onclick={() => toggleVerify(u)}>\n <Badge color={u.emailVerified ? 'success' : 'warning'}>\n {u.emailVerified ? '✅ Verified' : '⏳ Pending'}\n </Badge>\n </button>\n </td>\n <td class=\"px-4 py-3\">\n <div class=\"flex items-center justify-end gap-1\">\n <button class=\"btn preset-ghost variant-surface p-2 rounded\" onclick={() => openEdit(u)} aria-label=\"Edit user\">\n <Pencil size={16} />\n </button>\n <button class=\"btn preset-ghost variant-error p-2 rounded\" onclick={() => openDelete(u)} disabled={u.id === currentUserId} aria-label=\"Delete user\">\n <Trash size={16} />\n </button>\n </div>\n </td>\n </tr>\n {/each}\n </tbody>\n </table>\n </div>\n\n {#if filtered.length === 0}\n <p class=\"text-center text-surface-500 py-section\">No users found</p>\n {/if}\n</div>\n\n<!-- Modal overlay -->\n{#if modal}\n <div class=\"fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-element\" onclick={closeModal}>\n <Card class=\"w-full max-w-modal\" onclick={(e: Event) => e.stopPropagation()}>\n <div class=\"flex items-center justify-between mb-4\">\n <h3 class=\"text-lg font-heading font-bold\">\n {modal === 'create' ? 'Add User' : modal === 'edit' ? 'Edit User' : 'Delete User'}\n </h3>\n <button class=\"btn preset-ghost variant-surface p-1 rounded\" onclick={closeModal} aria-label=\"Close\">\n <X size={18} />\n </button>\n </div>\n\n {#if modal === 'create' || modal === 'edit'}\n <form class=\"space-y-4\" onsubmit={(e) => { e.preventDefault(); modal === 'create' ? submitCreate() : submitEdit(); }}>\n <Input label=\"Name\" bind:value={formName} placeholder=\"John Doe\" required />\n <Input label=\"Email\" type=\"email\" bind:value={formEmail} placeholder=\"john@example.com\" required />\n {#if modal === 'create'}\n <Input label=\"Password\" type=\"password\" bind:value={formPassword} placeholder=\"Min 8 characters\" required />\n {/if}\n <div class=\"flex justify-end gap-2 pt-2\">\n <Button variant=\"ghost\" onclick={closeModal}>Cancel</Button>\n <Button type=\"submit\">{modal === 'create' ? 'Create' : 'Save'}</Button>\n </div>\n </form>\n {:else if modal === 'delete' && deleteTarget}\n <p class=\"text-surface-500 mb-4\">\n Are you sure you want to delete <strong>{deleteTarget.name}</strong>? This action cannot be undone.\n </p>\n <div class=\"flex justify-end gap-2\">\n <Button variant=\"ghost\" onclick={closeModal}>Cancel</Button>\n <Button color=\"error\" onclick={submitDelete}>Delete</Button>\n </div>\n {/if}\n </Card>\n </div>\n{/if}\n",
79
85
  "/hooks.server.ts": "import type { Handle } from '@sveltejs/kit';\nimport { building } from '$app/environment';\nimport { auth } from '$lib/server/auth';\nimport { svelteKitHandler } from 'better-auth/svelte-kit';\n\nconst handleBetterAuth: Handle = async ({ event, resolve }) => {\n const session = await auth.api.getSession({ headers: event.request.headers });\n\n if (session) {\n event.locals.session = session.session;\n event.locals.user = session.user;\n }\n\n return svelteKitHandler({ event, resolve, auth, building });\n};\n\nexport const handle: Handle = handleBetterAuth;\n",
80
86
  "/lib/client/auth.ts": "import { createAuthClient } from 'better-auth/svelte';\n\nexport const authClient = createAuthClient();\n",
81
87
  "/lib/server/auth.ts": "import { betterAuth } from 'better-auth/minimal';\nimport { drizzleAdapter } from 'better-auth/adapters/drizzle';\nimport { sveltekitCookies } from 'better-auth/svelte-kit';\nimport { env } from '$env/dynamic/private';\nimport { getRequestEvent } from '$app/server';\nimport { db } from '$lib/server/db';\n\nexport const auth = betterAuth({\n baseURL: env.ORIGIN,\n secret: env.BETTER_AUTH_SECRET,\n database: drizzleAdapter(db, { provider: 'sqlite' }),\n emailAndPassword: { enabled: true },\n plugins: [\n sveltekitCookies(getRequestEvent) // make sure this is the last plugin in the array\n ]\n});\n",
@@ -83,9 +89,9 @@ const fullstackFiles = {
83
89
  "/lib/server/db/index.ts": "import { drizzle } from 'drizzle-orm/libsql';\nimport { createClient } from '@libsql/client';\nimport * as schema from './schema';\nimport { env } from '$env/dynamic/private';\n\nif (!env.DATABASE_URL) throw new Error('DATABASE_URL is not set');\n\nconst client = createClient({ url: env.DATABASE_URL });\n\nexport const db = drizzle(client, { schema });\n",
84
90
  "/lib/server/db/auth.schema.ts": "import { relations, sql } from \"drizzle-orm\";\nimport { sqliteTable, text, integer, index } from \"drizzle-orm/sqlite-core\";\n\nexport const user = sqliteTable(\"user\", {\n id: text(\"id\").primaryKey(),\n name: text(\"name\").notNull(),\n email: text(\"email\").notNull().unique(),\n emailVerified: integer(\"email_verified\", { mode: \"boolean\" })\n .default(false)\n .notNull(),\n image: text(\"image\"),\n createdAt: integer(\"created_at\", { mode: \"timestamp_ms\" })\n .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)\n .notNull(),\n updatedAt: integer(\"updated_at\", { mode: \"timestamp_ms\" })\n .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)\n .$onUpdate(() => /* @__PURE__ */ new Date())\n .notNull(),\n});\n\nexport const session = sqliteTable(\n \"session\",\n {\n id: text(\"id\").primaryKey(),\n expiresAt: integer(\"expires_at\", { mode: \"timestamp_ms\" }).notNull(),\n token: text(\"token\").notNull().unique(),\n createdAt: integer(\"created_at\", { mode: \"timestamp_ms\" })\n .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)\n .notNull(),\n updatedAt: integer(\"updated_at\", { mode: \"timestamp_ms\" })\n .$onUpdate(() => /* @__PURE__ */ new Date())\n .notNull(),\n ipAddress: text(\"ip_address\"),\n userAgent: text(\"user_agent\"),\n userId: text(\"user_id\")\n .notNull()\n .references(() => user.id, { onDelete: \"cascade\" }),\n },\n (table) => [index(\"session_userId_idx\").on(table.userId)],\n);\n\nexport const account = sqliteTable(\n \"account\",\n {\n id: text(\"id\").primaryKey(),\n accountId: text(\"account_id\").notNull(),\n providerId: text(\"provider_id\").notNull(),\n userId: text(\"user_id\")\n .notNull()\n .references(() => user.id, { onDelete: \"cascade\" }),\n accessToken: text(\"access_token\"),\n refreshToken: text(\"refresh_token\"),\n idToken: text(\"id_token\"),\n accessTokenExpiresAt: integer(\"access_token_expires_at\", {\n mode: \"timestamp_ms\",\n }),\n refreshTokenExpiresAt: integer(\"refresh_token_expires_at\", {\n mode: \"timestamp_ms\",\n }),\n scope: text(\"scope\"),\n password: text(\"password\"),\n createdAt: integer(\"created_at\", { mode: \"timestamp_ms\" })\n .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)\n .notNull(),\n updatedAt: integer(\"updated_at\", { mode: \"timestamp_ms\" })\n .$onUpdate(() => /* @__PURE__ */ new Date())\n .notNull(),\n },\n (table) => [index(\"account_userId_idx\").on(table.userId)],\n);\n\nexport const verification = sqliteTable(\n \"verification\",\n {\n id: text(\"id\").primaryKey(),\n identifier: text(\"identifier\").notNull(),\n value: text(\"value\").notNull(),\n expiresAt: integer(\"expires_at\", { mode: \"timestamp_ms\" }).notNull(),\n createdAt: integer(\"created_at\", { mode: \"timestamp_ms\" })\n .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)\n .notNull(),\n updatedAt: integer(\"updated_at\", { mode: \"timestamp_ms\" })\n .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)\n .$onUpdate(() => /* @__PURE__ */ new Date())\n .notNull(),\n },\n (table) => [index(\"verification_identifier_idx\").on(table.identifier)],\n);\n\nexport const userRelations = relations(user, ({ many }) => ({\n sessions: many(session),\n accounts: many(account),\n}));\n\nexport const sessionRelations = relations(session, ({ one }) => ({\n user: one(user, {\n fields: [session.userId],\n references: [user.id],\n }),\n}));\n\nexport const accountRelations = relations(account, ({ one }) => ({\n user: one(user, {\n fields: [account.userId],\n references: [user.id],\n }),\n}));\n",
85
91
  "/lib/server/admin.ts": "import { db } from '$lib/server/db';\nimport { user } from '$lib/server/db/schema';\nimport { asc } from 'drizzle-orm';\n\n/**\n * Check if a user is an admin.\n * \n * This template uses the \"first-user-is-admin\" pattern.\n * For multi-user scenarios, add a `role` column to the user schema\n * and check it here instead.\n */\nexport async function isAdmin(userId: string): Promise<boolean> {\n const [firstUser] = await db.select({ id: user.id })\n .from(user)\n .orderBy(asc(user.createdAt))\n .limit(1);\n return firstUser?.id === userId;\n}\n",
86
- "/lib/components/ui/Feedback.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import Check from 'phosphor-svelte/lib/Check';\n import Warning from 'phosphor-svelte/lib/Warning';\n import X from 'phosphor-svelte/lib/X';\n interface Props {\n type: 'success' | 'error';\n message: string;\n ondismiss?: () => void;\n class?: string;\n }\n let { type, message, ondismiss, class: className }: Props = $props();\n<\/script>\n\n{#if message}\n<div class={cn('flex items-center gap-2 p-3 rounded-card', type === 'success' ? 'bg-success-100 dark:bg-success-900 text-success-700 dark:text-success-300' : 'bg-error-100 dark:bg-error-900 text-error-700 dark:text-error-300', className)}>\n {#if type === 'success'}<Check size={18} />{:else}<Warning size={18} />{/if}\n <span class=\"text-sm\">{message}</span>\n {#if ondismiss}\n <button class=\"ml-auto\" onclick={ondismiss} aria-label=\"Dismiss\"><X size={14} /></button>\n {/if}\n</div>\n{/if}\n",
87
- "/lib/components/ui/AvatarInitial.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n interface Props {\n name: string;\n size?: 'sm' | 'md' | 'lg';\n class?: string;\n }\n let { name, size = 'md', class: className }: Props = $props();\n const initial = $derived(name[0]?.toUpperCase() ?? '?');\n const sizes = { sm: 'w-7 h-7 text-xs', md: 'w-8 h-8 text-sm', lg: 'w-12 h-12 text-lg' };\n<\/script>\n\n<div class={cn('rounded-full bg-primary-100 dark:bg-primary-900 flex items-center justify-center font-bold text-primary-600 dark:text-primary-400', sizes[size], className)}>\n {initial}\n</div>\n",
88
- "/lib/components/layout/AdminLayout.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import type { Snippet } from 'svelte';\n import type { HTMLAttributes } from 'svelte/elements';\n import Users from 'phosphor-svelte/lib/Users';\n import Gear from 'phosphor-svelte/lib/Gear';\n import ChartBar from 'phosphor-svelte/lib/ChartBar';\n import SignOut from 'phosphor-svelte/lib/SignOut';\n import Menu from 'phosphor-svelte/lib/List';\n import X from 'phosphor-svelte/lib/X';\n import ThemeToggle from '$lib/components/ui/ThemeToggle.svelte';\n\n type NavItem = { href: string; label: string; icon: typeof ChartBar };\n\n interface Props extends HTMLAttributes<HTMLElement> {\n items?: NavItem[];\n currentPath?: string;\n user?: { name: string; email: string } | null;\n onSignOut?: () => void;\n class?: string;\n children: Snippet;\n }\n\n let {\n items = [\n { href: '/admin', label: 'Dashboard', icon: ChartBar },\n { href: '/admin/users', label: 'Users', icon: Users },\n { href: '/admin/settings', label: 'Settings', icon: Gear }\n ],\n currentPath = '',\n user = null,\n onSignOut,\n class: className,\n children\n }: Props = $props();\n\n let collapsed = $state(false);\n let mobileOpen = $state(false);\n\n function navClass(href: string) {\n return cn(\n 'flex items-center gap-3 px-3 py-2 rounded-card transition-colors',\n currentPath === href\n ? 'bg-primary-100 dark:bg-primary-900 text-primary-700 dark:text-primary-300'\n : 'hover:bg-surface-100 dark:hover:bg-surface-800 text-surface-600 dark:text-surface-400'\n );\n }\n<\/script>\n\n<div class={cn('flex min-h-screen', className)}>\n <!-- Desktop Sidebar -->\n <aside class=\"hidden lg:flex flex-col border-r border-surface-200 dark:border-surface-800 bg-surface-50 dark:bg-surface-950 transition-all {collapsed ? 'w-16' : 'w-56'}\">\n <div class=\"flex items-center justify-between p-3 border-b border-surface-200 dark:border-surface-800\">\n {#if !collapsed}\n <a href=\"/admin\" class=\"text-lg font-heading font-bold text-primary-600 dark:text-primary-400\">Admin</a>\n {/if}\n <button\n class=\"btn preset-ghost variant-surface p-1 rounded\"\n onclick={() => (collapsed = !collapsed)}\n aria-label=\"Toggle sidebar\"\n >\n <Menu size={18} />\n </button>\n </div>\n\n <nav class=\"flex-1 p-2 space-y-1\">\n {#each items as item}\n {@const Icon = item.icon}\n <a\n href={item.href}\n class={navClass(item.href)}\n >\n <Icon size={20} />\n {#if !collapsed}<span class=\"text-sm font-medium\">{item.label}</span>{/if}\n </a>\n {/each}\n </nav>\n\n <div class=\"p-2 border-t border-surface-200 dark:border-surface-800\">\n <ThemeToggle />\n </div>\n </aside>\n\n <!-- Main area -->\n <div class=\"flex-1 flex flex-col\">\n <!-- Top bar -->\n <header class=\"sticky top-0 z-50 bg-surface-50/80 dark:bg-surface-950/80 backdrop-blur-md border-b border-surface-200 dark:border-surface-800 px-element py-3 flex items-center justify-between\">\n <div class=\"flex items-center gap-3\">\n <button class=\"lg:hidden btn preset-ghost variant-surface p-2 rounded\" onclick={() => (mobileOpen = !mobileOpen)} aria-label=\"Menu\">\n {#if mobileOpen}<X size={20} />{:else}<Menu size={20} />{/if}\n </button>\n <h1 class=\"font-heading font-bold text-lg\">Dashboard</h1>\n </div>\n <div class=\"flex items-center gap-3\">\n <ThemeToggle class=\"lg:hidden\" />\n {#if user}\n <span class=\"text-sm text-surface-500 hidden sm:block\">{user.name}</span>\n {#if onSignOut}\n <button class=\"btn preset-ghost variant-surface p-2 rounded\" onclick={onSignOut} aria-label=\"Sign out\">\n <SignOut size={18} />\n </button>\n {/if}\n {/if}\n </div>\n </header>\n\n <!-- Mobile sidebar overlay -->\n {#if mobileOpen}\n <div class=\"lg:hidden fixed inset-0 z-40 bg-black/50\" onclick={() => (mobileOpen = false)}>\n <aside class=\"w-56 h-full bg-surface-50 dark:bg-surface-950 border-r border-surface-200 dark:border-surface-800 p-3 space-y-1\" onclick={(e: Event) => e.stopPropagation()}>\n {#each items as item}\n {@const Icon = item.icon}\n <a\n href={item.href}\n class={navClass(item.href)}\n onclick={() => (mobileOpen = false)}\n >\n <Icon size={20} />\n <span class=\"text-sm font-medium\">{item.label}</span>\n </a>\n {/each}\n </aside>\n </div>\n {/if}\n\n <!-- Content -->\n <main class=\"flex-1 p-group overflow-auto\">\n {@render children()}\n </main>\n </div>\n</div>\n",
92
+ "/lib/components/layout/AdminLayout.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import type { Snippet } from 'svelte';\n import type { HTMLAttributes } from 'svelte/elements';\n import Users from 'phosphor-svelte/lib/Users';\n import Gear from 'phosphor-svelte/lib/Gear';\n import ChartBar from 'phosphor-svelte/lib/ChartBar';\n import SignOut from 'phosphor-svelte/lib/SignOut';\n import Menu from 'phosphor-svelte/lib/List';\n import X from 'phosphor-svelte/lib/X';\n import ThemeToggle from '$lib/components/svforge/ui/ThemeToggle.svelte';\n\n type NavItem = { href: string; label: string; icon: typeof ChartBar };\n\n interface Props extends HTMLAttributes<HTMLElement> {\n items?: NavItem[];\n currentPath?: string;\n user?: { name: string; email: string } | null;\n onSignOut?: () => void;\n class?: string;\n children: Snippet;\n }\n\n let {\n items = [\n { href: '/admin', label: 'Dashboard', icon: ChartBar },\n { href: '/admin/users', label: 'Users', icon: Users },\n { href: '/admin/settings', label: 'Settings', icon: Gear }\n ],\n currentPath = '',\n user = null,\n onSignOut,\n class: className,\n children\n }: Props = $props();\n\n let collapsed = $state(false);\n let mobileOpen = $state(false);\n\n function navClass(href: string) {\n return cn(\n 'flex items-center gap-3 px-3 py-2 rounded-card transition-colors',\n currentPath === href\n ? 'bg-primary-100 dark:bg-primary-900 text-primary-700 dark:text-primary-300'\n : 'hover:bg-surface-100 dark:hover:bg-surface-800 text-surface-600 dark:text-surface-400'\n );\n }\n<\/script>\n\n<div class={cn('flex min-h-screen', className)}>\n <!-- Desktop Sidebar -->\n <aside class=\"hidden lg:flex flex-col border-r border-surface-200 dark:border-surface-800 bg-surface-50 dark:bg-surface-950 transition-all {collapsed ? 'w-16' : 'w-56'}\">\n <div class=\"flex items-center justify-between p-3 border-b border-surface-200 dark:border-surface-800\">\n {#if !collapsed}\n <a href=\"/admin\" class=\"text-lg font-heading font-bold text-primary-600 dark:text-primary-400\">Admin</a>\n {/if}\n <button\n class=\"btn preset-ghost variant-surface p-1 rounded\"\n onclick={() => (collapsed = !collapsed)}\n aria-label=\"Toggle sidebar\"\n >\n <Menu size={18} />\n </button>\n </div>\n\n <nav class=\"flex-1 p-2 space-y-1\">\n {#each items as item}\n {@const Icon = item.icon}\n <a\n href={item.href}\n class={navClass(item.href)}\n >\n <Icon size={20} />\n {#if !collapsed}<span class=\"text-sm font-medium\">{item.label}</span>{/if}\n </a>\n {/each}\n </nav>\n\n <div class=\"p-2 border-t border-surface-200 dark:border-surface-800\">\n <ThemeToggle />\n </div>\n </aside>\n\n <!-- Main area -->\n <div class=\"flex-1 flex flex-col\">\n <!-- Top bar -->\n <header class=\"sticky top-0 z-50 bg-surface-50/80 dark:bg-surface-950/80 backdrop-blur-md border-b border-surface-200 dark:border-surface-800 px-element py-3 flex items-center justify-between\">\n <div class=\"flex items-center gap-3\">\n <button class=\"lg:hidden btn preset-ghost variant-surface p-2 rounded\" onclick={() => (mobileOpen = !mobileOpen)} aria-label=\"Menu\">\n {#if mobileOpen}<X size={20} />{:else}<Menu size={20} />{/if}\n </button>\n <h1 class=\"font-heading font-bold text-lg\">Dashboard</h1>\n </div>\n <div class=\"flex items-center gap-3\">\n <ThemeToggle class=\"lg:hidden\" />\n {#if user}\n <span class=\"text-sm text-surface-500 hidden sm:block\">{user.name}</span>\n {#if onSignOut}\n <button class=\"btn preset-ghost variant-surface p-2 rounded\" onclick={onSignOut} aria-label=\"Sign out\">\n <SignOut size={18} />\n </button>\n {/if}\n {/if}\n </div>\n </header>\n\n <!-- Mobile sidebar overlay -->\n {#if mobileOpen}\n <div class=\"lg:hidden fixed inset-0 z-40 bg-black/50\" onclick={() => (mobileOpen = false)}>\n <aside class=\"w-56 h-full bg-surface-50 dark:bg-surface-950 border-r border-surface-200 dark:border-surface-800 p-3 space-y-1\" onclick={(e: Event) => e.stopPropagation()}>\n {#each items as item}\n {@const Icon = item.icon}\n <a\n href={item.href}\n class={navClass(item.href)}\n onclick={() => (mobileOpen = false)}\n >\n <Icon size={20} />\n <span class=\"text-sm font-medium\">{item.label}</span>\n </a>\n {/each}\n </aside>\n </div>\n {/if}\n\n <!-- Content -->\n <main class=\"flex-1 p-group overflow-auto\">\n {@render children()}\n </main>\n </div>\n</div>\n",
93
+ "/lib/components/svforge/ui/Feedback.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n import Check from 'phosphor-svelte/lib/Check';\n import Warning from 'phosphor-svelte/lib/Warning';\n import X from 'phosphor-svelte/lib/X';\n interface Props {\n type: 'success' | 'error';\n message: string;\n ondismiss?: () => void;\n class?: string;\n }\n let { type, message, ondismiss, class: className }: Props = $props();\n<\/script>\n\n{#if message}\n<div class={cn('flex items-center gap-2 p-3 rounded-card', type === 'success' ? 'bg-success-100 dark:bg-success-900 text-success-700 dark:text-success-300' : 'bg-error-100 dark:bg-error-900 text-error-700 dark:text-error-300', className)}>\n {#if type === 'success'}<Check size={18} />{:else}<Warning size={18} />{/if}\n <span class=\"text-sm\">{message}</span>\n {#if ondismiss}\n <button class=\"ml-auto\" onclick={ondismiss} aria-label=\"Dismiss\"><X size={14} /></button>\n {/if}\n</div>\n{/if}\n",
94
+ "/lib/components/svforge/ui/AvatarInitial.svelte": "<script lang=\"ts\">\n import { cn } from '$lib/utils/cn';\n interface Props {\n name: string;\n size?: 'sm' | 'md' | 'lg';\n class?: string;\n }\n let { name, size = 'md', class: className }: Props = $props();\n const initial = $derived(name[0]?.toUpperCase() ?? '?');\n const sizes = { sm: 'w-7 h-7 text-xs', md: 'w-8 h-8 text-sm', lg: 'w-12 h-12 text-lg' };\n<\/script>\n\n<div class={cn('rounded-full bg-primary-100 dark:bg-primary-900 flex items-center justify-center font-bold text-primary-600 dark:text-primary-400', sizes[size], className)}>\n {initial}\n</div>\n",
89
95
  "/lib/types.ts": "export type UserRow = {\n id: string;\n name: string;\n email: string;\n emailVerified: boolean;\n image: string | null;\n createdAt: Date | null;\n};\n"
90
96
  };
91
97
  //#endregion
@@ -98,18 +104,18 @@ function applyBaseMode(sv, files) {
98
104
  for (const [path, content] of Object.entries(files)) sv.file(`src${path}`, () => content);
99
105
  }
100
106
  //#endregion
101
- //#region src/modes/fullstack.ts
107
+ //#region src/modes/dashboard.ts
102
108
  /**
103
- * Apply Fullstack mode files via sv.file()
104
- * Fullstack = base + admin/auth/DB routes + testing
109
+ * Apply Dashboard mode files via sv.file()
110
+ * Dashboard = base + admin dashboard + auth + DB
105
111
  */
106
- function applyFullstackMode(sv, baseFiles, fullstackFiles) {
112
+ function applyDashboardMode(sv, baseFiles, dashboardFiles) {
107
113
  sv.devDependency("@testing-library/jest-dom", "^6.9.1");
108
114
  sv.devDependency("@testing-library/svelte", "^5.3.1");
109
115
  sv.devDependency("jsdom", "^29.1.1");
110
116
  sv.devDependency("vitest", "^4.1.5");
111
117
  for (const [path, content] of Object.entries(baseFiles)) sv.file(`src${path}`, () => content);
112
- for (const [path, content] of Object.entries(fullstackFiles)) sv.file(`src${path}`, () => content);
118
+ for (const [path, content] of Object.entries(dashboardFiles)) sv.file(`src${path}`, () => content);
113
119
  }
114
120
  //#endregion
115
121
  //#region src/index.ts
@@ -126,8 +132,8 @@ var src_default = defineAddon({
126
132
  value: "base",
127
133
  label: "Base — UI kit + layouts + forms (landing, portfolio, marketing…)"
128
134
  }, {
129
- value: "fullstack",
130
- label: "Full Stack — base + admin dashboard + auth + DB"
135
+ value: "dashboard",
136
+ label: "Dashboard — base + admin dashboard + auth + DB"
131
137
  }]
132
138
  }).build(),
133
139
  setup: ({ unsupported, isKit }) => {
@@ -139,16 +145,9 @@ var src_default = defineAddon({
139
145
  sv.dependency("@fontsource-variable/inter", "latest");
140
146
  sv.dependency("@fontsource-variable/manrope", "latest");
141
147
  sv.dependency("@fontsource-variable/space-grotesk", "latest");
142
- sv.dependency("@tiptap/core", "latest");
143
- sv.dependency("@tiptap/extension-underline", "latest");
144
- sv.dependency("@tiptap/starter-kit", "latest");
145
148
  sv.dependency("clsx", "latest");
146
149
  sv.dependency("phosphor-svelte", "^3.1.0");
147
- sv.dependency("pino", "latest");
148
- sv.dependency("pino-pretty", "latest");
149
- sv.dependency("sveltekit-superforms", "latest");
150
150
  sv.dependency("tailwind-merge", "latest");
151
- sv.dependency("zod", "latest");
152
151
  sv.devDependency("@skeletonlabs/skeleton", "latest");
153
152
  sv.devDependency("@skeletonlabs/skeleton-svelte", "latest");
154
153
  sv.devDependency("@tailwindcss/forms", "^0.5.0");
@@ -162,7 +161,7 @@ var src_default = defineAddon({
162
161
  updated = updated.replace(/plugins:\s*\[/, "plugins: [tailwindcss(), ");
163
162
  return updated;
164
163
  });
165
- if (template === "fullstack") applyFullstackMode(sv, baseFiles, fullstackFiles);
164
+ if (template === "dashboard") applyDashboardMode(sv, baseFiles, dashboardFiles);
166
165
  else applyBaseMode(sv, baseFiles);
167
166
  },
168
167
  nextSteps: ({ options }) => [`SvelteForge ${options.template} template applied!`, "Run `npm run dev` (or `bun dev`) to start developing."]
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "svforge",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "type": "module",
5
5
  "description": "sv community addon — SvelteKit starter templates with Skeleton UI, Tailwind CSS, Better Auth, and Drizzle ORM",
6
6
  "author": "Ludo (https://lelab.dev)",
7
7
  "repository": {
8
8
  "type": "git",
9
9
  "url": "git+https://github.com/lelabdev/svelteforge.git",
10
- "directory": "svelteForge"
10
+ "directory": "packages/svforge"
11
11
  },
12
12
  "homepage": "https://github.com/lelabdev/svelteforge#readme",
13
13
  "bugs": "https://github.com/lelabdev/svelteforge/issues",