@motir/design-system 0.1.0

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.
Files changed (107) hide show
  1. package/README.md +126 -0
  2. package/dist/appearance.d.ts +47 -0
  3. package/dist/appearance.js +3 -0
  4. package/dist/appearance.js.map +1 -0
  5. package/dist/components/theme/AppearancePickers.d.ts +97 -0
  6. package/dist/components/theme/AppearancePickers.js +542 -0
  7. package/dist/components/theme/AppearancePickers.js.map +1 -0
  8. package/dist/components/theme/HandDrawnFilter.d.ts +23 -0
  9. package/dist/components/theme/HandDrawnFilter.js +41 -0
  10. package/dist/components/theme/HandDrawnFilter.js.map +1 -0
  11. package/dist/components/theme/ImmersiveTilt.d.ts +3 -0
  12. package/dist/components/theme/ImmersiveTilt.js +113 -0
  13. package/dist/components/theme/ImmersiveTilt.js.map +1 -0
  14. package/dist/components/theme/StyleVignette.d.ts +60 -0
  15. package/dist/components/theme/StyleVignette.js +382 -0
  16. package/dist/components/theme/StyleVignette.js.map +1 -0
  17. package/dist/components/ui/Button.d.ts +32 -0
  18. package/dist/components/ui/Button.js +92 -0
  19. package/dist/components/ui/Button.js.map +1 -0
  20. package/dist/components/ui/Card.d.ts +33 -0
  21. package/dist/components/ui/Card.js +64 -0
  22. package/dist/components/ui/Card.js.map +1 -0
  23. package/dist/components/ui/ColorSwatchPicker.d.ts +19 -0
  24. package/dist/components/ui/ColorSwatchPicker.js +71 -0
  25. package/dist/components/ui/ColorSwatchPicker.js.map +1 -0
  26. package/dist/components/ui/Combobox.d.ts +84 -0
  27. package/dist/components/ui/Combobox.js +374 -0
  28. package/dist/components/ui/Combobox.js.map +1 -0
  29. package/dist/components/ui/EmptyState.d.ts +38 -0
  30. package/dist/components/ui/EmptyState.js +73 -0
  31. package/dist/components/ui/EmptyState.js.map +1 -0
  32. package/dist/components/ui/ErrorState.d.ts +43 -0
  33. package/dist/components/ui/ErrorState.js +169 -0
  34. package/dist/components/ui/ErrorState.js.map +1 -0
  35. package/dist/components/ui/FormField.d.ts +31 -0
  36. package/dist/components/ui/FormField.js +43 -0
  37. package/dist/components/ui/FormField.js.map +1 -0
  38. package/dist/components/ui/Input.d.ts +29 -0
  39. package/dist/components/ui/Input.js +105 -0
  40. package/dist/components/ui/Input.js.map +1 -0
  41. package/dist/components/ui/Modal.d.ts +84 -0
  42. package/dist/components/ui/Modal.js +153 -0
  43. package/dist/components/ui/Modal.js.map +1 -0
  44. package/dist/components/ui/MultiSelectPicker.d.ts +73 -0
  45. package/dist/components/ui/MultiSelectPicker.js +358 -0
  46. package/dist/components/ui/MultiSelectPicker.js.map +1 -0
  47. package/dist/components/ui/Pill.d.ts +49 -0
  48. package/dist/components/ui/Pill.js +107 -0
  49. package/dist/components/ui/Pill.js.map +1 -0
  50. package/dist/components/ui/Popover.d.ts +65 -0
  51. package/dist/components/ui/Popover.js +57 -0
  52. package/dist/components/ui/Popover.js.map +1 -0
  53. package/dist/components/ui/SectionLabel.d.ts +28 -0
  54. package/dist/components/ui/SectionLabel.js +27 -0
  55. package/dist/components/ui/SectionLabel.js.map +1 -0
  56. package/dist/components/ui/Segmented.d.ts +30 -0
  57. package/dist/components/ui/Segmented.js +65 -0
  58. package/dist/components/ui/Segmented.js.map +1 -0
  59. package/dist/components/ui/Spinner.d.ts +22 -0
  60. package/dist/components/ui/Spinner.js +39 -0
  61. package/dist/components/ui/Spinner.js.map +1 -0
  62. package/dist/components/ui/Switch.d.ts +34 -0
  63. package/dist/components/ui/Switch.js +50 -0
  64. package/dist/components/ui/Switch.js.map +1 -0
  65. package/dist/components/ui/Textarea.d.ts +19 -0
  66. package/dist/components/ui/Textarea.js +72 -0
  67. package/dist/components/ui/Textarea.js.map +1 -0
  68. package/dist/components/ui/Toast.d.ts +45 -0
  69. package/dist/components/ui/Toast.js +116 -0
  70. package/dist/components/ui/Toast.js.map +1 -0
  71. package/dist/components/ui/Tooltip.d.ts +25 -0
  72. package/dist/components/ui/Tooltip.js +43 -0
  73. package/dist/components/ui/Tooltip.js.map +1 -0
  74. package/dist/contexts/theme-context.d.ts +95 -0
  75. package/dist/contexts/theme-context.js +561 -0
  76. package/dist/contexts/theme-context.js.map +1 -0
  77. package/dist/index.d.ts +41 -0
  78. package/dist/index.js +67 -0
  79. package/dist/specimen/TokensSpecimen.d.ts +13 -0
  80. package/dist/specimen/TokensSpecimen.js +1090 -0
  81. package/dist/specimen/TokensSpecimen.js.map +1 -0
  82. package/dist/theme/appearance-resolution.d.ts +59 -0
  83. package/dist/theme/appearance-resolution.js +379 -0
  84. package/dist/theme/appearance-resolution.js.map +1 -0
  85. package/dist/theme/init-script.d.ts +48 -0
  86. package/dist/theme/init-script.js +391 -0
  87. package/dist/theme/init-script.js.map +1 -0
  88. package/dist/theme/palettes.d.ts +146 -0
  89. package/dist/theme/palettes.js +85 -0
  90. package/dist/theme/palettes.js.map +1 -0
  91. package/dist/theme/styles.d.ts +351 -0
  92. package/dist/theme/styles.js +264 -0
  93. package/dist/theme/styles.js.map +1 -0
  94. package/dist/theme/tilt.d.ts +41 -0
  95. package/dist/theme/tilt.js +18 -0
  96. package/dist/theme/tilt.js.map +1 -0
  97. package/dist/theme/types.d.ts +61 -0
  98. package/dist/theme/types.js +234 -0
  99. package/dist/theme/types.js.map +1 -0
  100. package/dist/theme/typography.d.ts +144 -0
  101. package/dist/theme/typography.js +57 -0
  102. package/dist/theme/typography.js.map +1 -0
  103. package/dist/utils/cn.d.ts +16 -0
  104. package/dist/utils/cn.js +11 -0
  105. package/dist/utils/cn.js.map +1 -0
  106. package/package.json +60 -0
  107. package/theme.css +3924 -0
@@ -0,0 +1,561 @@
1
+ 'use client';
2
+ import { createContext, useState, useSyncExternalStore, useEffect, useRef, useCallback, useMemo, useContext } from 'react';
3
+ import { jsx } from 'react/jsx-runtime';
4
+
5
+ // src/theme/typography.ts
6
+ var TYPE_REGISTRY = {
7
+ motir: {
8
+ id: "motir",
9
+ name: "Motir",
10
+ tagline: "Editorial serif headlines over a clean sans body \u2014 the house pairing.",
11
+ faces: "Source Serif 4 headlines \xB7 Inter body \xB7 JetBrains Mono meta.",
12
+ designDoc: "docs/typography/motir.md"
13
+ },
14
+ "motir-sans": {
15
+ id: "motir-sans",
16
+ name: "Motir Sans",
17
+ tagline: "All-sans \u2014 Inter for headlines and body; structural, no serif.",
18
+ faces: "Inter headlines + body \xB7 JetBrains Mono meta.",
19
+ designDoc: "docs/typography/motir-sans.md"
20
+ },
21
+ "motir-mono": {
22
+ id: "motir-mono",
23
+ name: "Motir Mono",
24
+ tagline: "Monospace throughout \u2014 a code-native UI, headlines, body and all.",
25
+ faces: "JetBrains Mono headlines + body/UI + meta.",
26
+ designDoc: "docs/typography/motir-mono.md"
27
+ },
28
+ grotesk: {
29
+ id: "grotesk",
30
+ name: "Grotesk",
31
+ tagline: "Geometric neo-grotesque across the whole UI \u2014 tight, confident, product-y.",
32
+ faces: "Space Grotesk headlines + body/UI \xB7 JetBrains Mono meta.",
33
+ designDoc: "docs/typography/grotesk.md"
34
+ },
35
+ editorial: {
36
+ id: "editorial",
37
+ name: "Editorial",
38
+ tagline: "A characterful display serif over a humanist sans \u2014 magazine, considered.",
39
+ faces: "Fraunces display headlines \xB7 Inter body \xB7 JetBrains Mono meta.",
40
+ designDoc: "docs/typography/editorial.md"
41
+ },
42
+ "mono-technical": {
43
+ id: "mono-technical",
44
+ name: "Mono-Technical",
45
+ tagline: "IBM Plex Mono throughout \u2014 a precise, developer-grade UI.",
46
+ faces: "IBM Plex Mono headlines + body/UI + meta/code.",
47
+ designDoc: "docs/typography/mono-technical.md"
48
+ }
49
+ };
50
+ function isTypeId(value) {
51
+ return typeof value === "string" && Object.prototype.hasOwnProperty.call(TYPE_REGISTRY, value);
52
+ }
53
+
54
+ // src/theme/styles.ts
55
+ var STYLE_REGISTRY = {
56
+ "warm-editorial": {
57
+ id: "warm-editorial",
58
+ name: "Warm Editorial",
59
+ tagline: "Thoughtful, warm, technical-but-not-cold, slightly editorial.",
60
+ inspiration: "Notion's warm palette + Source Serif headlines over Inter body.",
61
+ designDoc: "docs/styles/warm-editorial.md",
62
+ defaultTypeId: "motir",
63
+ dimensions: {
64
+ silhouette: "Sober rectangles \u2014 8px buttons, 12px cards. Restrained, document-like.",
65
+ stroke: "Hairline borders (1px warm-grey); structure drawn quietly, never heavy.",
66
+ elevation: "Modest, low-spread shadows; surfaces sit close to the page.",
67
+ surface: "Opaque cream surfaces over a white canvas; pastel tints for feature cards only.",
68
+ density: "Comfortable default rhythm \u2014 18\xD710 button padding, 24px card padding.",
69
+ motion: "Brisk 150ms ease; understated, gets out of the way.",
70
+ typography: "Source Serif 4 editorial headlines + Inter body + JetBrains Mono meta.",
71
+ components: "Rectangular buttons/inputs, hairline-bordered cards, badge pills for status only."
72
+ }
73
+ },
74
+ "soft-playful": {
75
+ id: "soft-playful",
76
+ name: "Soft / Playful",
77
+ tagline: "More energy \u2014 rounded, generous, gently animated.",
78
+ inspiration: "Figma's pill-shape language (50px pills, roomy spacing).",
79
+ designDoc: "docs/styles/soft-playful.md",
80
+ defaultTypeId: "motir",
81
+ dimensions: {
82
+ silhouette: "Pill buttons (fully rounded) and large 24px card/input radii. Friendly, bubbly.",
83
+ stroke: "Same hairline borders as the base; identity comes from radius, not stroke weight.",
84
+ elevation: "Softer, more diffused shadows \u2014 surfaces float a touch more.",
85
+ surface: "Opaque, like the base \u2014 colour stays the palette axis; only the shape softens.",
86
+ density: "Roomier \u2014 20\xD712 button padding, 28px card padding, taller controls.",
87
+ motion: "Gentler 200ms ease and a slightly deeper press scale for a springy feel.",
88
+ typography: "Inherits the base type pairing; the personality is in shape, not type.",
89
+ components: "Pill buttons, heavily-rounded inputs/cards/modals, rounder small affordances."
90
+ }
91
+ },
92
+ "swiss-minimal-flat": {
93
+ id: "swiss-minimal-flat",
94
+ name: "Swiss / Minimal-Flat",
95
+ tagline: "International-typographic, structural, calm \u2014 flat, sharp, gridded.",
96
+ inspiration: "Swiss International Typographic Style \u2014 M\xFCller-Brockmann grids, flat surfaces.",
97
+ designDoc: "docs/styles/swiss-minimal-flat.md",
98
+ defaultTypeId: "motir-sans",
99
+ dimensions: {
100
+ silhouette: "Sharp near-square corners (2px) on every surface \u2014 hard right angles, structural.",
101
+ stroke: "Hairline borders do ALL the structural work; with elevation removed, the 1px rule is the only divider.",
102
+ elevation: "Flat \u2014 every shadow is removed (none). Depth comes from borders + whitespace, not lift.",
103
+ surface: "Opaque, untinted panels delineated by hairline rules; no floating, no glass, no wash.",
104
+ density: "Tight, gridded controls (crisp 16\xD79 buttons, 34px control height) with generous 28px card whitespace.",
105
+ motion: "Minimal \u2014 fast 100ms transitions and NO press-scale (calm, mechanical, never springy).",
106
+ typography: "Restrained neo-grotesque sans throughout \u2014 headlines drop the editorial serif for Inter, tight and strong.",
107
+ components: "Square buttons/inputs/cards/modals, rectangular (non-pill) status chips, flat hairline-ruled surfaces."
108
+ }
109
+ },
110
+ "neo-brutalism": {
111
+ id: "neo-brutalism",
112
+ name: "Neo-Brutalism",
113
+ tagline: "Raw, punchy, utilitarian \u2014 0px corners, thick borders, hard-offset shadows.",
114
+ inspiration: "Neo-brutalist web design \u2014 Gumroad / Figma-community brutalism: blocky, unpolished, loud.",
115
+ designDoc: "docs/styles/neo-brutalism.md",
116
+ defaultTypeId: "motir-mono",
117
+ dimensions: {
118
+ silhouette: "Zero radius \u2014 hard 0px corners on EVERY surface (buttons, cards, inputs, modals, status chips). Blocky and uncompromising.",
119
+ stroke: "Heavy solid 2px outlines do the structural work \u2014 borders are LOUD, not hairline (the defining neo-brutalist move, applied via a style-scoped component-variant block).",
120
+ elevation: "Hard-offset drop shadows with ZERO blur (e.g. 4px 4px 0 0) \u2014 a solid block of shadow, never a soft lift; the chunky frame, not diffusion.",
121
+ surface: "Opaque, flat, untinted panels framed by the thick border + the hard shadow; no glass, no wash, no gradient.",
122
+ density: "Tight, utilitarian controls (16\xD710 buttons, 36px control height) with compact 20px card padding \u2014 no wasted space.",
123
+ motion: "Snappy and mechanical \u2014 near-instant 60ms transitions and NO press-scale; the UI reacts, it does not animate.",
124
+ typography: "Raw monospace headlines \u2014 the editorial serif is re-pointed at the JetBrains Mono stack, against the Inter grotesk body.",
125
+ components: "Square buttons/inputs/cards/modals, rectangular (non-pill) status chips, thick-bordered surfaces with hard-offset shadows."
126
+ }
127
+ },
128
+ glassmorphism: {
129
+ id: "glassmorphism",
130
+ name: "Glassmorphism",
131
+ tagline: "Translucent frosted glass floating over a soft, vibrant gradient.",
132
+ inspiration: "Apple's visionOS / macOS Big Sur 'frosted glass' material \u2014 backdrop-blur over depth.",
133
+ designDoc: "docs/styles/glassmorphism.md",
134
+ defaultTypeId: "motir",
135
+ dimensions: {
136
+ silhouette: "Soft, rounded glass tiles \u2014 12px buttons/inputs, 18px cards, 22px modals. Friendly, never sharp.",
137
+ stroke: "Light 1px hairlines at reduced opacity \u2014 a glass edge catching light, not a structural rule.",
138
+ elevation: "Layered, diffuse, low-opacity shadows \u2014 panels float as hovering frosted sheets above the canvas.",
139
+ surface: "The identity axis: translucent frosted panels (backdrop-blur) over a soft palette-derived gradient canvas \u2014 material, not opaque.",
140
+ density: "Comfortable, a touch roomy \u2014 20\xD711 buttons, 26px card padding, 38px controls; glass tiles want air.",
141
+ motion: "Gentle, smooth \u2014 220ms ease and a light press-scale; glass slides into place, it never snaps.",
142
+ typography: "Inherits the base editorial type pairing; the personality is in the material, not the type.",
143
+ components: "Rounded frosted cards / popovers / modals / sidebar / inputs (the data-surface material layer), pill status chips."
144
+ }
145
+ },
146
+ "cybercore-y2k": {
147
+ id: "cybercore-y2k",
148
+ name: "Cybercore / Y2K",
149
+ tagline: "Neon-on-dark retro-tech \u2014 glowing HUD panels, a tech grid, mono headlines.",
150
+ inspiration: "Y2K / cyberpunk HUDs \u2014 neon-on-dark terminals, Tron grids, glowing edges, monospace displays.",
151
+ designDoc: "docs/styles/cybercore-y2k.md",
152
+ defaultTypeId: "motir-mono",
153
+ dimensions: {
154
+ silhouette: "Hard terminal-frame corners (sharp 2\u20134px) \u2014 chiseled HUD panels, not soft cards.",
155
+ stroke: "Structure drawn by a lit edge \u2014 a 1px glow-ring (currentColor halo) on every surface, not a heavier border.",
156
+ elevation: "Neon GLOW instead of drop-shadow \u2014 surfaces emit an outward currentColor halo, lit rather than lifted.",
157
+ surface: "A faint tech grid (scanline/grid texture) washes the canvas behind opaque HUD panels.",
158
+ density: "Tight, technical HUD rhythm \u2014 compact controls (18\xD79 buttons, 34px control height) over snug 20px card padding.",
159
+ motion: "Snappy and electric \u2014 very fast 90ms transitions with a crisp press-scale.",
160
+ typography: "Mono/display headlines \u2014 the serif headline face is swapped to JetBrains Mono for a terminal read; body stays sans.",
161
+ components: "Sharp glow-ringed panels, mono headings, rectangular chips; the lit edge replaces the drop shadow everywhere."
162
+ }
163
+ },
164
+ aurora: {
165
+ id: "aurora",
166
+ name: "Aurora",
167
+ tagline: "Soft, drifting aurora light behind fluid, gently-glowing surfaces.",
168
+ inspiration: 'The northern lights \u2014 slow ribbons of colour over a night sky \u2014 and the soft animated-gradient "aurora" hero look (Stripe / Vercel ambient gradients).',
169
+ designDoc: "docs/styles/aurora.md",
170
+ defaultTypeId: "motir",
171
+ dimensions: {
172
+ silhouette: "Fluid, soft corners \u2014 14px buttons/inputs, 20px cards, 26px modals. Organic and rounded, a touch softer than glass.",
173
+ stroke: "Quiet 1px hairlines; structure is implied by the gentle glow and elevation, never drawn by a heavy rule.",
174
+ elevation: "A soft neutral lift PLUS a gentle palette-derived COLOUR glow halo \u2014 surfaces feel lit from within, never hard-edged.",
175
+ surface: "The identity axis: opaque fluid surfaces floating over a slowly-DRIFTING, animated aurora gradient canvas (palette-derived ribbons).",
176
+ density: "Comfortable and roomy \u2014 20\xD711 buttons, 26px card padding, 38px controls; the light wants space to breathe.",
177
+ motion: "Smooth and slow \u2014 260ms eased transitions, a faint press-scale, and a 28s ambient drift of the aurora canvas (stilled under prefers-reduced-motion).",
178
+ typography: "Inherits the base editorial pairing (Source Serif headlines + Inter body); the personality is in the moving light, not the type.",
179
+ components: "Fluid, gently-glowing cards / popovers / modals / sidebar (the data-surface material layer); pill status chips."
180
+ }
181
+ },
182
+ "3d-immersive": {
183
+ id: "3d-immersive",
184
+ name: "3D / Immersive",
185
+ tagline: "Depth and perspective \u2014 dimensional cards floating over the page.",
186
+ inspiration: "Spatial / depth UI \u2014 visionOS spatial layers, Stripe-era layered cards, soft real-world light.",
187
+ designDoc: "docs/styles/3d-immersive.md",
188
+ defaultTypeId: "motir",
189
+ dimensions: {
190
+ silhouette: "Generously rounded dimensional tiles \u2014 14px buttons/inputs, 20px cards, 28px modals. Soft, tactile, never sharp.",
191
+ stroke: "Borders nearly vanish (a faint hairline) \u2014 structure is read from depth + shadow, not an outline.",
192
+ elevation: "The identity axis: deep multi-layer shadows float every surface off the canvas, and on tilt a card becomes a TRUE 3D object \u2014 perspective + preserve-3d + per-slot translateZ planes (header pops forward, footer behind) parallax under a cursor-tracked glare. Not a flat-plane tilt.",
193
+ surface: "A palette-derived immersive background atmosphere on every page, with opaque dimensional panels (cards, tables, board columns, widgets) floating above it on deep resting shadows \u2014 depth (not tint or glass) is the material; nothing sits flat.",
194
+ density: "Roomy and immersive \u2014 22\xD712 buttons, 28px card padding, 40px controls; depth wants air around each tile.",
195
+ motion: 'The standard "3D card" pointer-parallax \u2014 tiles tip toward the cursor (rotateX/rotateY over a perspective) via the ImmersiveTilt engine, lifting as they turn; reduced-motion-gated, settling flat on leave.',
196
+ typography: "Inherits the base editorial type pairing; the personality is in depth + light, not the type.",
197
+ components: "Rounded cards / board cards / modals that tilt toward the pointer with layered parallax; PHYSICAL buttons with real thickness (a solid base edge) that press DOWN on click; each board column floats as its own card. Nothing is flat."
198
+ }
199
+ },
200
+ neumorphism: {
201
+ id: "neumorphism",
202
+ name: "Neumorphism",
203
+ tagline: "Soft extruded surfaces that swell out of a single monochrome field.",
204
+ inspiration: 'Neumorphism / soft-UI \u2014 Dribbble-era "soft shadow" interfaces where panels are pressed OUT of (or INTO) one continuous surface by a paired light + dark shadow.',
205
+ designDoc: "docs/styles/neumorphism.md",
206
+ defaultTypeId: "motir",
207
+ dimensions: {
208
+ silhouette: "Soft, generously rounded pebbles \u2014 12px buttons/inputs, 18px cards, 22px modals. Tactile and friendly, the rounding that lets a shadow wrap a surface.",
209
+ stroke: "Borders nearly vanish \u2014 a faint hairline kept ONLY as the accessibility fallback (see below); structure is read from the paired soft shadow, not an outline.",
210
+ elevation: "The identity axis: a DUAL soft shadow \u2014 a light highlight top-left + a palette-derived dark shadow bottom-right \u2014 extrudes each surface OUT of the canvas (raised), while inputs and pressed controls invert it to an INSET well (pressed in). Depth is a soft swell, never a hard drop.",
211
+ surface: 'Monochrome and continuous: the page canvas and every panel share ONE opaque palette surface, so a card looks moulded out of the background rather than placed on top of it \u2014 the soft-UI "single material" look. Opaque (text AA preserved), with the depth carried entirely by the shadows.',
212
+ density: "Comfortable and roomy \u2014 20\xD711 buttons, 26px card padding, 38px controls; the soft shadows need air around each surface to read as extruded.",
213
+ motion: "Gentle \u2014 200ms ease and a soft press-scale; nothing snaps, matching the moulded surfaces (form fields rest as concave inset wells, panels swell out raised).",
214
+ typography: "Inherits the base editorial type pairing; the personality is in the moulded surfaces, not the type.",
215
+ components: "Raised, soft-shadowed cards / popovers / modals / sidebar (the data-surface material layer); concave inset inputs; pill status chips. Every surface swells from or sinks into the one field."
216
+ }
217
+ },
218
+ "hand-drawn-indie": {
219
+ id: "hand-drawn-indie",
220
+ name: "Hand-Drawn / Indie",
221
+ tagline: "Sketchy and friendly \u2014 rough hand-inked edges, wonky corners, soft offset shadows, a playful bounce.",
222
+ inspiration: "Indie / zine + hand-drawn web design \u2014 Excalidraw\u2019s rough hand-sketched strokes and doodled notebook UIs: warm, imperfect, human.",
223
+ designDoc: "docs/styles/hand-drawn-indie.md",
224
+ defaultTypeId: "editorial",
225
+ dimensions: {
226
+ silhouette: "Wonky hand-drawn corners \u2014 every surface gets a strongly asymmetric multi-value radius (one corner near-sharp, the opposite generously round, no two alike) so cards/buttons/inputs read as DRAWN boxes, never machined rectangles.",
227
+ stroke: "Genuinely ROUGH, wavy ink edges \u2014 a turbulence-displacement filter (Excalidraw / rough.js style) warps each outline into a hand-inked wobble, drawn on a content-safe pseudo-border so the lines are sketchy but the text stays crisp.",
228
+ elevation: "Soft, hand-placed offset shadows \u2014 a gentle blurred drop nudged down-right (a doodled shadow), friendly and low; never a hard brutalist block nor a flat removal.",
229
+ surface: "Opaque, warm, paper-like panels \u2014 colour stays the palette axis; the personality is in the rough wavy outline + the soft shadow, not a tint or a wash.",
230
+ density: "Roomy and relaxed \u2014 generous 20\xD712 buttons, 26px card padding, taller 38px controls; the hand-drawn feel wants air, like margins round a sketch.",
231
+ motion: "Springy and playful \u2014 gentle 200ms transitions with a bouncy hover lift (1.02) and a deeper press squish (0.97); the UI has a friendly wobble.",
232
+ typography: "Ships the Editorial pairing (Fraunces display headlines + Inter body) \u2014 the warmest, most characterful registered face, its soft optical wonk echoing the hand-drawn shapes; an actual marker/script face is a future data-type addition.",
233
+ components: "Wonky-cornered buttons/inputs/cards/modals, irregular (non-pill) status chips, rough wavy-ink-bordered surfaces with soft hand-placed shadows."
234
+ }
235
+ },
236
+ retrofuturism: {
237
+ id: "retrofuturism",
238
+ name: "Retrofuturism",
239
+ tagline: "Chrome, gradient sweeps and a synthwave horizon \u2014 70s/80s sci-fi.",
240
+ inspiration: "Retrofuturism / Outrun \u2014 70s\u201380s sci-fi: chrome lettering, synthwave sunset gradients, airbrushed sci-fi paperbacks, and wide geometric display type.",
241
+ designDoc: "docs/styles/retrofuturism.md",
242
+ defaultTypeId: "grotesk",
243
+ dimensions: {
244
+ silhouette: "Streamlined chrome capsules \u2014 fully-rounded pill buttons + chips, 16px cards, 22px modals. Aerodynamic 70s/80s curves, never sharp.",
245
+ stroke: "A lit chrome edge \u2014 a bright inset highlight + a dark base line over a faint hairline, reading as a polished metal rim rather than a drawn border.",
246
+ elevation: "Chrome bevels + a diagonal SPECULAR streak + a palette-derived glow \u2014 a bright top highlight fading to a dark base with a swept light-bar across each panel, so surfaces read as polished reflective metal, not flat cards.",
247
+ surface: "The identity axis: a synthwave SKY + a bright HORIZON GLOW band + a receding neon perspective GRID floor (all palette-derived) behind chrome-bevelled, specular-streaked surfaces. Dark-first \u2014 the neon horizon + grid read strongest on a dark palette.",
248
+ density: "Confident and roomy \u2014 22\xD711 chrome-capsule buttons, 24px card padding, 38px controls; the chrome wants room to catch the light.",
249
+ motion: "Smooth and gliding \u2014 200ms eased transitions and a confident press-scale; the synthwave horizon + grid are a STATIC backdrop (no motion to distract the work surface).",
250
+ typography: "Wide geometric grotesque display (the `grotesk` type pairing) \u2014 the retro-futurist sci-fi headline read; body stays a legible sans.",
251
+ components: "Chrome-capsule buttons with a metallic glint, specular-streaked bevelled cards / popovers / modals / sidebar / inputs (the data-surface material layer), pill status chips \u2014 a synthwave horizon + neon grid behind it all."
252
+ }
253
+ }
254
+ };
255
+ var STYLE_IDS = Object.keys(STYLE_REGISTRY);
256
+ var DEFAULT_STYLE_ID = "warm-editorial";
257
+ function isStyleId(value) {
258
+ return typeof value === "string" && Object.prototype.hasOwnProperty.call(STYLE_REGISTRY, value);
259
+ }
260
+ function resolveStyle(value) {
261
+ return STYLE_REGISTRY[isStyleId(value) ? value : DEFAULT_STYLE_ID];
262
+ }
263
+ Object.fromEntries(
264
+ STYLE_IDS.map((id) => [id, STYLE_REGISTRY[id].defaultTypeId])
265
+ );
266
+
267
+ // src/theme/palettes.ts
268
+ var PALETTE_REGISTRY = {
269
+ motir: {
270
+ id: "motir",
271
+ name: "Motir",
272
+ tagline: "Warm and editorial \u2014 cream surfaces, charcoal ink, a purple primary, pastel tints.",
273
+ inspiration: "Notion's warm marketing palette \u2014 the product's house colours.",
274
+ designDoc: "docs/palettes/motir.md"
275
+ },
276
+ cobalt: {
277
+ id: "cobalt",
278
+ name: "Cobalt",
279
+ tagline: "Cool and institutional \u2014 slate-cool surfaces, a confident cobalt primary, cooled tints.",
280
+ inspiration: "Coinbase's clean institutional blue + IBM's structured blue, on Radix Blue/Indigo/Slate scales.",
281
+ designDoc: "docs/palettes/cobalt.md"
282
+ },
283
+ graphite: {
284
+ id: "graphite",
285
+ name: "Graphite",
286
+ tagline: "Stark and editorial \u2014 cool greyscale surfaces + ink, an ink CTA, a single restrained cool-blue accent.",
287
+ inspiration: "Vercel's black-and-white precision + Linear's ultra-minimal, on Radix Slate/Blue scales.",
288
+ designDoc: "docs/palettes/graphite.md"
289
+ },
290
+ evergreen: {
291
+ id: "evergreen",
292
+ name: "Evergreen",
293
+ tagline: "Fresh and technical \u2014 an emerald primary over cooled forest neutrals, a green-leaning UI.",
294
+ inspiration: "getdesign.md \u2014 Supabase (dark emerald) / Spotify (vibrant green) / MongoDB (spring green), on Radix green/jade ramps.",
295
+ designDoc: "docs/palettes/evergreen.md"
296
+ },
297
+ spectrum: {
298
+ id: "spectrum",
299
+ name: "Spectrum",
300
+ tagline: "Vibrant and playful \u2014 crisp cool surfaces, a bright violet primary, a candy-bright multi-hue accent & tint set.",
301
+ inspiration: "Figma's vibrant multi-colour brand + Airtable's colourful, friendly palette, on Radix Violet/Iris/Pink/Blue scales.",
302
+ designDoc: "docs/palettes/spectrum.md"
303
+ },
304
+ amber: {
305
+ id: "amber",
306
+ name: "Amber",
307
+ tagline: "Warm and electric \u2014 Binance trading-floor gold over the exchange\u2019s graphite near-blacks; dark labels on the gold.",
308
+ inspiration: "Binance \u2014 its whole documented palette: the bold gold #F0B90B/#FCD535 over cool graphite trading-floor blacks (#0B0E11/#1E2329) + greys, with the buy-green/sell-red semantics.",
309
+ designDoc: "docs/palettes/amber.md"
310
+ },
311
+ sienna: {
312
+ id: "sienna",
313
+ name: "Sienna",
314
+ tagline: "Warm and vivid \u2014 Mistral flame-orange over the Mediterranean warm-cream surfaces; dark labels on the flame.",
315
+ inspiration: "Mistral AI \u2014 its whole documented palette (getdesign.md/mistral.ai): the flame #fa520f + Block-gradient + Sunshine ambers over Warm-Ivory cream surfaces (#fffaeb) and Mistral-Black ink.",
316
+ designDoc: "docs/palettes/sienna.md"
317
+ },
318
+ garnet: {
319
+ id: "garnet",
320
+ name: "Garnet",
321
+ tagline: "Rich and bold \u2014 Pinterest Pushpin red over the brand\u2019s neutral greys; white labels on the red.",
322
+ inspiration: "Pinterest \u2014 its whole documented palette: Pushpin red #E60023/#BD081C over Cod-Gray ink (#111111) and neutral greys (#767676/#EFEFEF), with Gestalt \u201CSkycicle\u201D blue links.",
323
+ designDoc: "docs/palettes/garnet.md"
324
+ },
325
+ citrine: {
326
+ id: "citrine",
327
+ name: "Citrine",
328
+ tagline: "Bright and collaborative \u2014 Miro\u2019s Sunglow yellow over the whiteboard greys, with the Miro action-blue accent.",
329
+ inspiration: "Miro \u2014 its whole Mirotone palette (every value a documented Mirotone step): Sunglow yellow #FFD02F (primary) + the action-blue #3859FF over the gray ramp + Stratos ink #050038, with the green/red semantic ramps.",
330
+ designDoc: "docs/palettes/citrine.md"
331
+ },
332
+ candy: {
333
+ id: "candy",
334
+ name: "Candy",
335
+ tagline: "Sweet and playful \u2014 a light bubblegum-pink primary over candy-paper pinks, with a full pastel-candy rainbow.",
336
+ inspiration: "A candy concept grounded entirely in documented Radix Colors scales (every value a Radix step): Pink primary + Mauve neutrals + a Sky/Jade/Violet/Amber/Crimson candy rainbow; Glossier Pink #F5DADF as the pastel-pink mood reference.",
337
+ designDoc: "docs/palettes/candy.md"
338
+ }
339
+ };
340
+ var DEFAULT_PALETTE_ID = "motir";
341
+ function isPaletteId(value) {
342
+ return typeof value === "string" && Object.prototype.hasOwnProperty.call(PALETTE_REGISTRY, value);
343
+ }
344
+
345
+ // src/theme/types.ts
346
+ var THEME_STORAGE_KEYS = {
347
+ pattern: "motir.theme.pattern",
348
+ style: "motir.theme.style",
349
+ palette: "motir.theme.palette",
350
+ type: "motir.theme.type"
351
+ };
352
+ var THEME_DEFAULTS = {
353
+ pattern: "system",
354
+ style: DEFAULT_STYLE_ID,
355
+ palette: DEFAULT_PALETTE_ID};
356
+ var SYNC_DEBOUNCE_MS = 250;
357
+ var ThemeContext = createContext(null);
358
+ function readStorage(key, fallback) {
359
+ if (typeof window === "undefined") return fallback;
360
+ try {
361
+ return window.localStorage.getItem(key) ?? fallback;
362
+ } catch {
363
+ return fallback;
364
+ }
365
+ }
366
+ function subscribeColorScheme(callback) {
367
+ if (typeof window === "undefined") return () => {
368
+ };
369
+ const mql = window.matchMedia("(prefers-color-scheme: dark)");
370
+ mql.addEventListener("change", callback);
371
+ return () => mql.removeEventListener("change", callback);
372
+ }
373
+ function getColorSchemeSnapshot() {
374
+ if (typeof window === "undefined") return "light";
375
+ return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
376
+ }
377
+ function getColorSchemeServerSnapshot() {
378
+ return "light";
379
+ }
380
+ function ThemeProvider({
381
+ children,
382
+ initialPreference = null,
383
+ signedIn = false
384
+ }) {
385
+ const [pattern, setPatternState] = useState(
386
+ () => initialPreference ? initialPreference.pattern : readStorage(THEME_STORAGE_KEYS.pattern, THEME_DEFAULTS.pattern)
387
+ );
388
+ const [styleId, setStyleIdState] = useState(() => {
389
+ if (initialPreference) {
390
+ return isStyleId(initialPreference.styleId) ? initialPreference.styleId : DEFAULT_STYLE_ID;
391
+ }
392
+ const stored = readStorage(THEME_STORAGE_KEYS.style, THEME_DEFAULTS.style);
393
+ return isStyleId(stored) ? stored : DEFAULT_STYLE_ID;
394
+ });
395
+ const [palette, setPaletteState] = useState(() => {
396
+ if (initialPreference) {
397
+ return isPaletteId(initialPreference.paletteId) ? initialPreference.paletteId : DEFAULT_PALETTE_ID;
398
+ }
399
+ const stored = readStorage(THEME_STORAGE_KEYS.palette, THEME_DEFAULTS.palette);
400
+ return isPaletteId(stored) ? stored : DEFAULT_PALETTE_ID;
401
+ });
402
+ const [typeChoice, setTypeChoiceState] = useState(() => {
403
+ if (initialPreference) {
404
+ return initialPreference.typePinned && isTypeId(initialPreference.typeId) ? initialPreference.typeId : null;
405
+ }
406
+ const stored = readStorage(THEME_STORAGE_KEYS.type, "");
407
+ return isTypeId(stored) ? stored : null;
408
+ });
409
+ const osColorScheme = useSyncExternalStore(
410
+ subscribeColorScheme,
411
+ getColorSchemeSnapshot,
412
+ getColorSchemeServerSnapshot
413
+ );
414
+ const resolvedPattern = pattern === "system" ? osColorScheme : pattern;
415
+ const type = typeChoice ?? resolveStyle(styleId).defaultTypeId;
416
+ useEffect(() => {
417
+ document.documentElement.setAttribute("data-theme", resolvedPattern);
418
+ }, [resolvedPattern]);
419
+ useEffect(() => {
420
+ document.documentElement.setAttribute("data-style", styleId);
421
+ }, [styleId]);
422
+ useEffect(() => {
423
+ document.documentElement.setAttribute("data-palette", palette);
424
+ }, [palette]);
425
+ useEffect(() => {
426
+ document.documentElement.setAttribute("data-type", type);
427
+ }, [type]);
428
+ const [syncState, setSyncState] = useState("idle");
429
+ const syncSeqRef = useRef(0);
430
+ const pendingPatchRef = useRef({});
431
+ const debounceRef = useRef(null);
432
+ const reconcile = useCallback((sent, resolved) => {
433
+ if ("pattern" in sent) setPatternState(resolved.pattern);
434
+ if ("styleId" in sent && isStyleId(resolved.styleId)) setStyleIdState(resolved.styleId);
435
+ if ("paletteId" in sent && isPaletteId(resolved.paletteId)) setPaletteState(resolved.paletteId);
436
+ if ("typeId" in sent && isTypeId(resolved.typeId)) setTypeChoiceState(resolved.typeId);
437
+ }, []);
438
+ const flushSync = useCallback(() => {
439
+ debounceRef.current = null;
440
+ const patch = pendingPatchRef.current;
441
+ pendingPatchRef.current = {};
442
+ if (Object.keys(patch).length === 0) return;
443
+ const seq = ++syncSeqRef.current;
444
+ fetch("/api/appearance-preference", {
445
+ method: "PATCH",
446
+ headers: { "Content-Type": "application/json" },
447
+ body: JSON.stringify(patch)
448
+ }).then(async (res) => {
449
+ if (!res.ok) throw new Error(`appearance sync failed: ${res.status}`);
450
+ const body = await res.json();
451
+ if (seq !== syncSeqRef.current) return;
452
+ reconcile(patch, body.preference);
453
+ setSyncState("idle");
454
+ }).catch(() => {
455
+ if (seq !== syncSeqRef.current) return;
456
+ setSyncState("error");
457
+ });
458
+ }, [reconcile]);
459
+ const queueSync = useCallback(
460
+ (patch) => {
461
+ if (!signedIn) return;
462
+ pendingPatchRef.current = { ...pendingPatchRef.current, ...patch };
463
+ if (debounceRef.current) clearTimeout(debounceRef.current);
464
+ debounceRef.current = setTimeout(flushSync, SYNC_DEBOUNCE_MS);
465
+ },
466
+ [signedIn, flushSync]
467
+ );
468
+ useEffect(() => {
469
+ return () => {
470
+ if (debounceRef.current) {
471
+ clearTimeout(debounceRef.current);
472
+ flushSync();
473
+ }
474
+ };
475
+ }, [flushSync]);
476
+ const setPattern = useCallback(
477
+ (next) => {
478
+ setPatternState(next);
479
+ try {
480
+ window.localStorage.setItem(THEME_STORAGE_KEYS.pattern, next);
481
+ } catch {
482
+ }
483
+ queueSync({ pattern: next });
484
+ },
485
+ [queueSync]
486
+ );
487
+ const setStyleId = useCallback(
488
+ (next) => {
489
+ setStyleIdState(next);
490
+ try {
491
+ window.localStorage.setItem(THEME_STORAGE_KEYS.style, next);
492
+ } catch {
493
+ }
494
+ queueSync({ styleId: next });
495
+ },
496
+ [queueSync]
497
+ );
498
+ const setPalette = useCallback(
499
+ (next) => {
500
+ setPaletteState(next);
501
+ try {
502
+ window.localStorage.setItem(THEME_STORAGE_KEYS.palette, next);
503
+ } catch {
504
+ }
505
+ queueSync({ paletteId: next });
506
+ },
507
+ [queueSync]
508
+ );
509
+ const setType = useCallback(
510
+ (next) => {
511
+ setTypeChoiceState(next);
512
+ try {
513
+ window.localStorage.setItem(THEME_STORAGE_KEYS.type, next);
514
+ } catch {
515
+ }
516
+ queueSync({ typeId: next });
517
+ },
518
+ [queueSync]
519
+ );
520
+ const value = useMemo(
521
+ () => ({
522
+ pattern,
523
+ resolvedPattern,
524
+ styleId,
525
+ palette,
526
+ type,
527
+ setPattern,
528
+ setStyleId,
529
+ setPalette,
530
+ setType,
531
+ syncState
532
+ }),
533
+ [
534
+ pattern,
535
+ resolvedPattern,
536
+ styleId,
537
+ palette,
538
+ type,
539
+ setPattern,
540
+ setStyleId,
541
+ setPalette,
542
+ setType,
543
+ syncState
544
+ ]
545
+ );
546
+ return /* @__PURE__ */ jsx(ThemeContext.Provider, { value, children });
547
+ }
548
+ function useTheme() {
549
+ const ctx = useContext(ThemeContext);
550
+ if (!ctx) {
551
+ throw new Error("useTheme must be used inside <ThemeProvider>");
552
+ }
553
+ return ctx;
554
+ }
555
+ function useOptionalTheme() {
556
+ return useContext(ThemeContext);
557
+ }
558
+
559
+ export { ThemeProvider, useOptionalTheme, useTheme };
560
+ //# sourceMappingURL=theme-context.js.map
561
+ //# sourceMappingURL=theme-context.js.map