craft-native 0.0.37 → 0.0.49

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 (89) hide show
  1. package/dist/android/src/index.js +268 -0
  2. package/dist/android/src/index.js.map +10 -0
  3. package/dist/android/templates/AndroidManifest.xml.template +69 -0
  4. package/dist/android/templates/CraftBridge.kt.template +3748 -0
  5. package/dist/android/templates/CraftBridgeExtensions.kt.template +383 -0
  6. package/dist/android/templates/CraftWidgetProvider.kt.template +246 -0
  7. package/dist/android/templates/MainActivity.kt.template +109 -0
  8. package/dist/android/templates/build.gradle.kts.app.template +80 -0
  9. package/dist/android/templates/build.gradle.kts.project.template +5 -0
  10. package/dist/android/templates/fastlane/Appfile +25 -0
  11. package/dist/android/templates/fastlane/Fastfile +192 -0
  12. package/dist/android/templates/fastlane/Gemfile +7 -0
  13. package/dist/android/templates/github-workflow-android.yml +248 -0
  14. package/dist/android/templates/github-workflow-test.yml +329 -0
  15. package/dist/android/templates/settings.gradle.kts.template +18 -0
  16. package/dist/android/templates/test-bridges.html +1463 -0
  17. package/dist/api/window.d.ts +18 -0
  18. package/dist/api/window.d.ts.map +1 -1
  19. package/dist/assets/index.d.ts.map +1 -1
  20. package/dist/bridge/core.d.ts +2 -0
  21. package/dist/bridge/core.d.ts.map +1 -1
  22. package/dist/cli.js +4168 -0
  23. package/dist/components/sidebar.d.ts +5 -1
  24. package/dist/components/sidebar.d.ts.map +1 -1
  25. package/dist/dev/devtools.d.ts +1 -1
  26. package/dist/dev/devtools.d.ts.map +1 -1
  27. package/dist/dev/hot-reload.d.ts +12 -3
  28. package/dist/dev/hot-reload.d.ts.map +1 -1
  29. package/dist/index.cjs +573 -208
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +585 -216
  32. package/dist/index.js.map +45 -0
  33. package/dist/ios/src/index.js +186 -0
  34. package/dist/ios/src/index.js.map +10 -0
  35. package/dist/ios/templates/CraftApp.swift +4241 -0
  36. package/dist/ios/templates/CraftAppExtensions.swift +361 -0
  37. package/dist/ios/templates/CraftWidget.swift +247 -0
  38. package/dist/ios/templates/Info.plist.template +65 -0
  39. package/dist/ios/templates/fastlane/Appfile +45 -0
  40. package/dist/ios/templates/fastlane/Fastfile +189 -0
  41. package/dist/ios/templates/fastlane/Gemfile +7 -0
  42. package/dist/ios/templates/fastlane/Matchfile +58 -0
  43. package/dist/ios/templates/github-workflow-ios.yml +192 -0
  44. package/dist/ios/templates/github-workflow-release.yml +175 -0
  45. package/dist/ios/templates/github-workflow-test.yml +272 -0
  46. package/dist/ios/templates/project.yml.template +25 -0
  47. package/dist/ios/templates/test-bridges.html +1463 -0
  48. package/dist/package.d.ts +106 -2
  49. package/dist/package.d.ts.map +1 -1
  50. package/dist/package.test.d.ts +2 -0
  51. package/dist/package.test.d.ts.map +1 -0
  52. package/dist/styles/sidebar-templates.d.ts.map +1 -1
  53. package/dist/styles/sidebars.d.ts +12 -8
  54. package/dist/styles/sidebars.d.ts.map +1 -1
  55. package/dist/templates/projects/blank/craft.config.ts +19 -0
  56. package/dist/templates/projects/blank/index.html +27 -0
  57. package/dist/templates/projects/blank/package.json +19 -0
  58. package/dist/templates/projects/blank/src/main.ts +26 -0
  59. package/dist/templates/projects/dashboard/craft.config.ts +42 -0
  60. package/dist/templates/projects/dashboard/index.html +13 -0
  61. package/dist/templates/projects/dashboard/package.json +20 -0
  62. package/dist/templates/projects/dashboard/src/main.ts +163 -0
  63. package/dist/templates/projects/dashboard/src/styles.css +280 -0
  64. package/dist/templates/projects/drawer/craft.config.ts +47 -0
  65. package/dist/templates/projects/drawer/index.html +13 -0
  66. package/dist/templates/projects/drawer/package.json +21 -0
  67. package/dist/templates/projects/drawer/src/main.ts +406 -0
  68. package/dist/templates/projects/drawer/src/styles.css +468 -0
  69. package/dist/templates/projects/ecommerce/craft.config.ts +32 -0
  70. package/dist/templates/projects/ecommerce/index.html +13 -0
  71. package/dist/templates/projects/ecommerce/package.json +19 -0
  72. package/dist/templates/projects/ecommerce/src/main.ts +194 -0
  73. package/dist/templates/projects/ecommerce/src/styles.css +169 -0
  74. package/dist/templates/projects/social/craft.config.ts +32 -0
  75. package/dist/templates/projects/social/index.html +13 -0
  76. package/dist/templates/projects/social/package.json +19 -0
  77. package/dist/templates/projects/social/src/main.ts +270 -0
  78. package/dist/templates/projects/social/src/styles.css +216 -0
  79. package/dist/templates/projects/tabs/craft.config.ts +47 -0
  80. package/dist/templates/projects/tabs/index.html +13 -0
  81. package/dist/templates/projects/tabs/package.json +21 -0
  82. package/dist/templates/projects/tabs/src/main.ts +177 -0
  83. package/dist/templates/projects/tabs/src/styles.css +163 -0
  84. package/dist/types.d.ts +29 -0
  85. package/dist/types.d.ts.map +1 -1
  86. package/dist/updater/index.d.ts +1 -0
  87. package/dist/updater/index.d.ts.map +1 -1
  88. package/package.json +5 -12
  89. package/bin/cli.ts +0 -937
@@ -0,0 +1,468 @@
1
+ :root {
2
+ --primary: #6200ee;
3
+ --primary-dark: #3700b3;
4
+ --secondary: #03dac6;
5
+ --bg: #ffffff;
6
+ --bg-secondary: #f5f5f5;
7
+ --bg-drawer: #ffffff;
8
+ --text: #1d1d1d;
9
+ --text-secondary: #757575;
10
+ --text-on-primary: #ffffff;
11
+ --border: #e0e0e0;
12
+ --drawer-width: 280px;
13
+ --header-height: 56px;
14
+ --safe-area-top: env(safe-area-inset-top, 0px);
15
+ --safe-area-bottom: env(safe-area-inset-bottom, 0px);
16
+ --overlay-bg: rgba(0, 0, 0, 0.5);
17
+ }
18
+
19
+ @media (prefers-color-scheme: dark) {
20
+ :root {
21
+ --primary: #bb86fc;
22
+ --primary-dark: #6200ee;
23
+ --secondary: #03dac6;
24
+ --bg: #121212;
25
+ --bg-secondary: #1e1e1e;
26
+ --bg-drawer: #1e1e1e;
27
+ --text: #e1e1e1;
28
+ --text-secondary: #a0a0a0;
29
+ --text-on-primary: #000000;
30
+ --border: #2c2c2c;
31
+ --overlay-bg: rgba(0, 0, 0, 0.7);
32
+ }
33
+ }
34
+
35
+ * {
36
+ margin: 0;
37
+ padding: 0;
38
+ box-sizing: border-box;
39
+ }
40
+
41
+ body {
42
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
43
+ background: var(--bg);
44
+ color: var(--text);
45
+ min-height: 100vh;
46
+ overflow-x: hidden;
47
+ }
48
+
49
+ /* Drawer Overlay */
50
+ .drawer-overlay {
51
+ position: fixed;
52
+ top: 0;
53
+ left: 0;
54
+ right: 0;
55
+ bottom: 0;
56
+ background: var(--overlay-bg);
57
+ z-index: 998;
58
+ opacity: 0;
59
+ visibility: hidden;
60
+ transition: opacity 0.3s ease, visibility 0.3s ease;
61
+ }
62
+
63
+ .drawer-overlay.open {
64
+ opacity: 1;
65
+ visibility: visible;
66
+ }
67
+
68
+ /* Drawer */
69
+ .drawer {
70
+ position: fixed;
71
+ top: 0;
72
+ left: 0;
73
+ width: var(--drawer-width);
74
+ height: 100%;
75
+ background: var(--bg-drawer);
76
+ z-index: 999;
77
+ transform: translateX(-100%);
78
+ transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
79
+ display: flex;
80
+ flex-direction: column;
81
+ box-shadow: 4px 0 16px rgba(0, 0, 0, 0.1);
82
+ }
83
+
84
+ .drawer.open {
85
+ transform: translateX(0);
86
+ }
87
+
88
+ .drawer-header {
89
+ padding: calc(var(--safe-area-top) + 24px) 16px 24px 16px;
90
+ background: var(--primary);
91
+ color: var(--text-on-primary);
92
+ }
93
+
94
+ .drawer-header-avatar {
95
+ width: 64px;
96
+ height: 64px;
97
+ border-radius: 50%;
98
+ background: rgba(255, 255, 255, 0.3);
99
+ display: flex;
100
+ align-items: center;
101
+ justify-content: center;
102
+ font-size: 28px;
103
+ margin-bottom: 16px;
104
+ }
105
+
106
+ .drawer-header-title {
107
+ font-size: 18px;
108
+ font-weight: 600;
109
+ margin-bottom: 4px;
110
+ }
111
+
112
+ .drawer-header-subtitle {
113
+ font-size: 14px;
114
+ opacity: 0.8;
115
+ }
116
+
117
+ .drawer-content {
118
+ flex: 1;
119
+ overflow-y: auto;
120
+ padding: 8px 0;
121
+ }
122
+
123
+ .drawer-section {
124
+ padding: 8px 0;
125
+ }
126
+
127
+ .drawer-section-title {
128
+ padding: 8px 16px;
129
+ font-size: 12px;
130
+ font-weight: 600;
131
+ color: var(--text-secondary);
132
+ text-transform: uppercase;
133
+ letter-spacing: 0.5px;
134
+ }
135
+
136
+ .drawer-item {
137
+ display: flex;
138
+ align-items: center;
139
+ gap: 16px;
140
+ padding: 12px 16px;
141
+ color: var(--text);
142
+ cursor: pointer;
143
+ transition: background 0.2s ease;
144
+ border: none;
145
+ background: none;
146
+ width: 100%;
147
+ text-align: left;
148
+ font-size: 14px;
149
+ }
150
+
151
+ .drawer-item:hover {
152
+ background: var(--bg-secondary);
153
+ }
154
+
155
+ .drawer-item.active {
156
+ background: rgba(98, 0, 238, 0.1);
157
+ color: var(--primary);
158
+ }
159
+
160
+ .drawer-item svg {
161
+ width: 24px;
162
+ height: 24px;
163
+ flex-shrink: 0;
164
+ }
165
+
166
+ .drawer-item-label {
167
+ flex: 1;
168
+ }
169
+
170
+ .drawer-item-badge {
171
+ background: var(--primary);
172
+ color: var(--text-on-primary);
173
+ font-size: 11px;
174
+ font-weight: 600;
175
+ padding: 2px 8px;
176
+ border-radius: 10px;
177
+ }
178
+
179
+ .drawer-divider {
180
+ height: 1px;
181
+ background: var(--border);
182
+ margin: 8px 0;
183
+ }
184
+
185
+ .drawer-footer {
186
+ padding: 16px;
187
+ padding-bottom: calc(var(--safe-area-bottom) + 16px);
188
+ border-top: 1px solid var(--border);
189
+ }
190
+
191
+ .drawer-footer-version {
192
+ font-size: 12px;
193
+ color: var(--text-secondary);
194
+ }
195
+
196
+ /* Header */
197
+ .header {
198
+ position: sticky;
199
+ top: 0;
200
+ height: var(--header-height);
201
+ background: var(--primary);
202
+ color: var(--text-on-primary);
203
+ display: flex;
204
+ align-items: center;
205
+ gap: 16px;
206
+ padding: 0 16px;
207
+ padding-top: var(--safe-area-top);
208
+ z-index: 100;
209
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
210
+ }
211
+
212
+ .header-menu-btn {
213
+ width: 40px;
214
+ height: 40px;
215
+ border: none;
216
+ background: none;
217
+ color: var(--text-on-primary);
218
+ cursor: pointer;
219
+ border-radius: 50%;
220
+ display: flex;
221
+ align-items: center;
222
+ justify-content: center;
223
+ transition: background 0.2s ease;
224
+ }
225
+
226
+ .header-menu-btn:hover {
227
+ background: rgba(255, 255, 255, 0.1);
228
+ }
229
+
230
+ .header-menu-btn:active {
231
+ background: rgba(255, 255, 255, 0.2);
232
+ }
233
+
234
+ .header-menu-btn svg {
235
+ width: 24px;
236
+ height: 24px;
237
+ }
238
+
239
+ .header h1 {
240
+ font-size: 20px;
241
+ font-weight: 500;
242
+ flex: 1;
243
+ }
244
+
245
+ .header-action {
246
+ width: 40px;
247
+ height: 40px;
248
+ border: none;
249
+ background: none;
250
+ color: var(--text-on-primary);
251
+ cursor: pointer;
252
+ border-radius: 50%;
253
+ display: flex;
254
+ align-items: center;
255
+ justify-content: center;
256
+ transition: background 0.2s ease;
257
+ }
258
+
259
+ .header-action:hover {
260
+ background: rgba(255, 255, 255, 0.1);
261
+ }
262
+
263
+ .header-action svg {
264
+ width: 24px;
265
+ height: 24px;
266
+ }
267
+
268
+ /* Content */
269
+ .content {
270
+ padding: 16px;
271
+ min-height: calc(100vh - var(--header-height) - var(--safe-area-top));
272
+ }
273
+
274
+ .page {
275
+ display: none;
276
+ }
277
+
278
+ .page.active {
279
+ display: block;
280
+ }
281
+
282
+ /* Cards */
283
+ .card {
284
+ background: var(--bg-secondary);
285
+ border-radius: 12px;
286
+ padding: 16px;
287
+ margin-bottom: 16px;
288
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
289
+ }
290
+
291
+ .card h3 {
292
+ font-size: 16px;
293
+ font-weight: 600;
294
+ margin-bottom: 8px;
295
+ }
296
+
297
+ .card p {
298
+ color: var(--text-secondary);
299
+ font-size: 14px;
300
+ line-height: 1.5;
301
+ }
302
+
303
+ /* List */
304
+ .list-item {
305
+ display: flex;
306
+ align-items: center;
307
+ gap: 16px;
308
+ padding: 16px 0;
309
+ border-bottom: 1px solid var(--border);
310
+ }
311
+
312
+ .list-item:last-child {
313
+ border-bottom: none;
314
+ }
315
+
316
+ .list-item-icon {
317
+ width: 40px;
318
+ height: 40px;
319
+ border-radius: 50%;
320
+ background: var(--primary);
321
+ display: flex;
322
+ align-items: center;
323
+ justify-content: center;
324
+ color: var(--text-on-primary);
325
+ font-size: 18px;
326
+ }
327
+
328
+ .list-item-content {
329
+ flex: 1;
330
+ }
331
+
332
+ .list-item-title {
333
+ font-weight: 500;
334
+ margin-bottom: 2px;
335
+ }
336
+
337
+ .list-item-subtitle {
338
+ font-size: 13px;
339
+ color: var(--text-secondary);
340
+ }
341
+
342
+ .list-item-action {
343
+ color: var(--text-secondary);
344
+ }
345
+
346
+ /* FAB */
347
+ .fab {
348
+ position: fixed;
349
+ bottom: calc(24px + var(--safe-area-bottom));
350
+ right: 24px;
351
+ width: 56px;
352
+ height: 56px;
353
+ border-radius: 16px;
354
+ background: var(--secondary);
355
+ color: #000;
356
+ border: none;
357
+ cursor: pointer;
358
+ display: flex;
359
+ align-items: center;
360
+ justify-content: center;
361
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
362
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
363
+ z-index: 50;
364
+ }
365
+
366
+ .fab:hover {
367
+ transform: scale(1.05);
368
+ box-shadow: 0 6px 16px rgba(0, 0, 0, 0.25);
369
+ }
370
+
371
+ .fab:active {
372
+ transform: scale(0.95);
373
+ }
374
+
375
+ .fab svg {
376
+ width: 24px;
377
+ height: 24px;
378
+ }
379
+
380
+ /* Empty State */
381
+ .empty-state {
382
+ text-align: center;
383
+ padding: 48px 24px;
384
+ }
385
+
386
+ .empty-state-icon {
387
+ font-size: 64px;
388
+ margin-bottom: 16px;
389
+ opacity: 0.5;
390
+ }
391
+
392
+ .empty-state-title {
393
+ font-size: 18px;
394
+ font-weight: 600;
395
+ margin-bottom: 8px;
396
+ }
397
+
398
+ .empty-state-text {
399
+ color: var(--text-secondary);
400
+ font-size: 14px;
401
+ }
402
+
403
+ /* Settings List */
404
+ .settings-list {
405
+ background: var(--bg-secondary);
406
+ border-radius: 12px;
407
+ overflow: hidden;
408
+ margin-bottom: 16px;
409
+ }
410
+
411
+ .settings-item {
412
+ display: flex;
413
+ align-items: center;
414
+ justify-content: space-between;
415
+ padding: 16px;
416
+ border-bottom: 1px solid var(--border);
417
+ cursor: pointer;
418
+ transition: background 0.2s ease;
419
+ }
420
+
421
+ .settings-item:last-child {
422
+ border-bottom: none;
423
+ }
424
+
425
+ .settings-item:hover {
426
+ background: var(--bg);
427
+ }
428
+
429
+ .settings-item-label {
430
+ font-size: 15px;
431
+ }
432
+
433
+ .settings-item-value {
434
+ font-size: 14px;
435
+ color: var(--text-secondary);
436
+ }
437
+
438
+ /* Toggle Switch */
439
+ .toggle {
440
+ position: relative;
441
+ width: 48px;
442
+ height: 28px;
443
+ background: var(--border);
444
+ border-radius: 14px;
445
+ cursor: pointer;
446
+ transition: background 0.3s ease;
447
+ }
448
+
449
+ .toggle.on {
450
+ background: var(--primary);
451
+ }
452
+
453
+ .toggle::after {
454
+ content: '';
455
+ position: absolute;
456
+ top: 2px;
457
+ left: 2px;
458
+ width: 24px;
459
+ height: 24px;
460
+ background: white;
461
+ border-radius: 50%;
462
+ transition: transform 0.3s ease;
463
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
464
+ }
465
+
466
+ .toggle.on::after {
467
+ transform: translateX(20px);
468
+ }
@@ -0,0 +1,32 @@
1
+ import type { CraftConfig } from 'craft-native'
2
+
3
+ export default {
4
+ name: '{{appName}}',
5
+ version: '0.1.0',
6
+
7
+ platforms: {
8
+ ios: {
9
+ bundleId: '{{bundleId}}',
10
+ minimumVersion: '15.0',
11
+ capabilities: ['apple-pay', 'push-notifications'],
12
+ },
13
+ android: {
14
+ packageName: '{{packageName}}',
15
+ minSdk: 24,
16
+ permissions: ['INTERNET', 'POST_NOTIFICATIONS'],
17
+ },
18
+ macos: {
19
+ bundleId: '{{bundleId}}',
20
+ category: 'business',
21
+ },
22
+ windows: {
23
+ packageName: '{{packageName}}',
24
+ },
25
+ },
26
+
27
+ build: {
28
+ outDir: 'dist',
29
+ minify: true,
30
+ sourcemap: true,
31
+ },
32
+ } satisfies CraftConfig
@@ -0,0 +1,13 @@
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
+ <title>{{appName}}</title>
7
+ <link rel="stylesheet" href="src/styles.css">
8
+ </head>
9
+ <body>
10
+ <div id="app"></div>
11
+ <script type="module" src="src/main.ts"></script>
12
+ </body>
13
+ </html>
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "{{projectName}}",
3
+ "version": "0.1.0",
4
+ "description": "E-commerce app built with Craft",
5
+ "main": "src/main.ts",
6
+ "scripts": {
7
+ "dev": "craft dev",
8
+ "build": "craft build",
9
+ "build:ios": "craft build --platform ios",
10
+ "build:android": "craft build --platform android",
11
+ "build:all": "craft build --platform ios,android,macos,windows"
12
+ },
13
+ "dependencies": {
14
+ "craft-native": "workspace:*"
15
+ },
16
+ "devDependencies": {
17
+ "typescript": "^5.0.0"
18
+ }
19
+ }
@@ -0,0 +1,194 @@
1
+ /**
2
+ * E-commerce Template - {{appName}}
3
+ * A cross-platform shopping app built with Craft
4
+ */
5
+
6
+ import { db, win, isMobile, haptics } from 'craft-native'
7
+
8
+ // Types
9
+ interface Product {
10
+ id: string
11
+ name: string
12
+ description: string
13
+ price: number
14
+ image: string
15
+ category: string
16
+ inStock: boolean
17
+ }
18
+
19
+ interface CartItem {
20
+ product: Product
21
+ quantity: number
22
+ }
23
+
24
+ // State
25
+ let products: Product[] = []
26
+ let cart: CartItem[] = []
27
+ let currentView: 'home' | 'cart' | 'product' = 'home'
28
+ let selectedProductId: string | null = null
29
+
30
+ // Initialize
31
+ async function init(): Promise<void> {
32
+ await initDatabase()
33
+ await loadProducts()
34
+
35
+ win.setTitle('{{appName}}')
36
+ win.setSize(400, 800)
37
+
38
+ render()
39
+ }
40
+
41
+ async function initDatabase(): Promise<void> {
42
+ await db.execute(`
43
+ CREATE TABLE IF NOT EXISTS cart (
44
+ productId TEXT PRIMARY KEY,
45
+ quantity INTEGER DEFAULT 1
46
+ )
47
+ `)
48
+ }
49
+
50
+ async function loadProducts(): Promise<void> {
51
+ // Sample products - replace with API call
52
+ products = [
53
+ { id: '1', name: 'Product 1', description: 'Description', price: 29.99, image: '', category: 'Category', inStock: true },
54
+ { id: '2', name: 'Product 2', description: 'Description', price: 49.99, image: '', category: 'Category', inStock: true },
55
+ { id: '3', name: 'Product 3', description: 'Description', price: 19.99, image: '', category: 'Category', inStock: false },
56
+ ]
57
+ }
58
+
59
+ // Cart functions
60
+ function addToCart(productId: string): void {
61
+ const product = products.find(p => p.id === productId)
62
+ if (!product) return
63
+
64
+ const existingItem = cart.find(item => item.product.id === productId)
65
+ if (existingItem) {
66
+ existingItem.quantity++
67
+ }
68
+ else {
69
+ cart.push({ product, quantity: 1 })
70
+ }
71
+
72
+ if (isMobile()) {
73
+ haptics.notification('success')
74
+ }
75
+
76
+ render()
77
+ }
78
+
79
+ function removeFromCart(productId: string): void {
80
+ cart = cart.filter(item => item.product.id !== productId)
81
+ render()
82
+ }
83
+
84
+ function getCartTotal(): number {
85
+ return cart.reduce((sum, item) => sum + (item.product.price * item.quantity), 0)
86
+ }
87
+
88
+ // Navigation
89
+ function navigate(view: typeof currentView, productId?: string): void {
90
+ currentView = view
91
+ selectedProductId = productId || null
92
+ render()
93
+ }
94
+
95
+ // Render
96
+ function render(): void {
97
+ const app = document.getElementById('app')
98
+ if (!app) return
99
+
100
+ let content = ''
101
+ switch (currentView) {
102
+ case 'home':
103
+ content = renderHome()
104
+ break
105
+ case 'cart':
106
+ content = renderCart()
107
+ break
108
+ case 'product':
109
+ content = renderProduct()
110
+ break
111
+ }
112
+
113
+ app.innerHTML = `
114
+ <div class="app">
115
+ <header class="header">
116
+ <h1>{{appName}}</h1>
117
+ <button class="cart-btn" onclick="navigate('cart')">
118
+ 🛒 ${cart.length}
119
+ </button>
120
+ </header>
121
+ <main>${content}</main>
122
+ <nav class="bottom-nav">
123
+ <button onclick="navigate('home')" class="${currentView === 'home' ? 'active' : ''}">Home</button>
124
+ <button onclick="navigate('cart')" class="${currentView === 'cart' ? 'active' : ''}">Cart</button>
125
+ </nav>
126
+ </div>
127
+ `
128
+ }
129
+
130
+ function renderHome(): string {
131
+ return `
132
+ <div class="products-grid">
133
+ ${products.map(product => `
134
+ <div class="product-card" onclick="navigate('product', '${product.id}')">
135
+ <div class="product-image"></div>
136
+ <h3>${product.name}</h3>
137
+ <p class="price">$${product.price.toFixed(2)}</p>
138
+ <button onclick="event.stopPropagation(); addToCart('${product.id}')"
139
+ ${!product.inStock ? 'disabled' : ''}>
140
+ ${product.inStock ? 'Add to Cart' : 'Out of Stock'}
141
+ </button>
142
+ </div>
143
+ `).join('')}
144
+ </div>
145
+ `
146
+ }
147
+
148
+ function renderCart(): string {
149
+ if (cart.length === 0) {
150
+ return `<div class="empty-state"><p>Your cart is empty</p></div>`
151
+ }
152
+
153
+ return `
154
+ <div class="cart">
155
+ ${cart.map(item => `
156
+ <div class="cart-item">
157
+ <span>${item.product.name} x${item.quantity}</span>
158
+ <span>$${(item.product.price * item.quantity).toFixed(2)}</span>
159
+ <button onclick="removeFromCart('${item.product.id}')">Remove</button>
160
+ </div>
161
+ `).join('')}
162
+ <div class="cart-total">
163
+ <strong>Total: $${getCartTotal().toFixed(2)}</strong>
164
+ </div>
165
+ <button class="checkout-btn">Checkout</button>
166
+ </div>
167
+ `
168
+ }
169
+
170
+ function renderProduct(): string {
171
+ const product = products.find(p => p.id === selectedProductId)
172
+ if (!product) return '<p>Product not found</p>'
173
+
174
+ return `
175
+ <div class="product-detail">
176
+ <button onclick="navigate('home')">← Back</button>
177
+ <div class="product-image large"></div>
178
+ <h2>${product.name}</h2>
179
+ <p>${product.description}</p>
180
+ <p class="price">$${product.price.toFixed(2)}</p>
181
+ <button onclick="addToCart('${product.id}')" ${!product.inStock ? 'disabled' : ''}>
182
+ ${product.inStock ? 'Add to Cart' : 'Out of Stock'}
183
+ </button>
184
+ </div>
185
+ `
186
+ }
187
+
188
+ // Expose functions globally
189
+ (window as any).navigate = navigate
190
+ ;(window as any).addToCart = addToCart
191
+ ;(window as any).removeFromCart = removeFromCart
192
+
193
+ // Start app
194
+ init()