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