@scalebun-release/react-native 2.5.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 (1203) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +154 -0
  3. package/android/build.gradle +94 -0
  4. package/android/consumer-rules.pro +28 -0
  5. package/android/src/androidTest/java/com/scalebun/rn/ota/ScaleBunOtaVerifierInstrumentedTest.kt +112 -0
  6. package/android/src/main/AndroidManifest.xml +14 -0
  7. package/android/src/main/java/com/scalebun/core/crash/CrashStorage.kt +155 -0
  8. package/android/src/main/java/com/scalebun/core/crash/NativeCrashHandler.kt +93 -0
  9. package/android/src/main/java/com/scalebun/core/performance/ANRWatchdog.kt +100 -0
  10. package/android/src/main/java/com/scalebun/core/performance/AppStartCollector.kt +180 -0
  11. package/android/src/main/java/com/scalebun/core/performance/FrameMetricsCollector.kt +230 -0
  12. package/android/src/main/java/com/scalebun/core/performance/PerformanceCore.kt +165 -0
  13. package/android/src/main/java/com/scalebun/profiler/ProfilerModule.kt +398 -0
  14. package/android/src/main/java/com/scalebun/profiler/ProfilerPackage.kt +24 -0
  15. package/android/src/main/java/com/scalebun/profiler/collectors/CpuCollector.kt +161 -0
  16. package/android/src/main/java/com/scalebun/profiler/collectors/IncidentDetector.kt +223 -0
  17. package/android/src/main/java/com/scalebun/profiler/collectors/MemoryCollector.kt +157 -0
  18. package/android/src/main/java/com/scalebun/profiler/collectors/RenderCollector.kt +194 -0
  19. package/android/src/main/java/com/scalebun/profiler/collectors/SamplerScheduler.kt +218 -0
  20. package/android/src/main/java/com/scalebun/profiler/collectors/ThreadCollector.kt +142 -0
  21. package/android/src/main/java/com/scalebun/replaysdk/ReplaySdkModule.kt +1117 -0
  22. package/android/src/main/java/com/scalebun/replaysdk/ReplaySdkPackage.kt +24 -0
  23. package/android/src/main/java/com/scalebun/replaysdk/capture/CaptureReason.kt +47 -0
  24. package/android/src/main/java/com/scalebun/replaysdk/capture/FrameCaptureManager.kt +574 -0
  25. package/android/src/main/java/com/scalebun/replaysdk/capture/FrameCaptureScheduler.kt +187 -0
  26. package/android/src/main/java/com/scalebun/replaysdk/capture/UiStabilityDetector.kt +214 -0
  27. package/android/src/main/java/com/scalebun/replaysdk/core/ReplayConfig.kt +144 -0
  28. package/android/src/main/java/com/scalebun/replaysdk/core/ReplayLogger.kt +31 -0
  29. package/android/src/main/java/com/scalebun/replaysdk/core/SessionIdGenerator.kt +15 -0
  30. package/android/src/main/java/com/scalebun/replaysdk/models/ReplayModels.kt +94 -0
  31. package/android/src/main/java/com/scalebun/replaysdk/outbox/NativeOutbox.kt +574 -0
  32. package/android/src/main/java/com/scalebun/replaysdk/outbox/OutboxUploader.kt +559 -0
  33. package/android/src/main/java/com/scalebun/replaysdk/privacy/PrivacyMaskProcessor.kt +126 -0
  34. package/android/src/main/java/com/scalebun/replaysdk/session/SessionManager.kt +322 -0
  35. package/android/src/main/java/com/scalebun/replaysdk/storage/ReplayStorage.kt +136 -0
  36. package/android/src/main/java/com/scalebun/replaysdk/timeline/TimelineCollector.kt +28 -0
  37. package/android/src/main/java/com/scalebun/replaysdk/tracking/InteractionTracker.kt +470 -0
  38. package/android/src/main/java/com/scalebun/replaysdk/tracking/ScreenTracker.kt +20 -0
  39. package/android/src/main/java/com/scalebun/replaysdk/tracking/ScrollContextProbe.kt +188 -0
  40. package/android/src/main/java/com/scalebun/replaysdk/transport/ReplayTransport.kt +99 -0
  41. package/android/src/main/java/com/scalebun/replaysdk/voice/VoiceRecorder.kt +121 -0
  42. package/android/src/main/java/com/scalebun/replaysdk/voice/VoiceTranscriber.kt +170 -0
  43. package/android/src/main/java/com/scalebun/rn/ScaleBunPackage.kt +113 -0
  44. package/android/src/main/java/com/scalebun/rn/crash/ScaleBunCrashModule.kt +188 -0
  45. package/android/src/main/java/com/scalebun/rn/crash/ScaleBunCrashPackage.kt +22 -0
  46. package/android/src/main/java/com/scalebun/rn/engage/ScaleBunEngageModule.kt +579 -0
  47. package/android/src/main/java/com/scalebun/rn/engage/ScaleBunEngagePackage.kt +22 -0
  48. package/android/src/main/java/com/scalebun/rn/engage/ScaleBunNotificationPresenter.kt +110 -0
  49. package/android/src/main/java/com/scalebun/rn/ota/BsPatch.kt +240 -0
  50. package/android/src/main/java/com/scalebun/rn/ota/BundleDownloader.kt +195 -0
  51. package/android/src/main/java/com/scalebun/rn/ota/DeviceIntegrity.kt +81 -0
  52. package/android/src/main/java/com/scalebun/rn/ota/DownloadFailure.kt +55 -0
  53. package/android/src/main/java/com/scalebun/rn/ota/OtaProtocol.kt +244 -0
  54. package/android/src/main/java/com/scalebun/rn/ota/ScaleBunOtaKeyRegistry.kt +100 -0
  55. package/android/src/main/java/com/scalebun/rn/ota/ScaleBunOtaModule.kt +683 -0
  56. package/android/src/main/java/com/scalebun/rn/ota/ScaleBunOtaReleaseVerifier.kt +136 -0
  57. package/android/src/main/java/com/scalebun/rn/ota/SlotManager.kt +578 -0
  58. package/android/src/main/java/com/scalebun/rn/ota/TlsPinning.kt +118 -0
  59. package/android/src/main/java/com/scalebun/rn/performance/PerformanceModule.kt +217 -0
  60. package/android/src/main/java/com/scalebun/rn/referrer/ScaleBunInstallReferrerModule.kt +77 -0
  61. package/android/src/main/java/com/scalebun/rn/storage/ScaleBunStorageModule.kt +68 -0
  62. package/android/src/main/java/com/scalebun/storage/KvFileStore.kt +174 -0
  63. package/android/src/newarch/java/com/scalebun/profiler/ProfilerSpec.kt +12 -0
  64. package/android/src/newarch/java/com/scalebun/replaysdk/ReplaySdkSpec.kt +12 -0
  65. package/android/src/newarch/java/com/scalebun/rn/crash/ScaleBunCrashSpec.kt +15 -0
  66. package/android/src/newarch/java/com/scalebun/rn/ota/ScaleBunOtaSpec.kt +16 -0
  67. package/android/src/newarch/java/com/scalebun/rn/performance/PerformanceSpec.kt +12 -0
  68. package/android/src/newarch/java/com/scalebun/rn/referrer/ScaleBunInstallReferrerSpec.kt +14 -0
  69. package/android/src/newarch/java/com/scalebun/rn/storage/ScaleBunStorageSpec.kt +16 -0
  70. package/android/src/oldarch/java/com/scalebun/profiler/ProfilerSpec.kt +34 -0
  71. package/android/src/oldarch/java/com/scalebun/replaysdk/ReplaySdkSpec.kt +77 -0
  72. package/android/src/oldarch/java/com/scalebun/rn/crash/ScaleBunCrashSpec.kt +40 -0
  73. package/android/src/oldarch/java/com/scalebun/rn/ota/ScaleBunOtaSpec.kt +42 -0
  74. package/android/src/oldarch/java/com/scalebun/rn/performance/PerformanceSpec.kt +27 -0
  75. package/android/src/oldarch/java/com/scalebun/rn/referrer/ScaleBunInstallReferrerSpec.kt +18 -0
  76. package/android/src/oldarch/java/com/scalebun/rn/storage/ScaleBunStorageSpec.kt +26 -0
  77. package/bin/lib/androidCodemod.js +370 -0
  78. package/bin/lib/iosCodemod.js +343 -0
  79. package/bin/scalebun.js +1004 -0
  80. package/dist/scalebun.full.js +37348 -0
  81. package/dist/scalebun.slim.js +34137 -0
  82. package/ios/Capture/InteractionTracker.swift +237 -0
  83. package/ios/Capture/PrivacyMaskProcessor.swift +158 -0
  84. package/ios/Capture/ScreenshotCaptureManager.swift +398 -0
  85. package/ios/Capture/WindowResolver.swift +63 -0
  86. package/ios/Core/DeviceIdentity.swift +50 -0
  87. package/ios/Core/ReplayConfig.swift +100 -0
  88. package/ios/Core/ReplayLogger.swift +22 -0
  89. package/ios/Core/SessionIdGenerator.swift +18 -0
  90. package/ios/Crash/ScaleBunCrashBridge.mm +74 -0
  91. package/ios/Crash/ScaleBunCrashHandler.swift +84 -0
  92. package/ios/Crash/ScaleBunCrashModule.swift +98 -0
  93. package/ios/Crash/ScaleBunCrashSignalBridge.h +49 -0
  94. package/ios/Crash/ScaleBunCrashSignalBridge.m +296 -0
  95. package/ios/Crash/ScaleBunCrashSignalStress.c +137 -0
  96. package/ios/Crash/ScaleBunCrashStorage.swift +121 -0
  97. package/ios/Crash/ScaleBunExceptionChain.swift +71 -0
  98. package/ios/CrashTests/ScaleBunExceptionChainTests.swift +144 -0
  99. package/ios/Engage/ScaleBunEngageBridge.m +32 -0
  100. package/ios/Engage/ScaleBunEngageModule.swift +250 -0
  101. package/ios/Engage/ScaleBunPushNotificationCenter.swift +185 -0
  102. package/ios/Ota/BsPatch.swift +203 -0
  103. package/ios/Ota/BundleDownloader.swift +249 -0
  104. package/ios/Ota/DeviceIntegrity.swift +112 -0
  105. package/ios/Ota/DownloadFailure.swift +56 -0
  106. package/ios/Ota/OtaProtocol.swift +246 -0
  107. package/ios/Ota/OtaSlotManager.swift +613 -0
  108. package/ios/Ota/ScaleBunOtaBridge.mm +92 -0
  109. package/ios/Ota/ScaleBunOtaEventsModule.swift +61 -0
  110. package/ios/Ota/ScaleBunOtaKeyRegistry.swift +147 -0
  111. package/ios/Ota/ScaleBunOtaModule.swift +587 -0
  112. package/ios/Ota/ScaleBunOtaReleaseVerifier.swift +119 -0
  113. package/ios/Ota/TlsPinning.swift +222 -0
  114. package/ios/OtaTests/DownloadFailureTests.swift +78 -0
  115. package/ios/OtaTests/ScaleBunOtaVerifierTests.swift +548 -0
  116. package/ios/Outbox/NativeOutbox.swift +632 -0
  117. package/ios/Outbox/OutboxUploader.swift +578 -0
  118. package/ios/Performance/ScaleBunPerformanceBridge.m +32 -0
  119. package/ios/Performance/ScaleBunPerformanceModule.swift +290 -0
  120. package/ios/PrivacyInfo.xcprivacy +97 -0
  121. package/ios/Profiler/ScaleBunProfilerBridge.m +55 -0
  122. package/ios/Profiler/ScaleBunProfilerModule.swift +966 -0
  123. package/ios/ReplaySdk-Bridging-Header.h +2 -0
  124. package/ios/ReplaySdk.swift +793 -0
  125. package/ios/ReplaySdkBridge.m +97 -0
  126. package/ios/Session/SessionManager.swift +273 -0
  127. package/ios/Skan/ScaleBunSkanBridge.m +14 -0
  128. package/ios/Skan/ScaleBunSkanModule.swift +61 -0
  129. package/ios/Storage/ReplayStorage.swift +61 -0
  130. package/ios/Storage/ScaleBunKvFileStore.swift +125 -0
  131. package/ios/Storage/ScaleBunStorageBridge.mm +72 -0
  132. package/ios/Storage/ScaleBunStorageModule.swift +57 -0
  133. package/ios/Transport/ReplayTransport.swift +70 -0
  134. package/ios/VoiceRecorder.swift +145 -0
  135. package/lib/commonjs/analytics/EventTracker.js +598 -0
  136. package/lib/commonjs/analytics/automaticEvents.js +71 -0
  137. package/lib/commonjs/analytics/batching.js +64 -0
  138. package/lib/commonjs/analytics/eventLane.js +55 -0
  139. package/lib/commonjs/analytics/revenue.js +125 -0
  140. package/lib/commonjs/analytics/subscription.js +94 -0
  141. package/lib/commonjs/bootstrap/FeatureRegistry.js +86 -0
  142. package/lib/commonjs/bootstrap/SDKBootstrapper.js +748 -0
  143. package/lib/commonjs/bucketing/fnv1a32.js +45 -0
  144. package/lib/commonjs/compat/codepush.js +153 -0
  145. package/lib/commonjs/config/ConfigManager.js +238 -0
  146. package/lib/commonjs/config/configTypes.js +16 -0
  147. package/lib/commonjs/config/otaPolicyState.js +85 -0
  148. package/lib/commonjs/config/quotaState.js +435 -0
  149. package/lib/commonjs/core/architecture.js +45 -0
  150. package/lib/commonjs/core/clock/now.js +13 -0
  151. package/lib/commonjs/core/config/defaults.js +2 -0
  152. package/lib/commonjs/core/config/schema.js +512 -0
  153. package/lib/commonjs/core/constants/endpoints.js +19 -0
  154. package/lib/commonjs/core/constants/protocol.js +31 -0
  155. package/lib/commonjs/core/constants/timings.js +39 -0
  156. package/lib/commonjs/core/constants/version.js +18 -0
  157. package/lib/commonjs/core/context/app.js +2 -0
  158. package/lib/commonjs/core/context/device.js +155 -0
  159. package/lib/commonjs/core/context/session.js +2 -0
  160. package/lib/commonjs/core/context/user.js +2 -0
  161. package/lib/commonjs/core/contracts/IFeature.js +2 -0
  162. package/lib/commonjs/core/di/container.js +29 -0
  163. package/lib/commonjs/core/encoding/base64.js +50 -0
  164. package/lib/commonjs/core/id/deviceId.js +52 -0
  165. package/lib/commonjs/core/id/installationId.js +55 -0
  166. package/lib/commonjs/core/id/sessionId.js +2 -0
  167. package/lib/commonjs/core/lifecycle/appLifecycle.js +31 -0
  168. package/lib/commonjs/core/lifecycle/crashSafe.js +33 -0
  169. package/lib/commonjs/core/logger/errorClassification.js +107 -0
  170. package/lib/commonjs/core/logger/internalLogger.js +63 -0
  171. package/lib/commonjs/core/logger/levels.js +2 -0
  172. package/lib/commonjs/crypto/hmacSha256.js +132 -0
  173. package/lib/commonjs/debug/bootstrap.js +429 -0
  174. package/lib/commonjs/debug/commands.js +732 -0
  175. package/lib/commonjs/debug/configBuilder.js +70 -0
  176. package/lib/commonjs/debug/exportBridge.js +155 -0
  177. package/lib/commonjs/debug/hostResolver.js +42 -0
  178. package/lib/commonjs/debug/index.js +141 -0
  179. package/lib/commonjs/debug/metrics.js +39 -0
  180. package/lib/commonjs/debug/perf.js +186 -0
  181. package/lib/commonjs/debug/perfWiring.js +109 -0
  182. package/lib/commonjs/debug/protocol.js +121 -0
  183. package/lib/commonjs/debug/redaction.js +422 -0
  184. package/lib/commonjs/debug/replayWiring.js +149 -0
  185. package/lib/commonjs/debug/screenTracking.js +60 -0
  186. package/lib/commonjs/debug/sessionWiring.js +48 -0
  187. package/lib/commonjs/debug/stream.js +542 -0
  188. package/lib/commonjs/debug/transport.js +368 -0
  189. package/lib/commonjs/features/bugreport/BugReportFeature.js +112 -0
  190. package/lib/commonjs/features/bugreport/buildPayload.js +97 -0
  191. package/lib/commonjs/features/bugreport/index.js +2 -0
  192. package/lib/commonjs/features/crash/CrashFeature.js +86 -0
  193. package/lib/commonjs/features/crash/CrashReporter.js +160 -0
  194. package/lib/commonjs/features/crash/NativeCrashFeature.js +42 -0
  195. package/lib/commonjs/features/crash/capture.js +48 -0
  196. package/lib/commonjs/features/crash/globalHandler.js +41 -0
  197. package/lib/commonjs/features/crash/index.js +14 -0
  198. package/lib/commonjs/features/crash/nativeCrashBridge.js +361 -0
  199. package/lib/commonjs/features/crash/rejectionHandler.js +180 -0
  200. package/lib/commonjs/features/crash/scrubCrash.js +158 -0
  201. package/lib/commonjs/features/engage/EngageAnchor.js +132 -0
  202. package/lib/commonjs/features/engage/EngageArchetypeRenderers.js +4292 -0
  203. package/lib/commonjs/features/engage/EngageInAppView.js +2670 -0
  204. package/lib/commonjs/features/engage/EngageInlinePlacement.js +133 -0
  205. package/lib/commonjs/features/engage/EngagePromptProvider.js +1063 -0
  206. package/lib/commonjs/features/engage/EngagePromptView.js +1252 -0
  207. package/lib/commonjs/features/engage/EngageTransport.js +608 -0
  208. package/lib/commonjs/features/engage/EngageVariantContent.js +1778 -0
  209. package/lib/commonjs/features/engage/__test-support__/reactNativeStub.js +54 -0
  210. package/lib/commonjs/features/engage/attachmentCapture.js +48 -0
  211. package/lib/commonjs/features/engage/captureVoiceAttachment.js +88 -0
  212. package/lib/commonjs/features/engage/engageCoachmarkTour.js +106 -0
  213. package/lib/commonjs/features/engage/engageGameLogic.js +308 -0
  214. package/lib/commonjs/features/engage/engageMediaSizing.js +60 -0
  215. package/lib/commonjs/features/engage/engageMultiStepSheet.js +43 -0
  216. package/lib/commonjs/features/engage/engageOutcome.js +118 -0
  217. package/lib/commonjs/features/engage/engagePushTokenBridge.js +72 -0
  218. package/lib/commonjs/features/engage/engageShakeBridge.js +62 -0
  219. package/lib/commonjs/features/engage/engageSignals.js +39 -0
  220. package/lib/commonjs/features/engage/engageStoreReviewBridge.js +49 -0
  221. package/lib/commonjs/features/engage/engageTestSeenStore.js +99 -0
  222. package/lib/commonjs/features/engage/engageThrottle.js +404 -0
  223. package/lib/commonjs/features/engage/engageTriggerEngine.js +365 -0
  224. package/lib/commonjs/features/engage/engageTypes.js +116 -0
  225. package/lib/commonjs/features/engage/engageVariantResolver.js +143 -0
  226. package/lib/commonjs/features/engage/engageWindowInsets.js +98 -0
  227. package/lib/commonjs/features/engage/gestureTriggerDetector.js +85 -0
  228. package/lib/commonjs/features/engage/transcription/TranscriptionProvider.js +55 -0
  229. package/lib/commonjs/features/install-referrer/installReferrer.js +113 -0
  230. package/lib/commonjs/features/journey/ScaleBunDebugRoot.js +1038 -0
  231. package/lib/commonjs/features/journey/ScaleBunFlatList.js +39 -0
  232. package/lib/commonjs/features/journey/ScaleBunImpression.js +113 -0
  233. package/lib/commonjs/features/journey/ScaleBunScrollView.js +102 -0
  234. package/lib/commonjs/features/journey/ScaleBunSectionList.js +39 -0
  235. package/lib/commonjs/features/journey/autoInstrumentScroll.js +161 -0
  236. package/lib/commonjs/features/journey/calibrationContext.js +42 -0
  237. package/lib/commonjs/features/journey/gestureBuffer.js +63 -0
  238. package/lib/commonjs/features/journey/gestureDetector.js +176 -0
  239. package/lib/commonjs/features/journey/impression.js +93 -0
  240. package/lib/commonjs/features/journey/interactionProtocol.js +168 -0
  241. package/lib/commonjs/features/journey/journeyManager.js +466 -0
  242. package/lib/commonjs/features/journey/journeyTypes.js +55 -0
  243. package/lib/commonjs/features/journey/nativeScroll.js +85 -0
  244. package/lib/commonjs/features/journey/navDetector.js +161 -0
  245. package/lib/commonjs/features/journey/screenshotHelper.js +136 -0
  246. package/lib/commonjs/features/journey/scrollContext.js +154 -0
  247. package/lib/commonjs/features/journey/targetGeometry.js +185 -0
  248. package/lib/commonjs/features/journey/targetRegistry.js +53 -0
  249. package/lib/commonjs/features/journey/touchTarget.js +249 -0
  250. package/lib/commonjs/features/journey/uiState.js +188 -0
  251. package/lib/commonjs/features/navigation/AutoScreenDetector.js +607 -0
  252. package/lib/commonjs/features/network/NetworkFeature.js +216 -0
  253. package/lib/commonjs/features/network/index.js +23 -0
  254. package/lib/commonjs/features/network/thirdParty.js +151 -0
  255. package/lib/commonjs/features/ota/OtaEventEmitter.js +129 -0
  256. package/lib/commonjs/features/ota/OtaOrchestrator.js +1309 -0
  257. package/lib/commonjs/features/ota/OtaTypes.js +6 -0
  258. package/lib/commonjs/features/ota/deviceAttributes.js +48 -0
  259. package/lib/commonjs/features/ota/environment.js +172 -0
  260. package/lib/commonjs/features/ota/geoCountry.js +93 -0
  261. package/lib/commonjs/features/ota/retry.js +96 -0
  262. package/lib/commonjs/features/ota/signedDownload.js +187 -0
  263. package/lib/commonjs/features/ota/useOtaUpdate.js +106 -0
  264. package/lib/commonjs/features/performance/PerformanceFeature.js +649 -0
  265. package/lib/commonjs/features/performance/collectors/AppLaunchCollector.js +122 -0
  266. package/lib/commonjs/features/performance/collectors/AutoScreenLoadCollector.js +232 -0
  267. package/lib/commonjs/features/performance/collectors/CustomTraceCollector.js +99 -0
  268. package/lib/commonjs/features/performance/collectors/FrameMetricsCollector.js +184 -0
  269. package/lib/commonjs/features/performance/collectors/JsStallCollector.js +165 -0
  270. package/lib/commonjs/features/performance/collectors/NetworkPerfCollector.js +59 -0
  271. package/lib/commonjs/features/performance/collectors/ScreenLoadCollector.js +78 -0
  272. package/lib/commonjs/features/performance/collectors/UiHangCollector.js +78 -0
  273. package/lib/commonjs/features/performance/config.js +58 -0
  274. package/lib/commonjs/features/performance/getNativePerformanceModule.js +44 -0
  275. package/lib/commonjs/features/performance/index.js +58 -0
  276. package/lib/commonjs/features/performance/metrics/MetricsEngine.js +148 -0
  277. package/lib/commonjs/features/performance/models.js +48 -0
  278. package/lib/commonjs/features/performance/transport/PerformanceTransport.js +168 -0
  279. package/lib/commonjs/features/profiler/ProfilerFeature.js +600 -0
  280. package/lib/commonjs/features/profiler/config.js +151 -0
  281. package/lib/commonjs/features/profiler/contracts.js +62 -0
  282. package/lib/commonjs/features/profiler/index.js +40 -0
  283. package/lib/commonjs/features/profiler/models.js +112 -0
  284. package/lib/commonjs/features/profiler/transport/ProfilerTransport.js +94 -0
  285. package/lib/commonjs/features/replay/bridge/adapters/bridgeAdapter.js +203 -0
  286. package/lib/commonjs/features/replay/bridge/eventEmitter.js +128 -0
  287. package/lib/commonjs/features/replay/bridge/nativeModule.js +95 -0
  288. package/lib/commonjs/features/replay/bridge/nativeQuota.js +121 -0
  289. package/lib/commonjs/features/replay/core/clock/clock.js +47 -0
  290. package/lib/commonjs/features/replay/core/config/defaults.js +88 -0
  291. package/lib/commonjs/features/replay/core/config/validator.js +109 -0
  292. package/lib/commonjs/features/replay/core/env/environmentDetector.js +28 -0
  293. package/lib/commonjs/features/replay/core/errors/safeCall.js +42 -0
  294. package/lib/commonjs/features/replay/core/ids/sessionId.js +53 -0
  295. package/lib/commonjs/features/replay/core/logger/logger.js +36 -0
  296. package/lib/commonjs/features/replay/core/privacy/privacyTypes.js +34 -0
  297. package/lib/commonjs/features/replay/core/queue/boundedQueue.js +71 -0
  298. package/lib/commonjs/features/replay/integrations/alert/alertInstrumentation.js +259 -0
  299. package/lib/commonjs/features/replay/integrations/logs/consoleIntegration.js +139 -0
  300. package/lib/commonjs/features/replay/integrations/network/networkAdapter.js +77 -0
  301. package/lib/commonjs/features/replay/integrations/react-navigation/navigationIntegration.js +88 -0
  302. package/lib/commonjs/features/replay/integrations/touch/ReplayRoot.js +103 -0
  303. package/lib/commonjs/features/replay/pipeline/batching/batchManager.js +119 -0
  304. package/lib/commonjs/features/replay/pipeline/envelope/envelopeBuilder.js +52 -0
  305. package/lib/commonjs/features/replay/pipeline/retry/retryPolicy.js +53 -0
  306. package/lib/commonjs/features/replay/pipeline/transport/transportTypes.js +67 -0
  307. package/lib/commonjs/features/replay/public/api.js +336 -0
  308. package/lib/commonjs/features/replay/public/enums.js +85 -0
  309. package/lib/commonjs/features/replay/public/types.js +6 -0
  310. package/lib/commonjs/features/replay/replay/breadcrumbs/breadcrumbCollector.js +119 -0
  311. package/lib/commonjs/features/replay/replay/frames/frameModels.js +40 -0
  312. package/lib/commonjs/features/replay/replay/session/sessionOrchestrator.js +279 -0
  313. package/lib/commonjs/features/replay/replay/timeline/timelineModels.js +120 -0
  314. package/lib/commonjs/features/replay/transport/BackendReplayTransport.js +175 -0
  315. package/lib/commonjs/features/replay/transport/CompositeReplayTransport.js +75 -0
  316. package/lib/commonjs/features/replay/transport/DesktopReplayTransport.js +124 -0
  317. package/lib/commonjs/features/replay/transport/ReplayTransport.js +2 -0
  318. package/lib/commonjs/features/session/BackendSessionAdapter.js +1915 -0
  319. package/lib/commonjs/features/session/DesktopSessionTransport.js +124 -0
  320. package/lib/commonjs/features/session/JourneyEventPipeline.js +244 -0
  321. package/lib/commonjs/features/session/ReplayCaptureManager.js +1253 -0
  322. package/lib/commonjs/features/session/ScrollTracker.js +147 -0
  323. package/lib/commonjs/features/session/SessionManager.js +1448 -0
  324. package/lib/commonjs/features/session/SyncDecisionEngine.js +95 -0
  325. package/lib/commonjs/features/session/frameLink.js +36 -0
  326. package/lib/commonjs/features/session/frameScrollState.js +41 -0
  327. package/lib/commonjs/features/session/index.js +84 -0
  328. package/lib/commonjs/features/session/nativeCapture.js +53 -0
  329. package/lib/commonjs/features/session/outboxFrameCapture.js +83 -0
  330. package/lib/commonjs/features/session/sessionTypes.js +230 -0
  331. package/lib/commonjs/features/session/viewportPlausibility.js +143 -0
  332. package/lib/commonjs/features/skan/skanBridge.js +54 -0
  333. package/lib/commonjs/features/skan/skanConversionManager.js +134 -0
  334. package/lib/commonjs/index.js +267 -0
  335. package/lib/commonjs/integrations/fetch/fetchInterceptor.js +174 -0
  336. package/lib/commonjs/integrations/navigation/screenTracker.js +2 -0
  337. package/lib/commonjs/integrations/network/bodyCapture.js +336 -0
  338. package/lib/commonjs/integrations/xhr/xhrInterceptor.js +224 -0
  339. package/lib/commonjs/metro/composeSourceMap.js +105 -0
  340. package/lib/commonjs/metro/featureModules.js +131 -0
  341. package/lib/commonjs/metro/index.js +230 -0
  342. package/lib/commonjs/metro/optionalDependencyStub.js +6 -0
  343. package/lib/commonjs/metro/optionalModules.js +31 -0
  344. package/lib/commonjs/metro/serializerCompose.js +161 -0
  345. package/lib/commonjs/pipeline/dispatcher/dispatcher.js +28 -0
  346. package/lib/commonjs/pipeline/envelope/envelope.js +17 -0
  347. package/lib/commonjs/pipeline/envelope/serializer.js +14 -0
  348. package/lib/commonjs/pipeline/processors/dedupe.js +2 -0
  349. package/lib/commonjs/pipeline/processors/enrich.js +16 -0
  350. package/lib/commonjs/pipeline/processors/sampling.js +2 -0
  351. package/lib/commonjs/pipeline/processors/sanitize.js +28 -0
  352. package/lib/commonjs/pipeline/queue/flushQueue.js +107 -0
  353. package/lib/commonjs/pipeline/queue/memoryQueue.js +31 -0
  354. package/lib/commonjs/pipeline/queue/persistentQueue.js +342 -0
  355. package/lib/commonjs/pipeline/queue/queuePolicy.js +2 -0
  356. package/lib/commonjs/pipeline/scheduler/flushScheduler.js +45 -0
  357. package/lib/commonjs/pipeline/scheduler/retryPolicy.js +15 -0
  358. package/lib/commonjs/public/ScaleBunErrorBoundary.js +91 -0
  359. package/lib/commonjs/public/ScaleBunFacade.js +2044 -0
  360. package/lib/commonjs/public/ScaleBunProvider.js +62 -0
  361. package/lib/commonjs/public/typedTracker.js +79 -0
  362. package/lib/commonjs/public/types.js +6 -0
  363. package/lib/commonjs/push/PushManager.js +270 -0
  364. package/lib/commonjs/push/adapters/ManualAdapter.js +86 -0
  365. package/lib/commonjs/push/adapters/NativeBridgeAdapter.js +131 -0
  366. package/lib/commonjs/push/adapters/NoopAdapter.js +45 -0
  367. package/lib/commonjs/push/adapters/RNFirebaseMessagingAdapter.js +248 -0
  368. package/lib/commonjs/push/adapters/loadMessaging.js +29 -0
  369. package/lib/commonjs/push/adapters/loadNotifee.js +36 -0
  370. package/lib/commonjs/push/adapters/selectAdapter.js +153 -0
  371. package/lib/commonjs/push/detect/capabilities.js +136 -0
  372. package/lib/commonjs/push/index.js +33 -0
  373. package/lib/commonjs/push/native/nativeNotifications.js +168 -0
  374. package/lib/commonjs/push/types.js +2 -0
  375. package/lib/commonjs/specs/NativeReplaySdk.js +28 -0
  376. package/lib/commonjs/specs/NativeScaleBunCrash.js +30 -0
  377. package/lib/commonjs/specs/NativeScaleBunInstallReferrer.js +24 -0
  378. package/lib/commonjs/specs/NativeScaleBunOta.js +32 -0
  379. package/lib/commonjs/specs/NativeScaleBunPerformance.js +25 -0
  380. package/lib/commonjs/specs/NativeScaleBunProfiler.js +26 -0
  381. package/lib/commonjs/specs/NativeScaleBunStorage.js +40 -0
  382. package/lib/commonjs/storage/StorageBackend.js +240 -0
  383. package/lib/commonjs/storage/db/sqlite.js +2 -0
  384. package/lib/commonjs/storage/files/fileStore.js +2 -0
  385. package/lib/commonjs/transport/auth/sdkSession.js +28 -0
  386. package/lib/commonjs/transport/backoff/exponentialJitter.js +16 -0
  387. package/lib/commonjs/transport/http/endpoints.js +46 -0
  388. package/lib/commonjs/transport/http/httpClient.js +72 -0
  389. package/lib/commonjs/transport/rateLimit/retryAfter.js +2 -0
  390. package/lib/commonjs/transport/signing/requestSigner.js +2 -0
  391. package/lib/commonjs/vendor/protocol/index.js +327 -0
  392. package/lib/commonjs/vendor/protocol/internal/validation.js +429 -0
  393. package/lib/commonjs/vendor/sdk-contracts/bridge.js +2 -0
  394. package/lib/commonjs/vendor/sdk-contracts/config.js +2 -0
  395. package/lib/commonjs/vendor/sdk-contracts/events.js +2 -0
  396. package/lib/commonjs/vendor/sdk-contracts/features.js +2 -0
  397. package/lib/commonjs/vendor/sdk-contracts/index.js +2 -0
  398. package/lib/commonjs/vendor/sdk-contracts/platform.js +2 -0
  399. package/lib/commonjs/vendor/sdk-contracts/session-boundary.js +6 -0
  400. package/lib/commonjs/vendor/sdk-contracts/session.js +2 -0
  401. package/lib/commonjs/vendor/vendor.manifest +10 -0
  402. package/lib/module/analytics/EventTracker.js +592 -0
  403. package/lib/module/analytics/automaticEvents.js +63 -0
  404. package/lib/module/analytics/batching.js +55 -0
  405. package/lib/module/analytics/eventLane.js +47 -0
  406. package/lib/module/analytics/revenue.js +117 -0
  407. package/lib/module/analytics/subscription.js +86 -0
  408. package/lib/module/bootstrap/FeatureRegistry.js +79 -0
  409. package/lib/module/bootstrap/SDKBootstrapper.js +742 -0
  410. package/lib/module/bucketing/fnv1a32.js +37 -0
  411. package/lib/module/compat/codepush.js +147 -0
  412. package/lib/module/config/ConfigManager.js +230 -0
  413. package/lib/module/config/configTypes.js +10 -0
  414. package/lib/module/config/otaPolicyState.js +76 -0
  415. package/lib/module/config/quotaState.js +423 -0
  416. package/lib/module/core/architecture.js +38 -0
  417. package/lib/module/core/clock/now.js +7 -0
  418. package/lib/module/core/config/defaults.js +2 -0
  419. package/lib/module/core/config/schema.js +506 -0
  420. package/lib/module/core/constants/endpoints.js +13 -0
  421. package/lib/module/core/constants/protocol.js +25 -0
  422. package/lib/module/core/constants/timings.js +26 -0
  423. package/lib/module/core/constants/version.js +12 -0
  424. package/lib/module/core/context/app.js +2 -0
  425. package/lib/module/core/context/device.js +143 -0
  426. package/lib/module/core/context/session.js +2 -0
  427. package/lib/module/core/context/user.js +2 -0
  428. package/lib/module/core/contracts/IFeature.js +2 -0
  429. package/lib/module/core/di/container.js +22 -0
  430. package/lib/module/core/encoding/base64.js +44 -0
  431. package/lib/module/core/id/deviceId.js +45 -0
  432. package/lib/module/core/id/installationId.js +50 -0
  433. package/lib/module/core/id/sessionId.js +2 -0
  434. package/lib/module/core/lifecycle/appLifecycle.js +25 -0
  435. package/lib/module/core/lifecycle/crashSafe.js +26 -0
  436. package/lib/module/core/logger/errorClassification.js +96 -0
  437. package/lib/module/core/logger/internalLogger.js +59 -0
  438. package/lib/module/core/logger/levels.js +2 -0
  439. package/lib/module/crypto/hmacSha256.js +126 -0
  440. package/lib/module/debug/bootstrap.js +393 -0
  441. package/lib/module/debug/commands.js +726 -0
  442. package/lib/module/debug/configBuilder.js +63 -0
  443. package/lib/module/debug/exportBridge.js +148 -0
  444. package/lib/module/debug/hostResolver.js +37 -0
  445. package/lib/module/debug/index.js +13 -0
  446. package/lib/module/debug/metrics.js +33 -0
  447. package/lib/module/debug/perf.js +180 -0
  448. package/lib/module/debug/perfWiring.js +102 -0
  449. package/lib/module/debug/protocol.js +19 -0
  450. package/lib/module/debug/redaction.js +411 -0
  451. package/lib/module/debug/replayWiring.js +139 -0
  452. package/lib/module/debug/screenTracking.js +53 -0
  453. package/lib/module/debug/sessionWiring.js +43 -0
  454. package/lib/module/debug/stream.js +536 -0
  455. package/lib/module/debug/transport.js +362 -0
  456. package/lib/module/features/bugreport/BugReportFeature.js +105 -0
  457. package/lib/module/features/bugreport/buildPayload.js +91 -0
  458. package/lib/module/features/bugreport/index.js +2 -0
  459. package/lib/module/features/crash/CrashFeature.js +79 -0
  460. package/lib/module/features/crash/CrashReporter.js +151 -0
  461. package/lib/module/features/crash/NativeCrashFeature.js +36 -0
  462. package/lib/module/features/crash/capture.js +42 -0
  463. package/lib/module/features/crash/globalHandler.js +36 -0
  464. package/lib/module/features/crash/index.js +8 -0
  465. package/lib/module/features/crash/nativeCrashBridge.js +350 -0
  466. package/lib/module/features/crash/rejectionHandler.js +171 -0
  467. package/lib/module/features/crash/scrubCrash.js +149 -0
  468. package/lib/module/features/engage/EngageAnchor.js +121 -0
  469. package/lib/module/features/engage/EngageArchetypeRenderers.js +4264 -0
  470. package/lib/module/features/engage/EngageInAppView.js +2656 -0
  471. package/lib/module/features/engage/EngageInlinePlacement.js +123 -0
  472. package/lib/module/features/engage/EngagePromptProvider.js +1054 -0
  473. package/lib/module/features/engage/EngagePromptView.js +1243 -0
  474. package/lib/module/features/engage/EngageTransport.js +602 -0
  475. package/lib/module/features/engage/EngageVariantContent.js +1771 -0
  476. package/lib/module/features/engage/__test-support__/reactNativeStub.js +52 -0
  477. package/lib/module/features/engage/attachmentCapture.js +42 -0
  478. package/lib/module/features/engage/captureVoiceAttachment.js +80 -0
  479. package/lib/module/features/engage/engageCoachmarkTour.js +94 -0
  480. package/lib/module/features/engage/engageGameLogic.js +287 -0
  481. package/lib/module/features/engage/engageMediaSizing.js +51 -0
  482. package/lib/module/features/engage/engageMultiStepSheet.js +35 -0
  483. package/lib/module/features/engage/engageOutcome.js +108 -0
  484. package/lib/module/features/engage/engagePushTokenBridge.js +65 -0
  485. package/lib/module/features/engage/engageShakeBridge.js +56 -0
  486. package/lib/module/features/engage/engageSignals.js +34 -0
  487. package/lib/module/features/engage/engageStoreReviewBridge.js +43 -0
  488. package/lib/module/features/engage/engageTestSeenStore.js +92 -0
  489. package/lib/module/features/engage/engageThrottle.js +390 -0
  490. package/lib/module/features/engage/engageTriggerEngine.js +360 -0
  491. package/lib/module/features/engage/engageTypes.js +110 -0
  492. package/lib/module/features/engage/engageVariantResolver.js +135 -0
  493. package/lib/module/features/engage/engageWindowInsets.js +89 -0
  494. package/lib/module/features/engage/gestureTriggerDetector.js +80 -0
  495. package/lib/module/features/engage/transcription/TranscriptionProvider.js +46 -0
  496. package/lib/module/features/install-referrer/installReferrer.js +104 -0
  497. package/lib/module/features/journey/ScaleBunDebugRoot.js +1030 -0
  498. package/lib/module/features/journey/ScaleBunFlatList.js +32 -0
  499. package/lib/module/features/journey/ScaleBunImpression.js +105 -0
  500. package/lib/module/features/journey/ScaleBunScrollView.js +95 -0
  501. package/lib/module/features/journey/ScaleBunSectionList.js +32 -0
  502. package/lib/module/features/journey/autoInstrumentScroll.js +155 -0
  503. package/lib/module/features/journey/calibrationContext.js +35 -0
  504. package/lib/module/features/journey/gestureBuffer.js +56 -0
  505. package/lib/module/features/journey/gestureDetector.js +169 -0
  506. package/lib/module/features/journey/impression.js +84 -0
  507. package/lib/module/features/journey/interactionProtocol.js +158 -0
  508. package/lib/module/features/journey/journeyManager.js +459 -0
  509. package/lib/module/features/journey/journeyTypes.js +49 -0
  510. package/lib/module/features/journey/nativeScroll.js +77 -0
  511. package/lib/module/features/journey/navDetector.js +154 -0
  512. package/lib/module/features/journey/screenshotHelper.js +129 -0
  513. package/lib/module/features/journey/scrollContext.js +142 -0
  514. package/lib/module/features/journey/targetGeometry.js +175 -0
  515. package/lib/module/features/journey/targetRegistry.js +44 -0
  516. package/lib/module/features/journey/touchTarget.js +241 -0
  517. package/lib/module/features/journey/uiState.js +176 -0
  518. package/lib/module/features/navigation/AutoScreenDetector.js +601 -0
  519. package/lib/module/features/network/NetworkFeature.js +209 -0
  520. package/lib/module/features/network/index.js +17 -0
  521. package/lib/module/features/network/thirdParty.js +142 -0
  522. package/lib/module/features/ota/OtaEventEmitter.js +123 -0
  523. package/lib/module/features/ota/OtaOrchestrator.js +1302 -0
  524. package/lib/module/features/ota/OtaTypes.js +2 -0
  525. package/lib/module/features/ota/deviceAttributes.js +43 -0
  526. package/lib/module/features/ota/environment.js +165 -0
  527. package/lib/module/features/ota/geoCountry.js +86 -0
  528. package/lib/module/features/ota/retry.js +87 -0
  529. package/lib/module/features/ota/signedDownload.js +174 -0
  530. package/lib/module/features/ota/useOtaUpdate.js +100 -0
  531. package/lib/module/features/performance/PerformanceFeature.js +643 -0
  532. package/lib/module/features/performance/collectors/AppLaunchCollector.js +115 -0
  533. package/lib/module/features/performance/collectors/AutoScreenLoadCollector.js +225 -0
  534. package/lib/module/features/performance/collectors/CustomTraceCollector.js +92 -0
  535. package/lib/module/features/performance/collectors/FrameMetricsCollector.js +177 -0
  536. package/lib/module/features/performance/collectors/JsStallCollector.js +158 -0
  537. package/lib/module/features/performance/collectors/NetworkPerfCollector.js +52 -0
  538. package/lib/module/features/performance/collectors/ScreenLoadCollector.js +71 -0
  539. package/lib/module/features/performance/collectors/UiHangCollector.js +71 -0
  540. package/lib/module/features/performance/config.js +51 -0
  541. package/lib/module/features/performance/getNativePerformanceModule.js +39 -0
  542. package/lib/module/features/performance/index.js +9 -0
  543. package/lib/module/features/performance/metrics/MetricsEngine.js +139 -0
  544. package/lib/module/features/performance/models.js +42 -0
  545. package/lib/module/features/performance/transport/PerformanceTransport.js +162 -0
  546. package/lib/module/features/profiler/ProfilerFeature.js +594 -0
  547. package/lib/module/features/profiler/config.js +143 -0
  548. package/lib/module/features/profiler/contracts.js +56 -0
  549. package/lib/module/features/profiler/index.js +11 -0
  550. package/lib/module/features/profiler/models.js +106 -0
  551. package/lib/module/features/profiler/transport/ProfilerTransport.js +87 -0
  552. package/lib/module/features/replay/bridge/adapters/bridgeAdapter.js +198 -0
  553. package/lib/module/features/replay/bridge/eventEmitter.js +122 -0
  554. package/lib/module/features/replay/bridge/nativeModule.js +89 -0
  555. package/lib/module/features/replay/bridge/nativeQuota.js +109 -0
  556. package/lib/module/features/replay/core/clock/clock.js +37 -0
  557. package/lib/module/features/replay/core/config/defaults.js +82 -0
  558. package/lib/module/features/replay/core/config/validator.js +103 -0
  559. package/lib/module/features/replay/core/env/environmentDetector.js +22 -0
  560. package/lib/module/features/replay/core/errors/safeCall.js +35 -0
  561. package/lib/module/features/replay/core/ids/sessionId.js +44 -0
  562. package/lib/module/features/replay/core/logger/logger.js +29 -0
  563. package/lib/module/features/replay/core/privacy/privacyTypes.js +27 -0
  564. package/lib/module/features/replay/core/queue/boundedQueue.js +64 -0
  565. package/lib/module/features/replay/integrations/alert/alertInstrumentation.js +252 -0
  566. package/lib/module/features/replay/integrations/logs/consoleIntegration.js +131 -0
  567. package/lib/module/features/replay/integrations/network/networkAdapter.js +70 -0
  568. package/lib/module/features/replay/integrations/react-navigation/navigationIntegration.js +82 -0
  569. package/lib/module/features/replay/integrations/touch/ReplayRoot.js +96 -0
  570. package/lib/module/features/replay/pipeline/batching/batchManager.js +113 -0
  571. package/lib/module/features/replay/pipeline/envelope/envelopeBuilder.js +43 -0
  572. package/lib/module/features/replay/pipeline/retry/retryPolicy.js +45 -0
  573. package/lib/module/features/replay/pipeline/transport/transportTypes.js +60 -0
  574. package/lib/module/features/replay/public/api.js +332 -0
  575. package/lib/module/features/replay/public/enums.js +85 -0
  576. package/lib/module/features/replay/public/types.js +2 -0
  577. package/lib/module/features/replay/replay/breadcrumbs/breadcrumbCollector.js +113 -0
  578. package/lib/module/features/replay/replay/frames/frameModels.js +33 -0
  579. package/lib/module/features/replay/replay/session/sessionOrchestrator.js +272 -0
  580. package/lib/module/features/replay/replay/timeline/timelineModels.js +108 -0
  581. package/lib/module/features/replay/transport/BackendReplayTransport.js +169 -0
  582. package/lib/module/features/replay/transport/CompositeReplayTransport.js +68 -0
  583. package/lib/module/features/replay/transport/DesktopReplayTransport.js +119 -0
  584. package/lib/module/features/replay/transport/ReplayTransport.js +2 -0
  585. package/lib/module/features/session/BackendSessionAdapter.js +1908 -0
  586. package/lib/module/features/session/DesktopSessionTransport.js +118 -0
  587. package/lib/module/features/session/JourneyEventPipeline.js +239 -0
  588. package/lib/module/features/session/ReplayCaptureManager.js +1248 -0
  589. package/lib/module/features/session/ScrollTracker.js +141 -0
  590. package/lib/module/features/session/SessionManager.js +1443 -0
  591. package/lib/module/features/session/SyncDecisionEngine.js +88 -0
  592. package/lib/module/features/session/frameLink.js +29 -0
  593. package/lib/module/features/session/frameScrollState.js +34 -0
  594. package/lib/module/features/session/index.js +35 -0
  595. package/lib/module/features/session/nativeCapture.js +45 -0
  596. package/lib/module/features/session/outboxFrameCapture.js +77 -0
  597. package/lib/module/features/session/sessionTypes.js +226 -0
  598. package/lib/module/features/session/viewportPlausibility.js +137 -0
  599. package/lib/module/features/skan/skanBridge.js +48 -0
  600. package/lib/module/features/skan/skanConversionManager.js +128 -0
  601. package/lib/module/index.js +80 -0
  602. package/lib/module/integrations/fetch/fetchInterceptor.js +169 -0
  603. package/lib/module/integrations/navigation/screenTracker.js +2 -0
  604. package/lib/module/integrations/network/bodyCapture.js +320 -0
  605. package/lib/module/integrations/xhr/xhrInterceptor.js +219 -0
  606. package/lib/module/metro/composeSourceMap.js +97 -0
  607. package/lib/module/metro/featureModules.js +123 -0
  608. package/lib/module/metro/index.js +218 -0
  609. package/lib/module/metro/optionalDependencyStub.js +14 -0
  610. package/lib/module/metro/optionalModules.js +25 -0
  611. package/lib/module/metro/serializerCompose.js +152 -0
  612. package/lib/module/pipeline/dispatcher/dispatcher.js +21 -0
  613. package/lib/module/pipeline/envelope/envelope.js +11 -0
  614. package/lib/module/pipeline/envelope/serializer.js +8 -0
  615. package/lib/module/pipeline/processors/dedupe.js +2 -0
  616. package/lib/module/pipeline/processors/enrich.js +10 -0
  617. package/lib/module/pipeline/processors/sampling.js +2 -0
  618. package/lib/module/pipeline/processors/sanitize.js +22 -0
  619. package/lib/module/pipeline/queue/flushQueue.js +102 -0
  620. package/lib/module/pipeline/queue/memoryQueue.js +24 -0
  621. package/lib/module/pipeline/queue/persistentQueue.js +336 -0
  622. package/lib/module/pipeline/queue/queuePolicy.js +2 -0
  623. package/lib/module/pipeline/scheduler/flushScheduler.js +38 -0
  624. package/lib/module/pipeline/scheduler/retryPolicy.js +9 -0
  625. package/lib/module/public/ScaleBunErrorBoundary.js +82 -0
  626. package/lib/module/public/ScaleBunFacade.js +2038 -0
  627. package/lib/module/public/ScaleBunProvider.js +54 -0
  628. package/lib/module/public/typedTracker.js +72 -0
  629. package/lib/module/public/types.js +2 -0
  630. package/lib/module/push/PushManager.js +263 -0
  631. package/lib/module/push/adapters/ManualAdapter.js +79 -0
  632. package/lib/module/push/adapters/NativeBridgeAdapter.js +124 -0
  633. package/lib/module/push/adapters/NoopAdapter.js +38 -0
  634. package/lib/module/push/adapters/RNFirebaseMessagingAdapter.js +241 -0
  635. package/lib/module/push/adapters/loadMessaging.js +23 -0
  636. package/lib/module/push/adapters/loadNotifee.js +30 -0
  637. package/lib/module/push/adapters/selectAdapter.js +147 -0
  638. package/lib/module/push/detect/capabilities.js +130 -0
  639. package/lib/module/push/index.js +9 -0
  640. package/lib/module/push/native/nativeNotifications.js +155 -0
  641. package/lib/module/push/types.js +2 -0
  642. package/lib/module/specs/NativeReplaySdk.js +24 -0
  643. package/lib/module/specs/NativeScaleBunCrash.js +26 -0
  644. package/lib/module/specs/NativeScaleBunInstallReferrer.js +20 -0
  645. package/lib/module/specs/NativeScaleBunOta.js +28 -0
  646. package/lib/module/specs/NativeScaleBunPerformance.js +21 -0
  647. package/lib/module/specs/NativeScaleBunProfiler.js +22 -0
  648. package/lib/module/specs/NativeScaleBunStorage.js +36 -0
  649. package/lib/module/storage/StorageBackend.js +231 -0
  650. package/lib/module/storage/db/sqlite.js +2 -0
  651. package/lib/module/storage/files/fileStore.js +2 -0
  652. package/lib/module/transport/auth/sdkSession.js +21 -0
  653. package/lib/module/transport/backoff/exponentialJitter.js +10 -0
  654. package/lib/module/transport/http/endpoints.js +40 -0
  655. package/lib/module/transport/http/httpClient.js +64 -0
  656. package/lib/module/transport/rateLimit/retryAfter.js +2 -0
  657. package/lib/module/transport/signing/requestSigner.js +2 -0
  658. package/lib/module/vendor/protocol/index.js +312 -0
  659. package/lib/module/vendor/protocol/internal/validation.js +423 -0
  660. package/lib/module/vendor/sdk-contracts/bridge.js +2 -0
  661. package/lib/module/vendor/sdk-contracts/config.js +2 -0
  662. package/lib/module/vendor/sdk-contracts/events.js +2 -0
  663. package/lib/module/vendor/sdk-contracts/features.js +2 -0
  664. package/lib/module/vendor/sdk-contracts/index.js +2 -0
  665. package/lib/module/vendor/sdk-contracts/platform.js +2 -0
  666. package/lib/module/vendor/sdk-contracts/session-boundary.js +2 -0
  667. package/lib/module/vendor/sdk-contracts/session.js +2 -0
  668. package/lib/module/vendor/vendor.manifest +10 -0
  669. package/lib/typescript/analytics/EventTracker.d.ts +197 -0
  670. package/lib/typescript/analytics/automaticEvents.d.ts +30 -0
  671. package/lib/typescript/analytics/batching.d.ts +33 -0
  672. package/lib/typescript/analytics/eventLane.d.ts +22 -0
  673. package/lib/typescript/analytics/revenue.d.ts +94 -0
  674. package/lib/typescript/analytics/subscription.d.ts +35 -0
  675. package/lib/typescript/bootstrap/FeatureRegistry.d.ts +36 -0
  676. package/lib/typescript/bootstrap/SDKBootstrapper.d.ts +82 -0
  677. package/lib/typescript/bucketing/fnv1a32.d.ts +20 -0
  678. package/lib/typescript/compat/codepush.d.ts +76 -0
  679. package/lib/typescript/config/ConfigManager.d.ts +82 -0
  680. package/lib/typescript/config/configTypes.d.ts +73 -0
  681. package/lib/typescript/config/otaPolicyState.d.ts +55 -0
  682. package/lib/typescript/config/quotaState.d.ts +155 -0
  683. package/lib/typescript/core/architecture.d.ts +29 -0
  684. package/lib/typescript/core/clock/now.d.ts +5 -0
  685. package/lib/typescript/core/config/defaults.d.ts +1 -0
  686. package/lib/typescript/core/config/schema.d.ts +107 -0
  687. package/lib/typescript/core/constants/endpoints.d.ts +13 -0
  688. package/lib/typescript/core/constants/protocol.d.ts +23 -0
  689. package/lib/typescript/core/constants/timings.d.ts +22 -0
  690. package/lib/typescript/core/constants/version.d.ts +12 -0
  691. package/lib/typescript/core/context/app.d.ts +1 -0
  692. package/lib/typescript/core/context/device.d.ts +61 -0
  693. package/lib/typescript/core/context/session.d.ts +1 -0
  694. package/lib/typescript/core/context/user.d.ts +24 -0
  695. package/lib/typescript/core/contracts/IFeature.d.ts +44 -0
  696. package/lib/typescript/core/di/container.d.ts +9 -0
  697. package/lib/typescript/core/encoding/base64.d.ts +13 -0
  698. package/lib/typescript/core/id/deviceId.d.ts +9 -0
  699. package/lib/typescript/core/id/installationId.d.ts +20 -0
  700. package/lib/typescript/core/id/sessionId.d.ts +1 -0
  701. package/lib/typescript/core/lifecycle/appLifecycle.d.ts +14 -0
  702. package/lib/typescript/core/lifecycle/crashSafe.d.ts +7 -0
  703. package/lib/typescript/core/logger/errorClassification.d.ts +46 -0
  704. package/lib/typescript/core/logger/internalLogger.d.ts +24 -0
  705. package/lib/typescript/core/logger/levels.d.ts +1 -0
  706. package/lib/typescript/crypto/hmacSha256.d.ts +10 -0
  707. package/lib/typescript/debug/bootstrap.d.ts +148 -0
  708. package/lib/typescript/debug/commands.d.ts +77 -0
  709. package/lib/typescript/debug/configBuilder.d.ts +13 -0
  710. package/lib/typescript/debug/exportBridge.d.ts +27 -0
  711. package/lib/typescript/debug/hostResolver.d.ts +14 -0
  712. package/lib/typescript/debug/index.d.ts +12 -0
  713. package/lib/typescript/debug/metrics.d.ts +2 -0
  714. package/lib/typescript/debug/perf.d.ts +43 -0
  715. package/lib/typescript/debug/perfWiring.d.ts +33 -0
  716. package/lib/typescript/debug/protocol.d.ts +15 -0
  717. package/lib/typescript/debug/redaction.d.ts +69 -0
  718. package/lib/typescript/debug/replayWiring.d.ts +25 -0
  719. package/lib/typescript/debug/screenTracking.d.ts +16 -0
  720. package/lib/typescript/debug/sessionWiring.d.ts +16 -0
  721. package/lib/typescript/debug/stream.d.ts +77 -0
  722. package/lib/typescript/debug/transport.d.ts +110 -0
  723. package/lib/typescript/features/bugreport/BugReportFeature.d.ts +59 -0
  724. package/lib/typescript/features/bugreport/buildPayload.d.ts +51 -0
  725. package/lib/typescript/features/bugreport/index.d.ts +1 -0
  726. package/lib/typescript/features/crash/CrashFeature.d.ts +21 -0
  727. package/lib/typescript/features/crash/CrashReporter.d.ts +107 -0
  728. package/lib/typescript/features/crash/NativeCrashFeature.d.ts +17 -0
  729. package/lib/typescript/features/crash/capture.d.ts +22 -0
  730. package/lib/typescript/features/crash/globalHandler.d.ts +14 -0
  731. package/lib/typescript/features/crash/index.d.ts +2 -0
  732. package/lib/typescript/features/crash/nativeCrashBridge.d.ts +43 -0
  733. package/lib/typescript/features/crash/rejectionHandler.d.ts +65 -0
  734. package/lib/typescript/features/crash/scrubCrash.d.ts +35 -0
  735. package/lib/typescript/features/engage/EngageAnchor.d.ts +36 -0
  736. package/lib/typescript/features/engage/EngageArchetypeRenderers.d.ts +66 -0
  737. package/lib/typescript/features/engage/EngageInAppView.d.ts +164 -0
  738. package/lib/typescript/features/engage/EngageInlinePlacement.d.ts +42 -0
  739. package/lib/typescript/features/engage/EngagePromptProvider.d.ts +233 -0
  740. package/lib/typescript/features/engage/EngagePromptView.d.ts +97 -0
  741. package/lib/typescript/features/engage/EngageTransport.d.ts +158 -0
  742. package/lib/typescript/features/engage/EngageVariantContent.d.ts +12 -0
  743. package/lib/typescript/features/engage/attachmentCapture.d.ts +17 -0
  744. package/lib/typescript/features/engage/captureVoiceAttachment.d.ts +31 -0
  745. package/lib/typescript/features/engage/engageCoachmarkTour.d.ts +18 -0
  746. package/lib/typescript/features/engage/engageGameLogic.d.ts +34 -0
  747. package/lib/typescript/features/engage/engageMediaSizing.d.ts +23 -0
  748. package/lib/typescript/features/engage/engageMultiStepSheet.d.ts +25 -0
  749. package/lib/typescript/features/engage/engageOutcome.d.ts +38 -0
  750. package/lib/typescript/features/engage/engagePushTokenBridge.d.ts +30 -0
  751. package/lib/typescript/features/engage/engageShakeBridge.d.ts +20 -0
  752. package/lib/typescript/features/engage/engageSignals.d.ts +13 -0
  753. package/lib/typescript/features/engage/engageStoreReviewBridge.d.ts +23 -0
  754. package/lib/typescript/features/engage/engageTestSeenStore.d.ts +46 -0
  755. package/lib/typescript/features/engage/engageThrottle.d.ts +180 -0
  756. package/lib/typescript/features/engage/engageTriggerEngine.d.ts +16 -0
  757. package/lib/typescript/features/engage/engageTypes.d.ts +576 -0
  758. package/lib/typescript/features/engage/engageVariantResolver.d.ts +46 -0
  759. package/lib/typescript/features/engage/engageWindowInsets.d.ts +25 -0
  760. package/lib/typescript/features/engage/gestureTriggerDetector.d.ts +20 -0
  761. package/lib/typescript/features/engage/transcription/TranscriptionProvider.d.ts +51 -0
  762. package/lib/typescript/features/install-referrer/installReferrer.d.ts +39 -0
  763. package/lib/typescript/features/journey/ScaleBunDebugRoot.d.ts +49 -0
  764. package/lib/typescript/features/journey/ScaleBunFlatList.d.ts +20 -0
  765. package/lib/typescript/features/journey/ScaleBunImpression.d.ts +30 -0
  766. package/lib/typescript/features/journey/ScaleBunScrollView.d.ts +25 -0
  767. package/lib/typescript/features/journey/ScaleBunSectionList.d.ts +20 -0
  768. package/lib/typescript/features/journey/autoInstrumentScroll.d.ts +46 -0
  769. package/lib/typescript/features/journey/calibrationContext.d.ts +26 -0
  770. package/lib/typescript/features/journey/gestureBuffer.d.ts +27 -0
  771. package/lib/typescript/features/journey/gestureDetector.d.ts +40 -0
  772. package/lib/typescript/features/journey/impression.d.ts +48 -0
  773. package/lib/typescript/features/journey/interactionProtocol.d.ts +159 -0
  774. package/lib/typescript/features/journey/journeyManager.d.ts +62 -0
  775. package/lib/typescript/features/journey/journeyTypes.d.ts +161 -0
  776. package/lib/typescript/features/journey/nativeScroll.d.ts +47 -0
  777. package/lib/typescript/features/journey/navDetector.d.ts +42 -0
  778. package/lib/typescript/features/journey/screenshotHelper.d.ts +33 -0
  779. package/lib/typescript/features/journey/scrollContext.d.ts +49 -0
  780. package/lib/typescript/features/journey/targetGeometry.d.ts +126 -0
  781. package/lib/typescript/features/journey/targetRegistry.d.ts +28 -0
  782. package/lib/typescript/features/journey/touchTarget.d.ts +174 -0
  783. package/lib/typescript/features/journey/uiState.d.ts +93 -0
  784. package/lib/typescript/features/navigation/AutoScreenDetector.d.ts +196 -0
  785. package/lib/typescript/features/network/NetworkFeature.d.ts +43 -0
  786. package/lib/typescript/features/network/index.d.ts +46 -0
  787. package/lib/typescript/features/network/thirdParty.d.ts +95 -0
  788. package/lib/typescript/features/ota/OtaEventEmitter.d.ts +93 -0
  789. package/lib/typescript/features/ota/OtaOrchestrator.d.ts +287 -0
  790. package/lib/typescript/features/ota/OtaTypes.d.ts +193 -0
  791. package/lib/typescript/features/ota/deviceAttributes.d.ts +18 -0
  792. package/lib/typescript/features/ota/environment.d.ts +99 -0
  793. package/lib/typescript/features/ota/geoCountry.d.ts +14 -0
  794. package/lib/typescript/features/ota/retry.d.ts +53 -0
  795. package/lib/typescript/features/ota/signedDownload.d.ts +81 -0
  796. package/lib/typescript/features/ota/useOtaUpdate.d.ts +72 -0
  797. package/lib/typescript/features/performance/PerformanceFeature.d.ts +132 -0
  798. package/lib/typescript/features/performance/collectors/AppLaunchCollector.d.ts +27 -0
  799. package/lib/typescript/features/performance/collectors/AutoScreenLoadCollector.d.ts +70 -0
  800. package/lib/typescript/features/performance/collectors/CustomTraceCollector.d.ts +35 -0
  801. package/lib/typescript/features/performance/collectors/FrameMetricsCollector.d.ts +74 -0
  802. package/lib/typescript/features/performance/collectors/JsStallCollector.d.ts +45 -0
  803. package/lib/typescript/features/performance/collectors/NetworkPerfCollector.d.ts +23 -0
  804. package/lib/typescript/features/performance/collectors/ScreenLoadCollector.d.ts +30 -0
  805. package/lib/typescript/features/performance/collectors/UiHangCollector.d.ts +21 -0
  806. package/lib/typescript/features/performance/config.d.ts +73 -0
  807. package/lib/typescript/features/performance/getNativePerformanceModule.d.ts +2 -0
  808. package/lib/typescript/features/performance/index.d.ts +10 -0
  809. package/lib/typescript/features/performance/metrics/MetricsEngine.d.ts +69 -0
  810. package/lib/typescript/features/performance/models.d.ts +169 -0
  811. package/lib/typescript/features/performance/transport/PerformanceTransport.d.ts +40 -0
  812. package/lib/typescript/features/profiler/ProfilerFeature.d.ts +92 -0
  813. package/lib/typescript/features/profiler/config.d.ts +85 -0
  814. package/lib/typescript/features/profiler/contracts.d.ts +142 -0
  815. package/lib/typescript/features/profiler/index.d.ts +13 -0
  816. package/lib/typescript/features/profiler/models.d.ts +332 -0
  817. package/lib/typescript/features/profiler/transport/ProfilerTransport.d.ts +30 -0
  818. package/lib/typescript/features/replay/bridge/adapters/bridgeAdapter.d.ts +52 -0
  819. package/lib/typescript/features/replay/bridge/eventEmitter.d.ts +100 -0
  820. package/lib/typescript/features/replay/bridge/nativeModule.d.ts +166 -0
  821. package/lib/typescript/features/replay/bridge/nativeQuota.d.ts +26 -0
  822. package/lib/typescript/features/replay/core/clock/clock.d.ts +16 -0
  823. package/lib/typescript/features/replay/core/config/defaults.d.ts +28 -0
  824. package/lib/typescript/features/replay/core/config/validator.d.ts +13 -0
  825. package/lib/typescript/features/replay/core/env/environmentDetector.d.ts +14 -0
  826. package/lib/typescript/features/replay/core/errors/safeCall.d.ts +12 -0
  827. package/lib/typescript/features/replay/core/ids/sessionId.d.ts +15 -0
  828. package/lib/typescript/features/replay/core/logger/logger.d.ts +15 -0
  829. package/lib/typescript/features/replay/core/privacy/privacyTypes.d.ts +24 -0
  830. package/lib/typescript/features/replay/core/queue/boundedQueue.d.ts +29 -0
  831. package/lib/typescript/features/replay/integrations/alert/alertInstrumentation.d.ts +48 -0
  832. package/lib/typescript/features/replay/integrations/logs/consoleIntegration.d.ts +18 -0
  833. package/lib/typescript/features/replay/integrations/network/networkAdapter.d.ts +31 -0
  834. package/lib/typescript/features/replay/integrations/react-navigation/navigationIntegration.d.ts +25 -0
  835. package/lib/typescript/features/replay/integrations/touch/ReplayRoot.d.ts +26 -0
  836. package/lib/typescript/features/replay/pipeline/batching/batchManager.d.ts +47 -0
  837. package/lib/typescript/features/replay/pipeline/envelope/envelopeBuilder.d.ts +12 -0
  838. package/lib/typescript/features/replay/pipeline/retry/retryPolicy.d.ts +17 -0
  839. package/lib/typescript/features/replay/pipeline/transport/transportTypes.d.ts +40 -0
  840. package/lib/typescript/features/replay/public/api.d.ts +83 -0
  841. package/lib/typescript/features/replay/public/enums.d.ts +73 -0
  842. package/lib/typescript/features/replay/public/types.d.ts +249 -0
  843. package/lib/typescript/features/replay/replay/breadcrumbs/breadcrumbCollector.d.ts +26 -0
  844. package/lib/typescript/features/replay/replay/frames/frameModels.d.ts +23 -0
  845. package/lib/typescript/features/replay/replay/session/sessionOrchestrator.d.ts +53 -0
  846. package/lib/typescript/features/replay/replay/timeline/timelineModels.d.ts +29 -0
  847. package/lib/typescript/features/replay/transport/BackendReplayTransport.d.ts +45 -0
  848. package/lib/typescript/features/replay/transport/CompositeReplayTransport.d.ts +22 -0
  849. package/lib/typescript/features/replay/transport/DesktopReplayTransport.d.ts +35 -0
  850. package/lib/typescript/features/replay/transport/ReplayTransport.d.ts +72 -0
  851. package/lib/typescript/features/session/BackendSessionAdapter.d.ts +528 -0
  852. package/lib/typescript/features/session/DesktopSessionTransport.d.ts +28 -0
  853. package/lib/typescript/features/session/JourneyEventPipeline.d.ts +74 -0
  854. package/lib/typescript/features/session/ReplayCaptureManager.d.ts +391 -0
  855. package/lib/typescript/features/session/ScrollTracker.d.ts +48 -0
  856. package/lib/typescript/features/session/SessionManager.d.ts +414 -0
  857. package/lib/typescript/features/session/SyncDecisionEngine.d.ts +33 -0
  858. package/lib/typescript/features/session/frameLink.d.ts +27 -0
  859. package/lib/typescript/features/session/frameScrollState.d.ts +27 -0
  860. package/lib/typescript/features/session/index.d.ts +23 -0
  861. package/lib/typescript/features/session/nativeCapture.d.ts +18 -0
  862. package/lib/typescript/features/session/outboxFrameCapture.d.ts +20 -0
  863. package/lib/typescript/features/session/sessionTypes.d.ts +362 -0
  864. package/lib/typescript/features/session/viewportPlausibility.d.ts +72 -0
  865. package/lib/typescript/features/skan/skanBridge.d.ts +18 -0
  866. package/lib/typescript/features/skan/skanConversionManager.d.ts +69 -0
  867. package/lib/typescript/index.d.ts +59 -0
  868. package/lib/typescript/integrations/fetch/fetchInterceptor.d.ts +41 -0
  869. package/lib/typescript/integrations/navigation/screenTracker.d.ts +1 -0
  870. package/lib/typescript/integrations/network/bodyCapture.d.ts +101 -0
  871. package/lib/typescript/integrations/xhr/xhrInterceptor.d.ts +46 -0
  872. package/lib/typescript/metro/composeSourceMap.d.ts +26 -0
  873. package/lib/typescript/metro/featureModules.d.ts +49 -0
  874. package/lib/typescript/metro/index.d.ts +99 -0
  875. package/lib/typescript/metro/optionalDependencyStub.d.ts +14 -0
  876. package/lib/typescript/metro/optionalModules.d.ts +20 -0
  877. package/lib/typescript/metro/serializerCompose.d.ts +46 -0
  878. package/lib/typescript/pipeline/dispatcher/dispatcher.d.ts +7 -0
  879. package/lib/typescript/pipeline/envelope/envelope.d.ts +16 -0
  880. package/lib/typescript/pipeline/envelope/serializer.d.ts +7 -0
  881. package/lib/typescript/pipeline/processors/dedupe.d.ts +1 -0
  882. package/lib/typescript/pipeline/processors/enrich.d.ts +3 -0
  883. package/lib/typescript/pipeline/processors/sampling.d.ts +1 -0
  884. package/lib/typescript/pipeline/processors/sanitize.d.ts +3 -0
  885. package/lib/typescript/pipeline/queue/flushQueue.d.ts +12 -0
  886. package/lib/typescript/pipeline/queue/memoryQueue.d.ts +12 -0
  887. package/lib/typescript/pipeline/queue/persistentQueue.d.ts +120 -0
  888. package/lib/typescript/pipeline/queue/queuePolicy.d.ts +1 -0
  889. package/lib/typescript/pipeline/scheduler/flushScheduler.d.ts +9 -0
  890. package/lib/typescript/pipeline/scheduler/retryPolicy.d.ts +5 -0
  891. package/lib/typescript/public/ScaleBunErrorBoundary.d.ts +44 -0
  892. package/lib/typescript/public/ScaleBunFacade.d.ts +800 -0
  893. package/lib/typescript/public/ScaleBunProvider.d.ts +23 -0
  894. package/lib/typescript/public/typedTracker.d.ts +88 -0
  895. package/lib/typescript/public/types.d.ts +316 -0
  896. package/lib/typescript/push/PushManager.d.ts +69 -0
  897. package/lib/typescript/push/adapters/ManualAdapter.d.ts +40 -0
  898. package/lib/typescript/push/adapters/NativeBridgeAdapter.d.ts +37 -0
  899. package/lib/typescript/push/adapters/NoopAdapter.d.ts +21 -0
  900. package/lib/typescript/push/adapters/RNFirebaseMessagingAdapter.d.ts +39 -0
  901. package/lib/typescript/push/adapters/loadMessaging.d.ts +17 -0
  902. package/lib/typescript/push/adapters/loadNotifee.d.ts +15 -0
  903. package/lib/typescript/push/adapters/selectAdapter.d.ts +20 -0
  904. package/lib/typescript/push/detect/capabilities.d.ts +15 -0
  905. package/lib/typescript/push/index.d.ts +10 -0
  906. package/lib/typescript/push/native/nativeNotifications.d.ts +65 -0
  907. package/lib/typescript/push/types.d.ts +143 -0
  908. package/lib/typescript/specs/NativeReplaySdk.d.ts +69 -0
  909. package/lib/typescript/specs/NativeScaleBunCrash.d.ts +58 -0
  910. package/lib/typescript/specs/NativeScaleBunInstallReferrer.d.ts +28 -0
  911. package/lib/typescript/specs/NativeScaleBunOta.d.ts +141 -0
  912. package/lib/typescript/specs/NativeScaleBunPerformance.d.ts +31 -0
  913. package/lib/typescript/specs/NativeScaleBunProfiler.d.ts +38 -0
  914. package/lib/typescript/specs/NativeScaleBunStorage.d.ts +55 -0
  915. package/lib/typescript/storage/StorageBackend.d.ts +131 -0
  916. package/lib/typescript/storage/db/sqlite.d.ts +1 -0
  917. package/lib/typescript/storage/files/fileStore.d.ts +1 -0
  918. package/lib/typescript/transport/auth/sdkSession.d.ts +10 -0
  919. package/lib/typescript/transport/backoff/exponentialJitter.d.ts +5 -0
  920. package/lib/typescript/transport/http/endpoints.d.ts +26 -0
  921. package/lib/typescript/transport/http/httpClient.d.ts +23 -0
  922. package/lib/typescript/transport/rateLimit/retryAfter.d.ts +1 -0
  923. package/lib/typescript/transport/signing/requestSigner.d.ts +1 -0
  924. package/lib/typescript/vendor/protocol/index.d.ts +345 -0
  925. package/lib/typescript/vendor/protocol/internal/validation.d.ts +114 -0
  926. package/lib/typescript/vendor/sdk-contracts/bridge.d.ts +104 -0
  927. package/lib/typescript/vendor/sdk-contracts/config.d.ts +156 -0
  928. package/lib/typescript/vendor/sdk-contracts/events.d.ts +42 -0
  929. package/lib/typescript/vendor/sdk-contracts/features.d.ts +38 -0
  930. package/lib/typescript/vendor/sdk-contracts/index.d.ts +15 -0
  931. package/lib/typescript/vendor/sdk-contracts/platform.d.ts +35 -0
  932. package/lib/typescript/vendor/sdk-contracts/session-boundary.d.ts +42 -0
  933. package/lib/typescript/vendor/sdk-contracts/session.d.ts +157 -0
  934. package/package.json +153 -0
  935. package/react-native.config.js +24 -0
  936. package/scalebun-react-native.podspec +44 -0
  937. package/src/analytics/EventTracker.ts +654 -0
  938. package/src/analytics/automaticEvents.ts +109 -0
  939. package/src/analytics/batching.ts +54 -0
  940. package/src/analytics/eventLane.ts +48 -0
  941. package/src/analytics/revenue.ts +143 -0
  942. package/src/analytics/subscription.ts +145 -0
  943. package/src/bootstrap/FeatureRegistry.ts +81 -0
  944. package/src/bootstrap/SDKBootstrapper.ts +769 -0
  945. package/src/bucketing/fnv1a32.ts +40 -0
  946. package/src/compat/codepush.ts +189 -0
  947. package/src/config/ConfigManager.ts +233 -0
  948. package/src/config/configTypes.ts +62 -0
  949. package/src/config/otaPolicyState.ts +79 -0
  950. package/src/config/quotaState.ts +484 -0
  951. package/src/core/architecture.ts +39 -0
  952. package/src/core/clock/now.ts +6 -0
  953. package/src/core/config/defaults.ts +0 -0
  954. package/src/core/config/schema.ts +455 -0
  955. package/src/core/constants/endpoints.ts +12 -0
  956. package/src/core/constants/protocol.ts +24 -0
  957. package/src/core/constants/timings.ts +25 -0
  958. package/src/core/constants/version.ts +11 -0
  959. package/src/core/context/app.ts +0 -0
  960. package/src/core/context/device.ts +169 -0
  961. package/src/core/context/session.ts +0 -0
  962. package/src/core/context/user.ts +26 -0
  963. package/src/core/contracts/IFeature.ts +53 -0
  964. package/src/core/di/container.ts +26 -0
  965. package/src/core/encoding/base64.ts +46 -0
  966. package/src/core/id/deviceId.ts +51 -0
  967. package/src/core/id/installationId.ts +52 -0
  968. package/src/core/id/sessionId.ts +0 -0
  969. package/src/core/lifecycle/appLifecycle.ts +33 -0
  970. package/src/core/lifecycle/crashSafe.ts +29 -0
  971. package/src/core/logger/errorClassification.ts +108 -0
  972. package/src/core/logger/internalLogger.ts +65 -0
  973. package/src/core/logger/levels.ts +0 -0
  974. package/src/crypto/hmacSha256.ts +106 -0
  975. package/src/debug/bootstrap.ts +436 -0
  976. package/src/debug/commands.ts +597 -0
  977. package/src/debug/configBuilder.ts +56 -0
  978. package/src/debug/exportBridge.ts +155 -0
  979. package/src/debug/hostResolver.ts +34 -0
  980. package/src/debug/index.ts +25 -0
  981. package/src/debug/metrics.ts +29 -0
  982. package/src/debug/perf.ts +212 -0
  983. package/src/debug/perfWiring.ts +119 -0
  984. package/src/debug/protocol.ts +58 -0
  985. package/src/debug/redaction.ts +483 -0
  986. package/src/debug/replayWiring.ts +148 -0
  987. package/src/debug/screenTracking.ts +55 -0
  988. package/src/debug/sessionWiring.ts +59 -0
  989. package/src/debug/stream.ts +561 -0
  990. package/src/debug/transport.ts +440 -0
  991. package/src/features/bugreport/BugReportFeature.ts +128 -0
  992. package/src/features/bugreport/buildPayload.ts +116 -0
  993. package/src/features/bugreport/index.ts +0 -0
  994. package/src/features/crash/CrashFeature.ts +87 -0
  995. package/src/features/crash/CrashReporter.ts +189 -0
  996. package/src/features/crash/NativeCrashFeature.ts +42 -0
  997. package/src/features/crash/capture.ts +48 -0
  998. package/src/features/crash/globalHandler.ts +39 -0
  999. package/src/features/crash/index.ts +8 -0
  1000. package/src/features/crash/nativeCrashBridge.ts +352 -0
  1001. package/src/features/crash/rejectionHandler.ts +196 -0
  1002. package/src/features/crash/scrubCrash.ts +175 -0
  1003. package/src/features/engage/EngageAnchor.tsx +159 -0
  1004. package/src/features/engage/EngageArchetypeRenderers.tsx +3705 -0
  1005. package/src/features/engage/EngageInAppView.tsx +2678 -0
  1006. package/src/features/engage/EngageInlinePlacement.tsx +169 -0
  1007. package/src/features/engage/EngagePromptProvider.tsx +1318 -0
  1008. package/src/features/engage/EngagePromptView.tsx +1400 -0
  1009. package/src/features/engage/EngageTransport.ts +668 -0
  1010. package/src/features/engage/EngageVariantContent.tsx +1483 -0
  1011. package/src/features/engage/__test-support__/reactNativeStub.js +40 -0
  1012. package/src/features/engage/attachmentCapture.ts +45 -0
  1013. package/src/features/engage/captureVoiceAttachment.ts +81 -0
  1014. package/src/features/engage/engageCoachmarkTour.ts +120 -0
  1015. package/src/features/engage/engageGameLogic.ts +347 -0
  1016. package/src/features/engage/engageMediaSizing.ts +76 -0
  1017. package/src/features/engage/engageMultiStepSheet.ts +53 -0
  1018. package/src/features/engage/engageOutcome.ts +157 -0
  1019. package/src/features/engage/engagePushTokenBridge.ts +84 -0
  1020. package/src/features/engage/engageShakeBridge.ts +67 -0
  1021. package/src/features/engage/engageSignals.ts +40 -0
  1022. package/src/features/engage/engageStoreReviewBridge.ts +48 -0
  1023. package/src/features/engage/engageTestSeenStore.ts +95 -0
  1024. package/src/features/engage/engageThrottle.ts +478 -0
  1025. package/src/features/engage/engageTriggerEngine.ts +350 -0
  1026. package/src/features/engage/engageTypes.ts +712 -0
  1027. package/src/features/engage/engageVariantResolver.ts +364 -0
  1028. package/src/features/engage/engageWindowInsets.ts +108 -0
  1029. package/src/features/engage/gestureTriggerDetector.ts +83 -0
  1030. package/src/features/engage/transcription/TranscriptionProvider.ts +68 -0
  1031. package/src/features/install-referrer/installReferrer.ts +112 -0
  1032. package/src/features/journey/ScaleBunDebugRoot.tsx +1045 -0
  1033. package/src/features/journey/ScaleBunFlatList.tsx +39 -0
  1034. package/src/features/journey/ScaleBunImpression.tsx +109 -0
  1035. package/src/features/journey/ScaleBunScrollView.tsx +101 -0
  1036. package/src/features/journey/ScaleBunSectionList.tsx +39 -0
  1037. package/src/features/journey/autoInstrumentScroll.ts +155 -0
  1038. package/src/features/journey/calibrationContext.ts +44 -0
  1039. package/src/features/journey/gestureBuffer.ts +59 -0
  1040. package/src/features/journey/gestureDetector.ts +162 -0
  1041. package/src/features/journey/impression.ts +92 -0
  1042. package/src/features/journey/interactionProtocol.ts +243 -0
  1043. package/src/features/journey/journeyManager.ts +469 -0
  1044. package/src/features/journey/journeyTypes.ts +215 -0
  1045. package/src/features/journey/nativeScroll.ts +94 -0
  1046. package/src/features/journey/navDetector.ts +149 -0
  1047. package/src/features/journey/screenshotHelper.ts +137 -0
  1048. package/src/features/journey/scrollContext.ts +146 -0
  1049. package/src/features/journey/targetGeometry.ts +197 -0
  1050. package/src/features/journey/targetRegistry.ts +43 -0
  1051. package/src/features/journey/touchTarget.ts +339 -0
  1052. package/src/features/journey/uiState.ts +182 -0
  1053. package/src/features/navigation/AutoScreenDetector.ts +617 -0
  1054. package/src/features/network/NetworkFeature.ts +232 -0
  1055. package/src/features/network/index.ts +59 -0
  1056. package/src/features/network/thirdParty.ts +160 -0
  1057. package/src/features/ota/OtaEventEmitter.ts +187 -0
  1058. package/src/features/ota/OtaOrchestrator.ts +1513 -0
  1059. package/src/features/ota/OtaTypes.ts +230 -0
  1060. package/src/features/ota/deviceAttributes.ts +46 -0
  1061. package/src/features/ota/environment.ts +199 -0
  1062. package/src/features/ota/geoCountry.ts +85 -0
  1063. package/src/features/ota/retry.ts +123 -0
  1064. package/src/features/ota/signedDownload.ts +202 -0
  1065. package/src/features/ota/useOtaUpdate.ts +161 -0
  1066. package/src/features/performance/PerformanceFeature.ts +729 -0
  1067. package/src/features/performance/collectors/AppLaunchCollector.ts +131 -0
  1068. package/src/features/performance/collectors/AutoScreenLoadCollector.ts +258 -0
  1069. package/src/features/performance/collectors/CustomTraceCollector.ts +116 -0
  1070. package/src/features/performance/collectors/FrameMetricsCollector.ts +210 -0
  1071. package/src/features/performance/collectors/JsStallCollector.ts +184 -0
  1072. package/src/features/performance/collectors/NetworkPerfCollector.ts +69 -0
  1073. package/src/features/performance/collectors/ScreenLoadCollector.ts +95 -0
  1074. package/src/features/performance/collectors/UiHangCollector.ts +98 -0
  1075. package/src/features/performance/config.ts +136 -0
  1076. package/src/features/performance/getNativePerformanceModule.ts +38 -0
  1077. package/src/features/performance/index.ts +28 -0
  1078. package/src/features/performance/metrics/MetricsEngine.ts +152 -0
  1079. package/src/features/performance/models.ts +235 -0
  1080. package/src/features/performance/transport/PerformanceTransport.ts +161 -0
  1081. package/src/features/profiler/ProfilerFeature.ts +661 -0
  1082. package/src/features/profiler/config.ts +230 -0
  1083. package/src/features/profiler/contracts.ts +176 -0
  1084. package/src/features/profiler/index.ts +45 -0
  1085. package/src/features/profiler/models.ts +430 -0
  1086. package/src/features/profiler/transport/ProfilerTransport.ts +93 -0
  1087. package/src/features/replay/bridge/adapters/bridgeAdapter.ts +235 -0
  1088. package/src/features/replay/bridge/eventEmitter.ts +176 -0
  1089. package/src/features/replay/bridge/nativeModule.ts +252 -0
  1090. package/src/features/replay/bridge/nativeQuota.ts +111 -0
  1091. package/src/features/replay/core/clock/clock.ts +38 -0
  1092. package/src/features/replay/core/config/defaults.ts +66 -0
  1093. package/src/features/replay/core/config/validator.ts +113 -0
  1094. package/src/features/replay/core/env/environmentDetector.ts +21 -0
  1095. package/src/features/replay/core/errors/safeCall.ts +34 -0
  1096. package/src/features/replay/core/ids/sessionId.ts +44 -0
  1097. package/src/features/replay/core/logger/logger.ts +36 -0
  1098. package/src/features/replay/core/privacy/privacyTypes.ts +41 -0
  1099. package/src/features/replay/core/queue/boundedQueue.ts +71 -0
  1100. package/src/features/replay/integrations/alert/alertInstrumentation.ts +277 -0
  1101. package/src/features/replay/integrations/logs/consoleIntegration.ts +136 -0
  1102. package/src/features/replay/integrations/network/networkAdapter.ts +73 -0
  1103. package/src/features/replay/integrations/react-navigation/navigationIntegration.ts +89 -0
  1104. package/src/features/replay/integrations/touch/ReplayRoot.tsx +114 -0
  1105. package/src/features/replay/pipeline/batching/batchManager.ts +133 -0
  1106. package/src/features/replay/pipeline/envelope/envelopeBuilder.ts +66 -0
  1107. package/src/features/replay/pipeline/retry/retryPolicy.ts +62 -0
  1108. package/src/features/replay/pipeline/transport/transportTypes.ts +85 -0
  1109. package/src/features/replay/public/api.ts +364 -0
  1110. package/src/features/replay/public/enums.ts +78 -0
  1111. package/src/features/replay/public/types.ts +297 -0
  1112. package/src/features/replay/replay/breadcrumbs/breadcrumbCollector.ts +115 -0
  1113. package/src/features/replay/replay/frames/frameModels.ts +45 -0
  1114. package/src/features/replay/replay/session/sessionOrchestrator.ts +286 -0
  1115. package/src/features/replay/replay/timeline/timelineModels.ts +129 -0
  1116. package/src/features/replay/transport/BackendReplayTransport.ts +215 -0
  1117. package/src/features/replay/transport/CompositeReplayTransport.ts +82 -0
  1118. package/src/features/replay/transport/DesktopReplayTransport.ts +140 -0
  1119. package/src/features/replay/transport/ReplayTransport.ts +84 -0
  1120. package/src/features/session/BackendSessionAdapter.ts +1977 -0
  1121. package/src/features/session/DesktopSessionTransport.ts +130 -0
  1122. package/src/features/session/JourneyEventPipeline.ts +278 -0
  1123. package/src/features/session/ReplayCaptureManager.ts +1401 -0
  1124. package/src/features/session/ScrollTracker.ts +166 -0
  1125. package/src/features/session/SessionManager.ts +1687 -0
  1126. package/src/features/session/SyncDecisionEngine.ts +117 -0
  1127. package/src/features/session/frameLink.ts +41 -0
  1128. package/src/features/session/frameScrollState.ts +36 -0
  1129. package/src/features/session/index.ts +88 -0
  1130. package/src/features/session/nativeCapture.ts +43 -0
  1131. package/src/features/session/outboxFrameCapture.ts +79 -0
  1132. package/src/features/session/sessionTypes.ts +528 -0
  1133. package/src/features/session/viewportPlausibility.ts +134 -0
  1134. package/src/features/skan/skanBridge.ts +53 -0
  1135. package/src/features/skan/skanConversionManager.ts +173 -0
  1136. package/src/index.ts +206 -0
  1137. package/src/integrations/fetch/fetchInterceptor.ts +201 -0
  1138. package/src/integrations/navigation/screenTracker.ts +0 -0
  1139. package/src/integrations/network/bodyCapture.ts +328 -0
  1140. package/src/integrations/xhr/xhrInterceptor.ts +259 -0
  1141. package/src/metro/composeSourceMap.ts +104 -0
  1142. package/src/metro/featureModules.ts +143 -0
  1143. package/src/metro/index.ts +336 -0
  1144. package/src/metro/optionalDependencyStub.ts +13 -0
  1145. package/src/metro/optionalModules.ts +27 -0
  1146. package/src/metro/serializerCompose.ts +193 -0
  1147. package/src/pipeline/dispatcher/dispatcher.ts +22 -0
  1148. package/src/pipeline/envelope/envelope.ts +31 -0
  1149. package/src/pipeline/envelope/serializer.ts +14 -0
  1150. package/src/pipeline/processors/dedupe.ts +0 -0
  1151. package/src/pipeline/processors/enrich.ts +11 -0
  1152. package/src/pipeline/processors/sampling.ts +0 -0
  1153. package/src/pipeline/processors/sanitize.ts +23 -0
  1154. package/src/pipeline/queue/flushQueue.ts +144 -0
  1155. package/src/pipeline/queue/memoryQueue.ts +31 -0
  1156. package/src/pipeline/queue/persistentQueue.ts +385 -0
  1157. package/src/pipeline/queue/queuePolicy.ts +0 -0
  1158. package/src/pipeline/scheduler/flushScheduler.ts +47 -0
  1159. package/src/pipeline/scheduler/retryPolicy.ts +8 -0
  1160. package/src/public/ScaleBunErrorBoundary.tsx +91 -0
  1161. package/src/public/ScaleBunFacade.ts +2129 -0
  1162. package/src/public/ScaleBunProvider.tsx +91 -0
  1163. package/src/public/typedTracker.ts +123 -0
  1164. package/src/public/types.ts +362 -0
  1165. package/src/push/PushManager.ts +318 -0
  1166. package/src/push/adapters/ManualAdapter.ts +100 -0
  1167. package/src/push/adapters/NativeBridgeAdapter.ts +142 -0
  1168. package/src/push/adapters/NoopAdapter.ts +46 -0
  1169. package/src/push/adapters/RNFirebaseMessagingAdapter.ts +271 -0
  1170. package/src/push/adapters/loadMessaging.ts +24 -0
  1171. package/src/push/adapters/loadNotifee.ts +29 -0
  1172. package/src/push/adapters/selectAdapter.ts +176 -0
  1173. package/src/push/detect/capabilities.ts +124 -0
  1174. package/src/push/index.ts +22 -0
  1175. package/src/push/native/nativeNotifications.ts +207 -0
  1176. package/src/push/types.ts +159 -0
  1177. package/src/specs/NativeReplaySdk.ts +101 -0
  1178. package/src/specs/NativeScaleBunCrash.ts +64 -0
  1179. package/src/specs/NativeScaleBunInstallReferrer.ts +29 -0
  1180. package/src/specs/NativeScaleBunOta.ts +154 -0
  1181. package/src/specs/NativeScaleBunPerformance.ts +34 -0
  1182. package/src/specs/NativeScaleBunProfiler.ts +41 -0
  1183. package/src/specs/NativeScaleBunStorage.ts +59 -0
  1184. package/src/storage/StorageBackend.ts +272 -0
  1185. package/src/storage/db/sqlite.ts +0 -0
  1186. package/src/storage/files/fileStore.ts +0 -0
  1187. package/src/transport/auth/sdkSession.ts +27 -0
  1188. package/src/transport/backoff/exponentialJitter.ts +9 -0
  1189. package/src/transport/http/endpoints.ts +33 -0
  1190. package/src/transport/http/httpClient.ts +73 -0
  1191. package/src/transport/rateLimit/retryAfter.ts +0 -0
  1192. package/src/transport/signing/requestSigner.ts +0 -0
  1193. package/src/vendor/protocol/index.ts +391 -0
  1194. package/src/vendor/protocol/internal/validation.ts +497 -0
  1195. package/src/vendor/sdk-contracts/bridge.ts +162 -0
  1196. package/src/vendor/sdk-contracts/config.ts +192 -0
  1197. package/src/vendor/sdk-contracts/events.ts +50 -0
  1198. package/src/vendor/sdk-contracts/features.ts +42 -0
  1199. package/src/vendor/sdk-contracts/index.ts +98 -0
  1200. package/src/vendor/sdk-contracts/platform.ts +42 -0
  1201. package/src/vendor/sdk-contracts/session-boundary.ts +55 -0
  1202. package/src/vendor/sdk-contracts/session.ts +225 -0
  1203. package/src/vendor/vendor.manifest +10 -0
@@ -0,0 +1,4264 @@
1
+ /**
2
+ * ScaleBun SDK — Archetype-specific in-app renderers
3
+ *
4
+ * These renderers own mechanic content only. Presentation (modal, sheet, banner,
5
+ * fullscreen, anchored, or inline) stays in EngageInAppView so one mechanic can
6
+ * be mounted correctly in every container.
7
+ *
8
+ * Implemented archetypes (matching dashboard variant keys):
9
+ * spin_wheel → animated spinning prize wheel
10
+ * countdown_offer → live flip-style countdown clock
11
+ * scratch_card → tap-to-reveal scratch surface
12
+ * slot_reward → three-reel slot machine
13
+ * poll_card → single-choice option list
14
+ * nps_prompt → 0–10 NPS number row
15
+ * emoji_reaction_bar → emoji row picker
16
+ * rating_request → 1–5 star tap rating
17
+ * streak_reward → day-streak dot strip
18
+ * checklist_progress → tick-off checklist
19
+ * coupon_wallet → large coupon reveal chip
20
+ */
21
+
22
+ import React, { useEffect, useMemo, useRef, useState } from "react";
23
+ import { AccessibilityInfo, Animated, Easing, Image, PanResponder, Pressable, ScrollView, Text, TextInput, View } from "react-native";
24
+ import { addScratchedCell, boundedInt, clampPage, gradeGameAnswer, resolveSlotOutcome, scoreGameAnswer, scratchCellAt, scratchProgress, toggleGameSelection, wheelTargetAngle } from "./engageGameLogic";
25
+ import { resolveInAppMediaHeight } from "./engageMediaSizing";
26
+ import { inAppOutcomeTerminalForCta } from "./engageOutcome";
27
+ import { requestNativeStoreReview } from "./engageStoreReviewBridge";
28
+
29
+ // ─── helpers ──────────────────────────────────────────────────────────────────
30
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
31
+ const ext = props => props.spec.ext ?? {};
32
+ const str = v => typeof v === "string" ? v : "";
33
+ const num = (v, fallback) => typeof v === "number" ? v : fallback;
34
+ const arr = v => Array.isArray(v) ? v : [];
35
+ const record = v => v !== null && typeof v === "object" && !Array.isArray(v) ? v : {};
36
+ const labelOf = v => {
37
+ const value = record(v);
38
+ return str(value.label ?? value.title ?? value.name ?? value.prompt ?? v);
39
+ };
40
+ const firstText = (source, keys) => {
41
+ for (const key of keys) {
42
+ const value = str(source[key]);
43
+ if (value) return value;
44
+ }
45
+ return "";
46
+ };
47
+ const variantOf = props => props.variantKey || str(props.spec.variantKey);
48
+ const mediaUri = value => {
49
+ const item = record(value);
50
+ return str(item.uri ?? item.url ?? item.imageUrl ?? item.image ?? value);
51
+ };
52
+ const gameUnavailableText = error => {
53
+ if (error instanceof Error && error.message) return error.message;
54
+ return "Result unavailable. Please try again.";
55
+ };
56
+ const gameResultLabel = outcome => {
57
+ if (!outcome) return "";
58
+ if (outcome.label) return outcome.label;
59
+ return typeof outcome.value === "string" || typeof outcome.value === "number" ? String(outcome.value) : "";
60
+ };
61
+ const presentTerminalOutcome = (props, resultLabel) => {
62
+ if (!props.onOutcome) return false;
63
+ props.onOutcome({
64
+ ...(resultLabel ? {
65
+ resultLabel
66
+ } : {}),
67
+ fallback: inAppOutcomeTerminalForCta(props.spec.cta)
68
+ });
69
+ return true;
70
+ };
71
+ async function requireGameOutcome(props, attempt = 1, input) {
72
+ if (!props.requestGameResult) throw new Error("Game result handler is not configured");
73
+ const result = await props.requestGameResult(attempt, input);
74
+ if (result.status === "unavailable") {
75
+ throw new Error(result.message || "Result unavailable. Please try again.");
76
+ }
77
+ return result.outcome;
78
+ }
79
+ function useReducedMotion() {
80
+ const [enabled, setEnabled] = useState(null);
81
+ useEffect(() => {
82
+ let mounted = true;
83
+ AccessibilityInfo.isReduceMotionEnabled().then(value => {
84
+ if (mounted) setEnabled(value);
85
+ });
86
+ const subscription = AccessibilityInfo.addEventListener("reduceMotionChanged", setEnabled);
87
+ return () => {
88
+ mounted = false;
89
+ subscription.remove();
90
+ };
91
+ }, []);
92
+ return enabled;
93
+ }
94
+ function Title({
95
+ text,
96
+ theme
97
+ }) {
98
+ if (!text) return null;
99
+ return /*#__PURE__*/_jsx(Text, {
100
+ style: [sh.title, {
101
+ color: theme.titleColor
102
+ }],
103
+ children: text
104
+ });
105
+ }
106
+ function Body({
107
+ text,
108
+ theme
109
+ }) {
110
+ if (!text) return null;
111
+ return /*#__PURE__*/_jsx(Text, {
112
+ style: [sh.body, {
113
+ color: theme.bodyColor
114
+ }],
115
+ children: text
116
+ });
117
+ }
118
+ function PrimaryBtn({
119
+ label,
120
+ accent,
121
+ onPress,
122
+ disabled = false
123
+ }) {
124
+ return /*#__PURE__*/_jsx(Pressable, {
125
+ style: [sh.primaryBtn, {
126
+ backgroundColor: accent,
127
+ opacity: disabled ? 0.65 : 1
128
+ }],
129
+ onPress: onPress,
130
+ disabled: disabled,
131
+ accessibilityRole: "button",
132
+ accessibilityLabel: label,
133
+ accessibilityState: {
134
+ disabled
135
+ },
136
+ children: /*#__PURE__*/_jsx(Text, {
137
+ style: sh.primaryText,
138
+ children: label
139
+ })
140
+ });
141
+ }
142
+ function SecondaryBtn({
143
+ label,
144
+ onPress,
145
+ color
146
+ }) {
147
+ return /*#__PURE__*/_jsx(Pressable, {
148
+ style: sh.secondaryBtn,
149
+ onPress: onPress,
150
+ accessibilityRole: "button",
151
+ accessibilityLabel: label,
152
+ children: /*#__PURE__*/_jsx(Text, {
153
+ style: [sh.secondaryText, {
154
+ color
155
+ }],
156
+ children: label
157
+ })
158
+ });
159
+ }
160
+ function CtaArea({
161
+ beforePrimary,
162
+ onPrimary,
163
+ ...props
164
+ }) {
165
+ const {
166
+ spec,
167
+ theme,
168
+ onCta,
169
+ onDismiss
170
+ } = props;
171
+ if (!spec.cta && !spec.secondaryCta) return null;
172
+ return /*#__PURE__*/_jsxs(View, {
173
+ style: sh.ctaRow,
174
+ children: [spec.secondaryCta ? /*#__PURE__*/_jsx(SecondaryBtn, {
175
+ label: spec.secondaryCta.label,
176
+ onPress: onDismiss,
177
+ color: theme.secondaryColor
178
+ }) : null, spec.cta ? /*#__PURE__*/_jsx(PrimaryBtn, {
179
+ label: spec.cta.label,
180
+ accent: theme.accent,
181
+ onPress: () => {
182
+ beforePrimary?.();
183
+ if (onPrimary) onPrimary();else if (presentTerminalOutcome(props)) return;else if (spec.cta.action === "dismiss") onDismiss();else onCta(spec.cta);
184
+ }
185
+ }) : null]
186
+ });
187
+ }
188
+
189
+ // ─── 1. Spin Wheel ────────────────────────────────────────────────────────────
190
+
191
+ const WHEEL_COLORS = ["#6C5CE7", "#8B7CF6", "#F5A524", "#E85D75", "#4834D4", "#00A896"];
192
+ const WHEEL_MAX_SIZE = 264;
193
+ const MAX_WHEEL_SEGMENTS = 8;
194
+ export function resolveWheelDiameter(availableWidth) {
195
+ return Number.isFinite(availableWidth) && availableWidth > 0 ? Math.min(WHEEL_MAX_SIZE, Math.floor(availableWidth)) : 0;
196
+ }
197
+ function wheelOutcomeIdentity(source) {
198
+ const resultValue = source.resultState ?? source.rewardResult ?? source.result ?? source.reward;
199
+ const result = record(resultValue);
200
+ return {
201
+ explicitIndex: num(source.winningIndex ?? source.resultIndex ?? source.selectedIndex ?? result.index, -1),
202
+ resultId: str(source.winningId ?? source.segmentId ?? result.segmentId ?? result.id ?? result.value),
203
+ resultLabel: labelOf(resultValue)
204
+ };
205
+ }
206
+ function findWheelOutcomeIndex(segments, identity) {
207
+ if (identity.explicitIndex >= 0) {
208
+ const explicit = segments.findIndex(({
209
+ sourceIndex
210
+ }) => sourceIndex === Math.floor(identity.explicitIndex));
211
+ if (explicit >= 0) return explicit;
212
+ }
213
+ if (identity.resultId) {
214
+ const byId = segments.findIndex(({
215
+ id
216
+ }) => id === identity.resultId);
217
+ if (byId >= 0) return byId;
218
+ }
219
+ if (!identity.resultLabel) return -1;
220
+ const normalizedResult = normalizedWheelValue(identity.resultLabel);
221
+ const normalizedLabels = segments.map(segment => [segment.fullLabel, segment.label].map(normalizedWheelValue));
222
+ const exact = normalizedLabels.findIndex(labels => labels.includes(normalizedResult));
223
+ if (exact >= 0) return exact;
224
+ let bestIndex = -1;
225
+ let bestLength = -1;
226
+ normalizedLabels.forEach((labels, index) => {
227
+ const length = Math.max(...labels.map(label => normalizedResult.includes(label) ? label.length : -1));
228
+ if (length > bestLength) {
229
+ bestIndex = index;
230
+ bestLength = length;
231
+ }
232
+ });
233
+ return bestIndex;
234
+ }
235
+ export function prepareWheelSegments(value, source = {}) {
236
+ const segments = arr(value).map((entry, index) => {
237
+ const item = record(entry);
238
+ const fullLabel = labelOf(entry);
239
+ return {
240
+ id: str(item.id ?? item.value ?? item.code) || String(index),
241
+ label: str(item.shortLabel) || fullLabel,
242
+ fullLabel,
243
+ color: str(item.color ?? item.backgroundColor),
244
+ sourceIndex: index
245
+ };
246
+ }).filter(({
247
+ label
248
+ }) => Boolean(label));
249
+ if (segments.length <= MAX_WHEEL_SEGMENTS) return segments;
250
+ const winnerIndex = findWheelOutcomeIndex(segments, wheelOutcomeIdentity(source));
251
+ const winner = winnerIndex >= 0 ? segments[winnerIndex] : undefined;
252
+ const visible = segments.slice(0, MAX_WHEEL_SEGMENTS);
253
+ if (winner && !visible.includes(winner)) visible[MAX_WHEEL_SEGMENTS - 1] = winner;
254
+ return visible;
255
+ }
256
+ const normalizedWheelValue = value => value.toLowerCase().replace(/\s+/g, " ").trim();
257
+ export function resolveWheelOutcome(segments, source) {
258
+ const identity = wheelOutcomeIdentity(source);
259
+ const index = findWheelOutcomeIndex(segments, identity);
260
+ const resolved = index >= 0;
261
+ return {
262
+ index: resolved ? index : 0,
263
+ label: identity.resultLabel || (resolved ? segments[index]?.fullLabel ?? "" : ""),
264
+ resolved
265
+ };
266
+ }
267
+ export function SpinWheelInApp(props) {
268
+ const {
269
+ spec,
270
+ theme,
271
+ onDismiss,
272
+ onCta
273
+ } = props;
274
+ const e = ext(props);
275
+ const [gameOutcome, setGameOutcome] = useState(null);
276
+ const [gameError, setGameError] = useState("");
277
+ const outcomeSource = gameOutcome ? {
278
+ winningId: gameOutcome.segmentId || gameOutcome.id,
279
+ winningIndex: gameOutcome.segmentIndex,
280
+ resultState: {
281
+ id: gameOutcome.id,
282
+ label: gameResultLabel(gameOutcome)
283
+ }
284
+ } : {};
285
+ const configuredSegments = prepareWheelSegments(e.segments, outcomeSource);
286
+ const segments = configuredSegments.length > 0 ? configuredSegments : prepareWheelSegments(["10% OFF", "FREE GIFT", "20% OFF", "TRY AGAIN", "5% OFF", "JACKPOT!"]);
287
+ const n = segments.length;
288
+ const sliceDeg = 360 / n;
289
+ const [wheelSize, setWheelSize] = useState(WHEEL_MAX_SIZE);
290
+ const wheelRadius = wheelSize / 2;
291
+ const wheelScale = wheelSize / WHEEL_MAX_SIZE;
292
+ const labelRadius = wheelRadius * 0.65;
293
+ const hubSize = Math.max(30, 40 * wheelScale);
294
+ const wedgeHalfWidth = Math.ceil(wheelRadius * Math.tan(Math.PI / Math.max(n, 3)));
295
+ const outcome = resolveWheelOutcome(segments, outcomeSource);
296
+ const spin = useRef(new Animated.Value(0)).current;
297
+ const reducedMotion = useReducedMotion();
298
+ const angleRef = useRef(0);
299
+ const [phase, setPhase] = useState("idle");
300
+ useEffect(() => () => spin.stopAnimation(), [spin]);
301
+ const doSpin = async () => {
302
+ if (phase !== "idle") return;
303
+ setGameError("");
304
+ setPhase("resolving");
305
+ props.onInteraction?.({
306
+ mechanic: "spin_wheel",
307
+ phase: "started",
308
+ attempt: 1
309
+ });
310
+ let remoteOutcome;
311
+ try {
312
+ remoteOutcome = await requireGameOutcome(props);
313
+ } catch (error) {
314
+ setGameError(gameUnavailableText(error));
315
+ setPhase("idle");
316
+ return;
317
+ }
318
+ const remoteSource = {
319
+ winningId: remoteOutcome.segmentId || remoteOutcome.id,
320
+ winningIndex: remoteOutcome.segmentIndex,
321
+ resultState: {
322
+ id: remoteOutcome.id,
323
+ label: gameResultLabel(remoteOutcome)
324
+ }
325
+ };
326
+ const remoteSegments = prepareWheelSegments(e.segments, remoteSource);
327
+ const displayedSegments = remoteSegments.length > 0 ? remoteSegments : segments;
328
+ const remoteResult = resolveWheelOutcome(displayedSegments, remoteSource);
329
+ if (!remoteResult.resolved) {
330
+ setGameError("The backend result does not match a wheel segment.");
331
+ setPhase("idle");
332
+ return;
333
+ }
334
+ setGameOutcome(remoteOutcome);
335
+ setPhase("spinning");
336
+ const from = angleRef.current;
337
+ // Animation is visual only; the customer backend's result selects the segment.
338
+ const to = wheelTargetAngle(displayedSegments.length, remoteResult.index, from);
339
+ angleRef.current = to;
340
+ spin.setValue(from);
341
+ Animated.timing(spin, {
342
+ toValue: to,
343
+ duration: reducedMotion === false ? 3600 : 0,
344
+ easing: Easing.out(Easing.cubic),
345
+ useNativeDriver: true
346
+ }).start(({
347
+ finished
348
+ }) => {
349
+ if (!finished) return;
350
+ setPhase("result");
351
+ props.onInteraction?.({
352
+ mechanic: "spin_wheel",
353
+ phase: "resolved",
354
+ attempt: 1,
355
+ result: remoteResult.label || undefined,
356
+ meta: {
357
+ backendResolved: true,
358
+ segmentIndex: remoteResult.index
359
+ }
360
+ });
361
+ AccessibilityInfo.announceForAccessibility(remoteResult.label || "Spin complete");
362
+ presentTerminalOutcome(props, remoteResult.label);
363
+ });
364
+ };
365
+ const rotate = spin.interpolate({
366
+ inputRange: [0, 360],
367
+ outputRange: ["0deg", "360deg"],
368
+ extrapolate: "extend"
369
+ });
370
+ const counterRotate = spin.interpolate({
371
+ inputRange: [0, 360],
372
+ outputRange: ["0deg", "-360deg"],
373
+ extrapolate: "extend"
374
+ });
375
+ const onWheelLayout = ({
376
+ nativeEvent
377
+ }) => {
378
+ const measured = resolveWheelDiameter(nativeEvent.layout.width);
379
+ if (measured > 0) {
380
+ setWheelSize(current => current === measured ? current : measured);
381
+ }
382
+ };
383
+ const spinLabel = str(e.spinLabel) || (/spin/i.test(spec.cta?.label ?? "") ? spec.cta?.label : "") || "SPIN";
384
+ const claimLabel = str(e.claimLabel ?? e.ctaLabel) || (/spin/i.test(spec.cta?.label ?? "") ? gameOutcome ? "CLAIM REWARD" : "CONTINUE" : spec.cta?.label) || "CONTINUE";
385
+ const handleClaim = () => {
386
+ props.onInteraction?.({
387
+ mechanic: "spin_wheel",
388
+ phase: "claimed",
389
+ attempt: 1,
390
+ result: outcome.label || undefined
391
+ });
392
+ if (!spec.cta) {
393
+ onDismiss();
394
+ return;
395
+ }
396
+ if (spec.cta.action === "dismiss") {
397
+ onDismiss();
398
+ return;
399
+ }
400
+ onCta(spec.cta);
401
+ setPhase("claimed");
402
+ };
403
+ return /*#__PURE__*/_jsxs(View, {
404
+ style: sh.content,
405
+ children: [/*#__PURE__*/_jsx(Title, {
406
+ text: spec.title,
407
+ theme: theme
408
+ }), /*#__PURE__*/_jsx(Body, {
409
+ text: spec.body,
410
+ theme: theme
411
+ }), /*#__PURE__*/_jsxs(View, {
412
+ style: wh.stage,
413
+ onLayout: onWheelLayout,
414
+ children: [/*#__PURE__*/_jsx(View, {
415
+ style: [wh.pointer, {
416
+ borderTopColor: theme.accent
417
+ }]
418
+ }), /*#__PURE__*/_jsxs(Animated.View, {
419
+ accessibilityElementsHidden: true,
420
+ importantForAccessibility: "no-hide-descendants",
421
+ style: [wh.wheel, {
422
+ backgroundColor: theme.couponBg,
423
+ borderColor: theme.accent,
424
+ borderRadius: wheelRadius,
425
+ height: wheelSize,
426
+ transform: [{
427
+ rotate
428
+ }],
429
+ width: wheelSize
430
+ }],
431
+ children: [segments.map((segment, i) => {
432
+ const angle = sliceDeg * i;
433
+ const color = segment.color || WHEEL_COLORS[i % WHEEL_COLORS.length] || theme.accent;
434
+ const labelWidth = Math.max(28, Math.min(84 * wheelScale, 2 * labelRadius * Math.sin(Math.PI / Math.max(n, 2)) - 8 * wheelScale));
435
+ return /*#__PURE__*/_jsxs(React.Fragment, {
436
+ children: [n === 1 ? /*#__PURE__*/_jsx(View, {
437
+ style: [wh.singleWedge, {
438
+ backgroundColor: color
439
+ }]
440
+ }) : n === 2 ? /*#__PURE__*/_jsx(View, {
441
+ style: [wh.halfWedge, {
442
+ backgroundColor: color,
443
+ height: wheelRadius,
444
+ top: i * wheelRadius,
445
+ width: wheelSize
446
+ }]
447
+ }) : /*#__PURE__*/_jsx(View, {
448
+ style: [wh.wedgeOrbit, {
449
+ height: wheelSize,
450
+ transform: [{
451
+ rotate: `${angle + 180}deg`
452
+ }],
453
+ width: wheelSize
454
+ }],
455
+ children: /*#__PURE__*/_jsx(View, {
456
+ style: [wh.wedge, {
457
+ borderBottomWidth: wheelRadius,
458
+ borderLeftWidth: wedgeHalfWidth,
459
+ borderRightWidth: wedgeHalfWidth,
460
+ borderBottomColor: color,
461
+ left: wheelRadius - wedgeHalfWidth,
462
+ top: wheelRadius
463
+ }]
464
+ })
465
+ }), /*#__PURE__*/_jsx(View, {
466
+ style: [wh.labelOrbit, {
467
+ height: wheelSize,
468
+ transform: [{
469
+ rotate: `${angle}deg`
470
+ }],
471
+ width: wheelSize
472
+ }],
473
+ children: /*#__PURE__*/_jsx(Animated.View, {
474
+ style: [wh.segment, {
475
+ left: wheelRadius - labelWidth / 2,
476
+ minHeight: 36 * wheelScale,
477
+ top: 28 * wheelScale,
478
+ width: labelWidth,
479
+ transform: [{
480
+ rotate: `${-angle}deg`
481
+ }, {
482
+ rotate: counterRotate
483
+ }]
484
+ }],
485
+ children: /*#__PURE__*/_jsx(Text, {
486
+ numberOfLines: 2,
487
+ adjustsFontSizeToFit: true,
488
+ minimumFontScale: 0.7,
489
+ style: wh.segmentText,
490
+ children: segment.label
491
+ })
492
+ })
493
+ })]
494
+ }, `${segment.id}-${i}`);
495
+ }), /*#__PURE__*/_jsx(View, {
496
+ style: [wh.hub, {
497
+ borderColor: theme.accent,
498
+ borderRadius: hubSize / 2,
499
+ height: hubSize,
500
+ left: wheelRadius - hubSize / 2,
501
+ top: wheelRadius - hubSize / 2,
502
+ width: hubSize
503
+ }],
504
+ children: /*#__PURE__*/_jsx(Text, {
505
+ style: [wh.hubText, {
506
+ color: theme.accent
507
+ }],
508
+ children: "\u2605"
509
+ })
510
+ })]
511
+ })]
512
+ }), phase === "result" || phase === "claimed" ? /*#__PURE__*/_jsxs(View, {
513
+ style: [wh.result, {
514
+ backgroundColor: theme.accent
515
+ }],
516
+ accessibilityRole: "summary",
517
+ accessibilityLiveRegion: "polite",
518
+ children: [/*#__PURE__*/_jsx(Text, {
519
+ style: wh.resultEyebrow,
520
+ children: phase === "claimed" ? str(e.claimedLabel) || (outcome.resolved ? "CLAIMED" : "DONE") : outcome.resolved ? str(e.resultLabel) || "YOU LANDED ON" : str(e.resultLabel) || "SPIN COMPLETE"
521
+ }), /*#__PURE__*/_jsx(Text, {
522
+ style: wh.resultText,
523
+ children: phase === "claimed" ? str(e.claimedState) || outcome.label || (outcome.resolved ? "Reward claimed" : "Action complete") : outcome.label || "Your result will be confirmed next"
524
+ })]
525
+ }) : null, phase === "idle" && gameError ? /*#__PURE__*/_jsx(Text, {
526
+ style: [wh.unavailable, {
527
+ color: theme.secondaryColor
528
+ }],
529
+ children: gameError
530
+ }) : null, /*#__PURE__*/_jsx(View, {
531
+ style: sh.ctaRow,
532
+ children: phase === "idle" || phase === "resolving" || phase === "spinning" ? /*#__PURE__*/_jsx(PrimaryBtn, {
533
+ label: phase === "resolving" ? "CHECKING…" : phase === "spinning" ? "SPINNING…" : gameError ? "RETRY" : spinLabel,
534
+ accent: theme.accent,
535
+ onPress: () => {
536
+ void doSpin();
537
+ },
538
+ disabled: phase === "resolving" || phase === "spinning"
539
+ }) : phase === "result" ? /*#__PURE__*/_jsxs(_Fragment, {
540
+ children: [spec.secondaryCta ? /*#__PURE__*/_jsx(SecondaryBtn, {
541
+ label: spec.secondaryCta.label,
542
+ onPress: onDismiss,
543
+ color: theme.secondaryColor
544
+ }) : null, /*#__PURE__*/_jsx(PrimaryBtn, {
545
+ label: claimLabel,
546
+ accent: theme.accent,
547
+ onPress: handleClaim
548
+ })]
549
+ }) : /*#__PURE__*/_jsx(PrimaryBtn, {
550
+ label: "DONE",
551
+ accent: theme.accent,
552
+ onPress: onDismiss
553
+ })
554
+ })]
555
+ });
556
+ }
557
+ const wh = {
558
+ stage: {
559
+ alignItems: "center",
560
+ marginVertical: 12
561
+ },
562
+ pointer: {
563
+ width: 0,
564
+ height: 0,
565
+ borderLeftWidth: 12,
566
+ borderRightWidth: 12,
567
+ borderTopWidth: 24,
568
+ borderLeftColor: "transparent",
569
+ borderRightColor: "transparent",
570
+ zIndex: 10,
571
+ marginBottom: -4
572
+ },
573
+ wheel: {
574
+ borderWidth: 5,
575
+ overflow: "hidden",
576
+ position: "relative",
577
+ shadowColor: "#000",
578
+ shadowOpacity: 0.2,
579
+ shadowRadius: 10,
580
+ shadowOffset: {
581
+ width: 0,
582
+ height: 4
583
+ },
584
+ elevation: 6
585
+ },
586
+ wedgeOrbit: {
587
+ position: "absolute",
588
+ left: 0,
589
+ top: 0
590
+ },
591
+ wedge: {
592
+ position: "absolute",
593
+ width: 0,
594
+ height: 0,
595
+ borderLeftColor: "transparent",
596
+ borderRightColor: "transparent"
597
+ },
598
+ singleWedge: {
599
+ position: "absolute",
600
+ inset: 0
601
+ },
602
+ halfWedge: {
603
+ position: "absolute",
604
+ left: 0
605
+ },
606
+ labelOrbit: {
607
+ position: "absolute",
608
+ left: 0,
609
+ top: 0
610
+ },
611
+ segment: {
612
+ position: "absolute",
613
+ borderRadius: 10,
614
+ paddingHorizontal: 5,
615
+ justifyContent: "center",
616
+ alignItems: "center",
617
+ backgroundColor: "rgba(0,0,0,0.32)"
618
+ },
619
+ segmentText: {
620
+ color: "#FFFFFF",
621
+ fontSize: 10,
622
+ fontWeight: "800",
623
+ textAlign: "center",
624
+ lineHeight: 12,
625
+ textShadowColor: "rgba(0,0,0,0.25)",
626
+ textShadowOffset: {
627
+ width: 0,
628
+ height: 1
629
+ },
630
+ textShadowRadius: 2
631
+ },
632
+ hub: {
633
+ position: "absolute",
634
+ backgroundColor: "#FFFFFF",
635
+ borderWidth: 3,
636
+ alignItems: "center",
637
+ justifyContent: "center"
638
+ },
639
+ hubText: {
640
+ fontSize: 16,
641
+ fontWeight: "900"
642
+ },
643
+ result: {
644
+ borderRadius: 12,
645
+ paddingVertical: 10,
646
+ paddingHorizontal: 18,
647
+ alignSelf: "center",
648
+ marginBottom: 10
649
+ },
650
+ resultText: {
651
+ color: "#fff",
652
+ fontWeight: "800",
653
+ fontSize: 16,
654
+ textAlign: "center"
655
+ },
656
+ resultEyebrow: {
657
+ color: "rgba(255,255,255,0.78)",
658
+ fontWeight: "700",
659
+ fontSize: 10,
660
+ letterSpacing: 1,
661
+ textAlign: "center",
662
+ marginBottom: 2
663
+ },
664
+ unavailable: {
665
+ fontSize: 11,
666
+ lineHeight: 15,
667
+ textAlign: "center",
668
+ marginBottom: 4
669
+ }
670
+ };
671
+
672
+ // ─── 2. Countdown Timer ───────────────────────────────────────────────────────
673
+
674
+ export function resolveCountdownDeadline(expiryValue, now = Date.now()) {
675
+ const duration = expiryValue.match(/^(\d{1,2}):(\d{2}):(\d{2})$/);
676
+ if (duration) {
677
+ const [, hours = "0", minutes = "0", seconds = "0"] = duration;
678
+ return now + (Number(hours) * 3600 + Number(minutes) * 60 + Number(seconds)) * 1000;
679
+ }
680
+ const timestamp = Date.parse(expiryValue);
681
+ return Number.isFinite(timestamp) ? timestamp : now + 30 * 60 * 1000;
682
+ }
683
+ function useCountdown(expiryValue) {
684
+ const deadline = useMemo(() => resolveCountdownDeadline(expiryValue), [expiryValue]);
685
+ const getMs = () => Math.max(0, deadline - Date.now());
686
+ const [ms, setMs] = useState(getMs);
687
+ useEffect(() => {
688
+ const id = setInterval(() => setMs(getMs()), 1000);
689
+ return () => clearInterval(id);
690
+ }, [deadline]);
691
+ const totalSec = Math.floor(ms / 1000);
692
+ const h = Math.floor(totalSec / 3600);
693
+ const m = Math.floor(totalSec % 3600 / 60);
694
+ const s = totalSec % 60;
695
+ return {
696
+ h,
697
+ m,
698
+ s,
699
+ done: ms === 0
700
+ };
701
+ }
702
+ function FlipUnit({
703
+ value,
704
+ label
705
+ }) {
706
+ const v = String(value).padStart(2, "0");
707
+ return /*#__PURE__*/_jsxs(View, {
708
+ style: cd.unit,
709
+ children: [/*#__PURE__*/_jsx(View, {
710
+ style: cd.box,
711
+ children: /*#__PURE__*/_jsx(Text, {
712
+ adjustsFontSizeToFit: true,
713
+ minimumFontScale: 0.65,
714
+ numberOfLines: 1,
715
+ style: cd.num,
716
+ children: v
717
+ })
718
+ }), /*#__PURE__*/_jsx(Text, {
719
+ style: cd.lbl,
720
+ children: label
721
+ })]
722
+ });
723
+ }
724
+ export function CountdownInApp(props) {
725
+ const {
726
+ spec,
727
+ theme
728
+ } = props;
729
+ const e = ext(props);
730
+ const expiry = str(e.expiryTime) || str(e.expiry);
731
+ const units = num(e.countdownUnits, 3);
732
+ const {
733
+ h,
734
+ m,
735
+ s,
736
+ done
737
+ } = useCountdown(expiry);
738
+ return /*#__PURE__*/_jsxs(View, {
739
+ style: sh.content,
740
+ children: [/*#__PURE__*/_jsx(Title, {
741
+ text: spec.title,
742
+ theme: theme
743
+ }), /*#__PURE__*/_jsx(Body, {
744
+ text: done ? "This offer has expired." : spec.body,
745
+ theme: theme
746
+ }), !done ? /*#__PURE__*/_jsxs(View, {
747
+ style: cd.row,
748
+ children: [units >= 3 ? /*#__PURE__*/_jsx(FlipUnit, {
749
+ value: h,
750
+ label: "HRS"
751
+ }) : null, units >= 2 ? /*#__PURE__*/_jsxs(_Fragment, {
752
+ children: [/*#__PURE__*/_jsx(Text, {
753
+ allowFontScaling: false,
754
+ style: [cd.sep, {
755
+ color: theme.accent
756
+ }],
757
+ children: ":"
758
+ }), /*#__PURE__*/_jsx(FlipUnit, {
759
+ value: m,
760
+ label: "MIN"
761
+ })]
762
+ }) : null, /*#__PURE__*/_jsx(Text, {
763
+ allowFontScaling: false,
764
+ style: [cd.sep, {
765
+ color: theme.accent
766
+ }],
767
+ children: ":"
768
+ }), /*#__PURE__*/_jsx(FlipUnit, {
769
+ value: s,
770
+ label: "SEC"
771
+ })]
772
+ }) : null, /*#__PURE__*/_jsx(CtaArea, {
773
+ ...props
774
+ })]
775
+ });
776
+ }
777
+ const cd = {
778
+ row: {
779
+ flexDirection: "row",
780
+ alignItems: "flex-start",
781
+ justifyContent: "center",
782
+ marginVertical: 14,
783
+ width: "100%"
784
+ },
785
+ unit: {
786
+ alignItems: "center",
787
+ flex: 1,
788
+ marginHorizontal: 2,
789
+ minWidth: 0
790
+ },
791
+ box: {
792
+ backgroundColor: "#111827",
793
+ borderRadius: 10,
794
+ paddingVertical: 8,
795
+ paddingHorizontal: 4,
796
+ width: "100%"
797
+ },
798
+ num: {
799
+ fontSize: 24,
800
+ fontWeight: "800",
801
+ color: "#fff",
802
+ textAlign: "center",
803
+ fontVariant: ["tabular-nums"]
804
+ },
805
+ lbl: {
806
+ fontSize: 9,
807
+ color: "#9CA3AF",
808
+ marginTop: 4,
809
+ textTransform: "uppercase",
810
+ letterSpacing: 0.5
811
+ },
812
+ sep: {
813
+ fontSize: 28,
814
+ fontWeight: "800",
815
+ paddingHorizontal: 2,
816
+ paddingTop: 4
817
+ }
818
+ };
819
+
820
+ // ─── Carousel / Swipe Deck ───────────────────────────────────────────────────
821
+
822
+ function gameCards(value) {
823
+ return arr(value).slice(0, 20).map((item, index) => {
824
+ const card = record(item);
825
+ return {
826
+ id: str(card.id ?? card.value) || String(index),
827
+ title: labelOf(item),
828
+ body: firstText(card, ["body", "description", "message", "price"]),
829
+ image: mediaUri(card.image ?? card.imageUrl ?? card.cardImage ?? card.productImage)
830
+ };
831
+ });
832
+ }
833
+ export function CarouselDeckInApp(props) {
834
+ if (variantOf(props) === "swipe_deck") return /*#__PURE__*/_jsx(SwipeDeckInApp, {
835
+ ...props
836
+ });
837
+ const {
838
+ spec,
839
+ theme
840
+ } = props;
841
+ const e = ext(props);
842
+ const slides = gameCards(e.slides ?? e.cards ?? e.products ?? props.spec.assets?.products);
843
+ const mediaHeight = resolveInAppMediaHeight(spec, 104, 240);
844
+ const [page, setPage] = useState(0);
845
+ const [pageWidth, setPageWidth] = useState(280);
846
+ const onLayout = event => {
847
+ const width = Math.round(event.nativeEvent.layout.width);
848
+ if (width > 0) setPageWidth(width);
849
+ };
850
+ const onPage = event => {
851
+ const width = event.nativeEvent.layoutMeasurement.width || pageWidth;
852
+ setPage(clampPage(event.nativeEvent.contentOffset.x, width, slides.length));
853
+ };
854
+ return /*#__PURE__*/_jsxs(View, {
855
+ style: sh.content,
856
+ children: [/*#__PURE__*/_jsx(Title, {
857
+ text: spec.title,
858
+ theme: theme
859
+ }), /*#__PURE__*/_jsx(Body, {
860
+ text: spec.body,
861
+ theme: theme
862
+ }), slides.length > 0 ? /*#__PURE__*/_jsxs(_Fragment, {
863
+ children: [/*#__PURE__*/_jsx(ScrollView, {
864
+ horizontal: true,
865
+ pagingEnabled: true,
866
+ showsHorizontalScrollIndicator: false,
867
+ onLayout: onLayout,
868
+ onMomentumScrollEnd: onPage,
869
+ style: car.viewport,
870
+ children: slides.map((slide, index) => {
871
+ const image = slide.image || (index === 0 ? str(spec.assets?.image) || str(spec.imageUrl) : "");
872
+ return /*#__PURE__*/_jsx(View, {
873
+ style: {
874
+ width: pageWidth
875
+ },
876
+ children: /*#__PURE__*/_jsxs(View, {
877
+ style: [car.card, {
878
+ backgroundColor: theme.couponBg,
879
+ borderColor: theme.couponBorder
880
+ }],
881
+ children: [image ? /*#__PURE__*/_jsx(Image, {
882
+ source: {
883
+ uri: image
884
+ },
885
+ resizeMode: "cover",
886
+ style: [car.image, mediaHeight === undefined ? null : {
887
+ height: mediaHeight
888
+ }],
889
+ accessibilityLabel: slide.title || `Slide ${index + 1}`
890
+ }) : null, slide.title ? /*#__PURE__*/_jsx(Text, {
891
+ style: [car.title, {
892
+ color: theme.titleColor
893
+ }],
894
+ children: slide.title
895
+ }) : null, slide.body ? /*#__PURE__*/_jsx(Text, {
896
+ style: [car.body, {
897
+ color: theme.bodyColor
898
+ }],
899
+ children: slide.body
900
+ }) : null]
901
+ })
902
+ }, `${slide.title}-${index}`);
903
+ })
904
+ }), slides.length > 1 ? /*#__PURE__*/_jsx(View, {
905
+ style: car.dots,
906
+ accessibilityLabel: `Slide ${page + 1} of ${slides.length}`,
907
+ children: slides.map((_, index) => /*#__PURE__*/_jsx(View, {
908
+ style: [car.dot, {
909
+ backgroundColor: index === page ? theme.accent : theme.couponBorder
910
+ }]
911
+ }, index))
912
+ }) : null]
913
+ }) : null, /*#__PURE__*/_jsx(CtaArea, {
914
+ ...props
915
+ })]
916
+ });
917
+ }
918
+ const car = {
919
+ viewport: {
920
+ width: "100%",
921
+ marginTop: 10
922
+ },
923
+ card: {
924
+ minHeight: 160,
925
+ padding: 12,
926
+ borderRadius: 14,
927
+ borderWidth: 1,
928
+ overflow: "hidden"
929
+ },
930
+ image: {
931
+ width: "100%",
932
+ height: 104,
933
+ borderRadius: 10,
934
+ marginBottom: 10
935
+ },
936
+ title: {
937
+ fontSize: 15,
938
+ lineHeight: 20,
939
+ fontWeight: "700"
940
+ },
941
+ body: {
942
+ fontSize: 12,
943
+ lineHeight: 18,
944
+ marginTop: 3
945
+ },
946
+ dots: {
947
+ flexDirection: "row",
948
+ flexWrap: "wrap",
949
+ justifyContent: "center",
950
+ gap: 6,
951
+ marginTop: 10
952
+ },
953
+ dot: {
954
+ width: 7,
955
+ height: 7,
956
+ borderRadius: 4
957
+ }
958
+ };
959
+ function SwipeDeckInApp(props) {
960
+ const {
961
+ spec,
962
+ theme
963
+ } = props;
964
+ const e = ext(props);
965
+ const cards = gameCards(e.cards ?? e.products ?? props.spec.assets?.products);
966
+ const mediaHeight = resolveInAppMediaHeight(spec, 132, 300);
967
+ const [index, setIndex] = useState(0);
968
+ const [decisions, setDecisions] = useState([]);
969
+ const dragX = useRef(new Animated.Value(0)).current;
970
+ const reducedMotion = useReducedMotion();
971
+ const decideRef = useRef(() => {});
972
+ const decidingRef = useRef(false);
973
+ const current = cards[index];
974
+ const swipeActions = record(e.swipeActions);
975
+ const skipLabel = str(swipeActions.skip ?? swipeActions.reject) || "SKIP";
976
+ const keepLabel = str(swipeActions.keep ?? swipeActions.accept) || "KEEP";
977
+ const decide = decision => {
978
+ if (!current || decidingRef.current) return;
979
+ decidingRef.current = true;
980
+ const card = current;
981
+ Animated.timing(dragX, {
982
+ toValue: decision === "keep" ? 420 : -420,
983
+ duration: reducedMotion === false ? 180 : 0,
984
+ useNativeDriver: false
985
+ }).start(({
986
+ finished
987
+ }) => {
988
+ decidingRef.current = false;
989
+ if (!finished) {
990
+ dragX.setValue(0);
991
+ return;
992
+ }
993
+ props.onInteraction?.({
994
+ mechanic: "swipe_deck",
995
+ phase: "decision",
996
+ progress: cards.length > 0 ? (index + 1) / cards.length : 1,
997
+ value: decision,
998
+ meta: {
999
+ cardId: card.id
1000
+ }
1001
+ });
1002
+ dragX.setValue(0);
1003
+ setDecisions(value => [...value, {
1004
+ id: card.id,
1005
+ decision
1006
+ }]);
1007
+ setIndex(value => value + 1);
1008
+ });
1009
+ };
1010
+ decideRef.current = decide;
1011
+ const responder = useMemo(() => PanResponder.create({
1012
+ onMoveShouldSetPanResponder: (_, gesture) => Math.abs(gesture.dx) > 4,
1013
+ onPanResponderMove: Animated.event([null, {
1014
+ dx: dragX
1015
+ }], {
1016
+ useNativeDriver: false
1017
+ }),
1018
+ onPanResponderRelease: (_, gesture) => {
1019
+ if (Math.abs(gesture.dx) >= 72) decideRef.current(gesture.dx > 0 ? "keep" : "skip");else Animated.spring(dragX, {
1020
+ toValue: 0,
1021
+ useNativeDriver: false
1022
+ }).start();
1023
+ },
1024
+ onPanResponderTerminate: () => {
1025
+ Animated.spring(dragX, {
1026
+ toValue: 0,
1027
+ useNativeDriver: false
1028
+ }).start();
1029
+ }
1030
+ }), [dragX]);
1031
+ useEffect(() => () => dragX.stopAnimation(), [dragX]);
1032
+ const rotate = dragX.interpolate({
1033
+ inputRange: [-220, 0, 220],
1034
+ outputRange: ["-9deg", "0deg", "9deg"],
1035
+ extrapolate: "clamp"
1036
+ });
1037
+ const kept = decisions.filter(({
1038
+ decision
1039
+ }) => decision === "keep").length;
1040
+ return /*#__PURE__*/_jsxs(View, {
1041
+ style: sh.content,
1042
+ children: [/*#__PURE__*/_jsx(Title, {
1043
+ text: spec.title,
1044
+ theme: theme
1045
+ }), /*#__PURE__*/_jsx(Body, {
1046
+ text: spec.body,
1047
+ theme: theme
1048
+ }), current ? /*#__PURE__*/_jsxs(_Fragment, {
1049
+ children: [/*#__PURE__*/_jsxs(View, {
1050
+ style: deck.progressRow,
1051
+ children: [/*#__PURE__*/_jsxs(Text, {
1052
+ style: [deck.progressText, {
1053
+ color: theme.secondaryColor
1054
+ }],
1055
+ children: [index + 1, " / ", cards.length]
1056
+ }), /*#__PURE__*/_jsxs(Text, {
1057
+ style: [deck.progressText, {
1058
+ color: theme.accent
1059
+ }],
1060
+ children: [kept, " kept"]
1061
+ })]
1062
+ }), /*#__PURE__*/_jsxs(Animated.View, {
1063
+ ...responder.panHandlers,
1064
+ style: [deck.card, {
1065
+ backgroundColor: theme.couponBg,
1066
+ borderColor: theme.couponBorder,
1067
+ transform: [{
1068
+ translateX: dragX
1069
+ }, {
1070
+ rotate
1071
+ }]
1072
+ }],
1073
+ accessibilityLabel: `${current.title}. Swipe right to keep or left to skip`,
1074
+ children: [current.image ? /*#__PURE__*/_jsx(Image, {
1075
+ source: {
1076
+ uri: current.image
1077
+ },
1078
+ style: [deck.image, mediaHeight === undefined ? null : {
1079
+ height: mediaHeight
1080
+ }],
1081
+ resizeMode: "cover"
1082
+ }) : null, /*#__PURE__*/_jsx(Text, {
1083
+ style: [deck.title, {
1084
+ color: theme.titleColor
1085
+ }],
1086
+ children: current.title
1087
+ }), current.body ? /*#__PURE__*/_jsx(Text, {
1088
+ style: [deck.body, {
1089
+ color: theme.bodyColor
1090
+ }],
1091
+ children: current.body
1092
+ }) : null]
1093
+ }), /*#__PURE__*/_jsxs(View, {
1094
+ style: deck.actions,
1095
+ children: [/*#__PURE__*/_jsx(SecondaryBtn, {
1096
+ label: skipLabel,
1097
+ onPress: () => decide("skip"),
1098
+ color: theme.secondaryColor
1099
+ }), /*#__PURE__*/_jsx(PrimaryBtn, {
1100
+ label: keepLabel,
1101
+ onPress: () => decide("keep"),
1102
+ accent: theme.accent
1103
+ })]
1104
+ })]
1105
+ }) : /*#__PURE__*/_jsxs(View, {
1106
+ style: [deck.complete, {
1107
+ backgroundColor: theme.couponBg
1108
+ }],
1109
+ accessibilityLiveRegion: "polite",
1110
+ children: [/*#__PURE__*/_jsx(Text, {
1111
+ style: [deck.completeTitle, {
1112
+ color: theme.titleColor
1113
+ }],
1114
+ children: cards.length === 0 ? "Deck unavailable" : str(e.finalLabel) || "Deck complete"
1115
+ }), /*#__PURE__*/_jsx(Text, {
1116
+ style: [deck.completeBody, {
1117
+ color: theme.bodyColor
1118
+ }],
1119
+ children: cards.length === 0 ? "No cards were authored for this deck." : `You kept ${kept} of ${cards.length}`
1120
+ })]
1121
+ }), !current ? /*#__PURE__*/_jsx(CtaArea, {
1122
+ ...props
1123
+ }) : null]
1124
+ });
1125
+ }
1126
+ const deck = {
1127
+ progressRow: {
1128
+ flexDirection: "row",
1129
+ justifyContent: "space-between",
1130
+ marginTop: 10
1131
+ },
1132
+ progressText: {
1133
+ fontSize: 11,
1134
+ fontWeight: "700"
1135
+ },
1136
+ card: {
1137
+ minHeight: 220,
1138
+ borderWidth: 1.5,
1139
+ borderRadius: 18,
1140
+ padding: 16,
1141
+ marginVertical: 12,
1142
+ overflow: "hidden"
1143
+ },
1144
+ image: {
1145
+ width: "100%",
1146
+ height: 132,
1147
+ borderRadius: 12,
1148
+ marginBottom: 12
1149
+ },
1150
+ title: {
1151
+ fontSize: 18,
1152
+ fontWeight: "800",
1153
+ textAlign: "center"
1154
+ },
1155
+ body: {
1156
+ fontSize: 13,
1157
+ lineHeight: 18,
1158
+ textAlign: "center",
1159
+ marginTop: 5
1160
+ },
1161
+ actions: {
1162
+ flexDirection: "row",
1163
+ flexWrap: "wrap",
1164
+ gap: 12,
1165
+ alignItems: "center"
1166
+ },
1167
+ complete: {
1168
+ borderRadius: 16,
1169
+ padding: 22,
1170
+ marginTop: 14,
1171
+ alignItems: "center"
1172
+ },
1173
+ completeTitle: {
1174
+ fontSize: 18,
1175
+ fontWeight: "800"
1176
+ },
1177
+ completeBody: {
1178
+ fontSize: 13,
1179
+ marginTop: 5
1180
+ }
1181
+ };
1182
+
1183
+ // ─── 3. Scratch Card ──────────────────────────────────────────────────────────
1184
+
1185
+ const SCRATCH_COLUMNS = 6;
1186
+ const SCRATCH_ROWS = 4;
1187
+ const SCRATCH_CELLS = SCRATCH_COLUMNS * SCRATCH_ROWS;
1188
+ const SCRATCH_REVEAL_AT = 0.55;
1189
+ export function ScratchCardInApp(props) {
1190
+ const {
1191
+ spec,
1192
+ theme
1193
+ } = props;
1194
+ const e = ext(props);
1195
+ const mystery = variantOf(props) === "mystery_box";
1196
+ const [opened, setOpened] = useState(false);
1197
+ const [scratched, setScratched] = useState([]);
1198
+ const scratchBounds = useRef({
1199
+ width: 0,
1200
+ height: 0
1201
+ });
1202
+ const reportedProgress = useRef(0);
1203
+ const outcomeShown = useRef(false);
1204
+ const boxMotion = useRef(new Animated.Value(0)).current;
1205
+ const reducedMotion = useReducedMotion();
1206
+ const [gameOutcome, setGameOutcome] = useState(null);
1207
+ const [gameLoading, setGameLoading] = useState(false);
1208
+ const [gameError, setGameError] = useState("");
1209
+ const prize = gameResultLabel(gameOutcome) || "Reward revealed";
1210
+ const ctaIsPlay = /scratch|reveal|open/i.test(spec.cta?.label ?? "");
1211
+ const claimCta = spec.cta && !ctaIsPlay ? spec.cta : undefined;
1212
+ const rewardTitle = str(e.rewardTitle) || (mystery ? "Surprise!" : "You won!");
1213
+ const closed = str(e.closedMessage) || str(e.revealLabel) || (mystery ? "Tap the box to open it" : "Scratch to reveal your prize");
1214
+ const progress = scratchProgress(scratched, SCRATCH_CELLS);
1215
+ const revealed = mystery ? opened : progress >= SCRATCH_REVEAL_AT;
1216
+ useEffect(() => () => boxMotion.stopAnimation(), [boxMotion]);
1217
+ useEffect(() => {
1218
+ if (!revealed || !gameOutcome || outcomeShown.current || !props.onOutcome) {
1219
+ return;
1220
+ }
1221
+ props.onOutcome({
1222
+ resultLabel: prize,
1223
+ fallback: inAppOutcomeTerminalForCta(props.spec.cta)
1224
+ });
1225
+ outcomeShown.current = true;
1226
+ }, [gameOutcome, prize, props.onOutcome, props.spec.cta, revealed]);
1227
+ const resolveReward = async () => {
1228
+ if (gameOutcome) return gameOutcome;
1229
+ if (gameLoading) return null;
1230
+ setGameLoading(true);
1231
+ setGameError("");
1232
+ props.onInteraction?.({
1233
+ mechanic: mystery ? "mystery_box" : "scratch_card",
1234
+ phase: "started",
1235
+ attempt: 1
1236
+ });
1237
+ try {
1238
+ const outcome = await requireGameOutcome(props);
1239
+ setGameOutcome(outcome);
1240
+ return outcome;
1241
+ } catch (error) {
1242
+ setGameError(gameUnavailableText(error));
1243
+ return null;
1244
+ } finally {
1245
+ setGameLoading(false);
1246
+ }
1247
+ };
1248
+ const scratchResponder = useMemo(() => {
1249
+ const mark = event => {
1250
+ if (!gameOutcome) return;
1251
+ const {
1252
+ locationX,
1253
+ locationY
1254
+ } = event.nativeEvent;
1255
+ const {
1256
+ width,
1257
+ height
1258
+ } = scratchBounds.current;
1259
+ const cell = scratchCellAt(locationX, locationY, width, height, SCRATCH_COLUMNS, SCRATCH_ROWS);
1260
+ setScratched(current => {
1261
+ const next = addScratchedCell(current, cell);
1262
+ const nextProgress = scratchProgress(next, SCRATCH_CELLS);
1263
+ const reportAt = nextProgress >= SCRATCH_REVEAL_AT ? 1 : Math.min(0.5, Math.floor(nextProgress * 4) / 4);
1264
+ if (reportAt > reportedProgress.current) {
1265
+ reportedProgress.current = reportAt;
1266
+ props.onInteraction?.({
1267
+ mechanic: "scratch",
1268
+ phase: nextProgress >= SCRATCH_REVEAL_AT ? "resolved" : "progress",
1269
+ progress: nextProgress,
1270
+ result: nextProgress >= SCRATCH_REVEAL_AT ? prize : undefined
1271
+ });
1272
+ }
1273
+ return next;
1274
+ });
1275
+ };
1276
+ return PanResponder.create({
1277
+ onStartShouldSetPanResponder: () => true,
1278
+ onMoveShouldSetPanResponder: () => true,
1279
+ onPanResponderGrant: mark,
1280
+ onPanResponderMove: mark
1281
+ });
1282
+ }, [gameOutcome, prize, props.onInteraction, props.onOutcome, props.spec.cta]);
1283
+ const revealScratchAccessibly = () => {
1284
+ if (!gameOutcome) {
1285
+ void resolveReward();
1286
+ return;
1287
+ }
1288
+ setScratched(Array.from({
1289
+ length: SCRATCH_CELLS
1290
+ }, (_, index) => index));
1291
+ props.onInteraction?.({
1292
+ mechanic: "scratch",
1293
+ phase: "resolved",
1294
+ progress: 1,
1295
+ result: prize
1296
+ });
1297
+ };
1298
+ const openMystery = async () => {
1299
+ if (opened) return;
1300
+ const outcome = await resolveReward();
1301
+ if (!outcome) return;
1302
+ const duration = reducedMotion === false ? 90 : 0;
1303
+ Animated.sequence([Animated.timing(boxMotion, {
1304
+ toValue: 1,
1305
+ duration,
1306
+ useNativeDriver: true
1307
+ }), Animated.timing(boxMotion, {
1308
+ toValue: -1,
1309
+ duration,
1310
+ useNativeDriver: true
1311
+ }), Animated.timing(boxMotion, {
1312
+ toValue: 0,
1313
+ duration,
1314
+ useNativeDriver: true
1315
+ })]).start(({
1316
+ finished
1317
+ }) => {
1318
+ if (!finished) return;
1319
+ setOpened(true);
1320
+ props.onInteraction?.({
1321
+ mechanic: "mystery_box",
1322
+ phase: "resolved",
1323
+ result: gameResultLabel(outcome) || "Reward revealed"
1324
+ });
1325
+ AccessibilityInfo.announceForAccessibility(`${rewardTitle} ${gameResultLabel(outcome) || "Reward revealed"}`);
1326
+ });
1327
+ };
1328
+ const boxRotate = boxMotion.interpolate({
1329
+ inputRange: [-1, 0, 1],
1330
+ outputRange: ["-7deg", "0deg", "7deg"]
1331
+ });
1332
+ return /*#__PURE__*/_jsxs(View, {
1333
+ style: sh.content,
1334
+ children: [/*#__PURE__*/_jsx(Title, {
1335
+ text: spec.title,
1336
+ theme: theme
1337
+ }), /*#__PURE__*/_jsx(Body, {
1338
+ text: spec.body,
1339
+ theme: theme
1340
+ }), mystery ? /*#__PURE__*/_jsxs(Pressable, {
1341
+ style: [sc.boxStage, {
1342
+ backgroundColor: theme.couponBg
1343
+ }],
1344
+ onPress: () => {
1345
+ void openMystery();
1346
+ },
1347
+ disabled: opened || gameLoading,
1348
+ accessibilityRole: "button",
1349
+ accessibilityLabel: opened ? `${rewardTitle}: ${prize}` : closed,
1350
+ children: [/*#__PURE__*/_jsxs(Animated.View, {
1351
+ style: [sc.box, {
1352
+ transform: [{
1353
+ rotate: boxRotate
1354
+ }]
1355
+ }],
1356
+ children: [/*#__PURE__*/_jsx(View, {
1357
+ style: [sc.boxLid, {
1358
+ backgroundColor: theme.accent
1359
+ }, opened ? sc.boxLidOpen : null]
1360
+ }), /*#__PURE__*/_jsx(View, {
1361
+ style: [sc.boxBody, {
1362
+ backgroundColor: theme.accent
1363
+ }],
1364
+ children: /*#__PURE__*/_jsx(Text, {
1365
+ style: sc.boxEmoji,
1366
+ children: opened ? "✨" : "🎁"
1367
+ })
1368
+ })]
1369
+ }), /*#__PURE__*/_jsx(Text, {
1370
+ style: [sc.rewardTitle, {
1371
+ color: theme.titleColor
1372
+ }],
1373
+ children: opened ? rewardTitle : gameLoading ? "Checking your result…" : gameError ? "Try opening again" : closed
1374
+ }), opened ? /*#__PURE__*/_jsx(Text, {
1375
+ style: [sc.prize, {
1376
+ color: theme.accent
1377
+ }],
1378
+ children: prize
1379
+ }) : null]
1380
+ }) : /*#__PURE__*/_jsxs(_Fragment, {
1381
+ children: [/*#__PURE__*/_jsxs(View, {
1382
+ style: [sc.scratch, {
1383
+ borderColor: theme.accent,
1384
+ backgroundColor: theme.couponBg
1385
+ }],
1386
+ children: [/*#__PURE__*/_jsxs(View, {
1387
+ accessibilityElementsHidden: !revealed,
1388
+ importantForAccessibility: revealed ? "auto" : "no-hide-descendants",
1389
+ style: sc.rewardCopy,
1390
+ children: [/*#__PURE__*/_jsx(Text, {
1391
+ style: [sc.rewardTitle, {
1392
+ color: theme.titleColor
1393
+ }],
1394
+ children: rewardTitle
1395
+ }), /*#__PURE__*/_jsx(Text, {
1396
+ style: [sc.prize, {
1397
+ color: theme.accent
1398
+ }],
1399
+ children: prize
1400
+ }), !revealed ? /*#__PURE__*/_jsx(Text, {
1401
+ style: sc.scratchHintMeasure,
1402
+ children: closed
1403
+ }) : null]
1404
+ }), !revealed ? /*#__PURE__*/_jsxs(View, {
1405
+ style: sc.cover,
1406
+ onLayout: event => {
1407
+ scratchBounds.current = event.nativeEvent.layout;
1408
+ },
1409
+ ...(gameOutcome ? scratchResponder.panHandlers : {}),
1410
+ accessibilityRole: "button",
1411
+ accessibilityLabel: `${closed}. ${Math.round(progress * 100)} percent revealed`,
1412
+ accessibilityActions: [{
1413
+ name: "activate",
1414
+ label: "Reveal reward"
1415
+ }],
1416
+ onAccessibilityAction: revealScratchAccessibly,
1417
+ children: [Array.from({
1418
+ length: SCRATCH_CELLS
1419
+ }, (_, index) => /*#__PURE__*/_jsx(View, {
1420
+ pointerEvents: "none",
1421
+ style: [sc.coverCell, {
1422
+ width: `${100 / SCRATCH_COLUMNS}%`
1423
+ }, scratched.includes(index) ? sc.coverCellCleared : null]
1424
+ }, index)), /*#__PURE__*/_jsx(View, {
1425
+ pointerEvents: "none",
1426
+ style: sc.scratchHintWrap,
1427
+ children: /*#__PURE__*/_jsx(Text, {
1428
+ style: sc.scratchHint,
1429
+ children: closed
1430
+ })
1431
+ })]
1432
+ }) : null]
1433
+ }), /*#__PURE__*/_jsx(View, {
1434
+ style: [sc.progressTrack, {
1435
+ backgroundColor: theme.couponBg
1436
+ }],
1437
+ children: /*#__PURE__*/_jsx(View, {
1438
+ style: [sc.progressFill, {
1439
+ width: `${progress * 100}%`,
1440
+ backgroundColor: theme.accent
1441
+ }]
1442
+ })
1443
+ }), /*#__PURE__*/_jsx(Text, {
1444
+ style: [sc.progressLabel, {
1445
+ color: theme.secondaryColor
1446
+ }],
1447
+ children: revealed ? "Reward revealed" : `${Math.round(progress * 100)}% scratched`
1448
+ })]
1449
+ }), revealed ? /*#__PURE__*/_jsx(PrimaryBtn, {
1450
+ label: str(e.claimLabel) || claimCta?.label || "DONE",
1451
+ accent: theme.accent,
1452
+ onPress: () => {
1453
+ props.onInteraction?.({
1454
+ mechanic: mystery ? "mystery_box" : "scratch",
1455
+ phase: "claimed",
1456
+ result: prize
1457
+ });
1458
+ if (claimCta?.action === "dismiss") props.onDismiss();else if (claimCta) props.onCta(claimCta);else props.onDismiss();
1459
+ }
1460
+ }) : null, !mystery && !gameOutcome ? /*#__PURE__*/_jsx(PrimaryBtn, {
1461
+ label: gameLoading ? "CHECKING…" : gameError ? "RETRY" : str(e.startLabel) || "START",
1462
+ accent: theme.accent,
1463
+ onPress: () => {
1464
+ void resolveReward();
1465
+ },
1466
+ disabled: gameLoading
1467
+ }) : null, gameError ? /*#__PURE__*/_jsx(Text, {
1468
+ style: [sc.legal, {
1469
+ color: theme.secondaryColor
1470
+ }],
1471
+ children: gameError
1472
+ }) : null, str(e.legalText) ? /*#__PURE__*/_jsx(Text, {
1473
+ style: [sc.legal, {
1474
+ color: theme.secondaryColor
1475
+ }],
1476
+ children: str(e.legalText)
1477
+ }) : null]
1478
+ });
1479
+ }
1480
+ const sc = {
1481
+ scratch: {
1482
+ marginVertical: 14,
1483
+ borderRadius: 14,
1484
+ borderWidth: 2,
1485
+ borderStyle: "dashed",
1486
+ minHeight: 132,
1487
+ justifyContent: "center",
1488
+ alignItems: "center",
1489
+ overflow: "hidden",
1490
+ position: "relative"
1491
+ },
1492
+ prize: {
1493
+ fontSize: 28,
1494
+ fontWeight: "900",
1495
+ letterSpacing: 1,
1496
+ textAlign: "center"
1497
+ },
1498
+ rewardCopy: {
1499
+ alignItems: "center",
1500
+ alignSelf: "stretch",
1501
+ justifyContent: "center",
1502
+ paddingHorizontal: 16,
1503
+ paddingVertical: 24
1504
+ },
1505
+ rewardTitle: {
1506
+ fontSize: 13,
1507
+ fontWeight: "800",
1508
+ marginBottom: 6,
1509
+ textAlign: "center"
1510
+ },
1511
+ cover: {
1512
+ position: "absolute",
1513
+ top: 0,
1514
+ right: 0,
1515
+ bottom: 0,
1516
+ left: 0,
1517
+ flexDirection: "row",
1518
+ flexWrap: "wrap",
1519
+ alignContent: "stretch",
1520
+ backgroundColor: "#858B96"
1521
+ },
1522
+ coverCell: {
1523
+ height: `${100 / SCRATCH_ROWS}%`,
1524
+ backgroundColor: "#9299A5",
1525
+ borderWidth: 0.5,
1526
+ borderColor: "rgba(255,255,255,0.18)"
1527
+ },
1528
+ coverCellCleared: {
1529
+ opacity: 0
1530
+ },
1531
+ scratchHintWrap: {
1532
+ position: "absolute",
1533
+ alignItems: "center",
1534
+ bottom: 0,
1535
+ justifyContent: "center",
1536
+ left: 16,
1537
+ right: 16,
1538
+ top: 0
1539
+ },
1540
+ scratchHint: {
1541
+ fontSize: 13,
1542
+ color: "#fff",
1543
+ fontWeight: "600",
1544
+ textAlign: "center"
1545
+ },
1546
+ scratchHintMeasure: {
1547
+ fontSize: 13,
1548
+ fontWeight: "600",
1549
+ marginTop: 8,
1550
+ opacity: 0,
1551
+ textAlign: "center"
1552
+ },
1553
+ progressTrack: {
1554
+ height: 7,
1555
+ borderRadius: 4,
1556
+ overflow: "hidden"
1557
+ },
1558
+ progressFill: {
1559
+ height: "100%",
1560
+ borderRadius: 4
1561
+ },
1562
+ progressLabel: {
1563
+ fontSize: 11,
1564
+ textAlign: "center",
1565
+ marginTop: 5
1566
+ },
1567
+ boxStage: {
1568
+ borderRadius: 16,
1569
+ minHeight: 190,
1570
+ marginVertical: 14,
1571
+ alignItems: "center",
1572
+ justifyContent: "center",
1573
+ padding: 16
1574
+ },
1575
+ box: {
1576
+ width: 94,
1577
+ height: 94,
1578
+ alignItems: "center",
1579
+ justifyContent: "flex-end"
1580
+ },
1581
+ boxLid: {
1582
+ width: 98,
1583
+ height: 22,
1584
+ borderRadius: 6,
1585
+ marginBottom: -4,
1586
+ zIndex: 1
1587
+ },
1588
+ boxLidOpen: {
1589
+ transform: [{
1590
+ translateY: -12
1591
+ }, {
1592
+ rotate: "-10deg"
1593
+ }]
1594
+ },
1595
+ boxBody: {
1596
+ width: 82,
1597
+ height: 70,
1598
+ borderRadius: 8,
1599
+ alignItems: "center",
1600
+ justifyContent: "center"
1601
+ },
1602
+ boxEmoji: {
1603
+ fontSize: 36
1604
+ },
1605
+ legal: {
1606
+ fontSize: 10,
1607
+ lineHeight: 14,
1608
+ marginTop: 10,
1609
+ textAlign: "center"
1610
+ }
1611
+ };
1612
+
1613
+ // ─── 4. Slot Reward ───────────────────────────────────────────────────────────
1614
+
1615
+ const DEFAULT_REELS = [["🍒", "⭐", "💎", "🍋", "🎰"], ["⭐", "🍒", "🍋", "💎", "🎰"], ["💎", "🍋", "🍒", "⭐", "🎰"]];
1616
+ export function SlotRewardInApp(props) {
1617
+ const {
1618
+ spec,
1619
+ theme
1620
+ } = props;
1621
+ const e = ext(props);
1622
+ const configuredReels = arr(e.reels).map(reel => arr(record(reel).symbols ?? reel).map(labelOf).filter(Boolean)).filter(reel => reel.length > 0);
1623
+ const slotItems = arr(e.slotItems).map(item => {
1624
+ const value = record(item);
1625
+ return str(value.emoji ?? value.symbol ?? value.label ?? item);
1626
+ }).filter(Boolean);
1627
+ const reels = configuredReels.length > 0 ? configuredReels : slotItems.length > 0 ? [slotItems, slotItems, slotItems] : DEFAULT_REELS;
1628
+ const boundedReels = reels.slice(0, 3).map(reel => reel.slice(0, 12));
1629
+ const [phase, setPhase] = useState("idle");
1630
+ const [results, setResults] = useState([]);
1631
+ const [gameOutcome, setGameOutcome] = useState(null);
1632
+ const [gameError, setGameError] = useState("");
1633
+ const [frame, setFrame] = useState(0);
1634
+ const [attemptsUsed, setAttemptsUsed] = useState(0);
1635
+ const reducedMotion = useReducedMotion();
1636
+ const resultMessage = gameResultLabel(gameOutcome);
1637
+ // One UI attempt maps to one idempotent backend attempt. Retry reuses its id.
1638
+ const maxAttempts = 1;
1639
+ const spinTimerRef = useRef(null);
1640
+ const animsRef = useRef([]);
1641
+ while (animsRef.current.length < boundedReels.length) animsRef.current.push(new Animated.Value(0));
1642
+ const anims = animsRef.current.slice(0, boundedReels.length);
1643
+ useEffect(() => () => {
1644
+ if (spinTimerRef.current) clearInterval(spinTimerRef.current);
1645
+ animsRef.current.forEach(animation => animation.stopAnimation());
1646
+ }, []);
1647
+ const pull = async () => {
1648
+ if (phase === "resolving" || phase === "spinning" || attemptsUsed >= maxAttempts) return;
1649
+ const attempt = attemptsUsed + 1;
1650
+ setGameError("");
1651
+ setPhase("resolving");
1652
+ setResults([]);
1653
+ props.onInteraction?.({
1654
+ mechanic: "slot_reward",
1655
+ phase: "started",
1656
+ attempt
1657
+ });
1658
+ let remoteOutcome;
1659
+ try {
1660
+ remoteOutcome = await requireGameOutcome(props, attempt);
1661
+ } catch (error) {
1662
+ setGameError(gameUnavailableText(error));
1663
+ setPhase("idle");
1664
+ return;
1665
+ }
1666
+ const outcome = resolveSlotOutcome(boundedReels, remoteOutcome.symbols ?? []);
1667
+ if (!outcome.authored) {
1668
+ setGameError("The backend symbols do not match the configured reels.");
1669
+ setPhase("idle");
1670
+ return;
1671
+ }
1672
+ setAttemptsUsed(attempt);
1673
+ setGameOutcome(remoteOutcome);
1674
+ const remoteResultMessage = gameResultLabel(remoteOutcome);
1675
+ setPhase("spinning");
1676
+ if (reducedMotion === false) {
1677
+ spinTimerRef.current = setInterval(() => setFrame(value => value + 1), 85);
1678
+ }
1679
+ Animated.stagger(reducedMotion === false ? 150 : 0, anims.map((a, i) => Animated.sequence([Animated.timing(a, {
1680
+ toValue: 1,
1681
+ duration: reducedMotion === false ? 600 + i * 100 : 0,
1682
+ easing: Easing.out(Easing.back(2)),
1683
+ useNativeDriver: true
1684
+ }), Animated.timing(a, {
1685
+ toValue: 0,
1686
+ duration: 0,
1687
+ useNativeDriver: true
1688
+ })]))).start(({
1689
+ finished
1690
+ }) => {
1691
+ if (!finished) return;
1692
+ if (spinTimerRef.current) clearInterval(spinTimerRef.current);
1693
+ spinTimerRef.current = null;
1694
+ setResults(outcome.symbols);
1695
+ setPhase("result");
1696
+ props.onInteraction?.({
1697
+ mechanic: "slot_reward",
1698
+ phase: "resolved",
1699
+ attempt,
1700
+ result: remoteResultMessage || undefined,
1701
+ meta: {
1702
+ backendResolved: true
1703
+ }
1704
+ });
1705
+ AccessibilityInfo.announceForAccessibility(remoteResultMessage || "Spin complete");
1706
+ presentTerminalOutcome(props, remoteResultMessage);
1707
+ });
1708
+ };
1709
+ const playLabel = str(e.ctaLabel) || spec.cta?.label || "PULL";
1710
+ const ctaIsPlay = /spin|pull/i.test(spec.cta?.label ?? "");
1711
+ const finish = () => {
1712
+ props.onInteraction?.({
1713
+ mechanic: "slot_reward",
1714
+ phase: "completed",
1715
+ attempt: attemptsUsed,
1716
+ result: resultMessage || undefined
1717
+ });
1718
+ if (spec.cta && !ctaIsPlay && spec.cta.action !== "dismiss") props.onCta(spec.cta);else props.onDismiss();
1719
+ };
1720
+ return /*#__PURE__*/_jsxs(View, {
1721
+ style: sh.content,
1722
+ children: [/*#__PURE__*/_jsx(Title, {
1723
+ text: spec.title,
1724
+ theme: theme
1725
+ }), /*#__PURE__*/_jsx(Body, {
1726
+ text: spec.body,
1727
+ theme: theme
1728
+ }), /*#__PURE__*/_jsx(View, {
1729
+ style: sl.machine,
1730
+ children: boundedReels.map((reel, i) => {
1731
+ const displayEmoji = results[i] ?? reel[(frame + i) % reel.length] ?? "★";
1732
+ const scale = anims[i].interpolate({
1733
+ inputRange: [0, 0.5, 1],
1734
+ outputRange: [1, 1.4, 1]
1735
+ });
1736
+ const translateY = anims[i].interpolate({
1737
+ inputRange: [0, 0.5, 1],
1738
+ outputRange: [0, -18, 0]
1739
+ });
1740
+ return /*#__PURE__*/_jsx(View, {
1741
+ style: [sl.reel, {
1742
+ borderColor: theme.accent + "55",
1743
+ backgroundColor: theme.couponBg
1744
+ }],
1745
+ children: /*#__PURE__*/_jsx(Animated.Text, {
1746
+ style: [sl.reelEmoji, {
1747
+ transform: [{
1748
+ scale
1749
+ }, {
1750
+ translateY
1751
+ }]
1752
+ }],
1753
+ children: displayEmoji
1754
+ })
1755
+ }, i);
1756
+ })
1757
+ }), /*#__PURE__*/_jsxs(Text, {
1758
+ style: [sl.attempts, {
1759
+ color: theme.secondaryColor
1760
+ }],
1761
+ children: ["Attempt ", Math.min(attemptsUsed + (phase === "idle" ? 1 : 0), maxAttempts), " of ", maxAttempts]
1762
+ }), phase === "result" ? /*#__PURE__*/_jsx(View, {
1763
+ style: [sl.win, {
1764
+ backgroundColor: resultMessage ? theme.accent : theme.couponBg
1765
+ }],
1766
+ children: /*#__PURE__*/_jsx(Text, {
1767
+ style: [sl.winText, !resultMessage && {
1768
+ color: theme.titleColor
1769
+ }],
1770
+ children: resultMessage || "Spin complete"
1771
+ })
1772
+ }) : null, phase === "idle" && gameError ? /*#__PURE__*/_jsx(Text, {
1773
+ style: [sl.unavailable, {
1774
+ color: theme.secondaryColor
1775
+ }],
1776
+ children: gameError
1777
+ }) : null, phase === "result" ? /*#__PURE__*/_jsx(PrimaryBtn, {
1778
+ label: ctaIsPlay ? "DONE" : spec.cta?.label || "DONE",
1779
+ accent: theme.accent,
1780
+ onPress: finish
1781
+ }) : /*#__PURE__*/_jsx(PrimaryBtn, {
1782
+ label: phase === "resolving" ? "CHECKING…" : phase === "spinning" ? "SPINNING…" : gameError ? "RETRY" : playLabel,
1783
+ accent: theme.accent,
1784
+ onPress: () => {
1785
+ void pull();
1786
+ },
1787
+ disabled: phase === "resolving" || phase === "spinning"
1788
+ })]
1789
+ });
1790
+ }
1791
+ const sl = {
1792
+ machine: {
1793
+ alignSelf: "center",
1794
+ flexDirection: "row",
1795
+ gap: 10,
1796
+ justifyContent: "center",
1797
+ maxWidth: 230,
1798
+ marginVertical: 16,
1799
+ width: "100%"
1800
+ },
1801
+ reel: {
1802
+ flex: 1,
1803
+ height: 80,
1804
+ maxWidth: 70,
1805
+ minWidth: 0,
1806
+ borderRadius: 12,
1807
+ borderWidth: 1.5,
1808
+ justifyContent: "center",
1809
+ alignItems: "center"
1810
+ },
1811
+ reelEmoji: {
1812
+ fontSize: 36
1813
+ },
1814
+ win: {
1815
+ borderRadius: 10,
1816
+ paddingVertical: 8,
1817
+ paddingHorizontal: 14,
1818
+ alignSelf: "center",
1819
+ marginBottom: 10
1820
+ },
1821
+ winText: {
1822
+ color: "#fff",
1823
+ fontWeight: "800",
1824
+ fontSize: 15
1825
+ },
1826
+ attempts: {
1827
+ fontSize: 11,
1828
+ fontWeight: "600",
1829
+ textAlign: "center",
1830
+ marginBottom: 8
1831
+ },
1832
+ unavailable: {
1833
+ fontSize: 11,
1834
+ lineHeight: 15,
1835
+ textAlign: "center",
1836
+ marginBottom: 8
1837
+ }
1838
+ };
1839
+
1840
+ // ─── 5. Poll Card ─────────────────────────────────────────────────────────────
1841
+
1842
+ export function PollCardInApp(props) {
1843
+ const {
1844
+ spec,
1845
+ theme
1846
+ } = props;
1847
+ const e = ext(props);
1848
+ const variantKey = variantOf(props);
1849
+ const chipMode = variantKey === "preferences_picker" || variantKey === "product_finder";
1850
+ const quizMode = variantKey === "quiz_card";
1851
+ const questions = arr(e.questions).slice(0, 20);
1852
+ const [questionIndex, setQuestionIndex] = useState(0);
1853
+ const [answerPath, setAnswerPath] = useState([]);
1854
+ const questionSpec = record(questions[questionIndex]);
1855
+ const question = str(questionSpec.prompt ?? questionSpec.question ?? questionSpec.title) || str(e.question ?? e.promptText) || spec.title || "What do you think?";
1856
+ const categoryOptions = [];
1857
+ for (const category of arr(e.categories).slice(0, 20)) {
1858
+ const value = record(category);
1859
+ categoryOptions.push(...arr(value.options ?? value.items ?? value.choices).slice(0, 20));
1860
+ if (categoryOptions.length >= 20) break;
1861
+ }
1862
+ const optionSources = [questionSpec.choices, questionSpec.options, questionSpec.answers, e.options, e.answers, e.preferenceOptions, e.productList, e.itemList, categoryOptions];
1863
+ const rawOptions = arr(optionSources.find(value => Array.isArray(value) && value.length > 0) ?? []).slice(0, 20);
1864
+ const scaleMax = boundedInt(questionSpec.scaleMax, 5, 2, 10);
1865
+ const options = rawOptions.length > 0 ? rawOptions.map((option, index) => labelOf(option) || `Option ${index + 1}`) : questionSpec.kind === "scale" ? Array.from({
1866
+ length: scaleMax
1867
+ }, (_, i) => String(i + 1)) : [];
1868
+ const showResults = e.showResults === true;
1869
+ const [selected, setSelected] = useState([]);
1870
+ const [answerLocked, setAnswerLocked] = useState(false);
1871
+ const [score, setScore] = useState(0);
1872
+ const [gradedCount, setGradedCount] = useState(0);
1873
+ const [quizAnswers, setQuizAnswers] = useState([]);
1874
+ const [quizOutcome, setQuizOutcome] = useState(null);
1875
+ const [quizLoading, setQuizLoading] = useState(false);
1876
+ const [quizError, setQuizError] = useState("");
1877
+ const quizSubmission = useRef(null);
1878
+ const allowMultiple = questionSpec.kind === "multi" || e.allowMultiple === true || quizMode && Array.isArray(questionSpec.correctAnswer ?? e.correctAnswer) || variantKey === "preferences_picker" && e.multiSelect !== false;
1879
+ const hasNext = questionIndex + 1 < questions.length;
1880
+ const answered = selected.length > 0;
1881
+ const correctAnswer = questionSpec.correctAnswer ?? e.correctAnswer;
1882
+ const correctIndexes = (Array.isArray(correctAnswer) ? correctAnswer : [correctAnswer]).map(answer => typeof answer === "number" ? Math.floor(answer) : options.findIndex(option => option === labelOf(answer))).filter(index => index >= 0 && index < options.length);
1883
+ const quizGraded = quizMode && correctIndexes.length > 0;
1884
+ const quizCorrect = quizGraded && gradeGameAnswer(selected, correctIndexes);
1885
+ const explanation = firstText(questionSpec, ["explanation"]) || str(e.explanation);
1886
+ const selectedLabel = options[selected[0] ?? -1] ?? "";
1887
+ const resultMapping = record(e.resultMapping);
1888
+ const currentAnswerPath = [...answerPath, selectedLabel].filter(Boolean);
1889
+ const mappedValue = resultMapping[currentAnswerPath.join(" > ")] ?? resultMapping[currentAnswerPath.join("|")] ?? resultMapping[selectedLabel] ?? arr(e.productList ?? props.spec.assets?.products)[selected[0] ?? -1];
1890
+ const mappedRecord = record(mappedValue);
1891
+ const mappedResult = labelOf(mappedValue);
1892
+ const mappedBody = firstText(mappedRecord, ["description", "body", "reason", "message"]);
1893
+ const mappedPrice = firstText(mappedRecord, ["price", "value", "discount"]);
1894
+ const mappedImage = mediaUri(mappedRecord.image ?? mappedRecord.imageUrl ?? mappedRecord.thumbnail);
1895
+ const toggle = index => {
1896
+ if (quizMode && answerLocked) return;
1897
+ const next = toggleGameSelection(selected, index, allowMultiple);
1898
+ setSelected(next);
1899
+ if (!quizMode) {
1900
+ const labels = next.map(value => options[value]).filter(Boolean);
1901
+ props.onInteraction?.({
1902
+ mechanic: variantKey || "question_flow",
1903
+ phase: "decision",
1904
+ value: labels[0],
1905
+ meta: {
1906
+ questionIndex,
1907
+ selected: next,
1908
+ labels
1909
+ }
1910
+ });
1911
+ }
1912
+ };
1913
+ const submitQuiz = async input => {
1914
+ setQuizLoading(true);
1915
+ setQuizError("");
1916
+ quizSubmission.current = input;
1917
+ try {
1918
+ const outcome = await requireGameOutcome(props, 1, input);
1919
+ setQuizOutcome(outcome);
1920
+ props.onInteraction?.({
1921
+ mechanic: "quiz_card",
1922
+ phase: "completed",
1923
+ result: gameResultLabel(outcome) || outcome.id,
1924
+ meta: {
1925
+ backendResolved: true
1926
+ }
1927
+ });
1928
+ } catch (error) {
1929
+ setQuizError(gameUnavailableText(error));
1930
+ } finally {
1931
+ setQuizLoading(false);
1932
+ }
1933
+ };
1934
+ const checkQuizAnswer = () => {
1935
+ if (!answered || answerLocked) return;
1936
+ setAnswerLocked(true);
1937
+ const nextGradedCount = gradedCount + (quizGraded ? 1 : 0);
1938
+ const nextScore = scoreGameAnswer(score, quizCorrect, answerLocked, quizTotal);
1939
+ const nextAnswers = [...quizAnswers, [...selected]];
1940
+ if (quizGraded) setGradedCount(nextGradedCount);
1941
+ setScore(nextScore);
1942
+ setQuizAnswers(nextAnswers);
1943
+ props.onInteraction?.({
1944
+ mechanic: "quiz_card",
1945
+ phase: "resolved",
1946
+ progress: Math.min(1, (questionIndex + 1) / Math.max(1, questions.length)),
1947
+ value: quizGraded ? quizCorrect : undefined,
1948
+ meta: {
1949
+ questionIndex,
1950
+ selected
1951
+ }
1952
+ });
1953
+ if (!hasNext) {
1954
+ void submitQuiz({
1955
+ answers: nextAnswers,
1956
+ score: nextScore,
1957
+ gradedQuestions: nextGradedCount,
1958
+ totalQuestions: quizTotal
1959
+ });
1960
+ }
1961
+ };
1962
+ const advanceQuiz = () => {
1963
+ setQuestionIndex(current => current + 1);
1964
+ setSelected([]);
1965
+ setAnswerLocked(false);
1966
+ };
1967
+ const quizTotal = Math.max(1, questions.length);
1968
+ const reward = gameResultLabel(quizOutcome);
1969
+ return /*#__PURE__*/_jsxs(View, {
1970
+ style: sh.content,
1971
+ children: [/*#__PURE__*/_jsx(Text, {
1972
+ style: [sh.title, {
1973
+ color: theme.titleColor
1974
+ }],
1975
+ children: question
1976
+ }), /*#__PURE__*/_jsx(Body, {
1977
+ text: spec.body,
1978
+ theme: theme
1979
+ }), quizMode ? /*#__PURE__*/_jsx(View, {
1980
+ style: po.quizProgress,
1981
+ children: Array.from({
1982
+ length: quizTotal
1983
+ }, (_, index) => /*#__PURE__*/_jsx(View, {
1984
+ style: [po.quizStep, {
1985
+ backgroundColor: index <= questionIndex ? theme.accent : theme.couponBg
1986
+ }]
1987
+ }, index))
1988
+ }) : null, /*#__PURE__*/_jsx(View, {
1989
+ style: [po.options, chipMode && po.chips],
1990
+ children: options.map((opt, i) => {
1991
+ const active = selected.includes(i);
1992
+ const correct = quizMode && answerLocked && correctIndexes.includes(i);
1993
+ const wrong = quizMode && answerLocked && active && !correct;
1994
+ const configuredPercent = num(record(rawOptions[i]).percent, -1);
1995
+ return /*#__PURE__*/_jsxs(Pressable, {
1996
+ style: [chipMode ? po.chip : po.option, {
1997
+ borderColor: correct ? "#22A06B" : wrong ? "#D92D20" : active ? theme.accent : "#E4E7EC",
1998
+ backgroundColor: correct || active ? theme.couponBg : "#FAFAFA"
1999
+ }],
2000
+ onPress: () => toggle(i),
2001
+ disabled: quizMode && answerLocked,
2002
+ accessibilityRole: allowMultiple ? "checkbox" : "radio",
2003
+ accessibilityState: allowMultiple ? {
2004
+ checked: active
2005
+ } : {
2006
+ selected: active
2007
+ },
2008
+ accessibilityLabel: opt,
2009
+ children: [!chipMode ? /*#__PURE__*/_jsx(View, {
2010
+ style: [po.radio, {
2011
+ borderColor: correct ? "#22A06B" : wrong ? "#D92D20" : active ? theme.accent : "#9CA3AF",
2012
+ backgroundColor: correct ? "#22A06B" : wrong ? "#D92D20" : active ? theme.accent : "transparent"
2013
+ }]
2014
+ }) : null, /*#__PURE__*/_jsx(Text, {
2015
+ style: [chipMode ? po.chipLabel : po.optLabel, {
2016
+ color: active ? theme.accent : theme.titleColor
2017
+ }],
2018
+ children: opt
2019
+ }), showResults && answered && configuredPercent >= 0 ? /*#__PURE__*/_jsxs(Text, {
2020
+ style: [po.pct, {
2021
+ color: theme.secondaryColor
2022
+ }],
2023
+ children: [configuredPercent, "%"]
2024
+ }) : null]
2025
+ }, i);
2026
+ })
2027
+ }), quizMode && answerLocked && correctIndexes.length > 0 ? /*#__PURE__*/_jsxs(View, {
2028
+ style: [po.result, {
2029
+ backgroundColor: theme.couponBg
2030
+ }],
2031
+ children: [/*#__PURE__*/_jsx(Text, {
2032
+ style: [po.resultTitle, {
2033
+ color: theme.titleColor
2034
+ }],
2035
+ children: quizCorrect ? "Correct" : "Not quite"
2036
+ }), explanation ? /*#__PURE__*/_jsx(Text, {
2037
+ style: [po.resultBody, {
2038
+ color: theme.bodyColor
2039
+ }],
2040
+ children: explanation
2041
+ }) : null]
2042
+ }) : null, quizMode && answerLocked && !quizGraded ? /*#__PURE__*/_jsx(View, {
2043
+ style: [po.result, {
2044
+ backgroundColor: theme.couponBg
2045
+ }],
2046
+ children: /*#__PURE__*/_jsx(Text, {
2047
+ style: [po.resultTitle, {
2048
+ color: theme.titleColor
2049
+ }],
2050
+ children: "Answer recorded"
2051
+ })
2052
+ }) : null, variantKey === "product_finder" && answered && !hasNext && mappedResult ? /*#__PURE__*/_jsxs(View, {
2053
+ style: [po.finderResult, {
2054
+ backgroundColor: theme.couponBg
2055
+ }],
2056
+ children: [mappedImage ? /*#__PURE__*/_jsx(Image, {
2057
+ source: {
2058
+ uri: mappedImage
2059
+ },
2060
+ resizeMode: "cover",
2061
+ style: po.finderImage
2062
+ }) : null, /*#__PURE__*/_jsxs(View, {
2063
+ style: po.finderCopy,
2064
+ children: [/*#__PURE__*/_jsx(Text, {
2065
+ style: [po.resultTitle, {
2066
+ color: theme.titleColor
2067
+ }],
2068
+ children: mappedResult
2069
+ }), mappedBody ? /*#__PURE__*/_jsx(Text, {
2070
+ style: [po.resultBody, {
2071
+ color: theme.bodyColor
2072
+ }],
2073
+ children: mappedBody
2074
+ }) : null, mappedPrice ? /*#__PURE__*/_jsx(Text, {
2075
+ style: [po.finderPrice, {
2076
+ color: theme.accent
2077
+ }],
2078
+ children: mappedPrice
2079
+ }) : null]
2080
+ })]
2081
+ }) : null, quizMode && answered && !answerLocked ? /*#__PURE__*/_jsx(View, {
2082
+ style: sh.ctaRow,
2083
+ children: /*#__PURE__*/_jsx(PrimaryBtn, {
2084
+ label: str(e.checkLabel) || "CHECK ANSWER",
2085
+ accent: theme.accent,
2086
+ onPress: checkQuizAnswer
2087
+ })
2088
+ }) : quizMode && answerLocked ? hasNext ? /*#__PURE__*/_jsx(View, {
2089
+ style: sh.ctaRow,
2090
+ children: /*#__PURE__*/_jsx(PrimaryBtn, {
2091
+ label: str(e.nextLabel) || "Next",
2092
+ accent: theme.accent,
2093
+ onPress: advanceQuiz
2094
+ })
2095
+ }) : /*#__PURE__*/_jsxs(_Fragment, {
2096
+ children: [/*#__PURE__*/_jsxs(View, {
2097
+ style: [po.score, {
2098
+ backgroundColor: theme.couponBg
2099
+ }],
2100
+ accessibilityLiveRegion: "polite",
2101
+ children: [/*#__PURE__*/_jsx(Text, {
2102
+ style: [po.scoreTitle, {
2103
+ color: theme.titleColor
2104
+ }],
2105
+ children: gradedCount > 0 ? `Score ${score} / ${gradedCount}` : "Responses recorded"
2106
+ }), quizLoading ? /*#__PURE__*/_jsx(Text, {
2107
+ style: [po.scoreReward, {
2108
+ color: theme.bodyColor
2109
+ }],
2110
+ children: "Confirming your result\u2026"
2111
+ }) : null, quizError ? /*#__PURE__*/_jsx(Text, {
2112
+ style: [po.scoreReward, {
2113
+ color: theme.bodyColor
2114
+ }],
2115
+ children: quizError
2116
+ }) : null, reward ? /*#__PURE__*/_jsx(Text, {
2117
+ style: [po.scoreReward, {
2118
+ color: theme.accent
2119
+ }],
2120
+ children: reward
2121
+ }) : null]
2122
+ }), quizError && !quizLoading ? /*#__PURE__*/_jsx(PrimaryBtn, {
2123
+ label: "RETRY",
2124
+ accent: theme.accent,
2125
+ onPress: () => {
2126
+ if (quizSubmission.current) void submitQuiz(quizSubmission.current);
2127
+ }
2128
+ }) : quizOutcome && (spec.cta || spec.secondaryCta) ? /*#__PURE__*/_jsx(CtaArea, {
2129
+ ...props
2130
+ }) : quizOutcome ? /*#__PURE__*/_jsx(PrimaryBtn, {
2131
+ label: "DONE",
2132
+ accent: theme.accent,
2133
+ onPress: () => {
2134
+ if (!presentTerminalOutcome(props, reward)) props.onDismiss();
2135
+ }
2136
+ }) : null]
2137
+ }) : answered ? hasNext ? /*#__PURE__*/_jsx(View, {
2138
+ style: sh.ctaRow,
2139
+ children: /*#__PURE__*/_jsx(PrimaryBtn, {
2140
+ label: str(e.nextLabel) || "Next",
2141
+ accent: theme.accent,
2142
+ onPress: () => {
2143
+ if (variantKey === "product_finder" && selectedLabel) {
2144
+ setAnswerPath(current => [...current, selectedLabel]);
2145
+ }
2146
+ setQuestionIndex(current => current + 1);
2147
+ setSelected([]);
2148
+ }
2149
+ })
2150
+ }) : /*#__PURE__*/_jsx(CtaArea, {
2151
+ ...props,
2152
+ beforePrimary: () => props.onInteraction?.({
2153
+ mechanic: variantKey || "question_flow",
2154
+ phase: "resolved",
2155
+ value: selectedLabel,
2156
+ meta: {
2157
+ questionIndex,
2158
+ selected,
2159
+ labels: selected.map(value => options[value]).filter(Boolean),
2160
+ ...(variantKey === "product_finder" && mappedResult ? {
2161
+ result: {
2162
+ title: mappedResult,
2163
+ body: mappedBody,
2164
+ price: mappedPrice,
2165
+ image: mappedImage
2166
+ }
2167
+ } : {})
2168
+ }
2169
+ })
2170
+ }) : null]
2171
+ });
2172
+ }
2173
+ const po = {
2174
+ options: {
2175
+ marginVertical: 8
2176
+ },
2177
+ chips: {
2178
+ flexDirection: "row",
2179
+ flexWrap: "wrap",
2180
+ gap: 8
2181
+ },
2182
+ option: {
2183
+ flexDirection: "row",
2184
+ alignItems: "center",
2185
+ gap: 10,
2186
+ borderWidth: 1.5,
2187
+ borderRadius: 10,
2188
+ padding: 10,
2189
+ marginBottom: 8
2190
+ },
2191
+ chip: {
2192
+ borderWidth: 1.5,
2193
+ borderRadius: 999,
2194
+ maxWidth: "100%",
2195
+ paddingVertical: 9,
2196
+ paddingHorizontal: 14
2197
+ },
2198
+ radio: {
2199
+ width: 18,
2200
+ height: 18,
2201
+ borderRadius: 9,
2202
+ borderWidth: 2
2203
+ },
2204
+ optLabel: {
2205
+ flex: 1,
2206
+ fontSize: 13,
2207
+ fontWeight: "500"
2208
+ },
2209
+ chipLabel: {
2210
+ flexShrink: 1,
2211
+ fontSize: 13,
2212
+ fontWeight: "600"
2213
+ },
2214
+ pct: {
2215
+ fontSize: 12,
2216
+ fontWeight: "600"
2217
+ },
2218
+ result: {
2219
+ borderRadius: 10,
2220
+ padding: 10,
2221
+ marginTop: 4
2222
+ },
2223
+ resultTitle: {
2224
+ fontSize: 13,
2225
+ fontWeight: "800"
2226
+ },
2227
+ resultBody: {
2228
+ fontSize: 12,
2229
+ lineHeight: 17,
2230
+ marginTop: 2
2231
+ },
2232
+ quizProgress: {
2233
+ flexDirection: "row",
2234
+ gap: 5,
2235
+ marginVertical: 10
2236
+ },
2237
+ quizStep: {
2238
+ flex: 1,
2239
+ height: 5,
2240
+ borderRadius: 3
2241
+ },
2242
+ score: {
2243
+ borderRadius: 12,
2244
+ padding: 14,
2245
+ marginTop: 10,
2246
+ alignItems: "center"
2247
+ },
2248
+ scoreTitle: {
2249
+ fontSize: 18,
2250
+ fontWeight: "900"
2251
+ },
2252
+ scoreReward: {
2253
+ fontSize: 13,
2254
+ fontWeight: "700",
2255
+ marginTop: 4
2256
+ },
2257
+ finderResult: {
2258
+ flexDirection: "row",
2259
+ gap: 12,
2260
+ borderRadius: 12,
2261
+ padding: 12,
2262
+ marginTop: 8,
2263
+ alignItems: "center"
2264
+ },
2265
+ finderImage: {
2266
+ width: 72,
2267
+ height: 72,
2268
+ borderRadius: 10
2269
+ },
2270
+ finderCopy: {
2271
+ flex: 1
2272
+ },
2273
+ finderPrice: {
2274
+ fontSize: 13,
2275
+ fontWeight: "800",
2276
+ marginTop: 4
2277
+ }
2278
+ };
2279
+
2280
+ // ─── 6. NPS Prompt ────────────────────────────────────────────────────────────
2281
+
2282
+ export function NpsPromptInApp(props) {
2283
+ const {
2284
+ spec,
2285
+ theme
2286
+ } = props;
2287
+ const e = ext(props);
2288
+ const question = str(e.question) || spec.title || "How likely are you to recommend us?";
2289
+ const scaleMax = boundedInt(e.scaleMax, 10, 1, 10);
2290
+ const scaleLabels = arr(e.scaleLabels).map(str);
2291
+ const [selected, setSelected] = useState(null);
2292
+ const [feedback, setFeedback] = useState("");
2293
+ const feedbackEnabled = e.feedbackArea === true || typeof e.feedbackArea === "string";
2294
+ const emitNps = () => props.onInteraction?.({
2295
+ mechanic: "nps_prompt",
2296
+ phase: "resolved",
2297
+ value: selected ?? undefined,
2298
+ result: feedback.trim() || undefined,
2299
+ meta: feedback.trim() ? {
2300
+ feedback: feedback.trim()
2301
+ } : undefined
2302
+ });
2303
+ return /*#__PURE__*/_jsxs(View, {
2304
+ style: sh.content,
2305
+ children: [/*#__PURE__*/_jsx(Text, {
2306
+ style: [sh.title, {
2307
+ color: theme.titleColor
2308
+ }],
2309
+ children: question
2310
+ }), /*#__PURE__*/_jsx(Body, {
2311
+ text: spec.body,
2312
+ theme: theme
2313
+ }), /*#__PURE__*/_jsx(ScrollView, {
2314
+ horizontal: true,
2315
+ showsHorizontalScrollIndicator: false,
2316
+ style: {
2317
+ marginVertical: 12
2318
+ },
2319
+ contentContainerStyle: nps.row,
2320
+ children: Array.from({
2321
+ length: scaleMax + 1
2322
+ }, (_, i) => /*#__PURE__*/_jsx(Pressable, {
2323
+ style: [nps.cell, {
2324
+ borderColor: selected === i ? theme.accent : "#E4E7EC",
2325
+ backgroundColor: selected === i ? theme.couponBg : "#FAFAFA"
2326
+ }],
2327
+ onPress: () => {
2328
+ setSelected(i);
2329
+ props.onInteraction?.({
2330
+ mechanic: "nps_prompt",
2331
+ phase: "decision",
2332
+ value: i
2333
+ });
2334
+ },
2335
+ accessibilityRole: "radio",
2336
+ accessibilityLabel: String(i),
2337
+ children: /*#__PURE__*/_jsx(Text, {
2338
+ style: [nps.num, {
2339
+ color: selected === i ? theme.accent : theme.bodyColor
2340
+ }],
2341
+ children: i
2342
+ })
2343
+ }, i))
2344
+ }), scaleLabels.length >= 2 ? /*#__PURE__*/_jsxs(View, {
2345
+ style: nps.labels,
2346
+ children: [/*#__PURE__*/_jsx(Text, {
2347
+ style: [nps.lbl, {
2348
+ color: theme.secondaryColor
2349
+ }],
2350
+ children: scaleLabels[0]
2351
+ }), /*#__PURE__*/_jsx(Text, {
2352
+ style: [nps.lbl, nps.lblEnd, {
2353
+ color: theme.secondaryColor
2354
+ }],
2355
+ children: scaleLabels[scaleLabels.length - 1]
2356
+ })]
2357
+ }) : null, selected !== null && feedbackEnabled ? /*#__PURE__*/_jsx(TextInput, {
2358
+ value: feedback,
2359
+ onChangeText: setFeedback,
2360
+ multiline: true,
2361
+ placeholder: typeof e.feedbackArea === "string" ? e.feedbackArea : "Tell us more (optional)",
2362
+ placeholderTextColor: theme.secondaryColor,
2363
+ accessibilityLabel: "Additional NPS feedback",
2364
+ style: [nps.feedback, {
2365
+ color: theme.titleColor,
2366
+ borderColor: theme.couponBorder
2367
+ }]
2368
+ }) : null, selected !== null ? props.spec.cta || props.spec.secondaryCta ? /*#__PURE__*/_jsx(CtaArea, {
2369
+ ...props,
2370
+ beforePrimary: emitNps
2371
+ }) : /*#__PURE__*/_jsx(PrimaryBtn, {
2372
+ label: "SUBMIT",
2373
+ accent: theme.accent,
2374
+ onPress: () => {
2375
+ emitNps();
2376
+ if (!presentTerminalOutcome(props)) props.onDismiss();
2377
+ }
2378
+ }) : null]
2379
+ });
2380
+ }
2381
+ const nps = {
2382
+ row: {
2383
+ flexDirection: "row",
2384
+ gap: 6,
2385
+ paddingHorizontal: 2
2386
+ },
2387
+ cell: {
2388
+ minHeight: 44,
2389
+ minWidth: 44,
2390
+ paddingHorizontal: 8,
2391
+ paddingVertical: 8,
2392
+ borderRadius: 8,
2393
+ borderWidth: 1.5,
2394
+ justifyContent: "center",
2395
+ alignItems: "center"
2396
+ },
2397
+ num: {
2398
+ fontSize: 13,
2399
+ fontWeight: "700"
2400
+ },
2401
+ labels: {
2402
+ flexDirection: "row",
2403
+ justifyContent: "space-between",
2404
+ marginTop: 2
2405
+ },
2406
+ lbl: {
2407
+ flex: 1,
2408
+ fontSize: 10,
2409
+ fontWeight: "500"
2410
+ },
2411
+ lblEnd: {
2412
+ textAlign: "right"
2413
+ },
2414
+ feedback: {
2415
+ minHeight: 76,
2416
+ borderWidth: 1,
2417
+ borderRadius: 10,
2418
+ padding: 10,
2419
+ marginTop: 10,
2420
+ fontSize: 13
2421
+ }
2422
+ };
2423
+
2424
+ // ─── 7. Emoji Reaction Bar ────────────────────────────────────────────────────
2425
+
2426
+ export function EmojiReactionBarInApp(props) {
2427
+ const {
2428
+ spec,
2429
+ theme
2430
+ } = props;
2431
+ const e = ext(props);
2432
+ const reactions = arr(e.reactions).map(str);
2433
+ const emojis = reactions.length > 0 ? reactions : ["😍", "😊", "😐", "😕", "😡"];
2434
+ const [selected, setSelected] = useState(null);
2435
+ return /*#__PURE__*/_jsxs(View, {
2436
+ style: sh.content,
2437
+ children: [/*#__PURE__*/_jsx(Title, {
2438
+ text: spec.title,
2439
+ theme: theme
2440
+ }), /*#__PURE__*/_jsx(Body, {
2441
+ text: spec.body,
2442
+ theme: theme
2443
+ }), /*#__PURE__*/_jsx(View, {
2444
+ style: er.row,
2445
+ children: emojis.map((e, i) => /*#__PURE__*/_jsx(Pressable, {
2446
+ style: [er.btn, selected === i && {
2447
+ backgroundColor: theme.couponBg,
2448
+ borderColor: theme.accent
2449
+ }],
2450
+ onPress: () => {
2451
+ setSelected(i);
2452
+ props.onInteraction?.({
2453
+ mechanic: "emoji_reaction_bar",
2454
+ phase: "decision",
2455
+ value: e
2456
+ });
2457
+ },
2458
+ accessibilityRole: "radio",
2459
+ accessibilityLabel: e,
2460
+ children: /*#__PURE__*/_jsx(Text, {
2461
+ style: er.emoji,
2462
+ children: e
2463
+ })
2464
+ }, i))
2465
+ }), selected !== null ? /*#__PURE__*/_jsx(CtaArea, {
2466
+ ...props,
2467
+ beforePrimary: () => props.onInteraction?.({
2468
+ mechanic: "emoji_reaction_bar",
2469
+ phase: "resolved",
2470
+ value: emojis[selected]
2471
+ })
2472
+ }) : null]
2473
+ });
2474
+ }
2475
+ const er = {
2476
+ row: {
2477
+ flexDirection: "row",
2478
+ flexWrap: "wrap",
2479
+ justifyContent: "center",
2480
+ gap: 10,
2481
+ marginVertical: 14
2482
+ },
2483
+ btn: {
2484
+ alignItems: "center",
2485
+ justifyContent: "center",
2486
+ minHeight: 44,
2487
+ minWidth: 44,
2488
+ padding: 8,
2489
+ borderRadius: 12,
2490
+ borderWidth: 1.5,
2491
+ borderColor: "transparent"
2492
+ },
2493
+ emoji: {
2494
+ fontSize: 32
2495
+ }
2496
+ };
2497
+
2498
+ // ─── 8. Star Rating ───────────────────────────────────────────────────────────
2499
+
2500
+ export function StarRatingInApp(props) {
2501
+ const {
2502
+ spec,
2503
+ theme
2504
+ } = props;
2505
+ const e = ext(props);
2506
+ const max = boundedInt(e.ratingScale, 5, 1, 10);
2507
+ const [rating, setRating] = useState(0);
2508
+ const threshold = boundedInt(e.storeReviewThreshold ?? e.ratingGate, 4, 1, Math.max(1, max));
2509
+ const complete = async () => {
2510
+ props.onInteraction?.({
2511
+ mechanic: "rating_request",
2512
+ phase: "resolved",
2513
+ value: rating,
2514
+ meta: {
2515
+ threshold
2516
+ }
2517
+ });
2518
+ if (rating >= threshold) {
2519
+ const ran = await requestNativeStoreReview();
2520
+ props.onInteraction?.({
2521
+ mechanic: "rating_request",
2522
+ phase: "completed",
2523
+ value: rating,
2524
+ meta: {
2525
+ route: ran ? "store_review" : "fallback",
2526
+ threshold
2527
+ }
2528
+ });
2529
+ if (ran) {
2530
+ if (presentTerminalOutcome(props)) return;
2531
+ props.onDismiss();
2532
+ return;
2533
+ }
2534
+ }
2535
+ if (presentTerminalOutcome(props)) return;
2536
+ if (!props.spec.cta || props.spec.cta.action === "dismiss") props.onDismiss();else props.onCta(props.spec.cta);
2537
+ };
2538
+ return /*#__PURE__*/_jsxs(View, {
2539
+ style: sh.content,
2540
+ children: [/*#__PURE__*/_jsx(Title, {
2541
+ text: spec.title,
2542
+ theme: theme
2543
+ }), /*#__PURE__*/_jsx(Body, {
2544
+ text: spec.body,
2545
+ theme: theme
2546
+ }), /*#__PURE__*/_jsx(View, {
2547
+ style: sr.row,
2548
+ children: Array.from({
2549
+ length: max
2550
+ }, (_, i) => /*#__PURE__*/_jsx(Pressable, {
2551
+ style: sr.btn,
2552
+ onPress: () => {
2553
+ setRating(i + 1);
2554
+ props.onInteraction?.({
2555
+ mechanic: "rating_request",
2556
+ phase: "decision",
2557
+ value: i + 1
2558
+ });
2559
+ },
2560
+ accessibilityRole: "radio",
2561
+ accessibilityLabel: `${i + 1} star`,
2562
+ children: /*#__PURE__*/_jsx(Text, {
2563
+ style: [sr.star, {
2564
+ color: i < rating ? "#F59E0B" : "#D1D5DB"
2565
+ }],
2566
+ children: "\u2605"
2567
+ })
2568
+ }, i))
2569
+ }), rating > 0 ? /*#__PURE__*/_jsxs(View, {
2570
+ style: sh.ctaRow,
2571
+ children: [props.spec.secondaryCta ? /*#__PURE__*/_jsx(SecondaryBtn, {
2572
+ label: props.spec.secondaryCta.label,
2573
+ onPress: props.onDismiss,
2574
+ color: theme.secondaryColor
2575
+ }) : null, /*#__PURE__*/_jsx(PrimaryBtn, {
2576
+ label: props.spec.cta?.label || (rating >= threshold ? "RATE APP" : "CONTINUE"),
2577
+ accent: theme.accent,
2578
+ onPress: () => {
2579
+ void complete();
2580
+ }
2581
+ })]
2582
+ }) : null]
2583
+ });
2584
+ }
2585
+ const sr = {
2586
+ row: {
2587
+ flexDirection: "row",
2588
+ flexWrap: "wrap",
2589
+ justifyContent: "center",
2590
+ gap: 6,
2591
+ marginVertical: 14
2592
+ },
2593
+ btn: {
2594
+ alignItems: "center",
2595
+ justifyContent: "center",
2596
+ minHeight: 44,
2597
+ minWidth: 44
2598
+ },
2599
+ star: {
2600
+ fontSize: 38
2601
+ }
2602
+ };
2603
+
2604
+ // ─── 9. Streak Reward ─────────────────────────────────────────────────────────
2605
+
2606
+ export function StreakRewardInApp(props) {
2607
+ const {
2608
+ spec,
2609
+ theme
2610
+ } = props;
2611
+ const e = ext(props);
2612
+ const daily = variantOf(props) === "daily_check_in";
2613
+ const [gameOutcome, setGameOutcome] = useState(null);
2614
+ const [gameLoading, setGameLoading] = useState(false);
2615
+ const [gameError, setGameError] = useState("");
2616
+ const loadStarted = useRef(false);
2617
+ const resolveState = async () => {
2618
+ if (gameLoading) return;
2619
+ setGameLoading(true);
2620
+ setGameError("");
2621
+ props.onInteraction?.({
2622
+ mechanic: daily ? "daily_check_in" : "streak_reward",
2623
+ phase: "started",
2624
+ attempt: 1
2625
+ });
2626
+ try {
2627
+ setGameOutcome(await requireGameOutcome(props));
2628
+ } catch (error) {
2629
+ setGameError(gameUnavailableText(error));
2630
+ } finally {
2631
+ setGameLoading(false);
2632
+ }
2633
+ };
2634
+ useEffect(() => {
2635
+ if (daily || loadStarted.current) return;
2636
+ loadStarted.current = true;
2637
+ void resolveState();
2638
+ }, [daily]);
2639
+ const remoteState = gameOutcome?.state ?? {};
2640
+ const calendarDays = arr(remoteState.calendarDays);
2641
+ const authoredFilled = calendarDays.filter(day => {
2642
+ const value = record(day);
2643
+ return day === true || value.done === true || value.checked === true || value.claimed === true;
2644
+ }).length;
2645
+ const total = boundedInt(remoteState.total ?? e.total, calendarDays.length || 7, 1, 31);
2646
+ const initialFilled = boundedInt(typeof remoteState.filled === "number" ? remoteState.filled : typeof remoteState.streakValue === "number" ? remoteState.streakValue : authoredFilled, 0, 0, total);
2647
+ const authoredToday = calendarDays.find(day => {
2648
+ const value = record(day);
2649
+ return value.today === true || value.current === true;
2650
+ });
2651
+ const todayState = record(authoredToday);
2652
+ const alreadyCheckedIn = initialFilled >= total || authoredToday === true || todayState.done === true || todayState.checked === true || todayState.claimed === true;
2653
+ const filled = initialFilled;
2654
+ const glyph = str(remoteState.glyph ?? e.glyph) || "🔥";
2655
+ const caption = gameResultLabel(gameOutcome) || str(remoteState.rewardMessage ?? e.rewardMessage) || (filled > 0 ? `${filled}-day streak` : "");
2656
+ const finishDaily = () => {
2657
+ if (presentTerminalOutcome(props, gameResultLabel(gameOutcome))) return;
2658
+ if (spec.cta && spec.cta.action !== "dismiss") props.onCta(spec.cta);else props.onDismiss();
2659
+ };
2660
+ return /*#__PURE__*/_jsxs(View, {
2661
+ style: sh.content,
2662
+ children: [/*#__PURE__*/_jsx(Title, {
2663
+ text: spec.title,
2664
+ theme: theme
2665
+ }), /*#__PURE__*/_jsx(Body, {
2666
+ text: spec.body,
2667
+ theme: theme
2668
+ }), !daily && (gameLoading || !gameOutcome && gameError) ? /*#__PURE__*/_jsxs(View, {
2669
+ style: [stk.claimed, {
2670
+ backgroundColor: theme.couponBg
2671
+ }],
2672
+ children: [/*#__PURE__*/_jsx(Text, {
2673
+ style: [stk.claimedTitle, {
2674
+ color: theme.bodyColor
2675
+ }],
2676
+ children: gameLoading ? "Loading your streak…" : gameError
2677
+ }), !gameLoading ? /*#__PURE__*/_jsx(PrimaryBtn, {
2678
+ label: "RETRY",
2679
+ accent: theme.accent,
2680
+ onPress: () => {
2681
+ void resolveState();
2682
+ }
2683
+ }) : null]
2684
+ }) : /*#__PURE__*/_jsx(View, {
2685
+ style: stk.row,
2686
+ children: Array.from({
2687
+ length: total
2688
+ }, (_, i) => {
2689
+ const day = record(calendarDays[i]);
2690
+ const frozen = day.frozen === true || day.freeze === true;
2691
+ const completed = calendarDays.length > 0 ? calendarDays[i] === true || day.done === true || day.checked === true || day.claimed === true : i < filled;
2692
+ return /*#__PURE__*/_jsx(View, {
2693
+ style: [stk.dot, {
2694
+ backgroundColor: completed ? theme.couponBg : "#F1F3F7",
2695
+ borderColor: completed ? theme.accent : "#E4E7EC"
2696
+ }],
2697
+ children: /*#__PURE__*/_jsx(Text, {
2698
+ style: stk.glyph,
2699
+ children: frozen ? "❄️" : completed ? glyph : ""
2700
+ })
2701
+ }, i);
2702
+ })
2703
+ }), caption ? /*#__PURE__*/_jsx(Text, {
2704
+ style: [stk.caption, {
2705
+ color: theme.accent
2706
+ }],
2707
+ children: caption
2708
+ }) : null, daily ? gameOutcome ? /*#__PURE__*/_jsxs(View, {
2709
+ style: [stk.claimed, {
2710
+ backgroundColor: theme.couponBg
2711
+ }],
2712
+ accessibilityLiveRegion: "polite",
2713
+ children: [/*#__PURE__*/_jsx(Text, {
2714
+ style: [stk.claimedTitle, {
2715
+ color: theme.accent
2716
+ }],
2717
+ children: alreadyCheckedIn ? "✓ Checked in today" : gameResultLabel(gameOutcome) || "Check-in confirmed"
2718
+ }), /*#__PURE__*/_jsx(PrimaryBtn, {
2719
+ label: "DONE",
2720
+ accent: theme.accent,
2721
+ onPress: finishDaily
2722
+ })]
2723
+ }) : /*#__PURE__*/_jsxs(_Fragment, {
2724
+ children: [gameError ? /*#__PURE__*/_jsx(Text, {
2725
+ style: [sl.unavailable, {
2726
+ color: theme.secondaryColor
2727
+ }],
2728
+ children: gameError
2729
+ }) : null, /*#__PURE__*/_jsx(PrimaryBtn, {
2730
+ label: gameLoading ? "CHECKING…" : gameError ? "RETRY" : spec.cta?.label || "CHECK IN",
2731
+ accent: theme.accent,
2732
+ onPress: () => {
2733
+ void resolveState();
2734
+ },
2735
+ disabled: gameLoading
2736
+ })]
2737
+ }) : gameOutcome ? /*#__PURE__*/_jsx(CtaArea, {
2738
+ ...props
2739
+ }) : null]
2740
+ });
2741
+ }
2742
+ const stk = {
2743
+ row: {
2744
+ flexDirection: "row",
2745
+ gap: 8,
2746
+ justifyContent: "center",
2747
+ marginVertical: 14,
2748
+ flexWrap: "wrap"
2749
+ },
2750
+ dot: {
2751
+ width: 38,
2752
+ height: 38,
2753
+ borderRadius: 10,
2754
+ borderWidth: 1.5,
2755
+ justifyContent: "center",
2756
+ alignItems: "center"
2757
+ },
2758
+ glyph: {
2759
+ fontSize: 18
2760
+ },
2761
+ caption: {
2762
+ textAlign: "center",
2763
+ fontWeight: "700",
2764
+ fontSize: 14,
2765
+ marginBottom: 12
2766
+ },
2767
+ claimed: {
2768
+ borderRadius: 12,
2769
+ padding: 12,
2770
+ gap: 8
2771
+ },
2772
+ claimedTitle: {
2773
+ fontSize: 14,
2774
+ fontWeight: "800",
2775
+ textAlign: "center"
2776
+ }
2777
+ };
2778
+
2779
+ // ─── 10. Checklist Progress ────────────────────────────────────────────────────
2780
+
2781
+ export function ChecklistProgressInApp(props) {
2782
+ const {
2783
+ spec,
2784
+ theme
2785
+ } = props;
2786
+ const e = ext(props);
2787
+ const rawItems = arr(e.items ?? e.steps ?? e.tasks ?? e.checklistItems ?? e.setupSteps);
2788
+ const initial = rawItems.length > 0 ? rawItems.map(item => {
2789
+ const value = record(item);
2790
+ const completed = value.done === true || value.completed === true;
2791
+ return {
2792
+ label: labelOf(item),
2793
+ done: completed,
2794
+ locked: completed || value.interactive === false || value.readOnly === true
2795
+ };
2796
+ }) : [];
2797
+ const [items, setItems] = useState(initial);
2798
+ const done = items.filter(i => i.done).length;
2799
+ const complete = items.length > 0 && done === items.length;
2800
+ const authoredComplete = initial.length > 0 && initial.every(item => item.done);
2801
+ return /*#__PURE__*/_jsxs(View, {
2802
+ style: sh.content,
2803
+ children: [/*#__PURE__*/_jsx(Title, {
2804
+ text: spec.title,
2805
+ theme: theme
2806
+ }), /*#__PURE__*/_jsx(Body, {
2807
+ text: spec.body,
2808
+ theme: theme
2809
+ }), items.length > 0 ? /*#__PURE__*/_jsxs(View, {
2810
+ style: cl.progress,
2811
+ children: [/*#__PURE__*/_jsx(View, {
2812
+ style: [cl.bar, {
2813
+ backgroundColor: theme.couponBg
2814
+ }],
2815
+ children: /*#__PURE__*/_jsx(View, {
2816
+ style: [cl.fill, {
2817
+ width: `${done / items.length * 100}%`,
2818
+ backgroundColor: theme.accent
2819
+ }]
2820
+ })
2821
+ }), /*#__PURE__*/_jsxs(Text, {
2822
+ style: [cl.pct, {
2823
+ color: theme.bodyColor
2824
+ }],
2825
+ children: [done, "/", items.length]
2826
+ })]
2827
+ }) : null, /*#__PURE__*/_jsx(View, {
2828
+ style: {
2829
+ marginVertical: 8
2830
+ },
2831
+ children: items.map((item, i) => /*#__PURE__*/_jsxs(Pressable, {
2832
+ style: [cl.row, {
2833
+ borderBottomColor: "#F1F3F7"
2834
+ }],
2835
+ onPress: () => setItems(prev => prev.map((it, idx) => idx === i && !it.locked ? {
2836
+ ...it,
2837
+ done: !it.done
2838
+ } : it)),
2839
+ onPressOut: () => {
2840
+ if (item.locked) return;
2841
+ const nextDone = done + (item.done ? -1 : 1);
2842
+ props.onInteraction?.({
2843
+ mechanic: "interactive_checklist",
2844
+ phase: "progress",
2845
+ progress: nextDone / items.length,
2846
+ value: nextDone
2847
+ });
2848
+ },
2849
+ disabled: item.locked,
2850
+ accessibilityRole: "checkbox",
2851
+ accessibilityState: {
2852
+ checked: item.done,
2853
+ disabled: item.locked
2854
+ },
2855
+ children: [/*#__PURE__*/_jsx(View, {
2856
+ style: [cl.check, {
2857
+ borderColor: item.done ? theme.accent : "#D1D5DB",
2858
+ backgroundColor: item.done ? theme.accent : "transparent"
2859
+ }],
2860
+ children: item.done ? /*#__PURE__*/_jsx(Text, {
2861
+ style: cl.tick,
2862
+ children: "\u2713"
2863
+ }) : null
2864
+ }), /*#__PURE__*/_jsx(Text, {
2865
+ style: [cl.label, {
2866
+ color: item.done ? theme.bodyColor : theme.titleColor,
2867
+ textDecorationLine: item.done ? "line-through" : "none"
2868
+ }],
2869
+ children: item.label
2870
+ })]
2871
+ }, i))
2872
+ }), complete ? /*#__PURE__*/_jsxs(View, {
2873
+ style: [cl.complete, {
2874
+ backgroundColor: theme.couponBg
2875
+ }],
2876
+ accessibilityLiveRegion: "polite",
2877
+ children: [/*#__PURE__*/_jsx(Text, {
2878
+ style: [cl.completeTitle, {
2879
+ color: theme.accent
2880
+ }],
2881
+ children: authoredComplete ? str(e.completionLabel) || "Checklist complete" : "Ready to finish"
2882
+ }), authoredComplete && labelOf(e.reward) ? /*#__PURE__*/_jsx(Text, {
2883
+ style: [cl.completeReward, {
2884
+ color: theme.bodyColor
2885
+ }],
2886
+ children: labelOf(e.reward)
2887
+ }) : null, /*#__PURE__*/_jsx(CtaArea, {
2888
+ ...props
2889
+ })]
2890
+ }) : null]
2891
+ });
2892
+ }
2893
+ const cl = {
2894
+ progress: {
2895
+ flexDirection: "row",
2896
+ alignItems: "center",
2897
+ gap: 8,
2898
+ marginBottom: 10
2899
+ },
2900
+ bar: {
2901
+ flex: 1,
2902
+ height: 8,
2903
+ borderRadius: 999,
2904
+ overflow: "hidden"
2905
+ },
2906
+ fill: {
2907
+ height: "100%",
2908
+ borderRadius: 999
2909
+ },
2910
+ pct: {
2911
+ fontSize: 11,
2912
+ fontWeight: "600",
2913
+ minWidth: 32,
2914
+ textAlign: "right"
2915
+ },
2916
+ row: {
2917
+ flexDirection: "row",
2918
+ alignItems: "center",
2919
+ gap: 10,
2920
+ paddingVertical: 10,
2921
+ borderBottomWidth: 1
2922
+ },
2923
+ check: {
2924
+ width: 22,
2925
+ height: 22,
2926
+ borderRadius: 6,
2927
+ borderWidth: 1.8,
2928
+ justifyContent: "center",
2929
+ alignItems: "center",
2930
+ flexShrink: 0
2931
+ },
2932
+ tick: {
2933
+ color: "#fff",
2934
+ fontSize: 12,
2935
+ fontWeight: "800"
2936
+ },
2937
+ label: {
2938
+ flex: 1,
2939
+ fontSize: 13,
2940
+ fontWeight: "500"
2941
+ },
2942
+ complete: {
2943
+ borderRadius: 12,
2944
+ padding: 12,
2945
+ marginTop: 8
2946
+ },
2947
+ completeTitle: {
2948
+ fontSize: 14,
2949
+ fontWeight: "800",
2950
+ textAlign: "center"
2951
+ },
2952
+ completeReward: {
2953
+ fontSize: 12,
2954
+ textAlign: "center",
2955
+ marginTop: 3
2956
+ }
2957
+ };
2958
+ function normalizedProgress(value) {
2959
+ if (typeof value !== "number" || !Number.isFinite(value)) return null;
2960
+ return Math.max(0, Math.min(100, value <= 1 ? value * 100 : value));
2961
+ }
2962
+ function ProgressSummaryInApp(props) {
2963
+ const {
2964
+ spec,
2965
+ theme
2966
+ } = props;
2967
+ const e = ext(props);
2968
+ const backendOwned = variantOf(props) === "progress_unlock";
2969
+ const [gameOutcome, setGameOutcome] = useState(null);
2970
+ const [gameLoading, setGameLoading] = useState(backendOwned);
2971
+ const [gameError, setGameError] = useState("");
2972
+ const loadStarted = useRef(false);
2973
+ const loadProgress = async () => {
2974
+ setGameLoading(true);
2975
+ setGameError("");
2976
+ try {
2977
+ const outcome = await requireGameOutcome(props);
2978
+ setGameOutcome(outcome);
2979
+ props.onInteraction?.({
2980
+ mechanic: "progress_unlock",
2981
+ phase: "resolved",
2982
+ result: gameResultLabel(outcome) || outcome.id
2983
+ });
2984
+ } catch (error) {
2985
+ setGameError(gameUnavailableText(error));
2986
+ } finally {
2987
+ setGameLoading(false);
2988
+ }
2989
+ };
2990
+ useEffect(() => {
2991
+ if (!backendOwned || loadStarted.current) return;
2992
+ loadStarted.current = true;
2993
+ void loadProgress();
2994
+ }, [backendOwned]);
2995
+ if (backendOwned && (gameLoading || !gameOutcome)) {
2996
+ return /*#__PURE__*/_jsxs(View, {
2997
+ style: sh.content,
2998
+ children: [/*#__PURE__*/_jsx(Title, {
2999
+ text: spec.title,
3000
+ theme: theme
3001
+ }), /*#__PURE__*/_jsx(Body, {
3002
+ text: spec.body,
3003
+ theme: theme
3004
+ }), /*#__PURE__*/_jsxs(View, {
3005
+ style: [prog.rewardCard, {
3006
+ backgroundColor: theme.couponBg
3007
+ }],
3008
+ children: [/*#__PURE__*/_jsx(Text, {
3009
+ style: [prog.reward, {
3010
+ color: theme.bodyColor
3011
+ }],
3012
+ children: gameLoading ? "Loading your progress…" : gameError || "Progress unavailable."
3013
+ }), !gameLoading ? /*#__PURE__*/_jsx(PrimaryBtn, {
3014
+ label: "RETRY",
3015
+ accent: theme.accent,
3016
+ onPress: () => {
3017
+ void loadProgress();
3018
+ }
3019
+ }) : null]
3020
+ })]
3021
+ });
3022
+ }
3023
+ const state = backendOwned ? gameOutcome?.state ?? {} : e;
3024
+ const steps = arr(state.steps ?? (backendOwned ? e.milestoneLabels : e.setupSteps ?? e.milestoneLabels));
3025
+ const doneSteps = steps.filter(step => {
3026
+ const value = record(step);
3027
+ return value.done === true || value.completed === true;
3028
+ }).length;
3029
+ const firstNumber = values => {
3030
+ const value = values.find(candidate => typeof candidate === "number" && Number.isFinite(candidate));
3031
+ return typeof value === "number" ? value : null;
3032
+ };
3033
+ const direct = firstNumber([state.progress, state.progressValue]);
3034
+ const current = firstNumber([state.currentValue, state.completedSteps, state.progressSource, state.goalSource]);
3035
+ const target = firstNumber([state.targetValue, state.goalValue]);
3036
+ const stepPosition = firstNumber([state.currentStep, state.stepIndex]);
3037
+ let progress = normalizedProgress(direct);
3038
+ if (progress === null && current !== null && target !== null && target > 0) {
3039
+ progress = normalizedProgress(current / target);
3040
+ }
3041
+ if (progress === null && steps.length > 0) {
3042
+ const completed = stepPosition ?? doneSteps;
3043
+ progress = normalizedProgress(completed / steps.length);
3044
+ }
3045
+ progress ??= 0;
3046
+ const unlocked = progress >= 100;
3047
+ const segmentCount = steps.length >= 2 ? Math.min(10, steps.length) : 8;
3048
+ const filledSegments = Math.round(progress / 100 * segmentCount);
3049
+ const message = firstText(e, ["message", "motivationalText", "onboardingMessage", "rewardMessage", "goal"]) || spec.body;
3050
+ const reward = backendOwned ? gameResultLabel(gameOutcome) : labelOf(e.reward);
3051
+ const nextStep = steps.find(step => {
3052
+ const value = record(step);
3053
+ return value.done !== true && value.completed !== true;
3054
+ });
3055
+ const labels = arr(e.milestoneLabels).map(labelOf).filter(Boolean);
3056
+ return /*#__PURE__*/_jsxs(View, {
3057
+ style: sh.content,
3058
+ children: [/*#__PURE__*/_jsx(Title, {
3059
+ text: spec.title || firstText(e, ["challengeTitle", "celebrationTitle", "goalTitle"]),
3060
+ theme: theme
3061
+ }), /*#__PURE__*/_jsx(Body, {
3062
+ text: message,
3063
+ theme: theme
3064
+ }), /*#__PURE__*/_jsxs(View, {
3065
+ style: prog.header,
3066
+ children: [/*#__PURE__*/_jsx(Text, {
3067
+ style: [prog.metric, {
3068
+ color: theme.accent
3069
+ }],
3070
+ children: current !== null && target !== null ? `${current}/${target}` : `${Math.round(progress)}%`
3071
+ }), nextStep ? /*#__PURE__*/_jsx(Text, {
3072
+ numberOfLines: 1,
3073
+ style: [prog.next, {
3074
+ color: theme.bodyColor
3075
+ }],
3076
+ children: labelOf(nextStep)
3077
+ }) : null]
3078
+ }), /*#__PURE__*/_jsx(View, {
3079
+ style: prog.segments,
3080
+ accessibilityRole: "progressbar",
3081
+ accessibilityValue: {
3082
+ min: 0,
3083
+ max: 100,
3084
+ now: Math.round(progress)
3085
+ },
3086
+ children: Array.from({
3087
+ length: segmentCount
3088
+ }, (_, index) => /*#__PURE__*/_jsx(View, {
3089
+ style: [prog.segment, {
3090
+ backgroundColor: index < filledSegments ? theme.accent : theme.couponBg
3091
+ }]
3092
+ }, index))
3093
+ }), labels.length > 1 ? /*#__PURE__*/_jsxs(View, {
3094
+ style: prog.labels,
3095
+ children: [/*#__PURE__*/_jsx(Text, {
3096
+ style: [prog.label, {
3097
+ color: theme.secondaryColor
3098
+ }],
3099
+ children: labels[0]
3100
+ }), /*#__PURE__*/_jsx(Text, {
3101
+ style: [prog.label, {
3102
+ color: theme.secondaryColor
3103
+ }],
3104
+ children: labels[labels.length - 1]
3105
+ })]
3106
+ }) : null, reward ? /*#__PURE__*/_jsx(View, {
3107
+ style: [prog.rewardCard, {
3108
+ backgroundColor: theme.couponBg
3109
+ }],
3110
+ children: /*#__PURE__*/_jsxs(Text, {
3111
+ style: [prog.reward, {
3112
+ color: unlocked ? theme.accent : theme.bodyColor
3113
+ }],
3114
+ children: [unlocked ? "✓ Unlocked" : "Unlocks", ": ", reward]
3115
+ })
3116
+ }) : null, /*#__PURE__*/_jsx(CtaArea, {
3117
+ ...props
3118
+ })]
3119
+ });
3120
+ }
3121
+ const prog = {
3122
+ header: {
3123
+ flexDirection: "row",
3124
+ alignItems: "center",
3125
+ gap: 10,
3126
+ marginTop: 12
3127
+ },
3128
+ metric: {
3129
+ fontSize: 20,
3130
+ fontWeight: "800"
3131
+ },
3132
+ next: {
3133
+ flex: 1,
3134
+ fontSize: 12,
3135
+ textAlign: "right"
3136
+ },
3137
+ segments: {
3138
+ flexDirection: "row",
3139
+ gap: 4,
3140
+ marginTop: 8
3141
+ },
3142
+ segment: {
3143
+ flex: 1,
3144
+ height: 9,
3145
+ borderRadius: 5
3146
+ },
3147
+ labels: {
3148
+ flexDirection: "row",
3149
+ justifyContent: "space-between",
3150
+ marginTop: 5
3151
+ },
3152
+ label: {
3153
+ fontSize: 10
3154
+ },
3155
+ reward: {
3156
+ fontSize: 12,
3157
+ fontWeight: "600",
3158
+ marginTop: 10
3159
+ },
3160
+ rewardCard: {
3161
+ borderRadius: 10,
3162
+ padding: 10,
3163
+ marginTop: 10
3164
+ }
3165
+ };
3166
+ function CommunityChallengeInApp(props) {
3167
+ const {
3168
+ spec,
3169
+ theme
3170
+ } = props;
3171
+ const e = ext(props);
3172
+ const [joined, setJoined] = useState(false);
3173
+ const participants = boundedInt(e.participantsCount, 0, 0, 999_999_999);
3174
+ const leaders = arr(e.leaderboard).slice(0, 5);
3175
+ const title = str(e.challengeTitle) || spec.title;
3176
+ const goal = str(e.goal) || spec.body;
3177
+ const reward = labelOf(e.reward);
3178
+ return /*#__PURE__*/_jsxs(View, {
3179
+ style: sh.content,
3180
+ children: [/*#__PURE__*/_jsx(Title, {
3181
+ text: title,
3182
+ theme: theme
3183
+ }), /*#__PURE__*/_jsx(Body, {
3184
+ text: goal,
3185
+ theme: theme
3186
+ }), /*#__PURE__*/_jsxs(View, {
3187
+ style: [challenge.hero, {
3188
+ backgroundColor: theme.couponBg
3189
+ }],
3190
+ children: [/*#__PURE__*/_jsx(Text, {
3191
+ style: challenge.emoji,
3192
+ children: joined ? "🏁" : "🏆"
3193
+ }), /*#__PURE__*/_jsx(Text, {
3194
+ style: [challenge.count, {
3195
+ color: theme.accent
3196
+ }],
3197
+ children: participants.toLocaleString()
3198
+ }), /*#__PURE__*/_jsx(Text, {
3199
+ style: [challenge.caption, {
3200
+ color: theme.bodyColor
3201
+ }],
3202
+ children: "participants"
3203
+ })]
3204
+ }), leaders.length > 0 ? /*#__PURE__*/_jsx(View, {
3205
+ style: challenge.board,
3206
+ children: leaders.map((leader, index) => /*#__PURE__*/_jsxs(View, {
3207
+ style: challenge.row,
3208
+ children: [/*#__PURE__*/_jsxs(Text, {
3209
+ style: [challenge.rank, {
3210
+ color: theme.accent
3211
+ }],
3212
+ children: ["#", index + 1]
3213
+ }), /*#__PURE__*/_jsx(Text, {
3214
+ style: [challenge.name, {
3215
+ color: theme.titleColor
3216
+ }],
3217
+ children: labelOf(leader)
3218
+ }), /*#__PURE__*/_jsx(Text, {
3219
+ style: [challenge.value, {
3220
+ color: theme.bodyColor
3221
+ }],
3222
+ children: typeof (record(leader).value ?? record(leader).score) === "number" ? String(record(leader).value ?? record(leader).score) : str(record(leader).value ?? record(leader).score)
3223
+ })]
3224
+ }, index))
3225
+ }) : null, reward ? /*#__PURE__*/_jsxs(Text, {
3226
+ style: [challenge.reward, {
3227
+ color: theme.bodyColor
3228
+ }],
3229
+ children: ["Reward: ", reward]
3230
+ }) : null, joined ? /*#__PURE__*/_jsxs(View, {
3231
+ style: [challenge.joined, {
3232
+ borderColor: theme.accent
3233
+ }],
3234
+ accessibilityLiveRegion: "polite",
3235
+ children: [/*#__PURE__*/_jsx(Text, {
3236
+ style: [challenge.joinedText, {
3237
+ color: theme.accent
3238
+ }],
3239
+ children: "Join request sent"
3240
+ }), /*#__PURE__*/_jsx(PrimaryBtn, {
3241
+ label: "DONE",
3242
+ accent: theme.accent,
3243
+ onPress: () => {
3244
+ if (!presentTerminalOutcome(props, "Join request sent")) props.onDismiss();
3245
+ }
3246
+ })]
3247
+ }) : /*#__PURE__*/_jsx(PrimaryBtn, {
3248
+ label: str(e.joinLabel) || spec.cta?.label || "JOIN CHALLENGE",
3249
+ accent: theme.accent,
3250
+ onPress: () => {
3251
+ setJoined(true);
3252
+ props.onInteraction?.({
3253
+ mechanic: "community_challenge",
3254
+ phase: "started"
3255
+ });
3256
+ if (spec.cta && spec.cta.action !== "dismiss") props.onCta(spec.cta);
3257
+ }
3258
+ })]
3259
+ });
3260
+ }
3261
+ const challenge = {
3262
+ hero: {
3263
+ borderRadius: 16,
3264
+ padding: 16,
3265
+ alignItems: "center",
3266
+ marginVertical: 12
3267
+ },
3268
+ emoji: {
3269
+ fontSize: 36
3270
+ },
3271
+ count: {
3272
+ fontSize: 24,
3273
+ fontWeight: "900",
3274
+ marginTop: 3
3275
+ },
3276
+ caption: {
3277
+ fontSize: 11,
3278
+ fontWeight: "600"
3279
+ },
3280
+ board: {
3281
+ marginBottom: 10
3282
+ },
3283
+ row: {
3284
+ flexDirection: "row",
3285
+ alignItems: "center",
3286
+ paddingVertical: 7,
3287
+ gap: 8
3288
+ },
3289
+ rank: {
3290
+ width: 28,
3291
+ fontSize: 12,
3292
+ fontWeight: "800"
3293
+ },
3294
+ name: {
3295
+ flex: 1,
3296
+ fontSize: 13,
3297
+ fontWeight: "600"
3298
+ },
3299
+ value: {
3300
+ fontSize: 12,
3301
+ fontWeight: "700"
3302
+ },
3303
+ reward: {
3304
+ fontSize: 12,
3305
+ fontWeight: "700",
3306
+ textAlign: "center",
3307
+ marginBottom: 10
3308
+ },
3309
+ joined: {
3310
+ borderWidth: 1.5,
3311
+ borderRadius: 12,
3312
+ padding: 12,
3313
+ gap: 8
3314
+ },
3315
+ joinedText: {
3316
+ fontSize: 13,
3317
+ fontWeight: "800",
3318
+ textAlign: "center"
3319
+ }
3320
+ };
3321
+
3322
+ // ─── 11. Coupon Wallet ─────────────────────────────────────────────────────────
3323
+
3324
+ export function CouponWalletInApp(props) {
3325
+ const {
3326
+ spec,
3327
+ theme
3328
+ } = props;
3329
+ const e = ext(props);
3330
+ const coupons = arr(e.coupons).map(coupon => {
3331
+ const value = record(coupon);
3332
+ return {
3333
+ code: str(value.code ?? value.couponCode),
3334
+ label: str(value.label ?? value.title)
3335
+ };
3336
+ }).filter(coupon => coupon.code);
3337
+ const fallbackCode = str(e.couponCode ?? e.referralCode ?? e.code) || str(spec.coupon);
3338
+ const fallbackLabel = str(e.couponLabel ?? e.referralLabel) || "Your exclusive coupon";
3339
+ const entries = coupons.length > 0 ? coupons : fallbackCode ? [{
3340
+ code: fallbackCode,
3341
+ label: fallbackLabel
3342
+ }] : [];
3343
+ return /*#__PURE__*/_jsxs(View, {
3344
+ style: sh.content,
3345
+ children: [/*#__PURE__*/_jsx(Title, {
3346
+ text: spec.title,
3347
+ theme: theme
3348
+ }), /*#__PURE__*/_jsx(Body, {
3349
+ text: spec.body,
3350
+ theme: theme
3351
+ }), entries.map(({
3352
+ code,
3353
+ label
3354
+ }, index) => /*#__PURE__*/_jsxs(View, {
3355
+ style: [cw.chip, {
3356
+ backgroundColor: theme.couponBg,
3357
+ borderColor: theme.couponBorder
3358
+ }],
3359
+ accessibilityLabel: `Coupon code ${code}`,
3360
+ children: [/*#__PURE__*/_jsx(Text, {
3361
+ style: [cw.label, {
3362
+ color: theme.bodyColor
3363
+ }],
3364
+ children: label || fallbackLabel
3365
+ }), /*#__PURE__*/_jsx(Text, {
3366
+ selectable: true,
3367
+ style: [cw.code, {
3368
+ color: theme.couponFg
3369
+ }],
3370
+ children: code
3371
+ }), /*#__PURE__*/_jsx(Text, {
3372
+ style: [cw.tapHint, {
3373
+ color: theme.accent
3374
+ }],
3375
+ children: "Press and hold to select"
3376
+ })]
3377
+ }, `${code}-${index}`)), /*#__PURE__*/_jsx(CtaArea, {
3378
+ ...props
3379
+ })]
3380
+ });
3381
+ }
3382
+ const cw = {
3383
+ chip: {
3384
+ borderRadius: 14,
3385
+ borderWidth: 1.5,
3386
+ borderStyle: "dashed",
3387
+ padding: 18,
3388
+ alignItems: "center",
3389
+ marginVertical: 14
3390
+ },
3391
+ label: {
3392
+ fontSize: 11,
3393
+ fontWeight: "600",
3394
+ marginBottom: 6,
3395
+ textTransform: "uppercase",
3396
+ letterSpacing: 0.5
3397
+ },
3398
+ code: {
3399
+ fontSize: 26,
3400
+ fontWeight: "900",
3401
+ letterSpacing: 3,
3402
+ marginBottom: 6
3403
+ },
3404
+ tapHint: {
3405
+ fontSize: 12,
3406
+ fontWeight: "600"
3407
+ }
3408
+ };
3409
+
3410
+ // ─── QR / NFC / Wallet pass ──────────────────────────────────────────────────
3411
+
3412
+ export function QrNfcInApp(props) {
3413
+ const {
3414
+ spec,
3415
+ theme
3416
+ } = props;
3417
+ const e = ext(props);
3418
+ const qr = str(spec.assets?.qr);
3419
+ const code = firstText(e, ["codeValue", "barcode", "fallbackLink"]);
3420
+ const kind = firstText(e, ["codeType", "passType", "walletProvider"]);
3421
+ const instruction = firstText(e, ["instruction", "passTitle"]);
3422
+ const expiry = str(e.expiry);
3423
+ const wallet = variantOf(props) === "wallet_pass_offer";
3424
+ const walletAsset = str(props.spec.assets?.wallet);
3425
+ return /*#__PURE__*/_jsxs(View, {
3426
+ style: sh.content,
3427
+ children: [/*#__PURE__*/_jsx(Title, {
3428
+ text: spec.title || str(e.passTitle),
3429
+ theme: theme
3430
+ }), /*#__PURE__*/_jsx(Body, {
3431
+ text: instruction || spec.body,
3432
+ theme: theme
3433
+ }), wallet ? /*#__PURE__*/_jsxs(View, {
3434
+ style: [qrn.pass, {
3435
+ backgroundColor: theme.couponBg,
3436
+ borderColor: theme.couponBorder
3437
+ }],
3438
+ children: [/*#__PURE__*/_jsx(Text, {
3439
+ style: qrn.passIcon,
3440
+ children: "\u25A4"
3441
+ }), /*#__PURE__*/_jsxs(View, {
3442
+ style: {
3443
+ flex: 1
3444
+ },
3445
+ children: [/*#__PURE__*/_jsx(Text, {
3446
+ style: [qrn.passTitle, {
3447
+ color: theme.titleColor
3448
+ }],
3449
+ children: str(e.passTitle) || spec.title || "Wallet pass"
3450
+ }), /*#__PURE__*/_jsx(Text, {
3451
+ style: [qrn.kind, {
3452
+ color: theme.bodyColor
3453
+ }],
3454
+ children: kind || "Mobile wallet"
3455
+ })]
3456
+ }), walletAsset ? /*#__PURE__*/_jsx(Text, {
3457
+ style: [qrn.ready, {
3458
+ color: theme.accent
3459
+ }],
3460
+ children: "Ready"
3461
+ }) : null]
3462
+ }) : null, qr ? /*#__PURE__*/_jsx(Image, {
3463
+ source: {
3464
+ uri: qr
3465
+ },
3466
+ resizeMode: "contain",
3467
+ style: qrn.image,
3468
+ accessibilityLabel: "Campaign QR code"
3469
+ }) : code ? /*#__PURE__*/_jsxs(View, {
3470
+ style: [qrn.code, {
3471
+ backgroundColor: theme.couponBg,
3472
+ borderColor: theme.couponBorder
3473
+ }],
3474
+ children: [kind ? /*#__PURE__*/_jsx(Text, {
3475
+ style: [qrn.kind, {
3476
+ color: theme.bodyColor
3477
+ }],
3478
+ children: kind
3479
+ }) : null, /*#__PURE__*/_jsx(Text, {
3480
+ selectable: true,
3481
+ style: [qrn.value, {
3482
+ color: theme.couponFg
3483
+ }],
3484
+ children: code
3485
+ })]
3486
+ }) : wallet && walletAsset ? null : /*#__PURE__*/_jsx(View, {
3487
+ style: [qrn.notice, {
3488
+ backgroundColor: theme.couponBg
3489
+ }],
3490
+ children: /*#__PURE__*/_jsx(Text, {
3491
+ style: [qrn.noticeText, {
3492
+ color: theme.bodyColor
3493
+ }],
3494
+ children: "No scannable code was provided. Use the campaign action to continue."
3495
+ })
3496
+ }), expiry ? /*#__PURE__*/_jsxs(Text, {
3497
+ style: [qrn.expiry, {
3498
+ color: theme.secondaryColor
3499
+ }],
3500
+ children: ["Expires ", expiry]
3501
+ }) : null, /*#__PURE__*/_jsx(CtaArea, {
3502
+ ...props,
3503
+ beforePrimary: () => wallet ? props.onInteraction?.({
3504
+ mechanic: "wallet_pass_offer",
3505
+ phase: "started",
3506
+ value: walletAsset || code || "host_action",
3507
+ meta: {
3508
+ hostAction: "add_to_wallet",
3509
+ wallet: walletAsset || undefined
3510
+ }
3511
+ }) : props.onInteraction?.({
3512
+ mechanic: "nfc_qr_prompt",
3513
+ phase: "started",
3514
+ value: str(props.spec.assets?.nfc) || code || "host_action"
3515
+ })
3516
+ })]
3517
+ });
3518
+ }
3519
+ const qrn = {
3520
+ image: {
3521
+ alignSelf: "center",
3522
+ aspectRatio: 1,
3523
+ marginTop: 12,
3524
+ maxWidth: 184,
3525
+ width: "100%"
3526
+ },
3527
+ code: {
3528
+ borderWidth: 1,
3529
+ borderRadius: 12,
3530
+ padding: 14,
3531
+ marginTop: 12,
3532
+ alignItems: "center"
3533
+ },
3534
+ kind: {
3535
+ fontSize: 10,
3536
+ fontWeight: "700",
3537
+ textTransform: "uppercase"
3538
+ },
3539
+ value: {
3540
+ fontSize: 15,
3541
+ lineHeight: 21,
3542
+ fontWeight: "700",
3543
+ textAlign: "center",
3544
+ marginTop: 4
3545
+ },
3546
+ notice: {
3547
+ borderRadius: 12,
3548
+ padding: 12,
3549
+ marginTop: 12
3550
+ },
3551
+ noticeText: {
3552
+ fontSize: 12,
3553
+ lineHeight: 18,
3554
+ textAlign: "center"
3555
+ },
3556
+ expiry: {
3557
+ fontSize: 11,
3558
+ textAlign: "center",
3559
+ marginTop: 8
3560
+ },
3561
+ pass: {
3562
+ flexDirection: "row",
3563
+ alignItems: "center",
3564
+ gap: 10,
3565
+ borderWidth: 1,
3566
+ borderRadius: 14,
3567
+ padding: 14,
3568
+ marginTop: 12
3569
+ },
3570
+ passIcon: {
3571
+ fontSize: 30
3572
+ },
3573
+ passTitle: {
3574
+ fontSize: 14,
3575
+ fontWeight: "800"
3576
+ },
3577
+ ready: {
3578
+ fontSize: 11,
3579
+ fontWeight: "800"
3580
+ }
3581
+ };
3582
+
3583
+ // ─── Video commerce ──────────────────────────────────────────────────────────
3584
+
3585
+ export function VideoCommerceInApp(props) {
3586
+ const {
3587
+ spec,
3588
+ theme,
3589
+ onCta,
3590
+ onDismiss
3591
+ } = props;
3592
+ const e = ext(props);
3593
+ const products = arr(e.products ?? spec.assets?.products);
3594
+ const firstProduct = record(products[0]);
3595
+ const firstProductImage = arr(e.productImages)[0] ?? firstProduct.image ?? firstProduct.imageUrl;
3596
+ const poster = str(spec.assets?.image) || str(spec.imageUrl) || mediaUri(e.posterImage ?? firstProductImage);
3597
+ const video = str(spec.assets?.video) || str(e.video);
3598
+ const mediaHeight = resolveInAppMediaHeight(spec, 180, 360);
3599
+ const product = firstText(e, ["productTitle", "itemName"]) || labelOf(firstProduct);
3600
+ const price = str(e.price) || str(firstProduct.price);
3601
+ const discount = str(e.discount) || str(firstProduct.discount);
3602
+ const act = () => {
3603
+ if (!spec.cta) return;
3604
+ spec.cta.action === "dismiss" ? onDismiss() : onCta(spec.cta);
3605
+ };
3606
+ return /*#__PURE__*/_jsxs(View, {
3607
+ style: sh.content,
3608
+ children: [/*#__PURE__*/_jsx(Title, {
3609
+ text: spec.title,
3610
+ theme: theme
3611
+ }), /*#__PURE__*/_jsx(Body, {
3612
+ text: spec.body,
3613
+ theme: theme
3614
+ }), /*#__PURE__*/_jsxs(View, {
3615
+ style: [vid.poster, {
3616
+ backgroundColor: theme.couponBg
3617
+ }],
3618
+ children: [poster ? /*#__PURE__*/_jsx(Image, {
3619
+ source: {
3620
+ uri: poster
3621
+ },
3622
+ resizeMode: "cover",
3623
+ style: [vid.image, mediaHeight === undefined ? null : {
3624
+ height: mediaHeight
3625
+ }],
3626
+ accessibilityLabel: product || spec.title || "Product video"
3627
+ }) : null, video ? /*#__PURE__*/_jsxs(Pressable, {
3628
+ style: [vid.play, {
3629
+ backgroundColor: theme.cardBg
3630
+ }],
3631
+ disabled: !spec.cta,
3632
+ onPress: act,
3633
+ accessibilityRole: "button",
3634
+ accessibilityLabel: "Play video through campaign action",
3635
+ children: [/*#__PURE__*/_jsx(Text, {
3636
+ style: [vid.playIcon, {
3637
+ color: theme.accent
3638
+ }],
3639
+ children: "\u25B6"
3640
+ }), /*#__PURE__*/_jsx(Text, {
3641
+ style: [vid.playLabel, {
3642
+ color: theme.titleColor
3643
+ }],
3644
+ children: str(e.playLabel) || "Play video"
3645
+ })]
3646
+ }) : null]
3647
+ }), product || price || discount ? /*#__PURE__*/_jsxs(View, {
3648
+ style: vid.product,
3649
+ children: [/*#__PURE__*/_jsxs(View, {
3650
+ style: vid.productCopy,
3651
+ children: [product ? /*#__PURE__*/_jsx(Text, {
3652
+ style: [vid.productTitle, {
3653
+ color: theme.titleColor
3654
+ }],
3655
+ children: product
3656
+ }) : null, discount ? /*#__PURE__*/_jsx(Text, {
3657
+ style: [vid.discount, {
3658
+ color: theme.accent
3659
+ }],
3660
+ children: discount
3661
+ }) : null]
3662
+ }), price ? /*#__PURE__*/_jsx(Text, {
3663
+ style: [vid.price, {
3664
+ color: theme.titleColor
3665
+ }],
3666
+ children: price
3667
+ }) : null]
3668
+ }) : null, /*#__PURE__*/_jsx(CtaArea, {
3669
+ ...props
3670
+ })]
3671
+ });
3672
+ }
3673
+ const vid = {
3674
+ poster: {
3675
+ minHeight: 150,
3676
+ borderRadius: 14,
3677
+ marginTop: 10,
3678
+ overflow: "hidden",
3679
+ alignItems: "center",
3680
+ justifyContent: "center"
3681
+ },
3682
+ image: {
3683
+ width: "100%",
3684
+ height: 180
3685
+ },
3686
+ play: {
3687
+ position: "absolute",
3688
+ flexDirection: "row",
3689
+ alignItems: "center",
3690
+ gap: 7,
3691
+ borderRadius: 999,
3692
+ maxWidth: "90%",
3693
+ paddingVertical: 9,
3694
+ paddingHorizontal: 13
3695
+ },
3696
+ playIcon: {
3697
+ fontSize: 15
3698
+ },
3699
+ playLabel: {
3700
+ flexShrink: 1,
3701
+ fontSize: 12,
3702
+ fontWeight: "700",
3703
+ textAlign: "center"
3704
+ },
3705
+ product: {
3706
+ flexDirection: "row",
3707
+ alignItems: "center",
3708
+ marginTop: 10,
3709
+ gap: 10
3710
+ },
3711
+ productCopy: {
3712
+ flex: 1
3713
+ },
3714
+ productTitle: {
3715
+ fontSize: 14,
3716
+ fontWeight: "700"
3717
+ },
3718
+ discount: {
3719
+ fontSize: 11,
3720
+ fontWeight: "700",
3721
+ marginTop: 2
3722
+ },
3723
+ price: {
3724
+ fontSize: 15,
3725
+ fontWeight: "800"
3726
+ }
3727
+ };
3728
+
3729
+ // ─── Celebration ─────────────────────────────────────────────────────────────
3730
+
3731
+ const BURST_PARTICLES = ["●", "✦", "●", "◆", "●", "✦", "●", "◆"];
3732
+ export function CelebrationInApp(props) {
3733
+ const {
3734
+ spec,
3735
+ theme
3736
+ } = props;
3737
+ const e = ext(props);
3738
+ const burst = useRef(new Animated.Value(0)).current;
3739
+ const reducedMotion = useReducedMotion();
3740
+ const title = spec.title || firstText(e, ["celebrationTitle", "milestoneName"]);
3741
+ const message = firstText(e, ["celebrationMessage", "message"]) || spec.body;
3742
+ // Reward copy is display-only; the renderer never decides or grants a reward.
3743
+ const reward = labelOf(e.reward);
3744
+ const confettiEnabled = e.confetti !== false && e.confettiAnimation !== false;
3745
+ useEffect(() => {
3746
+ if (reducedMotion !== null) {
3747
+ burst.stopAnimation();
3748
+ burst.setValue(0);
3749
+ if (!reducedMotion && confettiEnabled) {
3750
+ Animated.timing(burst, {
3751
+ toValue: 1,
3752
+ duration: 750,
3753
+ easing: Easing.out(Easing.cubic),
3754
+ useNativeDriver: true
3755
+ }).start();
3756
+ }
3757
+ }
3758
+ return () => burst.stopAnimation();
3759
+ }, [burst, confettiEnabled, reducedMotion]);
3760
+ return /*#__PURE__*/_jsxs(View, {
3761
+ style: sh.content,
3762
+ children: [/*#__PURE__*/_jsxs(View, {
3763
+ style: cel.stage,
3764
+ children: [confettiEnabled ? BURST_PARTICLES.map((particle, index) => {
3765
+ const angle = index / BURST_PARTICLES.length * Math.PI * 2;
3766
+ const distance = index % 2 === 0 ? 54 : 70;
3767
+ const translateX = burst.interpolate({
3768
+ inputRange: [0, 1],
3769
+ outputRange: [0, Math.cos(angle) * distance]
3770
+ });
3771
+ const translateY = burst.interpolate({
3772
+ inputRange: [0, 1],
3773
+ outputRange: [0, Math.sin(angle) * distance]
3774
+ });
3775
+ const opacity = burst.interpolate({
3776
+ inputRange: [0, 0.25, 1],
3777
+ outputRange: [0, 1, 0.35]
3778
+ });
3779
+ return /*#__PURE__*/_jsx(Animated.Text, {
3780
+ style: [cel.particle, {
3781
+ color: index % 2 === 0 ? theme.accent : "#F59E0B",
3782
+ opacity,
3783
+ transform: [{
3784
+ translateX
3785
+ }, {
3786
+ translateY
3787
+ }]
3788
+ }],
3789
+ children: particle
3790
+ }, index);
3791
+ }) : null, /*#__PURE__*/_jsx(Text, {
3792
+ style: cel.emoji,
3793
+ children: str(e.emoji) || "🎉"
3794
+ })]
3795
+ }), /*#__PURE__*/_jsx(Title, {
3796
+ text: title,
3797
+ theme: theme
3798
+ }), /*#__PURE__*/_jsx(Body, {
3799
+ text: message,
3800
+ theme: theme
3801
+ }), reward ? /*#__PURE__*/_jsx(View, {
3802
+ style: [cel.reward, {
3803
+ backgroundColor: theme.couponBg
3804
+ }],
3805
+ children: /*#__PURE__*/_jsxs(Text, {
3806
+ style: [cel.rewardText, {
3807
+ color: theme.couponFg
3808
+ }],
3809
+ children: ["Reward: ", reward]
3810
+ })
3811
+ }) : null, /*#__PURE__*/_jsx(CtaArea, {
3812
+ ...props
3813
+ })]
3814
+ });
3815
+ }
3816
+ const cel = {
3817
+ stage: {
3818
+ height: 150,
3819
+ overflow: "hidden",
3820
+ alignItems: "center",
3821
+ justifyContent: "center"
3822
+ },
3823
+ particle: {
3824
+ position: "absolute",
3825
+ fontSize: 15,
3826
+ fontWeight: "900"
3827
+ },
3828
+ emoji: {
3829
+ fontSize: 52
3830
+ },
3831
+ reward: {
3832
+ borderRadius: 10,
3833
+ padding: 10,
3834
+ marginTop: 10
3835
+ },
3836
+ rewardText: {
3837
+ fontSize: 13,
3838
+ fontWeight: "700",
3839
+ textAlign: "center"
3840
+ }
3841
+ };
3842
+ function FeedbackCaptureInApp(props) {
3843
+ const {
3844
+ spec,
3845
+ theme
3846
+ } = props;
3847
+ const e = ext(props);
3848
+ const [feedback, setFeedback] = useState("");
3849
+ const prompt = str(e.promptText) || spec.body;
3850
+ return /*#__PURE__*/_jsxs(View, {
3851
+ style: sh.content,
3852
+ children: [/*#__PURE__*/_jsx(Title, {
3853
+ text: spec.title,
3854
+ theme: theme
3855
+ }), /*#__PURE__*/_jsx(Body, {
3856
+ text: prompt,
3857
+ theme: theme
3858
+ }), /*#__PURE__*/_jsx(TextInput, {
3859
+ value: feedback,
3860
+ onChangeText: setFeedback,
3861
+ multiline: true,
3862
+ textAlignVertical: "top",
3863
+ placeholder: str(e.placeholder) || "Type your feedback",
3864
+ placeholderTextColor: theme.secondaryColor,
3865
+ accessibilityLabel: prompt || "Feedback",
3866
+ style: [fb.input, {
3867
+ color: theme.titleColor,
3868
+ borderColor: theme.couponBorder,
3869
+ backgroundColor: theme.cardBg
3870
+ }]
3871
+ }), feedback.trim() ? /*#__PURE__*/_jsx(CtaArea, {
3872
+ ...props,
3873
+ beforePrimary: () => props.onInteraction?.({
3874
+ mechanic: "feedback_capture",
3875
+ phase: "resolved",
3876
+ result: feedback.trim(),
3877
+ meta: {
3878
+ feedback: feedback.trim()
3879
+ }
3880
+ })
3881
+ }) : null]
3882
+ });
3883
+ }
3884
+ const fb = {
3885
+ input: {
3886
+ minHeight: 104,
3887
+ borderWidth: 1,
3888
+ borderRadius: 12,
3889
+ padding: 12,
3890
+ marginTop: 10,
3891
+ fontSize: 14,
3892
+ lineHeight: 20
3893
+ }
3894
+ };
3895
+
3896
+ /** One question-family entry point; payload shape selects the smallest native control. */
3897
+ export function QuestionFlowInApp(props) {
3898
+ const e = ext(props);
3899
+ const variantKey = variantOf(props);
3900
+ if (variantKey === "feedback_capture") {
3901
+ return /*#__PURE__*/_jsx(FeedbackCaptureInApp, {
3902
+ ...props
3903
+ });
3904
+ }
3905
+ if (variantKey === "emoji_reaction_bar" || arr(e.reactions).length > 0) {
3906
+ return /*#__PURE__*/_jsx(EmojiReactionBarInApp, {
3907
+ ...props
3908
+ });
3909
+ }
3910
+ if (variantKey === "rating_request" || typeof e.ratingScale === "number") {
3911
+ return /*#__PURE__*/_jsx(StarRatingInApp, {
3912
+ ...props
3913
+ });
3914
+ }
3915
+ if (variantKey === "nps_prompt" || typeof e.scaleMax === "number" || arr(e.scaleLabels).length > 0) {
3916
+ return /*#__PURE__*/_jsx(NpsPromptInApp, {
3917
+ ...props
3918
+ });
3919
+ }
3920
+ return /*#__PURE__*/_jsx(PollCardInApp, {
3921
+ ...props
3922
+ });
3923
+ }
3924
+ function GuidedStepsInApp(props) {
3925
+ const e = ext(props);
3926
+ const raw = arr(e.setupSteps ?? e.steps).slice(0, 12);
3927
+ const steps = raw.length > 0 ? raw : [{
3928
+ title: props.spec.title,
3929
+ body: props.spec.body
3930
+ }];
3931
+ const [index, setIndex] = useState(0);
3932
+ const [validated, setValidated] = useState([]);
3933
+ const step = record(steps[index]);
3934
+ const title = firstText(step, ["title", "label", "heading"]) || props.spec.title;
3935
+ const body = firstText(step, ["body", "message", "description"]) || props.spec.body;
3936
+ const image = mediaUri(step.image ?? step.imageUrl ?? (index === 0 ? props.spec.assets?.image : ""));
3937
+ const mediaHeight = resolveInAppMediaHeight(props.spec, 180, 360);
3938
+ const last = index >= steps.length - 1;
3939
+ const variant = variantOf(props);
3940
+ const validationRules = arr(e.stepValidation);
3941
+ const validation = step.validation ?? validationRules[index] ?? (validationRules.length ? undefined : e.stepValidation);
3942
+ const validationRequired = variant === "guided_setup_wizard" && Boolean(validation);
3943
+ const stepValidated = !validationRequired || validated.includes(index);
3944
+ const advance = () => {
3945
+ if (!stepValidated) return;
3946
+ if (!last) {
3947
+ const next = index + 1;
3948
+ setIndex(next);
3949
+ props.onInteraction?.({
3950
+ mechanic: variant,
3951
+ phase: "progress",
3952
+ progress: next / steps.length,
3953
+ value: next + 1
3954
+ });
3955
+ return;
3956
+ }
3957
+ props.onInteraction?.({
3958
+ mechanic: variant,
3959
+ phase: "completed",
3960
+ progress: 1
3961
+ });
3962
+ if (presentTerminalOutcome(props)) return;
3963
+ if (!props.spec.cta || props.spec.cta.action === "dismiss") props.onDismiss();else props.onCta(props.spec.cta);
3964
+ };
3965
+ return /*#__PURE__*/_jsxs(View, {
3966
+ style: sh.content,
3967
+ children: [/*#__PURE__*/_jsx(View, {
3968
+ style: guide.progress,
3969
+ accessibilityRole: "progressbar",
3970
+ accessibilityValue: {
3971
+ min: 1,
3972
+ max: steps.length,
3973
+ now: index + 1
3974
+ },
3975
+ children: steps.map((_, stepIndex) => /*#__PURE__*/_jsx(View, {
3976
+ style: [guide.step, {
3977
+ backgroundColor: stepIndex <= index ? props.theme.accent : props.theme.couponBg
3978
+ }]
3979
+ }, stepIndex))
3980
+ }), image ? /*#__PURE__*/_jsx(Image, {
3981
+ source: {
3982
+ uri: image
3983
+ },
3984
+ resizeMode: "cover",
3985
+ style: [guide.image, mediaHeight === undefined ? null : {
3986
+ height: mediaHeight
3987
+ }]
3988
+ }) : /*#__PURE__*/_jsx(View, {
3989
+ style: [guide.illustration, {
3990
+ backgroundColor: props.theme.couponBg
3991
+ }],
3992
+ children: /*#__PURE__*/_jsx(Text, {
3993
+ style: guide.emoji,
3994
+ children: variant === "guided_setup_wizard" ? "⚙️" : "👋"
3995
+ })
3996
+ }), /*#__PURE__*/_jsx(Title, {
3997
+ text: title,
3998
+ theme: props.theme
3999
+ }), /*#__PURE__*/_jsx(Body, {
4000
+ text: body,
4001
+ theme: props.theme
4002
+ }), validationRequired ? /*#__PURE__*/_jsxs(Pressable, {
4003
+ accessibilityRole: "checkbox",
4004
+ accessibilityState: {
4005
+ checked: stepValidated
4006
+ },
4007
+ onPress: () => {
4008
+ setValidated(current => current.includes(index) ? current.filter(value => value !== index) : [...current, index]);
4009
+ props.onInteraction?.({
4010
+ mechanic: variant,
4011
+ phase: "decision",
4012
+ value: index + 1,
4013
+ meta: {
4014
+ validation
4015
+ }
4016
+ });
4017
+ },
4018
+ style: [guide.validation, {
4019
+ borderColor: props.theme.couponBorder
4020
+ }],
4021
+ children: [/*#__PURE__*/_jsx(View, {
4022
+ style: [guide.validationCheck, {
4023
+ borderColor: stepValidated ? props.theme.accent : props.theme.couponBorder,
4024
+ backgroundColor: stepValidated ? props.theme.accent : props.theme.cardBg
4025
+ }],
4026
+ children: stepValidated ? /*#__PURE__*/_jsx(Text, {
4027
+ style: guide.validationTick,
4028
+ children: "\u2713"
4029
+ }) : null
4030
+ }), /*#__PURE__*/_jsx(Text, {
4031
+ style: [guide.validationLabel, {
4032
+ color: props.theme.titleColor
4033
+ }],
4034
+ children: labelOf(validation) || "Mark this step complete"
4035
+ })]
4036
+ }) : null, /*#__PURE__*/_jsxs(View, {
4037
+ style: guide.actions,
4038
+ children: [index > 0 ? /*#__PURE__*/_jsx(SecondaryBtn, {
4039
+ label: "Back",
4040
+ color: props.theme.secondaryColor,
4041
+ onPress: () => setIndex(value => Math.max(0, value - 1))
4042
+ }) : props.spec.secondaryCta ? /*#__PURE__*/_jsx(SecondaryBtn, {
4043
+ label: props.spec.secondaryCta.label,
4044
+ color: props.theme.secondaryColor,
4045
+ onPress: props.onDismiss
4046
+ }) : null, /*#__PURE__*/_jsx(PrimaryBtn, {
4047
+ label: last ? props.spec.cta?.label || "Finish" : "Next",
4048
+ accent: props.theme.accent,
4049
+ onPress: advance,
4050
+ disabled: !stepValidated
4051
+ })]
4052
+ })]
4053
+ });
4054
+ }
4055
+ const guide = {
4056
+ progress: {
4057
+ flexDirection: "row",
4058
+ gap: 5,
4059
+ marginBottom: 14
4060
+ },
4061
+ step: {
4062
+ flex: 1,
4063
+ height: 5,
4064
+ borderRadius: 3
4065
+ },
4066
+ image: {
4067
+ width: "100%",
4068
+ height: 180,
4069
+ borderRadius: 16,
4070
+ marginBottom: 14
4071
+ },
4072
+ illustration: {
4073
+ height: 150,
4074
+ borderRadius: 16,
4075
+ alignItems: "center",
4076
+ justifyContent: "center",
4077
+ marginBottom: 14
4078
+ },
4079
+ emoji: {
4080
+ fontSize: 52
4081
+ },
4082
+ actions: {
4083
+ flexDirection: "row",
4084
+ flexWrap: "wrap",
4085
+ gap: 6,
4086
+ alignItems: "center",
4087
+ marginTop: 14
4088
+ },
4089
+ validation: {
4090
+ flexDirection: "row",
4091
+ alignItems: "center",
4092
+ gap: 10,
4093
+ borderWidth: 1,
4094
+ borderRadius: 10,
4095
+ padding: 11,
4096
+ marginTop: 10
4097
+ },
4098
+ validationCheck: {
4099
+ width: 22,
4100
+ height: 22,
4101
+ borderRadius: 6,
4102
+ borderWidth: 1.5,
4103
+ alignItems: "center",
4104
+ justifyContent: "center"
4105
+ },
4106
+ validationTick: {
4107
+ color: "#FFFFFF",
4108
+ fontSize: 12,
4109
+ fontWeight: "900"
4110
+ },
4111
+ validationLabel: {
4112
+ flex: 1,
4113
+ fontSize: 13,
4114
+ fontWeight: "600"
4115
+ }
4116
+ };
4117
+
4118
+ /** Progress-family entry point shared by streak and task-list payloads. */
4119
+ export function ChecklistProgressFamilyInApp(props) {
4120
+ const e = ext(props);
4121
+ const variantKey = variantOf(props);
4122
+ if (variantKey === "first_session_onboarding" || variantKey === "guided_setup_wizard") {
4123
+ return /*#__PURE__*/_jsx(GuidedStepsInApp, {
4124
+ ...props
4125
+ });
4126
+ }
4127
+ if (variantKey === "community_challenge") {
4128
+ return /*#__PURE__*/_jsx(CommunityChallengeInApp, {
4129
+ ...props
4130
+ });
4131
+ }
4132
+ if (variantKey === "streak_reward" || variantKey === "daily_check_in" || typeof e.filled === "number" || typeof e.total === "number") {
4133
+ return /*#__PURE__*/_jsx(StreakRewardInApp, {
4134
+ ...props
4135
+ });
4136
+ }
4137
+ if (variantKey === "progress_unlock" || variantKey === "goal_tracker") {
4138
+ return /*#__PURE__*/_jsx(ProgressSummaryInApp, {
4139
+ ...props
4140
+ });
4141
+ }
4142
+ return /*#__PURE__*/_jsx(ChecklistProgressInApp, {
4143
+ ...props
4144
+ });
4145
+ }
4146
+
4147
+ // ─── Shared content styles ────────────────────────────────────────────────────
4148
+
4149
+ const sh = {
4150
+ content: {
4151
+ width: "100%"
4152
+ },
4153
+ title: {
4154
+ fontSize: 18,
4155
+ fontWeight: "800",
4156
+ marginBottom: 6,
4157
+ marginTop: 4,
4158
+ paddingRight: 28,
4159
+ lineHeight: 24
4160
+ },
4161
+ body: {
4162
+ fontSize: 14,
4163
+ lineHeight: 20,
4164
+ marginBottom: 4
4165
+ },
4166
+ ctaRow: {
4167
+ flexDirection: "row",
4168
+ flexWrap: "wrap",
4169
+ justifyContent: "flex-end",
4170
+ alignItems: "center",
4171
+ marginTop: 14,
4172
+ gap: 8
4173
+ },
4174
+ primaryBtn: {
4175
+ paddingVertical: 11,
4176
+ paddingHorizontal: 22,
4177
+ borderRadius: 12,
4178
+ flexBasis: 140,
4179
+ flexGrow: 1,
4180
+ flexShrink: 1,
4181
+ maxWidth: "100%"
4182
+ },
4183
+ primaryText: {
4184
+ fontSize: 15,
4185
+ color: "#fff",
4186
+ fontWeight: "700",
4187
+ flexShrink: 1,
4188
+ textAlign: "center"
4189
+ },
4190
+ secondaryBtn: {
4191
+ flexShrink: 1,
4192
+ maxWidth: "100%",
4193
+ paddingVertical: 11,
4194
+ paddingHorizontal: 14
4195
+ },
4196
+ secondaryText: {
4197
+ flexShrink: 1,
4198
+ fontSize: 14,
4199
+ fontWeight: "500",
4200
+ textAlign: "center"
4201
+ }
4202
+ };
4203
+
4204
+ // ─── Archetype registry ────────────────────────────────────────────────────────
4205
+
4206
+ /** Canonical mechanic families. Null means the container should render the payload. */
4207
+ export const ARCHETYPE_CONTENT_RENDERERS = {
4208
+ spinWheel: SpinWheelInApp,
4209
+ scratchReveal: ScratchCardInApp,
4210
+ slotReward: SlotRewardInApp,
4211
+ carouselDeck: CarouselDeckInApp,
4212
+ countdown: CountdownInApp,
4213
+ questionFlow: QuestionFlowInApp,
4214
+ checklistProgress: ChecklistProgressFamilyInApp,
4215
+ qrNfc: QrNfcInApp,
4216
+ videoCommerce: VideoCommerceInApp,
4217
+ celebration: CelebrationInApp
4218
+ };
4219
+
4220
+ /** Resolve a canonical family key without silently inventing missing mechanics. */
4221
+ export function resolveArchetypeContent(archetype) {
4222
+ if (!archetype || !Object.prototype.hasOwnProperty.call(ARCHETYPE_CONTENT_RENDERERS, archetype)) return null;
4223
+ return ARCHETYPE_CONTENT_RENDERERS[archetype];
4224
+ }
4225
+ export const ARCHETYPE_RENDERERS = {
4226
+ // Gamified
4227
+ spin_wheel: SpinWheelInApp,
4228
+ scratch_card: ScratchCardInApp,
4229
+ slot_reward: SlotRewardInApp,
4230
+ carousel_story: CarouselDeckInApp,
4231
+ swipe_deck: CarouselDeckInApp,
4232
+ countdown_offer: CountdownInApp,
4233
+ trial_conversion: CountdownInApp,
4234
+ // Feedback / questions
4235
+ poll_card: PollCardInApp,
4236
+ survey_sheet: QuestionFlowInApp,
4237
+ quiz_card: QuestionFlowInApp,
4238
+ product_finder: QuestionFlowInApp,
4239
+ preferences_picker: QuestionFlowInApp,
4240
+ feedback_capture: QuestionFlowInApp,
4241
+ nps_prompt: NpsPromptInApp,
4242
+ emoji_reaction_bar: EmojiReactionBarInApp,
4243
+ rating_request: StarRatingInApp,
4244
+ app_review_ask: StarRatingInApp,
4245
+ // Engagement / lifecycle
4246
+ streak_reward: StreakRewardInApp,
4247
+ daily_check_in: StreakRewardInApp,
4248
+ interactive_checklist: ChecklistProgressInApp,
4249
+ guided_setup_wizard: ChecklistProgressFamilyInApp,
4250
+ checklist_progress: ChecklistProgressInApp,
4251
+ progress_unlock: ChecklistProgressFamilyInApp,
4252
+ goal_tracker: ChecklistProgressFamilyInApp,
4253
+ first_session_onboarding: ChecklistProgressFamilyInApp,
4254
+ community_challenge: ChecklistProgressFamilyInApp,
4255
+ // Commerce / offers
4256
+ coupon_wallet: CouponWalletInApp,
4257
+ exit_intent_rescue: CouponWalletInApp,
4258
+ nfc_qr_prompt: QrNfcInApp,
4259
+ wallet_pass_offer: QrNfcInApp,
4260
+ video_commerce_card: VideoCommerceInApp,
4261
+ milestone_celebration: CelebrationInApp,
4262
+ celebration_confetti_full_screen: CelebrationInApp
4263
+ };
4264
+ //# sourceMappingURL=EngageArchetypeRenderers.js.map