@muten/shadcn 0.0.4 → 0.0.7

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/AGENTS.md ADDED
@@ -0,0 +1,116 @@
1
+ # @muten/shadcn - component catalog (for an AI assembling a muten app)
2
+
3
+ ## Setup (once): `plugins { shadcn {} }` in muten.config + `@import "@muten/shadcn/globals.css";` in src/styles.css. Then everything below is usable directly; `muten add <name>` only ejects a source.
4
+
5
+ > **Native primitives always win — for a checkbox / select / number / range / date / chart, use the native muten
6
+ > primitive, NOT a plugin part.** muten's core now ships these as primitives, so this plugin does NOT: write
7
+ > `Checkbox bind(agree)`, `Select bind(theme) options(System, Light, Dark) class("native-select")` (the `.native-select`
8
+ > class gives the native `<select>` the shadcn look), `Number bind(qty)`, `Range bind(vol)`, `Date bind(due)`,
9
+ > `Chart @data kind(bar) x(label) y(value)`. A part named the same as a primitive is unreachable (the primitive
10
+ > shadows it), so those parts were removed. For a *searchable* dropdown use the `Combobox` part.
11
+ > **The exact props of every part are in the header comment of `node_modules/@muten/shadcn/registry/<name>.muten` —
12
+ > read it before calling (the one-liners below are the catalog, not the API).**
13
+
14
+ ## PLUG-AND-PLAY (zero class props)
15
+ - **Btn**(variant, onClick) { slot } · **LinkBtn**(to, variant) { slot } · **Input**(value, placeholder)
16
+ - **DataTable** @rows columns(a, b) - STYLED BY DEFAULT, no class needed.
17
+ - + every Part / Custom below.
18
+
19
+ ## Parts (62) - imported by plugins {}
20
+
21
+ - **Card** - A surface with CardHeader / CardTitle / CardDescription / CardContent / CardFooter sub-components. · +CardHeader, CardTitle, CardDescription, CardContent, CardFooter
22
+ - **Badge** - A small status descriptor. variant: default | secondary | destructive | outline.
23
+ - **Alert** - A callout with AlertTitle / AlertDescription. variant: default | destructive. · +AlertTitle, AlertDescription
24
+ - **Avatar** - A user's initials in a circle (AvatarFallback).
25
+ - **Switch** - A toggle; the page owns the bool + the action.
26
+ - **Spinner** - A loading indicator (animate-spin loader).
27
+ - **Progress** - A determinate progress bar; value is 0-100.
28
+ - **Separator** - A thin divider line.
29
+ - **Empty** - An empty-state placeholder (title + description in a dashed border).
30
+ - **Breadcrumb** - A breadcrumb trail with BreadcrumbLink / BreadcrumbSeparator / BreadcrumbPage. · +BreadcrumbLink, BreadcrumbPage, BreadcrumbSeparator
31
+ - **Pagination** - Page navigation with PaginationLink / Previous / Next / Ellipsis. · +PaginationLink, PaginationPrevious, PaginationNext, PaginationEllipsis
32
+ - **Field** - A labelled form control: FieldLabel / FieldDescription / FieldError. · +FieldLabel, FieldDescription, FieldError
33
+ - **Item** - A bordered row: ItemContent / ItemTitle / ItemDescription. · +ItemContent, ItemTitle, ItemDescription
34
+ - **Toggle** - A two-state button; the page owns the bool + the toggle action.
35
+ - **ToggleGroup** - Single-select toolbar with ToggleGroupItem; page owns a text state. · +ToggleGroupItem
36
+ - **RadioGroup** - Single choice with RadioGroupItem; page owns a text state. · +RadioGroupItem
37
+ - **Collapsible** - A trigger + content shown when open; page owns the bool.
38
+ - **Accordion** - Stacked expandable sections with AccordionItem; page owns a bool per item. · +AccordionItem
39
+ - **TabsList** - Tab bar (TabsList/TabsTrigger) + TabsContent panels; page owns the active-tab text. · +TabsTrigger, TabsContent
40
+ - **Dialog** - A modal with DialogTitle/Description/Footer; page owns the open bool. Backdrop click closes. · +DialogTitle, DialogDescription, DialogFooter
41
+ - **Tooltip** - A hover tooltip (pure CSS, no state). The slot is the trigger.
42
+ - **DropdownMenu** - A menu below a trigger with DropdownItem/Label/Separator; page owns the open bool. · +DropdownItem, DropdownLabel, DropdownSeparator
43
+ - **Popover** - A floating panel below a trigger; page owns the open bool.
44
+ - **AlertDialog** - A modal requiring an explicit choice (no backdrop dismiss); page owns the open bool. · +AlertDialogTitle, AlertDialogDescription, AlertDialogFooter
45
+ - **Sheet** - A panel that slides in from the right; page owns the open bool. · +SheetTitle, SheetDescription
46
+ - **Drawer** - A panel that slides up from the bottom; page owns the open bool.
47
+ - **HoverCard** - Richer content shown on hover (pure CSS, no state).
48
+ - **ScrollArea** - A scrollable region with styled scrollbars.
49
+ - **Message** - Chat Message + Bubble + Marker (variant: sent | received). · +Bubble, Marker
50
+ - **MessageScroller** - A scrollable column of chat messages.
51
+ - **Attachment** - A file attachment chip (icon + name + size).
52
+ - **Menubar** - A bar of menus (File/Edit/View); page owns which is open via a text state. · +MenubarMenu, MenubarItem, MenubarSeparator
53
+ - **NavigationMenu** - A nav bar with hover-revealed panels (pure CSS, no state). · +NavigationMenuItem, NavigationMenuLink
54
+ - **Combobox** - A Select with a search box; page filters items with `each … where`. · +ComboboxItem
55
+ - **Command** - A command palette (search + grouped CommandItem list). · +CommandList, CommandGroup, CommandItem, CommandShortcut
56
+ - **SegmentedControl** - A single-select toolbar (SegmentedItem); page owns a text state. · +SegmentedItem, SegmentedTabs, IconTab
57
+ - **VectorField** - Paired NumberFields (X/Y) for position or size.
58
+ - **Toolbar** - A grouped button bar with ToolbarGroup / ToolbarSeparator. · +ToolbarGroup, ToolbarSeparator
59
+ - **StatusBar** - A thin bottom bar with StatusItem / StatusSpacer. · +StatusItem, StatusSpacer
60
+ - **ZoomControl** - A - 100% + zoom widget; page owns the zoom number.
61
+ - **SplitButton** - A main action + a caret dropdown; page owns the open bool.
62
+ - **SwatchPalette** - A grid of selectable color swatches (Swatch); page owns the selected hex + colors. · +Swatch
63
+ - **AppSidebar** - AppSidebar - a collapsible nav panel (SidebarHeader / SidebarGroup / SidebarItem). Named AppSidebar so it never shadows muten's core Sidebar <aside> primitive. · +SidebarHeader, SidebarGroup, SidebarGroupLabel, SidebarItem
64
+ - **Inspector** - Property grid: InspectorSection (collapsible) / InspectorRow (label : value slot). · +InspectorSection, InspectorRow
65
+ - **CommandPalette** - A top-anchored modal holding a Command; page owns the open bool.
66
+ - **Stepper** - Horizontal or vertical step progress indicator; done steps show a check, the current step is ringed (parts: Stepper, StepperVertical, Step). · +StepperVertical, Step
67
+ - **Timeline** - Vertical event timeline: a dot on a connector line + title/time/description per item (parts: Timeline, TimelineItem). · +TimelineItem
68
+ - **Pill** - A hero eyebrow chip (rounded-full, bigger than Badge); slot-based for an icon + text.
69
+ - **FeatureGrid** - BLOCK: responsive grid of feature cards (icon + title + copy). Parts: FeatureGrid, FeatureCard. · +FeatureCard
70
+ - **Pricing** - BLOCK: a row of pricing tiers (name/price/features/CTA), featured highlight. Parts: Pricing, PricingTier, PricingFeature. · +PricingTier, PricingTierFeatured, PricingFeature
71
+ - **LogoCloud** - BLOCK: a 'trusted by' strip of logos, muted + grayscale until hover. Parts: LogoCloud, LogoCloudItem. · +LogoCloudItem
72
+ - **NavPill** - BLOCK: the floating glass navbar (sticky wrapper + glass pill + scroll animation baked in). Drop logo/links/CTA inside.
73
+ - **StatBand** - BLOCK: a divided row of KPI stats. Parts: StatBand, Stat(label) - value goes in the slot (Span or Custom CountUp). · +Stat
74
+ - **FeatureRows** - BLOCK: numbered feature/schedule rows with side gutters (12-col grid inside). Parts: FeatureRows, FeatureRow. · +FeatureRow
75
+ - **BentoGallery** - BLOCK: fixed-row-height tiling gallery; cells pick their col-span. Parts: BentoGallery, BentoCell. · +BentoCell
76
+ - **ButtonGroup** - A segmented row of buttons that share borders. Put btn-outline buttons inside.
77
+ - **InputGroup** - An input with a leading icon/addon in one bordered field.
78
+ - **Skeleton** - A loading placeholder (pulse). Add size via class: Skeleton class("h-4 w-32").
79
+ - **Kbd** - A keyboard key hint. Kbd { Span "Ctrl" }.
80
+ - **Btn** - Plug-and-play button (part Btn - Button is a primitive). `Btn(variant: "default", onClick: save) { Span "Save" }`. variant: default|secondary|outline|ghost|destructive|link.
81
+ - **Input** - Plug-and-play text input (part Input wraps SearchField). `Input(value: q, placeholder: "...")` - value is two-way bound. For typed/validated fields use a Form.
82
+ - **LinkBtn** - A navigation link styled as a button. `LinkBtn(to: "/signup", variant: "default") { Span "Get started" }`.
83
+
84
+ ## Customs (20) - importable; some need a dep
85
+
86
+ - **Slider** (`slider`) - A draggable range slider (Custom). Page owns the value number.
87
+ - **InputOtp** (`input-otp`) - N single-character boxes synced to one value (Custom). Page owns the value text.
88
+ - **Calendar** (`calendar`) - A month-grid date picker (Custom). Page owns the selected ISO date text.
89
+ - **Toaster** (`toaster`) - Fixed toast notifications (Custom). Page bumps a trigger counter to fire one.
90
+ - **Carousel** (`carousel`) - A slide carousel over a list (Custom). Page owns the slides list.
91
+ - **GradientBlinds** (`gradient-blinds`) · **needs npm: ogl** - Animated WebGL gradient + diagonal-blinds hero background that follows the cursor and fades into the page (needs ogl).
92
+ - **Resizable** (`resizable`) - Two panels with a draggable splitter (Custom).
93
+ - **ContextMenu** (`context-menu`) - Right-click an area to open a menu of items (Custom).
94
+ - **NumberField** (`number-field`) - A numeric input with -/+ steppers, min/max/step (Custom).
95
+ - **FileInput** (`file-input`) - A click/drag-drop file dropzone (Custom); emits file names.
96
+ - **ColorPicker** (`color-picker`) - A swatch + saturation/value + hue popover (Custom); page owns the hex.
97
+ - **TreeView** (`tree-view`) - A nested folder/file tree (Custom): expandable, selectable, icons + indent.
98
+ - **CanvasHost** (`canvas-viewport`) - A <canvas> with pan/zoom (Custom) + a muten toolbar slot floating on top.
99
+ - **TagInput** (`tag-input`) - Chips you add by typing + Enter (Custom); page owns a comma-joined text.
100
+ - **RangeSlider** (`range-slider`) - A two-thumb range slider (Custom); page owns the low + high numbers.
101
+ - **SortableList** (`sortable-list`) - Drag rows to reorder (Custom); page owns a comma-joined order.
102
+ - **FolderPicker** (`folder-picker`) - Connect a local folder via showDirectoryPicker (Custom); emits the name.
103
+ - **Hotkey** (`hotkey`) - A global keyboard shortcut (Cmd/Ctrl+key), invisible (Custom).
104
+ - **Code** (`code`) - Syntax-highlighted code block that understands muten (shared highlight.js, same grammar as the VS Code extension).
105
+ - **CountUp** (`count-up`) - Animate a formatted number (2.4M / 94% / 40ms / 99.99%) from 0 when it scrolls into view; lands on the exact target. Custom. Styled form (takes the class): `Custom CountUp inputs(to: "2.4M") class("text-4xl font-bold")`. For stat bands + hero metrics.
106
+
107
+ ## Class recipes (4) - style a primitive (Table is default-styled; typography/textarea/motion)
108
+
109
+ - **textarea** - class("textarea") · label: class("label") · native select: class("native-select")
110
+ - **typography** - Title "Docs" h1 class("prose-h1") (prose-h1..h4 · prose-p|lead|large|small|muted|blockquote|code|list)
111
+ - **table** - DataTable @rows columns(name, email, status) - STYLED BY DEFAULT (no class needed). `class("data-table")` still works as an explicit alias. Raw cells; for badge/formatted cells build with each + Stack.
112
+ - **motion** - Page animations, all CSS + reduced-motion safe. Entrance: animate-fade-up|fade-in|scale-in|slide-in (+ d1..d6 stagger). Scroll: reveal|reveal-left|reveal-scale, and nav-scroll on a navbar. Hover/press: hover-lift, hover-glow, arrow-hover (nudges a trailing Icon), link-underline, press, float.
113
+
114
+ ### Gotcha: a part can not be named after a core primitive — the **primitive wins** and any same-named plugin part is unreachable on import. The primitives include `Button/Link/Form/Image/SearchField/DataTable/Icon/Text/Title/Span` (→ this plugin uses `Btn`/`Input`/`LinkBtn`) AND `Select/Checkbox/Number/Range/Date/Chart` (→ use those primitives directly, see the note at the top). If you write a part call and the oracle says `missing-prop: X is missing the required "bind"`, X is a primitive shadowing the part — switch to the primitive's API.
115
+
116
+ ### Gotcha: a **part takes NO trailing modifiers** — `Part(...) class(...)`, `Part(...) disabled when x`, `Part(...) on(...)` and `Part(...) aria(...)` are all syntax errors (a part call ends at its `)`/`{}`). When you need `class`/`disabled when`/`on`/`aria` on a control, use the **native primitive** (`Button "Save" -> save disabled when not valid class("btn btn-default")`), not the `Btn(...)` part.
package/README.md CHANGED
@@ -94,19 +94,25 @@ Display: **card** (Card / CardHeader / CardTitle / CardDescription / CardContent
94
94
  **alert** (Alert / AlertTitle / AlertDescription), **avatar**, **spinner**, **progress**, **empty**,
95
95
  **breadcrumb**, **pagination**, **field**, **item**.
96
96
 
97
- Stateful (the page owns the state, the part receives it - see the pattern below): **switch**, **checkbox**,
97
+ Stateful (the page owns the state, the part receives it - see the pattern below): **switch**,
98
98
  **toggle**, **toggle-group**, **radio-group**, **collapsible**, **accordion**, **tabs**.
99
99
 
100
100
  Overlays (the page owns an `open` bool): **dialog**, **alert-dialog**, **tooltip** (pure hover, no state),
101
- **dropdown-menu**, **popover**, **sheet**, **drawer**, **hover-card** (hover), **select**, **scroll-area**,
101
+ **dropdown-menu**, **popover**, **sheet**, **drawer**, **hover-card** (hover), **scroll-area**,
102
102
  **menubar**, **navigation-menu** (hover), **combobox**, **command**.
103
103
 
104
+ > **Not here on purpose:** checkbox / select / number / range / date / chart are **native muten primitives** now,
105
+ > so this plugin ships no part for them (a same-named part would be shadowed by the primitive). Use `Checkbox bind(x)`,
106
+ > `Select bind(x) options(a, b) class("native-select")`, `Number`/`Range`/`Date bind(x)`, `Chart @data kind(bar) …`.
107
+ > For a *searchable* dropdown, use **combobox**.
108
+
104
109
  Chat: **message** (Message / Bubble / Marker), **message-scroller**, **attachment**.
105
110
 
106
111
  ### Custom (eject-only - `muten add` copies a host `.js` into `src/components/`)
107
112
 
108
- The genuinely interactive 20%: **slider**, **input-otp**, **calendar**, **toaster**, **carousel**, **chart**,
109
- **resizable**, **context-menu**. Each is a thin muten part over a small vanilla-JS host you own and can edit.
113
+ The genuinely interactive 20%: **input-otp**, **toaster**, **carousel**, **resizable**, **context-menu** (+ more,
114
+ see AGENTS.md). Each is a thin muten part over a small vanilla-JS host you own and can edit. (A chart / slider /
115
+ date picker is **native** — `Chart`/`Range`/`Date` — not a Custom; don't `muten add` those.)
110
116
 
111
117
  `calendar` ships all the shadcn variants: `mode` (`single` / `range` / `multiple`), `months` (1-2 side by side),
112
118
  `caption` (`label` / `dropdown`). `selected` encodes the value (`"yyyy-mm-dd"`, `"from/to"`, or `"d1,d2,..."`).
package/globals.css CHANGED
@@ -175,6 +175,7 @@
175
175
  raw <svg> child - muten renders icons as iconify-icon). Icon sizing is the `.btn svg` rule below. */
176
176
  .btn { @apply inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium leading-none transition-all shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] active:scale-[0.97] aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 disabled:pointer-events-none disabled:opacity-50 h-8 px-3; }
177
177
  .btn-default { @apply bg-primary text-primary-foreground shadow-xs hover:bg-primary/90 active:bg-primary/80; }
178
+ .btn-primary { @apply bg-primary text-primary-foreground shadow-xs hover:bg-primary/90 active:bg-primary/80; } /* alias of btn-default — tolerate the near-universal `btn-primary` idiom */
178
179
  .btn-destructive { @apply bg-destructive text-white shadow-xs hover:bg-destructive/90 active:bg-destructive/80 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60; }
179
180
  .btn-outline { @apply border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground active:bg-accent/70 dark:bg-input/30 dark:border-input dark:hover:bg-input/50 dark:active:bg-input/70; }
180
181
  .btn-secondary { @apply bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80 active:bg-secondary/70; }
@@ -197,6 +198,20 @@
197
198
  .label { @apply text-sm font-medium leading-none; }
198
199
  .native-select { @apply flex h-8 w-full items-center rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-xs outline-none transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive dark:bg-input/30 disabled:cursor-not-allowed disabled:opacity-50; }
199
200
 
201
+ /* ---- native primitives styled shadcn-flavored (muten core ships Checkbox/Number/Range/Date/Chart) ---- */
202
+ .mu-checkbox { @apply inline-flex items-center gap-2 text-sm; }
203
+ .mu-checkbox input, .mu-field-check { @apply size-4 rounded-[4px] border-input; accent-color: var(--primary); }
204
+ .mu-number, .mu-date { @apply flex h-8 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-xs outline-none transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 dark:bg-input/30; }
205
+ .mu-range { @apply w-full; accent-color: var(--primary); }
206
+ .mu-chart-bar, .mu-chart-dot, .mu-chart-slice { fill: var(--primary); }
207
+ .mu-chart-slice { stroke: var(--card); stroke-width: 2.5; }
208
+ .mu-chart-dot { stroke: var(--card); stroke-width: 2; }
209
+ .mu-chart-line { fill: none; stroke: var(--primary); stroke-width: 2.25; stroke-linecap: round; stroke-linejoin: round; }
210
+ .mu-chart-area { stroke: none; } /* fill comes from the inline accent→transparent gradient __chart draws */
211
+ .mu-chart-grid { stroke: var(--border); stroke-opacity: 0.55; }
212
+ .mu-chart-tick, .mu-chart-xlabel { fill: var(--muted-foreground); font-size: 10px; font-variant-numeric: tabular-nums; }
213
+ .mu-chart-total { fill: var(--foreground); font-size: 15px; font-weight: 650; font-variant-numeric: tabular-nums; }
214
+
200
215
  /* ---- Card ---- */
201
216
  .card { @apply bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm; }
202
217
  .card-header { @apply grid auto-rows-min items-start gap-1.5 px-6; }
@@ -435,12 +450,14 @@
435
450
  .command-shortcut { @apply ml-auto text-xs tracking-widest text-muted-foreground; }
436
451
 
437
452
  /* ---- Table - style muten's DataTable to look shadcn: DataTable @rows columns(...) class("data-table") ---- */
438
- .data-table { @apply w-full caption-bottom text-sm; }
439
- .data-table thead tr { @apply border-b; }
440
- .data-table th { @apply h-11 px-3 text-left align-middle font-medium text-muted-foreground whitespace-nowrap; }
441
- .data-table tbody tr { @apply border-b transition-colors duration-200 hover:bg-muted/50; }
442
- .data-table tbody tr:last-child { @apply border-0; }
443
- .data-table td { @apply p-3 align-middle whitespace-nowrap; }
453
+ /* .mu-datatable is the base class the DataTable primitive always gets, so a bare `DataTable @rows columns(...)`
454
+ is styled out of the box (no class needed). Safe: DataTable is a leaf, never reused with another class. */
455
+ .data-table, .mu-datatable { @apply w-full caption-bottom text-sm; }
456
+ .data-table thead tr, .mu-datatable thead tr { @apply border-b; }
457
+ .data-table th, .mu-datatable th { @apply h-11 px-3 text-left align-middle font-medium text-muted-foreground whitespace-nowrap; }
458
+ .data-table tbody tr, .mu-datatable tbody tr { @apply border-b transition-colors duration-200 hover:bg-muted/50; }
459
+ .data-table tbody tr:last-child, .mu-datatable tbody tr:last-child { @apply border-0; }
460
+ .data-table td, .mu-datatable td { @apply p-3 align-middle whitespace-nowrap; }
444
461
 
445
462
  /* ---- Slider (Custom; the host JS builds .slider-range + .slider-thumb inside .slider) ---- */
446
463
  .slider { @apply relative h-2 w-full rounded-full bg-primary/20 cursor-pointer select-none; touch-action: none; }
@@ -715,6 +732,12 @@
715
732
  .pricing-features { @apply flex flex-col gap-2.5 flex-1; }
716
733
  .pricing-feature { @apply flex flex-row items-center gap-2 text-sm; }
717
734
 
735
+ /* ---- StatBand: a divided row of KPI stats (value + label), uniform, 2-up on mobile ---- */
736
+ .stat-band { @apply grid grid-cols-4 gap-px border-y border-border bg-border overflow-hidden max-sm:grid-cols-2; }
737
+ .stat { @apply flex flex-col items-center gap-1 bg-background px-6 py-10 text-center; }
738
+ .stat-value { @apply text-4xl font-bold tracking-tight; }
739
+ .stat-label { @apply text-sm text-muted-foreground; }
740
+
718
741
  /* ---- LogoCloud: the "trusted by" strip - a wrapping row of logos, muted until hover. GENERIC: an item holds
719
742
  ANY logo type - an <img>, an inline SVG/Icon, or a text wordmark - and each is normalized to the same height. ---- */
720
743
  .logo-cloud { @apply flex flex-row flex-wrap items-center justify-center gap-x-10 gap-y-6; }
@@ -738,6 +761,16 @@
738
761
  (the classic "call to action" micro-interaction). Respects reduced motion via the global rule below. ---- */
739
762
  .arrow-hover .mu-icon { @apply transition-transform duration-200; }
740
763
  .arrow-hover:hover .mu-icon:last-child { translate: 3px 0; }
764
+
765
+ /* ---- Touch targets. These components are sized for a mouse (h-8 = 32px); Apple HIG asks for >=44px and
766
+ Material for >=48dp before a finger hits reliably. `pointer: coarse` is the browser telling us there IS a
767
+ finger, so the SAME app adapts on a phone with no mobile build, no second plugin and no decision for the
768
+ author to make. Inside @layer components on purpose: a class("h-14") on the Button still wins.
769
+ Deliberately NOT everything h-8: `btn-xs`/`switch` are small by intent, and badges/avatars aren't targets. ---- */
770
+ @media (pointer: coarse) {
771
+ .btn, .input, .toggle, .select-trigger, .native-select { @apply h-11; }
772
+ .btn-sm { @apply h-10; }
773
+ }
741
774
  }
742
775
 
743
776
  @keyframes progress-indeterminate { 0% { left: -40%; } 100% { left: 100%; } }
@@ -754,8 +787,11 @@
754
787
  @keyframes mu-slide-in { from { opacity: 0; translate: -1.5rem 0; } to { opacity: 1; translate: 0 0; } }
755
788
  @keyframes mu-reveal-scale{ from { opacity: 0; scale: 0.92; translate: 0 1rem; } to { opacity: 1; scale: 1; translate: 0 0; } }
756
789
  /* nav pill: airy + shadowless at the top, settles + deepens its shadow once you scroll, and reverses smoothly
757
- when you return to the top (scroll-position driven, so "back to top" is free). */
758
- @keyframes mu-nav-scroll { from { scale: 1.015; box-shadow: 0 4px 12px -6px rgb(0 0 0 / 0.10); } to { scale: 0.98; box-shadow: 0 16px 36px -10px rgb(0 0 0 / 0.30); } }
790
+ when you return to the top (scroll-position driven, so "back to top" is free).
791
+ It scrubs the SHADOW ONLY. It used to scrub `scale: 1.015 → 0.98` as well, which resized the navbar by 3.5% of
792
+ its own width on every scroll — a visible jump on each landing, and blurred nav text for the whole animation.
793
+ A navbar is furniture: it may deepen, it must never change size. */
794
+ @keyframes mu-nav-scroll { from { box-shadow: 0 4px 12px -6px rgb(0 0 0 / 0.10); } to { box-shadow: 0 16px 36px -10px rgb(0 0 0 / 0.30); } }
759
795
  @keyframes mu-float { 0%, 100% { translate: 0 0; } 50% { translate: 0 -6px; } }
760
796
 
761
797
  @layer utilities {
package/motion.css ADDED
@@ -0,0 +1,49 @@
1
+ /* @muten/shadcn/motion.css — a small, tasteful set of entrance + hover animations, as CLASSES the AI can just
2
+ apply (`class("animate-rise")`). CSS-only, plays once on mount, and fully disabled under reduced-motion.
3
+ Import after globals: @import "@muten/shadcn/globals.css"; @import "@muten/shadcn/motion.css";
4
+
5
+ Vocabulary:
6
+ animate-fade-in — fade in
7
+ animate-rise — fade + slide up (the default for hero copy, cards, sections)
8
+ animate-scale-in — fade + subtle scale (badges, images, pop-ins)
9
+ animate-blur-in — fade + un-blur (editorial hero images / big type)
10
+ stagger — on a CONTAINER: its direct children rise in sequence (feature grids, lists)
11
+ hover-lift — on a card/link: lifts + deepens shadow on hover
12
+ hover-zoom — on an image WRAPPER (add `overflow-hidden`): the <img> scales gently on hover
13
+ */
14
+
15
+ @keyframes mu-fade-in { from { opacity: 0; } to { opacity: 1; } }
16
+ @keyframes mu-rise { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: none; } }
17
+ @keyframes mu-scale-in{ from { opacity: 0; transform: scale(0.96); } to { opacity: 1; transform: none; } }
18
+ @keyframes mu-blur-in { from { opacity: 0; filter: blur(12px); transform: scale(1.02); } to { opacity: 1; filter: blur(0); transform: none; } }
19
+
20
+ .animate-fade-in { animation: mu-fade-in 0.6s var(--mu-ease, cubic-bezier(0.16,1,0.3,1)) both; }
21
+ .animate-rise { animation: mu-rise 0.7s var(--mu-ease, cubic-bezier(0.16,1,0.3,1)) both; }
22
+ .animate-scale-in { animation: mu-scale-in 0.6s var(--mu-ease, cubic-bezier(0.16,1,0.3,1)) both; }
23
+ .animate-blur-in { animation: mu-blur-in 0.9s var(--mu-ease, cubic-bezier(0.16,1,0.3,1)) both; }
24
+
25
+ /* Stagger: children rise in, each ~80ms after the last (up to 12; more just share the last delay). */
26
+ .stagger > * { animation: mu-rise 0.7s var(--mu-ease, cubic-bezier(0.16,1,0.3,1)) both; }
27
+ .stagger > *:nth-child(1) { animation-delay: 0.00s; }
28
+ .stagger > *:nth-child(2) { animation-delay: 0.08s; }
29
+ .stagger > *:nth-child(3) { animation-delay: 0.16s; }
30
+ .stagger > *:nth-child(4) { animation-delay: 0.24s; }
31
+ .stagger > *:nth-child(5) { animation-delay: 0.32s; }
32
+ .stagger > *:nth-child(6) { animation-delay: 0.40s; }
33
+ .stagger > *:nth-child(7) { animation-delay: 0.48s; }
34
+ .stagger > *:nth-child(8) { animation-delay: 0.56s; }
35
+ .stagger > *:nth-child(9) { animation-delay: 0.64s; }
36
+ .stagger > *:nth-child(10) { animation-delay: 0.72s; }
37
+ .stagger > *:nth-child(11) { animation-delay: 0.80s; }
38
+ .stagger > *:nth-child(n+12) { animation-delay: 0.88s; }
39
+
40
+ /* Hover affordances. */
41
+ .hover-lift { transition: transform var(--mu-motion, 200ms) var(--mu-ease, cubic-bezier(0.16,1,0.3,1)), box-shadow var(--mu-motion, 200ms) ease; }
42
+ .hover-lift:hover { transform: translateY(-4px); box-shadow: var(--shadow-soft, 0 12px 32px -12px rgb(0 0 0 / 0.25)); }
43
+ .hover-zoom img { transition: transform 0.5s var(--mu-ease, cubic-bezier(0.16,1,0.3,1)); }
44
+ .hover-zoom:hover img { transform: scale(1.05); }
45
+
46
+ @media (prefers-reduced-motion: reduce) {
47
+ .animate-fade-in, .animate-rise, .animate-scale-in, .animate-blur-in, .stagger > * { animation: none !important; }
48
+ .hover-lift, .hover-zoom img { transition: none !important; }
49
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@muten/shadcn",
3
- "version": "0.0.4",
3
+ "version": "0.0.7",
4
4
  "description": "shadcn-style component registry for muten - own-the-source parts you add with `muten add`.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -15,10 +15,15 @@
15
15
  "registry",
16
16
  "registry.json",
17
17
  "globals.css",
18
- "README.md"
18
+ "motion.css",
19
+ "themes",
20
+ "README.md",
21
+ "AGENTS.md"
19
22
  ],
20
23
  "exports": {
21
24
  "./globals.css": "./globals.css",
25
+ "./motion.css": "./motion.css",
26
+ "./themes/*": "./themes/*",
22
27
  "./registry.json": "./registry.json",
23
28
  "./registry/*": "./registry/*"
24
29
  },
@@ -0,0 +1,9 @@
1
+ # BentoGallery - a tiling gallery on a fixed row-height grid, so cells of different column widths always line up.
2
+ # Each BentoCell picks its own col-span (and bg / content) via class:
3
+ # BentoGallery {
4
+ # BentoCell class("col-span-4 bg-muted") { Span "01" }
5
+ # BentoCell class("col-span-2 bg-accent") { Span "02" }
6
+ # }
7
+ # shadcn-for-muten · block · no deps · uses .bento-gallery / .bento-cell from globals.css.
8
+ part BentoGallery() { Stack class("bento-gallery") { slot } }
9
+ part BentoCell() { Stack class("bento-cell") { slot } }
@@ -0,0 +1,4 @@
1
+ # ButtonGroup - segmented row of buttons that share borders. Put `btn btn-outline` buttons inside:
2
+ # ButtonGroup { Button "Bold" -> b class("btn btn-outline") Button "Italic" -> i class("btn btn-outline") }
3
+ # shadcn-for-muten · part · no deps · uses .btn-group from globals.css.
4
+ part ButtonGroup() { Stack class("btn-group") { slot } }
@@ -0,0 +1,8 @@
1
+ # Button (part name Btn - `Button` is a muten primitive, so a part can't reuse that name) - a plug-and-play
2
+ # button. No class props: pass a `variant` + your action, put the label/icon in the slot.
3
+ # Btn(variant: "default", onClick: save) { Span "Save" }
4
+ # Btn(variant: "outline", onClick: cancel) { Icon "lucide:x" class("size-4") Span "Cancel" }
5
+ # variant: default | secondary | outline | ghost | destructive | link. For a link that looks like a button use
6
+ # `Link "..." -> "/path" class("btn btn-default")`; for sizes add btn-sm|btn-lg on the primitive.
7
+ # shadcn-for-muten · part · no deps · wraps the Button primitive + the .btn recipe.
8
+ part Btn(variant: text, onClick: action) { Button -> $onClick class("btn btn-{$variant}") { slot } }
@@ -0,0 +1,8 @@
1
+ # FeatureRows - a divided list of numbered feature/schedule rows, each with its own side gutters so the index
2
+ # never kisses the edge. A FeatureRow is a 12-col grid (index · title · copy); it stacks on mobile.
3
+ # FeatureRows {
4
+ # FeatureRow { Span "01" class("col-span-1") Title "One command" h3 class("col-span-4") Text "..." class("col-span-7") }
5
+ # }
6
+ # shadcn-for-muten · block · no deps · uses .feature-rows / .feature-row from globals.css.
7
+ part FeatureRows() { Stack class("feature-rows") { slot } }
8
+ part FeatureRow() { Stack class("feature-row") { slot } }
@@ -0,0 +1,4 @@
1
+ # InputGroup - an input with a leading icon / addon in one bordered field:
2
+ # InputGroup { Icon "lucide:search" SearchField bind(q) "Search..." class("input-group-control") }
3
+ # shadcn-for-muten · part · no deps · uses .input-group from globals.css.
4
+ part InputGroup() { Stack class("input-group") { slot } }
@@ -0,0 +1,7 @@
1
+ # Input (part name Input - muten's text-input primitive is `SearchField`, so this wraps it) - a plug-and-play
2
+ # text input. No class props: pass a state to `value` (two-way bound) + a placeholder.
3
+ # state { email = "" : text }
4
+ # Input(value: email, placeholder: "you@example.com")
5
+ # For typed fields (email/number/password/date) or validation, use a `Form` over an entity instead.
6
+ # shadcn-for-muten · part · no deps · wraps SearchField + the .input recipe.
7
+ part Input(value: text, placeholder: text) { SearchField bind($value) "{$placeholder}" class("input") }
@@ -0,0 +1,3 @@
1
+ # Kbd - a keyboard key hint. `Kbd { Span "Ctrl" }` or with class for combos.
2
+ # shadcn-for-muten · part · no deps · uses .kbd from globals.css.
3
+ part Kbd() { Span class("kbd") { slot } }
@@ -0,0 +1,7 @@
1
+ # LinkBtn - a navigation link that looks like a button (plug-and-play, no class props). For CTAs that go to a
2
+ # route. Put the label/icon in the slot.
3
+ # LinkBtn(to: "/signup", variant: "default") { Span "Get started" }
4
+ # LinkBtn(to: "/docs", variant: "outline") { Span "Read the docs" Icon "lucide:arrow-right" class("size-4") }
5
+ # variant: default | secondary | outline | ghost | destructive | link.
6
+ # shadcn-for-muten · part · no deps · wraps the Link primitive + the .btn recipe.
7
+ part LinkBtn(to: text, variant: text) { Link -> "{$to}" class("btn btn-{$variant}") { slot } }
@@ -0,0 +1,10 @@
1
+ # NavPill - the floating glass navbar, batteries included: a sticky + padded wrapper, the glass pill, AND the
2
+ # scroll animation (settles + returns to rest at the top). Just drop your logo / links / CTA inside; it lays them
3
+ # out with space-between (logo left, the rest right).
4
+ # NavPill {
5
+ # Stack class("flex flex-row items-center gap-2") { Icon "lucide:hexagon" class("size-5 text-primary") Span "Acme" class("font-bold") }
6
+ # Stack class("flex flex-row items-center gap-6 text-sm text-muted-foreground max-md:hidden") { Link "Features" -> "/" class("link-underline") Link "Pricing" -> "/" class("link-underline") }
7
+ # Link "Get started" -> "/" class("btn btn-default btn-sm press")
8
+ # }
9
+ # shadcn-for-muten · part · no deps · uses .nav-pill + nav-scroll from globals.css.
10
+ part NavPill() { Stack class("sticky top-0 z-50 px-4 pt-4") { Header class("nav-pill nav-scroll mx-auto w-full max-w-5xl") { slot } } }
@@ -0,0 +1,4 @@
1
+ # Skeleton - a loading placeholder. Add the size utilities via class (paramless part, so class() works):
2
+ # Skeleton class("h-4 w-32") · Skeleton class("size-10 rounded-full")
3
+ # shadcn-for-muten · part · no deps · uses .skeleton (pulse) from globals.css.
4
+ part Skeleton() { Stack class("skeleton") { slot } }
@@ -0,0 +1,9 @@
1
+ # StatBand - a divided row of stats (KPIs), uniform + responsive (2-up on mobile). Each Stat holds its value in
2
+ # the slot (a Span, or a `Custom CountUp` for the count-up animation) plus a label:
3
+ # StatBand {
4
+ # Stat(label: "trees planted") { Custom CountUp inputs(to: "2.4M") class("stat-value") }
5
+ # Stat(label: "countries") { Span "58" class("stat-value") }
6
+ # }
7
+ # shadcn-for-muten · block · no deps · uses .stat-band / .stat / .stat-value / .stat-label from globals.css.
8
+ part StatBand() { Stack class("stat-band") { slot } }
9
+ part Stat(label: text) { Stack class("stat") { slot Span "{$label}" class("stat-label") } }
package/registry.json CHANGED
@@ -94,13 +94,6 @@
94
94
  "description": "A bordered row: ItemContent / ItemTitle / ItemDescription.",
95
95
  "deps": []
96
96
  },
97
- {
98
- "name": "checkbox",
99
- "part": "Checkbox",
100
- "file": "registry/checkbox.muten",
101
- "description": "A checkbox; the page owns the bool + the toggle action.",
102
- "deps": []
103
- },
104
97
  {
105
98
  "name": "toggle",
106
99
  "part": "Toggle",
@@ -199,13 +192,6 @@
199
192
  "description": "Richer content shown on hover (pure CSS, no state).",
200
193
  "deps": []
201
194
  },
202
- {
203
- "name": "select",
204
- "part": "Select",
205
- "file": "registry/select.muten",
206
- "description": "A value picker with SelectItem; page owns the value text + open bool.",
207
- "deps": []
208
- },
209
195
  {
210
196
  "name": "scroll-area",
211
197
  "part": "ScrollArea",
@@ -302,14 +288,6 @@
302
288
  "description": "A slide carousel over a list (Custom). Page owns the slides list.",
303
289
  "deps": []
304
290
  },
305
- {
306
- "name": "chart",
307
- "part": "Chart",
308
- "file": "registry/chart.muten",
309
- "component": "Chart",
310
- "description": "An SVG bar chart over a list of { label, value } (Custom).",
311
- "deps": []
312
- },
313
291
  {
314
292
  "name": "gradient-blinds",
315
293
  "part": "GradientBlinds",
@@ -545,22 +523,90 @@
545
523
  "component": "CountUp",
546
524
  "description": "Animate a formatted number (2.4M / 94% / 40ms / 99.99%) from 0 when it scrolls into view; lands on the exact target. Custom. Styled form (takes the class): `Custom CountUp inputs(to: \"2.4M\") class(\"text-4xl font-bold\")`. For stat bands + hero metrics.",
547
525
  "deps": []
526
+ },
527
+ {
528
+ "name": "navpill",
529
+ "part": "NavPill",
530
+ "file": "registry/navpill.muten",
531
+ "description": "BLOCK: the floating glass navbar (sticky wrapper + glass pill + scroll animation baked in). Drop logo/links/CTA inside.",
532
+ "deps": []
533
+ },
534
+ {
535
+ "name": "stat-band",
536
+ "part": "StatBand",
537
+ "file": "registry/stat-band.muten",
538
+ "description": "BLOCK: a divided row of KPI stats. Parts: StatBand, Stat(label) - value goes in the slot (Span or Custom CountUp).",
539
+ "deps": []
540
+ },
541
+ {
542
+ "name": "feature-rows",
543
+ "part": "FeatureRows",
544
+ "file": "registry/feature-rows.muten",
545
+ "description": "BLOCK: numbered feature/schedule rows with side gutters (12-col grid inside). Parts: FeatureRows, FeatureRow.",
546
+ "deps": []
547
+ },
548
+ {
549
+ "name": "bento",
550
+ "part": "BentoGallery",
551
+ "file": "registry/bento.muten",
552
+ "description": "BLOCK: fixed-row-height tiling gallery; cells pick their col-span. Parts: BentoGallery, BentoCell.",
553
+ "deps": []
554
+ },
555
+ {
556
+ "name": "button-group",
557
+ "part": "ButtonGroup",
558
+ "file": "registry/button-group.muten",
559
+ "description": "A segmented row of buttons that share borders. Put btn-outline buttons inside.",
560
+ "deps": []
561
+ },
562
+ {
563
+ "name": "input-group",
564
+ "part": "InputGroup",
565
+ "file": "registry/input-group.muten",
566
+ "description": "An input with a leading icon/addon in one bordered field.",
567
+ "deps": []
568
+ },
569
+ {
570
+ "name": "skeleton",
571
+ "part": "Skeleton",
572
+ "file": "registry/skeleton.muten",
573
+ "description": "A loading placeholder (pulse). Add size via class: Skeleton class(\"h-4 w-32\").",
574
+ "deps": []
575
+ },
576
+ {
577
+ "name": "kbd",
578
+ "part": "Kbd",
579
+ "file": "registry/kbd.muten",
580
+ "description": "A keyboard key hint. Kbd { Span \"Ctrl\" }.",
581
+ "deps": []
582
+ },
583
+ {
584
+ "name": "button",
585
+ "part": "Btn",
586
+ "file": "registry/button.muten",
587
+ "description": "Plug-and-play button (part Btn - Button is a primitive). `Btn(variant: \"default\", onClick: save) { Span \"Save\" }`. variant: default|secondary|outline|ghost|destructive|link.",
588
+ "deps": []
589
+ },
590
+ {
591
+ "name": "input",
592
+ "part": "Input",
593
+ "file": "registry/input.muten",
594
+ "description": "Plug-and-play text input (part Input wraps SearchField). `Input(value: q, placeholder: \"...\")` - value is two-way bound. For typed/validated fields use a Form.",
595
+ "deps": []
596
+ },
597
+ {
598
+ "name": "link-btn",
599
+ "part": "LinkBtn",
600
+ "file": "registry/link-btn.muten",
601
+ "description": "A navigation link styled as a button. `LinkBtn(to: \"/signup\", variant: \"default\") { Span \"Get started\" }`.",
602
+ "deps": []
548
603
  }
549
604
  ],
550
605
  "classComponents": {
551
606
  "note": "Size/spacing-driven primitives ship as classes in globals.css (no `muten add` needed once you @import it). Style the muten primitive with these:",
552
- "button": "Button \"Save\" -> save class(\"btn btn-default\") (variants: btn-default|btn-secondary|btn-outline|btn-ghost|btn-destructive|btn-link · sizes: btn-sm|btn-lg|btn-icon)",
553
- "input": "SearchField bind(q) class(\"input\")",
554
607
  "textarea": "class(\"textarea\") · label: class(\"label\") · native select: class(\"native-select\")",
555
- "skeleton": "Stack class(\"skeleton h-4 w-32\") (add the size utilities)",
556
- "kbd": "Span \"Ctrl\" class(\"kbd\")",
557
608
  "typography": "Title \"Docs\" h1 class(\"prose-h1\") (prose-h1..h4 · prose-p|lead|large|small|muted|blockquote|code|list)",
558
- "button-group": "Stack class(\"btn-group\") { Button \"A\" class(\"btn btn-outline\") Button \"B\" class(\"btn btn-outline\") }",
559
- "input-group": "Stack class(\"input-group\") { Icon \"lucide:search\" SearchField bind(q) class(\"input-group-control\") }",
560
- "nav-pill": "Header class(\"nav-pill\") - the floating glass navbar; wrap in Stack class(\"sticky top-0 z-50 px-4 pt-4\"). Add `nav-scroll` so it settles + deepens its shadow on scroll and returns to rest at the top.",
561
- "feature-rows": "Stack class(\"feature-rows\") { Stack class(\"feature-row\") { Span \"01\" ... } } - numbered feature/schedule rows with baked side gutters (grid-cols-12 inside, stacks on mobile).",
562
- "bento-gallery": "Stack class(\"bento-gallery\") { Stack class(\"bento-cell col-span-4\") {} Stack class(\"bento-cell col-span-2\") {} } - a fixed-row-height tiling gallery; children pick their col-span so every row lines up.",
563
- "logo-cloud": "LogoCloud { LogoCloudItem { Span \"Acme\" } LogoCloudItem { Icon \"lucide:hexagon\" Span \"Northwind\" } } - a 'trusted by' strip, muted+grayscale until hover; accepts any logo type (image / icon / wordmark).",
609
+ "table": "DataTable @rows columns(name, email, status) - STYLED BY DEFAULT (no class needed). `class(\"data-table\")` still works as an explicit alias. Raw cells; for badge/formatted cells build with each + Stack.",
564
610
  "motion": "Page animations, all CSS + reduced-motion safe. Entrance: animate-fade-up|fade-in|scale-in|slide-in (+ d1..d6 stagger). Scroll: reveal|reveal-left|reveal-scale, and nav-scroll on a navbar. Hover/press: hover-lift, hover-glow, arrow-hover (nudges a trailing Icon), link-underline, press, float."
565
611
  }
566
612
  }
@@ -0,0 +1,53 @@
1
+ /* @muten/shadcn/themes/bloom.css — "Bloom": soft, friendly, rounded. A rounded display (Quicksand) + Inter, gentle
2
+ pastel tints (lavender paper, violet accent), large radius, airy soft shadows. Import AFTER globals.css.
3
+ @import "@muten/shadcn/globals.css"; @import "@muten/shadcn/themes/bloom.css"; */
4
+
5
+ @import url("https://fonts.googleapis.com/css2?family=Quicksand:wght@500;600;700&family=Inter:wght@400;500;600&display=swap");
6
+ @theme {
7
+ --font-sans: "Inter", ui-sans-serif, system-ui, sans-serif;
8
+ --font-serif: "Quicksand", ui-rounded, ui-sans-serif, system-ui, sans-serif; /* `font-serif` on headings → rounded display */
9
+ }
10
+
11
+ :root {
12
+ --radius: 1rem; /* pillowy */
13
+ --background: oklch(0.99 0.008 310);
14
+ --foreground: oklch(0.30 0.04 300);
15
+ --card: oklch(1 0.004 310);
16
+ --card-foreground: oklch(0.30 0.04 300);
17
+ --popover: oklch(1 0.004 310);
18
+ --popover-foreground: oklch(0.30 0.04 300);
19
+ --primary: oklch(0.62 0.19 300); /* soft violet */
20
+ --primary-foreground: oklch(0.99 0.008 310);
21
+ --secondary: oklch(0.95 0.03 310);
22
+ --secondary-foreground: oklch(0.40 0.06 300);
23
+ --muted: oklch(0.955 0.022 315);
24
+ --muted-foreground: oklch(0.55 0.05 300);
25
+ --accent: oklch(0.93 0.04 340); /* rose tint */
26
+ --accent-foreground: oklch(0.40 0.08 340);
27
+ --destructive: oklch(0.60 0.20 20);
28
+ --border: oklch(0.91 0.025 315);
29
+ --input: oklch(0.91 0.025 315);
30
+ --ring: oklch(0.62 0.19 300);
31
+ }
32
+ /* bridge both selectors: muten sets `data-theme="dark"`, shadcn keys off the `.dark` class (globals.css does the same). */
33
+ .dark, [data-theme="dark"] {
34
+ --background: oklch(0.21 0.03 300);
35
+ --foreground: oklch(0.95 0.02 315);
36
+ --card: oklch(0.25 0.035 300);
37
+ --card-foreground: oklch(0.95 0.02 315);
38
+ --popover: oklch(0.25 0.035 300);
39
+ --popover-foreground: oklch(0.95 0.02 315);
40
+ --primary: oklch(0.72 0.17 300);
41
+ --primary-foreground: oklch(0.20 0.03 300);
42
+ --secondary: oklch(0.30 0.035 300);
43
+ --secondary-foreground: oklch(0.95 0.02 315);
44
+ --muted: oklch(0.30 0.035 300);
45
+ --muted-foreground: oklch(0.75 0.03 310);
46
+ --accent: oklch(0.34 0.05 340);
47
+ --accent-foreground: oklch(0.95 0.02 315);
48
+ --destructive: oklch(0.68 0.18 20);
49
+ --border: oklch(0.33 0.035 300);
50
+ --input: oklch(0.35 0.035 300);
51
+ --ring: oklch(0.72 0.17 300);
52
+ }
53
+ @theme { --shadow-soft: 0 2px 8px oklch(0.5 0.1 300 / 0.06), 0 16px 40px -16px oklch(0.5 0.15 300 / 0.20); }
@@ -0,0 +1,65 @@
1
+ /* @muten/shadcn/themes/editorial.css — "Editorial": a warm, quiet, luxury look. A display serif (Fraunces) + a
2
+ clean sans (Inter), sand/stone neutrals with a terracotta accent, generous radius, soft warm shadows.
3
+ Import AFTER globals.css: @import "@muten/shadcn/globals.css"; @import "@muten/shadcn/themes/editorial.css";
4
+ Works in light (default) and dark. `font-serif` now renders Fraunces, so headings feel editorial for free. */
5
+
6
+ @import url("https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,400;9..144,500;9..144,600;9..144,700&family=Inter:wght@400;500;600&display=swap");
7
+
8
+ /* Tailwind picks these up: `font-sans` → Inter, `font-serif` → Fraunces (use it on headings). */
9
+ @theme {
10
+ --font-sans: "Inter", ui-sans-serif, system-ui, sans-serif;
11
+ --font-serif: "Fraunces", ui-serif, Georgia, "Times New Roman", serif;
12
+ }
13
+
14
+ /* LIGHT — warm sand paper, deep-ink text, terracotta as the single accent (primary = buttons + `text-primary`). */
15
+ :root {
16
+ --radius: 0.625rem;
17
+ --background: oklch(0.985 0.007 83);
18
+ --foreground: oklch(0.24 0.017 55);
19
+ --card: oklch(0.997 0.004 83);
20
+ --card-foreground: oklch(0.24 0.017 55);
21
+ --popover: oklch(0.997 0.004 83);
22
+ --popover-foreground: oklch(0.24 0.017 55);
23
+ --primary: oklch(0.62 0.115 46); /* terracotta */
24
+ --primary-foreground: oklch(0.99 0.006 83);
25
+ --secondary: oklch(0.955 0.011 80);
26
+ --secondary-foreground: oklch(0.30 0.02 50);
27
+ --muted: oklch(0.955 0.011 80);
28
+ --muted-foreground: oklch(0.52 0.018 60);
29
+ --accent: oklch(0.94 0.016 74); /* subtle warm hover bg (stays neutral, per shadcn) */
30
+ --accent-foreground: oklch(0.30 0.02 50);
31
+ --destructive: oklch(0.577 0.245 27.325);
32
+ --border: oklch(0.905 0.012 76);
33
+ --input: oklch(0.905 0.012 76);
34
+ --ring: oklch(0.62 0.115 46);
35
+ }
36
+
37
+ /* DARK — warm near-black, cream text, terracotta glows. */
38
+ /* bridge both selectors: muten sets `data-theme="dark"`, shadcn keys off the `.dark` class (globals.css does the same). */
39
+ .dark, [data-theme="dark"] {
40
+ --background: oklch(0.195 0.012 58);
41
+ --foreground: oklch(0.955 0.008 83);
42
+ --card: oklch(0.235 0.014 58);
43
+ --card-foreground: oklch(0.955 0.008 83);
44
+ --popover: oklch(0.235 0.014 58);
45
+ --popover-foreground: oklch(0.955 0.008 83);
46
+ --primary: oklch(0.70 0.128 47); /* terracotta, brighter for dark */
47
+ --primary-foreground: oklch(0.20 0.012 58);
48
+ --secondary: oklch(0.28 0.014 58);
49
+ --secondary-foreground: oklch(0.955 0.008 83);
50
+ --muted: oklch(0.28 0.014 58);
51
+ --muted-foreground: oklch(0.72 0.013 72);
52
+ --accent: oklch(0.30 0.015 58);
53
+ --accent-foreground: oklch(0.955 0.008 83);
54
+ --destructive: oklch(0.65 0.19 25);
55
+ --border: oklch(0.31 0.014 58);
56
+ --input: oklch(0.33 0.014 58);
57
+ --ring: oklch(0.70 0.128 47);
58
+ }
59
+
60
+ /* Editorial polish that the token vars alone can't express. */
61
+ body { font-feature-settings: "liga" 1, "calt" 1; -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility; }
62
+ /* Fraunces headings: a touch of optical weight + tight tracking reads as "designed", not default-serif. */
63
+ h1, h2, h3, .font-serif { letter-spacing: -0.01em; }
64
+ /* soft, warm-tinted elevation (shadcn ships flat) — reach for it on cards/hero via `shadow-soft`. */
65
+ @theme { --shadow-soft: 0 1px 2px oklch(0.4 0.03 60 / 0.04), 0 8px 24px -12px oklch(0.4 0.05 60 / 0.14); }
@@ -0,0 +1,54 @@
1
+ /* @muten/shadcn/themes/noir.css — "Noir": crisp, high-contrast, mono-minimal. A grotesk display (Space Grotesk) +
2
+ Inter, near-monochrome cool neutrals, sharp corners, one stark ink accent. Import AFTER globals.css.
3
+ @import "@muten/shadcn/globals.css"; @import "@muten/shadcn/themes/noir.css"; */
4
+
5
+ @import url("https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=Inter:wght@400;500;600&display=swap");
6
+ @theme {
7
+ --font-sans: "Inter", ui-sans-serif, system-ui, sans-serif;
8
+ --font-serif: "Space Grotesk", ui-sans-serif, system-ui, sans-serif; /* `font-serif` on headings → grotesk display */
9
+ }
10
+
11
+ :root {
12
+ --radius: 0.25rem; /* sharp */
13
+ --background: oklch(0.995 0 0);
14
+ --foreground: oklch(0.16 0.004 260);
15
+ --card: oklch(1 0 0);
16
+ --card-foreground: oklch(0.16 0.004 260);
17
+ --popover: oklch(1 0 0);
18
+ --popover-foreground: oklch(0.16 0.004 260);
19
+ --primary: oklch(0.16 0.004 260); /* ink */
20
+ --primary-foreground: oklch(0.99 0 0);
21
+ --secondary: oklch(0.965 0.002 260);
22
+ --secondary-foreground: oklch(0.16 0.004 260);
23
+ --muted: oklch(0.965 0.002 260);
24
+ --muted-foreground: oklch(0.48 0.006 260);
25
+ --accent: oklch(0.95 0.003 260);
26
+ --accent-foreground: oklch(0.16 0.004 260);
27
+ --destructive: oklch(0.577 0.245 27.325);
28
+ --border: oklch(0.90 0.004 260);
29
+ --input: oklch(0.90 0.004 260);
30
+ --ring: oklch(0.16 0.004 260);
31
+ }
32
+ /* bridge both selectors: muten sets `data-theme="dark"`, shadcn keys off the `.dark` class (globals.css does the same). */
33
+ .dark, [data-theme="dark"] {
34
+ --background: oklch(0.145 0.004 260);
35
+ --foreground: oklch(0.98 0 0);
36
+ --card: oklch(0.185 0.005 260);
37
+ --card-foreground: oklch(0.98 0 0);
38
+ --popover: oklch(0.185 0.005 260);
39
+ --popover-foreground: oklch(0.98 0 0);
40
+ --primary: oklch(0.98 0 0); /* stark white on black */
41
+ --primary-foreground: oklch(0.16 0.004 260);
42
+ --secondary: oklch(0.24 0.006 260);
43
+ --secondary-foreground: oklch(0.98 0 0);
44
+ --muted: oklch(0.24 0.006 260);
45
+ --muted-foreground: oklch(0.68 0.006 260);
46
+ --accent: oklch(0.26 0.006 260);
47
+ --accent-foreground: oklch(0.98 0 0);
48
+ --destructive: oklch(0.65 0.19 25);
49
+ --border: oklch(0.27 0.006 260);
50
+ --input: oklch(0.30 0.006 260);
51
+ --ring: oklch(0.98 0 0);
52
+ }
53
+ .font-serif, h1, h2, h3 { letter-spacing: -0.02em; }
54
+ @theme { --shadow-soft: 0 1px 2px oklch(0 0 0 / 0.05), 0 12px 28px -14px oklch(0 0 0 / 0.18); }
package/registry/chart.js DELETED
@@ -1,34 +0,0 @@
1
- // Chart host component (muten Custom). inputs: { data } (a list of { label, value }). Renders an SVG bar chart.
2
- // Edit this file for line/area/pie variants; this default is a clean bar chart driven by the page's data.
3
- export function mount(el, inputs, handlers) {
4
- const NS = 'http://www.w3.org/2000/svg';
5
- const render = (data) => {
6
- el.innerHTML = '';
7
- const items = Array.isArray(data) ? data : [];
8
- const W = 480, H = 200, pad = 24, gap = 10;
9
- const max = Math.max(1, ...items.map((d) => Number(d.value) || 0));
10
- const bw = items.length ? (W - pad * 2 - gap * (items.length - 1)) / items.length : 0;
11
- const svg = document.createElementNS(NS, 'svg');
12
- svg.setAttribute('viewBox', '0 0 ' + W + ' ' + H);
13
- svg.setAttribute('class', 'chart-svg');
14
- items.forEach((d, i) => {
15
- const v = Number(d.value) || 0;
16
- const bh = (v / max) * (H - pad * 2);
17
- const x = pad + i * (bw + gap);
18
- const y = H - pad - bh;
19
- const rect = document.createElementNS(NS, 'rect');
20
- rect.setAttribute('x', x); rect.setAttribute('y', y);
21
- rect.setAttribute('width', bw); rect.setAttribute('height', bh);
22
- rect.setAttribute('rx', 4); rect.setAttribute('class', 'chart-bar');
23
- svg.appendChild(rect);
24
- const t = document.createElementNS(NS, 'text');
25
- t.setAttribute('x', x + bw / 2); t.setAttribute('y', H - pad + 14);
26
- t.setAttribute('text-anchor', 'middle'); t.setAttribute('class', 'chart-label');
27
- t.textContent = d.label == null ? '' : String(d.label);
28
- svg.appendChild(t);
29
- });
30
- el.appendChild(svg);
31
- };
32
- render(inputs.data);
33
- return (n) => render(n.data);
34
- }
@@ -1,7 +0,0 @@
1
- # Chart - an SVG bar chart over a list of { label, value }. The PAGE owns the data:
2
- # entity Metric { label text value number } state { data = query data : list<Metric> } mock { data: [ … ] }
3
- # Chart(data: data)
4
- # shadcn-for-muten · Custom · host JS is src/components/Chart.js (edit it for line/area/pie variants).
5
- part Chart(data: list) {
6
- Custom Chart inputs(data: $data) class("chart")
7
- }
@@ -1,8 +0,0 @@
1
- # Checkbox - the PAGE owns the bool + the action that flips it (container/presentational):
2
- # state { agree = false : bool } action toggle mutates agree { agree.toggle() } Checkbox(checked: agree, onToggle: toggle)
3
- # shadcn-for-muten · part · no deps · the check always renders and scales/fades in via .checkbox-icon-on.
4
- part Checkbox(checked: bool, onToggle: action) {
5
- Button -> $onToggle aria(checked: $checked) class("checkbox", "checkbox-checked" when $checked) {
6
- Icon "lucide:check" class("checkbox-icon", "checkbox-icon-on" when $checked)
7
- }
8
- }
@@ -1,21 +0,0 @@
1
- # Select - a dropdown that shows the chosen value. The PAGE owns the value (text) + the open bool + actions:
2
- # state { fruit = "Apple" : text open = false : bool } action setFruit(v: text) mutates fruit { fruit.set(v) … }
3
- # Select(value: fruit, open: open, onToggle: t) { SelectItem(value: "Apple", current: fruit, label: "Apple", onSelect: setFruit) … }
4
- # Each SelectItem's action should set the value AND close. The backdrop catches outside clicks.
5
- # shadcn-for-muten · part · no deps · styles via the semantic .select* classes in globals.css.
6
- part Select(value: text, open: bool, onToggle: action) {
7
- Stack class("select-wrap") {
8
- Button -> $onToggle aria(expanded: $open) class("select-trigger") {
9
- Span "{$value}"
10
- Icon "lucide:chevrons-up-down" class("size-4 opacity-50")
11
- }
12
- when $open { Button -> $onToggle class("select-backdrop") }
13
- Stack class("select-content", "select-content-open" when $open) { slot }
14
- }
15
- }
16
- part SelectItem(value: text, current: text, label: text, onSelect: action) {
17
- Button -> $onSelect($value) class("select-item") {
18
- Span "{$label}"
19
- when $value == $current { Icon "lucide:check" class("size-4") }
20
- }
21
- }