@muten/shadcn 0.0.5 → 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 CHANGED
@@ -2,12 +2,21 @@
2
2
 
3
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
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
+
5
14
  ## PLUG-AND-PLAY (zero class props)
6
15
  - **Btn**(variant, onClick) { slot } · **LinkBtn**(to, variant) { slot } · **Input**(value, placeholder)
7
16
  - **DataTable** @rows columns(a, b) - STYLED BY DEFAULT, no class needed.
8
17
  - + every Part / Custom below.
9
18
 
10
- ## Parts (64) - imported by plugins {}
19
+ ## Parts (62) - imported by plugins {}
11
20
 
12
21
  - **Card** - A surface with CardHeader / CardTitle / CardDescription / CardContent / CardFooter sub-components. · +CardHeader, CardTitle, CardDescription, CardContent, CardFooter
13
22
  - **Badge** - A small status descriptor. variant: default | secondary | destructive | outline.
@@ -22,7 +31,6 @@
22
31
  - **Pagination** - Page navigation with PaginationLink / Previous / Next / Ellipsis. · +PaginationLink, PaginationPrevious, PaginationNext, PaginationEllipsis
23
32
  - **Field** - A labelled form control: FieldLabel / FieldDescription / FieldError. · +FieldLabel, FieldDescription, FieldError
24
33
  - **Item** - A bordered row: ItemContent / ItemTitle / ItemDescription. · +ItemContent, ItemTitle, ItemDescription
25
- - **Checkbox** - A checkbox; the page owns the bool + the toggle action.
26
34
  - **Toggle** - A two-state button; the page owns the bool + the toggle action.
27
35
  - **ToggleGroup** - Single-select toolbar with ToggleGroupItem; page owns a text state. · +ToggleGroupItem
28
36
  - **RadioGroup** - Single choice with RadioGroupItem; page owns a text state. · +RadioGroupItem
@@ -37,7 +45,6 @@
37
45
  - **Sheet** - A panel that slides in from the right; page owns the open bool. · +SheetTitle, SheetDescription
38
46
  - **Drawer** - A panel that slides up from the bottom; page owns the open bool.
39
47
  - **HoverCard** - Richer content shown on hover (pure CSS, no state).
40
- - **Select** - A value picker with SelectItem; page owns the value text + open bool. · +SelectItem
41
48
  - **ScrollArea** - A scrollable region with styled scrollbars.
42
49
  - **Message** - Chat Message + Bubble + Marker (variant: sent | received). · +Bubble, Marker
43
50
  - **MessageScroller** - A scrollable column of chat messages.
@@ -74,14 +81,13 @@
74
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.
75
82
  - **LinkBtn** - A navigation link styled as a button. `LinkBtn(to: "/signup", variant: "default") { Span "Get started" }`.
76
83
 
77
- ## Customs (21) - importable; some need a dep
84
+ ## Customs (20) - importable; some need a dep
78
85
 
79
86
  - **Slider** (`slider`) - A draggable range slider (Custom). Page owns the value number.
80
87
  - **InputOtp** (`input-otp`) - N single-character boxes synced to one value (Custom). Page owns the value text.
81
88
  - **Calendar** (`calendar`) - A month-grid date picker (Custom). Page owns the selected ISO date text.
82
89
  - **Toaster** (`toaster`) - Fixed toast notifications (Custom). Page bumps a trigger counter to fire one.
83
90
  - **Carousel** (`carousel`) - A slide carousel over a list (Custom). Page owns the slides list.
84
- - **Chart** (`chart`) - An SVG bar chart over a list of { label, value } (Custom).
85
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).
86
92
  - **Resizable** (`resizable`) - Two panels with a draggable splitter (Custom).
87
93
  - **ContextMenu** (`context-menu`) - Right-click an area to open a menu of items (Custom).
@@ -105,4 +111,6 @@
105
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.
106
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.
107
113
 
108
- ### Gotcha: a part can not be named after a primitive (Button/Link/Form/Image/SearchField/DataTable/Icon/Text/Title/Span) - hence Btn/Input/LinkBtn. `Part(...) class(...)` is a syntax error.
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; }
@@ -746,6 +761,16 @@
746
761
  (the classic "call to action" micro-interaction). Respects reduced motion via the global rule below. ---- */
747
762
  .arrow-hover .mu-icon { @apply transition-transform duration-200; }
748
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
+ }
749
774
  }
750
775
 
751
776
  @keyframes progress-indeterminate { 0% { left: -40%; } 100% { left: 100%; } }
@@ -762,8 +787,11 @@
762
787
  @keyframes mu-slide-in { from { opacity: 0; translate: -1.5rem 0; } to { opacity: 1; translate: 0 0; } }
763
788
  @keyframes mu-reveal-scale{ from { opacity: 0; scale: 0.92; translate: 0 1rem; } to { opacity: 1; scale: 1; translate: 0 0; } }
764
789
  /* nav pill: airy + shadowless at the top, settles + deepens its shadow once you scroll, and reverses smoothly
765
- when you return to the top (scroll-position driven, so "back to top" is free). */
766
- @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); } }
767
795
  @keyframes mu-float { 0%, 100% { translate: 0 0; } 50% { translate: 0 -6px; } }
768
796
 
769
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.5",
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,11 +15,15 @@
15
15
  "registry",
16
16
  "registry.json",
17
17
  "globals.css",
18
+ "motion.css",
19
+ "themes",
18
20
  "README.md",
19
21
  "AGENTS.md"
20
22
  ],
21
23
  "exports": {
22
24
  "./globals.css": "./globals.css",
25
+ "./motion.css": "./motion.css",
26
+ "./themes/*": "./themes/*",
23
27
  "./registry.json": "./registry.json",
24
28
  "./registry/*": "./registry/*"
25
29
  },
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",
@@ -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
- }