@wireai/activation 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/AGENTS.md +73 -0
  2. package/CHANGELOG.md +572 -0
  3. package/INTEGRATION_PROMPT.md +55 -0
  4. package/LICENSE +21 -0
  5. package/README.md +923 -0
  6. package/dist/CenteredModal-C3qQBHsA.d.mts +38 -0
  7. package/dist/CenteredModal-Cdgns6--.d.ts +38 -0
  8. package/dist/coachmarks/index.d.mts +352 -0
  9. package/dist/coachmarks/index.d.ts +352 -0
  10. package/dist/coachmarks/index.js +944 -0
  11. package/dist/coachmarks/index.js.map +1 -0
  12. package/dist/coachmarks/index.mjs +918 -0
  13. package/dist/coachmarks/index.mjs.map +1 -0
  14. package/dist/decision-CFvGY6nP.d.ts +228 -0
  15. package/dist/decision-Cl8OFYzu.d.mts +228 -0
  16. package/dist/index.d.mts +1390 -0
  17. package/dist/index.d.ts +1390 -0
  18. package/dist/index.js +3260 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/index.mjs +3186 -0
  21. package/dist/index.mjs.map +1 -0
  22. package/dist/questionnaire/index.d.mts +249 -0
  23. package/dist/questionnaire/index.d.ts +249 -0
  24. package/dist/questionnaire/index.js +922 -0
  25. package/dist/questionnaire/index.js.map +1 -0
  26. package/dist/questionnaire/index.mjs +906 -0
  27. package/dist/questionnaire/index.mjs.map +1 -0
  28. package/dist/reviews/index.d.mts +213 -0
  29. package/dist/reviews/index.d.ts +213 -0
  30. package/dist/reviews/index.js +1001 -0
  31. package/dist/reviews/index.js.map +1 -0
  32. package/dist/reviews/index.mjs +971 -0
  33. package/dist/reviews/index.mjs.map +1 -0
  34. package/dist/showcase/index.d.mts +119 -0
  35. package/dist/showcase/index.d.ts +119 -0
  36. package/dist/showcase/index.js +660 -0
  37. package/dist/showcase/index.js.map +1 -0
  38. package/dist/showcase/index.mjs +651 -0
  39. package/dist/showcase/index.mjs.map +1 -0
  40. package/dist/types-BKfpdZzX.d.mts +84 -0
  41. package/dist/types-BKfpdZzX.d.ts +84 -0
  42. package/dist/types-CMuOexw0.d.mts +116 -0
  43. package/dist/types-CMuOexw0.d.ts +116 -0
  44. package/dist/types-GL_hQ0TN.d.mts +46 -0
  45. package/dist/types-GL_hQ0TN.d.ts +46 -0
  46. package/llms.txt +36 -0
  47. package/metro/index.d.ts +32 -0
  48. package/metro/index.js +83 -0
  49. package/package.json +171 -0
  50. package/src/DemoOnboarding.tsx +133 -0
  51. package/src/OnboardingFlow.tsx +504 -0
  52. package/src/WireOnboarding.tsx +248 -0
  53. package/src/analytics/analyticsEvent.ts +69 -0
  54. package/src/analytics/reportClientEvent.ts +119 -0
  55. package/src/analytics/sendPreview.ts +73 -0
  56. package/src/attribution/attribution.ts +46 -0
  57. package/src/cards/ChipSelectCard.tsx +265 -0
  58. package/src/cards/InterstitialCard.tsx +299 -0
  59. package/src/cards/NumberStepperCard.tsx +121 -0
  60. package/src/cards/SelectionCard.tsx +270 -0
  61. package/src/cards/StatusCard.tsx +170 -0
  62. package/src/cards/TextInputCard.tsx +147 -0
  63. package/src/cards/index.ts +23 -0
  64. package/src/coachmarks/CoachmarkOverlayHost.tsx +44 -0
  65. package/src/coachmarks/CoachmarkProvider.tsx +108 -0
  66. package/src/coachmarks/GestureHint.tsx +331 -0
  67. package/src/coachmarks/SpotlightOverlay.tsx +254 -0
  68. package/src/coachmarks/coachmarkAnchorRegistry.ts +70 -0
  69. package/src/coachmarks/coachmarkOverlayStore.ts +63 -0
  70. package/src/coachmarks/index.ts +62 -0
  71. package/src/coachmarks/runtime.ts +103 -0
  72. package/src/coachmarks/selectTourSteps.ts +45 -0
  73. package/src/coachmarks/types.ts +58 -0
  74. package/src/coachmarks/useCoachmarkAnchor.ts +49 -0
  75. package/src/coachmarks/useCoachmarkTour.ts +200 -0
  76. package/src/components/AnimatedSparkle.tsx +101 -0
  77. package/src/components/Button.tsx +129 -0
  78. package/src/components/CardHandoff.tsx +212 -0
  79. package/src/components/CardLayout.tsx +103 -0
  80. package/src/components/CenteredModal.tsx +184 -0
  81. package/src/components/CompletionView.tsx +186 -0
  82. package/src/components/DoneBlock.tsx +37 -0
  83. package/src/components/ErrorBlock.tsx +42 -0
  84. package/src/components/Illustration.tsx +36 -0
  85. package/src/components/LoadingBlock.tsx +182 -0
  86. package/src/components/LoadingScreen.tsx +35 -0
  87. package/src/components/OnboardingScaffold.tsx +105 -0
  88. package/src/components/StepProgress.tsx +85 -0
  89. package/src/components/loaderChrome.ts +28 -0
  90. package/src/config/onboardingFlag.ts +39 -0
  91. package/src/config/wireConfigFromEnv.ts +52 -0
  92. package/src/device/deviceContext.ts +158 -0
  93. package/src/features/WireFeaturesProvider.tsx +81 -0
  94. package/src/features/cache.ts +92 -0
  95. package/src/features/defaults.ts +67 -0
  96. package/src/features/fetchWireFeatures.ts +86 -0
  97. package/src/features/index.ts +39 -0
  98. package/src/features/types.ts +61 -0
  99. package/src/features/useWireFeatures.ts +57 -0
  100. package/src/identity/userIdentity.ts +100 -0
  101. package/src/illustrations/defaultIllustrations.tsx +99 -0
  102. package/src/index.ts +159 -0
  103. package/src/motion/centeredModalMotion.ts +45 -0
  104. package/src/motion/motionSpec.ts +133 -0
  105. package/src/motion/useReducedMotion.ts +43 -0
  106. package/src/questionnaire/QuestionnaireGate.tsx +235 -0
  107. package/src/questionnaire/decision.ts +63 -0
  108. package/src/questionnaire/index.ts +54 -0
  109. package/src/questionnaire/runtime.ts +20 -0
  110. package/src/questionnaire/transport.ts +83 -0
  111. package/src/questionnaire/types.ts +182 -0
  112. package/src/questionnaire/useQuestionnaireGate.ts +159 -0
  113. package/src/reviews/ReviewGate.tsx +316 -0
  114. package/src/reviews/ReviewModal.tsx +13 -0
  115. package/src/reviews/decision.ts +120 -0
  116. package/src/reviews/equality.ts +54 -0
  117. package/src/reviews/expo-store-review.d.ts +16 -0
  118. package/src/reviews/feedbackForm.ts +41 -0
  119. package/src/reviews/index.ts +78 -0
  120. package/src/reviews/presentation.ts +29 -0
  121. package/src/reviews/runtime.ts +45 -0
  122. package/src/reviews/storeReview.ts +62 -0
  123. package/src/reviews/transport.ts +89 -0
  124. package/src/reviews/types.ts +172 -0
  125. package/src/reviews/useReviewGate.ts +137 -0
  126. package/src/session/persistedSession.ts +143 -0
  127. package/src/session-analytics/index.ts +18 -0
  128. package/src/session-analytics/reportSessionStart.ts +144 -0
  129. package/src/session-analytics/useSessionStart.ts +114 -0
  130. package/src/showcase/FeatureShowcase.tsx +233 -0
  131. package/src/showcase/index.ts +19 -0
  132. package/src/showcase/selectShowcaseSlides.ts +45 -0
  133. package/src/showcase/showcaseColors.ts +91 -0
  134. package/src/showcase/types.ts +74 -0
  135. package/src/theme/ThemeContext.tsx +38 -0
  136. package/src/theme/defaultTheme.ts +70 -0
  137. package/src/theme/themeFromBrand.ts +93 -0
  138. package/src/theme/types.ts +88 -0
  139. package/src/theme/typography.ts +46 -0
  140. package/src/types.ts +215 -0
  141. package/src/utils/deriveAnswers.ts +43 -0
  142. package/src/utils/readProgress.ts +33 -0
package/dist/index.js ADDED
@@ -0,0 +1,3260 @@
1
+ 'use strict';
2
+
3
+ var React14 = require('react');
4
+ var wireaiRn = require('wireai-rn');
5
+ var jsxRuntime = require('react/jsx-runtime');
6
+ var reactNative = require('react-native');
7
+ var reactNativeSafeAreaContext = require('react-native-safe-area-context');
8
+ var zod = require('zod');
9
+
10
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
11
+
12
+ var React14__default = /*#__PURE__*/_interopDefault(React14);
13
+
14
+ var __defProp = Object.defineProperty;
15
+ var __export = (target, all) => {
16
+ for (var name in all)
17
+ __defProp(target, name, { get: all[name], enumerable: true });
18
+ };
19
+
20
+ // src/theme/defaultTheme.ts
21
+ var defaultOnboardingTheme = {
22
+ colors: {
23
+ background: "#FFFFFF",
24
+ surface: "#F7F7F8",
25
+ text: "#101114",
26
+ textMuted: "#6B7280",
27
+ primary: "#111111",
28
+ onPrimary: "#FFFFFF",
29
+ primarySoft: "rgba(17,17,17,0.06)",
30
+ border: "#E4E4E7",
31
+ borderStrong: "#C7C7CC",
32
+ disabled: "#EBEBEB",
33
+ success: "#16A34A",
34
+ warning: "#D97706",
35
+ error: "#DC2626",
36
+ info: "#2563EB"
37
+ },
38
+ fonts: {
39
+ regular: void 0,
40
+ medium: void 0,
41
+ bold: void 0,
42
+ baseSize: 16,
43
+ scale: 1
44
+ },
45
+ radius: {
46
+ sm: 8,
47
+ md: 12,
48
+ lg: 16,
49
+ full: 9999
50
+ },
51
+ spacing: {
52
+ xs: 4,
53
+ sm: 8,
54
+ md: 16,
55
+ lg: 24,
56
+ xl: 32
57
+ },
58
+ // Circled (pill) CTA by default; an app can pass `button: { radius: 12 }` for
59
+ // a rounded rectangle, or override height/paddingX to match its design system.
60
+ button: {
61
+ radius: 9999,
62
+ height: 52,
63
+ paddingX: 24
64
+ }
65
+ };
66
+ var mergeTheme = (override) => {
67
+ if (!override) return defaultOnboardingTheme;
68
+ return {
69
+ colors: { ...defaultOnboardingTheme.colors, ...override.colors },
70
+ fonts: { ...defaultOnboardingTheme.fonts, ...override.fonts },
71
+ radius: { ...defaultOnboardingTheme.radius, ...override.radius },
72
+ spacing: { ...defaultOnboardingTheme.spacing, ...override.spacing },
73
+ button: { ...defaultOnboardingTheme.button, ...override.button }
74
+ };
75
+ };
76
+ var OnboardingThemeContext = React14.createContext(null);
77
+ var OnboardingThemeProvider = ({
78
+ theme,
79
+ children
80
+ }) => {
81
+ const resolved = React14.useMemo(() => mergeTheme(theme), [theme]);
82
+ return /* @__PURE__ */ jsxRuntime.jsx(OnboardingThemeContext.Provider, { value: resolved, children });
83
+ };
84
+ var useOnboardingTheme = () => {
85
+ const ctx = React14.useContext(OnboardingThemeContext);
86
+ return ctx != null ? ctx : mergeTheme();
87
+ };
88
+ var IllustrationContext = React14.createContext({});
89
+ var IllustrationProvider = ({
90
+ registry,
91
+ children
92
+ }) => /* @__PURE__ */ jsxRuntime.jsx(IllustrationContext.Provider, { value: registry != null ? registry : {}, children });
93
+ var useIllustration = (name) => {
94
+ const registry = React14.useContext(IllustrationContext);
95
+ if (!name) return void 0;
96
+ return registry[name];
97
+ };
98
+ var Glyph = ({ children }) => {
99
+ const t = useOnboardingTheme();
100
+ return /* @__PURE__ */ jsxRuntime.jsx(
101
+ reactNative.View,
102
+ {
103
+ style: [
104
+ styles.disc,
105
+ { backgroundColor: t.colors.primarySoft, borderRadius: t.radius.full }
106
+ ],
107
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles.glyphText, { color: t.colors.primary }], allowFontScaling: false, children })
108
+ }
109
+ );
110
+ };
111
+ var MomentumGlyph = () => /* @__PURE__ */ jsxRuntime.jsx(Glyph, { children: "\u2197" });
112
+ var BeforeAfterGlyph = () => {
113
+ const t = useOnboardingTheme();
114
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles.row, children: [
115
+ /* @__PURE__ */ jsxRuntime.jsx(
116
+ reactNative.View,
117
+ {
118
+ style: [
119
+ styles.chip,
120
+ { borderColor: t.colors.border, borderRadius: t.radius.md }
121
+ ]
122
+ }
123
+ ),
124
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles.arrow, { color: t.colors.primary }], allowFontScaling: false, children: "\u2192" }),
125
+ /* @__PURE__ */ jsxRuntime.jsx(
126
+ reactNative.View,
127
+ {
128
+ style: [
129
+ styles.chip,
130
+ { backgroundColor: t.colors.primarySoft, borderColor: t.colors.primary, borderRadius: t.radius.md }
131
+ ]
132
+ }
133
+ )
134
+ ] });
135
+ };
136
+ var defaultIllustrations = {
137
+ momentum: /* @__PURE__ */ jsxRuntime.jsx(MomentumGlyph, {}),
138
+ "before-after": /* @__PURE__ */ jsxRuntime.jsx(BeforeAfterGlyph, {})
139
+ };
140
+ var styles = reactNative.StyleSheet.create({
141
+ disc: {
142
+ width: 96,
143
+ height: 96,
144
+ alignItems: "center",
145
+ justifyContent: "center"
146
+ },
147
+ glyphText: {
148
+ fontSize: 44,
149
+ fontWeight: "700",
150
+ lineHeight: 52
151
+ },
152
+ row: {
153
+ flexDirection: "row",
154
+ alignItems: "center",
155
+ justifyContent: "center",
156
+ gap: 12
157
+ },
158
+ chip: {
159
+ width: 56,
160
+ height: 56,
161
+ borderWidth: 2
162
+ },
163
+ arrow: {
164
+ fontSize: 28,
165
+ fontWeight: "700"
166
+ }
167
+ });
168
+
169
+ // src/theme/typography.ts
170
+ var round = (n) => Math.round(n);
171
+ var bodyStyle = (f) => ({
172
+ fontFamily: f.regular,
173
+ fontSize: round(f.baseSize * f.scale),
174
+ lineHeight: round(f.baseSize * f.scale * 1.5)
175
+ });
176
+ var captionStyle = (f) => ({
177
+ fontFamily: f.regular,
178
+ fontSize: round(f.baseSize * 0.8125 * f.scale),
179
+ lineHeight: round(f.baseSize * 0.8125 * f.scale * 1.5)
180
+ });
181
+ var labelStyle = (f) => {
182
+ var _a;
183
+ return {
184
+ fontFamily: (_a = f.medium) != null ? _a : f.bold,
185
+ fontSize: round(f.baseSize * f.scale),
186
+ lineHeight: round(f.baseSize * f.scale * 1.4),
187
+ fontWeight: f.medium ? void 0 : "600"
188
+ };
189
+ };
190
+ var headingStyle = (f) => ({
191
+ fontFamily: f.bold,
192
+ fontSize: round(f.baseSize * 1.25 * f.scale),
193
+ lineHeight: round(f.baseSize * 1.25 * f.scale * 1.3),
194
+ fontWeight: f.bold ? void 0 : "700"
195
+ });
196
+ var buttonLabelStyle = (f) => {
197
+ var _a;
198
+ return {
199
+ fontFamily: (_a = f.bold) != null ? _a : f.medium,
200
+ fontSize: round(f.baseSize * f.scale),
201
+ fontWeight: f.bold ? void 0 : "600"
202
+ };
203
+ };
204
+
205
+ // src/motion/motionSpec.ts
206
+ var motionSpec_exports = {};
207
+ __export(motionSpec_exports, {
208
+ BUTTON_PRESS_SCALE: () => BUTTON_PRESS_SCALE,
209
+ BUTTON_PRESS_SPRING: () => BUTTON_PRESS_SPRING,
210
+ CARD_ENTER_MS: () => CARD_ENTER_MS,
211
+ CARD_ENTER_SCALE_FROM: () => CARD_ENTER_SCALE_FROM,
212
+ CARD_EXIT_LEAD_MS: () => CARD_EXIT_LEAD_MS,
213
+ CARD_EXIT_MS: () => CARD_EXIT_MS,
214
+ CARD_SLIDE_PX: () => CARD_SLIDE_PX,
215
+ CARD_SPRING: () => CARD_SPRING,
216
+ CHECK_SCALE_FROM: () => CHECK_SCALE_FROM,
217
+ CHECK_SPRING: () => CHECK_SPRING,
218
+ CHIP_FILL_MS: () => CHIP_FILL_MS,
219
+ CHIP_PRESS_SCALE: () => CHIP_PRESS_SCALE,
220
+ DURATION_BASE_MS: () => DURATION_BASE_MS,
221
+ DURATION_FAST_MS: () => DURATION_FAST_MS,
222
+ DURATION_SLOW_MS: () => DURATION_SLOW_MS,
223
+ INTERSTITIAL_CHECK_DELAY_MS: () => INTERSTITIAL_CHECK_DELAY_MS,
224
+ INTERSTITIAL_CHECK_SCALE_FROM: () => INTERSTITIAL_CHECK_SCALE_FROM,
225
+ INTERSTITIAL_HEAD_MS: () => INTERSTITIAL_HEAD_MS,
226
+ INTERSTITIAL_HEAD_STAGGER_MS: () => INTERSTITIAL_HEAD_STAGGER_MS,
227
+ INTERSTITIAL_ITEM_BASE_DELAY_MS: () => INTERSTITIAL_ITEM_BASE_DELAY_MS,
228
+ INTERSTITIAL_ITEM_MS: () => INTERSTITIAL_ITEM_MS,
229
+ INTERSTITIAL_ITEM_STAGGER_MS: () => INTERSTITIAL_ITEM_STAGGER_MS,
230
+ LOADER_HANDOFF_FADE_MS: () => LOADER_HANDOFF_FADE_MS,
231
+ LOADER_ORBIT_MS: () => LOADER_ORBIT_MS,
232
+ LOADER_PULSE_MIN_OPACITY: () => LOADER_PULSE_MIN_OPACITY,
233
+ LOADER_PULSE_MS: () => LOADER_PULSE_MS,
234
+ PRESS_SPRING: () => PRESS_SPRING,
235
+ PROGRESS_MS: () => PROGRESS_MS,
236
+ REDUCED_FADE_MS: () => REDUCED_FADE_MS,
237
+ REDUCED_PRESS_OPACITY: () => REDUCED_PRESS_OPACITY,
238
+ ROW_PRESS_SCALE: () => ROW_PRESS_SCALE,
239
+ SELECT_FILL_MS: () => SELECT_FILL_MS,
240
+ SKELETON_DELAY_MS: () => SKELETON_DELAY_MS,
241
+ SKELETON_MIN_OPACITY: () => SKELETON_MIN_OPACITY,
242
+ SKELETON_PULSE_MS: () => SKELETON_PULSE_MS,
243
+ STATUS_BURST_MS: () => STATUS_BURST_MS,
244
+ STATUS_BURST_OPACITY_FROM: () => STATUS_BURST_OPACITY_FROM,
245
+ STATUS_BURST_SCALE_FROM: () => STATUS_BURST_SCALE_FROM,
246
+ STATUS_BURST_SCALE_TO: () => STATUS_BURST_SCALE_TO,
247
+ STATUS_COPY_BASE_DELAY_MS: () => STATUS_COPY_BASE_DELAY_MS,
248
+ STATUS_COPY_MS: () => STATUS_COPY_MS,
249
+ STATUS_COPY_STAGGER_MS: () => STATUS_COPY_STAGGER_MS,
250
+ STATUS_POP_SCALE_FROM: () => STATUS_POP_SCALE_FROM,
251
+ STATUS_POP_SPRING: () => STATUS_POP_SPRING,
252
+ WIRE_BEZIER: () => WIRE_BEZIER,
253
+ entranceSpec: () => entranceSpec,
254
+ getMotionDurationScale: () => getMotionDurationScale,
255
+ interstitialCheckDelay: () => interstitialCheckDelay,
256
+ interstitialItemDelay: () => interstitialItemDelay,
257
+ loopEnabled: () => loopEnabled,
258
+ scaledMs: () => scaledMs,
259
+ setMotionDurationScale: () => setMotionDurationScale,
260
+ stateChangeDuration: () => stateChangeDuration,
261
+ statusCopyDelay: () => statusCopyDelay
262
+ });
263
+ var WIRE_BEZIER = [0.16, 1, 0.3, 1];
264
+ var DURATION_FAST_MS = 150;
265
+ var DURATION_BASE_MS = 300;
266
+ var DURATION_SLOW_MS = 500;
267
+ var REDUCED_FADE_MS = 120;
268
+ var ROW_PRESS_SCALE = 0.975;
269
+ var CHIP_PRESS_SCALE = 0.94;
270
+ var BUTTON_PRESS_SCALE = 0.975;
271
+ var REDUCED_PRESS_OPACITY = 0.85;
272
+ var PRESS_SPRING = { friction: 8, tension: 220 };
273
+ var BUTTON_PRESS_SPRING = { friction: 8, tension: 240 };
274
+ var SELECT_FILL_MS = 180;
275
+ var CHIP_FILL_MS = 160;
276
+ var CHECK_SPRING = { friction: 6, tension: 180 };
277
+ var CHECK_SCALE_FROM = 0.6;
278
+ var CARD_ENTER_MS = 220;
279
+ var CARD_EXIT_MS = 180;
280
+ var CARD_EXIT_LEAD_MS = 80;
281
+ var CARD_SLIDE_PX = 26;
282
+ var CARD_ENTER_SCALE_FROM = 0.96;
283
+ var CARD_SPRING = { damping: 14, stiffness: 180, mass: 1 };
284
+ var LOADER_HANDOFF_FADE_MS = 120;
285
+ var INTERSTITIAL_HEAD_MS = 500;
286
+ var INTERSTITIAL_HEAD_STAGGER_MS = 100;
287
+ var INTERSTITIAL_ITEM_MS = 300;
288
+ var INTERSTITIAL_ITEM_STAGGER_MS = 90;
289
+ var INTERSTITIAL_ITEM_BASE_DELAY_MS = 200;
290
+ var INTERSTITIAL_CHECK_DELAY_MS = 200;
291
+ var INTERSTITIAL_CHECK_SCALE_FROM = 0.4;
292
+ var interstitialItemDelay = (index) => INTERSTITIAL_ITEM_BASE_DELAY_MS + index * INTERSTITIAL_ITEM_STAGGER_MS;
293
+ var interstitialCheckDelay = (index) => interstitialItemDelay(index) + INTERSTITIAL_CHECK_DELAY_MS;
294
+ var STATUS_POP_SPRING = { friction: 6, tension: 200 };
295
+ var STATUS_POP_SCALE_FROM = 0.4;
296
+ var STATUS_BURST_MS = 500;
297
+ var STATUS_BURST_SCALE_FROM = 0.4;
298
+ var STATUS_BURST_SCALE_TO = 1.5;
299
+ var STATUS_BURST_OPACITY_FROM = 0.5;
300
+ var STATUS_COPY_MS = 300;
301
+ var STATUS_COPY_STAGGER_MS = 80;
302
+ var STATUS_COPY_BASE_DELAY_MS = 200;
303
+ var statusCopyDelay = (index) => STATUS_COPY_BASE_DELAY_MS + index * STATUS_COPY_STAGGER_MS;
304
+ var LOADER_PULSE_MS = 1600;
305
+ var LOADER_PULSE_MIN_OPACITY = 0.35;
306
+ var LOADER_ORBIT_MS = 2400;
307
+ var SKELETON_DELAY_MS = 300;
308
+ var SKELETON_PULSE_MS = 1500;
309
+ var SKELETON_MIN_OPACITY = 0.45;
310
+ var PROGRESS_MS = 400;
311
+ var durationScale = 1;
312
+ var setMotionDurationScale = (scale) => {
313
+ durationScale = Number.isFinite(scale) && scale > 0 ? Math.min(10, Math.max(0.1, scale)) : 1;
314
+ };
315
+ var getMotionDurationScale = () => durationScale;
316
+ var scaledMs = (ms) => Math.round(ms * durationScale);
317
+ var entranceSpec = (reduced, durationMs) => reduced ? { duration: REDUCED_FADE_MS, useTransform: false } : { duration: durationMs, useTransform: true };
318
+ var stateChangeDuration = (reduced, durationMs) => reduced ? 0 : durationMs;
319
+ var loopEnabled = (reduced) => !reduced;
320
+ var lastKnown = false;
321
+ var useReducedMotion = () => {
322
+ const [reduced, setReduced] = React14.useState(lastKnown);
323
+ React14.useEffect(() => {
324
+ let mounted = true;
325
+ reactNative.AccessibilityInfo.isReduceMotionEnabled().then((value) => {
326
+ lastKnown = value;
327
+ if (mounted) setReduced(value);
328
+ }).catch(() => {
329
+ });
330
+ const sub = reactNative.AccessibilityInfo.addEventListener("reduceMotionChanged", (value) => {
331
+ lastKnown = value;
332
+ setReduced(value);
333
+ });
334
+ return () => {
335
+ mounted = false;
336
+ sub == null ? void 0 : sub.remove();
337
+ };
338
+ }, []);
339
+ return reduced;
340
+ };
341
+ var ROTATION_MS = 1800;
342
+ var _AnimatedSparkle = ({ size = 30, color, variant = "spin" }) => {
343
+ const t = useOnboardingTheme();
344
+ const reduced = useReducedMotion();
345
+ const progress = React14.useRef(new reactNative.Animated.Value(0)).current;
346
+ React14.useEffect(() => {
347
+ if (!loopEnabled(reduced)) {
348
+ progress.setValue(0);
349
+ return;
350
+ }
351
+ const loop = variant === "pulse" ? reactNative.Animated.loop(
352
+ reactNative.Animated.sequence([
353
+ reactNative.Animated.timing(progress, {
354
+ toValue: 1,
355
+ duration: scaledMs(LOADER_PULSE_MS / 2),
356
+ easing: reactNative.Easing.inOut(reactNative.Easing.ease),
357
+ useNativeDriver: true
358
+ }),
359
+ reactNative.Animated.timing(progress, {
360
+ toValue: 0,
361
+ duration: scaledMs(LOADER_PULSE_MS / 2),
362
+ easing: reactNative.Easing.inOut(reactNative.Easing.ease),
363
+ useNativeDriver: true
364
+ })
365
+ ])
366
+ ) : reactNative.Animated.loop(
367
+ reactNative.Animated.timing(progress, {
368
+ toValue: 1,
369
+ duration: scaledMs(ROTATION_MS),
370
+ easing: reactNative.Easing.linear,
371
+ useNativeDriver: true
372
+ })
373
+ );
374
+ loop.start();
375
+ return () => loop.stop();
376
+ }, [progress, variant, reduced]);
377
+ const rotate = progress.interpolate({ inputRange: [0, 1], outputRange: ["0deg", "360deg"] });
378
+ const pulse = progress.interpolate({
379
+ inputRange: [0, 1],
380
+ outputRange: [1, LOADER_PULSE_MIN_OPACITY]
381
+ });
382
+ const animatedStyle = reduced ? void 0 : variant === "pulse" ? { opacity: pulse } : { transform: [{ rotate }] };
383
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.Animated.View, { style: animatedStyle, children: /* @__PURE__ */ jsxRuntime.jsx(
384
+ reactNative.Text,
385
+ {
386
+ accessibilityElementsHidden: true,
387
+ importantForAccessibility: "no",
388
+ style: [styles2.glyph, { fontSize: size, lineHeight: size * 1.1, color: color != null ? color : t.colors.primary }],
389
+ children: "\u2726"
390
+ }
391
+ ) });
392
+ };
393
+ var AnimatedSparkle = React14__default.default.memo(_AnimatedSparkle);
394
+ var styles2 = reactNative.StyleSheet.create({
395
+ glyph: { textAlign: "center" }
396
+ });
397
+ var LoaderChromeContext = React14.createContext({ offsetY: 0 });
398
+ var LoaderChromeProvider = LoaderChromeContext.Provider;
399
+ var useLoaderChrome = () => React14.useContext(LoaderChromeContext);
400
+ var STAGE_SIZE = 96;
401
+ var ORBIT_DOT = 9;
402
+ var OrbitDot = ({ color, reduced }) => {
403
+ const spin = React14.useRef(new reactNative.Animated.Value(0)).current;
404
+ React14.useEffect(() => {
405
+ if (!loopEnabled(reduced)) return;
406
+ const loop = reactNative.Animated.loop(
407
+ reactNative.Animated.timing(spin, {
408
+ toValue: 1,
409
+ duration: scaledMs(LOADER_ORBIT_MS),
410
+ easing: reactNative.Easing.linear,
411
+ useNativeDriver: true
412
+ })
413
+ );
414
+ loop.start();
415
+ return () => loop.stop();
416
+ }, [spin, reduced]);
417
+ if (reduced) return null;
418
+ const rotate = spin.interpolate({ inputRange: [0, 1], outputRange: ["0deg", "360deg"] });
419
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.Animated.View, { pointerEvents: "none", style: [reactNative.StyleSheet.absoluteFill, { transform: [{ rotate }] }], children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles3.orbitDot, { backgroundColor: color }] }) });
420
+ };
421
+ var SkeletonLine = ({
422
+ width,
423
+ delay,
424
+ color,
425
+ reduced
426
+ }) => {
427
+ const pulse = React14.useRef(new reactNative.Animated.Value(1)).current;
428
+ React14.useEffect(() => {
429
+ if (!loopEnabled(reduced)) return;
430
+ const loop = reactNative.Animated.loop(
431
+ reactNative.Animated.sequence([
432
+ reactNative.Animated.delay(delay),
433
+ reactNative.Animated.timing(pulse, {
434
+ toValue: SKELETON_MIN_OPACITY,
435
+ duration: scaledMs(SKELETON_PULSE_MS / 2),
436
+ easing: reactNative.Easing.inOut(reactNative.Easing.ease),
437
+ useNativeDriver: true
438
+ }),
439
+ reactNative.Animated.timing(pulse, {
440
+ toValue: 1,
441
+ duration: scaledMs(SKELETON_PULSE_MS / 2),
442
+ easing: reactNative.Easing.inOut(reactNative.Easing.ease),
443
+ useNativeDriver: true
444
+ })
445
+ ])
446
+ );
447
+ loop.start();
448
+ return () => loop.stop();
449
+ }, [pulse, delay, reduced]);
450
+ return /* @__PURE__ */ jsxRuntime.jsx(
451
+ reactNative.Animated.View,
452
+ {
453
+ style: [styles3.skeletonLine, { width, backgroundColor: color, opacity: reduced ? 1 : pulse }]
454
+ }
455
+ );
456
+ };
457
+ var _LoadingBlock = ({
458
+ title = "Thinking\u2026",
459
+ hint = "Personalizing your experience"
460
+ }) => {
461
+ const t = useOnboardingTheme();
462
+ const reduced = useReducedMotion();
463
+ const { offsetY } = useLoaderChrome();
464
+ const [showSkeleton, setShowSkeleton] = React14.useState(false);
465
+ React14.useEffect(() => {
466
+ const timer = setTimeout(() => setShowSkeleton(true), SKELETON_DELAY_MS);
467
+ return () => clearTimeout(timer);
468
+ }, []);
469
+ return /* @__PURE__ */ jsxRuntime.jsxs(
470
+ reactNative.View,
471
+ {
472
+ style: [
473
+ styles3.center,
474
+ { gap: t.spacing.md, padding: t.spacing.lg },
475
+ offsetY ? { transform: [{ translateY: offsetY }] } : null
476
+ ],
477
+ children: [
478
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles3.stage, children: [
479
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles3.ring, { borderColor: t.colors.border, borderRadius: t.radius.full }] }),
480
+ /* @__PURE__ */ jsxRuntime.jsx(OrbitDot, { color: t.colors.primary, reduced }),
481
+ /* @__PURE__ */ jsxRuntime.jsx(AnimatedSparkle, { size: 40, variant: "pulse" })
482
+ ] }),
483
+ title ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [headingStyle(t.fonts), { color: t.colors.text, textAlign: "center" }], children: title }) : null,
484
+ hint ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [bodyStyle(t.fonts), { color: t.colors.textMuted, textAlign: "center" }], children: hint }) : null,
485
+ showSkeleton ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles3.skeleton, { gap: t.spacing.sm, marginTop: t.spacing.sm }], children: [
486
+ /* @__PURE__ */ jsxRuntime.jsx(SkeletonLine, { width: "80%", delay: 0, color: t.colors.surface, reduced }),
487
+ /* @__PURE__ */ jsxRuntime.jsx(SkeletonLine, { width: "60%", delay: 200, color: t.colors.surface, reduced })
488
+ ] }) : null
489
+ ]
490
+ }
491
+ );
492
+ };
493
+ var LoadingBlock = React14__default.default.memo(_LoadingBlock);
494
+ var styles3 = reactNative.StyleSheet.create({
495
+ center: { flex: 1, alignItems: "center", justifyContent: "center" },
496
+ stage: {
497
+ width: STAGE_SIZE,
498
+ height: STAGE_SIZE,
499
+ alignItems: "center",
500
+ justifyContent: "center"
501
+ },
502
+ ring: {
503
+ position: "absolute",
504
+ top: 0,
505
+ left: 0,
506
+ right: 0,
507
+ bottom: 0,
508
+ borderWidth: 1.5,
509
+ borderStyle: "dashed"
510
+ },
511
+ orbitDot: {
512
+ position: "absolute",
513
+ top: -ORBIT_DOT / 2 + 1,
514
+ left: STAGE_SIZE / 2 - ORBIT_DOT / 2,
515
+ width: ORBIT_DOT,
516
+ height: ORBIT_DOT,
517
+ borderRadius: ORBIT_DOT / 2
518
+ },
519
+ skeleton: { width: "100%", alignItems: "center" },
520
+ skeletonLine: { height: 12, borderRadius: 6 }
521
+ });
522
+ var _LoadingScreen = ({ title, hint }) => {
523
+ const t = useOnboardingTheme();
524
+ return /* @__PURE__ */ jsxRuntime.jsx(
525
+ reactNativeSafeAreaContext.SafeAreaView,
526
+ {
527
+ edges: ["top", "bottom"],
528
+ style: [styles4.flex, { backgroundColor: t.colors.background }],
529
+ children: /* @__PURE__ */ jsxRuntime.jsx(LoadingBlock, { title, hint })
530
+ }
531
+ );
532
+ };
533
+ var LoadingScreen = React14__default.default.memo(_LoadingScreen);
534
+ var styles4 = reactNative.StyleSheet.create({
535
+ flex: { flex: 1 }
536
+ });
537
+ var easeWire = reactNative.Easing.bezier(...WIRE_BEZIER);
538
+ var DECAY = 0.62;
539
+ var MIN_FILL = 0.08;
540
+ var APPROX_CAP = 0.95;
541
+ var targetFor = (step, complete, approxScreens) => {
542
+ if (complete) return 1;
543
+ const s = Number.isFinite(step) && step > 0 ? Math.floor(step) : 1;
544
+ if (approxScreens && approxScreens > 0) {
545
+ return Math.max(MIN_FILL, Math.min(APPROX_CAP, s / approxScreens));
546
+ }
547
+ return Math.max(MIN_FILL, 1 - DECAY ** s);
548
+ };
549
+ var _StepProgress = ({ step, complete, approxScreens }) => {
550
+ const t = useOnboardingTheme();
551
+ const reduced = useReducedMotion();
552
+ const fill = React14.useRef(new reactNative.Animated.Value(targetFor(step, complete, approxScreens))).current;
553
+ React14.useEffect(() => {
554
+ reactNative.Animated.timing(fill, {
555
+ toValue: targetFor(step, complete, approxScreens),
556
+ duration: scaledMs(stateChangeDuration(reduced, PROGRESS_MS)),
557
+ easing: easeWire,
558
+ useNativeDriver: false
559
+ // animating width %
560
+ }).start();
561
+ }, [fill, step, complete, approxScreens, reduced]);
562
+ const width = fill.interpolate({ inputRange: [0, 1], outputRange: ["0%", "100%"] });
563
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles5.track, { backgroundColor: t.colors.border, borderRadius: t.radius.full }], children: /* @__PURE__ */ jsxRuntime.jsx(
564
+ reactNative.Animated.View,
565
+ {
566
+ style: [
567
+ styles5.fill,
568
+ { width, backgroundColor: t.colors.primary, borderRadius: t.radius.full }
569
+ ]
570
+ }
571
+ ) });
572
+ };
573
+ var StepProgress = React14__default.default.memo(_StepProgress);
574
+ var styles5 = reactNative.StyleSheet.create({
575
+ track: { height: 6, width: "100%", overflow: "hidden" },
576
+ fill: { height: 6 }
577
+ });
578
+ var PROGRESS_TRACK = 6;
579
+ var _OnboardingScaffold = ({
580
+ step,
581
+ complete,
582
+ approxScreens,
583
+ onBack,
584
+ onSkip,
585
+ skipLabel = "Skip",
586
+ backLabel = "Back",
587
+ children
588
+ }) => {
589
+ const t = useOnboardingTheme();
590
+ const hit = { top: 12, bottom: 12, left: 12, right: 12 };
591
+ const topChrome = t.spacing.md + PROGRESS_TRACK + t.spacing.md;
592
+ const loaderOffsetY = -Math.round(topChrome / 2);
593
+ return /* @__PURE__ */ jsxRuntime.jsxs(
594
+ reactNativeSafeAreaContext.SafeAreaView,
595
+ {
596
+ edges: ["top", "bottom"],
597
+ style: [styles6.flex, { backgroundColor: t.colors.background }],
598
+ children: [
599
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles6.header, { paddingHorizontal: t.spacing.md, paddingTop: t.spacing.md }], children: [
600
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles6.progressWrap, children: /* @__PURE__ */ jsxRuntime.jsx(StepProgress, { step, complete, approxScreens }) }),
601
+ onSkip ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.TouchableOpacity, { onPress: onSkip, hitSlop: hit, style: { marginLeft: t.spacing.md }, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [bodyStyle(t.fonts), { color: t.colors.textMuted }], children: skipLabel }) }) : null
602
+ ] }),
603
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles6.body, { paddingHorizontal: t.spacing.md, paddingTop: t.spacing.md }], children: /* @__PURE__ */ jsxRuntime.jsx(LoaderChromeProvider, { value: { offsetY: loaderOffsetY }, children }) }),
604
+ onBack ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles6.footer, { paddingHorizontal: t.spacing.md, paddingBottom: t.spacing.sm }], children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.TouchableOpacity, { onPress: onBack, hitSlop: hit, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [bodyStyle(t.fonts), { color: t.colors.textMuted }], children: backLabel }) }) }) : null
605
+ ]
606
+ }
607
+ );
608
+ };
609
+ var OnboardingScaffold = React14__default.default.memo(_OnboardingScaffold);
610
+ var styles6 = reactNative.StyleSheet.create({
611
+ flex: { flex: 1 },
612
+ header: { flexDirection: "row", alignItems: "center" },
613
+ progressWrap: { flex: 1 },
614
+ body: { flex: 1 },
615
+ footer: { flexDirection: "row", justifyContent: "flex-start" }
616
+ });
617
+ var _Button = ({
618
+ title,
619
+ onPress,
620
+ variant = "primary",
621
+ disabled = false,
622
+ fullWidth = false
623
+ }) => {
624
+ const t = useOnboardingTheme();
625
+ const reduced = useReducedMotion();
626
+ const isPrimary = variant === "primary";
627
+ const pressT = React14.useRef(new reactNative.Animated.Value(0)).current;
628
+ const pressTo = React14.useCallback(
629
+ (to) => {
630
+ if (reduced) {
631
+ reactNative.Animated.timing(pressT, {
632
+ toValue: to,
633
+ duration: scaledMs(REDUCED_FADE_MS),
634
+ useNativeDriver: true
635
+ }).start();
636
+ return;
637
+ }
638
+ reactNative.Animated.spring(pressT, {
639
+ toValue: to,
640
+ friction: BUTTON_PRESS_SPRING.friction,
641
+ tension: BUTTON_PRESS_SPRING.tension,
642
+ useNativeDriver: true
643
+ }).start();
644
+ },
645
+ [pressT, reduced]
646
+ );
647
+ const handlePressIn = React14.useCallback(() => pressTo(1), [pressTo]);
648
+ const handlePressOut = React14.useCallback(() => pressTo(0), [pressTo]);
649
+ const scale = pressT.interpolate({
650
+ inputRange: [0, 1],
651
+ outputRange: [1, reduced ? 1 : BUTTON_PRESS_SCALE]
652
+ });
653
+ const opacity = pressT.interpolate({
654
+ inputRange: [0, 1],
655
+ outputRange: [1, reduced ? REDUCED_PRESS_OPACITY : 1]
656
+ });
657
+ return /* @__PURE__ */ jsxRuntime.jsx(
658
+ reactNative.Pressable,
659
+ {
660
+ onPress: disabled ? void 0 : onPress,
661
+ onPressIn: disabled ? void 0 : handlePressIn,
662
+ onPressOut: disabled ? void 0 : handlePressOut,
663
+ disabled,
664
+ accessibilityRole: "button",
665
+ accessibilityState: { disabled },
666
+ style: fullWidth ? styles7.fill : void 0,
667
+ children: /* @__PURE__ */ jsxRuntime.jsx(
668
+ reactNative.Animated.View,
669
+ {
670
+ style: [
671
+ styles7.base,
672
+ fullWidth && styles7.fill,
673
+ {
674
+ borderRadius: t.button.radius,
675
+ minHeight: t.button.height,
676
+ paddingHorizontal: t.button.paddingX,
677
+ opacity,
678
+ transform: [{ scale }]
679
+ },
680
+ isPrimary ? { backgroundColor: disabled ? t.colors.disabled : t.colors.primary } : { borderWidth: 1.5, borderColor: disabled ? t.colors.border : t.colors.primary }
681
+ ],
682
+ children: /* @__PURE__ */ jsxRuntime.jsx(
683
+ reactNative.Text,
684
+ {
685
+ style: [
686
+ buttonLabelStyle(t.fonts),
687
+ {
688
+ color: disabled ? t.colors.textMuted : isPrimary ? t.colors.onPrimary : t.colors.primary
689
+ }
690
+ ],
691
+ children: title
692
+ }
693
+ )
694
+ }
695
+ )
696
+ }
697
+ );
698
+ };
699
+ var Button = React14__default.default.memo(_Button);
700
+ var styles7 = reactNative.StyleSheet.create({
701
+ base: { alignItems: "center", justifyContent: "center" },
702
+ fill: { width: "100%" }
703
+ });
704
+ var _ErrorBlock = ({
705
+ title = "Something went wrong",
706
+ message = "We couldn't load this step. You can try again.",
707
+ retryLabel = "Try again",
708
+ onRetry
709
+ }) => {
710
+ const t = useOnboardingTheme();
711
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles8.center, { gap: t.spacing.md, padding: t.spacing.lg }], children: [
712
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [headingStyle(t.fonts), { color: t.colors.text, textAlign: "center" }], children: title }),
713
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [bodyStyle(t.fonts), { color: t.colors.textMuted, textAlign: "center" }], children: message }),
714
+ onRetry ? /* @__PURE__ */ jsxRuntime.jsx(Button, { title: retryLabel, onPress: onRetry, variant: "outline" }) : null
715
+ ] });
716
+ };
717
+ var ErrorBlock = React14__default.default.memo(_ErrorBlock);
718
+ var styles8 = reactNative.StyleSheet.create({
719
+ center: { flex: 1, alignItems: "center", justifyContent: "center" }
720
+ });
721
+ var _CardLayout = ({
722
+ title,
723
+ subtitle,
724
+ children,
725
+ footer,
726
+ align = "top",
727
+ titleNode
728
+ }) => {
729
+ const t = useOnboardingTheme();
730
+ const hasHeader = !!title || !!subtitle || !!titleNode;
731
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.KeyboardAvoidingView, { style: styles9.fill, behavior: "padding", children: [
732
+ hasHeader ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { marginBottom: t.spacing.lg }, children: [
733
+ titleNode != null ? titleNode : title ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [labelStyle(t.fonts), { color: t.colors.text }], children: title }) : null,
734
+ subtitle ? /* @__PURE__ */ jsxRuntime.jsx(
735
+ reactNative.Text,
736
+ {
737
+ style: [
738
+ bodyStyle(t.fonts),
739
+ { color: t.colors.textMuted, marginTop: t.spacing.xs }
740
+ ],
741
+ children: subtitle
742
+ }
743
+ ) : null
744
+ ] }) : null,
745
+ /* @__PURE__ */ jsxRuntime.jsx(
746
+ reactNative.ScrollView,
747
+ {
748
+ style: styles9.fill,
749
+ contentContainerStyle: [
750
+ styles9.scrollContent,
751
+ align === "center" && styles9.center
752
+ ],
753
+ keyboardShouldPersistTaps: "handled",
754
+ keyboardDismissMode: "on-drag",
755
+ showsVerticalScrollIndicator: false,
756
+ children
757
+ }
758
+ ),
759
+ footer ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { paddingTop: t.spacing.lg }, children: footer }) : null
760
+ ] });
761
+ };
762
+ var CardLayout = React14__default.default.memo(_CardLayout);
763
+ var styles9 = reactNative.StyleSheet.create({
764
+ fill: { flex: 1 },
765
+ // flexGrow lets a short content block grow to fill (pinning the footer) while a
766
+ // tall one scrolls past the visible area.
767
+ scrollContent: { flexGrow: 1 },
768
+ center: { justifyContent: "center" }
769
+ });
770
+ var easeWire2 = reactNative.Easing.bezier(...WIRE_BEZIER);
771
+ var BADGE_SIZE = 64;
772
+ var BURST_SIZE = 104;
773
+ var _CompletionView = ({
774
+ title,
775
+ message,
776
+ ctaLabel = "Let's go",
777
+ onContinue
778
+ }) => {
779
+ const t = useOnboardingTheme();
780
+ const reduced = useReducedMotion();
781
+ const [submitted, setSubmitted] = React14.useState(false);
782
+ const pop = React14.useRef(new reactNative.Animated.Value(reduced ? 1 : 0)).current;
783
+ const burst = React14.useRef(new reactNative.Animated.Value(reduced ? 1 : 0)).current;
784
+ const titleT = React14.useRef(new reactNative.Animated.Value(reduced ? 1 : 0)).current;
785
+ const messageT = React14.useRef(new reactNative.Animated.Value(reduced ? 1 : 0)).current;
786
+ React14.useEffect(() => {
787
+ if (reduced) {
788
+ pop.setValue(1);
789
+ burst.setValue(1);
790
+ titleT.setValue(1);
791
+ messageT.setValue(1);
792
+ return;
793
+ }
794
+ const rise = (value, delay) => reactNative.Animated.timing(value, {
795
+ toValue: 1,
796
+ duration: scaledMs(STATUS_COPY_MS),
797
+ delay,
798
+ easing: easeWire2,
799
+ useNativeDriver: true
800
+ });
801
+ const anims = [
802
+ reactNative.Animated.spring(pop, {
803
+ toValue: 1,
804
+ friction: STATUS_POP_SPRING.friction,
805
+ tension: STATUS_POP_SPRING.tension,
806
+ useNativeDriver: true
807
+ }),
808
+ // One-shot celebratory burst: fires once, never loops.
809
+ reactNative.Animated.timing(burst, {
810
+ toValue: 1,
811
+ duration: scaledMs(STATUS_BURST_MS),
812
+ easing: reactNative.Easing.out(reactNative.Easing.ease),
813
+ useNativeDriver: true
814
+ }),
815
+ rise(titleT, scaledMs(statusCopyDelay(0))),
816
+ rise(messageT, scaledMs(statusCopyDelay(1)))
817
+ ];
818
+ anims.forEach((a) => a.start());
819
+ return () => anims.forEach((a) => a.stop());
820
+ }, [reduced, pop, burst, titleT, messageT]);
821
+ const handlePress = React14.useCallback(() => {
822
+ if (submitted) return;
823
+ setSubmitted(true);
824
+ onContinue();
825
+ }, [submitted, onContinue]);
826
+ const scale = pop.interpolate({ inputRange: [0, 1], outputRange: [STATUS_POP_SCALE_FROM, 1] });
827
+ const burstScale = burst.interpolate({
828
+ inputRange: [0, 1],
829
+ outputRange: [STATUS_BURST_SCALE_FROM, STATUS_BURST_SCALE_TO]
830
+ });
831
+ const burstOpacity = burst.interpolate({
832
+ inputRange: [0, 1],
833
+ outputRange: [STATUS_BURST_OPACITY_FROM, 0]
834
+ });
835
+ const riseStyle = (value) => ({
836
+ opacity: value,
837
+ transform: [{ translateY: value.interpolate({ inputRange: [0, 1], outputRange: [10, 0] }) }]
838
+ });
839
+ return /* @__PURE__ */ jsxRuntime.jsx(
840
+ CardLayout,
841
+ {
842
+ align: "center",
843
+ footer: /* @__PURE__ */ jsxRuntime.jsx(Button, { title: ctaLabel, onPress: handlePress, variant: "primary", disabled: submitted }),
844
+ children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles10.center, { gap: t.spacing.md }], children: [
845
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles10.badgeStage, children: [
846
+ /* @__PURE__ */ jsxRuntime.jsx(
847
+ reactNative.Animated.View,
848
+ {
849
+ pointerEvents: "none",
850
+ style: [
851
+ styles10.burst,
852
+ {
853
+ backgroundColor: t.colors.primarySoft,
854
+ borderRadius: t.radius.full,
855
+ opacity: burstOpacity,
856
+ transform: [{ scale: burstScale }]
857
+ }
858
+ ]
859
+ }
860
+ ),
861
+ /* @__PURE__ */ jsxRuntime.jsx(
862
+ reactNative.Animated.View,
863
+ {
864
+ style: [
865
+ styles10.badge,
866
+ {
867
+ backgroundColor: t.colors.primarySoft,
868
+ borderRadius: t.radius.full,
869
+ transform: [{ scale }],
870
+ opacity: pop
871
+ }
872
+ ],
873
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles10.check, { color: t.colors.primary }], children: "\u2713" })
874
+ }
875
+ )
876
+ ] }),
877
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Animated.View, { style: riseStyle(titleT), children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [headingStyle(t.fonts), { color: t.colors.text, textAlign: "center" }], children: title }) }),
878
+ message ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Animated.View, { style: riseStyle(messageT), children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [bodyStyle(t.fonts), { color: t.colors.textMuted, textAlign: "center" }], children: message }) }) : null
879
+ ] })
880
+ }
881
+ );
882
+ };
883
+ var CompletionView = React14__default.default.memo(_CompletionView);
884
+ var styles10 = reactNative.StyleSheet.create({
885
+ center: { width: "100%", alignItems: "center", justifyContent: "center" },
886
+ badgeStage: {
887
+ width: BURST_SIZE,
888
+ height: BURST_SIZE,
889
+ alignItems: "center",
890
+ justifyContent: "center"
891
+ },
892
+ burst: { position: "absolute", width: BURST_SIZE, height: BURST_SIZE },
893
+ badge: { width: BADGE_SIZE, height: BADGE_SIZE, alignItems: "center", justifyContent: "center" },
894
+ check: { fontSize: 34, fontWeight: "800" }
895
+ });
896
+ var easeWire3 = reactNative.Easing.bezier(...WIRE_BEZIER);
897
+ var _CardHandoff = ({
898
+ transitionKey,
899
+ variant = "slide",
900
+ children
901
+ }) => {
902
+ const reduced = useReducedMotion();
903
+ const [leaving, setLeaving] = React14.useState(null);
904
+ const enterT = React14.useRef(new reactNative.Animated.Value(0)).current;
905
+ const exitT = React14.useRef(new reactNative.Animated.Value(0)).current;
906
+ const prev = React14.useRef({ key: transitionKey, node: children, variant });
907
+ const [enterVariant, setEnterVariant] = React14.useState(variant);
908
+ const running = React14.useRef({});
909
+ const mounted = React14.useRef(false);
910
+ React14.useEffect(() => {
911
+ var _a, _b;
912
+ const last = prev.current;
913
+ prev.current = { key: transitionKey, node: children, variant };
914
+ if (!mounted.current) {
915
+ mounted.current = true;
916
+ setEnterVariant(variant);
917
+ running.current.enter = startEnter(enterT, variant, reduced, 0);
918
+ return;
919
+ }
920
+ if (last.key === transitionKey) return;
921
+ (_a = running.current.enter) == null ? void 0 : _a.stop();
922
+ (_b = running.current.exit) == null ? void 0 : _b.stop();
923
+ setLeaving(last);
924
+ setEnterVariant(variant);
925
+ enterT.setValue(0);
926
+ exitT.setValue(0);
927
+ const exitDuration = reduced ? REDUCED_FADE_MS : last.variant === "fade" ? LOADER_HANDOFF_FADE_MS : CARD_EXIT_MS;
928
+ const exit = reactNative.Animated.timing(exitT, {
929
+ toValue: 1,
930
+ duration: scaledMs(exitDuration),
931
+ easing: easeWire3,
932
+ useNativeDriver: true
933
+ });
934
+ running.current.exit = exit;
935
+ exit.start();
936
+ const enter = startEnter(
937
+ enterT,
938
+ variant,
939
+ reduced,
940
+ reduced ? 0 : CARD_EXIT_LEAD_MS,
941
+ () => setLeaving(null)
942
+ );
943
+ running.current.enter = enter;
944
+ });
945
+ React14.useEffect(
946
+ () => () => {
947
+ var _a, _b;
948
+ (_a = running.current.enter) == null ? void 0 : _a.stop();
949
+ (_b = running.current.exit) == null ? void 0 : _b.stop();
950
+ },
951
+ []
952
+ );
953
+ const enterStyle = {
954
+ opacity: enterT,
955
+ transform: transformFor(enterVariant, enterT, reduced, "enter")
956
+ };
957
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
958
+ leaving ? /* @__PURE__ */ jsxRuntime.jsx(
959
+ reactNative.Animated.View,
960
+ {
961
+ pointerEvents: "none",
962
+ style: [
963
+ reactNative.StyleSheet.absoluteFill,
964
+ {
965
+ opacity: exitT.interpolate({ inputRange: [0, 1], outputRange: [1, 0] }),
966
+ transform: transformFor(leaving.variant, exitT, reduced, "exit")
967
+ }
968
+ ],
969
+ children: leaving.node
970
+ }
971
+ ) : null,
972
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Animated.View, { style: [styles11.fill, enterStyle], children })
973
+ ] });
974
+ };
975
+ var startEnter = (value, variant, reduced, delay, onDone) => {
976
+ value.setValue(0);
977
+ const anim = !reduced && variant === "spring" ? reactNative.Animated.spring(value, {
978
+ toValue: 1,
979
+ delay: scaledMs(delay),
980
+ damping: CARD_SPRING.damping,
981
+ stiffness: CARD_SPRING.stiffness,
982
+ mass: CARD_SPRING.mass,
983
+ useNativeDriver: true
984
+ }) : reactNative.Animated.timing(value, {
985
+ toValue: 1,
986
+ delay: scaledMs(delay),
987
+ duration: scaledMs(
988
+ reduced ? REDUCED_FADE_MS : variant === "fade" ? LOADER_HANDOFF_FADE_MS : CARD_ENTER_MS
989
+ ),
990
+ easing: easeWire3,
991
+ useNativeDriver: true
992
+ });
993
+ anim.start(({ finished }) => {
994
+ if (finished) onDone == null ? void 0 : onDone();
995
+ });
996
+ return anim;
997
+ };
998
+ var transformFor = (variant, t, reduced, direction) => {
999
+ if (reduced || variant === "fade") return [];
1000
+ if (variant === "spring" && direction === "enter") {
1001
+ return [{ scale: t.interpolate({ inputRange: [0, 1], outputRange: [CARD_ENTER_SCALE_FROM, 1] }) }];
1002
+ }
1003
+ return [
1004
+ {
1005
+ translateX: t.interpolate({
1006
+ inputRange: [0, 1],
1007
+ outputRange: direction === "enter" ? [CARD_SLIDE_PX, 0] : [0, -CARD_SLIDE_PX]
1008
+ })
1009
+ }
1010
+ ];
1011
+ };
1012
+ var CardHandoff = React14__default.default.memo(_CardHandoff);
1013
+ var styles11 = reactNative.StyleSheet.create({
1014
+ fill: { flex: 1 }
1015
+ });
1016
+
1017
+ // src/utils/deriveAnswers.ts
1018
+ var stripPrefix = (s) => s.replace(/^(I selected:|My answer is:|I tapped:)\s*/i, "").trim();
1019
+ var tryParse = (s) => {
1020
+ try {
1021
+ return JSON.parse(s);
1022
+ } catch {
1023
+ return s;
1024
+ }
1025
+ };
1026
+ var deriveAnswers = (messages) => {
1027
+ var _a, _b, _c;
1028
+ const out = {};
1029
+ for (let i = 0; i < messages.length; i++) {
1030
+ const m = messages[i];
1031
+ const r = m == null ? void 0 : m.response;
1032
+ if (!m || m.role !== "assistant" || !r || r.action !== "render") continue;
1033
+ const props = (_a = r.props) != null ? _a : {};
1034
+ const progress = props.progress;
1035
+ const questionText = (_c = (_b = props.title) != null ? _b : props.label) != null ? _c : props.question;
1036
+ const key = (progress == null ? void 0 : progress.key) || questionText;
1037
+ const reply = messages[i + 1];
1038
+ if (key && (reply == null ? void 0 : reply.role) === "user") {
1039
+ out[key] = tryParse(stripPrefix(reply.content));
1040
+ }
1041
+ }
1042
+ return out;
1043
+ };
1044
+
1045
+ // src/utils/readProgress.ts
1046
+ var readProgress = (response) => {
1047
+ var _a;
1048
+ if (!response || response.action !== "render") return {};
1049
+ const props = (_a = response.props) != null ? _a : {};
1050
+ const raw = props.progress;
1051
+ if (!raw || typeof raw !== "object") return {};
1052
+ const p = raw;
1053
+ return {
1054
+ step: typeof p.step === "number" ? p.step : void 0,
1055
+ total: typeof p.total === "number" ? p.total : void 0,
1056
+ key: typeof p.key === "string" ? p.key : void 0,
1057
+ skippable: typeof p.skippable === "boolean" ? p.skippable : void 0
1058
+ };
1059
+ };
1060
+
1061
+ // src/analytics/reportClientEvent.ts
1062
+ var makeSessionId = () => `wire_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
1063
+ var reportClientEvents = (target, events) => {
1064
+ if (!(target == null ? void 0 : target.serverUrl) || events.length === 0) return;
1065
+ try {
1066
+ const url = `${target.serverUrl.replace(/\/$/, "")}/v1/events`;
1067
+ const headers = { "Content-Type": "application/json" };
1068
+ if (target.apiKey) headers.Authorization = `Bearer ${target.apiKey}`;
1069
+ void fetch(url, {
1070
+ method: "POST",
1071
+ headers,
1072
+ body: JSON.stringify({ events })
1073
+ }).catch(() => {
1074
+ });
1075
+ } catch {
1076
+ }
1077
+ };
1078
+ var reportClientEvent = (target, event) => reportClientEvents(target, [event]);
1079
+
1080
+ // src/analytics/sendPreview.ts
1081
+ var sendPreview = (target, { sessionId, userMessage }) => {
1082
+ if (!(target == null ? void 0 : target.serverUrl)) return;
1083
+ try {
1084
+ const url = `${target.serverUrl.replace(/\/$/, "")}/a2a`;
1085
+ const headers = { "Content-Type": "application/json" };
1086
+ if (target.apiKey) headers.Authorization = `Bearer ${target.apiKey}`;
1087
+ void fetch(url, {
1088
+ method: "POST",
1089
+ headers,
1090
+ body: JSON.stringify({
1091
+ jsonrpc: "2.0",
1092
+ id: Math.floor(Math.random() * 1e9),
1093
+ method: "message/preview",
1094
+ params: {
1095
+ message: { role: "user", parts: [{ text: userMessage }] },
1096
+ contextId: sessionId,
1097
+ metadata: { sessionId }
1098
+ }
1099
+ })
1100
+ }).catch(() => {
1101
+ });
1102
+ } catch {
1103
+ }
1104
+ };
1105
+ var SKIP_ONE_SENTINEL = "__wireai_skip__";
1106
+ var ANSWER_CALLBACKS = ["onSubmit", "onSelect", "onConfirm", "onPress"];
1107
+ var DEFAULT_COPY = {
1108
+ startingTitle: "Getting started\u2026",
1109
+ startingHint: "Setting up your onboarding",
1110
+ restoringTitle: "Picking up where you left off\u2026",
1111
+ restoringHint: "Restoring your session",
1112
+ thinkingTitle: "Personalizing\u2026",
1113
+ thinkingHint: "Tailoring your next question",
1114
+ checkingTitle: "Checking\u2026",
1115
+ checkingHint: "One moment",
1116
+ completeTitle: "You're all set",
1117
+ completeCta: "Let's go"
1118
+ };
1119
+ var OnboardingFlow = ({
1120
+ validators,
1121
+ onComplete,
1122
+ onError,
1123
+ onEvent,
1124
+ copy,
1125
+ approxScreens,
1126
+ startMessage,
1127
+ startTimeoutMs,
1128
+ fallbackFlow,
1129
+ maxRetries,
1130
+ reportTarget,
1131
+ sessionId,
1132
+ resumed = false,
1133
+ clientContext
1134
+ }) => {
1135
+ var _a, _b, _c, _d, _e, _f;
1136
+ const { messages, error, isLoading, sendMessage, reset } = wireaiRn.useWireAIThread();
1137
+ const makeActions = wireaiRn.useWireAIAction(sendMessage);
1138
+ const c = React14.useMemo(() => ({ ...DEFAULT_COPY, ...copy }), [copy]);
1139
+ const onEventRef = React14.useRef(onEvent);
1140
+ onEventRef.current = onEvent;
1141
+ const onErrorRef = React14.useRef(onError);
1142
+ onErrorRef.current = onError;
1143
+ const reportTargetRef = React14.useRef(reportTarget);
1144
+ reportTargetRef.current = reportTarget;
1145
+ const sessionIdRef = React14.useRef(sessionId);
1146
+ sessionIdRef.current = sessionId;
1147
+ const clientContextRef = React14.useRef(clientContext);
1148
+ clientContextRef.current = clientContext;
1149
+ const lastScreenIndexRef = React14.useRef(-1);
1150
+ const previewTimer = React14.useRef(null);
1151
+ const started = React14.useRef(false);
1152
+ const finished = React14.useRef(false);
1153
+ const errored = React14.useRef(false);
1154
+ const attempts = React14.useRef(0);
1155
+ const degradedRef = React14.useRef(false);
1156
+ const [degraded, setDegraded] = React14.useState(false);
1157
+ const [timedOut, setTimedOut] = React14.useState(false);
1158
+ const [validationError, setValidationError] = React14.useState();
1159
+ const [validating, setValidating] = React14.useState(false);
1160
+ const lastCard = React14.useMemo(
1161
+ () => [...messages].reverse().find((m) => {
1162
+ var _a2;
1163
+ return m.role === "assistant" && ((_a2 = m.response) == null ? void 0 : _a2.action) === "render";
1164
+ }),
1165
+ [messages]
1166
+ );
1167
+ const renderedCount = React14.useMemo(
1168
+ () => messages.filter((m) => {
1169
+ var _a2;
1170
+ return m.role === "assistant" && ((_a2 = m.response) == null ? void 0 : _a2.action) === "render";
1171
+ }).length,
1172
+ [messages]
1173
+ );
1174
+ const progress = React14.useMemo(() => readProgress(lastCard == null ? void 0 : lastCard.response), [lastCard]);
1175
+ React14.useEffect(() => {
1176
+ var _a2;
1177
+ if (started.current) return;
1178
+ started.current = true;
1179
+ sendMessage(resumed ? SKIP_ONE_SENTINEL : startMessage);
1180
+ (_a2 = onEventRef.current) == null ? void 0 : _a2.call(
1181
+ onEventRef,
1182
+ resumed ? { type: "resumed", contextId: sessionId } : { type: "started", contextId: sessionId }
1183
+ );
1184
+ }, [sendMessage, startMessage, resumed, sessionId]);
1185
+ React14.useEffect(() => {
1186
+ if (lastCard) return;
1187
+ const timer = setTimeout(() => setTimedOut(true), startTimeoutMs);
1188
+ return () => clearTimeout(timer);
1189
+ }, [lastCard, startTimeoutMs]);
1190
+ React14.useEffect(() => {
1191
+ var _a2, _b2, _c2, _d2;
1192
+ setValidationError(void 0);
1193
+ if (!(lastCard == null ? void 0 : lastCard.id)) return;
1194
+ const step2 = (_a2 = progress.step) != null ? _a2 : renderedCount;
1195
+ lastScreenIndexRef.current = Math.max(0, step2 - 1);
1196
+ (_c2 = onEventRef.current) == null ? void 0 : _c2.call(onEventRef, {
1197
+ type: "turn",
1198
+ step: step2,
1199
+ component: (_b2 = lastCard.response) == null ? void 0 : _b2.component
1200
+ });
1201
+ if (((_d2 = lastCard.response) == null ? void 0 : _d2.component) === "InterstitialCard") {
1202
+ sendPreview(reportTargetRef.current, {
1203
+ sessionId: sessionIdRef.current,
1204
+ userMessage: `My answer is: ${JSON.stringify("continue")}`
1205
+ });
1206
+ }
1207
+ }, [lastCard == null ? void 0 : lastCard.id]);
1208
+ React14.useEffect(() => {
1209
+ return () => {
1210
+ var _a2, _b2, _c2;
1211
+ if (finished.current || degradedRef.current) return;
1212
+ reportClientEvent(reportTargetRef.current, {
1213
+ event_type: "dropped",
1214
+ session_id: sessionIdRef.current,
1215
+ screen_index: lastScreenIndexRef.current >= 0 ? lastScreenIndexRef.current : void 0,
1216
+ device: (_a2 = clientContextRef.current) == null ? void 0 : _a2.device,
1217
+ user_context: (_b2 = clientContextRef.current) == null ? void 0 : _b2.userContext,
1218
+ user_id: (_c2 = clientContextRef.current) == null ? void 0 : _c2.userId
1219
+ });
1220
+ };
1221
+ }, []);
1222
+ const restartThread = React14.useCallback(() => {
1223
+ setTimedOut(false);
1224
+ started.current = false;
1225
+ errored.current = false;
1226
+ reset();
1227
+ }, [reset]);
1228
+ React14.useEffect(() => {
1229
+ var _a2, _b2, _c2, _d2, _e2, _f2, _g;
1230
+ if (errored.current) return;
1231
+ if (!error && !timedOut) return;
1232
+ errored.current = true;
1233
+ const reason = error ? "backend" : "timeout";
1234
+ (_a2 = onEventRef.current) == null ? void 0 : _a2.call(onEventRef, { type: "error", reason });
1235
+ attempts.current += 1;
1236
+ if (attempts.current <= maxRetries) {
1237
+ (_b2 = onEventRef.current) == null ? void 0 : _b2.call(onEventRef, { type: "retry", reason, attempt: attempts.current });
1238
+ restartThread();
1239
+ } else {
1240
+ (_c2 = onEventRef.current) == null ? void 0 : _c2.call(onEventRef, { type: "fallback", reason });
1241
+ reportClientEvent(reportTargetRef.current, {
1242
+ event_type: "client_fallback",
1243
+ session_id: sessionIdRef.current,
1244
+ screen_index: lastScreenIndexRef.current >= 0 ? lastScreenIndexRef.current : void 0,
1245
+ meta: JSON.stringify({ reason }),
1246
+ device: (_d2 = clientContextRef.current) == null ? void 0 : _d2.device,
1247
+ user_context: (_e2 = clientContextRef.current) == null ? void 0 : _e2.userContext,
1248
+ user_id: (_f2 = clientContextRef.current) == null ? void 0 : _f2.userId
1249
+ });
1250
+ degradedRef.current = true;
1251
+ setDegraded(true);
1252
+ (_g = onErrorRef.current) == null ? void 0 : _g.call(onErrorRef, error != null ? error : new Error("Onboarding timed out"));
1253
+ }
1254
+ }, [error, timedOut, maxRetries]);
1255
+ const handleFinish = React14.useCallback(() => {
1256
+ if (finished.current) return;
1257
+ finished.current = true;
1258
+ onComplete({ answers: deriveAnswers(messages), raw: messages });
1259
+ }, [messages, onComplete]);
1260
+ const handleRetry = React14.useCallback(() => {
1261
+ attempts.current = 0;
1262
+ degradedRef.current = false;
1263
+ setDegraded(false);
1264
+ restartThread();
1265
+ }, [restartThread]);
1266
+ const skipQuestion = React14.useCallback(() => {
1267
+ if (isLoading) return;
1268
+ sendMessage(SKIP_ONE_SENTINEL);
1269
+ }, [isLoading, sendMessage]);
1270
+ const previewOnSelect = React14.useCallback((value) => {
1271
+ const target = reportTargetRef.current;
1272
+ if (!(target == null ? void 0 : target.serverUrl)) return;
1273
+ if (Array.isArray(value) ? value.length === 0 : !value) return;
1274
+ if (previewTimer.current) clearTimeout(previewTimer.current);
1275
+ previewTimer.current = setTimeout(() => {
1276
+ sendPreview(target, {
1277
+ sessionId: sessionIdRef.current,
1278
+ userMessage: `I selected: ${JSON.stringify(value)}`
1279
+ });
1280
+ }, 300);
1281
+ }, []);
1282
+ React14.useEffect(() => () => {
1283
+ if (previewTimer.current) clearTimeout(previewTimer.current);
1284
+ }, []);
1285
+ const wrappedActions = React14.useCallback(
1286
+ (messageId) => {
1287
+ const base = makeActions(messageId);
1288
+ const key = progress.key;
1289
+ const validator = key ? validators == null ? void 0 : validators[key] : void 0;
1290
+ if (!validator) return base;
1291
+ const out = { ...base };
1292
+ for (const name of ANSWER_CALLBACKS) {
1293
+ const original = base[name];
1294
+ if (!original) continue;
1295
+ out[name] = (...args) => {
1296
+ const value = args[0];
1297
+ if (typeof value !== "string") return original(...args);
1298
+ setValidating(true);
1299
+ validator(value).then((res) => {
1300
+ var _a2;
1301
+ if (res.ok) {
1302
+ setValidationError(void 0);
1303
+ original(...args);
1304
+ } else {
1305
+ setValidationError((_a2 = res.error) != null ? _a2 : "That value isn't available.");
1306
+ }
1307
+ }).catch(() => {
1308
+ original(...args);
1309
+ }).finally(() => setValidating(false));
1310
+ };
1311
+ }
1312
+ return out;
1313
+ },
1314
+ [makeActions, progress.key, validators]
1315
+ );
1316
+ if (degraded) {
1317
+ if (fallbackFlow !== void 0) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: fallbackFlow });
1318
+ if (onError) {
1319
+ return /* @__PURE__ */ jsxRuntime.jsx(LoadingScreen, { title: c.startingTitle, hint: c.startingHint });
1320
+ }
1321
+ return /* @__PURE__ */ jsxRuntime.jsx(
1322
+ ErrorBlock,
1323
+ {
1324
+ onRetry: handleRetry,
1325
+ message: timedOut ? "This is taking longer than expected. Try again?" : "We couldn't reach the onboarding service. Try again?"
1326
+ }
1327
+ );
1328
+ }
1329
+ if (error || timedOut) {
1330
+ return /* @__PURE__ */ jsxRuntime.jsx(LoadingScreen, { title: c.startingTitle, hint: c.startingHint });
1331
+ }
1332
+ const response = lastCard == null ? void 0 : lastCard.response;
1333
+ if (!lastCard || !response) {
1334
+ return /* @__PURE__ */ jsxRuntime.jsx(LoadingScreen, { title: c.startingTitle, hint: c.startingHint });
1335
+ }
1336
+ const step = (_a = progress.step) != null ? _a : renderedCount;
1337
+ const total = (_b = progress.total) != null ? _b : approxScreens;
1338
+ const isTerminal = response.action === "render" && response.component === "StatusCard";
1339
+ if (isTerminal) {
1340
+ const props = (_c = response.props) != null ? _c : {};
1341
+ return /* @__PURE__ */ jsxRuntime.jsx(OnboardingScaffold, { step, complete: true, approxScreens: total, children: /* @__PURE__ */ jsxRuntime.jsx(
1342
+ CompletionView,
1343
+ {
1344
+ title: (_d = props.title) != null ? _d : c.completeTitle,
1345
+ message: props.message,
1346
+ ctaLabel: (_e = props.ctaLabel) != null ? _e : c.completeCta,
1347
+ onContinue: handleFinish
1348
+ }
1349
+ ) });
1350
+ }
1351
+ const overrides = {
1352
+ ...wrappedActions(lastCard.id),
1353
+ // Inject the inline error onto whichever card supports it (TextInputCard).
1354
+ validationError,
1355
+ // Choice cards (Selection/ChipSelect) call this as the live selection changes, so the flow
1356
+ // can prefetch the next turn before Continue. Non-committing; ignored by cards that don't use it.
1357
+ onSelectionChange: previewOnSelect
1358
+ };
1359
+ return /* @__PURE__ */ jsxRuntime.jsx(
1360
+ OnboardingScaffold,
1361
+ {
1362
+ step,
1363
+ onSkip: progress.skippable ? skipQuestion : void 0,
1364
+ approxScreens: total,
1365
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1366
+ CardHandoff,
1367
+ {
1368
+ transitionKey: validating ? "loader:checking" : isLoading ? "loader:thinking" : lastCard.id,
1369
+ variant: validating || isLoading ? "fade" : ((_f = lastCard.response) == null ? void 0 : _f.component) === "InterstitialCard" ? "spring" : "slide",
1370
+ children: validating ? /* @__PURE__ */ jsxRuntime.jsx(LoadingBlock, { title: c.checkingTitle, hint: c.checkingHint }) : isLoading ? (
1371
+ // Between turns: the answer was sent and we're waiting on the next card.
1372
+ // Show the "thinking" loader instead of leaving the old card on screen.
1373
+ /* @__PURE__ */ jsxRuntime.jsx(LoadingBlock, { title: c.thinkingTitle, hint: c.thinkingHint })
1374
+ ) : (
1375
+ // key={lastCard.id} is load-bearing: cards (SelectionCard/ChipSelectCard)
1376
+ // hold internal `selected`/`submitted` state, and React reuses a component
1377
+ // instance across turns when the rendered type is identical (e.g. two
1378
+ // SelectionCards in a row). Without a per-turn key the second card inherits
1379
+ // the first's `submitted=true` and silently rejects all taps. Keying on the
1380
+ // message id forces a fresh mount every turn. (CardHandoff's transitionKey
1381
+ // shares the id, but this renderer key stays the state-isolation guarantee.)
1382
+ /* @__PURE__ */ jsxRuntime.jsx(
1383
+ wireaiRn.ComponentRenderer,
1384
+ {
1385
+ messageId: lastCard.id,
1386
+ response,
1387
+ callbackOverrides: overrides
1388
+ },
1389
+ lastCard.id
1390
+ )
1391
+ )
1392
+ }
1393
+ )
1394
+ }
1395
+ );
1396
+ };
1397
+ var easeWire4 = reactNative.Easing.bezier(...WIRE_BEZIER);
1398
+ var schema = zod.z.object({
1399
+ title: zod.z.string().describe("Question or section header, e.g. 'Which season?'"),
1400
+ chips: zod.z.array(zod.z.string()).min(1).describe("List of option labels shown as chips, e.g. ['Spring','Summer','Autumn','Winter']"),
1401
+ multiSelect: zod.z.boolean().optional().describe("Allow multiple chips selected at once, default true"),
1402
+ submitLabel: zod.z.string().optional().describe("Confirm button label, default: Continue"),
1403
+ maxSelections: zod.z.number().optional().describe("Maximum number of chips the user can select at once")
1404
+ });
1405
+ var _ChipItem = ({ chip, isSelected, multiSelect, onToggle }) => {
1406
+ const t = useOnboardingTheme();
1407
+ const reduced = useReducedMotion();
1408
+ const handlePress = React14.useCallback(() => onToggle(chip), [chip, onToggle]);
1409
+ const pressScale = React14.useRef(new reactNative.Animated.Value(1)).current;
1410
+ const pressTo = React14.useCallback(
1411
+ (to) => {
1412
+ if (reduced) return;
1413
+ reactNative.Animated.spring(pressScale, {
1414
+ toValue: to,
1415
+ friction: PRESS_SPRING.friction,
1416
+ tension: PRESS_SPRING.tension,
1417
+ useNativeDriver: true
1418
+ }).start();
1419
+ },
1420
+ [pressScale, reduced]
1421
+ );
1422
+ const handlePressIn = React14.useCallback(() => pressTo(CHIP_PRESS_SCALE), [pressTo]);
1423
+ const handlePressOut = React14.useCallback(() => pressTo(1), [pressTo]);
1424
+ const fillT = React14.useRef(new reactNative.Animated.Value(isSelected ? 1 : 0)).current;
1425
+ React14.useEffect(() => {
1426
+ const anim = reactNative.Animated.timing(fillT, {
1427
+ toValue: isSelected ? 1 : 0,
1428
+ duration: scaledMs(stateChangeDuration(reduced, CHIP_FILL_MS)),
1429
+ easing: easeWire4,
1430
+ useNativeDriver: true
1431
+ });
1432
+ anim.start();
1433
+ return () => anim.stop();
1434
+ }, [isSelected, reduced, fillT]);
1435
+ const checkT = React14.useRef(new reactNative.Animated.Value(isSelected ? 1 : 0)).current;
1436
+ React14.useEffect(() => {
1437
+ if (!isSelected) {
1438
+ checkT.setValue(0);
1439
+ return;
1440
+ }
1441
+ if (reduced) {
1442
+ checkT.setValue(1);
1443
+ return;
1444
+ }
1445
+ checkT.setValue(0);
1446
+ const anim = reactNative.Animated.spring(checkT, {
1447
+ toValue: 1,
1448
+ friction: CHECK_SPRING.friction,
1449
+ tension: CHECK_SPRING.tension,
1450
+ useNativeDriver: true
1451
+ });
1452
+ anim.start();
1453
+ return () => anim.stop();
1454
+ }, [isSelected, reduced, checkT]);
1455
+ const checkScale = checkT.interpolate({ inputRange: [0, 1], outputRange: [CHECK_SCALE_FROM, 1] });
1456
+ return /* @__PURE__ */ jsxRuntime.jsx(
1457
+ reactNative.Pressable,
1458
+ {
1459
+ onPress: handlePress,
1460
+ onPressIn: handlePressIn,
1461
+ onPressOut: handlePressOut,
1462
+ accessibilityRole: multiSelect ? "checkbox" : "radio",
1463
+ accessibilityState: multiSelect ? { checked: isSelected } : { selected: isSelected },
1464
+ accessibilityLabel: chip,
1465
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
1466
+ reactNative.Animated.View,
1467
+ {
1468
+ style: [
1469
+ styles12.chip,
1470
+ {
1471
+ borderRadius: t.radius.full,
1472
+ paddingHorizontal: t.spacing.md,
1473
+ paddingVertical: t.spacing.sm,
1474
+ borderColor: t.colors.border,
1475
+ backgroundColor: t.colors.background,
1476
+ transform: [{ scale: pressScale }]
1477
+ }
1478
+ ],
1479
+ children: [
1480
+ /* @__PURE__ */ jsxRuntime.jsx(
1481
+ reactNative.Animated.View,
1482
+ {
1483
+ pointerEvents: "none",
1484
+ style: [
1485
+ reactNative.StyleSheet.absoluteFill,
1486
+ styles12.fillOverlay,
1487
+ {
1488
+ borderRadius: t.radius.full,
1489
+ borderColor: t.colors.primary,
1490
+ backgroundColor: t.colors.primary,
1491
+ opacity: fillT
1492
+ }
1493
+ ]
1494
+ }
1495
+ ),
1496
+ isSelected ? /* @__PURE__ */ jsxRuntime.jsx(
1497
+ reactNative.Animated.Text,
1498
+ {
1499
+ style: [
1500
+ styles12.check,
1501
+ { color: t.colors.onPrimary, opacity: checkT, transform: [{ scale: checkScale }] }
1502
+ ],
1503
+ children: "\u2713"
1504
+ }
1505
+ ) : null,
1506
+ /* @__PURE__ */ jsxRuntime.jsx(
1507
+ reactNative.Text,
1508
+ {
1509
+ style: [
1510
+ captionStyle(t.fonts),
1511
+ styles12.chipLabel,
1512
+ { color: isSelected ? t.colors.onPrimary : t.colors.textMuted }
1513
+ ],
1514
+ children: chip
1515
+ }
1516
+ )
1517
+ ]
1518
+ }
1519
+ )
1520
+ }
1521
+ );
1522
+ };
1523
+ var ChipItem = React14__default.default.memo(_ChipItem);
1524
+ var _ChipSelectCard = ({
1525
+ title,
1526
+ chips,
1527
+ multiSelect = true,
1528
+ submitLabel = "Continue",
1529
+ maxSelections,
1530
+ onSelect,
1531
+ onSelectionChange
1532
+ }) => {
1533
+ const t = useOnboardingTheme();
1534
+ const [selected, setSelected] = React14.useState([]);
1535
+ const [submitted, setSubmitted] = React14.useState(false);
1536
+ React14.useEffect(() => {
1537
+ if (!submitted && selected.length) onSelectionChange == null ? void 0 : onSelectionChange(multiSelect ? selected : selected[0]);
1538
+ }, [selected, submitted, multiSelect, onSelectionChange]);
1539
+ const toggle = React14.useCallback(
1540
+ (chip) => {
1541
+ if (submitted) return;
1542
+ if (multiSelect) {
1543
+ setSelected((prev) => {
1544
+ if (prev.includes(chip)) return prev.filter((c) => c !== chip);
1545
+ if (maxSelections && prev.length >= maxSelections) return prev;
1546
+ return [...prev, chip];
1547
+ });
1548
+ } else {
1549
+ setSelected([chip]);
1550
+ }
1551
+ },
1552
+ [submitted, multiSelect, maxSelections]
1553
+ );
1554
+ const handleSubmit = React14.useCallback(() => {
1555
+ if (!selected.length || !onSelect || submitted) return;
1556
+ setSubmitted(true);
1557
+ onSelect(multiSelect ? selected : selected[0]);
1558
+ }, [selected, onSelect, submitted, multiSelect]);
1559
+ return /* @__PURE__ */ jsxRuntime.jsx(
1560
+ CardLayout,
1561
+ {
1562
+ title,
1563
+ subtitle: maxSelections ? `Select up to ${maxSelections}` : void 0,
1564
+ footer: /* @__PURE__ */ jsxRuntime.jsx(
1565
+ Button,
1566
+ {
1567
+ title: submitLabel,
1568
+ onPress: handleSubmit,
1569
+ variant: "primary",
1570
+ disabled: selected.length === 0 || submitted
1571
+ }
1572
+ ),
1573
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles12.chipsRow, { gap: t.spacing.sm + t.spacing.xs }], children: chips.map((chip) => /* @__PURE__ */ jsxRuntime.jsx(
1574
+ ChipItem,
1575
+ {
1576
+ chip,
1577
+ isSelected: selected.includes(chip),
1578
+ multiSelect,
1579
+ onToggle: toggle
1580
+ },
1581
+ chip
1582
+ )) })
1583
+ }
1584
+ );
1585
+ };
1586
+ var ChipSelectCard = {
1587
+ name: "ChipSelectCard",
1588
+ description: "Use for compact short labels: seasons, activities, travel styles, moods, categories. Ideal for 4\u201312 single-word or two-word options. Use SelectionCard instead when options need descriptions.",
1589
+ component: React14__default.default.memo(_ChipSelectCard),
1590
+ propsSchema: schema,
1591
+ defaultProps: { multiSelect: true, submitLabel: "Continue" }
1592
+ };
1593
+ var styles12 = reactNative.StyleSheet.create({
1594
+ chipsRow: { flexDirection: "row", flexWrap: "wrap" },
1595
+ chip: { borderWidth: 1.5, flexDirection: "row", alignItems: "center", gap: 6, overflow: "hidden" },
1596
+ fillOverlay: { borderWidth: 1.5 },
1597
+ chipLabel: { fontWeight: "600" },
1598
+ check: { fontSize: 13, fontWeight: "800", lineHeight: 15 }
1599
+ });
1600
+ var schema2 = zod.z.object({
1601
+ label: zod.z.string().describe("Input field label / the question being asked"),
1602
+ hint: zod.z.string().optional().describe(
1603
+ "One short line under the question giving context: what the answer is used for or why you ask. Add it whenever the question alone might be unclear."
1604
+ ),
1605
+ placeholder: zod.z.string().optional().describe(
1606
+ 'A concrete example answer shown inside the box, phrased like a real user wrote it and starting with "e.g. ". ALWAYS provide one for open-ended questions: it shows the expected answer shape and inspires the user.'
1607
+ ),
1608
+ submitLabel: zod.z.string().optional().describe("Submit button label, default: Submit"),
1609
+ multiline: zod.z.boolean().optional().describe(
1610
+ "Tall multi-line box for open-ended answers (goals, notes, feedback). Default true. Set false for a single-line field like a name or username."
1611
+ ),
1612
+ lines: zod.z.number().optional().describe("Approx visible rows when multiline (default 6). Height is capped at 40% of the screen.")
1613
+ });
1614
+ var _TextInputCard = ({
1615
+ label,
1616
+ hint,
1617
+ placeholder,
1618
+ submitLabel = "Submit",
1619
+ onSubmit,
1620
+ validationError,
1621
+ multiline = true,
1622
+ lines = 6
1623
+ }) => {
1624
+ const t = useOnboardingTheme();
1625
+ const { height: screenHeight } = reactNative.useWindowDimensions();
1626
+ const [value, setValue] = React14.useState("");
1627
+ const [submitted, setSubmitted] = React14.useState(false);
1628
+ const locked = submitted && !validationError;
1629
+ const APPROX_ROW_HEIGHT = 26;
1630
+ const minHeight = multiline ? Math.min(Math.max(lines, 4) * APPROX_ROW_HEIGHT + t.spacing.md * 2, screenHeight * 0.4) : 52;
1631
+ const handleSubmit = React14.useCallback(() => {
1632
+ if (value.trim() && onSubmit && !locked) {
1633
+ setSubmitted(true);
1634
+ onSubmit(value.trim());
1635
+ }
1636
+ }, [value, onSubmit, locked]);
1637
+ return /* @__PURE__ */ jsxRuntime.jsx(
1638
+ CardLayout,
1639
+ {
1640
+ title: label,
1641
+ subtitle: hint,
1642
+ footer: /* @__PURE__ */ jsxRuntime.jsx(
1643
+ Button,
1644
+ {
1645
+ title: submitLabel,
1646
+ onPress: handleSubmit,
1647
+ variant: "primary",
1648
+ disabled: !value.trim() || locked
1649
+ }
1650
+ ),
1651
+ children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { gap: t.spacing.sm }, children: [
1652
+ /* @__PURE__ */ jsxRuntime.jsx(
1653
+ reactNative.TextInput,
1654
+ {
1655
+ value,
1656
+ onChangeText: setValue,
1657
+ placeholder: placeholder != null ? placeholder : "",
1658
+ placeholderTextColor: t.colors.textMuted,
1659
+ editable: !locked,
1660
+ multiline,
1661
+ numberOfLines: multiline ? lines : 1,
1662
+ textAlignVertical: multiline ? "top" : "center",
1663
+ style: [
1664
+ bodyStyle(t.fonts),
1665
+ styles13.input,
1666
+ {
1667
+ color: t.colors.text,
1668
+ backgroundColor: t.colors.surface,
1669
+ borderRadius: t.radius.md,
1670
+ borderColor: validationError ? t.colors.error : t.colors.border,
1671
+ padding: t.spacing.md,
1672
+ minHeight
1673
+ }
1674
+ ]
1675
+ }
1676
+ ),
1677
+ validationError ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [captionStyle(t.fonts), { color: t.colors.error }], children: validationError }) : null
1678
+ ] })
1679
+ }
1680
+ );
1681
+ };
1682
+ var TextInputCard = {
1683
+ name: "TextInputCard",
1684
+ description: "Use when the user needs to type a free-text answer: an open-ended goal, notes, feedback, a name, a destination, a search query. Multi-line by default (great for open-ended goals); set multiline:false for a short single-line field like a name. Use NumberStepperCard for numeric ranges with known bounds.",
1685
+ component: React14__default.default.memo(_TextInputCard),
1686
+ propsSchema: schema2,
1687
+ defaultProps: { submitLabel: "Submit", multiline: true, lines: 6 }
1688
+ };
1689
+ var styles13 = reactNative.StyleSheet.create({
1690
+ input: { borderWidth: 1 }
1691
+ });
1692
+ var easeWire5 = reactNative.Easing.bezier(...WIRE_BEZIER);
1693
+ var optionObjectSchema = zod.z.object({
1694
+ value: zod.z.string().describe("Option value"),
1695
+ label: zod.z.string().describe("Option display label")
1696
+ });
1697
+ var schema3 = zod.z.object({
1698
+ title: zod.z.string().describe("Question or prompt for the user"),
1699
+ options: zod.z.preprocess(
1700
+ (arr) => Array.isArray(arr) ? arr.map((item) => typeof item === "string" ? { value: item, label: item } : item) : arr,
1701
+ zod.z.array(optionObjectSchema).min(1)
1702
+ ).describe("List of choices"),
1703
+ submitLabel: zod.z.string().optional().describe("Submit button label, default: Continue"),
1704
+ multiSelect: zod.z.boolean().optional().describe("Allow multiple selections")
1705
+ });
1706
+ var _OptionRow = ({ opt, isSelected, multiSelect, onToggle }) => {
1707
+ const t = useOnboardingTheme();
1708
+ const reduced = useReducedMotion();
1709
+ const handlePress = React14.useCallback(() => onToggle(opt.value), [opt.value, onToggle]);
1710
+ const pressScale = React14.useRef(new reactNative.Animated.Value(1)).current;
1711
+ const pressTo = React14.useCallback(
1712
+ (to) => {
1713
+ if (reduced) return;
1714
+ reactNative.Animated.spring(pressScale, {
1715
+ toValue: to,
1716
+ friction: PRESS_SPRING.friction,
1717
+ tension: PRESS_SPRING.tension,
1718
+ useNativeDriver: true
1719
+ }).start();
1720
+ },
1721
+ [pressScale, reduced]
1722
+ );
1723
+ const handlePressIn = React14.useCallback(() => pressTo(ROW_PRESS_SCALE), [pressTo]);
1724
+ const handlePressOut = React14.useCallback(() => pressTo(1), [pressTo]);
1725
+ const selectT = React14.useRef(new reactNative.Animated.Value(isSelected ? 1 : 0)).current;
1726
+ React14.useEffect(() => {
1727
+ const anim = reactNative.Animated.timing(selectT, {
1728
+ toValue: isSelected ? 1 : 0,
1729
+ duration: scaledMs(stateChangeDuration(reduced, SELECT_FILL_MS)),
1730
+ easing: easeWire5,
1731
+ useNativeDriver: true
1732
+ });
1733
+ anim.start();
1734
+ return () => anim.stop();
1735
+ }, [isSelected, reduced, selectT]);
1736
+ const checkT = React14.useRef(new reactNative.Animated.Value(isSelected ? 1 : 0)).current;
1737
+ React14.useEffect(() => {
1738
+ if (!isSelected) {
1739
+ checkT.setValue(0);
1740
+ return;
1741
+ }
1742
+ if (reduced) {
1743
+ checkT.setValue(1);
1744
+ return;
1745
+ }
1746
+ checkT.setValue(0);
1747
+ const anim = reactNative.Animated.spring(checkT, {
1748
+ toValue: 1,
1749
+ friction: CHECK_SPRING.friction,
1750
+ tension: CHECK_SPRING.tension,
1751
+ useNativeDriver: true
1752
+ });
1753
+ anim.start();
1754
+ return () => anim.stop();
1755
+ }, [isSelected, reduced, checkT]);
1756
+ const checkScale = checkT.interpolate({ inputRange: [0, 1], outputRange: [CHECK_SCALE_FROM, 1] });
1757
+ return /* @__PURE__ */ jsxRuntime.jsx(
1758
+ reactNative.Pressable,
1759
+ {
1760
+ onPress: handlePress,
1761
+ onPressIn: handlePressIn,
1762
+ onPressOut: handlePressOut,
1763
+ accessibilityRole: multiSelect ? "checkbox" : "radio",
1764
+ accessibilityState: multiSelect ? { checked: isSelected } : { selected: isSelected },
1765
+ accessibilityLabel: opt.label,
1766
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
1767
+ reactNative.Animated.View,
1768
+ {
1769
+ style: [
1770
+ styles14.option,
1771
+ {
1772
+ padding: t.spacing.md,
1773
+ borderRadius: t.radius.md,
1774
+ gap: t.spacing.sm,
1775
+ borderColor: t.colors.border,
1776
+ backgroundColor: t.colors.background,
1777
+ transform: [{ scale: pressScale }]
1778
+ }
1779
+ ],
1780
+ children: [
1781
+ /* @__PURE__ */ jsxRuntime.jsx(
1782
+ reactNative.Animated.View,
1783
+ {
1784
+ pointerEvents: "none",
1785
+ style: [
1786
+ reactNative.StyleSheet.absoluteFill,
1787
+ styles14.selectedOverlay,
1788
+ {
1789
+ borderRadius: t.radius.md,
1790
+ borderColor: t.colors.primary,
1791
+ backgroundColor: t.colors.primarySoft,
1792
+ opacity: selectT
1793
+ }
1794
+ ]
1795
+ }
1796
+ ),
1797
+ /* @__PURE__ */ jsxRuntime.jsx(
1798
+ reactNative.View,
1799
+ {
1800
+ style: [
1801
+ styles14.indicator,
1802
+ { borderRadius: t.radius.full },
1803
+ isSelected ? { borderColor: t.colors.primary, backgroundColor: t.colors.primary } : { borderColor: t.colors.borderStrong, backgroundColor: "transparent" }
1804
+ ],
1805
+ children: isSelected ? /* @__PURE__ */ jsxRuntime.jsx(
1806
+ reactNative.Animated.Text,
1807
+ {
1808
+ style: [
1809
+ styles14.check,
1810
+ { color: t.colors.onPrimary, opacity: checkT, transform: [{ scale: checkScale }] }
1811
+ ],
1812
+ children: "\u2713"
1813
+ }
1814
+ ) : null
1815
+ }
1816
+ ),
1817
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [labelStyle(t.fonts), { color: t.colors.text }], children: opt.label })
1818
+ ]
1819
+ }
1820
+ )
1821
+ }
1822
+ );
1823
+ };
1824
+ var OptionRow = React14__default.default.memo(_OptionRow);
1825
+ var _SelectionCard = ({
1826
+ title,
1827
+ options,
1828
+ submitLabel = "Continue",
1829
+ multiSelect = false,
1830
+ onSelect,
1831
+ onSelectionChange
1832
+ }) => {
1833
+ const t = useOnboardingTheme();
1834
+ const [selected, setSelected] = React14.useState([]);
1835
+ const [submitted, setSubmitted] = React14.useState(false);
1836
+ React14.useEffect(() => {
1837
+ if (!submitted && selected.length) onSelectionChange == null ? void 0 : onSelectionChange(multiSelect ? selected : selected[0]);
1838
+ }, [selected, submitted, multiSelect, onSelectionChange]);
1839
+ const toggle = React14.useCallback(
1840
+ (value) => {
1841
+ if (submitted) return;
1842
+ if (multiSelect) {
1843
+ setSelected((prev) => prev.includes(value) ? prev.filter((v) => v !== value) : [...prev, value]);
1844
+ } else {
1845
+ setSelected([value]);
1846
+ }
1847
+ },
1848
+ [submitted, multiSelect]
1849
+ );
1850
+ const handleSubmit = React14.useCallback(() => {
1851
+ if (!selected.length || !onSelect || submitted) return;
1852
+ setSubmitted(true);
1853
+ onSelect(multiSelect ? selected : selected[0]);
1854
+ }, [selected, onSelect, submitted, multiSelect]);
1855
+ return /* @__PURE__ */ jsxRuntime.jsx(
1856
+ CardLayout,
1857
+ {
1858
+ title,
1859
+ footer: /* @__PURE__ */ jsxRuntime.jsx(
1860
+ Button,
1861
+ {
1862
+ title: submitLabel,
1863
+ onPress: handleSubmit,
1864
+ variant: "primary",
1865
+ disabled: selected.length === 0 || submitted
1866
+ }
1867
+ ),
1868
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { gap: t.spacing.sm + t.spacing.xs }, children: options.map((opt) => /* @__PURE__ */ jsxRuntime.jsx(
1869
+ OptionRow,
1870
+ {
1871
+ opt,
1872
+ isSelected: selected.includes(opt.value),
1873
+ multiSelect,
1874
+ onToggle: toggle
1875
+ },
1876
+ opt.value
1877
+ )) })
1878
+ }
1879
+ );
1880
+ };
1881
+ var SelectionCard = {
1882
+ name: "SelectionCard",
1883
+ description: "Use for mutually exclusive (radio) or multi-select (checkbox) choices from 2\u20138 options. Use ChipSelectCard instead when options are short single words.",
1884
+ component: React14__default.default.memo(_SelectionCard),
1885
+ propsSchema: schema3,
1886
+ defaultProps: { submitLabel: "Continue", multiSelect: false }
1887
+ };
1888
+ var styles14 = reactNative.StyleSheet.create({
1889
+ option: { borderWidth: 1.5, flexDirection: "row", alignItems: "center", overflow: "hidden" },
1890
+ selectedOverlay: { borderWidth: 1.5 },
1891
+ indicator: { width: 24, height: 24, borderWidth: 2, alignItems: "center", justifyContent: "center" },
1892
+ check: { fontSize: 13, fontWeight: "800", lineHeight: 15 }
1893
+ });
1894
+ var easeWire6 = reactNative.Easing.bezier(...WIRE_BEZIER);
1895
+ var schema4 = zod.z.object({
1896
+ status: zod.z.enum(["success", "warning", "error", "info"]).describe("Status type"),
1897
+ title: zod.z.string().describe("Status title"),
1898
+ message: zod.z.string().optional().describe("Supporting message"),
1899
+ ctaLabel: zod.z.string().optional().describe(
1900
+ "Optional call-to-action button label. Always include this on success status to keep the conversation going."
1901
+ )
1902
+ });
1903
+ var STATUS_ICONS = {
1904
+ success: "\u2713",
1905
+ warning: "\u26A0",
1906
+ error: "\u2717",
1907
+ info: "\u2139"
1908
+ };
1909
+ var _StatusCard = ({ status, title, message, ctaLabel, onContinue, onPress, onSubmit }) => {
1910
+ var _a, _b;
1911
+ const t = useOnboardingTheme();
1912
+ const reduced = useReducedMotion();
1913
+ const [submitted, setSubmitted] = React14.useState(false);
1914
+ const colorFor = {
1915
+ success: t.colors.success,
1916
+ warning: t.colors.warning,
1917
+ error: t.colors.error,
1918
+ info: t.colors.info
1919
+ };
1920
+ const color = (_a = colorFor[status]) != null ? _a : t.colors.info;
1921
+ const popT = React14.useRef(new reactNative.Animated.Value(reduced ? 1 : 0)).current;
1922
+ const copyT = React14.useRef(new reactNative.Animated.Value(reduced ? 1 : 0)).current;
1923
+ React14.useEffect(() => {
1924
+ if (reduced) {
1925
+ popT.setValue(1);
1926
+ copyT.setValue(1);
1927
+ return;
1928
+ }
1929
+ const anims = [
1930
+ reactNative.Animated.spring(popT, {
1931
+ toValue: 1,
1932
+ friction: STATUS_POP_SPRING.friction,
1933
+ tension: STATUS_POP_SPRING.tension,
1934
+ useNativeDriver: true
1935
+ }),
1936
+ reactNative.Animated.timing(copyT, {
1937
+ toValue: 1,
1938
+ duration: scaledMs(STATUS_COPY_MS),
1939
+ delay: scaledMs(STATUS_COPY_STAGGER_MS),
1940
+ easing: easeWire6,
1941
+ useNativeDriver: true
1942
+ })
1943
+ ];
1944
+ anims.forEach((a) => a.start());
1945
+ return () => anims.forEach((a) => a.stop());
1946
+ }, [reduced, popT, copyT]);
1947
+ const popScale = popT.interpolate({ inputRange: [0, 1], outputRange: [STATUS_POP_SCALE_FROM, 1] });
1948
+ const copyRise = copyT.interpolate({ inputRange: [0, 1], outputRange: [10, 0] });
1949
+ const handleContinue = React14.useCallback(() => {
1950
+ if (!submitted) {
1951
+ setSubmitted(true);
1952
+ if (onContinue) onContinue();
1953
+ else if (onPress && ctaLabel) onPress(ctaLabel);
1954
+ else if (onSubmit) onSubmit("continue");
1955
+ }
1956
+ }, [submitted, onContinue, onPress, onSubmit, ctaLabel]);
1957
+ return /* @__PURE__ */ jsxRuntime.jsx(
1958
+ CardLayout,
1959
+ {
1960
+ align: "center",
1961
+ footer: ctaLabel ? /* @__PURE__ */ jsxRuntime.jsx(Button, { title: ctaLabel, onPress: handleContinue, variant: "primary", disabled: submitted }) : void 0,
1962
+ children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles15.center, { gap: t.spacing.sm }], children: [
1963
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles15.titleRow, { gap: t.spacing.sm }], children: [
1964
+ /* @__PURE__ */ jsxRuntime.jsx(
1965
+ reactNative.Animated.Text,
1966
+ {
1967
+ style: [
1968
+ headingStyle(t.fonts),
1969
+ { color, opacity: popT, transform: [{ scale: popScale }] }
1970
+ ],
1971
+ children: (_b = STATUS_ICONS[status]) != null ? _b : "\xB7"
1972
+ }
1973
+ ),
1974
+ /* @__PURE__ */ jsxRuntime.jsx(
1975
+ reactNative.Animated.Text,
1976
+ {
1977
+ style: [
1978
+ headingStyle(t.fonts),
1979
+ { color, textAlign: "center", opacity: copyT, transform: [{ translateY: copyRise }] }
1980
+ ],
1981
+ children: title
1982
+ }
1983
+ )
1984
+ ] }),
1985
+ message ? /* @__PURE__ */ jsxRuntime.jsx(
1986
+ reactNative.Animated.Text,
1987
+ {
1988
+ style: [
1989
+ bodyStyle(t.fonts),
1990
+ {
1991
+ color: t.colors.textMuted,
1992
+ textAlign: "center",
1993
+ opacity: copyT,
1994
+ transform: [{ translateY: copyRise }]
1995
+ }
1996
+ ],
1997
+ children: message
1998
+ }
1999
+ ) : null
2000
+ ] })
2001
+ }
2002
+ );
2003
+ };
2004
+ var StatusCard = {
2005
+ name: "StatusCard",
2006
+ description: "Use ONLY for real outcomes: booking confirmed, data saved, action completed. NEVER for 'you just viewed X' acknowledgments. Always include ctaLabel so the user can continue after seeing the status.",
2007
+ component: React14__default.default.memo(_StatusCard),
2008
+ propsSchema: schema4
2009
+ };
2010
+ var styles15 = reactNative.StyleSheet.create({
2011
+ center: { width: "100%", alignItems: "center", justifyContent: "center" },
2012
+ titleRow: { flexDirection: "row", alignItems: "center", justifyContent: "center" }
2013
+ });
2014
+ var schema5 = zod.z.object({
2015
+ label: zod.z.string().describe("What is being counted, e.g. 'Number of days' or 'Travellers'"),
2016
+ min: zod.z.number().optional().describe("Minimum value, default 1"),
2017
+ max: zod.z.number().optional().describe("Maximum value, default 30"),
2018
+ defaultValue: zod.z.number().optional().describe("Starting value"),
2019
+ step: zod.z.number().optional().describe("Increment per tap, default 1"),
2020
+ unit: zod.z.string().optional().describe("Unit label shown next to value, e.g. 'days' or 'people'"),
2021
+ submitLabel: zod.z.string().optional().describe("Confirm button label, default: Confirm")
2022
+ });
2023
+ var safeNum = (n, fallback) => typeof n === "number" && Number.isFinite(n) ? n : fallback;
2024
+ var _NumberStepperCard = ({
2025
+ label,
2026
+ min,
2027
+ max,
2028
+ defaultValue,
2029
+ step,
2030
+ unit,
2031
+ submitLabel = "Confirm",
2032
+ onSubmit
2033
+ }) => {
2034
+ const t = useOnboardingTheme();
2035
+ const safeMin = safeNum(min, 1);
2036
+ const safeMaxRaw = safeNum(max, 30);
2037
+ const safeMax = safeMaxRaw < safeMin ? safeMin : safeMaxRaw;
2038
+ const safeStepRaw = safeNum(step, 1);
2039
+ const safeStep = safeStepRaw > 0 ? safeStepRaw : 1;
2040
+ const safeDefault = safeNum(defaultValue, safeMin);
2041
+ const [value, setValue] = React14.useState(Math.min(Math.max(safeDefault, safeMin), safeMax));
2042
+ const [submitted, setSubmitted] = React14.useState(false);
2043
+ const handleDecrement = React14.useCallback(() => {
2044
+ if (!submitted) setValue((v) => Math.max(v - safeStep, safeMin));
2045
+ }, [submitted, safeStep, safeMin]);
2046
+ const handleIncrement = React14.useCallback(() => {
2047
+ if (!submitted) setValue((v) => Math.min(v + safeStep, safeMax));
2048
+ }, [submitted, safeStep, safeMax]);
2049
+ const handleSubmit = React14.useCallback(() => {
2050
+ if (!submitted) {
2051
+ setSubmitted(true);
2052
+ onSubmit == null ? void 0 : onSubmit(value);
2053
+ }
2054
+ }, [submitted, onSubmit, value]);
2055
+ const atMin = value <= safeMin;
2056
+ const atMax = value >= safeMax;
2057
+ const stepBtnStyle = (disabled) => [
2058
+ styles16.stepBtn,
2059
+ { borderRadius: t.radius.full, backgroundColor: t.colors.background, borderColor: disabled ? t.colors.border : t.colors.primary }
2060
+ ];
2061
+ return /* @__PURE__ */ jsxRuntime.jsx(
2062
+ CardLayout,
2063
+ {
2064
+ title: label,
2065
+ align: "center",
2066
+ footer: /* @__PURE__ */ jsxRuntime.jsx(Button, { title: submitLabel, onPress: handleSubmit, variant: "primary", disabled: submitted }),
2067
+ children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { gap: t.spacing.md, alignItems: "center" }, children: [
2068
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles16.stepper, { gap: t.spacing.lg }], children: [
2069
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: atMin || submitted ? void 0 : handleDecrement, disabled: atMin || submitted, style: stepBtnStyle(atMin), children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [headingStyle(t.fonts), { color: atMin ? t.colors.textMuted : t.colors.primary }], children: "\u2212" }) }),
2070
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles16.valueBox, children: [
2071
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [headingStyle(t.fonts), { color: t.colors.primary }], children: String(value) }),
2072
+ unit ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [captionStyle(t.fonts), { color: t.colors.textMuted }], children: unit }) : null
2073
+ ] }),
2074
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: atMax || submitted ? void 0 : handleIncrement, disabled: atMax || submitted, style: stepBtnStyle(atMax), children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [headingStyle(t.fonts), { color: atMax ? t.colors.textMuted : t.colors.primary }], children: "+" }) })
2075
+ ] }),
2076
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [captionStyle(t.fonts), { color: t.colors.textMuted, textAlign: "center" }], children: `${safeMin} \u2013 ${safeMax}${unit ? ` ${unit}` : ""}` })
2077
+ ] })
2078
+ }
2079
+ );
2080
+ };
2081
+ var NumberStepperCard = {
2082
+ name: "NumberStepperCard",
2083
+ description: "Use for any bounded numeric input: days, people, portions, budget tiers, ratings. Best for ranges 1\u201399. Set unit (e.g. 'days', 'people') for clarity.",
2084
+ component: React14__default.default.memo(_NumberStepperCard),
2085
+ propsSchema: schema5,
2086
+ defaultProps: { min: 1, max: 30, defaultValue: 1, step: 1, submitLabel: "Confirm" }
2087
+ };
2088
+ var styles16 = reactNative.StyleSheet.create({
2089
+ stepper: { flexDirection: "row", alignItems: "center", justifyContent: "center" },
2090
+ stepBtn: { width: 44, height: 44, borderWidth: 2, alignItems: "center", justifyContent: "center" },
2091
+ valueBox: { alignItems: "center", minWidth: 80 }
2092
+ });
2093
+ var easeWire7 = reactNative.Easing.bezier(...WIRE_BEZIER);
2094
+ var RISE_PX = 10;
2095
+ var schema6 = zod.z.object({
2096
+ title: zod.z.string().describe("Short, warm headline for the value/momentum moment"),
2097
+ body: zod.z.string().optional().describe("One or two supporting lines that reflect the user's answers back and build momentum"),
2098
+ items: zod.z.array(zod.z.string()).max(4).optional().describe(
2099
+ "2-3 very short 'work being done' lines that each reference something the user already answered (e.g. 'Sized for a small team'). Shown as ticked items appearing in sequence. Prefer these over `body` when you have concrete answers to reflect back."
2100
+ ),
2101
+ illustration: zod.z.string().optional().describe(
2102
+ "Name of an app-provided illustration to show (the app's registry decides the artwork). Prefer this over imageUrl."
2103
+ ),
2104
+ imageUrl: zod.z.string().url().refine((v) => v.startsWith("https://"), "imageUrl must be an https URL").optional().describe(
2105
+ "Fallback remote HTTPS image URL, used only when no named illustration is registered. A non-https or malformed value is rejected and degrades to the named-illustration fallback."
2106
+ ),
2107
+ illustrationFallback: zod.z.string().optional().describe(
2108
+ "Named illustration to show when neither `illustration` nor `imageUrl` resolves (or the image fails to load). Defaults to the kit's always-present 'momentum' art so the screen is never blank."
2109
+ ),
2110
+ ctaLabel: zod.z.string().optional().describe("Continue button label, default: Continue")
2111
+ });
2112
+ var MIN_DISPLAY_MS = 500;
2113
+ var _WorkItem = ({
2114
+ text,
2115
+ index,
2116
+ reduced
2117
+ }) => {
2118
+ const t = useOnboardingTheme();
2119
+ const rowT = React14.useRef(new reactNative.Animated.Value(reduced ? 1 : 0)).current;
2120
+ const checkT = React14.useRef(new reactNative.Animated.Value(reduced ? 1 : 0)).current;
2121
+ React14.useEffect(() => {
2122
+ if (reduced) {
2123
+ rowT.setValue(1);
2124
+ checkT.setValue(1);
2125
+ return;
2126
+ }
2127
+ const anims = [
2128
+ reactNative.Animated.timing(rowT, {
2129
+ toValue: 1,
2130
+ duration: scaledMs(INTERSTITIAL_ITEM_MS),
2131
+ delay: scaledMs(interstitialItemDelay(index)),
2132
+ easing: easeWire7,
2133
+ useNativeDriver: true
2134
+ }),
2135
+ reactNative.Animated.spring(checkT, {
2136
+ toValue: 1,
2137
+ delay: scaledMs(interstitialCheckDelay(index)),
2138
+ friction: CHECK_SPRING.friction,
2139
+ tension: CHECK_SPRING.tension,
2140
+ useNativeDriver: true
2141
+ })
2142
+ ];
2143
+ anims.forEach((a) => a.start());
2144
+ return () => anims.forEach((a) => a.stop());
2145
+ }, [reduced, index, rowT, checkT]);
2146
+ const rise = rowT.interpolate({ inputRange: [0, 1], outputRange: [RISE_PX, 0] });
2147
+ const checkScale = checkT.interpolate({
2148
+ inputRange: [0, 1],
2149
+ outputRange: [INTERSTITIAL_CHECK_SCALE_FROM, 1]
2150
+ });
2151
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2152
+ reactNative.Animated.View,
2153
+ {
2154
+ style: [styles17.itemRow, { gap: t.spacing.sm + t.spacing.xs, opacity: rowT, transform: [{ translateY: rise }] }],
2155
+ children: [
2156
+ /* @__PURE__ */ jsxRuntime.jsx(
2157
+ reactNative.View,
2158
+ {
2159
+ style: [
2160
+ styles17.itemBadge,
2161
+ { backgroundColor: t.colors.primarySoft, borderRadius: t.radius.full }
2162
+ ],
2163
+ children: /* @__PURE__ */ jsxRuntime.jsx(
2164
+ reactNative.Animated.Text,
2165
+ {
2166
+ style: [
2167
+ styles17.itemCheck,
2168
+ { color: t.colors.primary, opacity: checkT, transform: [{ scale: checkScale }] }
2169
+ ],
2170
+ children: "\u2713"
2171
+ }
2172
+ )
2173
+ }
2174
+ ),
2175
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [bodyStyle(t.fonts), styles17.itemText, { color: t.colors.text }], children: text })
2176
+ ]
2177
+ }
2178
+ );
2179
+ };
2180
+ var WorkItem = React14__default.default.memo(_WorkItem);
2181
+ var _InterstitialCard = ({
2182
+ title,
2183
+ body,
2184
+ items,
2185
+ illustration,
2186
+ imageUrl,
2187
+ illustrationFallback,
2188
+ ctaLabel = "Continue",
2189
+ onSubmit,
2190
+ onPress
2191
+ }) => {
2192
+ const t = useOnboardingTheme();
2193
+ const reduced = useReducedMotion();
2194
+ const node = useIllustration(illustration);
2195
+ const fallbackNode = useIllustration(illustrationFallback);
2196
+ const [submitted, setSubmitted] = React14.useState(false);
2197
+ const [imgFailed, setImgFailed] = React14.useState(false);
2198
+ const [canAdvance, setCanAdvance] = React14.useState(false);
2199
+ React14.useEffect(() => {
2200
+ const timer = setTimeout(() => setCanAdvance(true), MIN_DISPLAY_MS);
2201
+ return () => clearTimeout(timer);
2202
+ }, []);
2203
+ const artT = React14.useRef(new reactNative.Animated.Value(reduced ? 1 : 0)).current;
2204
+ const titleT = React14.useRef(new reactNative.Animated.Value(reduced ? 1 : 0)).current;
2205
+ const bodyT = React14.useRef(new reactNative.Animated.Value(reduced ? 1 : 0)).current;
2206
+ React14.useEffect(() => {
2207
+ if (reduced) {
2208
+ artT.setValue(1);
2209
+ titleT.setValue(1);
2210
+ bodyT.setValue(1);
2211
+ return;
2212
+ }
2213
+ const rise = (value, delay) => reactNative.Animated.timing(value, {
2214
+ toValue: 1,
2215
+ duration: scaledMs(INTERSTITIAL_HEAD_MS),
2216
+ delay,
2217
+ easing: easeWire7,
2218
+ useNativeDriver: true
2219
+ });
2220
+ const anims = [
2221
+ rise(artT, 0),
2222
+ rise(titleT, scaledMs(INTERSTITIAL_HEAD_STAGGER_MS)),
2223
+ rise(bodyT, scaledMs(INTERSTITIAL_HEAD_STAGGER_MS * 2))
2224
+ ];
2225
+ anims.forEach((a) => a.start());
2226
+ return () => anims.forEach((a) => a.stop());
2227
+ }, [reduced, artT, titleT, bodyT]);
2228
+ const riseStyle = (value) => ({
2229
+ opacity: value,
2230
+ transform: [{ translateY: value.interpolate({ inputRange: [0, 1], outputRange: [RISE_PX, 0] }) }]
2231
+ });
2232
+ const handleContinue = React14.useCallback(() => {
2233
+ if (submitted || !canAdvance) return;
2234
+ setSubmitted(true);
2235
+ if (onSubmit) onSubmit("continue");
2236
+ else if (onPress) onPress(ctaLabel);
2237
+ }, [submitted, canAdvance, onSubmit, onPress, ctaLabel]);
2238
+ const showImage = !node && !!imageUrl && !imgFailed;
2239
+ const art = node != null ? node : showImage ? void 0 : fallbackNode;
2240
+ return /* @__PURE__ */ jsxRuntime.jsx(
2241
+ CardLayout,
2242
+ {
2243
+ align: "center",
2244
+ footer: /* @__PURE__ */ jsxRuntime.jsx(
2245
+ Button,
2246
+ {
2247
+ title: ctaLabel,
2248
+ onPress: handleContinue,
2249
+ variant: "primary",
2250
+ disabled: submitted || !canAdvance
2251
+ }
2252
+ ),
2253
+ children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles17.center, { gap: t.spacing.md }], children: [
2254
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Animated.View, { style: [styles17.fullWidth, riseStyle(artT)], children: showImage ? /* @__PURE__ */ jsxRuntime.jsx(
2255
+ reactNative.Image,
2256
+ {
2257
+ source: { uri: imageUrl },
2258
+ style: styles17.image,
2259
+ resizeMode: "contain",
2260
+ onError: () => setImgFailed(true)
2261
+ }
2262
+ ) : art ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles17.art, children: art }) : null }),
2263
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Animated.View, { style: riseStyle(titleT), children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [headingStyle(t.fonts), { color: t.colors.text, textAlign: "center" }], children: title }) }),
2264
+ (items == null ? void 0 : items.length) ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles17.items, { gap: t.spacing.md, marginTop: t.spacing.sm }], children: items.map((item, i) => /* @__PURE__ */ jsxRuntime.jsx(WorkItem, { text: item, index: i, reduced }, item)) }) : body ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Animated.View, { style: riseStyle(bodyT), children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [bodyStyle(t.fonts), { color: t.colors.textMuted, textAlign: "center" }], children: body }) }) : null
2265
+ ] })
2266
+ }
2267
+ );
2268
+ };
2269
+ var InterstitialCard = {
2270
+ name: "InterstitialCard",
2271
+ description: "Use ONCE mid-flow (never first, never last) as a momentum/value screen between questions \u2014 an illustration + a warm line that reflects answers back, like Duolingo/Cal-AI. NOT a question; the user just taps Continue. Set `illustration` to an app-registered artwork name. Prefer `items` (2-3 short ticked lines, each reflecting a real answer) when you have the signal. Skip it if you don't have enough signal to make the line specific.",
2272
+ component: React14__default.default.memo(_InterstitialCard),
2273
+ propsSchema: schema6,
2274
+ defaultProps: { ctaLabel: "Continue", illustrationFallback: "momentum" }
2275
+ };
2276
+ var styles17 = reactNative.StyleSheet.create({
2277
+ center: { width: "100%", alignItems: "center", justifyContent: "center" },
2278
+ fullWidth: { width: "100%", alignItems: "center" },
2279
+ art: { alignItems: "center", justifyContent: "center" },
2280
+ image: { width: "100%", height: 200 },
2281
+ items: { width: "100%", alignSelf: "stretch" },
2282
+ itemRow: { flexDirection: "row", alignItems: "center" },
2283
+ itemBadge: { width: 24, height: 24, alignItems: "center", justifyContent: "center" },
2284
+ itemCheck: { fontSize: 13, fontWeight: "800", lineHeight: 15 },
2285
+ itemText: { flex: 1 }
2286
+ });
2287
+
2288
+ // src/cards/index.ts
2289
+ var onboardingComponents = [
2290
+ ChipSelectCard,
2291
+ TextInputCard,
2292
+ SelectionCard,
2293
+ StatusCard,
2294
+ NumberStepperCard,
2295
+ InterstitialCard
2296
+ ];
2297
+ var deriveFormFactor = (iosIdiom, width, height) => {
2298
+ if (iosIdiom === "pad") return "tablet";
2299
+ if (iosIdiom === "phone") return "phone";
2300
+ if (typeof width === "number" && typeof height === "number") {
2301
+ return Math.min(width, height) >= 600 ? "tablet" : "phone";
2302
+ }
2303
+ return void 0;
2304
+ };
2305
+ var collectDeviceContext = () => {
2306
+ var _a;
2307
+ const ctx = { platform: reactNative.Platform.OS };
2308
+ try {
2309
+ const version = reactNative.Platform.Version;
2310
+ if (version !== void 0 && version !== null && String(version)) {
2311
+ ctx.osVersion = String(version);
2312
+ }
2313
+ } catch {
2314
+ }
2315
+ let constants = {};
2316
+ try {
2317
+ constants = (_a = reactNative.Platform.constants) != null ? _a : {};
2318
+ } catch {
2319
+ constants = {};
2320
+ }
2321
+ let iosIdiom;
2322
+ try {
2323
+ if (reactNative.Platform.OS === "android") {
2324
+ const brand = constants.Brand;
2325
+ const model = constants.Model;
2326
+ const release = constants.Release;
2327
+ if (typeof brand === "string" && brand) ctx.brand = brand;
2328
+ if (typeof model === "string" && model) ctx.model = model;
2329
+ if (release !== void 0 && release !== null && String(release)) {
2330
+ ctx.osVersion = String(release);
2331
+ }
2332
+ } else if (reactNative.Platform.OS === "ios") {
2333
+ const osVersion = constants.osVersion;
2334
+ const idiom = constants.interfaceIdiom;
2335
+ if (osVersion !== void 0 && osVersion !== null && String(osVersion)) {
2336
+ ctx.osVersion = String(osVersion);
2337
+ }
2338
+ if (typeof idiom === "string" && idiom) {
2339
+ ctx.interfaceIdiom = idiom;
2340
+ iosIdiom = idiom;
2341
+ }
2342
+ }
2343
+ } catch {
2344
+ }
2345
+ try {
2346
+ const screen = reactNative.Dimensions.get("screen");
2347
+ if (screen) {
2348
+ if (typeof screen.width === "number") ctx.screenWidth = screen.width;
2349
+ if (typeof screen.height === "number") ctx.screenHeight = screen.height;
2350
+ if (typeof screen.scale === "number") ctx.screenScale = screen.scale;
2351
+ const formFactor = deriveFormFactor(iosIdiom, screen.width, screen.height);
2352
+ if (formFactor) ctx.formFactor = formFactor;
2353
+ }
2354
+ } catch {
2355
+ }
2356
+ try {
2357
+ ctx.isRTL = reactNative.I18nManager.isRTL;
2358
+ } catch {
2359
+ }
2360
+ try {
2361
+ const resolved = Intl.DateTimeFormat().resolvedOptions();
2362
+ if (resolved.locale) ctx.locale = resolved.locale;
2363
+ if (resolved.timeZone) ctx.timeZone = resolved.timeZone;
2364
+ } catch {
2365
+ }
2366
+ return ctx;
2367
+ };
2368
+
2369
+ // src/session/persistedSession.ts
2370
+ var DEFAULT_SESSION_TTL_MS = 36e5;
2371
+ var READ_TIMEOUT_MS = 1500;
2372
+ var sessionStorageKey = (appId) => `wireai:session:${appId}`;
2373
+ var withTimeout = (p, ms) => {
2374
+ let timer;
2375
+ const timeout = new Promise((resolve) => {
2376
+ timer = setTimeout(() => resolve(void 0), ms);
2377
+ });
2378
+ return Promise.race([p, timeout]).finally(() => clearTimeout(timer));
2379
+ };
2380
+ var parsePersisted = (raw) => {
2381
+ if (!raw) return void 0;
2382
+ try {
2383
+ const parsed = JSON.parse(raw);
2384
+ if (typeof parsed === "object" && parsed !== null && typeof parsed.id === "string" && typeof parsed.ts === "number") {
2385
+ return parsed;
2386
+ }
2387
+ } catch {
2388
+ }
2389
+ return void 0;
2390
+ };
2391
+ var loadPersistedSession = async (storage, key, ttlMs = DEFAULT_SESSION_TTL_MS) => {
2392
+ let stored;
2393
+ try {
2394
+ stored = parsePersisted(await withTimeout(storage.getItem(key), READ_TIMEOUT_MS));
2395
+ } catch {
2396
+ stored = void 0;
2397
+ }
2398
+ if (stored && Date.now() - stored.ts < ttlMs) {
2399
+ return { id: stored.id, resumed: true };
2400
+ }
2401
+ const id = makeSessionId();
2402
+ savePersistedSession(storage, key, id);
2403
+ return { id, resumed: false };
2404
+ };
2405
+ var peekPersistedSession = async (storage, key) => {
2406
+ try {
2407
+ return parsePersisted(await withTimeout(storage.getItem(key), READ_TIMEOUT_MS));
2408
+ } catch {
2409
+ return void 0;
2410
+ }
2411
+ };
2412
+ var savePersistedSession = (storage, key, id) => {
2413
+ try {
2414
+ void storage.setItem(key, JSON.stringify({ id, ts: Date.now() })).catch(() => {
2415
+ });
2416
+ } catch {
2417
+ }
2418
+ };
2419
+ var clearPersistedSession = (storage, key) => {
2420
+ try {
2421
+ void storage.removeItem(key).catch(() => {
2422
+ });
2423
+ } catch {
2424
+ }
2425
+ };
2426
+
2427
+ // src/identity/userIdentity.ts
2428
+ var USER_ID_MAX_LENGTH = 128;
2429
+ var sanitizeUserId = (raw) => {
2430
+ if (typeof raw !== "string") return void 0;
2431
+ const trimmed = raw.trim();
2432
+ if (!trimmed) return void 0;
2433
+ return trimmed.length > USER_ID_MAX_LENGTH ? trimmed.slice(0, USER_ID_MAX_LENGTH) : trimmed;
2434
+ };
2435
+ var identifyOnboarding = async (opts) => {
2436
+ var _a, _b, _c;
2437
+ const userId = sanitizeUserId(opts.userId);
2438
+ if (!userId || !((_a = opts.config) == null ? void 0 : _a.serverUrl)) return false;
2439
+ let contextId = ((_b = opts.contextId) == null ? void 0 : _b.trim()) || void 0;
2440
+ if (!contextId && opts.storage) {
2441
+ const key = (_c = opts.persistKey) != null ? _c : opts.appId ? sessionStorageKey(opts.appId) : void 0;
2442
+ if (key) {
2443
+ const stored = await peekPersistedSession(opts.storage, key);
2444
+ contextId = stored == null ? void 0 : stored.id;
2445
+ }
2446
+ }
2447
+ if (!contextId) return false;
2448
+ reportClientEvent(
2449
+ { serverUrl: opts.config.serverUrl, apiKey: opts.config.apiKey },
2450
+ { event_type: "identify", session_id: contextId, user_id: userId }
2451
+ );
2452
+ return true;
2453
+ };
2454
+ var WireOnboarding = ({
2455
+ config,
2456
+ theme,
2457
+ components,
2458
+ illustrations,
2459
+ validators,
2460
+ onComplete,
2461
+ onSkip,
2462
+ onError,
2463
+ onEvent,
2464
+ copy,
2465
+ approxScreens,
2466
+ startMessage = "start",
2467
+ startTimeoutMs = 15e3,
2468
+ fallbackFlow,
2469
+ maxRetries = 1,
2470
+ storage,
2471
+ sessionTtlMs = DEFAULT_SESSION_TTL_MS,
2472
+ persistKey,
2473
+ userContext,
2474
+ userId
2475
+ }) => {
2476
+ var _a, _b, _c;
2477
+ const boundUserId = React14.useMemo(() => sanitizeUserId(userId), [userId]);
2478
+ const device = React14.useMemo(() => {
2479
+ const collected = collectDeviceContext();
2480
+ return config.appVersion ? { ...collected, appVersion: config.appVersion } : collected;
2481
+ }, [config.appVersion]);
2482
+ const userContextKey = userContext ? JSON.stringify(userContext) : "";
2483
+ const userContextStable = React14.useMemo(
2484
+ () => userContextKey ? JSON.parse(userContextKey) : void 0,
2485
+ [userContextKey]
2486
+ );
2487
+ const clientContext = React14.useMemo(
2488
+ () => ({ device, userContext: userContextStable, userId: boundUserId }),
2489
+ [device, userContextStable, boundUserId]
2490
+ );
2491
+ const [session, setSession] = React14.useState(
2492
+ () => storage ? null : { id: makeSessionId(), resumed: false }
2493
+ );
2494
+ const storageKey = persistKey != null ? persistKey : sessionStorageKey(config.appId);
2495
+ React14.useEffect(() => {
2496
+ if (!storage || session) return;
2497
+ let cancelled = false;
2498
+ void loadPersistedSession(storage, storageKey, sessionTtlMs).then((loaded) => {
2499
+ if (!cancelled) setSession(loaded);
2500
+ });
2501
+ return () => {
2502
+ cancelled = true;
2503
+ };
2504
+ }, [storage, storageKey, sessionTtlMs, session]);
2505
+ const handleComplete = React14.useCallback(
2506
+ (result) => {
2507
+ if (storage) clearPersistedSession(storage, storageKey);
2508
+ onComplete(result);
2509
+ },
2510
+ [storage, storageKey, onComplete]
2511
+ );
2512
+ const sessionId = (_a = session == null ? void 0 : session.id) != null ? _a : "";
2513
+ const startupUserIdRef = React14.useRef(boundUserId);
2514
+ const llm = React14.useMemo(() => {
2515
+ const baseUrl = config.serverUrl.replace(/\/$/, "");
2516
+ return {
2517
+ provider: "a2a",
2518
+ baseUrl: `${baseUrl}/a2a`,
2519
+ model: config.appId,
2520
+ apiKey: config.apiKey,
2521
+ // Carry the kit session id alongside any host attribution so it reaches the A2A
2522
+ // request's `params.metadata` (the correlation seed; see note above), plus the
2523
+ // names of the components this device can actually render — so the backend only
2524
+ // emits cards the kit knows how to draw.
2525
+ metadata: {
2526
+ ...config.metadata,
2527
+ sessionId,
2528
+ supportedComponents: (components != null ? components : onboardingComponents).map((c) => c.name),
2529
+ // Device snapshot + host-injected user context ride the session-start metadata so the
2530
+ // backend can segment the funnel. Old servers ignore these unknown keys (backward compat).
2531
+ device,
2532
+ ...userContextStable ? { userContext: userContextStable } : {},
2533
+ // The session-start user id (from the ref) rides the session-start metadata so the
2534
+ // server binds the session to a real user at creation. Reading the ref — not
2535
+ // `boundUserId` — keeps this memo off the userId dependency, so a mid-session change
2536
+ // does NOT recreate the adapter; it's bound by the `identify` effect below instead.
2537
+ ...startupUserIdRef.current ? { userId: startupUserIdRef.current } : {}
2538
+ },
2539
+ timeoutMs: 6e4
2540
+ };
2541
+ }, [config.serverUrl, config.appId, config.apiKey, config.metadata, sessionId, components, device, userContextStable]);
2542
+ const reportTarget = React14.useMemo(
2543
+ () => ({ serverUrl: config.serverUrl, apiKey: config.apiKey }),
2544
+ [config.serverUrl, config.apiKey]
2545
+ );
2546
+ const identifiedRef = React14.useRef(void 0);
2547
+ const identifySeededRef = React14.useRef(false);
2548
+ React14.useEffect(() => {
2549
+ if (!session || !sessionId) return;
2550
+ if (!identifySeededRef.current) {
2551
+ identifySeededRef.current = true;
2552
+ identifiedRef.current = session.resumed ? void 0 : boundUserId;
2553
+ }
2554
+ if (!boundUserId || boundUserId === identifiedRef.current) return;
2555
+ identifiedRef.current = boundUserId;
2556
+ reportClientEvent(reportTarget, {
2557
+ event_type: "identify",
2558
+ session_id: sessionId,
2559
+ user_id: boundUserId
2560
+ });
2561
+ }, [session, sessionId, boundUserId, reportTarget]);
2562
+ const cards = components != null ? components : onboardingComponents;
2563
+ if (!session) {
2564
+ return /* @__PURE__ */ jsxRuntime.jsx(OnboardingThemeProvider, { theme, children: /* @__PURE__ */ jsxRuntime.jsx(
2565
+ LoadingScreen,
2566
+ {
2567
+ title: (_b = copy == null ? void 0 : copy.restoringTitle) != null ? _b : DEFAULT_COPY.restoringTitle,
2568
+ hint: (_c = copy == null ? void 0 : copy.restoringHint) != null ? _c : DEFAULT_COPY.restoringHint
2569
+ }
2570
+ ) });
2571
+ }
2572
+ return /* @__PURE__ */ jsxRuntime.jsx(OnboardingThemeProvider, { theme, children: /* @__PURE__ */ jsxRuntime.jsx(IllustrationProvider, { registry: { ...defaultIllustrations, ...illustrations }, children: /* @__PURE__ */ jsxRuntime.jsx(wireaiRn.WireAIProvider, { llm, components: cards, children: /* @__PURE__ */ jsxRuntime.jsx(
2573
+ OnboardingFlow,
2574
+ {
2575
+ validators,
2576
+ onComplete: handleComplete,
2577
+ onSkip,
2578
+ onError,
2579
+ onEvent,
2580
+ copy,
2581
+ approxScreens,
2582
+ startMessage,
2583
+ startTimeoutMs,
2584
+ fallbackFlow,
2585
+ maxRetries,
2586
+ reportTarget,
2587
+ sessionId,
2588
+ resumed: session.resumed,
2589
+ clientContext
2590
+ }
2591
+ ) }) }) });
2592
+ };
2593
+ function DemoOnboarding({
2594
+ config,
2595
+ theme,
2596
+ illustrations,
2597
+ validators,
2598
+ copy,
2599
+ label = "Demo onboarding",
2600
+ onComplete,
2601
+ renderTrigger
2602
+ }) {
2603
+ const [open, setOpen] = React14.useState(false);
2604
+ const [runId, setRunId] = React14.useState(0);
2605
+ const t = React14.useMemo(() => mergeTheme(theme != null ? theme : {}), [theme]);
2606
+ const styles20 = React14.useMemo(() => makeStyles(t), [t]);
2607
+ const openDemo = React14.useCallback(() => {
2608
+ setRunId((n) => n + 1);
2609
+ setOpen(true);
2610
+ }, []);
2611
+ const close = React14.useCallback(() => setOpen(false), []);
2612
+ const finish = React14.useCallback(
2613
+ (result) => {
2614
+ onComplete == null ? void 0 : onComplete(result);
2615
+ setOpen(false);
2616
+ },
2617
+ [onComplete]
2618
+ );
2619
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2620
+ renderTrigger ? renderTrigger(openDemo) : /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: openDemo, style: styles20.trigger, accessibilityRole: "button", children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles20.triggerText, children: label }) }),
2621
+ /* @__PURE__ */ jsxRuntime.jsx(
2622
+ reactNative.Modal,
2623
+ {
2624
+ visible: open,
2625
+ animationType: "slide",
2626
+ presentationStyle: "fullScreen",
2627
+ onRequestClose: close,
2628
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles20.modal, children: config ? /* @__PURE__ */ jsxRuntime.jsx(
2629
+ WireOnboarding,
2630
+ {
2631
+ config,
2632
+ theme,
2633
+ illustrations,
2634
+ validators,
2635
+ copy,
2636
+ onComplete: finish,
2637
+ onSkip: close,
2638
+ onError: close
2639
+ },
2640
+ runId
2641
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles20.notice, children: [
2642
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles20.noticeTitle, children: "Wire AI not configured" }),
2643
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles20.noticeBody, children: "Set the Wire AI key + server URL (e.g. via wireConfigFromEnv) to demo the AI onboarding here." }),
2644
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: close, style: styles20.trigger, accessibilityRole: "button", children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles20.triggerText, children: "Close" }) })
2645
+ ] }) })
2646
+ }
2647
+ )
2648
+ ] });
2649
+ }
2650
+ function makeStyles(t) {
2651
+ return reactNative.StyleSheet.create({
2652
+ trigger: {
2653
+ backgroundColor: t.colors.primary,
2654
+ borderRadius: t.button.radius,
2655
+ paddingVertical: 14,
2656
+ paddingHorizontal: 20,
2657
+ alignItems: "center"
2658
+ },
2659
+ triggerText: { color: t.colors.onPrimary, fontFamily: t.fonts.medium, fontSize: 16 },
2660
+ modal: { flex: 1, backgroundColor: t.colors.background },
2661
+ notice: { flex: 1, alignItems: "center", justifyContent: "center", padding: 24, gap: 12 },
2662
+ noticeTitle: { color: t.colors.text, fontFamily: t.fonts.bold, fontSize: 20 },
2663
+ noticeBody: {
2664
+ color: t.colors.textMuted,
2665
+ fontFamily: t.fonts.regular,
2666
+ fontSize: 15,
2667
+ textAlign: "center"
2668
+ }
2669
+ });
2670
+ }
2671
+
2672
+ // src/theme/themeFromBrand.ts
2673
+ var hexToRgb = (hex) => {
2674
+ const m = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(hex.trim());
2675
+ if (!m) return null;
2676
+ let h = m[1];
2677
+ if (h.length === 3) h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2];
2678
+ const int = parseInt(h, 16);
2679
+ return [int >> 16 & 255, int >> 8 & 255, int & 255];
2680
+ };
2681
+ var softFromPrimary = (primary) => {
2682
+ const rgb = hexToRgb(primary);
2683
+ if (!rgb) return defaultOnboardingTheme.colors.primarySoft;
2684
+ const [r, g, b] = rgb;
2685
+ return `rgba(${r},${g},${b},0.10)`;
2686
+ };
2687
+ var themeFromBrand = (input) => {
2688
+ const { primary, background, surface, text, onPrimary, font, extra } = input;
2689
+ const derived = {
2690
+ colors: {
2691
+ ...defaultOnboardingTheme.colors,
2692
+ primary,
2693
+ primarySoft: softFromPrimary(primary),
2694
+ onPrimary: onPrimary != null ? onPrimary : defaultOnboardingTheme.colors.onPrimary,
2695
+ ...background ? { background } : {},
2696
+ ...surface ? { surface } : {},
2697
+ ...text ? { text } : {}
2698
+ },
2699
+ ...font ? {
2700
+ fonts: {
2701
+ ...defaultOnboardingTheme.fonts,
2702
+ regular: font,
2703
+ medium: font,
2704
+ bold: font
2705
+ }
2706
+ } : {}
2707
+ };
2708
+ const base = mergeTheme(derived);
2709
+ if (!extra) return base;
2710
+ return {
2711
+ colors: { ...base.colors, ...extra.colors },
2712
+ fonts: { ...base.fonts, ...extra.fonts },
2713
+ radius: { ...base.radius, ...extra.radius },
2714
+ spacing: { ...base.spacing, ...extra.spacing },
2715
+ button: { ...base.button, ...extra.button }
2716
+ };
2717
+ };
2718
+ var _DoneBlock = ({
2719
+ title = "You're all set",
2720
+ message = "Personalizing your experience\u2026"
2721
+ }) => {
2722
+ const t = useOnboardingTheme();
2723
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles18.center, { gap: t.spacing.sm, padding: t.spacing.lg }], children: [
2724
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [headingStyle(t.fonts), { color: t.colors.success, textAlign: "center" }], children: title }),
2725
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [bodyStyle(t.fonts), { color: t.colors.textMuted, textAlign: "center" }], children: message })
2726
+ ] });
2727
+ };
2728
+ var DoneBlock = React14__default.default.memo(_DoneBlock);
2729
+ var styles18 = reactNative.StyleSheet.create({
2730
+ center: { flex: 1, alignItems: "center", justifyContent: "center" }
2731
+ });
2732
+
2733
+ // src/motion/centeredModalMotion.ts
2734
+ var CENTERED_MODAL_ANIM = {
2735
+ /** Spring config for the card scale/opacity entrance (mirrors CompletionView's pop). */
2736
+ enterSpring: { friction: 7, tension: 80 },
2737
+ /** Exit timing in ms (fade + shrink back out before unmount). */
2738
+ exitMs: 160,
2739
+ /** Card scale at rest (progress 0); grows to 1 at progress 1. */
2740
+ scaleFrom: 0.9,
2741
+ /** Peak dim of the backdrop at progress 1. */
2742
+ backdropOpacity: 0.55
2743
+ };
2744
+ var _CenteredModal = React14.forwardRef(
2745
+ ({ theme, dismissible = true, onDismiss, children }, ref) => {
2746
+ const progress = React14.useRef(new reactNative.Animated.Value(0)).current;
2747
+ const [open, setOpen] = React14.useState(true);
2748
+ const closingRef = React14.useRef(false);
2749
+ const reduced = useReducedMotion();
2750
+ React14.useEffect(() => {
2751
+ const anim = reduced ? reactNative.Animated.timing(progress, {
2752
+ toValue: 1,
2753
+ duration: REDUCED_FADE_MS,
2754
+ useNativeDriver: true
2755
+ }) : reactNative.Animated.spring(progress, {
2756
+ toValue: 1,
2757
+ useNativeDriver: true,
2758
+ friction: CENTERED_MODAL_ANIM.enterSpring.friction,
2759
+ tension: CENTERED_MODAL_ANIM.enterSpring.tension
2760
+ });
2761
+ anim.start();
2762
+ return () => anim.stop();
2763
+ }, [progress, reduced]);
2764
+ const close = React14.useCallback(() => {
2765
+ if (closingRef.current) return;
2766
+ closingRef.current = true;
2767
+ reactNative.Animated.timing(progress, {
2768
+ toValue: 0,
2769
+ duration: reduced ? REDUCED_FADE_MS : CENTERED_MODAL_ANIM.exitMs,
2770
+ easing: reactNative.Easing.in(reactNative.Easing.cubic),
2771
+ useNativeDriver: true
2772
+ }).start(() => {
2773
+ setOpen(false);
2774
+ onDismiss();
2775
+ });
2776
+ }, [progress, onDismiss, reduced]);
2777
+ React14.useImperativeHandle(ref, () => ({ close }), [close]);
2778
+ React14.useEffect(() => {
2779
+ const sub = reactNative.BackHandler.addEventListener("hardwareBackPress", () => {
2780
+ if (dismissible) close();
2781
+ return true;
2782
+ });
2783
+ return () => sub.remove();
2784
+ }, [dismissible, close]);
2785
+ const onBackdrop = React14.useCallback(() => {
2786
+ if (dismissible) close();
2787
+ }, [dismissible, close]);
2788
+ const backdropOpacity = progress.interpolate({
2789
+ inputRange: [0, 1],
2790
+ outputRange: [0, CENTERED_MODAL_ANIM.backdropOpacity]
2791
+ });
2792
+ const cardScale = reduced ? 1 : progress.interpolate({
2793
+ inputRange: [0, 1],
2794
+ outputRange: [CENTERED_MODAL_ANIM.scaleFrom, 1]
2795
+ });
2796
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2797
+ reactNative.Modal,
2798
+ {
2799
+ visible: open,
2800
+ transparent: true,
2801
+ animationType: "none",
2802
+ statusBarTranslucent: true,
2803
+ onRequestClose: dismissible ? close : void 0,
2804
+ children: [
2805
+ /* @__PURE__ */ jsxRuntime.jsx(
2806
+ reactNative.Animated.View,
2807
+ {
2808
+ style: [styles19.backdrop, { opacity: backdropOpacity }],
2809
+ pointerEvents: "none"
2810
+ }
2811
+ ),
2812
+ /* @__PURE__ */ jsxRuntime.jsx(
2813
+ reactNative.Pressable,
2814
+ {
2815
+ style: styles19.fill,
2816
+ onPress: onBackdrop,
2817
+ accessibilityRole: "button",
2818
+ accessibilityLabel: "Dismiss",
2819
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNativeSafeAreaContext.SafeAreaView, { style: styles19.center, pointerEvents: "box-none", children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: () => {
2820
+ }, style: styles19.cardWrap, children: /* @__PURE__ */ jsxRuntime.jsx(
2821
+ reactNative.Animated.View,
2822
+ {
2823
+ accessibilityViewIsModal: true,
2824
+ accessibilityRole: "alert",
2825
+ style: [
2826
+ styles19.card,
2827
+ {
2828
+ backgroundColor: theme.colors.surface,
2829
+ borderRadius: theme.radius.lg,
2830
+ padding: theme.spacing.lg,
2831
+ gap: theme.spacing.md,
2832
+ opacity: progress,
2833
+ transform: [{ scale: cardScale }]
2834
+ }
2835
+ ],
2836
+ children
2837
+ }
2838
+ ) }) })
2839
+ }
2840
+ )
2841
+ ]
2842
+ }
2843
+ );
2844
+ }
2845
+ );
2846
+ _CenteredModal.displayName = "CenteredModal";
2847
+ var CenteredModal = React14__default.default.memo(_CenteredModal);
2848
+ var styles19 = reactNative.StyleSheet.create({
2849
+ fill: { flex: 1 },
2850
+ backdrop: { position: "absolute", top: 0, left: 0, right: 0, bottom: 0, backgroundColor: "#000" },
2851
+ center: { flex: 1, alignItems: "center", justifyContent: "center", padding: 24 },
2852
+ cardWrap: { width: "100%", maxWidth: 420 },
2853
+ card: {
2854
+ width: "100%",
2855
+ // Soft elevation so the popup reads as lifted above the app in both themes.
2856
+ shadowColor: "#000",
2857
+ shadowOffset: { width: 0, height: 8 },
2858
+ shadowOpacity: 0.28,
2859
+ shadowRadius: 24,
2860
+ elevation: 12
2861
+ }
2862
+ });
2863
+
2864
+ // src/features/defaults.ts
2865
+ var defaultWireFeatures = Object.freeze({
2866
+ coachmarks: Object.freeze({ enabled: true }),
2867
+ showcase: Object.freeze({ enabled: true }),
2868
+ onboarding: Object.freeze({ mode: "ai" }),
2869
+ review: Object.freeze({ enabled: true }),
2870
+ questionnaire: Object.freeze({ enabled: true }),
2871
+ announcements: Object.freeze({ enabled: true })
2872
+ });
2873
+ var VALID_MODES = ["ai", "static", "off"];
2874
+ var readEnabled = (raw) => {
2875
+ if (raw && typeof raw === "object" && typeof raw.enabled === "boolean") {
2876
+ return { enabled: raw.enabled };
2877
+ }
2878
+ return { enabled: true };
2879
+ };
2880
+ var parseWireFeatures = (raw) => {
2881
+ var _a;
2882
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return defaultWireFeatures;
2883
+ const src = raw;
2884
+ const modeRaw = (_a = src.onboarding) == null ? void 0 : _a.mode;
2885
+ const mode = typeof modeRaw === "string" && VALID_MODES.includes(modeRaw) ? modeRaw : "ai";
2886
+ return {
2887
+ coachmarks: readEnabled(src.coachmarks),
2888
+ showcase: readEnabled(src.showcase),
2889
+ onboarding: { mode },
2890
+ review: readEnabled(src.review),
2891
+ questionnaire: readEnabled(src.questionnaire),
2892
+ announcements: readEnabled(src.announcements)
2893
+ };
2894
+ };
2895
+ var featuresEqual = (a, b) => a === b || a.coachmarks.enabled === b.coachmarks.enabled && a.showcase.enabled === b.showcase.enabled && a.onboarding.mode === b.onboarding.mode && a.review.enabled === b.review.enabled && a.questionnaire.enabled === b.questionnaire.enabled && a.announcements.enabled === b.announcements.enabled;
2896
+
2897
+ // src/features/cache.ts
2898
+ var DEFAULT_FEATURES_TTL_MS = 3e5;
2899
+ var READ_TIMEOUT_MS2 = 1500;
2900
+ var featuresCacheKey = (appId) => `wireai:features:${appId && appId.length > 0 ? appId : "default"}`;
2901
+ var withTimeout2 = (p, ms) => {
2902
+ let timer;
2903
+ const timeout = new Promise((resolve) => {
2904
+ timer = setTimeout(() => resolve(void 0), ms);
2905
+ });
2906
+ return Promise.race([p, timeout]).finally(() => clearTimeout(timer));
2907
+ };
2908
+ var isFeaturesFresh = (ageMs, ttlMs = DEFAULT_FEATURES_TTL_MS) => ageMs >= 0 && ageMs < ttlMs;
2909
+ var readCachedFeatures = async (storage, key) => {
2910
+ let raw;
2911
+ try {
2912
+ raw = await withTimeout2(storage.getItem(key), READ_TIMEOUT_MS2);
2913
+ } catch {
2914
+ return void 0;
2915
+ }
2916
+ if (!raw) return void 0;
2917
+ try {
2918
+ const parsed = JSON.parse(raw);
2919
+ if (parsed && typeof parsed === "object" && typeof parsed.ts === "number") {
2920
+ const ts = parsed.ts;
2921
+ return {
2922
+ features: parseWireFeatures(parsed.features),
2923
+ ageMs: Date.now() - ts
2924
+ };
2925
+ }
2926
+ } catch {
2927
+ }
2928
+ return void 0;
2929
+ };
2930
+ var writeCachedFeatures = (storage, key, features) => {
2931
+ try {
2932
+ void storage.setItem(key, JSON.stringify({ features, ts: Date.now() })).catch(() => {
2933
+ });
2934
+ } catch {
2935
+ }
2936
+ };
2937
+
2938
+ // src/features/fetchWireFeatures.ts
2939
+ var DEFAULT_TIMEOUT_MS = 4e3;
2940
+ var failOpen = async (storage, key) => {
2941
+ var _a;
2942
+ if (!storage) return defaultWireFeatures;
2943
+ const cached = await readCachedFeatures(storage, key);
2944
+ return (_a = cached == null ? void 0 : cached.features) != null ? _a : defaultWireFeatures;
2945
+ };
2946
+ var fetchWithTimeout = async (url, apiKey, timeoutMs) => {
2947
+ const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
2948
+ const timer = setTimeout(() => controller == null ? void 0 : controller.abort(), timeoutMs);
2949
+ try {
2950
+ return await fetch(url, {
2951
+ method: "GET",
2952
+ headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
2953
+ signal: controller == null ? void 0 : controller.signal
2954
+ });
2955
+ } finally {
2956
+ clearTimeout(timer);
2957
+ }
2958
+ };
2959
+ var fetchWireFeatures = async (config) => {
2960
+ const key = featuresCacheKey(config == null ? void 0 : config.appId);
2961
+ const storage = config == null ? void 0 : config.storage;
2962
+ if (!(config == null ? void 0 : config.serverUrl) || !(config == null ? void 0 : config.apiKey)) return failOpen(storage, key);
2963
+ const url = `${config.serverUrl.replace(/\/$/, "")}/v1/features`;
2964
+ const timeoutMs = config.timeoutMs && config.timeoutMs > 0 ? config.timeoutMs : DEFAULT_TIMEOUT_MS;
2965
+ try {
2966
+ const res = await fetchWithTimeout(url, config.apiKey, timeoutMs);
2967
+ if (!res || !res.ok) return failOpen(storage, key);
2968
+ const json = await res.json();
2969
+ const features = parseWireFeatures(json);
2970
+ if (storage) writeCachedFeatures(storage, key, features);
2971
+ return features;
2972
+ } catch {
2973
+ return failOpen(storage, key);
2974
+ }
2975
+ };
2976
+ var useStableValue = (value, isEqual) => {
2977
+ const ref = React14.useRef(value);
2978
+ if (!isEqual(ref.current, value)) ref.current = value;
2979
+ return ref.current;
2980
+ };
2981
+ var sameFetchKey = (a, b) => (a == null ? void 0 : a.serverUrl) === (b == null ? void 0 : b.serverUrl) && (a == null ? void 0 : a.apiKey) === (b == null ? void 0 : b.apiKey) && (a == null ? void 0 : a.appId) === (b == null ? void 0 : b.appId);
2982
+ var useWireFeatures = (config) => {
2983
+ const stableConfig = useStableValue(config, sameFetchKey);
2984
+ const [flags, setFlags] = React14.useState(defaultWireFeatures);
2985
+ const canFetch = !!(stableConfig == null ? void 0 : stableConfig.serverUrl) && !!(stableConfig == null ? void 0 : stableConfig.apiKey);
2986
+ React14.useEffect(() => {
2987
+ if (!canFetch) {
2988
+ setFlags((prev) => featuresEqual(prev, defaultWireFeatures) ? prev : defaultWireFeatures);
2989
+ return;
2990
+ }
2991
+ let alive = true;
2992
+ void fetchWireFeatures(stableConfig).then((next) => {
2993
+ if (!alive) return;
2994
+ setFlags((prev) => featuresEqual(prev, next) ? prev : next);
2995
+ });
2996
+ return () => {
2997
+ alive = false;
2998
+ };
2999
+ }, [canFetch, stableConfig]);
3000
+ return flags;
3001
+ };
3002
+ var WireFeaturesContext = React14.createContext(null);
3003
+ var WireFeaturesProvider = ({
3004
+ config,
3005
+ flags,
3006
+ children
3007
+ }) => {
3008
+ const fetched = useWireFeatures(flags ? void 0 : config);
3009
+ const value = flags != null ? flags : fetched;
3010
+ const stable = React14.useMemo(() => value, [value]);
3011
+ return /* @__PURE__ */ jsxRuntime.jsx(WireFeaturesContext.Provider, { value: stable, children });
3012
+ };
3013
+ WireFeaturesProvider.displayName = "WireFeaturesProvider";
3014
+ var useWireFeaturesContext = () => React14.useContext(WireFeaturesContext);
3015
+ var useResolvedFeatures = (options) => {
3016
+ var _a, _b, _c;
3017
+ const ctx = useWireFeaturesContext();
3018
+ const shouldFetch = !(options == null ? void 0 : options.flags) && ctx == null;
3019
+ const fetched = useWireFeatures(shouldFetch ? options == null ? void 0 : options.config : void 0);
3020
+ return (_c = (_b = (_a = options == null ? void 0 : options.flags) != null ? _a : ctx) != null ? _b : fetched) != null ? _c : defaultWireFeatures;
3021
+ };
3022
+
3023
+ // src/config/wireConfigFromEnv.ts
3024
+ var wireConfigFromEnv = (overrides) => {
3025
+ var _a, _b, _c, _d;
3026
+ const apiKey = (_a = overrides == null ? void 0 : overrides.apiKey) != null ? _a : process.env.EXPO_PUBLIC_WIREAI_API_KEY;
3027
+ const serverUrl = (_b = overrides == null ? void 0 : overrides.serverUrl) != null ? _b : process.env.EXPO_PUBLIC_WIREAI_SERVER_URL;
3028
+ const appId = (_d = (_c = overrides == null ? void 0 : overrides.appId) != null ? _c : process.env.EXPO_PUBLIC_WIREAI_APP_ID) != null ? _d : "default";
3029
+ if (!apiKey || !serverUrl) return null;
3030
+ return {
3031
+ apiKey,
3032
+ serverUrl,
3033
+ appId,
3034
+ ...(overrides == null ? void 0 : overrides.metadata) ? { metadata: overrides.metadata } : {}
3035
+ };
3036
+ };
3037
+
3038
+ // src/config/onboardingFlag.ts
3039
+ var isOnboardingEnabled = (opts) => {
3040
+ var _a;
3041
+ const transportPresent = wireConfigFromEnv(opts == null ? void 0 : opts.config) !== null;
3042
+ if (!transportPresent) return false;
3043
+ return (_a = opts == null ? void 0 : opts.remote) != null ? _a : true;
3044
+ };
3045
+
3046
+ // src/analytics/analyticsEvent.ts
3047
+ var WIRE_ONBOARDING_EVENTS = {
3048
+ started: "wire_onboarding_started",
3049
+ /** A persisted session was restored after an app kill (fires instead of `started`). */
3050
+ resumed: "wire_onboarding_resumed",
3051
+ turn: "wire_onboarding_turn",
3052
+ error: "wire_onboarding_error",
3053
+ retry: "wire_onboarding_retry",
3054
+ fallback: "wire_onboarding_fallback",
3055
+ /** Logged by the host on `onComplete` (no matching kit `OnboardingEvent`). */
3056
+ completed: "wire_onboarding_completed"
3057
+ };
3058
+ var toAnalyticsEvent = (event) => {
3059
+ switch (event.type) {
3060
+ case "started":
3061
+ return { name: WIRE_ONBOARDING_EVENTS.started };
3062
+ case "resumed":
3063
+ return { name: WIRE_ONBOARDING_EVENTS.resumed };
3064
+ case "turn":
3065
+ return {
3066
+ name: WIRE_ONBOARDING_EVENTS.turn,
3067
+ params: { step: event.step, component: event.component }
3068
+ };
3069
+ case "error":
3070
+ return { name: WIRE_ONBOARDING_EVENTS.error, params: { reason: event.reason } };
3071
+ case "retry":
3072
+ return {
3073
+ name: WIRE_ONBOARDING_EVENTS.retry,
3074
+ params: { reason: event.reason, attempt: event.attempt }
3075
+ };
3076
+ case "fallback":
3077
+ return { name: WIRE_ONBOARDING_EVENTS.fallback, params: { reason: event.reason } };
3078
+ default: {
3079
+ const _exhaustive = event;
3080
+ return _exhaustive;
3081
+ }
3082
+ }
3083
+ };
3084
+
3085
+ // src/attribution/attribution.ts
3086
+ var attributionMetadata = (a) => {
3087
+ const attribution = {};
3088
+ for (const [key, value] of Object.entries(a)) {
3089
+ if (value !== void 0) attribution[key] = value;
3090
+ }
3091
+ return { attribution };
3092
+ };
3093
+
3094
+ // src/session-analytics/reportSessionStart.ts
3095
+ var SESSION_STARTED_EVENT = "app.session_started";
3096
+ var _emitted = /* @__PURE__ */ new Set();
3097
+ var _GUARD_MAX = 128;
3098
+ var resetSessionStartGuard = () => {
3099
+ _emitted.clear();
3100
+ };
3101
+ var reportSessionStart = (opts) => {
3102
+ var _a;
3103
+ const target = opts.target;
3104
+ if (!(target == null ? void 0 : target.serverUrl)) return;
3105
+ const sessionId = (_a = opts.sessionId) != null ? _a : makeSessionId();
3106
+ if (opts.once !== false) {
3107
+ if (_emitted.has(sessionId)) return;
3108
+ if (_emitted.size >= _GUARD_MAX) {
3109
+ const oldest = _emitted.values().next().value;
3110
+ if (oldest !== void 0) _emitted.delete(oldest);
3111
+ }
3112
+ _emitted.add(sessionId);
3113
+ }
3114
+ try {
3115
+ const url = `${target.serverUrl.replace(/\/$/, "")}/v1/events`;
3116
+ const headers = { "Content-Type": "application/json" };
3117
+ if (target.apiKey) headers.Authorization = `Bearer ${target.apiKey}`;
3118
+ const userContext = {};
3119
+ if (opts.deviceKey) userContext.device_key = opts.deviceKey;
3120
+ if (typeof opts.sessionCount === "number" && Number.isFinite(opts.sessionCount)) {
3121
+ userContext.session_count = opts.sessionCount;
3122
+ userContext.returning = opts.sessionCount > 1;
3123
+ }
3124
+ if (opts.appVersion) userContext.app_version = opts.appVersion;
3125
+ if (opts.platform) userContext.platform = opts.platform;
3126
+ const event = {
3127
+ event_type: "app_event",
3128
+ question_key: SESSION_STARTED_EVENT,
3129
+ session_id: sessionId
3130
+ };
3131
+ const userId = sanitizeUserId(opts.userId);
3132
+ if (userId) event.user_id = userId;
3133
+ if (Object.keys(userContext).length > 0) event.user_context = userContext;
3134
+ if (opts.device) event.device = opts.device;
3135
+ if (opts.meta && Object.keys(opts.meta).length > 0) event.meta = JSON.stringify(opts.meta);
3136
+ void fetch(url, {
3137
+ method: "POST",
3138
+ headers,
3139
+ body: JSON.stringify({ events: [event] })
3140
+ }).catch(() => {
3141
+ });
3142
+ } catch {
3143
+ }
3144
+ };
3145
+ var BACKGROUND_SESSION_MS = 30 * 60 * 1e3;
3146
+ var useSessionStart = (config, options = {}) => {
3147
+ const latest = React14.useRef({ config, options });
3148
+ latest.current = { config, options };
3149
+ React14.useEffect(() => {
3150
+ const fire = () => {
3151
+ var _a, _b;
3152
+ const { config: cfg, options: opts } = latest.current;
3153
+ if (!(cfg == null ? void 0 : cfg.serverUrl)) return;
3154
+ if (opts.enabled === false) return;
3155
+ const target = { serverUrl: cfg.serverUrl, apiKey: (_a = cfg.apiKey) != null ? _a : "" };
3156
+ const device = collectDeviceContext();
3157
+ if (cfg.appVersion && !device.appVersion) device.appVersion = cfg.appVersion;
3158
+ reportSessionStart({
3159
+ target,
3160
+ // A fresh per-open id each fire; the emitter's once-guard dedupes within the open.
3161
+ userId: opts.userId,
3162
+ deviceKey: opts.deviceKey,
3163
+ sessionCount: opts.sessionCount,
3164
+ appVersion: (_b = cfg.appVersion) != null ? _b : device.appVersion,
3165
+ platform: reactNative.Platform.OS,
3166
+ device,
3167
+ meta: opts.meta
3168
+ });
3169
+ };
3170
+ fire();
3171
+ let backgroundedAt = null;
3172
+ const onChange = (state) => {
3173
+ if (state === "background" || state === "inactive") {
3174
+ if (backgroundedAt == null) backgroundedAt = Date.now();
3175
+ return;
3176
+ }
3177
+ if (state === "active") {
3178
+ const since = backgroundedAt;
3179
+ backgroundedAt = null;
3180
+ if (since != null && Date.now() - since >= BACKGROUND_SESSION_MS) fire();
3181
+ }
3182
+ };
3183
+ const sub = reactNative.AppState.addEventListener("change", onChange);
3184
+ return () => {
3185
+ if (sub && typeof sub.remove === "function") sub.remove();
3186
+ };
3187
+ }, []);
3188
+ };
3189
+
3190
+ exports.AnimatedSparkle = AnimatedSparkle;
3191
+ exports.BACKGROUND_SESSION_MS = BACKGROUND_SESSION_MS;
3192
+ exports.CardHandoff = CardHandoff;
3193
+ exports.CenteredModal = CenteredModal;
3194
+ exports.ChipSelectCard = ChipSelectCard;
3195
+ exports.CompletionView = CompletionView;
3196
+ exports.DEFAULT_FEATURES_TTL_MS = DEFAULT_FEATURES_TTL_MS;
3197
+ exports.DEFAULT_SESSION_TTL_MS = DEFAULT_SESSION_TTL_MS;
3198
+ exports.DemoOnboarding = DemoOnboarding;
3199
+ exports.DoneBlock = DoneBlock;
3200
+ exports.ErrorBlock = ErrorBlock;
3201
+ exports.IllustrationProvider = IllustrationProvider;
3202
+ exports.InterstitialCard = InterstitialCard;
3203
+ exports.LoadingBlock = LoadingBlock;
3204
+ exports.LoadingScreen = LoadingScreen;
3205
+ exports.NumberStepperCard = NumberStepperCard;
3206
+ exports.OnboardingButton = Button;
3207
+ exports.OnboardingFlow = OnboardingFlow;
3208
+ exports.OnboardingScaffold = OnboardingScaffold;
3209
+ exports.OnboardingThemeProvider = OnboardingThemeProvider;
3210
+ exports.SESSION_STARTED_EVENT = SESSION_STARTED_EVENT;
3211
+ exports.SelectionCard = SelectionCard;
3212
+ exports.StatusCard = StatusCard;
3213
+ exports.StepProgress = StepProgress;
3214
+ exports.TextInputCard = TextInputCard;
3215
+ exports.USER_ID_MAX_LENGTH = USER_ID_MAX_LENGTH;
3216
+ exports.WIRE_ONBOARDING_EVENTS = WIRE_ONBOARDING_EVENTS;
3217
+ exports.WireFeaturesProvider = WireFeaturesProvider;
3218
+ exports.WireOnboarding = WireOnboarding;
3219
+ exports.attributionMetadata = attributionMetadata;
3220
+ exports.clearPersistedSession = clearPersistedSession;
3221
+ exports.collectDeviceContext = collectDeviceContext;
3222
+ exports.defaultIllustrations = defaultIllustrations;
3223
+ exports.defaultOnboardingTheme = defaultOnboardingTheme;
3224
+ exports.defaultWireFeatures = defaultWireFeatures;
3225
+ exports.deriveAnswers = deriveAnswers;
3226
+ exports.featuresCacheKey = featuresCacheKey;
3227
+ exports.featuresEqual = featuresEqual;
3228
+ exports.fetchWireFeatures = fetchWireFeatures;
3229
+ exports.identifyOnboarding = identifyOnboarding;
3230
+ exports.isFeaturesFresh = isFeaturesFresh;
3231
+ exports.isOnboardingEnabled = isOnboardingEnabled;
3232
+ exports.loadPersistedSession = loadPersistedSession;
3233
+ exports.makeSessionId = makeSessionId;
3234
+ exports.mergeTheme = mergeTheme;
3235
+ exports.motionSpec = motionSpec_exports;
3236
+ exports.onboardingComponents = onboardingComponents;
3237
+ exports.parseWireFeatures = parseWireFeatures;
3238
+ exports.peekPersistedSession = peekPersistedSession;
3239
+ exports.readCachedFeatures = readCachedFeatures;
3240
+ exports.readProgress = readProgress;
3241
+ exports.reportClientEvent = reportClientEvent;
3242
+ exports.reportClientEvents = reportClientEvents;
3243
+ exports.reportSessionStart = reportSessionStart;
3244
+ exports.resetSessionStartGuard = resetSessionStartGuard;
3245
+ exports.sanitizeUserId = sanitizeUserId;
3246
+ exports.savePersistedSession = savePersistedSession;
3247
+ exports.sessionStorageKey = sessionStorageKey;
3248
+ exports.themeFromBrand = themeFromBrand;
3249
+ exports.toAnalyticsEvent = toAnalyticsEvent;
3250
+ exports.useIllustration = useIllustration;
3251
+ exports.useOnboardingTheme = useOnboardingTheme;
3252
+ exports.useReducedMotion = useReducedMotion;
3253
+ exports.useResolvedFeatures = useResolvedFeatures;
3254
+ exports.useSessionStart = useSessionStart;
3255
+ exports.useWireFeatures = useWireFeatures;
3256
+ exports.useWireFeaturesContext = useWireFeaturesContext;
3257
+ exports.wireConfigFromEnv = wireConfigFromEnv;
3258
+ exports.writeCachedFeatures = writeCachedFeatures;
3259
+ //# sourceMappingURL=index.js.map
3260
+ //# sourceMappingURL=index.js.map