@justanarthur/modo-ui 0.0.0-stage → 0.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 (171) hide show
  1. package/LICENSE +21 -0
  2. package/LICENSE.beui +21 -0
  3. package/LICENSE.fluid-functionalism +21 -0
  4. package/LICENSE.motion-primitives +21 -0
  5. package/LICENSE.sileo +21 -0
  6. package/README.md +608 -2
  7. package/dist/base.css +994 -0
  8. package/dist/blocks/sidebar-app/index.d.ts +34 -0
  9. package/dist/blocks/sidebar-app/index.js +31 -0
  10. package/dist/components/badge/index.d.ts +59 -0
  11. package/dist/components/badge/index.js +16 -0
  12. package/dist/components/button/index.d.ts +59 -0
  13. package/dist/components/button/index.js +14 -0
  14. package/dist/components/card/index.d.ts +141 -0
  15. package/dist/components/card/index.js +42 -0
  16. package/dist/components/inputs/checkbox-group/index.d.ts +50 -0
  17. package/dist/components/inputs/checkbox-group/index.js +21 -0
  18. package/dist/components/inputs/color-picker/index.d.ts +137 -0
  19. package/dist/components/inputs/color-picker/index.js +35 -0
  20. package/dist/components/inputs/combobox/index.d.ts +161 -0
  21. package/dist/components/inputs/combobox/index.js +38 -0
  22. package/dist/components/inputs/date-time/calendar/index.d.ts +45 -0
  23. package/dist/components/inputs/date-time/calendar/index.js +18 -0
  24. package/dist/components/inputs/date-time/date-picker/index.d.ts +82 -0
  25. package/dist/components/inputs/date-time/date-picker/index.js +32 -0
  26. package/dist/components/inputs/date-time/date-range-picker/index.d.ts +64 -0
  27. package/dist/components/inputs/date-time/date-range-picker/index.js +31 -0
  28. package/dist/components/inputs/date-time/duration-picker/index.d.ts +57 -0
  29. package/dist/components/inputs/date-time/duration-picker/index.js +31 -0
  30. package/dist/components/inputs/date-time/month-picker/index.d.ts +57 -0
  31. package/dist/components/inputs/date-time/month-picker/index.js +30 -0
  32. package/dist/components/inputs/date-time/time-picker/index.d.ts +59 -0
  33. package/dist/components/inputs/date-time/time-picker/index.js +31 -0
  34. package/dist/components/inputs/input/index.d.ts +84 -0
  35. package/dist/components/inputs/input/index.js +19 -0
  36. package/dist/components/inputs/input-copy/index.d.ts +44 -0
  37. package/dist/components/inputs/input-copy/index.js +16 -0
  38. package/dist/components/inputs/input-message/index.d.ts +125 -0
  39. package/dist/components/inputs/input-message/index.js +25 -0
  40. package/dist/components/inputs/radio-group/index.d.ts +58 -0
  41. package/dist/components/inputs/radio-group/index.js +20 -0
  42. package/dist/components/inputs/search/index.d.ts +83 -0
  43. package/dist/components/inputs/search/index.js +25 -0
  44. package/dist/components/inputs/select/index.d.ts +105 -0
  45. package/dist/components/inputs/select/index.js +34 -0
  46. package/dist/components/inputs/slider/index.d.ts +108 -0
  47. package/dist/components/inputs/slider/index.js +14 -0
  48. package/dist/components/inputs/switch/index.d.ts +43 -0
  49. package/dist/components/inputs/switch/index.js +14 -0
  50. package/dist/components/navigation/accordion/index.d.ts +117 -0
  51. package/dist/components/navigation/accordion/index.js +28 -0
  52. package/dist/components/navigation/sidebar/index.d.ts +388 -0
  53. package/dist/components/navigation/sidebar/index.js +99 -0
  54. package/dist/components/navigation/tabs/index.d.ts +70 -0
  55. package/dist/components/navigation/tabs/index.js +23 -0
  56. package/dist/components/navigation/tabs-subtle/index.d.ts +73 -0
  57. package/dist/components/navigation/tabs-subtle/index.js +21 -0
  58. package/dist/components/overlays/alert-dialog/index.d.ts +67 -0
  59. package/dist/components/overlays/alert-dialog/index.js +22 -0
  60. package/dist/components/overlays/command/index.d.ts +121 -0
  61. package/dist/components/overlays/command/index.js +36 -0
  62. package/dist/components/overlays/context-menu/index.d.ts +97 -0
  63. package/dist/components/overlays/context-menu/index.js +29 -0
  64. package/dist/components/overlays/dialog/index.d.ts +130 -0
  65. package/dist/components/overlays/dialog/index.js +37 -0
  66. package/dist/components/overlays/dropdown/index.d.ts +222 -0
  67. package/dist/components/overlays/dropdown/index.js +44 -0
  68. package/dist/components/overlays/popover/index.d.ts +87 -0
  69. package/dist/components/overlays/popover/index.js +26 -0
  70. package/dist/components/overlays/preview-card/index.d.ts +75 -0
  71. package/dist/components/overlays/preview-card/index.js +19 -0
  72. package/dist/components/overlays/sheet/index.d.ts +67 -0
  73. package/dist/components/overlays/sheet/index.js +24 -0
  74. package/dist/components/overlays/toast/index.d.ts +107 -0
  75. package/dist/components/overlays/toast/index.js +21 -0
  76. package/dist/components/overlays/tooltip/index.d.ts +75 -0
  77. package/dist/components/overlays/tooltip/index.js +17 -0
  78. package/dist/components/table/index.d.ts +44 -0
  79. package/dist/components/table/index.js +26 -0
  80. package/dist/components/thinking-indicator/index.d.ts +32 -0
  81. package/dist/components/thinking-indicator/index.js +13 -0
  82. package/dist/index.d.ts +3926 -0
  83. package/dist/index.js +511 -0
  84. package/dist/preset.d.ts +104 -0
  85. package/dist/preset.js +124 -0
  86. package/dist/primitives/code/index.d.ts +23 -0
  87. package/dist/primitives/code/index.js +11 -0
  88. package/dist/primitives/craft/index.d.ts +31 -0
  89. package/dist/primitives/craft/index.js +11 -0
  90. package/dist/primitives/fluid-hover/index.d.ts +233 -0
  91. package/dist/primitives/fluid-hover/index.js +25 -0
  92. package/dist/primitives/morph/index.d.ts +127 -0
  93. package/dist/primitives/morph/index.js +19 -0
  94. package/dist/primitives/motion/index.d.ts +115 -0
  95. package/dist/primitives/motion/index.js +19 -0
  96. package/dist/primitives/scroll-area/index.d.ts +41 -0
  97. package/dist/primitives/scroll-area/index.js +12 -0
  98. package/dist/primitives/sizes/index.d.ts +128 -0
  99. package/dist/primitives/sizes/index.js +21 -0
  100. package/dist/primitives/surface/index.d.ts +69 -0
  101. package/dist/primitives/surface/index.js +17 -0
  102. package/dist/shared/components-0ke6xada.js +123 -0
  103. package/dist/shared/components-0q0sqfh4.js +146 -0
  104. package/dist/shared/components-0sn7ypjw.js +61 -0
  105. package/dist/shared/components-1j9791sn.js +345 -0
  106. package/dist/shared/components-1rfwbyyc.js +361 -0
  107. package/dist/shared/components-1v7rxytx.js +30 -0
  108. package/dist/shared/components-1xekx2hx.js +215 -0
  109. package/dist/shared/components-39jq9va1.js +86 -0
  110. package/dist/shared/components-3gae8nta.js +719 -0
  111. package/dist/shared/components-3k8xpnpq.js +1759 -0
  112. package/dist/shared/components-3ky44908.js +9 -0
  113. package/dist/shared/components-3mkepka5.js +111 -0
  114. package/dist/shared/components-3wk0w46f.js +667 -0
  115. package/dist/shared/components-4cnjmgex.js +124 -0
  116. package/dist/shared/components-59efxjd9.js +298 -0
  117. package/dist/shared/components-5xc2j8jz.js +159 -0
  118. package/dist/shared/components-62ybc7t2.js +496 -0
  119. package/dist/shared/components-66mv71kx.js +262 -0
  120. package/dist/shared/components-7ans7ce5.js +120 -0
  121. package/dist/shared/components-7btqt08d.js +270 -0
  122. package/dist/shared/components-7r3etddy.js +171 -0
  123. package/dist/shared/components-7x9kawxv.js +152 -0
  124. package/dist/shared/components-8fpr3s4p.js +292 -0
  125. package/dist/shared/components-9jvzzvt1.js +198 -0
  126. package/dist/shared/components-apdwtweq.js +484 -0
  127. package/dist/shared/components-asr5pg50.js +191 -0
  128. package/dist/shared/components-b2wyq2cq.js +232 -0
  129. package/dist/shared/components-c6s6b43g.js +15 -0
  130. package/dist/shared/components-ca2c898n.js +1828 -0
  131. package/dist/shared/components-cbxjn9p8.js +141 -0
  132. package/dist/shared/components-chacnk61.js +216 -0
  133. package/dist/shared/components-cv16mryv.js +62 -0
  134. package/dist/shared/components-ez65vtbq.js +110 -0
  135. package/dist/shared/components-ezxhrmer.js +17 -0
  136. package/dist/shared/components-gc5vtq2f.js +149 -0
  137. package/dist/shared/components-gf5s6ap9.js +42 -0
  138. package/dist/shared/components-gvr9w41t.js +193 -0
  139. package/dist/shared/components-h2dpcwgc.js +137 -0
  140. package/dist/shared/components-hawvy2qz.js +105 -0
  141. package/dist/shared/components-hqeq3w9q.js +375 -0
  142. package/dist/shared/components-jjw69c4c.js +28 -0
  143. package/dist/shared/components-jwst3vtj.js +346 -0
  144. package/dist/shared/components-kvr7f9hy.js +45 -0
  145. package/dist/shared/components-men63rwj.js +9 -0
  146. package/dist/shared/components-mhd39z7x.js +22 -0
  147. package/dist/shared/components-n7mjr357.js +26 -0
  148. package/dist/shared/components-ng72k97e.js +128 -0
  149. package/dist/shared/components-nh8tma1n.js +986 -0
  150. package/dist/shared/components-njzgqdwy.js +445 -0
  151. package/dist/shared/components-ny774h8p.js +293 -0
  152. package/dist/shared/components-q4daerx0.js +58 -0
  153. package/dist/shared/components-q5xj5c16.js +79 -0
  154. package/dist/shared/components-rr7a6ja9.js +160 -0
  155. package/dist/shared/components-s2bz1xpk.js +63 -0
  156. package/dist/shared/components-tqvarbfv.js +288 -0
  157. package/dist/shared/components-v6xp325t.js +304 -0
  158. package/dist/shared/components-v7wtz8hk.js +144 -0
  159. package/dist/shared/components-vc7vq5nz.js +32 -0
  160. package/dist/shared/components-vfbhxf5r.js +58 -0
  161. package/dist/shared/components-vynjtrk6.js +19 -0
  162. package/dist/shared/components-wvp1m52g.js +1200 -0
  163. package/dist/shared/components-yh3kwdhr.js +981 -0
  164. package/dist/shared/components-ytb2tefr.js +291 -0
  165. package/dist/shared/components-zfnmn5d4.js +220 -0
  166. package/dist/shared/components-zrkzcayz.js +141 -0
  167. package/dist/shared/components-zx6b6yby.js +42 -0
  168. package/dist/styles.css +1949 -0
  169. package/dist/tailwind.css +219 -0
  170. package/dist/tokens.css +345 -0
  171. package/package.json +103 -4
package/dist/base.css ADDED
@@ -0,0 +1,994 @@
1
+ @import "@fontsource-variable/inter/opsz.css";
2
+ /*
3
+ * Fluid Functionalism color tokens.
4
+ *
5
+ * Source: mickadesign/fluid-functionalism `app/globals.css` §1 (DESIGN TOKENS)
6
+ * at commit b3587bdbd83fc66c2a6aae3817ffb856cb09260b — MIT License
7
+ * © 2026 Micka Touillaud — fluidfunctionalism.com.
8
+ *
9
+ * Migration from upstream: every HEX literal was converted to its exact oklch
10
+ * equivalent (sRGB → OKLab, round-trip verified to the original hex). The
11
+ * rgb()/rgba() alpha overlays (--overlay, --hover, --active, --shadow-color,
12
+ * --dm-*) are kept as-is — they are alpha contracts, not palette colors.
13
+ *
14
+ * Every color token is written ONCE, as light-dark(light, dark). Which side
15
+ * renders is driven by `color-scheme` (set in base.css): :root follows the
16
+ * OS, and a `.light` or `.dark` class pins it. The block is declared on
17
+ * `:root, .light, .dark` — not just `:root` — because light-dark() inside a
18
+ * custom property resolves against the color-scheme of the element the
19
+ * property is declared on, so a forced-scope subtree becomes its own
20
+ * resolution point.
21
+ *
22
+ * The surface ladder and its shadow recipes live with their primitive in
23
+ * primitives/surface/surface.css. The one token that cannot ride
24
+ * light-dark() here — the --overlay RGB triplet (consumed as
25
+ * rgb(var(--overlay) / α), an alpha contract) — is re-pointed per scheme in
26
+ * base.css.
27
+ */
28
+
29
+ :root,
30
+ .light,
31
+ .dark {
32
+ /* The surface ladder (--surface-1..8) and the shadow recipes pair with it
33
+ in primitives/surface/surface.css. */
34
+
35
+ /* ── Semantic tokens ── */
36
+ /* Same color as --surface-1, inlined so the global token set never
37
+ depends on the surface primitive's CSS being loaded. */
38
+ --background: light-dark(oklch(0.985 0 0), oklch(0.205 0 0));
39
+ --foreground: light-dark(oklch(0.205 0 0), oklch(0.97 0 0));
40
+ /* Same color as --surface-3, inlined for the same reason. */
41
+ --card: light-dark(oklch(1 0 0), oklch(0.264 0 0));
42
+ --card-foreground: light-dark(oklch(0.205 0 0), oklch(0.97 0 0));
43
+ /* Light `muted` decouples from surface-2: the light surface ladder is so
44
+ compressed that a ladder value would read as near-white, making anything
45
+ sitting on it blend together. A slightly greyer value gives muted real
46
+ semantic presence. Dark keeps the ladder value (= surface-2). */
47
+ --muted: light-dark(oklch(0.967 0.001 286.375), oklch(0.235 0 0));
48
+ --muted-foreground: light-dark(oklch(0.556 0 0), oklch(0.7155 0 0));
49
+
50
+ /* ── Interactive states ── */
51
+ --accent: light-dark(oklch(0.922 0 0), oklch(0.439 0 0));
52
+ --accent-foreground: light-dark(oklch(0.205 0 0), oklch(0.97 0 0));
53
+ /* --accent nudged toward the overlay direction (the switch track's hover). */
54
+ --accent-hover: color-mix(in oklab, var(--accent), rgb(var(--overlay)) 10%);
55
+ --selected: light-dark(oklch(0.87 0 0), oklch(0.439 0 0));
56
+
57
+ /* ── Borders ──
58
+ --border mixes from --foreground, so it tracks the theme automatically —
59
+ including inside nested forced scopes, where light-dark() re-resolves
60
+ --foreground before the mix happens. */
61
+ --border: color-mix(in oklab, var(--foreground) 12%, transparent);
62
+ --ring: light-dark(oklch(0.922 0 0), oklch(0.371 0 0));
63
+ --input: light-dark(oklch(0.922 0 0), oklch(0.371 0 0));
64
+
65
+ /* ── Error / Destructive ── */
66
+ --destructive: light-dark(oklch(0.6368 0.2078 25.33), oklch(0.7106 0.1661 22.22));
67
+ --destructive-light: light-dark(oklch(0.971 0.013 17.38), oklch(0.2575 0.0886 26.04));
68
+
69
+ /* ── Brand blue ──
70
+ FF's one chromatic accent: the switch's on-track, the copy field's hover
71
+ highlight, drop-target and selected-swatch rings. */
72
+ --brand: oklch(0.693 0.16 265);
73
+ --brand-hover: oklch(0.649 0.164 264.76);
74
+
75
+ /* ── Focus indicator ──
76
+ Every component focus ring reads it (`ring-focus-ring`,
77
+ `border-focus-ring`), so this is the single theming point. */
78
+ --focus-ring: var(--brand);
79
+
80
+ /* ── Alpha checkerboard (color-picker swatches) ── */
81
+ --checker-a: light-dark(oklch(0.792 0 0), oklch(0.239 0 0));
82
+ --checker-b: light-dark(oklch(1 0 0), oklch(0.285 0 0));
83
+ /* Swatch edges: a mid-grey hairline that reads on any swatch color in
84
+ either scheme, plus the hover / selected rings, set off by a
85
+ --background gap. */
86
+ --swatch-edge: rgb(127 127 127 / 0.25);
87
+ --swatch-edge-hover: rgb(127 127 127 / 0.4);
88
+ --shadow-swatch: inset 0 0 0 1px var(--swatch-edge);
89
+ --shadow-swatch-hover: var(--shadow-swatch), 0 0 0 2px var(--background), 0 0 0 4px var(--swatch-edge-hover);
90
+ --shadow-swatch-selected: var(--shadow-swatch), 0 0 0 2px var(--background), 0 0 0 4px var(--brand);
91
+
92
+ /* ── Thumbs (switch, slider) ──
93
+ White in both schemes, set off by a soft drop. */
94
+ --thumb: oklch(1 0 0);
95
+ --shadow-thumb: 0 1px 2px rgb(0 0 0 / 0.1);
96
+
97
+ /* ── Color picker affordances ──
98
+ Drawn over any user color, so fixed white and black rather than theme
99
+ colors: they have to read on every fill, in either scheme. */
100
+ --picker-edge: oklch(0 0 0);
101
+ --picker-ring: oklch(1 0 0 / 0.55);
102
+ --picker-thumb-border: oklch(1 0 0 / 0.9);
103
+ --shadow-picker-thumb: 0 0 0 1px var(--picker-edge);
104
+ --shadow-picker-cursor: 0 0 0 1px oklch(0 0 0 / 0.2);
105
+ --drop-shadow-picker-cursor: 0 1px 1px oklch(0 0 0 / 0.4);
106
+
107
+ /* ── Controls ──
108
+ An unchecked checkbox or radio takes a stronger edge than --border;
109
+ a modal dialog dims the page behind it with the scrim. */
110
+ --control-border: light-dark(oklch(0.708 0 0), oklch(0.556 0 0));
111
+ --scrim: light-dark(oklch(0 0 0 / 0.4), oklch(0 0 0 / 0.8));
112
+
113
+ /* ── Surface-relative interactive overlays (work on any elevation) ──
114
+ --overlay is the tint-direction triplet — black for light surfaces,
115
+ white for dark. Components consume it as rgb(var(--overlay) / α), so it
116
+ stays an RGB triplet and switches per theme in base.css. --hover and
117
+ --active are the pre-mixed conveniences. */
118
+ --overlay: 0 0 0;
119
+ --hover: light-dark(rgb(0 0 0 / 0.04), rgb(255 255 255 / 0.06));
120
+ --active: light-dark(rgb(0 0 0 / 0.07), rgb(255 255 255 / 0.1));
121
+
122
+ /* ── Syntax (primitives/code) ──
123
+ Low-chroma hues so code reads as text first; identifiers, JSX text and
124
+ punctuation ride the existing foreground pair. */
125
+ --syntax-keyword: light-dark(oklch(0.52 0.16 300), oklch(0.76 0.12 300));
126
+ --syntax-string: light-dark(oklch(0.52 0.12 150), oklch(0.79 0.11 150));
127
+ --syntax-class: light-dark(oklch(0.56 0.12 65), oklch(0.81 0.1 75));
128
+ --syntax-entity: light-dark(oklch(0.52 0.15 255), oklch(0.77 0.11 250));
129
+ --syntax-property: light-dark(oklch(0.52 0.1 205), oklch(0.79 0.08 200));
130
+ --syntax-comment: light-dark(oklch(0.62 0 0), oklch(0.56 0 0));
131
+ --syntax-identifier: var(--foreground);
132
+ --syntax-jsx: var(--foreground);
133
+ --syntax-sign: var(--muted-foreground);
134
+ }
135
+
136
+ /* ── Badge palette ──
137
+ Badge's `color` hues (upstream hex, as exact oklch). Constant across
138
+ schemes; the solid variant mixes each 15% into --background. */
139
+ :root {
140
+ --badge-gray: oklch(0.715 0 0);
141
+ --badge-red: oklch(0.637 0.208 25.33);
142
+ --badge-orange: oklch(0.7049 0.1867 47.6);
143
+ --badge-amber: oklch(0.7686 0.1647 70.08);
144
+ --badge-yellow: oklch(0.7952 0.1617 86.05);
145
+ --badge-lime: oklch(0.7681 0.2044 130.85);
146
+ --badge-green: oklch(0.723 0.192 149.58);
147
+ --badge-emerald: oklch(0.6959 0.1491 162.48);
148
+ --badge-teal: oklch(0.704 0.123 182.5);
149
+ --badge-cyan: oklch(0.7148 0.1257 215.22);
150
+ --badge-blue: oklch(0.623 0.188 259.81);
151
+ --badge-indigo: oklch(0.585 0.204 277.12);
152
+ --badge-violet: oklch(0.606 0.219 292.72);
153
+ --badge-purple: oklch(0.627 0.233 303.9);
154
+ --badge-fuchsia: oklch(0.667 0.259 322.15);
155
+ --badge-pink: oklch(0.656 0.212 354.31);
156
+ --badge-rose: oklch(0.645 0.215 16.44);
157
+ }
158
+
159
+ /* ── Status roles ──
160
+ The toast states, picked from the badge palette (and --destructive /
161
+ --brand) by reference, so a status reads the same wherever it shows. */
162
+ :root {
163
+ --status-success: var(--badge-green);
164
+ --status-loading: var(--badge-gray);
165
+ --status-error: var(--destructive);
166
+ --status-warning: var(--badge-amber);
167
+ --status-info: var(--badge-blue);
168
+ --status-action: var(--brand);
169
+ }
170
+
171
+ /*
172
+ * Fluid Functionalism motion tokens.
173
+ *
174
+ * Source: mickadesign/fluid-functionalism `registry/default/lib/springs.ts`
175
+ * at commit b3587bdbd83fc66c2a6aae3817ffb856cb09260b — MIT License © 2026
176
+ * Micka Touillaud — fluidfunctionalism.com.
177
+ *
178
+ * FF's motion is JS: the `spring` tiers in lib/springs.ts feed
179
+ * `motion/react`, and components always reach for a tier rather than a
180
+ * hand-written duration. These properties mirror those tiers for CSS
181
+ * consumers and for the tokens page — keep the two in sync.
182
+ *
183
+ * Each tier's enter transition is a spring of the given duration; its exit is
184
+ * a plain tween one step quicker, so a dismissal reads as crisp and final.
185
+ * fast 80ms enter, 60ms exit — critically damped (bounce 0)
186
+ * moderate 160ms enter, 120ms exit — critically damped (bounce 0): panels,
187
+ * dropdowns, tabs, merged selection backgrounds
188
+ * slow 240ms enter, 160ms exit — the one tier with a little bounce
189
+ * (bounce 0.12)
190
+ * The bigger the thing that moves, the slower the tier.
191
+ *
192
+ * Local addition: `goo`, the morph engine's tier for the goo effect — 250ms
193
+ * to land (bounce 0.15), a 200ms spring back — so the liquid neck reads.
194
+ */
195
+
196
+ :root {
197
+ --duration-fast: 80ms;
198
+ --duration-fast-exit: 60ms;
199
+ --duration-moderate: 160ms;
200
+ --duration-moderate-exit: 120ms;
201
+ --duration-slow: 240ms;
202
+ --duration-slow-exit: 160ms;
203
+ --duration-goo: 250ms;
204
+ --duration-goo-exit: 200ms;
205
+ }
206
+
207
+ :root {
208
+ /* Fluid Functionalism's corner token (upstream tokens/radius.css). */
209
+ --radius: 0.625rem;
210
+
211
+ /* Small-box corners between wind4's xs (2px) and md (6px) steps, read by
212
+ the `rounded-box|glyph` utility in preset.ts: checkbox squares and
213
+ keycaps, and the composer's stop glyph. */
214
+ --radius-box: 5px;
215
+ --radius-glyph: 3px;
216
+ }
217
+
218
+ :root {
219
+ /* The docs chrome's spacing rhythm (AGENTS.md generic tokens). */
220
+ --space-1: 4px;
221
+ --space-2: 8px;
222
+ --space-3: 12px;
223
+ --space-4: 16px;
224
+ --space-5: 24px;
225
+ --space-6: 32px;
226
+ }
227
+
228
+ /*
229
+ * Fluid Functionalism type tokens.
230
+ *
231
+ * Source: mickadesign/fluid-functionalism `app/globals.css` §3–4 (`--font-sans`,
232
+ * the `--fs-*` type-scale roles and their `@utility text-*` classes) at commit
233
+ * b3587bdbd83fc66c2a6aae3817ffb856cb09260b — MIT License © 2026 Micka
234
+ * Touillaud — fluidfunctionalism.com.
235
+ *
236
+ * Local changes:
237
+ * - The family is Inter Variable from @fontsource-variable/inter/opsz
238
+ * (imported in base.css) instead of upstream's self-hosted
239
+ * InterVariable.ttf. The opsz build matters: the `--weight-*` ladder sets
240
+ * both the wght and opsz axes.
241
+ * - The role sizes are renamed `--fs-*` → `--text-*` and read by the
242
+ * `text-<role>[-compact]` utility in preset.ts. They hold both columns of
243
+ * `typeScale` in lib/size-context.tsx (keep the two in sync), so a
244
+ * component's `compact ? … : …` picks a token on either side. Upstream's
245
+ * site-chrome extras — the 22px mobile display step and the
246
+ * `html[data-size="compact"]` override set by the docs site's
247
+ * SizeAttribute — are not carried over.
248
+ * - `micro` (keycaps, one notch below caption) is a local role.
249
+ * - Each role's line-height (FF's typography page: 28/34, 16/22, 14/20,
250
+ * 13/20, 12/16) is a `--leading-*` token, emitted by the same
251
+ * `text-<role>` rule; the compact step keeps its role's leading.
252
+ * - The docs chrome's element roles (`--font-size-<el>`, `--line-height-<el>`,
253
+ * `--font-weight-<el>`, `--color-<el>`) reference these, following FF's
254
+ * rules: headings in foreground, text muted.
255
+ * - Upstream's `fontWeights` (lib/font-weight.ts, fontVariationSettings
256
+ * strings) become the `--weight-*` variation settings, read by the
257
+ * `weight-*` utility in preset.ts. Each derives its wght from
258
+ * `--font-weight-*`, the plain font-weight form the docs chrome reads.
259
+ */
260
+
261
+ :root {
262
+ --font-sans: "Inter Variable", ui-sans-serif, system-ui, sans-serif;
263
+ --font-mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
264
+
265
+ /* Page titles. */
266
+ --text-display: 28px;
267
+ /* Section headings, dialog titles. */
268
+ --text-title: 16px;
269
+ /* Card titles, emphasized rows. */
270
+ --text-subtitle: 14px;
271
+ /* Control labels and body copy. */
272
+ --text-body: 13px;
273
+ /* Secondary text: descriptions, meta rows, errors, group labels. */
274
+ --text-caption: 12px;
275
+ /* Keycaps and chips. */
276
+ --text-micro: 11px;
277
+
278
+ /* Each role's paired line-height. */
279
+ --leading-display: 34px;
280
+ --leading-title: 22px;
281
+ --leading-subtitle: 20px;
282
+ --leading-body: 20px;
283
+ --leading-caption: 16px;
284
+ --leading-micro: 14px;
285
+
286
+ /* The compact step: each role one notch down. */
287
+ --text-display-compact: 24px;
288
+ --text-title-compact: 15px;
289
+ --text-subtitle-compact: 13px;
290
+ --text-body-compact: 12px;
291
+ --text-caption-compact: 11px;
292
+ --text-micro-compact: 10px;
293
+
294
+ /* Inter variable weights, each paired with the opsz that keeps a label's
295
+ advance width steady as it changes weight. */
296
+ --font-weight-normal: 400;
297
+ --font-weight-medium: 450;
298
+ --font-weight-semibold: 550;
299
+ --font-weight-bold: 700;
300
+ --weight-normal: "wght" var(--font-weight-normal), "opsz" 14;
301
+ --weight-medium: "wght" var(--font-weight-medium), "opsz" 15;
302
+ --weight-semibold: "wght" var(--font-weight-semibold), "opsz" 18;
303
+ --weight-bold: "wght" var(--font-weight-bold), "opsz" 25;
304
+
305
+ /* The docs chrome's elements (modo's type contract), on the roles above. */
306
+ --font-size-h1: var(--text-display);
307
+ --line-height-h1: var(--leading-display);
308
+ --font-weight-h1: var(--font-weight-bold);
309
+ --color-h1: var(--foreground);
310
+
311
+ --font-size-h2: var(--text-title);
312
+ --line-height-h2: var(--leading-title);
313
+ --font-weight-h2: var(--font-weight-semibold);
314
+ --color-h2: var(--foreground);
315
+
316
+ --font-size-h3: var(--text-subtitle);
317
+ --line-height-h3: var(--leading-subtitle);
318
+ --font-weight-h3: var(--font-weight-semibold);
319
+ --color-h3: var(--foreground);
320
+
321
+ --font-size-h4: var(--text-body);
322
+ --line-height-h4: var(--leading-body);
323
+ --font-weight-h4: var(--font-weight-semibold);
324
+ --color-h4: var(--foreground);
325
+
326
+ --font-size-p: var(--text-body);
327
+ --line-height-p: var(--leading-body);
328
+ --font-weight-p: var(--font-weight-normal);
329
+ --color-p: var(--muted-foreground);
330
+
331
+ --font-size-lead: var(--text-body);
332
+ --line-height-lead: var(--leading-body);
333
+ --font-weight-lead: var(--font-weight-normal);
334
+ --color-lead: var(--muted-foreground);
335
+
336
+ --font-size-eyebrow: var(--text-caption);
337
+ --line-height-eyebrow: var(--leading-caption);
338
+ --font-weight-eyebrow: var(--font-weight-medium);
339
+ --color-eyebrow: var(--muted-foreground);
340
+
341
+ --font-size-sm: var(--text-caption);
342
+ --line-height-sm: var(--leading-caption);
343
+
344
+ --font-size-xs: var(--text-micro);
345
+ --line-height-xs: var(--leading-micro);
346
+ }
347
+
348
+ /* biome-ignore-all lint: vendored upstream code keeps its own values (see the header) */
349
+ /*
350
+ * UI theme plumbing and base styles.
351
+ *
352
+ * Source: mickadesign/fluid-functionalism `app/globals.css` at commit
353
+ * b3587bdbd83fc66c2a6aae3817ffb856cb09260b — MIT License © 2026 Micka
354
+ * Touillaud — fluidfunctionalism.com. Translated from Tailwind v4 CSS to
355
+ * plain CSS for UnoCSS (uno.config.ts); vite.ts injects the UnoCSS
356
+ * stylesheet ahead of this file. The docs site imports it from global.css;
357
+ * the package ships it inside dist/base.css and dist/styles.css.
358
+ *
359
+ * Where upstream's sections went:
360
+ * - §1 design tokens → tokens/colors.css; the surface ladder and shadow
361
+ * recipes → primitives/surface/surface.css. All HEX migrated to oklch.
362
+ * - §2 theme switching → the color-scheme / --overlay block below (the
363
+ * shadow re-pointing lives in surface.css).
364
+ * - §3 `@theme inline` → preset.ts `theme`; the `--fs-*` roles and
365
+ * `@utility text-*` → tokens/typography.css (`--text-*`) + preset.ts
366
+ * `utilities`.
367
+ * - §4–6 → below: html/body base, `@layer base` (text wrap, inline code chip,
368
+ * :focus-visible fallback), html.transitioning, native scrollbars,
369
+ * .scrollbar-hide, shimmer + spinner keyframes, .scroll-fade(-x).
370
+ *
371
+ * Local value changes: the inline code chip and the shimmer stops read color
372
+ * tokens instead of upstream's hex (light code chip `--muted`, dark
373
+ * `--surface-4`; shimmer dim → bright runs `--muted-foreground` →
374
+ * `--foreground` in light and `--accent` → `--muted-foreground` in dark).
375
+ *
376
+ * Dropped: the Tailwind directives (`@import "tailwindcss"`,
377
+ * `@custom-variant`), the self-hosted Inter @font-face (replaced by
378
+ * @fontsource-variable/inter/opsz), the Radix-only scroll-lock margin fix
379
+ * (`body[data-scroll-locked]`), the reserved shadcn-compare colors, and the
380
+ * docs-site-only CSS (.xl-fade-*, .inview-fade-block, .scroll-divider — only
381
+ * the omitted Sidebar uses it — .bento-*, .shiki).
382
+ *
383
+ * Layers: UnoCSS (`outputToCssLayers`) declares `@layer properties, theme,
384
+ * base, default` first, and its reset lives in `base`. The `@layer base`
385
+ * rules here join that layer after the reset, so the reset loses to them and
386
+ * every utility (`default`) still wins — the same arrangement as Tailwind's
387
+ * base/utilities layers upstream.
388
+ *
389
+ * The switch blocks double their class selector (.light.light / .dark.dark)
390
+ * because user CSS is injected BEFORE tokens/*.css and item CSS — the
391
+ * doubled selector (0,2,0) must out-specify the shared `:root, .light,
392
+ * .dark` token blocks (0,1,0) so the re-pointed values win regardless of
393
+ * stylesheet order.
394
+ */
395
+
396
+
397
+ /* ── Theme switching (upstream §2) ──
398
+ `color-scheme` is what flips every light-dark() token; the classes work on
399
+ <html> (vite.ts puts the stored preference there, or the OS one under
400
+ 'system') and on any subtree (forced-theme previews). The only value that can't ride light-dark() here
401
+ is the --overlay RGB triplet, consumed as rgb(var(--overlay) / α). */
402
+
403
+ :root {
404
+ color-scheme: light dark;
405
+ }
406
+
407
+ @media (prefers-color-scheme: dark) {
408
+ :root:not(.light) {
409
+ --overlay: 255 255 255;
410
+ }
411
+ }
412
+
413
+ .light.light {
414
+ color-scheme: light;
415
+ --overlay: 0 0 0;
416
+ }
417
+
418
+ .dark.dark {
419
+ color-scheme: dark;
420
+ --overlay: 255 255 255;
421
+ }
422
+
423
+ /* ── Base styles (upstream §4) ── */
424
+
425
+ /* No explicit overflow here: scrollbar-gutter applies to the viewport when
426
+ set on the root, and an explicit `overflow-y: auto` would stop dialog
427
+ scroll locks (`body { overflow: hidden }`) from propagating to the
428
+ viewport. */
429
+ html {
430
+ scrollbar-gutter: stable;
431
+ }
432
+
433
+ html,
434
+ body {
435
+ margin: 0;
436
+ min-height: 100vh;
437
+ overscroll-behavior-y: none;
438
+ background-color: var(--background);
439
+ /* Explicit text color so unstyled text follows the theme (and so the UA's
440
+ color-scheme-driven canvastext default never shows through). */
441
+ color: var(--foreground);
442
+ font-family: var(--font-sans);
443
+ /* Inherited by everything (portals included) — a `subpixel-antialiased`
444
+ utility on a subtree is the only way to opt back out. */
445
+ -webkit-font-smoothing: antialiased;
446
+ -moz-osx-font-smoothing: grayscale;
447
+ }
448
+
449
+ /* @layer base so a utility can still win: unlayered, these outrank
450
+ `truncate` on the text-wrap-mode longhand (white-space is a shorthand for
451
+ white-space-collapse + text-wrap-mode), which silently un-truncates every
452
+ <p> that asks for it. */
453
+ @layer base {
454
+ h1,
455
+ h2,
456
+ h3 {
457
+ text-wrap: balance;
458
+ }
459
+
460
+ p {
461
+ text-wrap: pretty;
462
+ }
463
+
464
+ /* Inline code chips: bare <code> in prose gets its own ground. Excludes
465
+ code inside <pre>. Base layer, so a page can still override with
466
+ utilities. */
467
+ code:not(pre code) {
468
+ background: light-dark(var(--muted), var(--surface-4));
469
+ border-radius: 4px;
470
+ padding: 1px 4px;
471
+ font-size: var(--text-caption, 12px);
472
+ color: var(--foreground, inherit);
473
+ }
474
+ }
475
+
476
+ /* Theme-toggle cross-fade: ShapeProvider (and any theme switcher) adds
477
+ `.transitioning` around a class swap so colors and corners tween instead of
478
+ snapping. */
479
+ html.transitioning *,
480
+ html.transitioning *::before,
481
+ html.transitioning *::after {
482
+ transition:
483
+ border-radius 180ms ease-in-out,
484
+ background-color 180ms ease-in-out,
485
+ color 180ms ease-in-out,
486
+ border-color 180ms ease-in-out,
487
+ fill 180ms ease-in-out,
488
+ stroke 180ms ease-in-out !important;
489
+ }
490
+
491
+ /* Focus fallback — natively focusable elements no component styles (scroll
492
+ containers Chrome makes tabbable, prose links, demo boxes) get the same
493
+ token ring as the components instead of the UA default. Lives in
494
+ @layer base so `outline-none` (a utility) still wins — components that
495
+ draw their own ring aren't double-ringed. The radius rides
496
+ --shape-input-radius (published by ShapeProvider); 8px = the rounded-mode
497
+ default. */
498
+ @layer base {
499
+ :focus-visible {
500
+ outline: 1px solid var(--focus-ring);
501
+ outline-offset: 2px;
502
+ border-radius: var(--shape-input-radius, 8px);
503
+ }
504
+
505
+ /* Prose links are inline text boxes — the shape-system radius reads
506
+ pill-ish at text height, so their ring takes a tight 2px corner. */
507
+ a:focus-visible {
508
+ border-radius: 2px;
509
+ }
510
+ }
511
+
512
+ /* ── Native scrollbar theme (upstream §5) ──
513
+ Matches the ScrollArea thumb language (narrow, low-contrast, darkens on
514
+ hover) so surfaces that fall back to native scrollbars stay consistent
515
+ with the custom scrollbar. Rides the surface-relative overlay ramp (rest
516
+ 8%, hover 12%, drag 16%) — adapts to dark mode through --overlay. Only on
517
+ fine pointers; touch devices keep their overlay momentum bars. Chromium
518
+ 121+ renders scrollbar-color and ignores the ::-webkit-* block, so both
519
+ ramps must stay in sync. */
520
+
521
+ @media (pointer: fine) {
522
+ * {
523
+ scrollbar-width: thin;
524
+ scrollbar-color: rgb(var(--overlay) / 0.08) transparent;
525
+ }
526
+
527
+ ::-webkit-scrollbar {
528
+ width: 10px;
529
+ height: 10px;
530
+ }
531
+
532
+ ::-webkit-scrollbar-track,
533
+ ::-webkit-scrollbar-corner {
534
+ background: transparent;
535
+ }
536
+
537
+ ::-webkit-scrollbar-thumb {
538
+ /* Transparent borders + content-box clip leave a ~4px visible thumb
539
+ inside the 10px hit area; the asymmetric edge-side border slides it
540
+ 2px away from the edge, matching the ScrollArea thumb's nudge. */
541
+ background-color: rgb(var(--overlay) / 0.08);
542
+ border: 3px solid transparent;
543
+ background-clip: content-box;
544
+ border-radius: 9999px;
545
+ }
546
+
547
+ ::-webkit-scrollbar-thumb:vertical {
548
+ border-left-width: 1px;
549
+ border-right-width: 5px;
550
+ }
551
+
552
+ ::-webkit-scrollbar-thumb:horizontal {
553
+ border-top-width: 1px;
554
+ border-bottom-width: 5px;
555
+ }
556
+
557
+ ::-webkit-scrollbar-thumb:hover {
558
+ background-color: rgb(var(--overlay) / 0.12);
559
+ }
560
+
561
+ ::-webkit-scrollbar-thumb:active {
562
+ background-color: rgb(var(--overlay) / 0.16);
563
+ }
564
+ }
565
+
566
+ .scrollbar-hide {
567
+ -ms-overflow-style: none;
568
+ scrollbar-width: none;
569
+ }
570
+
571
+ .scrollbar-hide::-webkit-scrollbar {
572
+ display: none;
573
+ }
574
+
575
+ /* ── Utilities (upstream §6) ── */
576
+
577
+ /* shimmer-text (thinking-indicator) */
578
+
579
+ @keyframes shimmer {
580
+ 0% {
581
+ background-position: 0% 0;
582
+ }
583
+ 100% {
584
+ background-position: 100% 0;
585
+ }
586
+ }
587
+
588
+ .shimmer-text {
589
+ color: transparent;
590
+ background: linear-gradient(
591
+ 90deg,
592
+ light-dark(var(--muted-foreground), var(--accent)) 0%,
593
+ light-dark(var(--muted-foreground), var(--accent)) 35%,
594
+ light-dark(var(--foreground), var(--muted-foreground)) 50%,
595
+ light-dark(var(--muted-foreground), var(--accent)) 65%,
596
+ light-dark(var(--muted-foreground), var(--accent)) 100%
597
+ );
598
+ background-size: 300% 100%;
599
+ background-clip: text;
600
+ -webkit-background-clip: text;
601
+ animation: shimmer 1.5s ease-in-out infinite;
602
+ }
603
+
604
+ /* Spinner keyframes (Button's loading state). */
605
+
606
+ @keyframes spinner-move {
607
+ to {
608
+ stroke-dashoffset: -100;
609
+ }
610
+ }
611
+
612
+ @keyframes spinner-dash {
613
+ 0%,
614
+ 100% {
615
+ stroke-dasharray: 15 85;
616
+ }
617
+ 50% {
618
+ stroke-dasharray: 40 60;
619
+ }
620
+ }
621
+
622
+ /* scroll-fade — vendored equivalent of shadcn's scroll-fade utility. A
623
+ mask-image dissolves the content toward the edges that have more to
624
+ scroll. CSS scroll-driven animations make it scroll-aware: the true
625
+ start/end edge stays crisp until you scroll past it. Browsers without
626
+ scroll-driven animations fall back to a static fade on both edges. Apply
627
+ to a scroll container, or to a ScrollArea via `viewportClassName`. */
628
+
629
+ @property --sf-start {
630
+ syntax: "<number>";
631
+ inherits: false;
632
+ initial-value: 1;
633
+ }
634
+ @property --sf-end {
635
+ syntax: "<number>";
636
+ inherits: false;
637
+ initial-value: 1;
638
+ }
639
+
640
+ @keyframes sf-reveal-start {
641
+ from {
642
+ --sf-start: 1;
643
+ }
644
+ to {
645
+ --sf-start: 0;
646
+ }
647
+ }
648
+ @keyframes sf-reveal-end {
649
+ from {
650
+ --sf-end: 0;
651
+ }
652
+ to {
653
+ --sf-end: 1;
654
+ }
655
+ }
656
+
657
+ .scroll-fade,
658
+ .scroll-fade-x {
659
+ --scroll-fade-size: 48px;
660
+ }
661
+
662
+ /* Static fallback: fade both edges. */
663
+ .scroll-fade {
664
+ -webkit-mask-image: linear-gradient(
665
+ to bottom,
666
+ transparent 0,
667
+ #000 var(--scroll-fade-size),
668
+ #000 calc(100% - var(--scroll-fade-size)),
669
+ transparent 100%
670
+ );
671
+ mask-image: linear-gradient(
672
+ to bottom,
673
+ transparent 0,
674
+ #000 var(--scroll-fade-size),
675
+ #000 calc(100% - var(--scroll-fade-size)),
676
+ transparent 100%
677
+ );
678
+ }
679
+ .scroll-fade-x {
680
+ -webkit-mask-image: linear-gradient(
681
+ to right,
682
+ transparent 0,
683
+ #000 var(--scroll-fade-size),
684
+ #000 calc(100% - var(--scroll-fade-size)),
685
+ transparent 100%
686
+ );
687
+ mask-image: linear-gradient(
688
+ to right,
689
+ transparent 0,
690
+ #000 var(--scroll-fade-size),
691
+ #000 calc(100% - var(--scroll-fade-size)),
692
+ transparent 100%
693
+ );
694
+ }
695
+
696
+ /* Scroll-aware: the start/end edge stays solid until there's content past it. */
697
+ @supports (animation-timeline: scroll()) {
698
+ .scroll-fade {
699
+ mask-image: linear-gradient(
700
+ to bottom,
701
+ rgba(0, 0, 0, var(--sf-start)) 0,
702
+ #000 var(--scroll-fade-size),
703
+ #000 calc(100% - var(--scroll-fade-size)),
704
+ rgba(0, 0, 0, var(--sf-end)) 100%
705
+ );
706
+ -webkit-mask-image: linear-gradient(
707
+ to bottom,
708
+ rgba(0, 0, 0, var(--sf-start)) 0,
709
+ #000 var(--scroll-fade-size),
710
+ #000 calc(100% - var(--scroll-fade-size)),
711
+ rgba(0, 0, 0, var(--sf-end)) 100%
712
+ );
713
+ animation:
714
+ sf-reveal-start both linear,
715
+ sf-reveal-end both linear;
716
+ animation-timeline: scroll(self), scroll(self);
717
+ animation-range-start: 0, calc(100% - var(--scroll-fade-size));
718
+ animation-range-end: var(--scroll-fade-size), 100%;
719
+ }
720
+ .scroll-fade-x {
721
+ mask-image: linear-gradient(
722
+ to right,
723
+ rgba(0, 0, 0, var(--sf-start)) 0,
724
+ #000 var(--scroll-fade-size),
725
+ #000 calc(100% - var(--scroll-fade-size)),
726
+ rgba(0, 0, 0, var(--sf-end)) 100%
727
+ );
728
+ -webkit-mask-image: linear-gradient(
729
+ to right,
730
+ rgba(0, 0, 0, var(--sf-start)) 0,
731
+ #000 var(--scroll-fade-size),
732
+ #000 calc(100% - var(--scroll-fade-size)),
733
+ rgba(0, 0, 0, var(--sf-end)) 100%
734
+ );
735
+ animation:
736
+ sf-reveal-start both linear,
737
+ sf-reveal-end both linear;
738
+ animation-timeline: scroll(self inline), scroll(self inline);
739
+ animation-range-start: 0, calc(100% - var(--scroll-fade-size));
740
+ animation-range-end: var(--scroll-fade-size), 100%;
741
+ }
742
+ }
743
+
744
+ /*
745
+ * Ported from Fluid Functionalism — github.com/mickadesign/fluid-functionalism
746
+ * `app/globals.css` (`.scroll-divider`) @ b3587bdbd83fc66c2a6aae3817ffb856cb09260b
747
+ * MIT License © 2026 Micka Touillaud — fluidfunctionalism.com (notice: LICENSE.fluid-functionalism)
748
+ * Local modifications: none (modo injects this file beside the Sidebar).
749
+ */
750
+
751
+ /* ── scroll-divider — a hairline marking a scroll edge ──────────────────────
752
+ Pairs with .scroll-fade on a descendant viewport: where the fade dissolves
753
+ content, this draws the line that says "the region continues". The line
754
+ can't live inside the scroller — the fade's own mask would erase it at
755
+ exactly the edge it belongs on — so it rides the parent's pseudo-elements
756
+ and borrows the scroller's timeline by name. Browsers without scroll-driven
757
+ timelines simply get the fade, as before. */
758
+ @keyframes sf-divider-start {
759
+ from {
760
+ opacity: 0;
761
+ }
762
+ to {
763
+ opacity: 1;
764
+ }
765
+ }
766
+ @keyframes sf-divider-end {
767
+ from {
768
+ opacity: 1;
769
+ }
770
+ to {
771
+ opacity: 0;
772
+ }
773
+ }
774
+
775
+ @supports (timeline-scope: --sf) {
776
+ .scroll-divider {
777
+ position: relative;
778
+ timeline-scope: --sf-scroller;
779
+ --scroll-divider-size: 48px;
780
+ }
781
+ .scroll-divider .scroll-fade {
782
+ scroll-timeline-name: --sf-scroller;
783
+ }
784
+ .scroll-divider::before,
785
+ .scroll-divider::after {
786
+ content: "";
787
+ position: absolute;
788
+ /* Inset so the line can hug the content's own gutter rather than the
789
+ panel edge — the sidebar's inset variant sets this to 8px. */
790
+ left: var(--scroll-divider-inset, 0px);
791
+ right: var(--scroll-divider-inset, 0px);
792
+ height: 1px;
793
+ background-color: var(--border);
794
+ pointer-events: none;
795
+ z-index: 1;
796
+ opacity: 0;
797
+ }
798
+ /* Top edge: absent at rest, fading in as content passes above. */
799
+ .scroll-divider::before {
800
+ top: 0;
801
+ animation: sf-divider-start both linear;
802
+ animation-timeline: --sf-scroller;
803
+ animation-range: 0 var(--scroll-divider-size);
804
+ }
805
+ /* A region flush with the panel's own top edge has nothing above for
806
+ content to pass under, so the start line would read as a stray border. */
807
+ .scroll-divider:first-child::before {
808
+ content: none;
809
+ }
810
+ /* Bottom edge: present while content continues below, gone at the end. */
811
+ .scroll-divider::after {
812
+ bottom: 0;
813
+ animation: sf-divider-end both linear;
814
+ animation-timeline: --sf-scroller;
815
+ animation-range: calc(100% - var(--scroll-divider-size)) 100%;
816
+ }
817
+ }
818
+
819
+ /* biome-ignore-all lint: vendored upstream code keeps its own values (see the header) */
820
+ /*
821
+ * Surface elevation module: the 8-level surface ladder and its 1:1 shadow
822
+ * recipes, plus the per-scheme selection of the active --shadow-N alias.
823
+ *
824
+ * Extracted from tokens/colors.css and base.css — the elevation system
825
+ * belongs to this primitive, not the global token set.
826
+ * Source: mickadesign/fluid-functionalism `app/globals.css` §1–2 at commit
827
+ * b3587bdbd83fc66c2a6aae3817ffb856cb09260b — MIT License
828
+ * © 2026 Micka Touillaud — fluidfunctionalism.com. All HEX migrated to
829
+ * exact oklch (see tokens/colors.css for the note).
830
+ *
831
+ * Like tokens/colors.css, the token block is declared on
832
+ * `:root, .light, .dark` so light-dark() re-resolves per forced scope. The
833
+ * switch blocks double their class selector (.light.light / .dark.dark) to
834
+ * out-specify the shared (0,1,0) token block regardless of stylesheet
835
+ * order.
836
+ */
837
+
838
+ :root,
839
+ .light,
840
+ .dark {
841
+ /* ── Surfaces (8-level ladder) ──
842
+ Light: 2 color steps (floor, sunken) then flat white; shadow does the
843
+ work. Dark: additive white-opacity ladder over near-black. Each surface
844
+ pairs 1:1 with a shadow recipe (--shadow-1 .. --shadow-8). */
845
+ --surface-1: light-dark(oklch(0.985 0 0), oklch(0.205 0 0));
846
+ --surface-2: light-dark(oklch(0.991 0 0), oklch(0.235 0 0));
847
+ --surface-3: light-dark(oklch(1 0 0), oklch(0.264 0 0));
848
+ --surface-4: light-dark(oklch(1 0 0), oklch(0.293 0 0));
849
+ --surface-5: light-dark(oklch(1 0 0), oklch(0.321 0 0));
850
+ --surface-6: light-dark(oklch(1 0 0), oklch(0.349 0 0));
851
+ --surface-7: light-dark(oklch(1 0 0), oklch(0.375 0 0));
852
+ --surface-8: light-dark(oklch(1 0 0), oklch(0.402 0 0));
853
+
854
+ /* ── Shadow recipes ──
855
+ Light: additive stacked drops, halving offsets (1/3/6/12/24/48/96 px).
856
+ Dark: inset highlight + inset ring + stacked drops.
857
+ Structurally different, so each ladder is written out once here and the
858
+ active --shadow-N is selected per scheme below.
859
+ --shadow-color is intentionally static: it is also consumed directly by
860
+ components and has never varied by theme. */
861
+ --shadow-color: rgb(0 0 0 / 0.06);
862
+ --shadow-light-1: 0 0 0 1px var(--shadow-color);
863
+ --shadow-light-2: 0 0 0 1px var(--shadow-color), 0 1px 1px -0.5px var(--shadow-color);
864
+ --shadow-light-3:
865
+ 0 0 0 1px var(--shadow-color), 0 1px 1px -0.5px var(--shadow-color), 0 3px 3px -1.5px var(--shadow-color);
866
+ --shadow-light-4:
867
+ 0 0 0 1px var(--shadow-color), 0 1px 1px -0.5px var(--shadow-color), 0 3px 3px -1.5px var(--shadow-color), 0 6px 6px -3px var(
868
+ --shadow-color
869
+ );
870
+ --shadow-light-5:
871
+ 0 0 0 1px var(--shadow-color), 0 1px 1px -0.5px var(--shadow-color), 0 3px 3px -1.5px var(--shadow-color), 0 6px 6px -3px var(
872
+ --shadow-color
873
+ ), 0 12px 12px -6px var(--shadow-color);
874
+ --shadow-light-6:
875
+ 0 0 0 1px var(--shadow-color), 0 1px 1px -0.5px var(--shadow-color), 0 3px 3px -1.5px var(--shadow-color), 0 6px 6px -3px var(
876
+ --shadow-color
877
+ ), 0 12px 12px -6px var(--shadow-color), 0 24px 24px -12px var(--shadow-color);
878
+ --shadow-light-7:
879
+ 0 0 0 1px var(--shadow-color), 0 1px 1px -0.5px var(--shadow-color), 0 3px 3px -1.5px var(--shadow-color), 0 6px 6px -3px var(
880
+ --shadow-color
881
+ ), 0 12px 12px -6px var(--shadow-color), 0 24px 24px -12px var(--shadow-color), 0 48px 48px -24px var(
882
+ --shadow-color
883
+ );
884
+ --shadow-light-8:
885
+ 0 0 0 1px var(--shadow-color), 0 1px 1px -0.5px var(--shadow-color), 0 3px 3px -1.5px var(--shadow-color), 0 6px 6px -3px var(
886
+ --shadow-color
887
+ ), 0 12px 12px -6px var(--shadow-color), 0 24px 24px -12px var(--shadow-color), 0 48px 48px -24px var(
888
+ --shadow-color
889
+ ), 0 96px 96px -48px var(--shadow-color);
890
+ /* Surface-2/3 with the hairline moved INSIDE the box — for cards whose
891
+ border must stay within their own edge. Dark's recipes already draw
892
+ their ring inset, so dark scopes alias --shadow-dark-2/3 directly. */
893
+ --shadow-light-2-inset: inset 0 0 0 1px var(--shadow-color), 0 1px 1px -0.5px var(--shadow-color);
894
+ --shadow-light-3-inset:
895
+ inset 0 0 0 1px var(--shadow-color), 0 1px 1px -0.5px var(--shadow-color), 0 3px 3px -1.5px var(--shadow-color);
896
+
897
+ --dm-hi-base: rgba(255, 255, 255, 0.01);
898
+ --dm-hi-mid: rgba(255, 255, 255, 0.02);
899
+ --dm-hi-high: rgba(255, 255, 255, 0.04);
900
+ --dm-hi-peak: rgba(255, 255, 255, 0.06);
901
+ --dm-ring-base: rgba(255, 255, 255, 0.02);
902
+ --dm-ring-mid: rgba(255, 255, 255, 0.04);
903
+ --dm-ring-high: rgba(255, 255, 255, 0.06);
904
+ --dm-drop: rgba(0, 0, 0, 0.18);
905
+ --shadow-dark-1: inset 0 0 0 1px var(--dm-ring-base);
906
+ --shadow-dark-2:
907
+ inset 0 1px 0 0 var(--dm-hi-base), inset 0 0 0 1px var(--dm-ring-base), 0 1px 1px -0.5px var(--dm-drop);
908
+ --shadow-dark-3:
909
+ inset 0 1px 0 0 var(--dm-hi-mid), inset 0 0 0 1px var(--dm-ring-base), 0 0 0 1px rgba(0, 0, 0, 0.12), 0 1px 1px -0.5px var(
910
+ --dm-drop
911
+ ), 0 3px 3px -1.5px var(--dm-drop);
912
+ --shadow-dark-4:
913
+ inset 0 1px 0 0 var(--dm-hi-mid), inset 0 0 0 1px var(--dm-ring-mid), 0 0 0 1px rgba(0, 0, 0, 0.14), 0 1px 1px -0.5px var(
914
+ --dm-drop
915
+ ), 0 3px 3px -1.5px var(--dm-drop), 0 6px 6px -3px var(--dm-drop);
916
+ --shadow-dark-5:
917
+ inset 0 1px 0 0 var(--dm-hi-high), inset 0 0 0 1px var(--dm-ring-mid), 0 0 0 1px rgba(0, 0, 0, 0.16), 0 1px 1px -0.5px var(
918
+ --dm-drop
919
+ ), 0 3px 3px -1.5px var(--dm-drop), 0 6px 6px -3px var(--dm-drop), 0 12px 12px -6px var(--dm-drop);
920
+ --shadow-dark-6:
921
+ inset 0 1px 0 0 var(--dm-hi-high), inset 0 0 0 1px var(--dm-ring-high), 0 0 0 1px rgba(0, 0, 0, 0.18), 0 1px 1px -0.5px var(
922
+ --dm-drop
923
+ ), 0 3px 3px -1.5px var(--dm-drop), 0 6px 6px -3px var(--dm-drop), 0 12px 12px -6px var(--dm-drop), 0 24px 24px -12px var(
924
+ --dm-drop
925
+ );
926
+ --shadow-dark-7:
927
+ inset 0 1px 0 0 var(--dm-hi-peak), inset 0 0 0 1px var(--dm-ring-high), 0 0 0 1px rgba(0, 0, 0, 0.2), 0 1px 1px -0.5px var(
928
+ --dm-drop
929
+ ), 0 3px 3px -1.5px var(--dm-drop), 0 6px 6px -3px var(--dm-drop), 0 12px 12px -6px var(--dm-drop), 0 24px 24px -12px var(
930
+ --dm-drop
931
+ ), 0 48px 48px -24px var(--dm-drop);
932
+ --shadow-dark-8:
933
+ inset 0 1px 0 0 var(--dm-hi-peak), inset 0 0 0 1px var(--dm-ring-high), 0 0 0 1px rgba(0, 0, 0, 0.22), 0 1px 1px -0.5px var(
934
+ --dm-drop
935
+ ), 0 3px 3px -1.5px var(--dm-drop), 0 6px 6px -3px var(--dm-drop), 0 12px 12px -6px var(--dm-drop), 0 24px 24px -12px var(
936
+ --dm-drop
937
+ ), 0 48px 48px -24px var(--dm-drop), 0 96px 96px -48px var(--dm-drop);
938
+
939
+ --shadow-1: var(--shadow-light-1);
940
+ --shadow-2: var(--shadow-light-2);
941
+ --shadow-3: var(--shadow-light-3);
942
+ --shadow-4: var(--shadow-light-4);
943
+ --shadow-5: var(--shadow-light-5);
944
+ --shadow-6: var(--shadow-light-6);
945
+ --shadow-7: var(--shadow-light-7);
946
+ --shadow-8: var(--shadow-light-8);
947
+ --shadow-2-inset: var(--shadow-light-2-inset);
948
+ --shadow-3-inset: var(--shadow-light-3-inset);
949
+ }
950
+
951
+ /* ── Per-scheme shadow selection ──
952
+ The light/dark recipes are structurally different, so they cannot ride
953
+ light-dark(); the active --shadow-N alias is re-pointed per scheme here. */
954
+
955
+ @media (prefers-color-scheme: dark) {
956
+ :root:not(.light) {
957
+ --shadow-1: var(--shadow-dark-1);
958
+ --shadow-2: var(--shadow-dark-2);
959
+ --shadow-3: var(--shadow-dark-3);
960
+ --shadow-4: var(--shadow-dark-4);
961
+ --shadow-5: var(--shadow-dark-5);
962
+ --shadow-6: var(--shadow-dark-6);
963
+ --shadow-7: var(--shadow-dark-7);
964
+ --shadow-8: var(--shadow-dark-8);
965
+ --shadow-2-inset: var(--shadow-dark-2);
966
+ --shadow-3-inset: var(--shadow-dark-3);
967
+ }
968
+ }
969
+
970
+ .light.light {
971
+ --shadow-1: var(--shadow-light-1);
972
+ --shadow-2: var(--shadow-light-2);
973
+ --shadow-3: var(--shadow-light-3);
974
+ --shadow-4: var(--shadow-light-4);
975
+ --shadow-5: var(--shadow-light-5);
976
+ --shadow-6: var(--shadow-light-6);
977
+ --shadow-7: var(--shadow-light-7);
978
+ --shadow-8: var(--shadow-light-8);
979
+ --shadow-2-inset: var(--shadow-light-2-inset);
980
+ --shadow-3-inset: var(--shadow-light-3-inset);
981
+ }
982
+
983
+ .dark.dark {
984
+ --shadow-1: var(--shadow-dark-1);
985
+ --shadow-2: var(--shadow-dark-2);
986
+ --shadow-3: var(--shadow-dark-3);
987
+ --shadow-4: var(--shadow-dark-4);
988
+ --shadow-5: var(--shadow-dark-5);
989
+ --shadow-6: var(--shadow-dark-6);
990
+ --shadow-7: var(--shadow-dark-7);
991
+ --shadow-8: var(--shadow-dark-8);
992
+ --shadow-2-inset: var(--shadow-dark-2);
993
+ --shadow-3-inset: var(--shadow-dark-3);
994
+ }