@encorekit/web-sdk 0.1.1 → 0.1.7

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 (134) hide show
  1. package/README.md +94 -9
  2. package/dist/cjs/index.cjs +1 -1
  3. package/dist/cjs/index.cjs.map +1 -1
  4. package/dist/esm/index.js +1 -1
  5. package/dist/esm/index.js.map +1 -1
  6. package/dist/umd/encore.min.js +1 -1
  7. package/dist/umd/encore.min.js.map +1 -1
  8. package/embed/README.md +409 -0
  9. package/embed/index.html +57 -0
  10. package/embed/styles.css +154 -0
  11. package/examples/README.md +149 -0
  12. package/examples/angular/README.md +210 -0
  13. package/examples/angular/angular.json +73 -0
  14. package/examples/angular/package.json +32 -0
  15. package/examples/angular/src/app/app.component.html +56 -0
  16. package/examples/angular/src/app/app.component.ts +114 -0
  17. package/examples/angular/src/app/encore.service.ts +83 -0
  18. package/examples/angular/src/index.html +13 -0
  19. package/examples/angular/src/main.ts +7 -0
  20. package/examples/angular/src/styles.css +225 -0
  21. package/examples/angular/tsconfig.json +33 -0
  22. package/examples/ios-webview/EncoreURLBuilder.swift +87 -0
  23. package/examples/ios-webview/EncoreWebViewBridge.swift +426 -0
  24. package/examples/ios-webview/ExampleViewController.swift +233 -0
  25. package/examples/ios-webview/README.md +416 -0
  26. package/examples/ios-webview/SimpleEncoreView.swift +94 -0
  27. package/examples/ios-webview/SimpleExample.swift +131 -0
  28. package/examples/react/README.md +186 -0
  29. package/examples/react/index.html +13 -0
  30. package/examples/react/package.json +24 -0
  31. package/examples/react/src/App.tsx +173 -0
  32. package/examples/react/src/index.css +227 -0
  33. package/examples/react/src/main.tsx +11 -0
  34. package/examples/react/src/vite-env.d.ts +2 -0
  35. package/examples/react/tsconfig.json +25 -0
  36. package/examples/react/vite.config.ts +8 -0
  37. package/examples/svelte/README.md +233 -0
  38. package/examples/svelte/index.html +13 -0
  39. package/examples/svelte/package.json +25 -0
  40. package/examples/svelte/src/App.svelte +164 -0
  41. package/examples/svelte/src/app.css +224 -0
  42. package/examples/svelte/src/main.ts +9 -0
  43. package/examples/svelte/src/vite-env.d.ts +3 -0
  44. package/examples/svelte/svelte.config.js +8 -0
  45. package/examples/svelte/tsconfig.json +16 -0
  46. package/examples/svelte/tsconfig.node.json +11 -0
  47. package/examples/svelte/vite.config.ts +8 -0
  48. package/examples/vanilla-js/README.md +271 -0
  49. package/examples/vanilla-js/index.html +421 -0
  50. package/examples/vue/README.md +212 -0
  51. package/examples/vue/index.html +13 -0
  52. package/examples/vue/package.json +22 -0
  53. package/examples/vue/src/App.vue +170 -0
  54. package/examples/vue/src/main.ts +6 -0
  55. package/examples/vue/src/style.css +224 -0
  56. package/examples/vue/src/vite-env.d.ts +2 -0
  57. package/examples/vue/tsconfig.json +25 -0
  58. package/examples/vue/vite.config.ts +8 -0
  59. package/package.json +22 -3
  60. package/types/analytics/AnalyticsClient.d.ts +14 -0
  61. package/types/analytics/AnalyticsClient.d.ts.map +1 -0
  62. package/types/analytics/events.d.ts +63 -0
  63. package/types/analytics/events.d.ts.map +1 -0
  64. package/types/analytics/models.d.ts +17 -0
  65. package/types/analytics/models.d.ts.map +1 -0
  66. package/types/api/APIClient.d.ts +44 -8
  67. package/types/api/APIClient.d.ts.map +1 -1
  68. package/types/api/endpoints.d.ts +11 -7
  69. package/types/api/endpoints.d.ts.map +1 -1
  70. package/types/api/models.d.ts +134 -68
  71. package/types/api/models.d.ts.map +1 -1
  72. package/types/core/Configuration.d.ts +4 -0
  73. package/types/core/Configuration.d.ts.map +1 -1
  74. package/types/core/Encore.d.ts +16 -12
  75. package/types/core/Encore.d.ts.map +1 -1
  76. package/types/core/EntitlementManager.d.ts +9 -0
  77. package/types/core/EntitlementManager.d.ts.map +1 -1
  78. package/types/core/OfferManager.d.ts +27 -7
  79. package/types/core/OfferManager.d.ts.map +1 -1
  80. package/types/types.d.ts +1 -1
  81. package/types/types.d.ts.map +1 -1
  82. package/types/ui/OfferCard.d.ts.map +1 -1
  83. package/types/ui/OfferCarousel.d.ts.map +1 -1
  84. package/types/ui/Tooltip.d.ts +22 -0
  85. package/types/ui/Tooltip.d.ts.map +1 -0
  86. package/types/ui/styles.d.ts.map +1 -1
  87. package/dist/cjs/index.js +0 -2
  88. package/dist/cjs/index.js.map +0 -1
  89. package/types/src/api/APIClient.d.ts +0 -63
  90. package/types/src/api/APIClient.d.ts.map +0 -1
  91. package/types/src/api/endpoints.d.ts +0 -35
  92. package/types/src/api/endpoints.d.ts.map +0 -1
  93. package/types/src/api/models.d.ts +0 -156
  94. package/types/src/api/models.d.ts.map +0 -1
  95. package/types/src/core/Configuration.d.ts +0 -42
  96. package/types/src/core/Configuration.d.ts.map +0 -1
  97. package/types/src/core/Encore.d.ts +0 -81
  98. package/types/src/core/Encore.d.ts.map +0 -1
  99. package/types/src/core/EntitlementManager.d.ts +0 -65
  100. package/types/src/core/EntitlementManager.d.ts.map +0 -1
  101. package/types/src/core/OfferManager.d.ts +0 -35
  102. package/types/src/core/OfferManager.d.ts.map +0 -1
  103. package/types/src/core/PlacementBuilder.d.ts +0 -27
  104. package/types/src/core/PlacementBuilder.d.ts.map +0 -1
  105. package/types/src/core/SignalManager.d.ts +0 -51
  106. package/types/src/core/SignalManager.d.ts.map +0 -1
  107. package/types/src/core/StorageManager.d.ts +0 -34
  108. package/types/src/core/StorageManager.d.ts.map +0 -1
  109. package/types/src/core/VerificationPoller.d.ts +0 -27
  110. package/types/src/core/VerificationPoller.d.ts.map +0 -1
  111. package/types/src/index.d.ts +0 -7
  112. package/types/src/index.d.ts.map +0 -1
  113. package/types/src/types.d.ts +0 -156
  114. package/types/src/types.d.ts.map +0 -1
  115. package/types/src/ui/OfferCard.d.ts +0 -29
  116. package/types/src/ui/OfferCard.d.ts.map +0 -1
  117. package/types/src/ui/OfferCarousel.d.ts +0 -55
  118. package/types/src/ui/OfferCarousel.d.ts.map +0 -1
  119. package/types/src/ui/OfferModal.d.ts +0 -41
  120. package/types/src/ui/OfferModal.d.ts.map +0 -1
  121. package/types/src/ui/SuccessScreen.d.ts +0 -33
  122. package/types/src/ui/SuccessScreen.d.ts.map +0 -1
  123. package/types/src/ui/styles.d.ts +0 -44
  124. package/types/src/ui/styles.d.ts.map +0 -1
  125. package/types/src/utils/eventEmitter.d.ts +0 -50
  126. package/types/src/utils/eventEmitter.d.ts.map +0 -1
  127. package/types/src/utils/focusDetection.d.ts +0 -21
  128. package/types/src/utils/focusDetection.d.ts.map +0 -1
  129. package/types/src/utils/logger.d.ts +0 -21
  130. package/types/src/utils/logger.d.ts.map +0 -1
  131. package/types/src/utils/network.d.ts +0 -57
  132. package/types/src/utils/network.d.ts.map +0 -1
  133. package/types/src/utils/uuid.d.ts +0 -10
  134. package/types/src/utils/uuid.d.ts.map +0 -1
@@ -0,0 +1,409 @@
1
+ # Encore iOS WebView Embed
2
+
3
+ This directory contains the embeddable HTML page and JavaScript bridge that allows iOS apps to use Encore via WKWebView without installing the native Swift SDK.
4
+
5
+ ## 🎨 Mobile-Native UI
6
+
7
+ The embed now features an **iOS-native bottom sheet UI** that provides a mobile-optimized experience matching the iOS SDK's visual design:
8
+
9
+ - ✅ **Bottom Sheet Design** - Slides up from bottom (not a centered popup)
10
+ - ✅ **Native Animations** - Smooth 300ms cubic-bezier animations
11
+ - ✅ **Swipe-to-Dismiss** - Pull down gesture to close
12
+ - ✅ **Auto-Present** - Automatically shows offers on load
13
+ - ✅ **iOS Styling** - SF Pro font, system blue, native shadows
14
+ - ✅ **Safe Area Support** - Respects notches and home indicators
15
+ - ✅ **Dark Mode** - Automatic dark mode support
16
+
17
+ 📖 **[Full Mobile UI Guide](./MOBILE-UI-GUIDE.md)** | 🧪 **[Testing Guide](../../encore-swift-sdk/EncoreSwiftUIDemo/MOBILE-UI-TESTING.md)**
18
+
19
+ ## Overview
20
+
21
+ The embed solution consists of:
22
+
23
+ - **`index.html`** - The main HTML page to be loaded in WKWebView
24
+ - **`bridge.js`** - JavaScript-iOS communication bridge via `webkit.messageHandlers`
25
+ - **`app.js`** - Application logic connecting Encore Web SDK to the bridge
26
+ - **`styles.css`** - Base styling (transparent until offer shown)
27
+ - **`mobile-styles.css`** - iOS-native bottom sheet components
28
+ - **`mobile-overrides.css`** - Transforms Web SDK modal to mobile sheet
29
+
30
+ ## Architecture
31
+
32
+ ```
33
+ iOS App (WKWebView)
34
+ ↕ (webkit.messageHandlers)
35
+ Bridge.js
36
+
37
+ App.js
38
+
39
+ Encore Web SDK
40
+ ```
41
+
42
+ ### URL Flow (How Offers Are Opened)
43
+
44
+ When a user claims an offer, the URL flows through the system as follows:
45
+
46
+ 1. **SDK builds final URL** - `Encore.ts` constructs the advertiser URL with tracking parameters (transaction ID, user ID, etc.)
47
+ 2. **SDK calls window.open()** - The SDK opens the URL using the standard `window.open(finalUrl)` API
48
+ 3. **Embed intercepts** - `app.js` overrides `window.open()` to capture the URL before it opens
49
+ 4. **Embed sends to iOS** - The captured URL is sent via `bridge.sendToIOS('encore:openURL', { url })`
50
+ 5. **iOS opens Safari** - The native app receives the message and opens the URL in `SFSafariViewController`
51
+
52
+ This design ensures:
53
+ - ✅ **No DOM coupling** - The SDK doesn't need to write URLs to DOM attributes
54
+ - ✅ **Clean separation** - The SDK is unaware of the iOS integration
55
+ - ✅ **Tracking preserved** - All tracking parameters flow through naturally
56
+ - ✅ **Framework agnostic** - Works regardless of how the SDK UI is implemented
57
+
58
+ ## Message Protocol
59
+
60
+ ### Web-to-iOS Messages
61
+
62
+ Messages sent from JavaScript to iOS via `webkit.messageHandlers.encore.postMessage()`:
63
+
64
+ | Message Type | Payload | Description |
65
+ |-------------|---------|-------------|
66
+ | `encore:ready` | `{ timestamp, hasURLConfig, isMobile }` | Embed app loaded and ready |
67
+ | `encore:initialized` | `{ success, userId, version }` | SDK initialized successfully |
68
+ | `encore:sheet:presented` | `{ timestamp }` | **[Mobile]** Bottom sheet shown |
69
+ | `encore:sheet:dismissed` | `{ reason, timestamp }` | **[Mobile]** Bottom sheet closed |
70
+ | `encore:openURL` | `{ url, timestamp }` | **[Mobile]** Request to open URL in Safari |
71
+ | `encore:entitlementGranted` | `{ entitlement, timestamp }` | User completed offer |
72
+ | `encore:userDeclined` | `{ reason, details, timestamp }` | User declined offer |
73
+ | `encore:error` | `{ code, message, context }` | Error occurred |
74
+ | `encore:userIdentified` | `{ success, userId }` | User identified |
75
+ | `encore:attributesSet` | `{ success }` | Attributes set |
76
+ | `encore:reset` | `{ success }` | SDK reset |
77
+ | `encore:state` | `{ isInitialized, userId, version, config }` | Current state |
78
+
79
+ ### iOS-to-Web Messages
80
+
81
+ Messages sent from iOS to JavaScript via `webView.evaluateJavaScript()` or `postMessage()`:
82
+
83
+ | Message Type | Payload | Description |
84
+ |-------------|---------|-------------|
85
+ | `configure` | `{ apiKey, environment?, userId?, attributes?, logLevel?, uiConfiguration? }` | Initialize SDK |
86
+ | `presentOffer` | `{}` | Show offer modal |
87
+ | `identify` | `{ userId, attributes? }` | Identify user |
88
+ | `setUserAttributes` | `{ attributes }` | Update user attributes |
89
+ | `reset` | `{}` | Reset SDK state |
90
+ | `getState` | `{}` | Request current state |
91
+
92
+ ## Integration Methods
93
+
94
+ ### Method 1: URL Builder (Recommended for Mobile)
95
+
96
+ Use the `EncoreURL` builder to create a properly formatted URL with auto-present:
97
+
98
+ ```swift
99
+ import SwiftUI
100
+
101
+ let url = EncoreURL.build(
102
+ apiKey: "your_api_key",
103
+ userId: "user123",
104
+ environment: "production",
105
+ autoPresentOffer: true, // Auto-show offers
106
+ debug: false
107
+ )
108
+
109
+ // In SwiftUI
110
+ .sheet(isPresented: $showEncore) {
111
+ EncoreWebView(
112
+ apiKey: "your_api_key",
113
+ userId: "user123",
114
+ autoPresentOffer: true
115
+ )
116
+ }
117
+ ```
118
+
119
+ **Pros**: Dead simple, iOS-native UI, auto-presents, swipe gestures
120
+ **Cons**: API key visible in URL (use server-side API key rotation)
121
+
122
+ ### Method 2: URL Parameters (Manual)
123
+
124
+ Load the embed page with configuration in URL:
125
+
126
+ ```swift
127
+ let url = URL(string: "https://encorekit.com/embed/?apiKey=YOUR_KEY&userId=USER_123&environment=production&autoPresentOffer=true")!
128
+ webView.load(URLRequest(url: url))
129
+ ```
130
+
131
+ ### Method 3: postMessage (Advanced)
132
+
133
+ Load empty embed page, then configure via postMessage:
134
+
135
+ ```swift
136
+ // 1. Load embed page
137
+ webView.load(URL(string: "https://encorekit.com/embed/")!)
138
+
139
+ // 2. Configure after load (in WKNavigationDelegate)
140
+ func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
141
+ let config = """
142
+ window.postMessage({
143
+ type: 'configure',
144
+ payload: {
145
+ apiKey: '\(apiKey)',
146
+ userId: '\(userId)',
147
+ environment: 'production'
148
+ }
149
+ }, '*');
150
+ """
151
+ webView.evaluateJavaScript(config)
152
+ }
153
+ ```
154
+
155
+ **Pros**: More secure, flexible, dynamic configuration
156
+ **Cons**: Slightly more code
157
+
158
+ ### Method 4: JavaScript with Callbacks (Most Control)
159
+
160
+ For scenarios where you need to control the flow after initialization:
161
+
162
+ ```javascript
163
+ // In your custom JavaScript or via evaluateJavaScript
164
+ if (window.EncoreBridge && window.Encore) {
165
+ window.EncoreBridge.on('encore:initialized', (payload) => {
166
+ console.log('SDK initialized:', payload);
167
+ // Now safe to present offer
168
+ window.postMessage({ type: 'presentOffer', payload: {} }, '*');
169
+ });
170
+
171
+ window.EncoreBridge.on('encore:error', (payload) => {
172
+ console.error('Initialization error:', payload);
173
+ });
174
+
175
+ // Trigger initialization
176
+ window.postMessage({
177
+ type: 'configure',
178
+ payload: {
179
+ apiKey: 'pk_live_...',
180
+ userId: 'user_123',
181
+ environment: 'production'
182
+ }
183
+ }, '*');
184
+ }
185
+ ```
186
+
187
+ **Pros**: Full control, proper error handling, no race conditions
188
+ **Cons**: More code, requires understanding of message flow
189
+
190
+ ## Usage Examples
191
+
192
+ ### Present an Offer
193
+
194
+ ```swift
195
+ // From iOS
196
+ let script = """
197
+ window.postMessage({
198
+ type: 'presentOffer',
199
+ payload: {}
200
+ }, '*');
201
+ """
202
+ webView.evaluateJavaScript(script)
203
+ ```
204
+
205
+ ### Handle Entitlement Granted
206
+
207
+ ```swift
208
+ // In iOS message handler
209
+ func userContentController(_ userContentController: WKUserContentController,
210
+ didReceive message: WKScriptMessage) {
211
+ guard message.name == "encore" else { return }
212
+
213
+ guard let dict = message.body as? [String: Any],
214
+ let type = dict["type"] as? String else {
215
+ return
216
+ }
217
+
218
+ switch type {
219
+ case "encore:entitlementGranted":
220
+ if let payload = dict["payload"] as? [String: Any],
221
+ let entitlement = payload["entitlement"] {
222
+ handleEntitlementGranted(entitlement)
223
+ }
224
+ case "encore:userDeclined":
225
+ if let payload = dict["payload"] as? [String: Any],
226
+ let reason = payload["reason"] as? String {
227
+ handleUserDeclined(reason)
228
+ }
229
+ case "encore:error":
230
+ if let payload = dict["payload"] as? [String: Any] {
231
+ handleError(payload)
232
+ }
233
+ case "encore:openURL":
234
+ if let payload = dict["payload"] as? [String: Any],
235
+ let urlString = payload["url"] as? String,
236
+ let url = URL(string: urlString) {
237
+ openInSafari(url: url)
238
+ }
239
+ default:
240
+ break
241
+ }
242
+ }
243
+
244
+ func openInSafari(url: URL) {
245
+ let safari = SFSafariViewController(url: url)
246
+ safari.delegate = self
247
+ present(safari, animated: true)
248
+ }
249
+
250
+ // SFSafariViewControllerDelegate
251
+ func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
252
+ // User closed Safari, notify webview
253
+ let script = """
254
+ window.postMessage({
255
+ type: 'safariClosed',
256
+ payload: { timestamp: \(Date().timeIntervalSince1970 * 1000) }
257
+ }, '*');
258
+ """
259
+ webView.evaluateJavaScript(script)
260
+ }
261
+ ```
262
+
263
+ ## Bridge API
264
+
265
+ The `EncoreBridge` object provides methods for communication between JavaScript and iOS:
266
+
267
+ ### Listening for Messages
268
+
269
+ ```javascript
270
+ // Listen for messages (stays subscribed)
271
+ const unsubscribe = bridge.on('encore:initialized', (payload) => {
272
+ console.log('SDK initialized:', payload);
273
+ });
274
+
275
+ // Stop listening
276
+ unsubscribe();
277
+
278
+ // Listen once (automatically unsubscribes after first call)
279
+ bridge.once('encore:initialized', (payload) => {
280
+ console.log('SDK initialized (one-time):', payload);
281
+ // Handler is automatically removed after this
282
+ });
283
+ ```
284
+
285
+ ### Sending Messages
286
+
287
+ ```javascript
288
+ // Send message to iOS
289
+ bridge.sendToIOS('encore:customEvent', {
290
+ data: 'value',
291
+ timestamp: Date.now()
292
+ });
293
+ ```
294
+
295
+ ### Initialization Callbacks
296
+
297
+ The `initializeSDK` function supports optional success/error callbacks to avoid race conditions:
298
+
299
+ ```javascript
300
+ // Proper way to auto-present after initialization
301
+ initializeSDK(
302
+ { apiKey: 'pk_...', userId: 'user_123' },
303
+ () => {
304
+ // Success - SDK is ready
305
+ presentOffer();
306
+ },
307
+ (error) => {
308
+ // Error - handle failure
309
+ console.error('Init failed:', error);
310
+ }
311
+ );
312
+ ```
313
+
314
+ **⚠️ Important**: Never use `setTimeout()` to wait for initialization. Always use callbacks or listen for the `encore:initialized` message to ensure the SDK is truly ready.
315
+
316
+ ## Debug Mode
317
+
318
+ Enable debug mode to see message logs:
319
+
320
+ 1. **Via URL**: Add `?debug=true` to the embed URL
321
+ 2. **Via Console**: Run `enableEncoreDebug()` in the web inspector
322
+ 3. **Persistent**: Once enabled, it's saved to localStorage
323
+
324
+ Debug panel shows:
325
+ - All messages sent/received
326
+ - SDK initialization status
327
+ - Errors and warnings
328
+ - Timestamps
329
+
330
+ ## File Structure
331
+
332
+ ```
333
+ embed/
334
+ ├── index.html # Main HTML page
335
+ ├── bridge.js # iOS-JavaScript bridge (SOURCE FILE - vanilla JS)
336
+ ├── app.js # Application logic (SOURCE FILE - vanilla JS)
337
+ ├── styles.css # Base styles (transparent background)
338
+ ├── mobile-styles.css # iOS-native bottom sheet UI
339
+ ├── mobile-overrides.css # Web SDK → mobile sheet transforms
340
+ ├── dist/
341
+ │ ├── bridge.min.js # Minified bridge (generated)
342
+ │ ├── bridge.min.js.map # Source map (generated)
343
+ │ ├── app.min.js # Minified app (generated)
344
+ │ └── app.min.js.map # Source map (generated)
345
+ ├── README.md # This file
346
+ ├── MOBILE-UI-GUIDE.md # Comprehensive mobile UI docs
347
+ └── NEXTJS-DEPLOYMENT.md # Deployment guide
348
+ ```
349
+
350
+ **Note**: `bridge.js` and `app.js` are vanilla JavaScript source files (not TypeScript). They are intentionally written as pure ES6+ to avoid transpilation overhead and are only minified for production.
351
+
352
+ ## Building for Production
353
+
354
+ ```bash
355
+ # Build the embed bundle (minifies bridge.js and app.js)
356
+ npm run build:embed
357
+
358
+ # Or build everything (SDK + embed)
359
+ npm run build
360
+
361
+ # Output will be in embed/dist/
362
+ ```
363
+
364
+ ## Security Considerations
365
+
366
+ 1. **HTTPS Only**: Always use HTTPS for the embed URL
367
+ 2. **API Key Security**: API keys are public but should have usage limits
368
+ 3. **Message Validation**: Bridge validates all message structures
369
+ 4. **CSP Headers**: Content Security Policy should be configured on server
370
+ 5. **Origin Validation**: Messages should validate expected origins
371
+
372
+ ## Browser Compatibility
373
+
374
+ - iOS 13+ (WKWebView)
375
+ - Modern JavaScript (ES6+)
376
+ - No external dependencies
377
+
378
+ ## Troubleshooting
379
+
380
+ ### Messages not being received by iOS
381
+
382
+ Check:
383
+ 1. Message handler is registered: `userContentController.add(self, name: "encore")`
384
+ 2. WKWebView configuration is correct
385
+ 3. Debug mode is enabled to see logs
386
+
387
+ ### SDK not initializing
388
+
389
+ Check:
390
+ 1. API key is valid
391
+ 2. Network connectivity
392
+ 3. CORS settings (if self-hosting)
393
+ 4. Check debug panel for error messages
394
+
395
+ ### Offer modal not showing
396
+
397
+ Check:
398
+ 1. SDK is initialized (`encore:initialized` received)
399
+ 2. Offers are available for this user
400
+ 3. CSS/styles are loaded properly
401
+ 4. Check console for errors
402
+
403
+ ## Support
404
+
405
+ For more information:
406
+ - [Full iOS WebView Integration Guide](../docs/ios-webview-integration.md)
407
+ - [Swift Example Code](../examples/ios-webview/)
408
+ - [Encore Documentation](https://docs.encorekit.com)
409
+
@@ -0,0 +1,57 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
6
+ <meta name="apple-mobile-web-app-capable" content="yes">
7
+ <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
8
+ <title>Encore Embed</title>
9
+ <link rel="stylesheet" href="embed/styles.css">
10
+ <!-- Mobile-native styles for iOS bottom sheet UI -->
11
+ <link rel="stylesheet" href="embed/mobile-styles.css">
12
+ <!-- Override Web SDK modal to work as bottom sheet -->
13
+ <link rel="stylesheet" href="embed/mobile-overrides.css">
14
+ </head>
15
+ <body>
16
+ <!--
17
+ This page is designed to be embedded in iOS WKWebView.
18
+ It remains transparent until an offer is presented.
19
+ -->
20
+
21
+ <!-- Loading indicator (hidden by default) -->
22
+ <div id="encore-loading" class="encore-loading" style="display: none;">
23
+ <div class="encore-spinner"></div>
24
+ </div>
25
+
26
+ <!-- Debug panel (only shown in development) -->
27
+ <div id="encore-debug" class="encore-debug" style="display: none;">
28
+ <div class="debug-header">
29
+ <span>Encore Debug</span>
30
+ <div class="debug-controls">
31
+ <button id="debug-minimize" title="Minimize">−</button>
32
+ <button id="debug-close" title="Close">×</button>
33
+ </div>
34
+ </div>
35
+ <div id="debug-log" class="debug-log"></div>
36
+ </div>
37
+
38
+ <!-- Credit Claimed Confirmation View -->
39
+ <div id="encore-claimed" class="encore-claimed-view" style="display: none;">
40
+ <div class="claimed-content">
41
+ <div class="claimed-checkmark">✓</div>
42
+ <h2 class="claimed-title">Credit Claimed!</h2>
43
+ <p class="claimed-message">Your reward has been unlocked</p>
44
+ <button id="claimed-done" class="encore-button encore-button-primary">Done</button>
45
+ </div>
46
+ </div>
47
+
48
+ <!-- Load Encore Web SDK (local build) -->
49
+ <script src="embed/dist/encore.min.js"></script>
50
+
51
+ <!-- Load iOS Bridge -->
52
+ <script src="embed/dist/bridge.min.js"></script>
53
+
54
+ <!-- Load Application Logic -->
55
+ <script src="embed/dist/app.min.js"></script>
56
+ </body>
57
+ </html>
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Encore Embed Styles
3
+ * Minimal styles for the embed container
4
+ * The Web SDK provides its own modal styles
5
+ */
6
+
7
+ * {
8
+ margin: 0;
9
+ padding: 0;
10
+ box-sizing: border-box;
11
+ }
12
+
13
+ html, body {
14
+ width: 100%;
15
+ height: 100%;
16
+ /* White background to fill the sheet */
17
+ background: white;
18
+ overflow: hidden;
19
+ /* No padding - SwiftUI sheet handles safe areas */
20
+ padding: 0;
21
+ }
22
+
23
+ /* Prevent text selection in embed */
24
+ body {
25
+ -webkit-user-select: none;
26
+ user-select: none;
27
+ -webkit-touch-callout: none;
28
+ }
29
+
30
+ /* Loading indicator */
31
+ .encore-loading {
32
+ position: fixed;
33
+ top: 50%;
34
+ left: 50%;
35
+ transform: translate(-50%, -50%);
36
+ z-index: 9997;
37
+ }
38
+
39
+ .encore-spinner {
40
+ width: 40px;
41
+ height: 40px;
42
+ border: 4px solid rgba(0, 0, 0, 0.1);
43
+ border-top-color: #000;
44
+ border-radius: 50%;
45
+ animation: encore-spin 1s linear infinite;
46
+ }
47
+
48
+ @keyframes encore-spin {
49
+ to { transform: rotate(360deg); }
50
+ }
51
+
52
+ /* Dark mode support */
53
+ @media (prefers-color-scheme: dark) {
54
+ .encore-spinner {
55
+ border-color: rgba(255, 255, 255, 0.1);
56
+ border-top-color: #fff;
57
+ }
58
+ }
59
+
60
+ /* Debug panel */
61
+ .encore-debug {
62
+ position: fixed;
63
+ bottom: 0;
64
+ left: 0;
65
+ right: 0;
66
+ max-height: 40vh;
67
+ background: rgba(0, 0, 0, 0.9);
68
+ color: #00ff00;
69
+ font-family: 'Courier New', monospace;
70
+ font-size: 12px;
71
+ z-index: 99999;
72
+ border-top: 2px solid #00ff00;
73
+ display: flex;
74
+ flex-direction: column;
75
+ transition: max-height 0.3s ease;
76
+ }
77
+
78
+ .encore-debug.minimized {
79
+ max-height: 40px;
80
+ }
81
+
82
+ .encore-debug.minimized .debug-log {
83
+ display: none;
84
+ }
85
+
86
+ .encore-debug.minimized .debug-header {
87
+ border-bottom: none;
88
+ }
89
+
90
+ .debug-header {
91
+ display: flex;
92
+ justify-content: space-between;
93
+ align-items: center;
94
+ padding: 8px 12px;
95
+ background: rgba(0, 0, 0, 0.95);
96
+ border-bottom: 1px solid #00ff00;
97
+ font-weight: bold;
98
+ cursor: pointer;
99
+ }
100
+
101
+ .debug-controls {
102
+ display: flex;
103
+ gap: 8px;
104
+ }
105
+
106
+ #debug-minimize,
107
+ #debug-close {
108
+ background: transparent;
109
+ border: 1px solid #00ff00;
110
+ color: #00ff00;
111
+ width: 24px;
112
+ height: 24px;
113
+ border-radius: 50%;
114
+ cursor: pointer;
115
+ font-size: 16px;
116
+ line-height: 1;
117
+ padding: 0;
118
+ }
119
+
120
+ #debug-minimize:active,
121
+ #debug-close:active {
122
+ background: rgba(0, 255, 0, 0.2);
123
+ }
124
+
125
+ .debug-log {
126
+ flex: 1;
127
+ overflow-y: auto;
128
+ padding: 8px 12px;
129
+ -webkit-overflow-scrolling: touch;
130
+ }
131
+
132
+ .debug-entry {
133
+ margin-bottom: 4px;
134
+ word-break: break-word;
135
+ }
136
+
137
+ .debug-entry.error {
138
+ color: #ff0000;
139
+ }
140
+
141
+ .debug-entry.warning {
142
+ color: #ffaa00;
143
+ }
144
+
145
+ .debug-entry.info {
146
+ color: #00aaff;
147
+ }
148
+
149
+ /* Ensure Web SDK modal appears above everything */
150
+ :root {
151
+ --encore-modal-z-index: 9998;
152
+ --encore-backdrop-z-index: 9997;
153
+ }
154
+