diff --git a/web/src/app.css b/web/src/app.css
index c3e72ae..90737c7 100644
--- a/web/src/app.css
+++ b/web/src/app.css
@@ -160,6 +160,19 @@ button:disabled {
border-radius: var(--radius-m);
}
+.control {
+ padding: var(--space-1) var(--space-2);
+ border: 1px solid var(--border);
+ border-radius: var(--radius-s);
+ background: var(--surface);
+ max-width: var(--control-max-width);
+}
+
+input.control[type='number'] {
+ font-family: var(--font-mono);
+ width: 100%;
+}
+
.error-banner {
padding: var(--space-2) var(--space-3);
border: 1px solid var(--danger);
diff --git a/web/src/lib/components/ui/Button.svelte b/web/src/lib/components/ui/Button.svelte
new file mode 100644
index 0000000..72e7b68
--- /dev/null
+++ b/web/src/lib/components/ui/Button.svelte
@@ -0,0 +1,77 @@
+
+
+
+
+
diff --git a/web/src/lib/components/ui/CheckboxField.svelte b/web/src/lib/components/ui/CheckboxField.svelte
new file mode 100644
index 0000000..745532b
--- /dev/null
+++ b/web/src/lib/components/ui/CheckboxField.svelte
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+{#if hint}
+ {hint}
+{/if}
+
+
diff --git a/web/src/lib/components/ui/ColorField.svelte b/web/src/lib/components/ui/ColorField.svelte
new file mode 100644
index 0000000..3c94e03
--- /dev/null
+++ b/web/src/lib/components/ui/ColorField.svelte
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
diff --git a/web/src/lib/components/ui/EmptyState.svelte b/web/src/lib/components/ui/EmptyState.svelte
new file mode 100644
index 0000000..cba7241
--- /dev/null
+++ b/web/src/lib/components/ui/EmptyState.svelte
@@ -0,0 +1,38 @@
+
+
+
+
{title}
+ {#if hint}
+
{hint}
+ {/if}
+ {#if children}
+ {@render children()}
+ {/if}
+
+
+
diff --git a/web/src/lib/components/ui/Field.svelte b/web/src/lib/components/ui/Field.svelte
new file mode 100644
index 0000000..a64df98
--- /dev/null
+++ b/web/src/lib/components/ui/Field.svelte
@@ -0,0 +1,29 @@
+
+
+
+
+ {@render children()}
+ {#if hint}
+
{hint}
+ {/if}
+
+
+
diff --git a/web/src/lib/components/ui/SelectField.svelte b/web/src/lib/components/ui/SelectField.svelte
new file mode 100644
index 0000000..493dcb9
--- /dev/null
+++ b/web/src/lib/components/ui/SelectField.svelte
@@ -0,0 +1,21 @@
+
+
+
+
+
diff --git a/web/src/lib/components/ui/SliderField.svelte b/web/src/lib/components/ui/SliderField.svelte
new file mode 100644
index 0000000..9b14b02
--- /dev/null
+++ b/web/src/lib/components/ui/SliderField.svelte
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/web/src/lib/components/ui/TextField.svelte b/web/src/lib/components/ui/TextField.svelte
new file mode 100644
index 0000000..2a290de
--- /dev/null
+++ b/web/src/lib/components/ui/TextField.svelte
@@ -0,0 +1,20 @@
+
+
+
+
+