@heyramzi/ui 2.1.0 → 2.2.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 (312) hide show
  1. package/package.json +7 -8
  2. package/scripts/sync-shadcn.mjs +3 -4
  3. package/src/agents/agent-avatar.svelte +604 -575
  4. package/src/agents/agent-cast.svelte +179 -183
  5. package/src/agents/demo.svelte +125 -113
  6. package/src/agents/helpers.ts +23 -17
  7. package/src/agents/index.ts +4 -4
  8. package/src/agents/page-state.ts +6 -6
  9. package/src/agents/types.ts +6 -6
  10. package/src/ai/ai-button/AiButton.svelte +4 -12
  11. package/src/ai/generation-progress/GenerationProgress.svelte +10 -3
  12. package/src/ai/prompt-input/PromptInputTextarea.svelte +1 -2
  13. package/src/ai/response/Response.css +2 -3
  14. package/src/animations/avatar-group-hover.css +2 -16
  15. package/src/animations/card-tilt.css +3 -6
  16. package/src/animations/digit-pop-in.css +2 -15
  17. package/src/animations/dropdown.css +4 -17
  18. package/src/animations/input-clear-dissolve.css +5 -18
  19. package/src/animations/input-shake.css +9 -41
  20. package/src/animations/learn-more-hover.css +17 -9
  21. package/src/animations/like-button.css +2 -4
  22. package/src/animations/page-side-by-side.css +2 -10
  23. package/src/animations/plus-menu-morph.css +2 -5
  24. package/src/animations/shimmer-text.css +5 -18
  25. package/src/animations/spinning-counter.css +2 -6
  26. package/src/animations/success-check.css +2 -15
  27. package/src/animations/thinking-states.css +2 -7
  28. package/src/brand/brand.test.ts +21 -5
  29. package/src/brand/index.ts +4 -10
  30. package/src/canvas/card/card-frame.svelte +43 -39
  31. package/src/canvas/card/resize-handles.svelte +89 -80
  32. package/src/canvas/chrome/chrome-zoom.svelte +101 -100
  33. package/src/canvas/chrome/lock-badge.svelte +8 -8
  34. package/src/canvas/chrome/side-handles.svelte +24 -19
  35. package/src/canvas/chrome/size-chip.svelte +30 -30
  36. package/src/canvas/chrome/snap-guides.svelte +20 -20
  37. package/src/canvas/components/icons/ToolIcon.svelte +8 -8
  38. package/src/canvas/components/shared/svg/PreviewSvg.svelte +19 -19
  39. package/src/canvas/components/shared/svg/ShapeOutline.svelte +71 -48
  40. package/src/canvas/components/shared/svg/StrokeNode.svelte +21 -27
  41. package/src/canvas/components/shared/svg/TextRule.svelte +25 -17
  42. package/src/canvas/edges/bend-handle.svelte +46 -46
  43. package/src/canvas/edges/edge-gradient.svelte +10 -10
  44. package/src/canvas/edges/gradient-edge.svelte +45 -29
  45. package/src/canvas/editor/lock.test.ts +6 -1
  46. package/src/canvas/editor/style.ts +7 -1
  47. package/src/canvas/headless/svg-figure.test.ts +5 -1
  48. package/src/canvas/lod/lod-switch.svelte +111 -106
  49. package/src/canvas/pen/PenField.svelte +87 -81
  50. package/src/canvas/pen/PenOverlay.svelte +127 -127
  51. package/src/canvas/pen/ShapeNode.svelte +31 -32
  52. package/src/canvas/pen/TextNode.svelte +54 -53
  53. package/src/canvas/pen/host-field.test.svelte +7 -7
  54. package/src/canvas/pen/shapes.ts +7 -1
  55. package/src/canvas/screen/GlyphIcon.svelte +22 -17
  56. package/src/canvas/screen/PartView.svelte +138 -72
  57. package/src/canvas/screen/ScreenBody.svelte +81 -60
  58. package/src/canvas/screen/ScreenCard.svelte +20 -20
  59. package/src/canvas/ui/CanvasToolbar.svelte +173 -85
  60. package/src/canvas/ui/FrameLayer.svelte +59 -59
  61. package/src/canvas/ui/PenLayer.svelte +39 -30
  62. package/src/canvas/ui/StylePanel.svelte +119 -102
  63. package/src/canvas/ui/canvas-grid.svelte +137 -129
  64. package/src/charts/animated-value.svelte +1 -1
  65. package/src/charts/area-chart.css +2 -5
  66. package/src/charts/area-chart.svelte +1 -1
  67. package/src/charts/bar-chart.css +2 -3
  68. package/src/charts/bar-chart.svelte +1 -1
  69. package/src/charts/charts.css +5 -17
  70. package/src/charts/crosshair.svelte +1 -1
  71. package/src/charts/dashboard/dashboard-empty.svelte +25 -25
  72. package/src/charts/dashboard/dashboard-section.svelte +24 -24
  73. package/src/charts/dashboard/data-panel.svelte +50 -50
  74. package/src/charts/dashboard/data-sort-head.svelte +56 -56
  75. package/src/charts/dashboard/data-table.svelte +138 -138
  76. package/src/charts/dashboard/panel-grid.svelte +30 -30
  77. package/src/charts/dashboard/period-filter.svelte +25 -25
  78. package/src/charts/dashboard/rating-chip.svelte +19 -19
  79. package/src/charts/dashboard/stat-band.svelte +32 -32
  80. package/src/charts/dashboard/stat-tile.svelte +106 -99
  81. package/src/charts/donut-chart.svelte +1 -1
  82. package/src/charts/edge-fade.svelte +1 -1
  83. package/src/charts/figure.svelte +1 -1
  84. package/src/charts/gauge-chart.css +4 -7
  85. package/src/charts/gauge-chart.svelte +1 -1
  86. package/src/charts/hover-focus.svelte +1 -1
  87. package/src/charts/legend.css +1 -3
  88. package/src/charts/legend.svelte +1 -1
  89. package/src/charts/line-chart.svelte +1 -1
  90. package/src/charts/number-chart.css +6 -9
  91. package/src/charts/number-chart.svelte +1 -1
  92. package/src/charts/reference-line.svelte +19 -19
  93. package/src/charts/reveal.svelte +1 -1
  94. package/src/charts/ring-chart.css +2 -4
  95. package/src/charts/ring-chart.svelte +1 -1
  96. package/src/charts/sparkline.svelte +5 -2
  97. package/src/charts/stat-card.svelte +1 -1
  98. package/src/charts/time-series-chart.css +2 -5
  99. package/src/charts/trend-pill.css +2 -4
  100. package/src/charts/trend-pill.svelte +1 -1
  101. package/src/components/copy/copy-button.svelte +93 -98
  102. package/src/components/copy/copy-button.test.ts +10 -2
  103. package/src/components/data-table/infinite-scroll.svelte +42 -42
  104. package/src/components/data-table/pagination.svelte +34 -22
  105. package/src/components/data-table/row-arrow.svelte +19 -16
  106. package/src/components/data-table/row-context-menu.svelte +52 -52
  107. package/src/components/data-table/sortable-head.svelte +25 -25
  108. package/src/components/data-table/table-skeleton.svelte +41 -36
  109. package/src/components/docs/doc-page.svelte +18 -9
  110. package/src/components/docs/on-this-page.svelte +16 -3
  111. package/src/components/docs/scroll-spy.svelte.ts +2 -3
  112. package/src/components/field-row/field-row.svelte +10 -8
  113. package/src/components/hold-to-confirm/hold-to-confirm.svelte +5 -1
  114. package/src/components/icon/icon.svelte +14 -14
  115. package/src/components/language-flag/language-flag.svelte +3 -1
  116. package/src/components/list-page/detail-empty.svelte +34 -34
  117. package/src/components/list-page/detail-header.svelte +55 -47
  118. package/src/components/list-page/detail-panel.svelte +51 -59
  119. package/src/components/list-page/detail-row.svelte +19 -19
  120. package/src/components/list-page/detail-stack.svelte +21 -21
  121. package/src/components/list-page/empty-state.svelte +30 -30
  122. package/src/components/list-page/filter-item.svelte +20 -20
  123. package/src/components/list-page/filter-popover.svelte +60 -55
  124. package/src/components/list-page/grouped-list.svelte +4 -2
  125. package/src/components/list-page/index.ts +5 -1
  126. package/src/components/list-page/list-page-shell.svelte +2 -5
  127. package/src/components/list-page/master-detail.svelte +87 -88
  128. package/src/components/list-page/notice-bar.svelte +38 -38
  129. package/src/components/list-page/prev-next.svelte +63 -63
  130. package/src/components/list-page/rail-body.svelte +18 -18
  131. package/src/components/list-page/rail-empty.svelte +23 -30
  132. package/src/components/list-page/rail-head.svelte +24 -24
  133. package/src/components/list-page/rail-row.svelte +77 -73
  134. package/src/components/list-page/section-host.ts +2 -3
  135. package/src/components/locale-switch/locale-switch.svelte +41 -41
  136. package/src/components/mark/mark.svelte +34 -38
  137. package/src/components/open-in/index.ts +1 -5
  138. package/src/components/open-in/open-in.svelte +40 -40
  139. package/src/components/picker/mark-picker.svelte +49 -49
  140. package/src/components/picker/picker.svelte +5 -3
  141. package/src/components/section-header/section-header.test.ts +7 -1
  142. package/src/components/section-header/section-label.svelte +20 -20
  143. package/src/components/segmented/animated-tabs.svelte +31 -2
  144. package/src/components/segmented/collapse.test.ts +2 -1
  145. package/src/components/segmented/index.ts +2 -0
  146. package/src/components/segmented/segment.svelte +52 -49
  147. package/src/components/segmented/segmented-filter.svelte +79 -80
  148. package/src/components/segmented/segmented-nav.svelte +34 -40
  149. package/src/components/segmented/segmented-overflow.svelte +53 -50
  150. package/src/components/segmented/segmented-track.svelte +62 -46
  151. package/src/components/segmented/segmented.ts +2 -3
  152. package/src/components/star-rating-input/star-rating-input.svelte +103 -103
  153. package/src/components/svg/svg.svelte +4 -4
  154. package/src/components/table-view/table-view.svelte +17 -2
  155. package/src/components/table-view/table-view.test.ts +7 -1
  156. package/src/components/table-view/types.ts +11 -11
  157. package/src/components/tags-input/index.ts +13 -0
  158. package/src/components/tags-input/tags-input.svelte +305 -0
  159. package/src/components/tags-input/tags.test.ts +64 -0
  160. package/src/components/tags-input/tags.ts +142 -0
  161. package/src/components/theme/index.ts +2 -4
  162. package/src/components/theme/theme-provider.svelte +1 -3
  163. package/src/components/toolbar/toolbar-search.svelte +25 -25
  164. package/src/components/toolbar/toolbar.svelte +11 -7
  165. package/src/components/toolbar/toolbar.test.ts +10 -1
  166. package/src/design/index.css +21 -78
  167. package/src/design/polish.css +26 -35
  168. package/src/editor/editor-toolbar.svelte +104 -99
  169. package/src/editor/editor.svelte +296 -266
  170. package/src/editor/image-toolbar.svelte +20 -22
  171. package/src/editor/index.ts +1 -4
  172. package/src/editor/inline-editor.svelte +280 -238
  173. package/src/editor/markdown.ts +15 -15
  174. package/src/editor/renderer.svelte +71 -71
  175. package/src/editor/slash-menu.svelte +24 -27
  176. package/src/effects/animated-beam/diagrams/BeamSvg.svelte +1 -1
  177. package/src/effects/image-comparison/ImageComparison.svelte +11 -1
  178. package/src/effects/interactive-hover-button/InteractiveHoverButton.svelte +59 -46
  179. package/src/effects/magic-card/MagicCard.css +14 -7
  180. package/src/effects/marquee/Marquee.svelte +3 -5
  181. package/src/effects/reveal/observe.ts +0 -3
  182. package/src/effects/spotlight/Spotlight.svelte +1 -1
  183. package/src/effects/theme-toggle/ThemeToggle.css +25 -22
  184. package/src/effects/theme-toggle/ThemeToggle.svelte +6 -7
  185. package/src/gallery.test.ts +14 -4
  186. package/src/inbox/README.md +1 -3
  187. package/src/inbox/inbox.test.ts +181 -138
  188. package/src/inbox/index.ts +20 -20
  189. package/src/inbox/logic.ts +114 -111
  190. package/src/inbox/thread-inbox.svelte +227 -151
  191. package/src/inbox/types.ts +130 -108
  192. package/src/lib/theme-transition.ts +2 -4
  193. package/src/lib/tone.ts +2 -4
  194. package/src/lib/utils.ts +2 -2
  195. package/src/modules.test.ts +79 -69
  196. package/src/onboarding/define-tours.test.ts +55 -40
  197. package/src/onboarding/define-tours.ts +23 -23
  198. package/src/onboarding/demo.svelte +24 -5
  199. package/src/onboarding/index.ts +1 -8
  200. package/src/onboarding/onboarding-provider.svelte +19 -7
  201. package/src/onboarding/onboarding.svelte.ts +16 -16
  202. package/src/onboarding/pick.test.ts +61 -52
  203. package/src/onboarding/pick.ts +16 -13
  204. package/src/onboarding/stores.test.ts +80 -80
  205. package/src/onboarding/stores.ts +41 -39
  206. package/src/onboarding/tour-card.svelte +30 -5
  207. package/src/onboarding/tour-runner.svelte +12 -3
  208. package/src/onboarding/types.ts +20 -12
  209. package/src/onboarding/wait.ts +22 -22
  210. package/src/panel/side-panel.svelte +94 -93
  211. package/src/record/demo-data.ts +291 -225
  212. package/src/record/field-section.svelte +38 -34
  213. package/src/record/fields.ts +148 -96
  214. package/src/record/format.ts +63 -56
  215. package/src/record/index.ts +49 -35
  216. package/src/record/info-list.svelte +145 -114
  217. package/src/record/kinds.ts +31 -31
  218. package/src/record/logic.ts +216 -214
  219. package/src/record/next-up.svelte +45 -34
  220. package/src/record/project-list.svelte +56 -50
  221. package/src/record/record-demo.svelte +130 -110
  222. package/src/record/record-header.svelte +37 -28
  223. package/src/record/record-shell.svelte +54 -54
  224. package/src/record/record-stats.svelte +25 -25
  225. package/src/record/record-timeline.svelte +134 -114
  226. package/src/record/record.test.ts +430 -309
  227. package/src/record/steps-rail.svelte +225 -216
  228. package/src/record/types.ts +234 -215
  229. package/src/record/where-we-are.svelte +62 -58
  230. package/src/shadcn/button/button.svelte +1 -1
  231. package/src/shadcn/card/card-title.svelte +1 -5
  232. package/src/shadcn/hooks/use-highlighted-code.svelte.ts +2 -13
  233. package/src/shadcn/sheet/sheet-content.svelte +2 -4
  234. package/src/shadcn/table/table-cell.svelte +2 -3
  235. package/src/shadcn/utilities.css +3 -8
  236. package/src/slides/Controls.svelte +150 -124
  237. package/src/slides/Deck.svelte +243 -238
  238. package/src/slides/Fragment.svelte +64 -57
  239. package/src/slides/Overview.svelte +106 -101
  240. package/src/slides/Presenter.svelte +300 -266
  241. package/src/slides/Slide.svelte +185 -170
  242. package/src/slides/SlideList.svelte +16 -12
  243. package/src/slides/ambient.d.ts +1 -1
  244. package/src/slides/bridges.ts +42 -23
  245. package/src/slides/deck-controller.svelte.ts +47 -35
  246. package/src/slides/demo-slides.svelte +330 -306
  247. package/src/slides/demo.svelte +164 -160
  248. package/src/slides/export/capture.ts +20 -16
  249. package/src/slides/export/ffmpeg.ts +43 -38
  250. package/src/slides/export/index.ts +5 -5
  251. package/src/slides/export/launch.ts +3 -1
  252. package/src/slides/export/pdf.ts +30 -27
  253. package/src/slides/export/types.ts +10 -8
  254. package/src/slides/index.ts +11 -11
  255. package/src/slides/labels.ts +22 -19
  256. package/src/slides/layout.ts +9 -2
  257. package/src/slides/model.ts +4 -4
  258. package/src/slides/navigation.ts +43 -31
  259. package/src/slides/theme.css +5 -7
  260. package/src/slides/types.ts +9 -10
  261. package/src/status/define-statuses.ts +17 -24
  262. package/src/status/index.ts +5 -5
  263. package/src/status/status-badge.svelte +3 -1
  264. package/src/status/status-dot.svelte +1 -3
  265. package/src/status/status-icon.svelte +1 -1
  266. package/src/status/status-types.ts +21 -21
  267. package/src/tasks/board-order.ts +2 -4
  268. package/src/tasks/demo.svelte +189 -31
  269. package/src/tasks/detail/detail-demo.svelte +73 -13
  270. package/src/tasks/detail/detail-types.ts +45 -45
  271. package/src/tasks/detail/task-detail.svelte +40 -10
  272. package/src/tasks/detail/task-property.svelte +8 -2
  273. package/src/tasks/detail/task-thread.svelte +53 -12
  274. package/src/tasks/gantt/gantt-bar.svelte +17 -6
  275. package/src/tasks/gantt/gantt-grid.svelte +40 -9
  276. package/src/tasks/gantt/gantt-group-row.svelte +8 -2
  277. package/src/tasks/gantt/gantt-header.svelte +9 -3
  278. package/src/tasks/gantt/gantt-task-row.svelte +26 -6
  279. package/src/tasks/gantt/gantt.css +2 -1
  280. package/src/tasks/gantt/gantt.test.ts +3 -1
  281. package/src/tasks/gantt/logic.test.ts +110 -20
  282. package/src/tasks/gantt/logic.ts +42 -6
  283. package/src/tasks/inbox/inbox-card.svelte +13 -5
  284. package/src/tasks/inbox/inbox-draft.svelte +1 -1
  285. package/src/tasks/inbox/inbox-logic.ts +6 -40
  286. package/src/tasks/inbox/inbox-statuses.ts +23 -23
  287. package/src/tasks/inbox/inbox-trace.svelte +9 -3
  288. package/src/tasks/inbox/inbox-types.ts +54 -58
  289. package/src/tasks/inbox/index.ts +22 -21
  290. package/src/tasks/index.ts +49 -52
  291. package/src/tasks/kanban-board.svelte +16 -12
  292. package/src/tasks/kanban-types.ts +2 -3
  293. package/src/tasks/presets/sales.ts +6 -6
  294. package/src/tasks/task-gantt.svelte +28 -8
  295. package/src/tasks/task-list.svelte +41 -17
  296. package/src/tasks/time/duration.ts +40 -40
  297. package/src/tasks/time/time-chip.svelte +9 -2
  298. package/src/tasks/time/time-dot.svelte +1 -1
  299. package/src/tasks/time/time-ring.svelte +11 -3
  300. package/src/tasks/time/time-tracker.svelte +57 -15
  301. package/src/tasks/time/time-types.ts +57 -57
  302. package/src/tasks/use-kanban-dnd.svelte.ts +2 -3
  303. package/src/thread/index.ts +7 -7
  304. package/src/thread/logic.ts +36 -33
  305. package/src/thread/thread.svelte +82 -16
  306. package/src/thread/thread.test.ts +58 -47
  307. package/src/thread/types.ts +68 -68
  308. package/src/tokens/craft.css +17 -53
  309. package/src/tokens/index.css +3 -13
  310. package/src/tokens/palette.css +13 -11
  311. package/src/tokens/primitives.css +10 -23
  312. package/src/tasks/inbox/inbox.svelte +0 -203
@@ -1,30 +1,12 @@
1
- /* The craft layer, the opinionated half of a design system.
2
- *
3
- * A palette says which colours a product uses. This file says how a surface
4
- * lifts off the page, how round a control is against a card, and how long an
5
- * overlay takes to arrive. Those three answers are what separate a kit that
6
- * looks assembled from one that looks made, and they were the part every app
7
- * here was re-deciding per component.
8
- *
9
- * WHY it ships beside the palette rather than inside it: the palette is the
10
- * brand and differs per app; elevation, radius and motion are craft and should
11
- * not. An app imports this, then tints it with `--elev-tint` and nothing else.
12
- *
13
- * Studied from beautifului.dev and rareui.com, which both build the same three
14
- * ladders: a hairline that is a shadow rather than a border, a radius that
15
- * steps by the role of the box, and a close that is faster than its open.
16
- *
17
- * Import once, after Tailwind:
18
- * @import "@heyramzi/ui/design";
1
+ /* The craft layer, imported once after Tailwind: elevation, radius and motion
2
+ * stay the same in every app, and an app tints it with `--elev-tint` alone.
19
3
  */
20
4
 
21
5
  @import "./polish.css";
22
6
 
23
7
  /* ── Radius, by role not by size ─────────────────────────────
24
- A chip inside a control inside a card has to read as nested, which needs
25
- three radii that step, not one radius reused. The Tailwind names are the
26
- home so `rounded-lg` on a button already resolves to the control radius and
27
- no component has to learn a new class. */
8
+ Nested boxes need radii that step, so the Tailwind names map to roles and
9
+ `rounded-lg` is already the control radius. */
28
10
  @theme {
29
11
  --radius-xs: 4px; /* tag, dot, inline code */
30
12
  --radius-sm: 6px; /* chip, badge, small pill */
@@ -36,28 +18,8 @@
36
18
  }
37
19
 
38
20
  /* ── Elevation ───────────────────────────────────────────────
39
- Four steps of lift, plus the two edges that are not lift.
40
-
41
- WHY the steps are shadow-only and the hairline is its own token: most boxes
42
- in a kit already draw a border, and an elevation that bakes a `0 0 0 1px`
43
- ring into every step gives those boxes two edges a pixel apart. Compose them
44
- where a component drops its border:
45
-
46
- box-shadow: var(--elev-hairline), var(--elev-card);
47
-
48
- WHY each step is stacked rather than one blurred drop: a single shadow reads
49
- as a sticker. Depth is several offsets falling off together, which is what
50
- the eye reads as distance. They cost nothing extra because every stop is the
51
- same colour at a different alpha.
52
-
53
- WHY the blur radii are large and the alphas small: a 4px blur at 20% draws a
54
- visible grey edge, which the eye reads as a second border. A 28px blur at 7%
55
- is barely a colour anywhere and reads as height. Reach for more blur before
56
- more alpha, every time.
57
-
58
- `--elev-tint` is the one knob an app turns. Pure black greys a warm palette
59
- out; the hue of the app's own darkest surface keeps the shadow in the
60
- family. */
21
+ The steps are shadow-only and the hairline is its own token, or a box that
22
+ already draws a border gets two edges a pixel apart. */
61
23
  :root {
62
24
  --elev-tint: oklch(0 0 0);
63
25
 
@@ -84,9 +46,8 @@
84
46
  0 12px 26px -8px color-mix(in oklab, var(--elev-tint) 12%, transparent),
85
47
  0 40px 72px -20px color-mix(in oklab, var(--elev-tint) 20%, transparent);
86
48
 
87
- /* The two edges. A hairline separates a surface from what is behind it; a rim
88
- is the light catching its top edge, which is what makes a filled control
89
- read as a physical key rather than a coloured rectangle. */
49
+ /* A hairline separates a surface from what's behind it; a rim lights its top
50
+ edge so a filled control reads as a key, not a coloured box. */
90
51
  --elev-hairline: 0 0 0 1px var(--color-border, var(--border));
91
52
  --elev-rim: inset 0 1px 0 color-mix(in oklab, white 16%, transparent);
92
53
 
@@ -94,10 +55,8 @@
94
55
  --elev-field: inset 0 1px 2px color-mix(in oklab, var(--elev-tint) 7%, transparent);
95
56
  }
96
57
 
97
- /* A dark surface cannot cast a darker shadow than the page it sits on, so the
98
- hairline does the separating and the shadow only sinks what is behind it. On
99
- dark the hairline is white at a low alpha, which is what reads as a lit edge
100
- rather than as a drawn border. */
58
+ /* A dark surface can't cast a shadow darker than its page, so on dark a
59
+ low-alpha white hairline does the separating. */
101
60
  .dark {
102
61
  --elev-btn: 0 1px 2px color-mix(in oklab, black 30%, transparent);
103
62
  --elev-card:
@@ -116,25 +75,19 @@
116
75
  }
117
76
 
118
77
  /* ── Focus ───────────────────────────────────────────────────
119
- One ring, two parts: a solid edge that redraws the control's own outline in
120
- the accent, and a soft halo that carries it off the surface. Written as a
121
- variable so a component spends one line on it and every control focuses the
122
- same way. */
78
+ One ring in one variable, a solid accent edge plus a soft halo, so every
79
+ control focuses the same way. */
123
80
  :root {
124
81
  --ring-color: var(--color-ring, var(--ring));
125
82
  --ring-halo: color-mix(in oklab, var(--ring-color) 16%, transparent);
126
- /* A solid pixel redrawing the control's own edge, then a wide soft band. At a
127
- higher alpha the band reads as a second border rather than as a glow, which
128
- is why it is 16% over 4px and not 30% over 2px. */
83
+ /* At a higher alpha the halo reads as a second border, not a glow, which is
84
+ why it's 16% over 4px and not 30% over 2px. */
129
85
  --ring-focus: 0 0 0 1px var(--ring-color), 0 0 0 5px var(--ring-halo);
130
86
  }
131
87
 
132
88
  /* ── Motion ──────────────────────────────────────────────────
133
- The transitions-dev scale, verbatim, so a snippet pasted from that skill and
134
- a component written here read from the same names.
135
-
136
- The doctrine in one line: an open is an invitation and a close should get out
137
- of the way, so every close is shorter than its open and no close bounces. */
89
+ The transitions-dev scale, verbatim so pasted snippets share its names; every
90
+ close is shorter than its open, and no close bounces. */
138
91
  :root {
139
92
  --duration-stagger: 40ms;
140
93
  --duration-micro: 80ms;
@@ -186,13 +139,9 @@
186
139
 
187
140
  --ease-smooth-out: var(--ease-smooth-out);
188
141
 
189
- /* WHY the durations are re-declared under a second name: Tailwind's
190
- `duration-*` utility reads `--transition-duration-*`, not `--duration-*`, so
191
- the six names above generated no class at all. `duration-fast` therefore
192
- compiled to nothing and the element ended up at `transition-duration: 0s`:
193
- no error, no warning, and a component that simply does not animate. The
194
- comment at the top of this block had claimed `duration-fast` was a class
195
- since the block was written. Found 31 Aug 2026 by probing a live page. */
142
+ /* Tailwind's `duration-*` reads `--transition-duration-*`, not `--duration-*`,
143
+ so without these `duration-fast` silently compiles to nothing and nothing
144
+ animates (found 31 Aug 2026). */
196
145
  --transition-duration-micro: var(--duration-micro);
197
146
  --transition-duration-quick: var(--duration-quick);
198
147
  --transition-duration-fast: var(--duration-fast);
@@ -201,14 +150,8 @@
201
150
  --transition-duration-very-slow: var(--duration-very-slow);
202
151
  }
203
152
 
204
- /* The bits-ui state variants every primitive in `./shadcn` is written against.
205
- *
206
- * bits-ui v2 writes `data-checked` / `data-open` and v1 wrote
207
- * `data-state="checked"`, so each variant matches both and a component does not
208
- * have to know which it is talking to. They live here rather than in a
209
- * consumer's stylesheet because the classes that need them ship in this
210
- * package: an app that defines only the palette rendered a switch that never
211
- * moved. Moved out of upsys `tokens.css` on 14 Sep 2026, with the kit. */
153
+ /* Each bits-ui variant matches v2's `data-open` and v1's `data-state`, and ships
154
+ * here because an app that defined only the palette got a switch that never moved. */
212
155
  @custom-variant data-open {
213
156
  &:where([data-state="open"]),
214
157
  &:where([data-open]:not([data-open="false"])) {
@@ -1,16 +1,5 @@
1
- /* The rules the craft layer applies for itself.
2
- *
3
- * Two layers, on purpose. `base` holds the element defaults (focus, selection,
4
- * scrollbars) so any component class or utility beats them. `components` holds
5
- * the three motion primitives, so a utility still beats those too.
6
- *
7
- * WHY the layers are not optional: an `@import` at the top of an app stylesheet
8
- * lands OUTSIDE Tailwind's layers, and unlayered CSS beats every layered rule
9
- * whatever its specificity. Written bare, a `:where()` selector here would
10
- * silently outrank `focus-visible:ring-3` on every control in the app.
11
- *
12
- * Imported by ./index.css. Do not import it on its own, because it reads the tokens
13
- * that file declares. */
1
+ /* Keep every rule here inside a layer: unlayered CSS beats all layered rules, so a bare
2
+ * `:where()` would silently outrank `focus-visible:ring-3` on every control. */
14
3
 
15
4
  @layer base {
16
5
  /* One ring for the whole kit. `:where` keeps it at specificity zero on top of
@@ -26,9 +15,8 @@
26
15
  background: color-mix(in oklab, var(--ring-color) 24%, transparent);
27
16
  }
28
17
 
29
- /* A default macOS overlay scrollbar paints a black bar on a dark panel. Track
30
- transparent, thumb on the line token, so it belongs to whatever surface it
31
- is over. The border is what insets the thumb without a second element. */
18
+ /* The default macOS scrollbar paints a black bar on a dark panel, so the track stays
19
+ transparent and the border insets the thumb. */
32
20
  :where(html) {
33
21
  scrollbar-color: var(--color-border, var(--border)) transparent;
34
22
  }
@@ -49,22 +37,19 @@
49
37
  background-color: var(--color-border, var(--border));
50
38
  }
51
39
 
52
- ::-webkit-scrollbar-thumb:hover {
53
- background-color: var(--color-muted-foreground, var(--muted-foreground));
40
+ @media (hover: hover) and (pointer: fine) {
41
+ ::-webkit-scrollbar-thumb:hover {
42
+ background-color: var(--color-muted-foreground, var(--muted-foreground));
43
+ }
54
44
  }
55
45
  }
56
46
 
57
47
  @layer components {
58
- /* Three moves the kit repeats. They are classes rather than tokens because a
59
- component wants one name, not four declarations, and because the reduced
60
- motion guard then lives in one place instead of in every component.
61
-
62
- Namespaced `t-` to match the transitions-dev snippets, which these sit
63
- beside rather than duplicate. */
48
+ /* Classes, not tokens, so the reduced motion guard lives here once instead of in
49
+ every component. */
64
50
 
65
- /* A control that reads as a key: it travels down a pixel under the finger.
66
- The travel is what sells it, because a colour change alone reads as a hover
67
- state, not as a press. */
51
+ /* A key that travels down a pixel when pressed, because a colour change alone
52
+ reads as hover, not as a press. */
68
53
  .t-press {
69
54
  transition:
70
55
  background-color var(--duration-quick) var(--ease-smooth-out),
@@ -78,18 +63,19 @@
78
63
  transform: translateY(1px);
79
64
  }
80
65
 
81
- /* A card that answers the pointer. It rises by a pixel and gains the next
82
- step of elevation, so the hover reads as the card coming forward rather
83
- than as its border changing colour. */
66
+ /* The card rises a pixel and gains an elevation step, so hover reads as the card
67
+ coming forward, not its border changing colour. */
84
68
  .t-lift {
85
69
  transition:
86
70
  box-shadow var(--duration-fast) var(--ease-smooth-out),
87
71
  transform var(--duration-fast) var(--ease-smooth-out);
88
72
  }
89
73
 
90
- .t-lift:hover {
91
- transform: translateY(-1px);
92
- box-shadow: var(--elev-raised);
74
+ @media (hover: hover) and (pointer: fine) {
75
+ .t-lift:hover {
76
+ transform: translateY(-1px);
77
+ box-shadow: var(--elev-raised);
78
+ }
93
79
  }
94
80
 
95
81
  /* A surface arriving. The open carries the distance, the scale and the blur;
@@ -109,11 +95,16 @@
109
95
  transition: none;
110
96
  }
111
97
 
112
- .t-press:active,
113
- .t-lift:hover {
98
+ .t-press:active {
114
99
  transform: none;
115
100
  }
116
101
 
102
+ @media (hover: hover) and (pointer: fine) {
103
+ .t-lift:hover {
104
+ transform: none;
105
+ }
106
+ }
107
+
117
108
  .t-enter,
118
109
  .t-exit {
119
110
  animation-duration: 1ms;
@@ -1,87 +1,87 @@
1
1
  <script lang="ts">
2
- // =========================
3
- // IMPORTS
4
- // =========================
5
- import type { Editor } from "@tiptap/core";
6
- import { Button } from "../shadcn/button/index.js";
7
- import { Input } from "../shadcn/input/index.js";
8
- import {
9
- TextB,
10
- TextItalic,
11
- TextStrikethrough,
12
- LinkSimple,
13
- LinkBreak,
14
- ListBullets,
15
- ListNumbers,
16
- Quotes,
17
- VideoCamera,
18
- } from "phosphor-svelte";
2
+ // =========================
3
+ // IMPORTS
4
+ // =========================
5
+ import type { Editor } from "@tiptap/core";
6
+ import { Button } from "../shadcn/button/index.js";
7
+ import { Input } from "../shadcn/input/index.js";
8
+ import {
9
+ TextB,
10
+ TextItalic,
11
+ TextStrikethrough,
12
+ LinkSimple,
13
+ LinkBreak,
14
+ ListBullets,
15
+ ListNumbers,
16
+ Quotes,
17
+ VideoCamera,
18
+ } from "phosphor-svelte";
19
19
 
20
- // =========================
21
- // TYPES
22
- // =========================
23
- interface Props {
24
- editor: Editor | null;
25
- /** Bumped by the parent on every transaction, so active states re-read. */
26
- tick: number;
27
- preset: "full" | "notes";
28
- onInsertYoutube: () => void;
29
- }
20
+ // =========================
21
+ // TYPES
22
+ // =========================
23
+ interface Props {
24
+ editor: Editor | null;
25
+ /** Bumped by the parent on every transaction, so active states re-read. */
26
+ tick: number;
27
+ preset: "full" | "notes";
28
+ onInsertYoutube: () => void;
29
+ }
30
30
 
31
- // =========================
32
- // PROPS
33
- // =========================
34
- let { editor: editorProp, tick, preset, onInsertYoutube }: Props = $props();
31
+ // =========================
32
+ // PROPS
33
+ // =========================
34
+ let { editor: editorProp, tick, preset, onInsertYoutube }: Props = $props();
35
35
 
36
- // =========================
37
- // STATE
38
- // =========================
39
- let linkUrl = $state("");
40
- let showLinkPopover = $state(false);
41
- let linkInputEl: HTMLInputElement | undefined = $state();
36
+ // =========================
37
+ // STATE
38
+ // =========================
39
+ let linkUrl = $state("");
40
+ let showLinkPopover = $state(false);
41
+ let linkInputEl: HTMLInputElement | undefined = $state();
42
42
 
43
- // =========================
44
- // DERIVED
45
- // =========================
46
- let editor: Editor | null = $derived(tick >= 0 ? editorProp : null);
43
+ // =========================
44
+ // DERIVED
45
+ // =========================
46
+ let editor: Editor | null = $derived(tick >= 0 ? editorProp : null);
47
47
 
48
- // =========================
49
- // EFFECTS
50
- // =========================
51
- $effect(() => {
52
- if (showLinkPopover && linkInputEl) {
53
- linkInputEl.focus();
54
- }
55
- });
48
+ // =========================
49
+ // EFFECTS
50
+ // =========================
51
+ $effect(() => {
52
+ if (showLinkPopover && linkInputEl) {
53
+ linkInputEl.focus();
54
+ }
55
+ });
56
56
 
57
- // =========================
58
- // FUNCTIONS
59
- // =========================
60
- function toggleLink() {
61
- if (!editor) return;
62
- if (editor.isActive("link")) {
63
- editor.chain().focus().unsetLink().run();
64
- return;
57
+ // =========================
58
+ // FUNCTIONS
59
+ // =========================
60
+ function toggleLink() {
61
+ if (!editor) return;
62
+ if (editor.isActive("link")) {
63
+ editor.chain().focus().unsetLink().run();
64
+ return;
65
+ }
66
+ const previousUrl = editor.getAttributes("link").href;
67
+ linkUrl = previousUrl || "";
68
+ showLinkPopover = true;
65
69
  }
66
- const previousUrl = editor.getAttributes("link").href;
67
- linkUrl = previousUrl || "";
68
- showLinkPopover = true;
69
- }
70
70
 
71
- function applyLink() {
72
- if (!editor) return;
73
- if (linkUrl.trim()) {
74
- editor.chain().focus().setLink({ href: linkUrl.trim() }).run();
71
+ function applyLink() {
72
+ if (!editor) return;
73
+ if (linkUrl.trim()) {
74
+ editor.chain().focus().setLink({ href: linkUrl.trim() }).run();
75
+ }
76
+ linkUrl = "";
77
+ showLinkPopover = false;
75
78
  }
76
- linkUrl = "";
77
- showLinkPopover = false;
78
- }
79
79
 
80
- function cancelLink() {
81
- linkUrl = "";
82
- showLinkPopover = false;
83
- editor?.chain().focus().run();
84
- }
80
+ function cancelLink() {
81
+ linkUrl = "";
82
+ showLinkPopover = false;
83
+ editor?.chain().focus().run();
84
+ }
85
85
  </script>
86
86
 
87
87
  <!-- =========================== -->
@@ -98,7 +98,10 @@ function cancelLink() {
98
98
  placeholder="Paste link..."
99
99
  class="h-7 w-52 text-[0.8125rem]"
100
100
  onkeydown={(e) => {
101
- if (e.key === "Enter") { e.preventDefault(); applyLink(); }
101
+ if (e.key === "Enter") {
102
+ e.preventDefault();
103
+ applyLink();
104
+ }
102
105
  if (e.key === "Escape") cancelLink();
103
106
  }}
104
107
  />
@@ -106,12 +109,12 @@ function cancelLink() {
106
109
  <LinkSimple size={14} />
107
110
  </Button>
108
111
  <Button
109
- variant="ghost"
110
- size="sm"
111
- icon
112
- class="text-muted-foreground aria-pressed:bg-brand/10 aria-pressed:text-brand"
113
- onclick={cancelLink}
114
- title="Cancel"
112
+ variant="ghost"
113
+ size="sm"
114
+ icon
115
+ class="text-muted-foreground aria-pressed:bg-brand/10 aria-pressed:text-brand"
116
+ onclick={cancelLink}
117
+ title="Cancel"
115
118
  >
116
119
  &times;
117
120
  </Button>
@@ -191,24 +194,25 @@ function cancelLink() {
191
194
 
192
195
  {#if editor.isActive("link")}
193
196
  <Button
194
- variant="ghost"
195
- size="sm"
196
- icon
197
- class="text-muted-foreground aria-pressed:bg-brand/10 aria-pressed:text-brand"
198
- aria-pressed={true}
199
- onclick={() => editor?.chain().focus().unsetLink().run()}
197
+ variant="ghost"
198
+ size="sm"
199
+ icon
200
+ class="text-muted-foreground aria-pressed:bg-brand/10 aria-pressed:text-brand"
201
+ aria-pressed={true}
202
+ onclick={() => editor?.chain().focus().unsetLink().run()}
200
203
  title="Remove link"
201
204
  >
202
205
  <LinkBreak size={15} />
203
206
  </Button>
204
207
  {:else}
205
208
  <Button
206
- variant="ghost"
207
- size="sm"
208
- icon
209
- class="text-muted-foreground aria-pressed:bg-brand/10 aria-pressed:text-brand"
210
- onclick={toggleLink}
211
- title="Add link (Cmd+K)">
209
+ variant="ghost"
210
+ size="sm"
211
+ icon
212
+ class="text-muted-foreground aria-pressed:bg-brand/10 aria-pressed:text-brand"
213
+ onclick={toggleLink}
214
+ title="Add link (Cmd+K)"
215
+ >
212
216
  <LinkSimple size={15} />
213
217
  </Button>
214
218
  {/if}
@@ -253,13 +257,14 @@ function cancelLink() {
253
257
  <div class="toolbar-divider"></div>
254
258
 
255
259
  <Button
256
- variant="ghost"
257
- size="sm"
258
- icon
259
- class="text-muted-foreground aria-pressed:bg-brand/10 aria-pressed:text-brand"
260
- aria-pressed={editor.isActive("youtube")}
261
- onclick={onInsertYoutube}
262
- title="Insert YouTube video">
260
+ variant="ghost"
261
+ size="sm"
262
+ icon
263
+ class="text-muted-foreground aria-pressed:bg-brand/10 aria-pressed:text-brand"
264
+ aria-pressed={editor.isActive("youtube")}
265
+ onclick={onInsertYoutube}
266
+ title="Insert YouTube video"
267
+ >
263
268
  <VideoCamera size={15} />
264
269
  </Button>
265
270
  {/if}