Create a tinykit app: [DESCRIBE YOUR APP HERE]
Output valid JSON with this exact structure:
```json
{
"description": "App name",
"frontend_code": "SVELTE_5_COMPONENT_HERE",
"design": [],
"content": [],
"collections": []
}
```
## Architecture
- Single Svelte 5 file with standard CSS in <style>
- NO Tailwind/utility classes - use semantic class names (.card, .button, .header)
- Data via `import data from '$data'` with realtime subscriptions
- Content via `import content from '$content'` for editable text
- Design via CSS variables (--kebab-case) with fallbacks
- External data via `import { proxy } from '$tinykit'`
## Svelte 5 Runes (REQUIRED)
```javascript
let count = $state(0) // reactive state
let doubled = $derived(count * 2) // simple expressions ONLY
let filtered = $derived.by(() => items.filter(x => x.done)) // use .by() for callbacks/filters
$effect(() => { /* side effects, return cleanup fn */ })
```
- Events: `onclick={fn}` (NOT on:click). Call e.preventDefault() in handler.
- Props: `let { x } = $props()` (NOT export let)
- Bindings: `bind:value` works in Svelte 5
## Data API
```javascript
import data from '$data'
// Subscribe for realtime updates (returns unsubscribe fn)
$effect(() => data.todos.subscribe(items => { todos = items; loading = false }))
// CRUD - realtime auto-updates UI
await data.todos.create({ title: 'New' })
await data.todos.update(id, { done: true })
await data.todos.delete(id)
```
## Proxy API (for external data)
```javascript
import { proxy } from '$tinykit'
const data = await proxy.json('https://api.example.com/data') // JSON
const rss = await proxy.text('https://hnrss.org/frontpage') // text/RSS/XML
<audio src={proxy.url('https://example.com/audio.mp3')} /> // media URLs
```
## Responsive Layout
- Mobile-first: base styles for small screens, media queries for larger
- Stack layouts vertically by default, horizontal on wider screens
- Use relative units (%, rem, fr) not fixed px widths
- Touch-friendly: buttons/links min 44px tap target
```css
.container { padding: 1rem; }
.grid { display: flex; flex-direction: column; gap: 1rem; }
@media (min-width: 768px) {
.container { padding: 2rem; }
.grid { flex-direction: row; }
}
```
## Design Fields
Use CSS vars with fallbacks in code:
```css
.card { background: var(--card-background, #ffffff); }
```
Types: "color", "font", "radius", "shadow", "size", "text"
Name descriptively: "Card Background" not "Primary Color"
## Content Fields
Reference in code as content.field_key:
```svelte
<h1>{content.hero_title}</h1>
```
Types: "text", "textarea", "number", "boolean", "json"
Extract ALL user-facing text: titles, buttons, placeholders, empty states.
## Collections (Database)
- schema: array of {name, type} where type is "text", "number", "boolean", "date", "email", or "url"
- records: sample data (optional)
## Common Mistakes (AVOID)
- `$derived(items.filter(...))` → use `$derived.by(() => items.filter(...))` for callbacks
- `result.sort()` in $derived → use `[...result].sort()` (sort mutates, copy first)
- Missing return in $effect → `$effect(() => { return data.x.subscribe(...) })` for cleanup
- `on:click` → use `onclick` (Svelte 5)
- `export let x` → use `let { x } = $props()` (Svelte 5)
- No loading state → always `let loading = $state(true)`, set false in subscribe callback
- Missing fallback → `var(--x, #default)` not `var(--x)`
## UX Polish
- Use `transition:fade={{ duration: 100 }}` for dialogs, modals, and list items
- Import: `import { fade } from 'svelte/transition'`
## JSON Output Rules
- Escape newlines as \n and quotes as \" in frontend_code
- Output ONLY the JSON, no explanation