@expokit/cli 0.2.0 → 0.4.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 (232) hide show
  1. package/README.md +20 -2
  2. package/bin/expo-kit.js +219 -44
  3. package/bin/lib.mjs +110 -1
  4. package/bin/mcp.mjs +48 -19
  5. package/package.json +9 -4
  6. package/registry/_docs/accordion.json +16 -0
  7. package/registry/_docs/action-sheet.json +28 -0
  8. package/registry/_docs/alert.json +28 -0
  9. package/registry/_docs/assistant-app.json +47 -0
  10. package/registry/_docs/avatar.json +25 -1
  11. package/registry/_docs/badge.json +36 -5
  12. package/registry/_docs/bottom-nav.json +30 -6
  13. package/registry/_docs/bottom-sheet.json +28 -0
  14. package/registry/_docs/bullet.json +3 -0
  15. package/registry/_docs/button-3d.json +18 -2
  16. package/registry/_docs/button.json +16 -0
  17. package/registry/_docs/calendar.json +21 -5
  18. package/registry/_docs/card-stack.json +16 -0
  19. package/registry/_docs/card.json +20 -0
  20. package/registry/_docs/carousel.json +20 -0
  21. package/registry/_docs/chat.json +184 -0
  22. package/registry/_docs/checkbox.json +16 -0
  23. package/registry/_docs/checkout.json +10 -1
  24. package/registry/_docs/chip-group.json +16 -0
  25. package/registry/_docs/confetti.json +23 -3
  26. package/registry/_docs/coupon.json +16 -0
  27. package/registry/_docs/cube-pager.json +16 -0
  28. package/registry/_docs/dashboard.json +3 -0
  29. package/registry/_docs/date-picker.json +24 -4
  30. package/registry/_docs/dialog.json +24 -0
  31. package/registry/_docs/donut-shop.json +88 -0
  32. package/registry/_docs/drawer.json +32 -4
  33. package/registry/_docs/fab.json +24 -0
  34. package/registry/_docs/fintech-app.json +49 -0
  35. package/registry/_docs/flip-card.json +16 -0
  36. package/registry/_docs/floating-input.json +25 -5
  37. package/registry/_docs/food-app.json +47 -0
  38. package/registry/_docs/gift-card.json +16 -0
  39. package/registry/_docs/glass-button.json +24 -0
  40. package/registry/_docs/glass-nav-bar.json +24 -0
  41. package/registry/_docs/glass-search-bar.json +24 -0
  42. package/registry/_docs/glass-segmented.json +28 -0
  43. package/registry/_docs/glass-sheet.json +28 -0
  44. package/registry/_docs/glass-tab-bar.json +24 -0
  45. package/registry/_docs/glass.json +17 -1
  46. package/registry/_docs/grain.json +80 -0
  47. package/registry/_docs/inbox.json +3 -0
  48. package/registry/_docs/isometric-bar.json +3 -0
  49. package/registry/_docs/kit.json +3 -0
  50. package/registry/_docs/liquidglass-tab.json +3 -0
  51. package/registry/_docs/list-item.json +16 -0
  52. package/registry/_docs/loyalty-card.json +16 -0
  53. package/registry/_docs/morphing-button-group.json +96 -0
  54. package/registry/_docs/number-ticker.json +16 -0
  55. package/registry/_docs/offers.json +3 -0
  56. package/registry/_docs/onboarding.json +3 -0
  57. package/registry/_docs/orb-reveal.json +132 -0
  58. package/registry/_docs/otp-input.json +28 -4
  59. package/registry/_docs/parallax-layers.json +20 -0
  60. package/registry/_docs/parallax-scroll.json +24 -0
  61. package/registry/_docs/password-input.json +23 -3
  62. package/registry/_docs/payment-card.json +16 -0
  63. package/registry/_docs/pixel-bar.json +3 -0
  64. package/registry/_docs/polar-area.json +3 -0
  65. package/registry/_docs/profile.json +3 -0
  66. package/registry/_docs/progress-steps.json +24 -0
  67. package/registry/_docs/progress.json +20 -0
  68. package/registry/_docs/qr-code-button.json +122 -0
  69. package/registry/_docs/radio-group.json +20 -0
  70. package/registry/_docs/rating.json +24 -0
  71. package/registry/_docs/ring-carousel.json +20 -0
  72. package/registry/_docs/scratch-card.json +20 -0
  73. package/registry/_docs/search-bar.json +23 -3
  74. package/registry/_docs/search-results.json +3 -0
  75. package/registry/_docs/segmented-control.json +20 -0
  76. package/registry/_docs/select.json +33 -8
  77. package/registry/_docs/settings.json +9 -0
  78. package/registry/_docs/sign-in.json +3 -0
  79. package/registry/_docs/skeleton.json +22 -2
  80. package/registry/_docs/slide-to-confirm.json +24 -0
  81. package/registry/_docs/slider.json +27 -7
  82. package/registry/_docs/spin-wheel.json +24 -0
  83. package/registry/_docs/spinner.json +41 -11
  84. package/registry/_docs/stateful-button.json +184 -0
  85. package/registry/_docs/stepper.json +23 -7
  86. package/registry/_docs/swipe-deck.json +16 -0
  87. package/registry/_docs/swipeable-row.json +24 -1
  88. package/registry/_docs/switch.json +20 -0
  89. package/registry/_docs/tabs.json +24 -0
  90. package/registry/_docs/text-animation.json +141 -0
  91. package/registry/_docs/textarea.json +18 -2
  92. package/registry/_docs/ticket-card.json +20 -0
  93. package/registry/_docs/ticket-shape.json +4 -1
  94. package/registry/_docs/tilt-card.json +20 -4
  95. package/registry/_docs/time-picker.json +22 -6
  96. package/registry/_docs/timeline-chart.json +3 -0
  97. package/registry/_docs/timeline.json +18 -2
  98. package/registry/_docs/toast.json +32 -8
  99. package/registry/_docs/treemap.json +3 -0
  100. package/registry/_docs/waterfall.json +3 -0
  101. package/registry/_docs/wheel-list.json +20 -0
  102. package/registry/accordion/files/src/components/ui/accordion.tsx +5 -4
  103. package/registry/accordion/files/src/lib/kit.tsx +8 -5
  104. package/registry/action-sheet/files/src/components/ui/action-sheet.tsx +9 -8
  105. package/registry/action-sheet/files/src/lib/kit.tsx +8 -5
  106. package/registry/alert/files/src/components/ui/alert.tsx +7 -4
  107. package/registry/alert/files/src/components/ui/button.tsx +4 -3
  108. package/registry/alert/files/src/lib/kit.tsx +8 -5
  109. package/registry/assistant-app.json +49 -0
  110. package/registry/avatar/files/src/components/ui/avatar.tsx +7 -4
  111. package/registry/avatar/files/src/lib/kit.tsx +8 -5
  112. package/registry/badge/files/src/components/ui/badge.tsx +345 -21
  113. package/registry/badge/files/src/lib/kit.tsx +8 -5
  114. package/registry/badge.json +1 -1
  115. package/registry/bottom-nav/files/src/components/ui/bottom-nav.tsx +14 -7
  116. package/registry/bottom-nav/files/src/lib/kit.tsx +8 -5
  117. package/registry/bottom-sheet/files/src/components/ui/bottom-sheet.tsx +7 -6
  118. package/registry/bottom-sheet/files/src/lib/kit.tsx +8 -5
  119. package/registry/button/files/src/components/ui/button.tsx +4 -3
  120. package/registry/button/files/src/lib/kit.tsx +8 -5
  121. package/registry/button-3d/files/src/components/ui/button-3d.tsx +3 -2
  122. package/registry/button-3d/files/src/lib/kit.tsx +8 -5
  123. package/registry/calendar/files/src/components/ui/calendar.tsx +9 -5
  124. package/registry/calendar/files/src/lib/kit.tsx +8 -5
  125. package/registry/card/files/src/components/ui/card.tsx +8 -6
  126. package/registry/card/files/src/lib/kit.tsx +8 -5
  127. package/registry/chat.json +18 -0
  128. package/registry/checkbox/files/src/components/ui/checkbox.tsx +6 -5
  129. package/registry/checkbox/files/src/lib/kit.tsx +8 -5
  130. package/registry/chip-group/files/src/components/ui/chip-group.tsx +4 -2
  131. package/registry/chip-group/files/src/lib/kit.tsx +8 -5
  132. package/registry/confetti/files/src/lib/kit.tsx +8 -5
  133. package/registry/coupon/files/src/components/ui/coupon.tsx +23 -20
  134. package/registry/coupon/files/src/components/ui/flip-card.tsx +1 -1
  135. package/registry/coupon/files/src/components/ui/tilt-card.tsx +2 -2
  136. package/registry/coupon/files/src/lib/kit.tsx +8 -5
  137. package/registry/date-picker/files/src/components/ui/calendar.tsx +9 -5
  138. package/registry/date-picker/files/src/components/ui/date-picker.tsx +2 -2
  139. package/registry/date-picker/files/src/lib/kit.tsx +8 -5
  140. package/registry/dialog/files/src/components/ui/dialog.tsx +12 -6
  141. package/registry/dialog/files/src/lib/kit.tsx +8 -5
  142. package/registry/donut-shop.json +18 -0
  143. package/registry/drawer/files/src/components/ui/drawer.tsx +25 -19
  144. package/registry/drawer/files/src/lib/kit.tsx +8 -5
  145. package/registry/fintech-app.json +57 -0
  146. package/registry/flip-card/files/src/components/ui/flip-card.tsx +1 -1
  147. package/registry/flip-card/files/src/lib/kit.tsx +8 -5
  148. package/registry/floating-input/files/src/components/ui/floating-input.tsx +16 -6
  149. package/registry/floating-input/files/src/lib/kit.tsx +8 -5
  150. package/registry/food-app.json +55 -0
  151. package/registry/grain/files/src/components/ui/grain.tsx +113 -0
  152. package/registry/{tag-input → grain}/files/src/lib/kit.tsx +8 -5
  153. package/registry/grain.json +17 -0
  154. package/registry/kit/files/src/lib/kit.tsx +8 -5
  155. package/registry/liquidglass-tab/files/src/components/GlassTabBar.tsx +1 -1
  156. package/registry/liquidglass-tab/files/src/lib/tab-bar.ts +1 -1
  157. package/registry/list-item/files/src/components/ui/list-item.tsx +20 -16
  158. package/registry/list-item/files/src/lib/kit.tsx +8 -5
  159. package/registry/morphing-button-group.json +18 -0
  160. package/registry/orb-reveal.json +20 -0
  161. package/registry/otp-input/files/src/components/ui/otp-input.tsx +8 -6
  162. package/registry/otp-input/files/src/lib/kit.tsx +8 -5
  163. package/registry/password-input/files/src/components/ui/floating-input.tsx +16 -6
  164. package/registry/password-input/files/src/components/ui/password-input.tsx +2 -2
  165. package/registry/password-input/files/src/lib/kit.tsx +8 -5
  166. package/registry/payment-card/files/src/components/ui/flip-card.tsx +1 -1
  167. package/registry/payment-card/files/src/components/ui/payment-card.tsx +27 -14
  168. package/registry/payment-card/files/src/components/ui/tilt-card.tsx +2 -2
  169. package/registry/payment-card/files/src/lib/kit.tsx +8 -5
  170. package/registry/progress/files/src/components/ui/progress.tsx +8 -5
  171. package/registry/progress/files/src/lib/kit.tsx +8 -5
  172. package/registry/progress-steps/files/src/components/ui/progress-steps.tsx +15 -11
  173. package/registry/progress-steps/files/src/lib/kit.tsx +8 -5
  174. package/registry/qr-code-button.json +18 -0
  175. package/registry/radio-group/files/src/components/ui/radio-group.tsx +10 -9
  176. package/registry/radio-group/files/src/lib/kit.tsx +8 -5
  177. package/registry/search-bar/files/src/components/ui/search-bar.tsx +13 -11
  178. package/registry/search-bar/files/src/lib/kit.tsx +8 -5
  179. package/registry/segmented-control/files/src/components/ui/segmented-control.tsx +4 -2
  180. package/registry/segmented-control/files/src/lib/kit.tsx +8 -5
  181. package/registry/select/files/src/components/ui/select.tsx +3 -3
  182. package/registry/select/files/src/lib/kit.tsx +8 -5
  183. package/registry/skeleton/files/src/components/ui/skeleton.tsx +4 -4
  184. package/registry/skeleton/files/src/lib/kit.tsx +8 -5
  185. package/registry/slider/files/src/components/ui/slider.tsx +5 -4
  186. package/registry/slider/files/src/lib/kit.tsx +8 -5
  187. package/registry/spinner/files/src/components/ui/spinner.tsx +452 -34
  188. package/registry/spinner/files/src/lib/kit.tsx +8 -5
  189. package/registry/spinner.json +1 -1
  190. package/registry/stateful-button/files/src/components/ui/stateful-button.tsx +532 -0
  191. package/registry/{pagination-dots → stateful-button}/files/src/lib/kit.tsx +8 -5
  192. package/registry/stateful-button.json +18 -0
  193. package/registry/stepper/files/src/components/ui/stepper.tsx +8 -4
  194. package/registry/stepper/files/src/lib/kit.tsx +8 -5
  195. package/registry/switch/files/src/components/ui/switch.tsx +4 -3
  196. package/registry/switch/files/src/lib/kit.tsx +8 -5
  197. package/registry/tabs/files/src/components/ui/tabs.tsx +10 -8
  198. package/registry/tabs/files/src/lib/kit.tsx +8 -5
  199. package/registry/text-animation/files/src/components/ui/text-animation.tsx +650 -0
  200. package/registry/text-animation/files/src/lib/kit.tsx +222 -0
  201. package/registry/text-animation.json +15 -0
  202. package/registry/textarea/files/src/components/ui/textarea.tsx +11 -9
  203. package/registry/textarea/files/src/lib/kit.tsx +8 -5
  204. package/registry/ticket-shape/files/src/lib/kit.tsx +8 -5
  205. package/registry/tilt-card/files/src/components/ui/tilt-card.tsx +2 -2
  206. package/registry/tilt-card/files/src/lib/kit.tsx +8 -5
  207. package/registry/time-picker/files/src/components/ui/time-picker.tsx +2 -2
  208. package/registry/time-picker/files/src/lib/kit.tsx +8 -5
  209. package/registry/timeline/files/src/components/ui/avatar.tsx +7 -4
  210. package/registry/timeline/files/src/components/ui/timeline.tsx +12 -10
  211. package/registry/timeline/files/src/lib/kit.tsx +8 -5
  212. package/registry/toast/files/src/components/ui/button.tsx +4 -3
  213. package/registry/toast/files/src/components/ui/toast.tsx +13 -11
  214. package/registry/toast/files/src/lib/kit.tsx +8 -5
  215. package/schema/components.schema.json +1 -1
  216. package/registry/_docs/color-swatch.json +0 -66
  217. package/registry/_docs/kpi-card.json +0 -11
  218. package/registry/_docs/like-button.json +0 -72
  219. package/registry/_docs/morph-button.json +0 -108
  220. package/registry/_docs/pagination-dots.json +0 -88
  221. package/registry/_docs/shimmer-button.json +0 -78
  222. package/registry/_docs/tag-input.json +0 -128
  223. package/registry/color-swatch.json +0 -17
  224. package/registry/kpi-card/files/src/components/charts/kpi-card.tsx +0 -45
  225. package/registry/kpi-card.json +0 -14
  226. package/registry/like-button.json +0 -18
  227. package/registry/morph-button.json +0 -18
  228. package/registry/pagination-dots/files/src/components/ui/pagination-dots.tsx +0 -262
  229. package/registry/pagination-dots.json +0 -15
  230. package/registry/shimmer-button.json +0 -17
  231. package/registry/tag-input/files/src/components/ui/tag-input.tsx +0 -720
  232. package/registry/tag-input.json +0 -17
@@ -181,14 +181,17 @@ export function useKit(): Theme {
181
181
  return useMemo(() => build(scheme, ctx?.overrides), [scheme, ctx?.overrides]);
182
182
  }
183
183
 
184
- /** Shared motion presets so the whole kit moves the same way. */
184
+ /**
185
+ * Shared motion presets so the whole kit moves the same way. Every spring is critically damped
186
+ * (damping ≥ 2·√(stiffness·mass)): it settles smoothly with no overshoot or wobble.
187
+ */
185
188
  export const motion = {
186
189
  /** Toggles, selection indicators. */
187
- snappy: { damping: 18, stiffness: 260, mass: 0.8 },
188
- /** Press feedback, pops. */
189
- bouncy: { damping: 11, stiffness: 220, mass: 0.7 },
190
+ snappy: { damping: 30, stiffness: 260, mass: 0.8 },
191
+ /** Press feedback, pops. (Kept under its old name; it no longer bounces.) */
192
+ bouncy: { damping: 26, stiffness: 220, mass: 0.7 },
190
193
  /** Sheets, layout changes. */
191
- gentle: { damping: 22, stiffness: 160, mass: 1 },
194
+ gentle: { damping: 27, stiffness: 160, mass: 1 },
192
195
  fast: 160,
193
196
  normal: 240,
194
197
  };
@@ -116,7 +116,8 @@ export function Button({
116
116
  onPressOut,
117
117
  ...props
118
118
  }: ButtonProps) {
119
- const { colors, radius, dark } = useKit();
119
+ const { colors, radius, dark, font } = useKit();
120
+ const ff = font.family ? { fontFamily: font.family } : null;
120
121
  const gid = 'btn' + useId().replace(/[^a-zA-Z0-9]/g, '');
121
122
  const scale = useRef(new Animated.Value(1)).current;
122
123
  const isDisabled = disabled || loading;
@@ -149,7 +150,7 @@ export function Button({
149
150
  const label = isTextNode(children) ? (
150
151
  <Text
151
152
  numberOfLines={1}
152
- style={[styles.label, { color: fg, fontSize: s.font }, variant === 'link' && styles.link, textStyle]}
153
+ style={[styles.label, ff, { color: fg, fontSize: s.font }, variant === 'link' && styles.link, textStyle]}
153
154
  >
154
155
  {children}
155
156
  </Text>
@@ -235,7 +236,7 @@ export function Button({
235
236
  <View style={styles.row}>
236
237
  <ActivityIndicator size="small" color={fg} />
237
238
  {size !== 'icon' && isTextNode(children) ? (
238
- <Text style={[styles.label, { color: fg, fontSize: s.font, opacity: 0.85 }]}>{children}</Text>
239
+ <Text style={[styles.label, ff, { color: fg, fontSize: s.font, opacity: 0.85 }]}>{children}</Text>
239
240
  ) : null}
240
241
  </View>
241
242
  ) : (
@@ -181,14 +181,17 @@ export function useKit(): Theme {
181
181
  return useMemo(() => build(scheme, ctx?.overrides), [scheme, ctx?.overrides]);
182
182
  }
183
183
 
184
- /** Shared motion presets so the whole kit moves the same way. */
184
+ /**
185
+ * Shared motion presets so the whole kit moves the same way. Every spring is critically damped
186
+ * (damping ≥ 2·√(stiffness·mass)): it settles smoothly with no overshoot or wobble.
187
+ */
185
188
  export const motion = {
186
189
  /** Toggles, selection indicators. */
187
- snappy: { damping: 18, stiffness: 260, mass: 0.8 },
188
- /** Press feedback, pops. */
189
- bouncy: { damping: 11, stiffness: 220, mass: 0.7 },
190
+ snappy: { damping: 30, stiffness: 260, mass: 0.8 },
191
+ /** Press feedback, pops. (Kept under its old name; it no longer bounces.) */
192
+ bouncy: { damping: 26, stiffness: 220, mass: 0.7 },
190
193
  /** Sheets, layout changes. */
191
- gentle: { damping: 22, stiffness: 160, mass: 1 },
194
+ gentle: { damping: 27, stiffness: 160, mass: 1 },
192
195
  fast: 160,
193
196
  normal: 240,
194
197
  };
@@ -105,7 +105,8 @@ export function Button3D({
105
105
  accessibilityLabel,
106
106
  style,
107
107
  }: Button3DProps) {
108
- const { dark } = useKit();
108
+ const { dark, font } = useKit();
109
+ const ff = font.family ? { fontFamily: font.family } : null;
109
110
  const s = SIZES[size];
110
111
  const depth = depthProp ?? s.depth;
111
112
  const kind = appearance === 'auto' ? (dark ? 'light' : 'dark') : appearance;
@@ -122,7 +123,7 @@ export function Button3D({
122
123
  ) : isTextNode(children) ? (
123
124
  // No numberOfLines: on a narrow fixed-width key ("esc", "Q") the label may use the padding
124
125
  // instead of being cut to an ellipsis.
125
- <Text style={{ color: m.ink, fontSize: round ? s.font + 4 : s.font, fontWeight: '600', letterSpacing: 0.1, flexShrink: 0, textAlign: 'center' }}>
126
+ <Text style={[{ color: m.ink, fontSize: round ? s.font + 4 : s.font, fontWeight: '600', letterSpacing: 0.1, flexShrink: 0, textAlign: 'center' }, ff]}>
126
127
  {children}
127
128
  </Text>
128
129
  ) : (
@@ -181,14 +181,17 @@ export function useKit(): Theme {
181
181
  return useMemo(() => build(scheme, ctx?.overrides), [scheme, ctx?.overrides]);
182
182
  }
183
183
 
184
- /** Shared motion presets so the whole kit moves the same way. */
184
+ /**
185
+ * Shared motion presets so the whole kit moves the same way. Every spring is critically damped
186
+ * (damping ≥ 2·√(stiffness·mass)): it settles smoothly with no overshoot or wobble.
187
+ */
185
188
  export const motion = {
186
189
  /** Toggles, selection indicators. */
187
- snappy: { damping: 18, stiffness: 260, mass: 0.8 },
188
- /** Press feedback, pops. */
189
- bouncy: { damping: 11, stiffness: 220, mass: 0.7 },
190
+ snappy: { damping: 30, stiffness: 260, mass: 0.8 },
191
+ /** Press feedback, pops. (Kept under its old name; it no longer bounces.) */
192
+ bouncy: { damping: 26, stiffness: 220, mass: 0.7 },
190
193
  /** Sheets, layout changes. */
191
- gentle: { damping: 22, stiffness: 160, mass: 1 },
194
+ gentle: { damping: 27, stiffness: 160, mass: 1 },
192
195
  fast: 160,
193
196
  normal: 240,
194
197
  };
@@ -201,7 +201,8 @@ export function Calendar(props: CalendarProps) {
201
201
  accent,
202
202
  style,
203
203
  } = props as unknown as FlatProps;
204
- const { colors, radius } = useKit();
204
+ const { colors, radius, font } = useKit();
205
+ const ff = font.family ? { fontFamily: font.family } : null;
205
206
  const tint = accent ?? colors.accent;
206
207
  const fmt = useFormatters(locale);
207
208
 
@@ -368,6 +369,7 @@ export function Calendar(props: CalendarProps) {
368
369
  numberOfLines={1}
369
370
  style={[
370
371
  styles.title,
372
+ ff,
371
373
  { color: view === 'days' ? colors.foreground : tint },
372
374
  view === 'days'
373
375
  ? {
@@ -390,7 +392,7 @@ export function Calendar(props: CalendarProps) {
390
392
  hitSlop={4}
391
393
  style={({ pressed }) => [styles.todayBtn, { borderRadius: radius.capsule(32), backgroundColor: pressed ? colors.secondary : colors.muted }]}
392
394
  >
393
- <Text style={[styles.todayText, { color: tint }]}>Today</Text>
395
+ <Text style={[styles.todayText, ff, { color: tint }]}>Today</Text>
394
396
  </Pressable>
395
397
  ) : null}
396
398
  <NavButton dir={-1} disabled={prevDisabled} onPress={() => step(-1)} />
@@ -407,7 +409,7 @@ export function Calendar(props: CalendarProps) {
407
409
  >
408
410
  <View style={styles.weekRow} accessibilityElementsHidden importantForAccessibility="no-hide-descendants">
409
411
  {weekdays.map((w) => (
410
- <Text key={w.long} numberOfLines={1} style={[styles.weekday, { color: colors.mutedForeground }]}>
412
+ <Text key={w.long} numberOfLines={1} style={[styles.weekday, ff, { color: colors.mutedForeground }]}>
411
413
  {w.short}
412
414
  </Text>
413
415
  ))}
@@ -486,7 +488,7 @@ export function Calendar(props: CalendarProps) {
486
488
  },
487
489
  ]}
488
490
  >
489
- <Text style={[styles.pickText, { color: selected ? readableOn(tint) : isNow ? tint : colors.foreground }]}>{label}</Text>
491
+ <Text style={[styles.pickText, ff, { color: selected ? readableOn(tint) : isNow ? tint : colors.foreground }]}>{label}</Text>
490
492
  </View>
491
493
  )}
492
494
  </Pressable>
@@ -638,7 +640,8 @@ const DayCell = memo(function DayCell({
638
640
  tint,
639
641
  onPress,
640
642
  }: DayCellProps) {
641
- const { colors, radius } = useKit();
643
+ const { colors, radius, font } = useKit();
644
+ const ff = font.family ? { fontFamily: font.family } : null;
642
645
  const r = tileRadius(radius.md);
643
646
  const pop = useRef(new Animated.Value(selected ? 1 : 0)).current; // native: circle scale + opacity
644
647
  const bandIn = useRef(new Animated.Value(band === 'none' ? 0 : 1)).current; // native: band opacity
@@ -719,6 +722,7 @@ const DayCell = memo(function DayCell({
719
722
  <Text
720
723
  style={[
721
724
  styles.day,
725
+ ff,
722
726
  { color: textColor },
723
727
  (today || selected) && styles.dayStrong,
724
728
  outside && !selected && styles.outside,
@@ -181,14 +181,17 @@ export function useKit(): Theme {
181
181
  return useMemo(() => build(scheme, ctx?.overrides), [scheme, ctx?.overrides]);
182
182
  }
183
183
 
184
- /** Shared motion presets so the whole kit moves the same way. */
184
+ /**
185
+ * Shared motion presets so the whole kit moves the same way. Every spring is critically damped
186
+ * (damping ≥ 2·√(stiffness·mass)): it settles smoothly with no overshoot or wobble.
187
+ */
185
188
  export const motion = {
186
189
  /** Toggles, selection indicators. */
187
- snappy: { damping: 18, stiffness: 260, mass: 0.8 },
188
- /** Press feedback, pops. */
189
- bouncy: { damping: 11, stiffness: 220, mass: 0.7 },
190
+ snappy: { damping: 30, stiffness: 260, mass: 0.8 },
191
+ /** Press feedback, pops. (Kept under its old name; it no longer bounces.) */
192
+ bouncy: { damping: 26, stiffness: 220, mass: 0.7 },
190
193
  /** Sheets, layout changes. */
191
- gentle: { damping: 22, stiffness: 160, mass: 1 },
194
+ gentle: { damping: 27, stiffness: 160, mass: 1 },
192
195
  fast: 160,
193
196
  normal: 240,
194
197
  };
@@ -1,4 +1,4 @@
1
- import React, { createContext, useContext, useId, useRef, useState } from 'react';
1
+ import React, { createContext, useContext, useId, useRef, useState, type ComponentRef } from 'react';
2
2
  import {
3
3
  Animated,
4
4
  Image,
@@ -98,7 +98,7 @@ export function Card({
98
98
  if (highlight !== 'none') Animated.timing(glow, { toValue: 0, duration: 420, useNativeDriver: true }).start();
99
99
  };
100
100
  // Touch coordinates are relative to whichever child was hit, so work from page coordinates.
101
- const clipRef = useRef<View>(null);
101
+ const clipRef = useRef<ComponentRef<typeof View>>(null);
102
102
  const origin = useRef({ x: 0, y: 0 });
103
103
  const placeSpot = (pageX: number, pageY: number) =>
104
104
  spot.setValue({ x: pageX - origin.current.x - spotD / 2, y: pageY - origin.current.y - spotD / 2 });
@@ -215,18 +215,20 @@ export interface CardTextProps {
215
215
  }
216
216
 
217
217
  export function CardTitle({ children, numberOfLines, style }: CardTextProps) {
218
- const { colors } = useKit();
218
+ const { colors, font } = useKit();
219
+ const ff = font.family ? { fontFamily: font.family } : null;
219
220
  return (
220
- <Text accessibilityRole="header" numberOfLines={numberOfLines} style={[styles.title, { color: colors.cardForeground }, style]}>
221
+ <Text accessibilityRole="header" numberOfLines={numberOfLines} style={[styles.title, ff, { color: colors.cardForeground }, style]}>
221
222
  {children}
222
223
  </Text>
223
224
  );
224
225
  }
225
226
 
226
227
  export function CardDescription({ children, numberOfLines, style }: CardTextProps) {
227
- const { colors } = useKit();
228
+ const { colors, font } = useKit();
229
+ const ff = font.family ? { fontFamily: font.family } : null;
228
230
  return (
229
- <Text numberOfLines={numberOfLines} style={[styles.description, { color: colors.mutedForeground }, style]}>
231
+ <Text numberOfLines={numberOfLines} style={[styles.description, ff, { color: colors.mutedForeground }, style]}>
230
232
  {children}
231
233
  </Text>
232
234
  );
@@ -181,14 +181,17 @@ export function useKit(): Theme {
181
181
  return useMemo(() => build(scheme, ctx?.overrides), [scheme, ctx?.overrides]);
182
182
  }
183
183
 
184
- /** Shared motion presets so the whole kit moves the same way. */
184
+ /**
185
+ * Shared motion presets so the whole kit moves the same way. Every spring is critically damped
186
+ * (damping ≥ 2·√(stiffness·mass)): it settles smoothly with no overshoot or wobble.
187
+ */
185
188
  export const motion = {
186
189
  /** Toggles, selection indicators. */
187
- snappy: { damping: 18, stiffness: 260, mass: 0.8 },
188
- /** Press feedback, pops. */
189
- bouncy: { damping: 11, stiffness: 220, mass: 0.7 },
190
+ snappy: { damping: 30, stiffness: 260, mass: 0.8 },
191
+ /** Press feedback, pops. (Kept under its old name; it no longer bounces.) */
192
+ bouncy: { damping: 26, stiffness: 220, mass: 0.7 },
190
193
  /** Sheets, layout changes. */
191
- gentle: { damping: 22, stiffness: 160, mass: 1 },
194
+ gentle: { damping: 27, stiffness: 160, mass: 1 },
192
195
  fast: 160,
193
196
  normal: 240,
194
197
  };
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "chat",
3
+ "type": "block",
4
+ "title": "Chat",
5
+ "description": "An AI assistant chat screen: replies stream in word by word with a soft fade and a blinking caret, a shimmering “Thinking…” waits for the first token, and the send button morphs into Stop. Light markdown (bold, inline code, lists, scrollable code blocks), suggestion chips, copy / regenerate / thumbs actions and a prompt field that grows to five lines and stays above the keyboard.",
6
+ "tier": "pro",
7
+ "dependencies": [
8
+ "react-native-svg",
9
+ "expo-haptics"
10
+ ],
11
+ "registryDependencies": [
12
+ "kit"
13
+ ],
14
+ "sharedFiles": [
15
+ "lib/kit.tsx"
16
+ ],
17
+ "filesDir": null
18
+ }
@@ -38,7 +38,8 @@ export function Checkbox({
38
38
  shape = 'square',
39
39
  style,
40
40
  }: CheckboxProps) {
41
- const { colors, radius } = useKit();
41
+ const { colors, radius, font } = useKit();
42
+ const ff = font.family ? { fontFamily: font.family } : null;
42
43
  const tint = accent ?? colors.accent;
43
44
  const corner = shape === 'circle' ? size / 2 : Math.min(size / 2, radius.sm + 1);
44
45
  const [inner, setInner] = useState(defaultChecked);
@@ -49,7 +50,7 @@ export function Checkbox({
49
50
  const burst = useRef(new Animated.Value(0)).current;
50
51
 
51
52
  useEffect(() => {
52
- Animated.spring(progress, { toValue: on ? 1 : 0, useNativeDriver: false, speed: 14, bounciness: 6 }).start();
53
+ Animated.spring(progress, { toValue: on ? 1 : 0, useNativeDriver: false, speed: 14, bounciness: 0 }).start();
53
54
  if (on) {
54
55
  burst.setValue(0);
55
56
  Animated.timing(burst, { toValue: 1, duration: 450, useNativeDriver: true }).start();
@@ -72,7 +73,7 @@ export function Checkbox({
72
73
  disabled={disabled}
73
74
  onPress={toggle}
74
75
  onPressIn={() => Animated.spring(press, { toValue: 0.82, useNativeDriver: true, speed: 50 }).start()}
75
- onPressOut={() => Animated.spring(press, { toValue: 1, useNativeDriver: true, speed: 20, bounciness: 14 }).start()}
76
+ onPressOut={() => Animated.spring(press, { toValue: 1, useNativeDriver: true, speed: 20, bounciness: 0 }).start()}
76
77
  style={[styles.row, { opacity: disabled ? 0.5 : 1 }, style]}
77
78
  >
78
79
  <Animated.View style={{ transform: [{ scale: press }] }}>
@@ -119,7 +120,7 @@ export function Checkbox({
119
120
  {label ? (
120
121
  <View style={styles.text}>
121
122
  <View style={styles.labelBox}>
122
- <Animated.Text style={[styles.label, { color: progress.interpolate({ inputRange: [0, 1], outputRange: [fg, strike ? muted : fg] }) }]}>
123
+ <Animated.Text style={[styles.label, ff, { color: progress.interpolate({ inputRange: [0, 1], outputRange: [fg, strike ? muted : fg] }) }]}>
123
124
  {label}
124
125
  </Animated.Text>
125
126
  {strike ? (
@@ -131,7 +132,7 @@ export function Checkbox({
131
132
  />
132
133
  ) : null}
133
134
  </View>
134
- {description ? <Text style={[styles.description, { color: muted }]}>{description}</Text> : null}
135
+ {description ? <Text style={[styles.description, ff, { color: muted }]}>{description}</Text> : null}
135
136
  </View>
136
137
  ) : null}
137
138
  </Pressable>
@@ -181,14 +181,17 @@ export function useKit(): Theme {
181
181
  return useMemo(() => build(scheme, ctx?.overrides), [scheme, ctx?.overrides]);
182
182
  }
183
183
 
184
- /** Shared motion presets so the whole kit moves the same way. */
184
+ /**
185
+ * Shared motion presets so the whole kit moves the same way. Every spring is critically damped
186
+ * (damping ≥ 2·√(stiffness·mass)): it settles smoothly with no overshoot or wobble.
187
+ */
185
188
  export const motion = {
186
189
  /** Toggles, selection indicators. */
187
- snappy: { damping: 18, stiffness: 260, mass: 0.8 },
188
- /** Press feedback, pops. */
189
- bouncy: { damping: 11, stiffness: 220, mass: 0.7 },
190
+ snappy: { damping: 30, stiffness: 260, mass: 0.8 },
191
+ /** Press feedback, pops. (Kept under its old name; it no longer bounces.) */
192
+ bouncy: { damping: 26, stiffness: 220, mass: 0.7 },
190
193
  /** Sheets, layout changes. */
191
- gentle: { damping: 22, stiffness: 160, mass: 1 },
194
+ gentle: { damping: 27, stiffness: 160, mass: 1 },
192
195
  fast: 160,
193
196
  normal: 240,
194
197
  };
@@ -151,7 +151,8 @@ interface ChipProps {
151
151
  }
152
152
 
153
153
  function Chip({ option, active, onPress, variant, size, tint, showCheck, multiple, disabled }: ChipProps) {
154
- const { colors, radius } = useKit();
154
+ const { colors, radius, font } = useKit();
155
+ const ff = font.family ? { fontFamily: font.family } : null;
155
156
  // p: colours, check width + stroke (JS driver). press: squish (native, on the outer view).
156
157
  const p = useRef(new Animated.Value(active ? 1 : 0)).current;
157
158
  const press = useRef(new Animated.Value(1)).current;
@@ -237,7 +238,7 @@ function Chip({ option, active, onPress, variant, size, tint, showCheck, multipl
237
238
  {option.label ? (
238
239
  <Animated.Text
239
240
  numberOfLines={1}
240
- style={[styles.label, { fontSize: size.font, color: p.interpolate({ ...range, outputRange: l.fg }) }]}
241
+ style={[styles.label, ff, { fontSize: size.font, color: p.interpolate({ ...range, outputRange: l.fg }) }]}
241
242
  >
242
243
  {option.label}
243
244
  </Animated.Text>
@@ -246,6 +247,7 @@ function Chip({ option, active, onPress, variant, size, tint, showCheck, multipl
246
247
  <Animated.Text
247
248
  style={[
248
249
  styles.count,
250
+ ff,
249
251
  { fontSize: size.font - 1.5, color: p.interpolate({ ...range, outputRange: [colors.mutedForeground, l.fg[1]] }) },
250
252
  ]}
251
253
  >
@@ -181,14 +181,17 @@ export function useKit(): Theme {
181
181
  return useMemo(() => build(scheme, ctx?.overrides), [scheme, ctx?.overrides]);
182
182
  }
183
183
 
184
- /** Shared motion presets so the whole kit moves the same way. */
184
+ /**
185
+ * Shared motion presets so the whole kit moves the same way. Every spring is critically damped
186
+ * (damping ≥ 2·√(stiffness·mass)): it settles smoothly with no overshoot or wobble.
187
+ */
185
188
  export const motion = {
186
189
  /** Toggles, selection indicators. */
187
- snappy: { damping: 18, stiffness: 260, mass: 0.8 },
188
- /** Press feedback, pops. */
189
- bouncy: { damping: 11, stiffness: 220, mass: 0.7 },
190
+ snappy: { damping: 30, stiffness: 260, mass: 0.8 },
191
+ /** Press feedback, pops. (Kept under its old name; it no longer bounces.) */
192
+ bouncy: { damping: 26, stiffness: 220, mass: 0.7 },
190
193
  /** Sheets, layout changes. */
191
- gentle: { damping: 22, stiffness: 160, mass: 1 },
194
+ gentle: { damping: 27, stiffness: 160, mass: 1 },
192
195
  fast: 160,
193
196
  normal: 240,
194
197
  };
@@ -181,14 +181,17 @@ export function useKit(): Theme {
181
181
  return useMemo(() => build(scheme, ctx?.overrides), [scheme, ctx?.overrides]);
182
182
  }
183
183
 
184
- /** Shared motion presets so the whole kit moves the same way. */
184
+ /**
185
+ * Shared motion presets so the whole kit moves the same way. Every spring is critically damped
186
+ * (damping ≥ 2·√(stiffness·mass)): it settles smoothly with no overshoot or wobble.
187
+ */
185
188
  export const motion = {
186
189
  /** Toggles, selection indicators. */
187
- snappy: { damping: 18, stiffness: 260, mass: 0.8 },
188
- /** Press feedback, pops. */
189
- bouncy: { damping: 11, stiffness: 220, mass: 0.7 },
190
+ snappy: { damping: 30, stiffness: 260, mass: 0.8 },
191
+ /** Press feedback, pops. (Kept under its old name; it no longer bounces.) */
192
+ bouncy: { damping: 26, stiffness: 220, mass: 0.7 },
190
193
  /** Sheets, layout changes. */
191
- gentle: { damping: 22, stiffness: 160, mass: 1 },
194
+ gentle: { damping: 27, stiffness: 160, mass: 1 },
192
195
  fast: 160,
193
196
  normal: 240,
194
197
  };
@@ -198,6 +198,8 @@ function useRemaining(at?: Date | number) {
198
198
 
199
199
  /** The promo code in a dashed chip; a tap copies it and flips the icon to a check for a moment. */
200
200
  function CodeChip({ code, color, ink, fontSize, onCopy, labels }: { code: string; color: string; ink: string; fontSize: number; onCopy?: (code: string) => void; labels: Required<CouponLabels> }) {
201
+ const { font } = useKit();
202
+ const ff = font.family ? { fontFamily: font.family } : null;
201
203
  const [copied, setCopied] = useState(false);
202
204
  const [box, setBox] = useState({ w: 0, h: 0 });
203
205
  const timer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
@@ -226,7 +228,7 @@ function CodeChip({ code, color, ink, fontSize, onCopy, labels }: { code: string
226
228
  <Rect x={0.75} y={0.75} width={box.w - 1.5} height={box.h - 1.5} rx={8} fill="none" stroke={alpha(color, 0.75)} strokeWidth={1.5} strokeDasharray={[5, 4]} />
227
229
  </Svg>
228
230
  ) : null}
229
- <Text numberOfLines={1} style={{ color: ink, fontSize, fontWeight: '800', letterSpacing: 1, flexShrink: 1 }}>
231
+ <Text numberOfLines={1} style={[{ color: ink, fontSize, fontWeight: '800', letterSpacing: 1, flexShrink: 1 }, ff]}>
230
232
  {code}
231
233
  </Text>
232
234
  {onCopy ? (
@@ -325,7 +327,8 @@ export function Coupon({
325
327
  labels: labelsProp,
326
328
  style,
327
329
  }: CouponProps) {
328
- const { colors, dark } = useKit();
330
+ const { colors, dark, font } = useKit();
331
+ const ff = font.family ? { fontFamily: font.family } : null;
329
332
  const theme = typeof themeProp === 'object' ? themeProp : couponThemes[themeProp];
330
333
  const fills = typeof theme.fill === 'string' ? [theme.fill] : theme.fill;
331
334
  const accent = theme.accent ?? fills[fills.length - 1];
@@ -421,7 +424,7 @@ export function Coupon({
421
424
  left !== undefined && left > 0 && !spent ? (
422
425
  <View style={[styles.timer, { backgroundColor: timerBg }]}>
423
426
  <Icon d={ICON.clock} size={11} color={timerInk} />
424
- <Text style={{ color: timerInk, fontSize: 10.5, fontWeight: '800', fontVariant: ['tabular-nums'] }}>
427
+ <Text style={[{ color: timerInk, fontSize: 10.5, fontWeight: '800', fontVariant: ['tabular-nums'] }, ff]}>
425
428
  {labels.endsIn} {formatCountdown(left)}
426
429
  </Text>
427
430
  </View>
@@ -433,7 +436,7 @@ export function Coupon({
433
436
  <View style={[styles.row, { gap: 6, flexShrink: 1 }]}>
434
437
  {logo}
435
438
  {brand ? (
436
- <Text numberOfLines={1} style={{ color: horizontal ? bodySoft : valueSoft, fontSize: 10.5, fontWeight: '800', letterSpacing: 1.2, flexShrink: 1 }}>
439
+ <Text numberOfLines={1} style={[{ color: horizontal ? bodySoft : valueSoft, fontSize: 10.5, fontWeight: '800', letterSpacing: 1.2, flexShrink: 1 }, ff]}>
437
440
  {brand.toUpperCase()}
438
441
  </Text>
439
442
  ) : null}
@@ -445,15 +448,15 @@ export function Coupon({
445
448
  // Value panel ------------------------------------------------------------------------------------
446
449
  const valueBlock = (
447
450
  <View style={{ alignItems: 'center' }}>
448
- {prefix ? <Text style={[{ color: valueSoft, fontSize: 10.5, fontWeight: '900', letterSpacing: 1.6 }, lift]}>{prefix.toUpperCase()}</Text> : null}
451
+ {prefix ? <Text style={[{ color: valueSoft, fontSize: 10.5, fontWeight: '900', letterSpacing: 1.6 }, ff, lift]}>{prefix.toUpperCase()}</Text> : null}
449
452
  {locked ? <Icon d={ICON.lock} size={valueSize * 0.6} color={valueInk} /> : null}
450
453
  {/* Vertical coupons set "50% OFF" on one line when it fits, and stack a long unit ("CASHBACK"). */}
451
454
  <View style={{ flexDirection: inline ? 'row' : 'column', alignItems: inline ? 'flex-end' : 'center', gap: inline ? 6 : 0 }}>
452
- <Text numberOfLines={1} adjustsFontSizeToFit style={[{ color: valueInk, fontSize: valueSize, lineHeight: valueSize * 1.08, fontWeight: '900', letterSpacing: -1.2 }, lift]}>
455
+ <Text numberOfLines={1} adjustsFontSizeToFit style={[{ color: valueInk, fontSize: valueSize, lineHeight: valueSize * 1.08, fontWeight: '900', letterSpacing: -1.2 }, ff, lift]}>
453
456
  {value}
454
457
  </Text>
455
458
  {unit ? (
456
- <Text style={[{ color: valueInk, fontSize: horizontal ? 12.5 : 16, fontWeight: '900', letterSpacing: 2.4, marginBottom: inline ? valueSize * 0.14 : 0 }, lift]}>
459
+ <Text style={[{ color: valueInk, fontSize: horizontal ? 12.5 : 16, fontWeight: '900', letterSpacing: 2.4, marginBottom: inline ? valueSize * 0.14 : 0 }, ff, lift]}>
457
460
  {unit.toUpperCase()}
458
461
  </Text>
459
462
  ) : null}
@@ -482,7 +485,7 @@ export function Coupon({
482
485
  {brandRow}
483
486
  {valueBlock}
484
487
  {title ? (
485
- <Text numberOfLines={2} style={[{ color: valueInk, fontSize: s.title + 1, fontWeight: '800', textAlign: 'center', letterSpacing: -0.3 }, lift]}>
488
+ <Text numberOfLines={2} style={[{ color: valueInk, fontSize: s.title + 1, fontWeight: '800', textAlign: 'center', letterSpacing: -0.3 }, ff, lift]}>
486
489
  {title}
487
490
  </Text>
488
491
  ) : null}
@@ -511,7 +514,7 @@ export function Coupon({
511
514
  ]}
512
515
  >
513
516
  {applied || locked ? <Icon d={applied ? ICON.check : ICON.lock} size={13} color={applied ? '#ffffff' : bodySoft} /> : null}
514
- <Text numberOfLines={1} style={{ color: applied ? '#ffffff' : locked || spent ? bodySoft : readableOn(accent), fontSize: s.chip - 0.5, fontWeight: '800' }}>
517
+ <Text numberOfLines={1} style={[{ color: applied ? '#ffffff' : locked || spent ? bodySoft : readableOn(accent), fontSize: s.chip - 0.5, fontWeight: '800' }, ff]}>
515
518
  {applied ? labels.applied : locked ? labels.locked : labels.apply}
516
519
  </Text>
517
520
  </Pressable>
@@ -520,11 +523,11 @@ export function Coupon({
520
523
  const footer =
521
524
  expires || canFlip ? (
522
525
  <View style={[styles.row, { justifyContent: 'flex-start', gap: 6 }]}>
523
- {expires ? <Text style={{ color: bodySoft, fontSize: 11, fontWeight: '600' }}>{expires}</Text> : null}
524
- {expires && canFlip ? <Text style={{ color: bodySoft, fontSize: 11 }}>·</Text> : null}
526
+ {expires ? <Text style={[{ color: bodySoft, fontSize: 11, fontWeight: '600' }, ff]}>{expires}</Text> : null}
527
+ {expires && canFlip ? <Text style={[{ color: bodySoft, fontSize: 11 }, ff]}>·</Text> : null}
525
528
  {canFlip ? (
526
529
  <Pressable onPress={toggle} hitSlop={10} accessibilityRole="button" accessibilityLabel={labels.termsTitle}>
527
- <Text style={{ color: textAccent, fontSize: 11, fontWeight: '800', textDecorationLine: 'underline' }}>{labels.terms}</Text>
530
+ <Text style={[{ color: textAccent, fontSize: 11, fontWeight: '800', textDecorationLine: 'underline' }, ff]}>{labels.terms}</Text>
528
531
  </Pressable>
529
532
  ) : null}
530
533
  </View>
@@ -552,16 +555,16 @@ export function Coupon({
552
555
  >
553
556
  {horizontal ? brandRow : null}
554
557
  {horizontal && title ? (
555
- <Text numberOfLines={2} style={{ color: bodyInk, fontSize: s.title, fontWeight: '800', letterSpacing: -0.3 }}>
558
+ <Text numberOfLines={2} style={[{ color: bodyInk, fontSize: s.title, fontWeight: '800', letterSpacing: -0.3 }, ff]}>
556
559
  {title}
557
560
  </Text>
558
561
  ) : null}
559
562
  {locked && lockedHint ? (
560
- <Text numberOfLines={2} style={{ color: textAccent, fontSize: s.desc, fontWeight: '700' }}>
563
+ <Text numberOfLines={2} style={[{ color: textAccent, fontSize: s.desc, fontWeight: '700' }, ff]}>
561
564
  {lockedHint}
562
565
  </Text>
563
566
  ) : description ? (
564
- <Text numberOfLines={size === 'sm' ? 1 : 2} style={{ color: bodySoft, fontSize: s.desc, lineHeight: s.desc * 1.35 }}>
567
+ <Text numberOfLines={size === 'sm' ? 1 : 2} style={[{ color: bodySoft, fontSize: s.desc, lineHeight: s.desc * 1.35 }, ff]}>
565
568
  {description}
566
569
  </Text>
567
570
  ) : null}
@@ -627,23 +630,23 @@ export function Coupon({
627
630
  <TicketShape {...shape} fill={body} style={StyleSheet.absoluteFill}>
628
631
  <Pressable onPress={toggle} accessibilityRole="button" accessibilityHint={labels.back} style={[StyleSheet.absoluteFill, { padding: s.pad + edgePad, gap: 6, flexDirection: horizontal ? 'row' : 'column' }]}>
629
632
  <View style={{ flex: 1, gap: 4 }}>
630
- <Text style={{ color: bodyInk, fontSize: 12, fontWeight: '800', letterSpacing: 0.3 }}>{labels.termsTitle}</Text>
633
+ <Text style={[{ color: bodyInk, fontSize: 12, fontWeight: '800', letterSpacing: 0.3 }, ff]}>{labels.termsTitle}</Text>
631
634
  {terms!.slice(0, size === 'lg' ? 5 : 4).map((t, i) => (
632
635
  <View key={i} style={{ flexDirection: 'row', gap: 6 }}>
633
- <Text style={{ color: textAccent, fontSize: 11, fontWeight: '900' }}>•</Text>
634
- <Text numberOfLines={2} style={{ color: bodySoft, fontSize: 11, lineHeight: 14.5, flex: 1 }}>
636
+ <Text style={[{ color: textAccent, fontSize: 11, fontWeight: '900' }, ff]}>•</Text>
637
+ <Text numberOfLines={2} style={[{ color: bodySoft, fontSize: 11, lineHeight: 14.5, flex: 1 }, ff]}>
635
638
  {t}
636
639
  </Text>
637
640
  </View>
638
641
  ))}
639
- <Text style={{ color: alpha(bodyInk, 0.4), fontSize: 10, fontWeight: '700', marginTop: 'auto' }}>{labels.back}</Text>
642
+ <Text style={[{ color: alpha(bodyInk, 0.4), fontSize: 10, fontWeight: '700', marginTop: 'auto' }, ff]}>{labels.back}</Text>
640
643
  </View>
641
644
  {code ? (
642
645
  <View style={{ alignItems: 'center', justifyContent: 'center', gap: 4 }}>
643
646
  <View style={styles.plate}>
644
647
  <BarcodeLike code={code} width={horizontal ? 74 : 200} height={horizontal ? 54 : 46} />
645
648
  </View>
646
- <Text style={{ color: bodyInk, fontSize: 10, fontWeight: '800', letterSpacing: 1.2 }}>{code}</Text>
649
+ <Text style={[{ color: bodyInk, fontSize: 10, fontWeight: '800', letterSpacing: 1.2 }, ff]}>{code}</Text>
647
650
  </View>
648
651
  ) : null}
649
652
  </Pressable>
@@ -121,7 +121,7 @@ export function FlipCard({
121
121
  borderRadius: radius,
122
122
  opacity,
123
123
  transform: [{ perspective }, { [axis]: turn } as never, { scale: lift }],
124
- }) as Animated.WithAnimatedObject<ViewStyle>;
124
+ }) as Animated.WithAnimatedValue<ViewStyle>;
125
125
 
126
126
  // The shadow squeezes along the turning axis as the card goes edge-on (see SQUEEZE) and fades a little.
127
127
  const squeeze = p.interpolate(SQUEEZE);
@@ -1,4 +1,4 @@
1
- import React, { createContext, useContext, useEffect, useMemo, useRef, useState } from 'react';
1
+ import React, { createContext, useContext, useEffect, useMemo, useRef, useState, type ComponentRef } from 'react';
2
2
  import {
3
3
  Animated,
4
4
  Easing,
@@ -267,7 +267,7 @@ export function TiltCard({
267
267
  const touching = useRef(false);
268
268
  const origin = useRef({ x: 0, y: 0 });
269
269
  const moved = useRef(false);
270
- const viewRef = useRef<View>(null);
270
+ const viewRef = useRef<ComponentRef<typeof View>>(null);
271
271
  const disabledRef = useRef(disabled);
272
272
  disabledRef.current = disabled;
273
273
 
@@ -181,14 +181,17 @@ export function useKit(): Theme {
181
181
  return useMemo(() => build(scheme, ctx?.overrides), [scheme, ctx?.overrides]);
182
182
  }
183
183
 
184
- /** Shared motion presets so the whole kit moves the same way. */
184
+ /**
185
+ * Shared motion presets so the whole kit moves the same way. Every spring is critically damped
186
+ * (damping ≥ 2·√(stiffness·mass)): it settles smoothly with no overshoot or wobble.
187
+ */
185
188
  export const motion = {
186
189
  /** Toggles, selection indicators. */
187
- snappy: { damping: 18, stiffness: 260, mass: 0.8 },
188
- /** Press feedback, pops. */
189
- bouncy: { damping: 11, stiffness: 220, mass: 0.7 },
190
+ snappy: { damping: 30, stiffness: 260, mass: 0.8 },
191
+ /** Press feedback, pops. (Kept under its old name; it no longer bounces.) */
192
+ bouncy: { damping: 26, stiffness: 220, mass: 0.7 },
190
193
  /** Sheets, layout changes. */
191
- gentle: { damping: 22, stiffness: 160, mass: 1 },
194
+ gentle: { damping: 27, stiffness: 160, mass: 1 },
192
195
  fast: 160,
193
196
  normal: 240,
194
197
  };