= $props();
+
+
+
diff --git a/src/shared/shadcn/ui/button-group/button-group-text.svelte b/src/shared/shadcn/ui/button-group/button-group-text.svelte
new file mode 100644
index 0000000..17c9cd2
--- /dev/null
+++ b/src/shared/shadcn/ui/button-group/button-group-text.svelte
@@ -0,0 +1,33 @@
+
+
+{#if child}
+ {@render child({ props: mergedProps })}
+{:else}
+
+ {@render mergedProps.children?.()}
+
+{/if}
diff --git a/src/shared/shadcn/ui/button-group/button-group.svelte b/src/shared/shadcn/ui/button-group/button-group.svelte
new file mode 100644
index 0000000..60b4e29
--- /dev/null
+++ b/src/shared/shadcn/ui/button-group/button-group.svelte
@@ -0,0 +1,53 @@
+
+
+
+
+
+ {@render children?.()}
+
diff --git a/src/shared/shadcn/ui/button-group/index.ts b/src/shared/shadcn/ui/button-group/index.ts
new file mode 100644
index 0000000..76299b7
--- /dev/null
+++ b/src/shared/shadcn/ui/button-group/index.ts
@@ -0,0 +1,13 @@
+import Separator from './button-group-separator.svelte';
+import Text from './button-group-text.svelte';
+import Root from './button-group.svelte';
+
+export {
+ Root,
+ //
+ Root as ButtonGroup,
+ Separator,
+ Separator as ButtonGroupSeparator,
+ Text,
+ Text as ButtonGroupText,
+};
diff --git a/src/shared/shadcn/ui/item/index.ts b/src/shared/shadcn/ui/item/index.ts
new file mode 100644
index 0000000..e5c35f9
--- /dev/null
+++ b/src/shared/shadcn/ui/item/index.ts
@@ -0,0 +1,34 @@
+import Actions from './item-actions.svelte';
+import Content from './item-content.svelte';
+import Description from './item-description.svelte';
+import Footer from './item-footer.svelte';
+import Group from './item-group.svelte';
+import Header from './item-header.svelte';
+import Media from './item-media.svelte';
+import Separator from './item-separator.svelte';
+import Title from './item-title.svelte';
+import Root from './item.svelte';
+
+export {
+ Actions,
+ Actions as ItemActions,
+ Content,
+ Content as ItemContent,
+ Description,
+ Description as ItemDescription,
+ Footer,
+ Footer as ItemFooter,
+ Group,
+ Group as ItemGroup,
+ Header,
+ Header as ItemHeader,
+ Media,
+ Media as ItemMedia,
+ Root,
+ //
+ Root as Item,
+ Separator,
+ Separator as ItemSeparator,
+ Title,
+ Title as ItemTitle,
+};
diff --git a/src/shared/shadcn/ui/item/item-actions.svelte b/src/shared/shadcn/ui/item/item-actions.svelte
new file mode 100644
index 0000000..fd10882
--- /dev/null
+++ b/src/shared/shadcn/ui/item/item-actions.svelte
@@ -0,0 +1,23 @@
+
+
+
+ {@render children?.()}
+
diff --git a/src/shared/shadcn/ui/item/item-content.svelte b/src/shared/shadcn/ui/item/item-content.svelte
new file mode 100644
index 0000000..cbfd5f3
--- /dev/null
+++ b/src/shared/shadcn/ui/item/item-content.svelte
@@ -0,0 +1,23 @@
+
+
+
+ {@render children?.()}
+
diff --git a/src/shared/shadcn/ui/item/item-description.svelte b/src/shared/shadcn/ui/item/item-description.svelte
new file mode 100644
index 0000000..9a51cbe
--- /dev/null
+++ b/src/shared/shadcn/ui/item/item-description.svelte
@@ -0,0 +1,27 @@
+
+
+a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4',
+ className,
+ )}
+ {...restProps}
+>
+ {@render children?.()}
+
diff --git a/src/shared/shadcn/ui/item/item-footer.svelte b/src/shared/shadcn/ui/item/item-footer.svelte
new file mode 100644
index 0000000..42844ca
--- /dev/null
+++ b/src/shared/shadcn/ui/item/item-footer.svelte
@@ -0,0 +1,23 @@
+
+
+
+ {@render children?.()}
+
diff --git a/src/shared/shadcn/ui/item/item-group.svelte b/src/shared/shadcn/ui/item/item-group.svelte
new file mode 100644
index 0000000..6f44cad
--- /dev/null
+++ b/src/shared/shadcn/ui/item/item-group.svelte
@@ -0,0 +1,24 @@
+
+
+
+ {@render children?.()}
+
diff --git a/src/shared/shadcn/ui/item/item-header.svelte b/src/shared/shadcn/ui/item/item-header.svelte
new file mode 100644
index 0000000..a4a3cff
--- /dev/null
+++ b/src/shared/shadcn/ui/item/item-header.svelte
@@ -0,0 +1,23 @@
+
+
+
+ {@render children?.()}
+
diff --git a/src/shared/shadcn/ui/item/item-media.svelte b/src/shared/shadcn/ui/item/item-media.svelte
new file mode 100644
index 0000000..052321b
--- /dev/null
+++ b/src/shared/shadcn/ui/item/item-media.svelte
@@ -0,0 +1,49 @@
+
+
+
+
+
+ {@render children?.()}
+
diff --git a/src/shared/shadcn/ui/item/item-separator.svelte b/src/shared/shadcn/ui/item/item-separator.svelte
new file mode 100644
index 0000000..cbb5d87
--- /dev/null
+++ b/src/shared/shadcn/ui/item/item-separator.svelte
@@ -0,0 +1,19 @@
+
+
+
diff --git a/src/shared/shadcn/ui/item/item-title.svelte b/src/shared/shadcn/ui/item/item-title.svelte
new file mode 100644
index 0000000..90ad16b
--- /dev/null
+++ b/src/shared/shadcn/ui/item/item-title.svelte
@@ -0,0 +1,23 @@
+
+
+
+ {@render children?.()}
+
diff --git a/src/shared/shadcn/ui/item/item.svelte b/src/shared/shadcn/ui/item/item.svelte
new file mode 100644
index 0000000..c01acbf
--- /dev/null
+++ b/src/shared/shadcn/ui/item/item.svelte
@@ -0,0 +1,67 @@
+
+
+
+
+{#if child}
+ {@render child({ props: mergedProps })}
+{:else}
+
+ {@render mergedProps.children?.()}
+
+{/if}
diff --git a/src/shared/shadcn/ui/popover/index.ts b/src/shared/shadcn/ui/popover/index.ts
new file mode 100644
index 0000000..e5456bb
--- /dev/null
+++ b/src/shared/shadcn/ui/popover/index.ts
@@ -0,0 +1,19 @@
+import Close from './popover-close.svelte';
+import Content from './popover-content.svelte';
+import Portal from './popover-portal.svelte';
+import Trigger from './popover-trigger.svelte';
+import Root from './popover.svelte';
+
+export {
+ Close,
+ Close as PopoverClose,
+ Content,
+ Content as PopoverContent,
+ Portal,
+ Portal as PopoverPortal,
+ Root,
+ //
+ Root as Popover,
+ Trigger,
+ Trigger as PopoverTrigger,
+};
diff --git a/src/shared/shadcn/ui/popover/popover-close.svelte b/src/shared/shadcn/ui/popover/popover-close.svelte
new file mode 100644
index 0000000..71124e2
--- /dev/null
+++ b/src/shared/shadcn/ui/popover/popover-close.svelte
@@ -0,0 +1,7 @@
+
+
+
diff --git a/src/shared/shadcn/ui/popover/popover-content.svelte b/src/shared/shadcn/ui/popover/popover-content.svelte
new file mode 100644
index 0000000..304e895
--- /dev/null
+++ b/src/shared/shadcn/ui/popover/popover-content.svelte
@@ -0,0 +1,34 @@
+
+
+
+
+
diff --git a/src/shared/shadcn/ui/popover/popover-portal.svelte b/src/shared/shadcn/ui/popover/popover-portal.svelte
new file mode 100644
index 0000000..8e68a02
--- /dev/null
+++ b/src/shared/shadcn/ui/popover/popover-portal.svelte
@@ -0,0 +1,7 @@
+
+
+
diff --git a/src/shared/shadcn/ui/popover/popover-trigger.svelte b/src/shared/shadcn/ui/popover/popover-trigger.svelte
new file mode 100644
index 0000000..1192af2
--- /dev/null
+++ b/src/shared/shadcn/ui/popover/popover-trigger.svelte
@@ -0,0 +1,17 @@
+
+
+
diff --git a/src/shared/shadcn/ui/popover/popover.svelte b/src/shared/shadcn/ui/popover/popover.svelte
new file mode 100644
index 0000000..7cd4812
--- /dev/null
+++ b/src/shared/shadcn/ui/popover/popover.svelte
@@ -0,0 +1,7 @@
+
+
+
diff --git a/src/shared/shadcn/ui/slider/index.ts b/src/shared/shadcn/ui/slider/index.ts
new file mode 100644
index 0000000..414e720
--- /dev/null
+++ b/src/shared/shadcn/ui/slider/index.ts
@@ -0,0 +1,7 @@
+import Root from './slider.svelte';
+
+export {
+ Root,
+ //
+ Root as Slider,
+};
diff --git a/src/shared/shadcn/ui/slider/slider.svelte b/src/shared/shadcn/ui/slider/slider.svelte
new file mode 100644
index 0000000..49e30bc
--- /dev/null
+++ b/src/shared/shadcn/ui/slider/slider.svelte
@@ -0,0 +1,51 @@
+
+
+
+ {#snippet children({ thumbs })}
+
+
+
+ {#each thumbs as thumb (thumb)}
+
+ {/each}
+ {/snippet}
+
diff --git a/src/shared/store/createControlStore.ts b/src/shared/store/createControlStore.ts
new file mode 100644
index 0000000..a7e7463
--- /dev/null
+++ b/src/shared/store/createControlStore.ts
@@ -0,0 +1,117 @@
+import {
+ type Writable,
+ get,
+ writable,
+} from 'svelte/store';
+
+/**
+ * Model for a control value with min/max bounds
+ */
+export type ControlModel<
+ TValue extends number = number,
+> = {
+ value: TValue;
+ min: TValue;
+ max: TValue;
+ step?: TValue;
+};
+
+/**
+ * Store model with methods for control manipulation
+ */
+export type ControlStoreModel<
+ TValue extends number,
+> =
+ & Writable>
+ & {
+ increase: () => void;
+ decrease: () => void;
+ /** Set a specific value */
+ setValue: (newValue: TValue) => void;
+ isAtMax: () => boolean;
+ isAtMin: () => boolean;
+ };
+
+/**
+ * Create a writable store for numeric control values with bounds
+ *
+ * @template TValue - The value type (extends number)
+ * @param initialState - Initial state containing value, min, and max
+ */
+/**
+ * Get the number of decimal places in a number
+ *
+ * For example:
+ * - 1 -> 0
+ * - 0.1 -> 1
+ * - 0.01 -> 2
+ * - 0.05 -> 2
+ *
+ * @param step - The step number to analyze
+ * @returns The number of decimal places
+ */
+function getDecimalPlaces(step: number): number {
+ const str = step.toString();
+ const decimalPart = str.split('.')[1];
+ return decimalPart ? decimalPart.length : 0;
+}
+
+/**
+ * Round a value to the precision of the given step
+ *
+ * This fixes floating-point precision errors that occur with decimal steps.
+ * For example, with step=0.05, adding it repeatedly can produce values like
+ * 1.3499999999999999 instead of 1.35.
+ *
+ * We use toFixed() to round to the appropriate decimal places instead of
+ * Math.round(value / step) * step, which doesn't always work correctly
+ * due to floating-point arithmetic errors.
+ *
+ * @param value - The value to round
+ * @param step - The step to round to (defaults to 1)
+ * @returns The rounded value
+ */
+function roundToStepPrecision(value: number, step: number = 1): number {
+ if (step <= 0) {
+ return value;
+ }
+ const decimals = getDecimalPlaces(step);
+ return parseFloat(value.toFixed(decimals));
+}
+
+export function createControlStore<
+ TValue extends number = number,
+>(
+ initialState: ControlModel,
+): ControlStoreModel {
+ const store = writable(initialState);
+ const { subscribe, set, update } = store;
+
+ const clamp = (value: number): TValue => {
+ return Math.max(initialState.min, Math.min(value, initialState.max)) as TValue;
+ };
+
+ return {
+ subscribe,
+ set,
+ update,
+ increase: () =>
+ update(m => {
+ const step = m.step ?? 1;
+ const newValue = clamp(m.value + step);
+ return { ...m, value: roundToStepPrecision(newValue, step) as TValue };
+ }),
+ decrease: () =>
+ update(m => {
+ const step = m.step ?? 1;
+ const newValue = clamp(m.value - step);
+ return { ...m, value: roundToStepPrecision(newValue, step) as TValue };
+ }),
+ setValue: (v: TValue) => {
+ const step = initialState.step ?? 1;
+ update(m => ({ ...m, value: roundToStepPrecision(clamp(v), step) as TValue }));
+ },
+ isAtMin: () => get(store).value === initialState.min,
+ isAtMax: () => get(store).value === initialState.max,
+ };
+}
diff --git a/src/shared/ui/ComboControl/ComboControl.svelte b/src/shared/ui/ComboControl/ComboControl.svelte
new file mode 100644
index 0000000..e600d00
--- /dev/null
+++ b/src/shared/ui/ComboControl/ComboControl.svelte
@@ -0,0 +1,155 @@
+
+
+
+
+
+
+
+
+ {#snippet child({ props })}
+
+ {value}
+
+ {/snippet}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/widgets/TypographySettings/index.ts b/src/widgets/TypographySettings/index.ts
new file mode 100644
index 0000000..e69de29
diff --git a/src/widgets/TypographySettings/ui/TypographyMenu.svelte b/src/widgets/TypographySettings/ui/TypographyMenu.svelte
new file mode 100644
index 0000000..4398c5c
--- /dev/null
+++ b/src/widgets/TypographySettings/ui/TypographyMenu.svelte
@@ -0,0 +1,10 @@
+
+
+
+
+