svforge 1.0.0 → 1.0.2

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.
Files changed (3) hide show
  1. package/README.md +19 -13
  2. package/dist/index.js +7 -5
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,27 +1,32 @@
1
- # SvelteForge
1
+ # SVForge
2
2
 
3
3
  **sv community addon** — SvelteKit starter templates built on Skeleton UI v4 and Tailwind CSS v4. Adds production-ready UI components, auth (Better Auth), database (Drizzle + SQLite), and admin dashboard to new SvelteKit projects.
4
4
 
5
5
  ## Install
6
6
 
7
7
  ```bash
8
- # One-liner
9
- npx sv create my-app --template minimal --types ts --add '@ludoloops/svelteforge=template:base' --install bun
8
+ # Base template
9
+ bunx sv create my-app --template minimal --types ts --add svforge --install bun
10
10
  cd my-app && bun dev
11
+
12
+ # Fullstack template (auth + DB + admin)
13
+ bunx sv create my-app --template minimal --types ts --add 'svforge=template:fullstack' --install bun
14
+ cd my-app && cp .env.example .env && bunx drizzle-kit push && bun dev
11
15
  ```
12
16
 
13
17
  Or step by step:
14
18
  ```bash
15
- npx sv create my-app --template minimal --types ts
19
+ bunx sv create my-app --template minimal --types ts
16
20
  cd my-app
17
- npx sv add @ludoloops/svelteforge # prompts: base or fullstack
21
+ bunx sv add svforge # prompts: base or fullstack
18
22
  ```
19
23
 
20
24
  ## What you get — Base
21
25
 
22
26
  - **15 UI components** — Button, Card, Badge, Avatar, Alert, Input, Select, Textarea, Checkbox, Toggle, Accordion, Tabs, Table, Breadcrumb, ThemeToggle
23
27
  - **2 layout components** — Navbar (responsive), Footer
24
- - **SvelteForge theme** — custom oklch color palette
28
+ - **SVForge theme** — custom oklch color palette
29
+ - **Dark/light mode** — auto-detects system preference, manual toggle
25
30
  - **Demo page** at `/demo-ui`
26
31
 
27
32
  ## What you get — Fullstack
@@ -31,10 +36,11 @@ Everything from Base, plus:
31
36
  - **Better Auth** — email/password, sessions, protected routes
32
37
  - **Drizzle + SQLite** — schema auto-generated
33
38
  - **Admin dashboard** (`/admin`) — stats, user CRUD, settings
39
+ - **Setup page** (`/setup`) — dev-only, create first admin user
34
40
 
35
41
  ```bash
36
- cp .env.example .env && npx drizzle-kit push && bun dev
37
- # Visit /login first user is admin
42
+ cp .env.example .env && bunx drizzle-kit push && bun dev
43
+ # Visit /setup to create your admin, then /login
38
44
  ```
39
45
 
40
46
  ## Usage
@@ -45,8 +51,7 @@ cp .env.example .env && npx drizzle-kit push && bun dev
45
51
  </script>
46
52
 
47
53
  <Button variant="filled" color="primary" size="lg">Get Started</Button>
48
- <Button variant="gradient" color="primary">Gradient</Button>
49
- <Button variant="glass" color="secondary">Glass</Button>
54
+ <Button variant="tonal" color="success">Tonal</Button>
50
55
  <Button loading={saving}>Saving...</Button>
51
56
  <Button href="/about">Link Button</Button>
52
57
 
@@ -63,7 +68,7 @@ cp .env.example .env && npx drizzle-kit push && bun dev
63
68
 
64
69
  | Prop | Values | Default |
65
70
  |------|--------|---------|
66
- | variant | `filled` `outlined` `tonal` `ghost` `glass` `elevated` `gradient` | `filled` |
71
+ | variant | `filled` `outlined` `tonal` `ghost` | `filled` |
67
72
  | color | `primary` `secondary` `tertiary` `success` `warning` `error` `surface` | `primary` |
68
73
  | size | `sm` `md` `lg` | `md` |
69
74
  | loading | `boolean` | `false` |
@@ -122,8 +127,9 @@ cd /tmp/test && bun install && bun dev
122
127
 
123
128
  ## Links
124
129
 
125
- - [npm](https://www.npmjs.com/package/@ludoloops/svelteforge)
126
- - [GitHub](https://github.com/ludoloops/svelteforge)
130
+ - [npm](https://www.npmjs.com/package/svforge)
131
+ - [GitHub](https://github.com/lelabdev/svforge)
132
+ - [Org](https://www.npmjs.com/org/svforge)
127
133
 
128
134
  ## License
129
135
 
package/dist/index.js CHANGED
@@ -11,12 +11,13 @@ const baseFiles = {
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
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",
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';\nexport { default as Logo } from './Logo.svelte';\n",
15
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
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
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
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
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/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",
20
21
  "/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
22
  "/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
23
  "/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",
@@ -26,8 +27,8 @@ const baseFiles = {
26
27
  "/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
28
  "/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
29
  "/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",
30
+ "/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 Logo from '$lib/components/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",
31
+ "/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
32
  "/lib/components/layout/index.ts": "export { default as Footer } from './Footer.svelte';\nexport { default as Navbar } from './Navbar.svelte';\n",
32
33
  "/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
34
  "/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"
@@ -49,6 +50,7 @@ const fullstackFiles = {
49
50
  "/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
51
  "/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
52
  "/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",
53
+ "/lib/components/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",
52
54
  "/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
55
  "/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
56
  "/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",
@@ -58,8 +60,8 @@ const fullstackFiles = {
58
60
  "/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
61
  "/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
62
  "/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",
63
+ "/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 Logo from '$lib/components/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",
64
+ "/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
65
  "/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",
64
66
  "/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
67
  "/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",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svforge",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
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)",