@tickean/checkout-elements 0.2.0 → 0.2.22

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.
package/dist/index.mjs CHANGED
@@ -41,101 +41,1136 @@ function formatMoney(amount, currency = "ARS", locale = "es-AR") {
41
41
  // src/appearance.ts
42
42
  var themeVars = {
43
43
  default: {
44
- "--tickean-font-family": "ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, sans-serif",
44
+ "--tickean-font-family": '"Inter", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif',
45
45
  "--tickean-bg": "#ffffff",
46
- "--tickean-fg": "#111827",
47
- "--tickean-muted": "#6b7280",
48
- "--tickean-border": "#e5e7eb",
49
- "--tickean-primary": "#111827",
46
+ "--tickean-surface": "#f0fdf4",
47
+ "--tickean-fg": "#052e16",
48
+ "--tickean-muted": "#64748b",
49
+ "--tickean-border": "#dcfce7",
50
+ "--tickean-primary": "#16a34a",
51
+ "--tickean-primary-hover": "#15803d",
50
52
  "--tickean-primary-fg": "#ffffff",
51
- "--tickean-danger": "#b91c1c",
53
+ "--tickean-danger": "#dc2626",
54
+ "--tickean-success": "#10b981",
55
+ "--tickean-pass": "#7c3aed",
56
+ "--tickean-pass-bg": "#f5f3ff",
57
+ "--tickean-pass-border": "#ddd6fe",
52
58
  "--tickean-radius": "10px",
53
- "--tickean-space": "12px",
54
- "--tickean-input-bg": "#ffffff"
59
+ "--tickean-space": "8px",
60
+ "--tickean-input-bg": "#ffffff",
61
+ "--tickean-shadow": "0 1px 2px rgba(15, 23, 42, 0.05)",
62
+ "--tickean-shadow-lg": "0 8px 20px rgba(15, 23, 42, 0.07)",
63
+ "--tickean-focus": "rgba(22, 163, 74, 0.22)"
55
64
  },
56
65
  flat: {
57
- "--tickean-font-family": "ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, sans-serif",
58
- "--tickean-bg": "#f8fafc",
66
+ "--tickean-font-family": '"Inter", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif',
67
+ "--tickean-bg": "#ffffff",
68
+ "--tickean-surface": "#f8fafc",
59
69
  "--tickean-fg": "#0f172a",
60
70
  "--tickean-muted": "#64748b",
61
- "--tickean-border": "#cbd5e1",
71
+ "--tickean-border": "#e2e8f0",
62
72
  "--tickean-primary": "#0f172a",
73
+ "--tickean-primary-hover": "#1e293b",
63
74
  "--tickean-primary-fg": "#ffffff",
64
75
  "--tickean-danger": "#dc2626",
65
- "--tickean-radius": "0px",
66
- "--tickean-space": "12px",
67
- "--tickean-input-bg": "#ffffff"
76
+ "--tickean-success": "#0f766e",
77
+ "--tickean-radius": "10px",
78
+ "--tickean-space": "14px",
79
+ "--tickean-input-bg": "#ffffff",
80
+ "--tickean-shadow": "0 1px 2px rgba(15, 23, 42, 0.06)",
81
+ "--tickean-shadow-lg": "0 12px 28px rgba(15, 23, 42, 0.08)",
82
+ "--tickean-focus": "rgba(15, 23, 42, 0.18)"
68
83
  },
69
84
  night: {
70
- "--tickean-font-family": "ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, sans-serif",
85
+ "--tickean-font-family": '"Inter", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif',
71
86
  "--tickean-bg": "#0b1220",
87
+ "--tickean-surface": "#111827",
72
88
  "--tickean-fg": "#e5e7eb",
73
89
  "--tickean-muted": "#9ca3af",
74
90
  "--tickean-border": "#1f2937",
75
- "--tickean-primary": "#e5e7eb",
91
+ "--tickean-primary": "#818cf8",
92
+ "--tickean-primary-hover": "#a5b4fc",
76
93
  "--tickean-primary-fg": "#0b1220",
77
94
  "--tickean-danger": "#f87171",
78
- "--tickean-radius": "10px",
79
- "--tickean-space": "12px",
80
- "--tickean-input-bg": "#111827"
95
+ "--tickean-success": "#2dd4bf",
96
+ "--tickean-radius": "12px",
97
+ "--tickean-space": "14px",
98
+ "--tickean-input-bg": "#0f172a",
99
+ "--tickean-shadow": "0 2px 8px rgba(0, 0, 0, 0.35)",
100
+ "--tickean-shadow-lg": "0 18px 40px rgba(0, 0, 0, 0.45)",
101
+ "--tickean-focus": "rgba(129, 140, 248, 0.35)"
81
102
  }
82
103
  };
83
104
  var baseStyles = `
105
+ @import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap");
106
+
84
107
  :host {
85
108
  display: block;
86
- color: var(--tickean-fg, #111827);
87
- font-family: var(--tickean-font-family, ui-sans-serif, system-ui, sans-serif);
109
+ width: 100%;
110
+ max-width: none;
111
+ margin: 0;
112
+ color: var(--tickean-fg, #052e16);
113
+ font-family: var(--tickean-font-family, Inter, ui-sans-serif, system-ui, sans-serif);
88
114
  box-sizing: border-box;
115
+ -webkit-font-smoothing: antialiased;
116
+ text-rendering: optimizeLegibility;
117
+ font-size: 15px;
118
+ }
119
+ /* Only the top-level shell caps width; nested elements fill the wizard. */
120
+ :host(tickean-checkout) {
121
+ max-width: min(100%, 560px);
122
+ margin: 0 auto;
89
123
  }
90
124
  *, *::before, *::after { box-sizing: border-box; }
125
+
126
+ @keyframes tickean-fade-up {
127
+ from { opacity: 0; transform: translateY(10px); }
128
+ to { opacity: 1; transform: translateY(0); }
129
+ }
130
+ @keyframes tickean-shimmer {
131
+ 0% { background-position: 100% 0; }
132
+ 100% { background-position: -100% 0; }
133
+ }
134
+ @keyframes tickean-pulse {
135
+ 0%, 100% { opacity: 0.55; }
136
+ 50% { opacity: 1; }
137
+ }
138
+
139
+ .reveal {
140
+ animation: tickean-fade-up 380ms cubic-bezier(0.22, 1, 0.36, 1) both;
141
+ }
142
+ .reveal-1 { animation-delay: 30ms; }
143
+ .reveal-2 { animation-delay: 70ms; }
144
+ .reveal-3 { animation-delay: 110ms; }
145
+ .reveal-4 { animation-delay: 150ms; }
146
+ .reveal-5 { animation-delay: 190ms; }
147
+ .reveal-6 { animation-delay: 230ms; }
148
+ /* After first paint, hosts can set data-animated to avoid re-animating on cart updates */
149
+ :host([data-animated]) .reveal {
150
+ animation: none !important;
151
+ }
152
+
91
153
  .wrap {
92
154
  background: var(--tickean-bg, #fff);
93
- border: 1px solid var(--tickean-border, #e5e7eb);
155
+ border: 1px solid var(--tickean-border, #e3e8ee);
94
156
  border-radius: var(--tickean-radius, 10px);
95
- padding: calc(var(--tickean-space, 12px) * 1.25);
157
+ padding: 10px;
158
+ box-shadow: var(--tickean-shadow);
96
159
  }
97
- .muted { color: var(--tickean-muted, #6b7280); font-size: 0.9rem; }
98
- .danger { color: var(--tickean-danger, #b91c1c); }
160
+ .card {
161
+ background: var(--tickean-bg, #fff);
162
+ border: 1px solid var(--tickean-border, #e3e8ee);
163
+ border-radius: calc(var(--tickean-radius, 10px) - 2px);
164
+ padding: var(--tickean-space, 8px);
165
+ box-shadow: var(--tickean-shadow);
166
+ transition: border-color 160ms ease, box-shadow 160ms ease, transform 160ms ease;
167
+ }
168
+ .card:hover {
169
+ border-color: color-mix(in srgb, var(--tickean-primary, #635bff) 35%, var(--tickean-border, #e3e8ee));
170
+ box-shadow: var(--tickean-shadow-lg);
171
+ }
172
+ .muted { color: var(--tickean-muted, #697386); font-size: 0.82rem; line-height: 1.35; }
173
+ .danger { color: var(--tickean-danger, #df1b41); font-size: 0.82rem; }
174
+ .success { color: var(--tickean-success, #0d9488); font-size: 0.82rem; }
99
175
  .row { display: flex; align-items: center; gap: 8px; justify-content: space-between; }
100
- .stack { display: grid; gap: var(--tickean-space, 12px); }
176
+ .stack { display: grid; gap: 8px; }
177
+
178
+ .section-title {
179
+ margin: 0;
180
+ font-size: 0.7rem;
181
+ font-weight: 600;
182
+ letter-spacing: 0.04em;
183
+ text-transform: uppercase;
184
+ color: var(--tickean-muted, #697386);
185
+ }
186
+
101
187
  .ticket {
102
- border: 1px solid var(--tickean-border, #e5e7eb);
103
- border-radius: var(--tickean-radius, 10px);
104
- padding: var(--tickean-space, 12px);
188
+ border: 1px solid var(--tickean-border, #dcfce7);
189
+ border-radius: calc(var(--tickean-radius, 10px) - 2px);
190
+ padding: 8px 10px;
191
+ background: var(--tickean-bg, #fff);
192
+ box-shadow: none;
193
+ transition: border-color 160ms ease, box-shadow 160ms ease;
194
+ }
195
+ .ticket[data-selected="true"] {
196
+ border-color: var(--tickean-primary, #16a34a);
197
+ box-shadow: 0 0 0 3px var(--tickean-focus);
198
+ }
199
+ .ticket.pass {
200
+ border-color: var(--tickean-pass-border, #ddd6fe);
201
+ background: var(--tickean-pass-bg, #f5f3ff);
202
+ }
203
+ .ticket.pass[data-selected="true"] {
204
+ border-color: var(--tickean-pass, #7c3aed);
205
+ box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.18);
206
+ }
207
+ .pass-badge {
208
+ display: inline-flex;
209
+ align-items: center;
210
+ padding: 2px 8px;
211
+ border-radius: 999px;
212
+ background: color-mix(in srgb, var(--tickean-pass, #7c3aed) 14%, transparent);
213
+ color: var(--tickean-pass, #7c3aed);
214
+ font-size: 0.7rem;
215
+ font-weight: 700;
216
+ letter-spacing: 0.02em;
217
+ text-transform: uppercase;
218
+ }
219
+ .promo-badge {
220
+ display: inline-flex;
221
+ align-items: center;
222
+ padding: 2px 7px;
223
+ border-radius: 999px;
224
+ background: color-mix(in srgb, var(--tickean-primary, #16a34a) 14%, transparent);
225
+ color: var(--tickean-primary, #16a34a);
226
+ font-size: 0.68rem;
227
+ font-weight: 700;
228
+ }
229
+ .promo-legend {
230
+ margin-top: 3px;
231
+ font-size: 0.72rem;
232
+ color: var(--tickean-primary, #16a34a);
233
+ font-weight: 550;
234
+ }
235
+ .stock-badge {
236
+ display: inline-flex;
237
+ align-items: center;
238
+ padding: 2px 7px;
239
+ border-radius: 999px;
240
+ font-size: 0.68rem;
241
+ font-weight: 700;
242
+ }
243
+ .stock-badge.low-stock {
244
+ background: #fffbeb;
245
+ color: #b45309;
246
+ border: 1px solid #fde68a;
247
+ }
248
+ .stock-badge.sold-out {
249
+ background: #fef2f2;
250
+ color: #b91c1c;
251
+ border: 1px solid #fecaca;
252
+ }
253
+ .stock-badge.coming-soon {
254
+ background: #f1f5f9;
255
+ color: #475569;
256
+ border: 1px solid #e2e8f0;
257
+ }
258
+ .ticket.sold-out,
259
+ .ticket.coming-soon {
260
+ opacity: 0.72;
261
+ background: var(--tickean-surface, #f8fafc);
262
+ }
263
+ .pay-methods {
264
+ display: grid;
265
+ gap: 8px;
266
+ }
267
+ .pay-method {
268
+ display: flex;
269
+ align-items: flex-start;
270
+ gap: 10px;
271
+ width: 100%;
272
+ text-align: left;
273
+ padding: 10px 12px;
274
+ border-radius: 10px;
275
+ border: 1.5px solid var(--tickean-border, #dcfce7);
276
+ background: var(--tickean-bg, #fff);
277
+ color: inherit;
278
+ box-shadow: none;
279
+ font-weight: 500;
280
+ }
281
+ .pay-method:hover:not(:disabled) {
282
+ transform: none;
283
+ background: var(--tickean-surface, #f0fdf4);
284
+ }
285
+ .pay-method:disabled {
286
+ opacity: 0.72;
287
+ cursor: not-allowed;
288
+ }
289
+ .pay-methods[data-locked="true"] {
290
+ pointer-events: none;
291
+ opacity: 0.85;
292
+ }
293
+ .pay-method[data-selected="true"] {
294
+ border-color: var(--tickean-primary, #16a34a);
295
+ box-shadow: 0 0 0 1px var(--tickean-primary, #16a34a);
296
+ }
297
+ .pay-method-radio {
298
+ width: 16px;
299
+ height: 16px;
300
+ margin-top: 2px;
301
+ border-radius: 999px;
302
+ border: 2px solid var(--tickean-border, #cbd5e1);
303
+ flex-shrink: 0;
304
+ }
305
+ .pay-method[data-selected="true"] .pay-method-radio {
306
+ border-color: var(--tickean-primary, #16a34a);
307
+ box-shadow: inset 0 0 0 3px var(--tickean-primary, #16a34a);
308
+ }
309
+ .pay-method-copy {
310
+ display: grid;
311
+ gap: 2px;
312
+ min-width: 0;
313
+ }
314
+ .pay-method-copy strong {
315
+ font-size: 0.88rem;
316
+ }
317
+ .pay-method-copy .muted {
318
+ font-size: 0.72rem;
319
+ line-height: 1.3;
320
+ }
321
+ .transfer-box {
322
+ padding: 12px;
323
+ border-radius: 12px;
324
+ border: 1px solid var(--tickean-border, #dcfce7);
325
+ background: var(--tickean-surface, #f0fdf4);
326
+ }
327
+ .transfer-box code {
328
+ font-size: 0.9rem;
329
+ font-weight: 700;
330
+ word-break: break-all;
331
+ color: var(--tickean-fg);
332
+ }
333
+ .transfer-amount {
334
+ display: flex;
335
+ flex-direction: column;
336
+ gap: 2px;
337
+ padding: 12px 12px 10px;
338
+ border-radius: 10px;
339
+ background: color-mix(in srgb, var(--tickean-brand, #16a34a) 10%, #fff);
340
+ border: 1px solid color-mix(in srgb, var(--tickean-brand, #16a34a) 28%, transparent);
341
+ text-align: center;
342
+ }
343
+ .transfer-amount-label {
344
+ font-size: 0.75rem;
345
+ font-weight: 600;
346
+ letter-spacing: 0.02em;
347
+ text-transform: uppercase;
348
+ color: var(--tickean-muted);
349
+ }
350
+ .transfer-amount-value {
351
+ font-size: 1.7rem;
352
+ line-height: 1.15;
353
+ font-weight: 800;
354
+ letter-spacing: -0.02em;
355
+ color: var(--tickean-brand, #16a34a);
356
+ }
357
+ .transfer-waiting {
358
+ display: flex;
359
+ align-items: center;
360
+ gap: 8px;
361
+ font-size: 0.84rem;
362
+ font-weight: 600;
363
+ color: var(--tickean-fg);
364
+ }
365
+ .transfer-waiting-dot {
366
+ width: 8px;
367
+ height: 8px;
368
+ border-radius: 999px;
369
+ background: var(--tickean-brand, #16a34a);
370
+ animation: tickean-pulse 1.2s ease-in-out infinite;
371
+ }
372
+ .copy-row {
373
+ display: flex;
374
+ align-items: center;
375
+ justify-content: space-between;
376
+ gap: 10px;
377
+ padding: 8px 0;
378
+ border-top: 1px solid color-mix(in srgb, var(--tickean-border) 80%, transparent);
379
+ }
380
+ .copy-row:first-of-type {
381
+ border-top: none;
382
+ padding-top: 0;
383
+ }
384
+ .copy-row-text {
385
+ display: flex;
386
+ flex-direction: column;
387
+ gap: 2px;
388
+ min-width: 0;
389
+ }
390
+ .copy-row-text .muted {
391
+ font-size: 0.72rem;
392
+ }
393
+ .copy-btn {
394
+ flex: 0 0 auto;
395
+ display: inline-flex;
396
+ align-items: center;
397
+ gap: 5px;
398
+ width: auto;
399
+ min-height: 34px;
400
+ padding: 6px 10px;
401
+ border-radius: 8px;
402
+ border: 1px solid var(--tickean-border);
403
+ background: #fff;
404
+ color: var(--tickean-fg);
405
+ font-size: 0.75rem;
406
+ font-weight: 650;
407
+ cursor: pointer;
408
+ }
409
+ .copy-btn:hover {
410
+ border-color: color-mix(in srgb, var(--tickean-brand, #16a34a) 45%, var(--tickean-border));
411
+ }
412
+ .pay-breakdown {
413
+ padding: 10px 12px;
414
+ border-radius: 10px;
415
+ border: 1px solid var(--tickean-border);
416
+ background: var(--tickean-bg, #fff);
417
+ }
418
+ .pay-breakdown-title {
419
+ font-size: 0.82rem;
420
+ }
421
+ .pay-breakdown-total {
422
+ margin-top: 2px;
423
+ padding-top: 8px;
424
+ border-top: 1px solid var(--tickean-border);
425
+ font-size: 0.92rem;
426
+ }
427
+ .pay-method-locked {
428
+ align-items: center;
429
+ justify-content: space-between;
430
+ gap: 10px;
431
+ }
432
+ .ghost-btn {
433
+ width: auto;
434
+ min-height: 34px;
435
+ padding: 6px 10px;
436
+ border-radius: 8px;
437
+ border: 1px solid var(--tickean-border);
438
+ background: transparent;
439
+ color: var(--tickean-fg);
440
+ font-size: 0.78rem;
441
+ font-weight: 650;
442
+ cursor: pointer;
443
+ }
444
+ .ghost-btn:hover {
445
+ background: color-mix(in srgb, var(--tickean-brand, #16a34a) 8%, transparent);
446
+ }
447
+ .terms-row {
448
+ display: flex;
449
+ align-items: flex-start;
450
+ gap: 8px;
451
+ font-size: 0.8rem;
452
+ line-height: 1.35;
453
+ }
454
+ .terms-row input {
455
+ width: auto;
456
+ margin-top: 2px;
457
+ }
458
+ .group-title {
459
+ margin: 4px 0 0;
460
+ font-size: 0.8rem;
461
+ font-weight: 650;
462
+ color: var(--tickean-fg);
463
+ }
464
+ .discount-inline {
465
+ margin-top: 2px;
466
+ width: 100%;
467
+ }
468
+ .discount-inline details {
469
+ width: 100%;
470
+ border: 1px solid var(--tickean-border);
471
+ border-radius: 10px;
472
+ padding: 8px 10px;
473
+ background: var(--tickean-bg);
474
+ }
475
+ .discount-inline summary {
476
+ cursor: pointer;
477
+ font-size: 0.82rem;
478
+ font-weight: 600;
479
+ color: var(--tickean-muted);
480
+ list-style: none;
481
+ }
482
+ .discount-inline summary::-webkit-details-marker { display: none; }
483
+ .promo-unlock {
484
+ width: 100%;
485
+ border: 1px solid var(--tickean-border);
486
+ border-radius: 12px;
487
+ padding: 12px;
488
+ background: color-mix(in srgb, var(--tickean-muted, #697386) 6%, var(--tickean-bg, #fff));
489
+ display: grid;
490
+ gap: 10px;
491
+ }
492
+ .promo-unlock-copy { display: grid; gap: 2px; }
493
+ .promo-unlock-title {
494
+ margin: 0;
495
+ font-size: 0.9rem;
496
+ font-weight: 650;
497
+ color: var(--tickean-fg);
498
+ }
499
+ .promo-unlock-hint {
500
+ margin: 0;
501
+ font-size: 0.78rem;
502
+ line-height: 1.35;
503
+ }
504
+ .promo-unlock-row {
505
+ display: flex;
506
+ gap: 8px;
507
+ align-items: stretch;
508
+ }
509
+ .promo-unlock-row input {
510
+ flex: 1;
511
+ min-width: 0;
512
+ text-transform: uppercase;
513
+ }
514
+ .promo-unlock-row button {
515
+ flex-shrink: 0;
516
+ }
517
+ .promo-unlock-msg {
518
+ margin: 0;
519
+ font-size: 0.78rem;
520
+ line-height: 1.35;
521
+ }
522
+ .promo-unlock-msg.success {
523
+ color: var(--tickean-success, #0d9488);
524
+ }
525
+ .promo-unlock-msg.danger {
526
+ color: var(--tickean-danger, #df1b41);
527
+ }
528
+ .sheet-backdrop {
529
+ position: fixed;
530
+ inset: 0;
531
+ background: rgba(15, 23, 42, 0.45);
532
+ display: grid;
533
+ place-items: end center;
534
+ z-index: 40;
535
+ padding: 0;
536
+ animation: tickean-fade-up 180ms ease both;
537
+ }
538
+ .sheet {
539
+ width: min(100%, 420px);
540
+ background: var(--tickean-bg, #fff);
541
+ border-radius: 14px 14px 0 0;
542
+ padding: 14px 14px calc(14px + env(safe-area-inset-bottom, 0px));
543
+ box-shadow: var(--tickean-shadow-lg);
544
+ display: grid;
545
+ gap: 10px;
546
+ animation: tickean-fade-up 260ms cubic-bezier(0.22, 1, 0.36, 1) both;
547
+ }
548
+ .sheet h3 {
549
+ margin: 0;
550
+ font-size: 1rem;
551
+ letter-spacing: -0.02em;
105
552
  }
106
- button, input {
553
+ .otp-digits {
554
+ display: grid;
555
+ grid-template-columns: repeat(6, 1fr);
556
+ gap: 5px;
557
+ }
558
+ .otp-digits input {
559
+ text-align: center;
560
+ font-weight: 700;
561
+ padding: 10px 0;
562
+ font-size: 16px;
563
+ }
564
+ .compact-aside .wrap {
565
+ padding: 8px 10px;
566
+ box-shadow: none;
567
+ }
568
+
569
+ button, input, select {
107
570
  font: inherit;
108
571
  }
109
572
  button {
110
- border: 1px solid var(--tickean-border, #e5e7eb);
111
- background: var(--tickean-primary, #111827);
573
+ border: 1px solid transparent;
574
+ background: var(--tickean-primary, #635bff);
112
575
  color: var(--tickean-primary-fg, #fff);
113
- border-radius: calc(var(--tickean-radius, 10px) * 0.7);
114
- padding: 8px 12px;
576
+ border-radius: 999px;
577
+ padding: 10px 14px;
115
578
  cursor: pointer;
579
+ font-weight: 600;
580
+ letter-spacing: 0.01em;
581
+ font-size: 0.92rem;
582
+ transition: background 150ms ease, transform 120ms ease, box-shadow 150ms ease, opacity 150ms ease;
583
+ box-shadow: 0 2px 8px color-mix(in srgb, var(--tickean-primary, #635bff) 22%, transparent);
584
+ }
585
+ button:hover:not(:disabled) {
586
+ background: var(--tickean-primary-hover, #5851ea);
587
+ transform: translateY(-1px);
116
588
  }
589
+ button:active:not(:disabled) { transform: translateY(0); }
117
590
  button.secondary {
118
591
  background: transparent;
119
- color: var(--tickean-fg, #111827);
592
+ color: var(--tickean-fg, #0a2540);
593
+ border-color: var(--tickean-border, #e3e8ee);
594
+ box-shadow: none;
595
+ }
596
+ button.secondary:hover:not(:disabled) {
597
+ background: var(--tickean-surface, #f6f9fc);
598
+ border-color: color-mix(in srgb, var(--tickean-fg, #0a2540) 18%, var(--tickean-border, #e3e8ee));
599
+ }
600
+ button:disabled { opacity: 0.45; cursor: not-allowed; box-shadow: none; transform: none; }
601
+ button.ghost {
602
+ background: transparent;
603
+ color: var(--tickean-muted, #697386);
604
+ border: none;
605
+ box-shadow: none;
606
+ padding: 8px 10px;
607
+ }
608
+
609
+ .field {
610
+ display: grid;
611
+ gap: 4px;
120
612
  }
121
- button:disabled { opacity: 0.55; cursor: not-allowed; }
122
- input {
613
+ .field > span {
614
+ font-size: 0.76rem;
615
+ font-weight: 500;
616
+ color: var(--tickean-fg, #0a2540);
617
+ }
618
+ .field-hint {
619
+ font-size: 0.72rem;
620
+ color: var(--tickean-muted, #697386);
621
+ }
622
+ input, select {
123
623
  width: 100%;
124
- border: 1px solid var(--tickean-border, #e5e7eb);
624
+ border: 1px solid var(--tickean-border, #e3e8ee);
125
625
  background: var(--tickean-input-bg, #fff);
126
- color: var(--tickean-fg, #111827);
127
- border-radius: calc(var(--tickean-radius, 10px) * 0.7);
128
- padding: 8px 10px;
626
+ color: var(--tickean-fg, #0a2540);
627
+ border-radius: 8px;
628
+ padding: 9px 10px;
629
+ transition: border-color 140ms ease, box-shadow 140ms ease;
630
+ outline: none;
631
+ font-size: 16px; /* prevent iOS zoom */
632
+ }
633
+ input:focus, select:focus {
634
+ border-color: var(--tickean-primary, #635bff);
635
+ box-shadow: 0 0 0 3px var(--tickean-focus);
636
+ }
637
+ .phone-row {
638
+ display: grid;
639
+ grid-template-columns: minmax(118px, 38%) 1fr;
640
+ gap: 8px;
641
+ }
642
+ .phone-country {
643
+ display: flex;
644
+ align-items: center;
645
+ gap: 6px;
646
+ border: 1px solid var(--tickean-border, #e3e8ee);
647
+ border-radius: 10px;
648
+ background: var(--tickean-input-bg, #fff);
649
+ padding: 0 8px 0 10px;
650
+ transition: border-color 140ms ease, box-shadow 140ms ease;
651
+ }
652
+ .phone-country:focus-within {
653
+ border-color: var(--tickean-primary, #635bff);
654
+ box-shadow: 0 0 0 3px var(--tickean-focus);
655
+ }
656
+ .phone-country select {
657
+ border: none;
658
+ box-shadow: none;
659
+ padding: 10px 0;
660
+ background: transparent;
661
+ min-width: 0;
662
+ }
663
+ .phone-country select:focus { box-shadow: none; }
664
+ .qty {
665
+ display: inline-flex;
666
+ align-items: center;
667
+ gap: 2px;
668
+ background: var(--tickean-surface, #f6f9fc);
669
+ border-radius: 999px;
670
+ padding: 2px;
671
+ border: 1px solid var(--tickean-border, #e3e8ee);
672
+ }
673
+ .qty button {
674
+ min-width: 32px;
675
+ height: 32px;
676
+ min-height: 32px;
677
+ padding: 0;
678
+ border-radius: 999px;
679
+ box-shadow: none;
680
+ background: var(--tickean-bg, #fff);
681
+ color: var(--tickean-fg, #0a2540);
682
+ border: 1px solid var(--tickean-border, #e3e8ee);
683
+ }
684
+ .qty button:hover:not(:disabled) {
685
+ background: var(--tickean-surface, #f6f9fc);
686
+ transform: none;
687
+ }
688
+ .qty span {
689
+ min-width: 28px;
690
+ text-align: center;
691
+ font-weight: 600;
692
+ font-variant-numeric: tabular-nums;
693
+ }
694
+ .total[aria-live] {
695
+ font-weight: 700;
696
+ font-size: 1.05rem;
697
+ letter-spacing: -0.02em;
129
698
  }
130
- label { display: grid; gap: 4px; font-size: 0.85rem; }
131
- .qty button { min-width: 36px; }
132
- .total[aria-live] { font-weight: 700; font-size: 1.1rem; }
133
699
  .provider-slot {
134
- min-height: 48px;
135
- border: 1px dashed var(--tickean-border, #e5e7eb);
700
+ min-height: 56px;
701
+ border: 1px dashed var(--tickean-border, #e3e8ee);
702
+ border-radius: var(--tickean-radius, 12px);
703
+ padding: var(--tickean-space, 14px);
704
+ color: var(--tickean-muted, #697386);
705
+ background: var(--tickean-surface, #f6f9fc);
706
+ }
707
+
708
+ /* Stepped checkout shell */
709
+ .checkout-steps {
710
+ display: grid;
711
+ gap: 8px;
712
+ padding: 10px;
713
+ background:
714
+ radial-gradient(700px 220px at 8% -20%, color-mix(in srgb, var(--tickean-primary, #16a34a) 10%, transparent), transparent 55%),
715
+ var(--tickean-bg, #fff);
716
+ border: 1px solid var(--tickean-border, #dcfce7);
717
+ border-radius: calc(var(--tickean-radius, 10px) + 2px);
718
+ box-shadow: var(--tickean-shadow);
719
+ }
720
+ .checkout-steps.compact {
721
+ padding-bottom: calc(72px + env(safe-area-inset-bottom, 0px));
722
+ }
723
+ .checkout-header h2 {
724
+ margin: 0;
725
+ font-size: 0.98rem;
726
+ line-height: 1.2;
727
+ letter-spacing: -0.02em;
728
+ font-weight: 700;
729
+ }
730
+ .checkout-header p {
731
+ margin: 2px 0 0;
732
+ }
733
+ .event-hero {
734
+ display: grid;
735
+ grid-template-columns: 48px minmax(0, 1fr);
736
+ gap: 8px;
737
+ align-items: start;
738
+ }
739
+ .event-flyer {
740
+ width: 48px;
741
+ height: 48px;
742
+ border-radius: 8px;
743
+ object-fit: cover;
744
+ border: 1px solid var(--tickean-border, #dcfce7);
745
+ background: var(--tickean-surface, #f0fdf4);
746
+ }
747
+ .event-flyer-fallback {
748
+ display: grid;
749
+ place-items: center;
750
+ color: var(--tickean-primary, #16a34a);
751
+ font-size: 1rem;
752
+ }
753
+ .event-hero-copy {
754
+ min-width: 0;
755
+ display: grid;
756
+ gap: 2px;
757
+ }
758
+ .event-dates {
759
+ margin: 0;
760
+ font-size: 0.78rem;
761
+ font-weight: 650;
762
+ color: var(--tickean-fg, #052e16);
763
+ letter-spacing: -0.01em;
764
+ }
765
+ .event-schedule {
766
+ margin: 2px 0 0;
767
+ }
768
+ .event-schedule summary {
769
+ cursor: pointer;
770
+ font-size: 0.72rem;
771
+ font-weight: 600;
772
+ color: var(--tickean-muted, #64748b);
773
+ list-style: none;
774
+ }
775
+ .event-schedule summary::-webkit-details-marker { display: none; }
776
+ .event-show-times {
777
+ list-style: none;
778
+ margin: 4px 0 0;
779
+ padding: 0;
780
+ display: grid;
781
+ gap: 1px;
782
+ }
783
+ .event-show-times li {
784
+ font-size: 0.72rem;
785
+ color: var(--tickean-muted, #64748b);
786
+ line-height: 1.3;
787
+ }
788
+ .event-place {
789
+ margin: 0;
790
+ font-size: 0.72rem;
791
+ }
792
+ .progress {
793
+ height: 3px;
794
+ border-radius: 999px;
795
+ background: var(--tickean-surface, #f6f9fc);
796
+ overflow: hidden;
797
+ border: none;
798
+ }
799
+ .progress > span {
800
+ display: block;
801
+ height: 100%;
802
+ width: var(--tickean-progress, 25%);
803
+ background: linear-gradient(90deg, var(--tickean-primary, #635bff), color-mix(in srgb, var(--tickean-primary, #635bff) 65%, #fff));
804
+ border-radius: inherit;
805
+ transition: width 380ms cubic-bezier(0.22, 1, 0.36, 1);
806
+ }
807
+ .stepper {
808
+ display: grid;
809
+ grid-template-columns: repeat(4, minmax(0, 1fr));
810
+ gap: 4px;
811
+ list-style: none;
812
+ margin: 0;
813
+ padding: 0;
814
+ }
815
+ .stepper-item {
816
+ display: grid;
817
+ gap: 3px;
818
+ justify-items: center;
819
+ font-size: 0.65rem;
820
+ color: var(--tickean-muted, #697386);
821
+ font-weight: 500;
822
+ text-align: center;
823
+ }
824
+ .stepper-label {
825
+ white-space: nowrap;
826
+ overflow: hidden;
827
+ text-overflow: ellipsis;
828
+ max-width: 100%;
829
+ }
830
+ .stepper-dot {
831
+ width: 22px;
832
+ height: 22px;
833
+ border-radius: 999px;
834
+ display: grid;
835
+ place-items: center;
836
+ border: 1px solid var(--tickean-border, #e3e8ee);
837
+ background: var(--tickean-input-bg, #fff);
838
+ font-weight: 650;
839
+ font-size: 0.68rem;
840
+ color: var(--tickean-muted, #697386);
841
+ transition: all 200ms ease;
842
+ }
843
+ .stepper-item[data-active="true"] {
844
+ color: var(--tickean-fg, #0a2540);
845
+ font-weight: 650;
846
+ }
847
+ .stepper-item[data-active="true"] .stepper-dot,
848
+ .stepper-item[data-done="true"] .stepper-dot {
849
+ background: var(--tickean-primary, #635bff);
850
+ border-color: var(--tickean-primary, #635bff);
851
+ color: var(--tickean-primary-fg, #fff);
852
+ }
853
+ .step-caption {
854
+ margin: 0;
855
+ font-size: 0.72rem;
856
+ }
857
+ .checkout-layout {
858
+ display: grid;
859
+ gap: 8px;
860
+ }
861
+ .summary-drawer {
862
+ border: 1px solid var(--tickean-border, #dcfce7);
863
+ border-radius: 10px;
864
+ background: var(--tickean-bg, #fff);
865
+ overflow: hidden;
866
+ }
867
+ .summary-drawer-toggle {
868
+ display: flex;
869
+ align-items: center;
870
+ justify-content: space-between;
871
+ gap: 8px;
872
+ padding: 8px 10px;
873
+ cursor: pointer;
874
+ list-style: none;
875
+ font-size: 0.8rem;
876
+ font-weight: 600;
877
+ }
878
+ .summary-drawer-toggle::-webkit-details-marker { display: none; }
879
+ .summary-drawer-toggle::after {
880
+ content: "";
881
+ width: 0.45em;
882
+ height: 0.45em;
883
+ border-right: 1.5px solid var(--tickean-muted);
884
+ border-bottom: 1.5px solid var(--tickean-muted);
885
+ transform: rotate(45deg);
886
+ transition: transform 0.15s ease;
887
+ margin-left: 4px;
888
+ flex: 0 0 auto;
889
+ }
890
+ .summary-drawer[open] .summary-drawer-toggle::after {
891
+ transform: rotate(225deg);
892
+ margin-top: 4px;
893
+ }
894
+ .summary-drawer-toggle strong {
895
+ font-variant-numeric: tabular-nums;
896
+ letter-spacing: -0.02em;
897
+ margin-left: auto;
898
+ }
899
+ .summary-drawer-body {
900
+ display: grid;
901
+ gap: 6px;
902
+ padding: 0 8px 8px;
903
+ border-top: 1px solid var(--tickean-border, #e3e8ee);
904
+ width: 100%;
905
+ }
906
+ .summary-drawer-body > * {
907
+ display: block;
908
+ width: 100%;
909
+ min-width: 0;
910
+ }
911
+ .summary-drawer:not([open]) .summary-drawer-body { display: none; }
912
+ .checkout-sticky {
913
+ display: grid;
914
+ gap: 0;
915
+ background: color-mix(in srgb, var(--tickean-bg, #fff) 92%, transparent);
916
+ backdrop-filter: blur(10px);
917
+ border-top: 1px solid var(--tickean-border, #dcfce7);
918
+ box-shadow: 0 -6px 18px rgba(15, 23, 42, 0.06);
919
+ padding-bottom: env(safe-area-inset-bottom, 0px);
920
+ }
921
+ .checkout-sticky[hidden] { display: none !important; }
922
+ .checkout-dock {
923
+ display: grid;
924
+ grid-template-columns: 1fr auto 1fr;
925
+ align-items: center;
926
+ gap: 8px;
927
+ padding: 8px 12px 4px;
928
+ }
929
+ .checkout-dock[hidden] { display: none !important; }
930
+ .checkout-sticky .powered {
931
+ width: 100%;
932
+ margin: 0;
933
+ padding: 2px 10px 8px;
934
+ }
935
+ .dock-side {
936
+ display: flex;
937
+ align-items: center;
938
+ min-width: 0;
939
+ }
940
+ .dock-side-start { justify-content: flex-start; }
941
+ .dock-side-end { justify-content: flex-end; }
942
+ .dock-total {
943
+ display: grid;
944
+ gap: 0;
945
+ min-width: 0;
946
+ line-height: 1.15;
947
+ text-align: center;
948
+ justify-items: center;
949
+ }
950
+ .dock-total .muted { font-size: 0.68rem; }
951
+ .dock-total strong {
952
+ font-size: 0.98rem;
953
+ font-variant-numeric: tabular-nums;
954
+ letter-spacing: -0.02em;
955
+ }
956
+ .dock-back {
957
+ padding: 9px 12px;
958
+ min-height: 40px;
959
+ flex-shrink: 0;
960
+ }
961
+ .dock-next {
962
+ padding: 9px 16px;
963
+ min-height: 40px;
964
+ min-width: 112px;
965
+ }
966
+ .step-panels { display: grid; gap: 8px; width: 100%; }
967
+ .step-panel[hidden] { display: none !important; }
968
+ .step-panel {
969
+ width: 100%;
970
+ animation: tickean-fade-up 280ms cubic-bezier(0.22, 1, 0.36, 1);
971
+ }
972
+ .step-panel > * {
973
+ display: block;
974
+ width: 100%;
975
+ min-width: 0;
976
+ }
977
+ .checkout-aside,
978
+ .checkout-layout {
979
+ width: 100%;
980
+ }
981
+ .step-nav {
982
+ display: flex;
983
+ gap: 8px;
984
+ justify-content: space-between;
985
+ flex-wrap: wrap;
986
+ align-items: center;
987
+ margin-top: 2px;
988
+ }
989
+ .step-nav .spacer { flex: 1; }
990
+ .done-card {
991
+ text-align: center;
992
+ padding: 16px 12px;
993
+ background: var(--tickean-surface, #f6f9fc);
136
994
  border-radius: var(--tickean-radius, 10px);
137
- padding: var(--tickean-space, 12px);
138
- color: var(--tickean-muted, #6b7280);
995
+ border: 1px solid var(--tickean-border, #e3e8ee);
996
+ }
997
+ .done-card h3 {
998
+ margin: 0 0 6px;
999
+ font-size: 1.15rem;
1000
+ letter-spacing: -0.03em;
1001
+ }
1002
+ .done-icon {
1003
+ width: 40px;
1004
+ height: 40px;
1005
+ margin: 0 auto 10px;
1006
+ border-radius: 999px;
1007
+ display: grid;
1008
+ place-items: center;
1009
+ background: color-mix(in srgb, var(--tickean-success, #0d9488) 16%, transparent);
1010
+ color: var(--tickean-success, #0d9488);
1011
+ font-size: 1.15rem;
1012
+ font-weight: 700;
1013
+ }
1014
+ .done-actions {
1015
+ display: grid;
1016
+ gap: 8px;
1017
+ margin-top: 14px;
1018
+ }
1019
+ .done-primary {
1020
+ display: inline-flex;
1021
+ align-items: center;
1022
+ justify-content: center;
1023
+ min-height: 42px;
1024
+ padding: 10px 14px;
1025
+ border-radius: 999px;
1026
+ background: var(--tickean-primary, #16a34a);
1027
+ color: var(--tickean-primary-fg, #fff);
1028
+ text-decoration: none;
1029
+ font-weight: 650;
1030
+ font-size: 0.92rem;
1031
+ box-shadow: 0 2px 8px color-mix(in srgb, var(--tickean-primary, #16a34a) 22%, transparent);
1032
+ }
1033
+ .done-primary:hover {
1034
+ background: var(--tickean-primary-hover, #15803d);
1035
+ }
1036
+ .done-actions .secondary {
1037
+ width: 100%;
1038
+ }
1039
+ .powered {
1040
+ display: inline-flex;
1041
+ align-items: center;
1042
+ justify-content: center;
1043
+ gap: 5px;
1044
+ margin: 0 auto;
1045
+ padding: 4px 8px;
1046
+ color: var(--tickean-muted, #697386);
1047
+ text-decoration: none;
1048
+ font-size: 0.7rem;
1049
+ font-weight: 500;
1050
+ transition: color 140ms ease;
1051
+ }
1052
+ .powered:hover { color: var(--tickean-fg, #0a2540); }
1053
+ .powered strong {
1054
+ color: inherit;
1055
+ font-weight: 700;
1056
+ letter-spacing: -0.02em;
1057
+ }
1058
+ .powered-mark {
1059
+ width: 16px;
1060
+ height: 16px;
1061
+ border-radius: 5px;
1062
+ background: linear-gradient(135deg, #22c55e, #16a34a);
1063
+ box-shadow: 0 2px 6px rgba(22, 163, 74, 0.35);
1064
+ }
1065
+ .loading-shell {
1066
+ padding: calc(var(--tickean-space, 14px) * 1.5);
1067
+ border-radius: calc(var(--tickean-radius, 12px) + 4px);
1068
+ border: 1px solid var(--tickean-border, #e3e8ee);
1069
+ background: var(--tickean-bg, #fff);
1070
+ box-shadow: var(--tickean-shadow-lg);
1071
+ display: grid;
1072
+ gap: 14px;
1073
+ }
1074
+ .skeleton {
1075
+ height: 14px;
1076
+ border-radius: 8px;
1077
+ background: linear-gradient(
1078
+ 90deg,
1079
+ var(--tickean-surface, #f6f9fc) 0%,
1080
+ color-mix(in srgb, var(--tickean-surface, #f6f9fc) 40%, #fff) 50%,
1081
+ var(--tickean-surface, #f6f9fc) 100%
1082
+ );
1083
+ background-size: 200% 100%;
1084
+ animation: tickean-shimmer 1.2s ease-in-out infinite;
1085
+ }
1086
+ .skeleton.lg { height: 22px; width: 55%; }
1087
+ .skeleton.md { height: 44px; }
1088
+ .skeleton.sm { width: 35%; }
1089
+ .loading-label {
1090
+ display: flex;
1091
+ align-items: center;
1092
+ gap: 8px;
1093
+ color: var(--tickean-muted, #697386);
1094
+ font-size: 0.9rem;
1095
+ animation: tickean-pulse 1.4s ease-in-out infinite;
1096
+ }
1097
+ .summary-line {
1098
+ display: flex;
1099
+ justify-content: space-between;
1100
+ gap: 8px;
1101
+ font-size: 0.82rem;
1102
+ }
1103
+ .badge {
1104
+ display: inline-flex;
1105
+ align-items: center;
1106
+ gap: 4px;
1107
+ padding: 2px 7px;
1108
+ border-radius: 999px;
1109
+ background: color-mix(in srgb, var(--tickean-success, #0d9488) 14%, transparent);
1110
+ color: var(--tickean-success, #0d9488);
1111
+ font-size: 0.7rem;
1112
+ font-weight: 600;
1113
+ }
1114
+
1115
+ /* Mobile-first dock + density */
1116
+ @media (max-width: 640px) {
1117
+ :host {
1118
+ max-width: 100%;
1119
+ font-size: 14.5px;
1120
+ }
1121
+ .checkout-steps {
1122
+ border-radius: 0;
1123
+ border-left: none;
1124
+ border-right: none;
1125
+ box-shadow: none;
1126
+ padding: 8px 10px;
1127
+ }
1128
+ .checkout-steps.compact {
1129
+ padding-bottom: calc(96px + env(safe-area-inset-bottom, 0px));
1130
+ }
1131
+ .step-caption,
1132
+ .stepper-label {
1133
+ display: none;
1134
+ }
1135
+ .stepper-item {
1136
+ justify-items: center;
1137
+ }
1138
+ .checkout-sticky {
1139
+ position: fixed;
1140
+ left: 0;
1141
+ right: 0;
1142
+ bottom: 0;
1143
+ z-index: 35;
1144
+ }
1145
+ .phone-row {
1146
+ grid-template-columns: minmax(104px, 34%) 1fr;
1147
+ gap: 6px;
1148
+ }
1149
+ }
1150
+
1151
+ @media (min-width: 641px) {
1152
+ .checkout-steps.compact {
1153
+ padding-bottom: 10px;
1154
+ }
1155
+ .checkout-sticky {
1156
+ position: sticky;
1157
+ bottom: 0;
1158
+ border: 1px solid var(--tickean-border, #dcfce7);
1159
+ border-radius: 10px;
1160
+ margin-top: 2px;
1161
+ border-top: none;
1162
+ }
1163
+ .summary-drawer {
1164
+ border: 1px solid var(--tickean-border, #dcfce7);
1165
+ background: var(--tickean-bg, #fff);
1166
+ }
1167
+ }
1168
+
1169
+ @media (prefers-reduced-motion: reduce) {
1170
+ .reveal, .step-panel, .skeleton, .loading-label, .ticket, button {
1171
+ animation: none !important;
1172
+ transition: none !important;
1173
+ }
139
1174
  }
140
1175
  `;
141
1176
  function applyAppearance(host, appearance) {
@@ -179,16 +1214,92 @@ var catalogs = {
179
1214
  verifyOtp: "Verificar",
180
1215
  total: "Total",
181
1216
  pay: "Confirmar compra",
1217
+ creatingPayment: "Creando pago\u2026",
182
1218
  transferInstructions: "Datos para transferir",
183
1219
  alias: "Alias",
184
1220
  cvu: "CVU",
185
1221
  amount: "Monto",
186
1222
  redirectPay: "Continuar al pago",
1223
+ redirectingPay: "Te estamos llevando a Mercado Pago\u2026",
187
1224
  providerPlaceholder: "Complet\xE1 el pago con tu proveedor",
188
1225
  completed: "\xA1Compra completada!",
189
1226
  error: "Ocurri\xF3 un error",
190
1227
  emptyCart: "Eleg\xED al menos una entrada",
191
- quantity: "Cantidad"
1228
+ quantity: "Cantidad",
1229
+ stepTickets: "Entradas",
1230
+ stepBuyer: "Tus datos",
1231
+ stepPayment: "Pago",
1232
+ stepTransfer: "Transferir",
1233
+ stepDone: "Listo",
1234
+ next: "Continuar",
1235
+ back: "Volver",
1236
+ stepOf: "Paso {current} de {total}",
1237
+ doneSubtitle: "Te enviamos la confirmaci\xF3n por email y WhatsApp.",
1238
+ viewTickets: "Ver mis entradas",
1239
+ buyAgain: "Seguir comprando",
1240
+ country: "Pa\xEDs",
1241
+ verified: "Identidad verificada",
1242
+ sending: "Enviando\u2026",
1243
+ verifying: "Verificando\u2026",
1244
+ namePlaceholder: "Nombre y apellido",
1245
+ poweredBy: "Powered by",
1246
+ orderSummary: "Resumen",
1247
+ itemsCount: "{count} \xEDtem(s)",
1248
+ passes: "Pases",
1249
+ discountToggle: "\xBFTen\xE9s un c\xF3digo?",
1250
+ otpTitle: "Verific\xE1 tu tel\xE9fono",
1251
+ otpHint: "Te enviamos un c\xF3digo de 6 d\xEDgitos",
1252
+ resendOtp: "Reenviar c\xF3digo",
1253
+ close: "Cerrar",
1254
+ attendees: "Asistentes",
1255
+ attendeeN: "Asistente {n}",
1256
+ firstName: "Nombre",
1257
+ lastName: "Apellido",
1258
+ continueToOtp: "Enviar c\xF3digo",
1259
+ viewSummary: "Ver resumen",
1260
+ showTimes: "Horarios",
1261
+ soldOut: "Agotado",
1262
+ comingSoon: "Pr\xF3ximamente",
1263
+ lastTicket: "\xA1\xDAltima!",
1264
+ lastTickets: "\xA1\xDAltimas {count}!",
1265
+ unlocked: "Desbloqueada",
1266
+ promoCodeHint: "\xBFTen\xE9s un c\xF3digo? Ingresalo arriba para desbloquear entradas ocultas.",
1267
+ promoUnlockHint: "Ingresalo antes de elegir entradas para desbloquear opciones exclusivas.",
1268
+ discountCodeHint: "Ingresalo ac\xE1 para aplicar el descuento a tu compra.",
1269
+ promoUnlockPlaceholder: "Ej: ARTISTAS",
1270
+ promoUnlockSuccess: "C\xF3digo v\xE1lido. Se revelaron entradas exclusivas.",
1271
+ promoUnlockSuccessNamed: "C\xF3digo v\xE1lido. Desbloqueaste: {names}.",
1272
+ promoUnlockApplied: "C\xF3digo v\xE1lido. Se aplicar\xE1 al pagar.",
1273
+ promoNxM: "{label}: llev\xE1 {buy}, pag\xE1 {pay}",
1274
+ promoNth: "{label}: {percent}% off en la unidad {unit}",
1275
+ paymentMethods: "M\xE9todo de pago",
1276
+ choosePayment: "Eleg\xED un m\xE9todo",
1277
+ payTransfer: "Transferencia bancaria",
1278
+ payTransferDesc: "Transfer\xEDs desde tu banco o billetera. Las entradas se env\xEDan al confirmar el pago.",
1279
+ payTransferHint: "Al confirmar te mostramos alias/CVU para transferir el monto exacto.",
1280
+ payMercadoPago: "Mercado Pago",
1281
+ payMercadoPagoDesc: "Tarjeta, dinero en cuenta o saldo de Mercado Pago.",
1282
+ payCard: "Tarjeta",
1283
+ payCardDesc: "D\xE9bito o cr\xE9dito, inclusive internacionales.",
1284
+ payFree: "Gratis",
1285
+ payFreeDesc: "Sin cargo para este evento.",
1286
+ transferPendingHint: "Cuando acreditemos la transferencia te enviamos las entradas por email/WhatsApp.",
1287
+ transferExactAmount: "Monto exacto a transferir",
1288
+ transferWaiting: "Esperando confirmaci\xF3n de tu transferencia\u2026",
1289
+ copy: "Copiar",
1290
+ copied: "Copiado",
1291
+ changePaymentMethod: "Cambiar m\xE9todo",
1292
+ subtotal: "Subtotal",
1293
+ serviceCharge: "Cargo de servicio",
1294
+ promoDiscount: "Promoci\xF3n",
1295
+ accountHolder: "Titular",
1296
+ bank: "Banco",
1297
+ acceptTerms: "Acepto los t\xE9rminos",
1298
+ onboardHint: "No encontramos una cuenta con ese tel\xE9fono. Complet\xE1 tus datos para continuar.",
1299
+ onboardRequired: "Nombre y email son obligatorios",
1300
+ emailInvalid: "Email inv\xE1lido",
1301
+ changePhone: "Cambiar tel\xE9fono",
1302
+ welcomeBack: "Hola {name}, te enviamos un c\xF3digo para verificar."
192
1303
  },
193
1304
  "es-CL": {
194
1305
  loading: "Cargando checkout\u2026",
@@ -203,16 +1314,92 @@ var catalogs = {
203
1314
  verifyOtp: "Verificar",
204
1315
  total: "Total",
205
1316
  pay: "Confirmar compra",
1317
+ creatingPayment: "Creando pago\u2026",
206
1318
  transferInstructions: "Datos para transferir",
207
1319
  alias: "Alias",
208
1320
  cvu: "CVU",
209
1321
  amount: "Monto",
210
1322
  redirectPay: "Continuar al pago",
1323
+ redirectingPay: "Te estamos llevando a Mercado Pago\u2026",
211
1324
  providerPlaceholder: "Completa el pago con tu proveedor",
212
1325
  completed: "\xA1Compra completada!",
213
1326
  error: "Ocurri\xF3 un error",
214
1327
  emptyCart: "Elige al menos una entrada",
215
- quantity: "Cantidad"
1328
+ quantity: "Cantidad",
1329
+ stepTickets: "Entradas",
1330
+ stepBuyer: "Tus datos",
1331
+ stepPayment: "Pago",
1332
+ stepTransfer: "Transferir",
1333
+ stepDone: "Listo",
1334
+ next: "Continuar",
1335
+ back: "Volver",
1336
+ stepOf: "Paso {current} de {total}",
1337
+ doneSubtitle: "Te enviamos la confirmaci\xF3n por email y WhatsApp.",
1338
+ viewTickets: "Ver mis entradas",
1339
+ buyAgain: "Seguir comprando",
1340
+ country: "Pa\xEDs",
1341
+ verified: "Identidad verificada",
1342
+ sending: "Enviando\u2026",
1343
+ verifying: "Verificando\u2026",
1344
+ namePlaceholder: "Nombre y apellido",
1345
+ poweredBy: "Powered by",
1346
+ orderSummary: "Resumen",
1347
+ itemsCount: "{count} \xEDtem(s)",
1348
+ passes: "Pases",
1349
+ discountToggle: "\xBFTienes un c\xF3digo?",
1350
+ otpTitle: "Verifica tu tel\xE9fono",
1351
+ otpHint: "Te enviamos un c\xF3digo de 6 d\xEDgitos",
1352
+ resendOtp: "Reenviar c\xF3digo",
1353
+ close: "Cerrar",
1354
+ attendees: "Asistentes",
1355
+ attendeeN: "Asistente {n}",
1356
+ firstName: "Nombre",
1357
+ lastName: "Apellido",
1358
+ continueToOtp: "Enviar c\xF3digo",
1359
+ viewSummary: "Ver resumen",
1360
+ showTimes: "Horarios",
1361
+ soldOut: "Agotado",
1362
+ comingSoon: "Pr\xF3ximamente",
1363
+ lastTicket: "\xA1\xDAltima!",
1364
+ lastTickets: "\xA1\xDAltimas {count}!",
1365
+ unlocked: "Desbloqueada",
1366
+ promoCodeHint: "\xBFTienes un c\xF3digo? Ingr\xE9salo arriba para desbloquear entradas ocultas.",
1367
+ promoUnlockHint: "Ingr\xE9salo antes de elegir entradas para desbloquear opciones exclusivas.",
1368
+ discountCodeHint: "Ingr\xE9salo ac\xE1 para aplicar el descuento a tu compra.",
1369
+ promoUnlockPlaceholder: "Ej: ARTISTAS",
1370
+ promoUnlockSuccess: "C\xF3digo v\xE1lido. Se revelaron entradas exclusivas.",
1371
+ promoUnlockSuccessNamed: "C\xF3digo v\xE1lido. Desbloqueaste: {names}.",
1372
+ promoUnlockApplied: "C\xF3digo v\xE1lido. Se aplicar\xE1 al pagar.",
1373
+ promoNxM: "{label}: lleva {buy}, paga {pay}",
1374
+ promoNth: "{label}: {percent}% off en la unidad {unit}",
1375
+ paymentMethods: "M\xE9todo de pago",
1376
+ choosePayment: "Elige un m\xE9todo",
1377
+ payTransfer: "Transferencia bancaria",
1378
+ payTransferDesc: "Transfieres desde tu banco o billetera. Las entradas se env\xEDan al confirmar el pago.",
1379
+ payTransferHint: "Al confirmar te mostramos alias/CVU para transferir el monto exacto.",
1380
+ payMercadoPago: "Mercado Pago",
1381
+ payMercadoPagoDesc: "Tarjeta, dinero en cuenta o saldo de Mercado Pago.",
1382
+ payCard: "Tarjeta",
1383
+ payCardDesc: "D\xE9bito o cr\xE9dito, inclusive internacionales.",
1384
+ payFree: "Gratis",
1385
+ payFreeDesc: "Sin cargo para este evento.",
1386
+ transferPendingHint: "Cuando acreditemos la transferencia te enviamos las entradas por email/WhatsApp.",
1387
+ transferExactAmount: "Monto exacto a transferir",
1388
+ transferWaiting: "Esperando confirmaci\xF3n de tu transferencia\u2026",
1389
+ copy: "Copiar",
1390
+ copied: "Copiado",
1391
+ changePaymentMethod: "Cambiar m\xE9todo",
1392
+ subtotal: "Subtotal",
1393
+ serviceCharge: "Cargo de servicio",
1394
+ promoDiscount: "Promoci\xF3n",
1395
+ accountHolder: "Titular",
1396
+ bank: "Banco",
1397
+ acceptTerms: "Acepto los t\xE9rminos",
1398
+ onboardHint: "No encontramos una cuenta con ese tel\xE9fono. Completa tus datos para continuar.",
1399
+ onboardRequired: "Nombre y email son obligatorios",
1400
+ emailInvalid: "Email inv\xE1lido",
1401
+ changePhone: "Cambiar tel\xE9fono",
1402
+ welcomeBack: "Hola {name}, te enviamos un c\xF3digo para verificar."
216
1403
  },
217
1404
  en: {
218
1405
  loading: "Loading checkout\u2026",
@@ -227,22 +1414,105 @@ var catalogs = {
227
1414
  verifyOtp: "Verify",
228
1415
  total: "Total",
229
1416
  pay: "Confirm purchase",
1417
+ creatingPayment: "Creating payment\u2026",
230
1418
  transferInstructions: "Transfer details",
231
1419
  alias: "Alias",
232
1420
  cvu: "CVU",
233
1421
  amount: "Amount",
234
1422
  redirectPay: "Continue to payment",
1423
+ redirectingPay: "Taking you to Mercado Pago\u2026",
235
1424
  providerPlaceholder: "Complete payment with your provider",
236
1425
  completed: "Purchase completed!",
237
1426
  error: "Something went wrong",
238
1427
  emptyCart: "Select at least one ticket",
239
- quantity: "Quantity"
1428
+ quantity: "Quantity",
1429
+ stepTickets: "Tickets",
1430
+ stepBuyer: "Your details",
1431
+ stepPayment: "Payment",
1432
+ stepTransfer: "Transfer",
1433
+ stepDone: "Done",
1434
+ next: "Continue",
1435
+ back: "Back",
1436
+ stepOf: "Step {current} of {total}",
1437
+ doneSubtitle: "We sent your confirmation by email and WhatsApp.",
1438
+ viewTickets: "View my tickets",
1439
+ buyAgain: "Buy again",
1440
+ country: "Country",
1441
+ verified: "Identity verified",
1442
+ sending: "Sending\u2026",
1443
+ verifying: "Verifying\u2026",
1444
+ namePlaceholder: "Full name",
1445
+ poweredBy: "Powered by",
1446
+ orderSummary: "Summary",
1447
+ itemsCount: "{count} item(s)",
1448
+ passes: "Passes",
1449
+ discountToggle: "Have a code?",
1450
+ otpTitle: "Verify your phone",
1451
+ otpHint: "We sent a 6-digit code",
1452
+ resendOtp: "Resend code",
1453
+ close: "Close",
1454
+ attendees: "Attendees",
1455
+ attendeeN: "Attendee {n}",
1456
+ firstName: "First name",
1457
+ lastName: "Last name",
1458
+ continueToOtp: "Send code",
1459
+ viewSummary: "View summary",
1460
+ showTimes: "Show times",
1461
+ soldOut: "Sold out",
1462
+ comingSoon: "Coming soon",
1463
+ lastTicket: "Last one!",
1464
+ lastTickets: "Only {count} left!",
1465
+ unlocked: "Unlocked",
1466
+ promoCodeHint: "Have a code? Enter it above to unlock hidden tickets.",
1467
+ promoUnlockHint: "Enter it before choosing tickets to unlock exclusive options.",
1468
+ discountCodeHint: "Enter it here to apply the discount to your purchase.",
1469
+ promoUnlockPlaceholder: "E.g. ARTISTAS",
1470
+ promoUnlockSuccess: "Valid code. Exclusive tickets were revealed.",
1471
+ promoUnlockSuccessNamed: "Valid code. Unlocked: {names}.",
1472
+ promoUnlockApplied: "Valid code. It will apply at payment.",
1473
+ promoNxM: "{label}: buy {buy}, pay {pay}",
1474
+ promoNth: "{label}: {percent}% off on unit {unit}",
1475
+ paymentMethods: "Payment method",
1476
+ choosePayment: "Choose a method",
1477
+ payTransfer: "Bank transfer",
1478
+ payTransferDesc: "Pay from your bank or wallet. Tickets are sent once payment is confirmed.",
1479
+ payTransferHint: "After confirming we\u2019ll show alias/CVU for the exact amount.",
1480
+ payMercadoPago: "Mercado Pago",
1481
+ payMercadoPagoDesc: "Card, account money, or Mercado Pago balance.",
1482
+ payCard: "Card",
1483
+ payCardDesc: "Debit or credit, including international cards.",
1484
+ payFree: "Free",
1485
+ payFreeDesc: "No charge for this event.",
1486
+ transferPendingHint: "Once we confirm the transfer we\u2019ll send your tickets by email/WhatsApp.",
1487
+ transferExactAmount: "Exact amount to transfer",
1488
+ transferWaiting: "Waiting for your transfer confirmation\u2026",
1489
+ copy: "Copy",
1490
+ copied: "Copied",
1491
+ changePaymentMethod: "Change method",
1492
+ subtotal: "Subtotal",
1493
+ serviceCharge: "Service fee",
1494
+ promoDiscount: "Promotion",
1495
+ accountHolder: "Account holder",
1496
+ bank: "Bank",
1497
+ acceptTerms: "I accept the terms",
1498
+ onboardHint: "We couldn't find an account with that phone. Add your details to continue.",
1499
+ onboardRequired: "Name and email are required",
1500
+ emailInvalid: "Invalid email",
1501
+ changePhone: "Change phone",
1502
+ welcomeBack: "Hi {name}, we sent you a verification code."
240
1503
  }
241
1504
  };
242
1505
  function t(locale, key) {
243
1506
  const resolved = locale && catalogs[locale] ? locale : "es-AR";
244
1507
  return catalogs[resolved][key];
245
1508
  }
1509
+ function tFormat(locale, key, vars) {
1510
+ let out = t(locale, key);
1511
+ for (const [k, v] of Object.entries(vars)) {
1512
+ out = out.replace(new RegExp(`\\{${k}\\}`, "g"), String(v));
1513
+ }
1514
+ return out;
1515
+ }
246
1516
  function resolveLocale(locale) {
247
1517
  if (locale && catalogs[locale]) return locale;
248
1518
  return "es-AR";
@@ -332,85 +1602,499 @@ var TickeanElementBase = class extends NativeHTMLElement {
332
1602
  }
333
1603
  };
334
1604
 
335
- // src/elements/ticket-selector.ts
336
- var TickeanTicketSelector = class extends TickeanElementBase {
337
- renderBody() {
338
- const state = this.state;
339
- if (!state || state.loading) {
340
- return `<div class="wrap muted">${t(this.elementLocale, "loading")}</div>`;
341
- }
342
- const options = (state.event?.shows || []).flatMap(
343
- (show) => (show.showOptions || []).map((opt) => ({ ...opt, showTitle: show.title }))
344
- );
345
- return `
346
- <div class="wrap stack" part="ticket-selector">
347
- <strong>${t(this.elementLocale, "tickets")}</strong>
348
- ${options.map((opt) => {
349
- const qty = state.cart.find((c) => c.showOptionId === opt.id)?.amount || 0;
350
- return `
351
- <div class="ticket" data-option-id="${opt.id}">
352
- <div class="row">
353
- <div>
354
- <div><strong>${escapeHtml(opt.name || opt.id)}</strong></div>
355
- <div class="muted">${escapeHtml(opt.showTitle || "")}</div>
356
- </div>
357
- <div>${this.money(Number(opt.price || 0))}</div>
358
- </div>
359
- <div class="row qty" style="margin-top:10px" role="group" aria-label="${t(this.elementLocale, "quantity")} ${escapeHtml(opt.name || "")}">
360
- <button type="button" class="secondary" data-action="dec" aria-label="-">\u2212</button>
361
- <span aria-live="polite">${qty}</span>
362
- <button type="button" class="secondary" data-action="inc" aria-label="+">+</button>
363
- </div>
364
- </div>
365
- `;
366
- }).join("")}
367
- </div>
368
- `;
1605
+ // src/event-meta.ts
1606
+ function parseInstant(input) {
1607
+ if (input instanceof Date) {
1608
+ return Number.isNaN(input.getTime()) ? null : input;
369
1609
  }
370
- afterRender() {
371
- this.root.querySelectorAll("[data-option-id]").forEach((el) => {
372
- const optionId = el.getAttribute("data-option-id");
373
- const qty = this.state?.cart.find((c) => c.showOptionId === optionId)?.amount || 0;
374
- el.querySelector('[data-action="dec"]')?.addEventListener("click", () => {
375
- this.controller?.setCartItem(optionId, Math.max(0, qty - 1));
376
- });
377
- el.querySelector('[data-action="inc"]')?.addEventListener("click", () => {
378
- this.controller?.setCartItem(optionId, qty + 1);
379
- });
380
- });
1610
+ const value = typeof input === "string" ? input.trim() : "";
1611
+ if (!value) return null;
1612
+ const parsed = new Date(value);
1613
+ return Number.isNaN(parsed.getTime()) ? null : parsed;
1614
+ }
1615
+ function isValidTimeZone(tz) {
1616
+ if (!tz?.trim()) return false;
1617
+ try {
1618
+ new Intl.DateTimeFormat("en-US", { timeZone: tz }).format();
1619
+ return true;
1620
+ } catch {
1621
+ return false;
381
1622
  }
382
- };
383
- function escapeHtml(value) {
384
- return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
385
1623
  }
386
- if (typeof customElements !== "undefined" && !customElements.get("tickean-ticket-selector")) {
1624
+ function resolveEventTimeZone(location) {
1625
+ if (isValidTimeZone(location?.timeZone)) return location.timeZone.trim();
1626
+ const country = (location?.country || "").toLowerCase();
1627
+ if (country.includes("chile") || country === "cl") return "America/Santiago";
1628
+ if (country.includes("m\xE9xico") || country.includes("mexico") || country === "mx") {
1629
+ return "America/Mexico_City";
1630
+ }
1631
+ if (country.includes("united states") || country === "us") return "America/New_York";
1632
+ return "America/Argentina/Buenos_Aires";
1633
+ }
1634
+ function formatInZone(input, timeZone, locale, options) {
1635
+ const instant = parseInstant(input);
1636
+ if (!instant) return "";
1637
+ const tz = isValidTimeZone(timeZone) ? timeZone : "America/Argentina/Buenos_Aires";
1638
+ return new Intl.DateTimeFormat(locale, { ...options, timeZone: tz }).format(instant);
1639
+ }
1640
+ function formatEventDate(input, timeZone, locale = "es-AR", options = {
1641
+ weekday: "long",
1642
+ day: "numeric",
1643
+ month: "long",
1644
+ year: "numeric"
1645
+ }) {
1646
+ return formatInZone(input, timeZone, locale, options);
1647
+ }
1648
+ function formatEventTime(input, timeZone, locale = "es-AR") {
1649
+ return formatInZone(input, timeZone, locale, {
1650
+ hour: "2-digit",
1651
+ minute: "2-digit",
1652
+ hour12: false
1653
+ });
1654
+ }
1655
+ function getCalendarDateParts(input, timeZone) {
1656
+ const instant = parseInstant(input);
1657
+ if (!instant) return null;
1658
+ const tz = isValidTimeZone(timeZone) ? timeZone : "America/Argentina/Buenos_Aires";
1659
+ const parts = new Intl.DateTimeFormat("en-CA", {
1660
+ timeZone: tz,
1661
+ year: "numeric",
1662
+ month: "2-digit",
1663
+ day: "2-digit"
1664
+ }).formatToParts(instant);
1665
+ const values = Object.fromEntries(parts.map((p) => [p.type, p.value]));
1666
+ const year = Number(values.year);
1667
+ const month = Number(values.month);
1668
+ const day = Number(values.day);
1669
+ if (![year, month, day].every(Number.isInteger)) return null;
1670
+ const key = `${String(year).padStart(4, "0")}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
1671
+ return { key, year, month, day };
1672
+ }
1673
+ function capitalize(label) {
1674
+ if (!label) return label;
1675
+ return label.charAt(0).toUpperCase() + label.slice(1);
1676
+ }
1677
+ function andWord(locale) {
1678
+ return locale.toLowerCase().startsWith("en") ? "and" : "y";
1679
+ }
1680
+ function joinWithAnd(items, word) {
1681
+ if (items.length <= 1) return items[0] || "";
1682
+ if (items.length === 2) return `${items[0]} ${word} ${items[1]}`;
1683
+ return `${items.slice(0, -1).join(", ")} ${word} ${items[items.length - 1]}`;
1684
+ }
1685
+ function monthName(year, month, locale) {
1686
+ const probe = new Date(Date.UTC(year, month - 1, 1, 12, 0, 0));
1687
+ return capitalize(
1688
+ new Intl.DateTimeFormat(locale, { month: "long", timeZone: "UTC" }).format(probe)
1689
+ );
1690
+ }
1691
+ function formatMultiDayShowDates(shows, timeZone, locale = "es-AR") {
1692
+ const byKey = /* @__PURE__ */ new Map();
1693
+ for (const show of shows ?? []) {
1694
+ const parts = getCalendarDateParts(show.date, timeZone);
1695
+ if (!parts || byKey.has(parts.key)) continue;
1696
+ byKey.set(parts.key, parts);
1697
+ }
1698
+ const days = [...byKey.values()].sort((a, b) => a.key.localeCompare(b.key));
1699
+ if (days.length <= 1) return null;
1700
+ const word = andWord(locale);
1701
+ const isEs = word === "y";
1702
+ const sameMonthYear = days.every(
1703
+ (d) => d.month === days[0].month && d.year === days[0].year
1704
+ );
1705
+ if (sameMonthYear) {
1706
+ const dayList = joinWithAnd(
1707
+ days.map((d) => String(d.day)),
1708
+ word
1709
+ );
1710
+ const month = monthName(days[0].year, days[0].month, locale);
1711
+ return isEs ? `${dayList} de ${month} ${days[0].year}` : `${dayList} ${month} ${days[0].year}`;
1712
+ }
1713
+ return joinWithAnd(
1714
+ days.map(
1715
+ (d) => formatEventDate(
1716
+ `${d.key}T12:00:00Z`,
1717
+ "UTC",
1718
+ locale,
1719
+ { day: "numeric", month: "short" }
1720
+ )
1721
+ ),
1722
+ word
1723
+ );
1724
+ }
1725
+ function formatShowTimeRange(show, timeZone, locale = "es-AR") {
1726
+ const start = formatEventTime(show?.date, timeZone, locale);
1727
+ if (!start) return "";
1728
+ const end = formatEventTime(show?.endDate, timeZone, locale);
1729
+ if (end && end !== start) return `${start}\u2013${end}`;
1730
+ return start;
1731
+ }
1732
+ function formatShowDayScheduleLabel(show, timeZone, locale = "es-AR", options) {
1733
+ const datePart = formatEventDate(show.date, timeZone, locale, {
1734
+ weekday: "short",
1735
+ day: "numeric",
1736
+ month: "short"
1737
+ });
1738
+ const timePart = formatShowTimeRange(show, timeZone, locale);
1739
+ const parts = [];
1740
+ if (options?.includeTitle && show.title?.trim()) parts.push(show.title.trim());
1741
+ if (datePart) parts.push(datePart);
1742
+ if (timePart) parts.push(timePart);
1743
+ return parts.join(" \xB7 ");
1744
+ }
1745
+ function getEventFlyerUrl(images) {
1746
+ const list = images || [];
1747
+ if (!list.length) return null;
1748
+ const main = list.find((img) => img.kind === "main") || list[0];
1749
+ return main?.mobileUrl || main?.desktopUrl || main?.url || null;
1750
+ }
1751
+ function buildEventScheduleSummary(shows, timeZone, locale = "es-AR") {
1752
+ const list = [...shows || []].sort((a, b) => {
1753
+ const da = parseInstant(a.date)?.getTime() ?? 0;
1754
+ const db = parseInstant(b.date)?.getTime() ?? 0;
1755
+ return da - db;
1756
+ });
1757
+ const multi = formatMultiDayShowDates(list, timeZone, locale);
1758
+ const earliest = list[0];
1759
+ const dateLabel = multi || formatEventDate(earliest?.date, timeZone, locale, {
1760
+ weekday: "long",
1761
+ day: "numeric",
1762
+ month: "long",
1763
+ year: "numeric"
1764
+ }) || "";
1765
+ const showLines = list.map(
1766
+ (show) => formatShowDayScheduleLabel(show, timeZone, locale, {
1767
+ includeTitle: list.length > 1
1768
+ })
1769
+ );
1770
+ if (!multi && list.length === 1) {
1771
+ const time = formatShowTimeRange(list[0], timeZone, locale);
1772
+ return {
1773
+ dateLabel: time ? `${dateLabel} \xB7 ${time}` : dateLabel,
1774
+ showLines: []
1775
+ };
1776
+ }
1777
+ return { dateLabel, showLines };
1778
+ }
1779
+
1780
+ // src/elements/ticket-selector.ts
1781
+ function isAbono(opt) {
1782
+ return Boolean(opt.accessScope && opt.accessScope !== "SINGLE_SHOW");
1783
+ }
1784
+ function availableStock(opt) {
1785
+ if (typeof opt.availableStock === "number") return Math.max(0, opt.availableStock);
1786
+ const stock = Number(opt.stock ?? 0);
1787
+ const sold = Number(opt.sold ?? 0);
1788
+ const reserved = Number(opt.reserved ?? 0);
1789
+ return Math.max(0, stock - sold - reserved);
1790
+ }
1791
+ function isSoldOut(opt) {
1792
+ if (opt.status === "SOLD_OUT" || opt.status === "INACTIVE") return true;
1793
+ return availableStock(opt) <= 0;
1794
+ }
1795
+ function isComingSoon(opt) {
1796
+ return opt.status === "COMING_SOON";
1797
+ }
1798
+ function activePromotion(opt) {
1799
+ const nxm = opt.promotionNxM;
1800
+ if (nxm?.enabled && nxm.buyQty && nxm.payQty) {
1801
+ return { kind: "nxm", data: nxm };
1802
+ }
1803
+ const qty = opt.quantityDiscount;
1804
+ if (qty?.enabled && typeof qty.percentOff === "number") {
1805
+ return { kind: "qty", data: qty };
1806
+ }
1807
+ return null;
1808
+ }
1809
+ function promoLegend(opt, locale) {
1810
+ const promo = activePromotion(opt);
1811
+ if (!promo) return null;
1812
+ if (promo.kind === "nxm") {
1813
+ const label2 = promo.data.label || "Promo";
1814
+ return tFormat(locale, "promoNxM", {
1815
+ label: label2,
1816
+ buy: promo.data.buyQty || 0,
1817
+ pay: promo.data.payQty || 0
1818
+ });
1819
+ }
1820
+ const label = promo.data.label || "Promo";
1821
+ const unit = typeof promo.data.targetUnit === "number" && promo.data.targetUnit >= 2 ? promo.data.targetUnit : 2;
1822
+ return tFormat(locale, "promoNth", {
1823
+ label,
1824
+ unit,
1825
+ percent: promo.data.percentOff || 0
1826
+ });
1827
+ }
1828
+ function promoBadge(opt) {
1829
+ const promo = activePromotion(opt);
1830
+ if (!promo) return null;
1831
+ if (promo.kind === "nxm") {
1832
+ return promo.data.label || `${promo.data.buyQty}x${promo.data.payQty}`;
1833
+ }
1834
+ return promo.data.label || `-${promo.data.percentOff}%`;
1835
+ }
1836
+ function passSubtitle(opt, showCount, locale) {
1837
+ if (!isAbono(opt)) return "";
1838
+ const covered = opt.accessScope === "ALL_EVENT_SHOWS" ? showCount : opt.coveredShowIds?.length || showCount;
1839
+ const coverage = covered <= 1 ? "1 d\xEDa" : locale.startsWith("en") ? `${covered} days` : `${covered} d\xEDas`;
1840
+ const issuance = opt.passIssuanceMode === "SINGLE_PASS" ? locale.startsWith("en") ? "1 QR for all days" : "1 QR para todos los d\xEDas" : locale.startsWith("en") ? "1 ticket per day" : "1 entrada por d\xEDa";
1841
+ return `${locale.startsWith("en") ? "Pass" : "Pase"} \xB7 ${coverage} \xB7 ${issuance}`;
1842
+ }
1843
+ function maxQty(opt) {
1844
+ const stock = availableStock(opt);
1845
+ const cap = opt.maxPerPurchase && opt.maxPerPurchase > 0 ? opt.maxPerPurchase : stock;
1846
+ return Math.max(0, Math.min(stock, cap || stock));
1847
+ }
1848
+ var TickeanTicketSelector = class extends TickeanElementBase {
1849
+ constructor() {
1850
+ super(...arguments);
1851
+ this.firstPaint = true;
1852
+ }
1853
+ renderBody() {
1854
+ const state = this.state;
1855
+ if (!state || state.loading) {
1856
+ return `<div class="wrap muted">${t(this.elementLocale, "loading")}</div>`;
1857
+ }
1858
+ const shows = state.event?.shows || [];
1859
+ const showCount = shows.length;
1860
+ const locale = this.elementLocale;
1861
+ const timeZone = resolveEventTimeZone(
1862
+ state.event?.location || {}
1863
+ );
1864
+ const allOptions = shows.flatMap(
1865
+ (show) => (show.showOptions || []).map((opt) => ({
1866
+ ...opt,
1867
+ showId: show.id,
1868
+ showTitle: show.title,
1869
+ showDate: show.date,
1870
+ showEndDate: show.endDate
1871
+ }))
1872
+ );
1873
+ const abonoMap = /* @__PURE__ */ new Map();
1874
+ for (const opt of allOptions) {
1875
+ if (isAbono(opt) && !abonoMap.has(opt.id)) abonoMap.set(opt.id, opt);
1876
+ }
1877
+ const abonos = [...abonoMap.values()];
1878
+ const dayOptions = allOptions.filter((opt) => !isAbono(opt));
1879
+ const days = /* @__PURE__ */ new Map();
1880
+ for (const opt of dayOptions) {
1881
+ const key = opt.showId;
1882
+ if (!days.has(key)) days.set(key, []);
1883
+ days.get(key).push(opt);
1884
+ }
1885
+ const renderOption = (opt, pass = false) => {
1886
+ const qty = state.cart.find((c) => c.showOptionId === opt.id)?.amount || 0;
1887
+ const soldOut = isSoldOut(opt);
1888
+ const comingSoon = isComingSoon(opt);
1889
+ const remaining = availableStock(opt);
1890
+ const lowStock = !soldOut && !comingSoon && remaining > 0 && remaining <= 10;
1891
+ const max = maxQty(opt);
1892
+ const subtitle = pass ? passSubtitle(opt, showCount, locale) : formatShowTimeRange(
1893
+ { date: opt.showDate, endDate: opt.showEndDate },
1894
+ timeZone,
1895
+ locale
1896
+ ) || opt.showTitle || "";
1897
+ const legend = promoLegend(opt, locale);
1898
+ const badge = promoBadge(opt);
1899
+ const gated = opt.catalogVisibility === "PROMO_GATED";
1900
+ let stockBadge = "";
1901
+ if (soldOut) {
1902
+ stockBadge = `<span class="stock-badge sold-out">${escapeHtml(t(locale, "soldOut"))}</span>`;
1903
+ } else if (comingSoon) {
1904
+ stockBadge = `<span class="stock-badge coming-soon">${escapeHtml(t(locale, "comingSoon"))}</span>`;
1905
+ } else if (lowStock) {
1906
+ stockBadge = remaining === 1 ? `<span class="stock-badge low-stock">${escapeHtml(t(locale, "lastTicket"))}</span>` : `<span class="stock-badge low-stock">${escapeHtml(tFormat(locale, "lastTickets", { count: remaining }))}</span>`;
1907
+ }
1908
+ return `
1909
+ <div class="ticket ${pass ? "pass" : ""} ${soldOut ? "sold-out" : ""} ${comingSoon ? "coming-soon" : ""}"
1910
+ data-option-id="${opt.id}" data-selected="${qty > 0}" data-sold-out="${soldOut}" data-max="${max}">
1911
+ <div class="row">
1912
+ <div style="min-width:0">
1913
+ <div style="display:flex;gap:6px;align-items:center;flex-wrap:wrap">
1914
+ <strong>${escapeHtml(opt.name || opt.id)}</strong>
1915
+ ${pass ? `<span class="pass-badge">${locale.startsWith("en") ? "Pass" : "Pase"}</span>` : ""}
1916
+ ${gated ? `<span class="pass-badge" style="color:var(--tickean-primary);background:color-mix(in srgb,var(--tickean-primary) 12%,transparent)">${escapeHtml(t(locale, "unlocked"))}</span>` : ""}
1917
+ ${badge ? `<span class="promo-badge">${escapeHtml(badge)}</span>` : ""}
1918
+ ${stockBadge}
1919
+ </div>
1920
+ <div class="muted" style="font-size:0.78rem;margin-top:2px">${escapeHtml(subtitle || "")}</div>
1921
+ ${legend ? `<div class="promo-legend">${escapeHtml(legend)}</div>` : ""}
1922
+ </div>
1923
+ <div style="font-weight:650;font-variant-numeric:tabular-nums;white-space:nowrap">${this.money(Number(opt.price || 0))}</div>
1924
+ </div>
1925
+ <div class="row" style="margin-top:8px">
1926
+ <span class="muted" style="font-size:0.78rem">${soldOut ? escapeHtml(t(locale, "soldOut")) : t(locale, "quantity")}</span>
1927
+ ${soldOut || comingSoon ? `<span class="muted" style="font-size:0.78rem">\u2014</span>` : `<div class="qty" role="group" aria-label="${t(locale, "quantity")} ${escapeHtml(opt.name || "")}">
1928
+ <button type="button" class="secondary" data-action="dec" aria-label="-" ${qty <= 0 ? "disabled" : ""}>\u2212</button>
1929
+ <span data-qty aria-live="polite">${qty}</span>
1930
+ <button type="button" class="secondary" data-action="inc" aria-label="+" ${qty >= max ? "disabled" : ""}>+</button>
1931
+ </div>`}
1932
+ </div>
1933
+ </div>
1934
+ `;
1935
+ };
1936
+ const dayBlocks = [...days.entries()].map(([showId, opts]) => {
1937
+ const show = shows.find((s) => s.id === showId);
1938
+ const schedule = show ? formatShowDayScheduleLabel(
1939
+ {
1940
+ date: show.date,
1941
+ endDate: show.endDate,
1942
+ title: show.title
1943
+ },
1944
+ timeZone,
1945
+ locale,
1946
+ { includeTitle: Boolean(show.title) }
1947
+ ) : "";
1948
+ const label = schedule || show?.title || t(locale, "tickets");
1949
+ return `
1950
+ <div class="stack" style="gap:8px">
1951
+ <p class="group-title">${escapeHtml(label)}</p>
1952
+ ${opts.map((o) => renderOption(o, false)).join("")}
1953
+ </div>
1954
+ `;
1955
+ }).join("");
1956
+ return `
1957
+ <div class="stack" part="ticket-selector" style="gap:8px">
1958
+ <p class="section-title">${t(locale, "tickets")}</p>
1959
+ ${abonos.length ? `<div class="stack" style="gap:8px">
1960
+ <p class="group-title" style="color:var(--tickean-pass)">${escapeHtml(t(locale, "passes"))}</p>
1961
+ ${abonos.map((o) => renderOption(o, true)).join("")}
1962
+ </div>` : ""}
1963
+ ${days.size > 1 ? dayBlocks : dayOptions.map((o) => renderOption(o, false)).join("")}
1964
+ </div>
1965
+ `;
1966
+ }
1967
+ afterRender() {
1968
+ if (this.firstPaint) {
1969
+ this.firstPaint = false;
1970
+ this.setAttribute("data-animated", "");
1971
+ }
1972
+ this.root.querySelectorAll("[data-option-id]").forEach((el) => {
1973
+ if (el.dataset.soldOut === "true") return;
1974
+ const optionId = el.getAttribute("data-option-id");
1975
+ const max = Number(el.dataset.max || 99);
1976
+ const qty = this.state?.cart.find((c) => c.showOptionId === optionId)?.amount || 0;
1977
+ el.querySelector('[data-action="dec"]')?.addEventListener("click", () => {
1978
+ this.controller?.setCartItem(optionId, Math.max(0, qty - 1));
1979
+ });
1980
+ el.querySelector('[data-action="inc"]')?.addEventListener("click", () => {
1981
+ this.controller?.setCartItem(optionId, Math.min(max, qty + 1));
1982
+ });
1983
+ });
1984
+ }
1985
+ };
1986
+ function escapeHtml(value) {
1987
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
1988
+ }
1989
+ if (typeof customElements !== "undefined" && !customElements.get("tickean-ticket-selector")) {
387
1990
  customElements.define("tickean-ticket-selector", TickeanTicketSelector);
388
1991
  }
389
1992
 
390
1993
  // src/elements/discount.ts
1994
+ function countUnlockedOptions(state) {
1995
+ if (!state?.event?.shows) return 0;
1996
+ let n = 0;
1997
+ for (const show of state.event.shows) {
1998
+ for (const opt of show.showOptions || []) {
1999
+ if (opt.catalogVisibility === "PROMO_GATED") n += 1;
2000
+ }
2001
+ }
2002
+ return n;
2003
+ }
2004
+ function shouldShowDiscountCode(params) {
2005
+ if (params.discountCode) return true;
2006
+ return params.discountsEnabled !== false;
2007
+ }
391
2008
  var TickeanDiscount = class extends TickeanElementBase {
2009
+ constructor() {
2010
+ super(...arguments);
2011
+ this.lastSuccess = null;
2012
+ }
2013
+ static get observedAttributes() {
2014
+ return [...TickeanElementBase.observedAttributes, "mode"];
2015
+ }
2016
+ mode() {
2017
+ return this.getAttribute("mode") === "unlock" ? "unlock" : "discount";
2018
+ }
392
2019
  renderBody() {
393
- const code = this.state?.discountCode || "";
2020
+ const state = this.state;
2021
+ if (!state) return "";
2022
+ const mode = this.mode();
2023
+ const unlockedCount = countUnlockedOptions(state);
2024
+ const discountsEnabled = state.session?.capabilities?.discounts;
2025
+ const visible = mode === "unlock" ? Boolean(state.event?.hasPromoGatedShowOptions) || unlockedCount > 0 : shouldShowDiscountCode({
2026
+ discountsEnabled,
2027
+ discountCode: state.discountCode
2028
+ });
2029
+ if (!visible) return "";
2030
+ const code = state.discountCode || "";
2031
+ const locale = this.elementLocale;
2032
+ const quoting = Boolean(state.isQuoting);
2033
+ const error = state.error?.code === "discount_invalid" ? state.error.message : null;
2034
+ const title = t(locale, "discountToggle");
2035
+ const hint = mode === "unlock" ? t(locale, "promoUnlockHint") : t(locale, "discountCodeHint");
394
2036
  return `
395
- <div class="wrap stack" part="discount">
396
- <label>
397
- ${t(this.elementLocale, "discount")}
398
- <input id="code" value="${escapeAttr(code)}" autocomplete="off" />
399
- </label>
400
- <button type="button" id="apply">${t(this.elementLocale, "apply")}</button>
401
- ${this.state?.error ? `<div class="danger" role="alert">${escapeHtml2(this.state.error.message)}</div>` : ""}
2037
+ <div class="promo-unlock" part="discount" data-mode="${mode}">
2038
+ <div class="promo-unlock-copy">
2039
+ <p class="promo-unlock-title">${escapeHtml2(title)}</p>
2040
+ <p class="promo-unlock-hint muted">${escapeHtml2(hint)}</p>
2041
+ </div>
2042
+ <div class="promo-unlock-row">
2043
+ <input
2044
+ id="code"
2045
+ value="${escapeAttr(code)}"
2046
+ autocomplete="off"
2047
+ spellcheck="false"
2048
+ placeholder="${escapeAttr(t(locale, "promoUnlockPlaceholder"))}"
2049
+ aria-label="${escapeAttr(t(locale, "discount"))}"
2050
+ ${quoting ? "disabled" : ""}
2051
+ />
2052
+ <button type="button" class="secondary" id="apply" ${quoting ? "disabled" : ""}>
2053
+ ${quoting ? escapeHtml2(t(locale, "verifying")) : escapeHtml2(t(locale, "apply"))}
2054
+ </button>
2055
+ </div>
2056
+ ${error ? `<p class="promo-unlock-msg danger" role="alert">${escapeHtml2(error)}</p>` : this.lastSuccess ? `<p class="promo-unlock-msg success" role="status">${escapeHtml2(this.lastSuccess)}</p>` : ""}
402
2057
  </div>
403
2058
  `;
404
2059
  }
405
2060
  afterRender() {
406
2061
  const input = this.root.querySelector("#code");
407
- this.root.querySelector("#apply")?.addEventListener("click", async () => {
408
- const value = input?.value?.trim() || "";
409
- try {
410
- await this.controller?.applyDiscountCode(value);
411
- } catch {
2062
+ input?.addEventListener("input", () => {
2063
+ if (input) input.value = input.value.toUpperCase();
2064
+ });
2065
+ input?.addEventListener("keydown", (ev) => {
2066
+ if (ev.key === "Enter") {
2067
+ ev.preventDefault();
2068
+ void this.applyCode();
412
2069
  }
413
2070
  });
2071
+ this.root.querySelector("#apply")?.addEventListener("click", () => {
2072
+ void this.applyCode();
2073
+ });
2074
+ }
2075
+ async applyCode() {
2076
+ const input = this.root.querySelector("#code");
2077
+ const value = input?.value?.trim() || "";
2078
+ if (!value) return;
2079
+ this.lastSuccess = null;
2080
+ try {
2081
+ const quote = await this.controller?.applyDiscountCode(value);
2082
+ const unlocked = quote?.unlockedShowOptions || [];
2083
+ const names = unlocked.map((o) => o.name).filter((n) => Boolean(n));
2084
+ const locale = this.elementLocale;
2085
+ if (names.length > 0) {
2086
+ this.lastSuccess = tFormat(locale, "promoUnlockSuccessNamed", {
2087
+ names: names.join(", ")
2088
+ });
2089
+ } else if ((quote?.unlockedShowOptionIds || []).length > 0 || unlocked.length > 0) {
2090
+ this.lastSuccess = t(locale, "promoUnlockSuccess");
2091
+ } else {
2092
+ this.lastSuccess = t(locale, "promoUnlockApplied");
2093
+ }
2094
+ this.render();
2095
+ } catch {
2096
+ this.lastSuccess = null;
2097
+ }
414
2098
  }
415
2099
  };
416
2100
  function escapeAttr(value) {
@@ -423,65 +2107,360 @@ if (typeof customElements !== "undefined" && !customElements.get("tickean-discou
423
2107
  customElements.define("tickean-discount", TickeanDiscount);
424
2108
  }
425
2109
 
2110
+ // src/phone-countries.ts
2111
+ var PHONE_COUNTRIES = [
2112
+ { iso: "AR", name: "Argentina", dial: "54", flag: "\u{1F1E6}\u{1F1F7}" },
2113
+ { iso: "CL", name: "Chile", dial: "56", flag: "\u{1F1E8}\u{1F1F1}" },
2114
+ { iso: "UY", name: "Uruguay", dial: "598", flag: "\u{1F1FA}\u{1F1FE}" },
2115
+ { iso: "PY", name: "Paraguay", dial: "595", flag: "\u{1F1F5}\u{1F1FE}" },
2116
+ { iso: "BO", name: "Bolivia", dial: "591", flag: "\u{1F1E7}\u{1F1F4}" },
2117
+ { iso: "BR", name: "Brasil", dial: "55", flag: "\u{1F1E7}\u{1F1F7}" },
2118
+ { iso: "PE", name: "Per\xFA", dial: "51", flag: "\u{1F1F5}\u{1F1EA}" },
2119
+ { iso: "CO", name: "Colombia", dial: "57", flag: "\u{1F1E8}\u{1F1F4}" },
2120
+ { iso: "EC", name: "Ecuador", dial: "593", flag: "\u{1F1EA}\u{1F1E8}" },
2121
+ { iso: "MX", name: "M\xE9xico", dial: "52", flag: "\u{1F1F2}\u{1F1FD}" },
2122
+ { iso: "US", name: "United States", dial: "1", flag: "\u{1F1FA}\u{1F1F8}" },
2123
+ { iso: "ES", name: "Espa\xF1a", dial: "34", flag: "\u{1F1EA}\u{1F1F8}" },
2124
+ { iso: "GB", name: "United Kingdom", dial: "44", flag: "\u{1F1EC}\u{1F1E7}" }
2125
+ ];
2126
+ var byIso = new Map(PHONE_COUNTRIES.map((c) => [c.iso, c]));
2127
+ function countryFromIso(iso) {
2128
+ const key = (iso || "").toUpperCase();
2129
+ return byIso.get(key) || PHONE_COUNTRIES[0];
2130
+ }
2131
+ function countryFromLocale(locale) {
2132
+ const l = (locale || "").toLowerCase();
2133
+ if (l.startsWith("es-cl")) return countryFromIso("CL");
2134
+ if (l.startsWith("es-mx")) return countryFromIso("MX");
2135
+ if (l.startsWith("pt") || l.startsWith("pt-br")) return countryFromIso("BR");
2136
+ if (l.startsWith("en-gb")) return countryFromIso("GB");
2137
+ if (l.startsWith("en")) return countryFromIso("US");
2138
+ if (l.startsWith("es")) return countryFromIso("AR");
2139
+ return PHONE_COUNTRIES[0];
2140
+ }
2141
+ function toE164(dial, national) {
2142
+ const digits = national.replace(/\D/g, "").replace(/^0+/, "");
2143
+ const d = dial.replace(/\D/g, "");
2144
+ return `+${d}${digits}`;
2145
+ }
2146
+ function nationalPlaceholder(iso) {
2147
+ switch (iso) {
2148
+ case "AR":
2149
+ return "11 2345 6789";
2150
+ case "CL":
2151
+ return "9 1234 5678";
2152
+ case "MX":
2153
+ return "55 1234 5678";
2154
+ case "US":
2155
+ return "(555) 123-4567";
2156
+ default:
2157
+ return "Phone number";
2158
+ }
2159
+ }
2160
+ async function detectCountryIso(apiBaseUrl) {
2161
+ const base = (apiBaseUrl || "https://api.tickean.com").replace(/\/$/, "");
2162
+ try {
2163
+ const res = await fetch(`${base}/bff/marketplace/location/ip`, {
2164
+ credentials: "omit"
2165
+ });
2166
+ if (!res.ok) return null;
2167
+ const json = await res.json();
2168
+ if (json?.available && json.countryCode) {
2169
+ return String(json.countryCode).toUpperCase();
2170
+ }
2171
+ } catch {
2172
+ }
2173
+ return null;
2174
+ }
2175
+
426
2176
  // src/elements/buyer-verification.ts
427
2177
  var TickeanBuyerVerification = class extends TickeanElementBase {
428
2178
  constructor() {
429
2179
  super(...arguments);
430
- this.phone = "+5491112345678";
2180
+ this.country = countryFromLocale("es-AR");
2181
+ this.national = "";
431
2182
  this.name = "";
432
2183
  this.email = "";
433
2184
  this.code = "";
2185
+ this.geoResolved = false;
2186
+ this.countryTouched = false;
2187
+ this.step = "phone";
2188
+ this.knownBuyer = null;
2189
+ this.busy = null;
2190
+ this.firstPaint = true;
2191
+ this.formError = "";
2192
+ }
2193
+ connectedCallback() {
2194
+ super.connectedCallback();
2195
+ void this.resolveGeoCountry();
2196
+ }
2197
+ apiBaseUrl() {
2198
+ return this.getAttribute("api-base-url") || this.closest("tickean-checkout")?.getAttribute("api-base-url") || "https://api.tickean.com";
2199
+ }
2200
+ async resolveGeoCountry() {
2201
+ if (this.geoResolved || this.countryTouched) return;
2202
+ this.country = countryFromLocale(this.elementLocale);
2203
+ const iso = await detectCountryIso(this.apiBaseUrl());
2204
+ if (!this.countryTouched && iso) {
2205
+ this.country = countryFromIso(iso);
2206
+ this.geoResolved = true;
2207
+ this.render();
2208
+ } else {
2209
+ this.geoResolved = true;
2210
+ }
2211
+ }
2212
+ fullPhone() {
2213
+ return toE164(this.country.dial, this.national);
434
2214
  }
435
2215
  renderBody() {
436
2216
  const verified = this.state?.buyerVerified;
437
- return `
438
- <div class="wrap stack" part="buyer-verification">
439
- <label>
440
- ${t(this.elementLocale, "phone")}
441
- <input id="phone" type="tel" value="${escapeAttr2(this.phone)}" autocomplete="tel" />
442
- </label>
443
- <label>
444
- ${t(this.elementLocale, "name")}
445
- <input id="name" value="${escapeAttr2(this.name)}" autocomplete="name" />
446
- </label>
447
- <label>
448
- ${t(this.elementLocale, "email")}
449
- <input id="email" type="email" value="${escapeAttr2(this.email)}" autocomplete="email" />
450
- </label>
451
- <button type="button" class="secondary" id="send">${t(this.elementLocale, "sendOtp")}</button>
452
- <label>
453
- ${t(this.elementLocale, "otpCode")}
454
- <input id="otp" inputmode="numeric" value="${escapeAttr2(this.code)}" autocomplete="one-time-code" />
2217
+ const locale = this.elementLocale;
2218
+ const options = PHONE_COUNTRIES.map(
2219
+ (c) => `<option value="${c.iso}" ${c.iso === this.country.iso ? "selected" : ""}>${c.flag} +${c.dial}</option>`
2220
+ ).join("");
2221
+ if (verified) {
2222
+ return `
2223
+ <div class="wrap stack" part="buyer-verification">
2224
+ <div class="badge">\u2713 ${escapeHtml3(t(locale, "verified"))}</div>
2225
+ <div class="muted">${escapeHtml3(this.state?.buyer?.name || this.state?.buyer?.phone || this.fullPhone())}</div>
2226
+ </div>
2227
+ `;
2228
+ }
2229
+ const phoneBlock = `
2230
+ <label class="field">
2231
+ <span>${escapeHtml3(t(locale, "phone"))}</span>
2232
+ <div class="phone-row">
2233
+ <div class="phone-country">
2234
+ <select id="country" aria-label="${escapeAttr2(t(locale, "country"))}" ${this.step !== "phone" ? "disabled" : ""}>${options}</select>
2235
+ </div>
2236
+ <input id="phone" type="tel" inputmode="tel" autocomplete="tel-national"
2237
+ placeholder="${escapeAttr2(nationalPlaceholder(this.country.iso))}"
2238
+ value="${escapeAttr2(this.national)}" ${this.step !== "phone" ? "disabled" : ""} />
2239
+ </div>
2240
+ </label>`;
2241
+ const onboardBlock = this.step === "onboard" ? `
2242
+ <p class="muted" style="margin:0;font-size:0.8rem">${escapeHtml3(t(locale, "onboardHint"))}</p>
2243
+ <label class="field">
2244
+ <span>${escapeHtml3(t(locale, "name"))}</span>
2245
+ <input id="name" value="${escapeAttr2(this.name)}" autocomplete="name" placeholder="${escapeAttr2(t(locale, "namePlaceholder"))}" />
455
2246
  </label>
456
- <button type="button" id="verify">${t(this.elementLocale, "verifyOtp")}</button>
457
- ${verified ? `<div class="muted" role="status">${escapeHtml3(this.state?.buyer?.name || this.state?.buyer?.phone || "OK")}</div>` : ""}
2247
+ <label class="field">
2248
+ <span>${escapeHtml3(t(locale, "email"))}</span>
2249
+ <input id="email" type="email" value="${escapeAttr2(this.email)}" autocomplete="email" placeholder="name@email.com" />
2250
+ </label>` : "";
2251
+ const welcome = this.step === "otp" && this.knownBuyer?.name ? `<p class="muted" style="margin:0;font-size:0.8rem">${escapeHtml3(t(locale, "welcomeBack").replace("{name}", this.knownBuyer.name))}</p>` : "";
2252
+ const primaryLabel = this.busy === "lookup" || this.busy === "send" ? t(locale, "sending") : this.step === "onboard" ? t(locale, "continueToOtp") : t(locale, "continueToOtp");
2253
+ return `
2254
+ <div class="wrap stack" part="buyer-verification" style="gap:10px">
2255
+ <p class="section-title">${escapeHtml3(t(locale, "stepBuyer"))}</p>
2256
+ ${phoneBlock}
2257
+ ${onboardBlock}
2258
+ ${welcome}
2259
+ ${this.step === "phone" || this.step === "onboard" ? `<button type="button" id="continue" ${this.busy ? "disabled" : ""}>
2260
+ ${escapeHtml3(primaryLabel)}
2261
+ </button>` : ""}
2262
+ ${this.step !== "phone" ? `<button type="button" class="ghost" id="change-phone">${escapeHtml3(t(locale, "changePhone"))}</button>` : ""}
2263
+ ${this.formError ? `<div class="danger" role="alert">${escapeHtml3(this.formError)}</div>` : ""}
2264
+ ${this.step === "otp" ? `<div class="sheet-backdrop" data-sheet>
2265
+ <div class="sheet" role="dialog" aria-modal="true" aria-label="${escapeAttr2(t(locale, "otpTitle"))}">
2266
+ <div class="row">
2267
+ <h3>${escapeHtml3(t(locale, "otpTitle"))}</h3>
2268
+ <button type="button" class="ghost" data-close>${escapeHtml3(t(locale, "close"))}</button>
2269
+ </div>
2270
+ <p class="muted" style="margin:0">${escapeHtml3(t(locale, "otpHint"))}</p>
2271
+ <p class="muted" style="margin:0;font-weight:600">${escapeHtml3(this.fullPhone())}</p>
2272
+ <div class="otp-digits" id="otp-boxes">
2273
+ ${[0, 1, 2, 3, 4, 5].map(
2274
+ (i) => `<input data-otp-i="${i}" inputmode="numeric" maxlength="1" autocomplete="${i === 0 ? "one-time-code" : "off"}" value="${escapeAttr2(this.code[i] || "")}" />`
2275
+ ).join("")}
2276
+ </div>
2277
+ <button type="button" id="verify" ${this.busy === "verify" ? "disabled" : ""}>
2278
+ ${escapeHtml3(this.busy === "verify" ? t(locale, "verifying") : t(locale, "verifyOtp"))}
2279
+ </button>
2280
+ <button type="button" class="secondary" id="resend" ${this.busy === "send" ? "disabled" : ""}>
2281
+ ${escapeHtml3(t(locale, "resendOtp"))}
2282
+ </button>
2283
+ </div>
2284
+ </div>` : ""}
458
2285
  </div>
459
2286
  `;
460
2287
  }
461
2288
  afterRender() {
2289
+ if (this.firstPaint) {
2290
+ this.firstPaint = false;
2291
+ this.setAttribute("data-animated", "");
2292
+ }
2293
+ const country = this.root.querySelector("#country");
462
2294
  const phone = this.root.querySelector("#phone");
463
2295
  const name = this.root.querySelector("#name");
464
2296
  const email = this.root.querySelector("#email");
465
- const otp = this.root.querySelector("#otp");
466
- const sync = () => {
467
- this.phone = phone?.value || "";
468
- this.name = name?.value || "";
469
- this.email = email?.value || "";
470
- this.code = otp?.value || "";
2297
+ const syncProfile = () => {
2298
+ this.national = phone?.value || this.national;
2299
+ this.name = name?.value ?? this.name;
2300
+ this.email = email?.value ?? this.email;
2301
+ };
2302
+ country?.addEventListener("change", () => {
2303
+ this.countryTouched = true;
2304
+ this.country = countryFromIso(country.value);
2305
+ syncProfile();
2306
+ this.render();
2307
+ });
2308
+ phone?.addEventListener("input", syncProfile);
2309
+ name?.addEventListener("input", syncProfile);
2310
+ email?.addEventListener("input", syncProfile);
2311
+ const sendOtp = async () => {
2312
+ this.busy = "send";
2313
+ this.formError = "";
2314
+ this.render();
2315
+ try {
2316
+ await this.controller?.sendOtp(this.fullPhone());
2317
+ this.step = "otp";
2318
+ this.code = "";
2319
+ } catch (err) {
2320
+ this.formError = err?.message || t(this.elementLocale, "error");
2321
+ } finally {
2322
+ this.busy = null;
2323
+ this.render();
2324
+ }
2325
+ };
2326
+ const continueFlow = async () => {
2327
+ syncProfile();
2328
+ this.formError = "";
2329
+ if (!this.national.replace(/\D/g, "")) {
2330
+ this.formError = t(this.elementLocale, "phone");
2331
+ this.render();
2332
+ return;
2333
+ }
2334
+ if (this.step === "onboard") {
2335
+ if (!this.name.trim() || !this.email.trim()) {
2336
+ this.formError = t(this.elementLocale, "onboardRequired");
2337
+ this.render();
2338
+ return;
2339
+ }
2340
+ if (!/^\S+@\S+\.\S+$/.test(this.email.trim())) {
2341
+ this.formError = t(this.elementLocale, "emailInvalid");
2342
+ this.render();
2343
+ return;
2344
+ }
2345
+ await sendOtp();
2346
+ return;
2347
+ }
2348
+ this.busy = "lookup";
2349
+ this.render();
2350
+ try {
2351
+ const result = await this.controller?.lookupBuyer(this.fullPhone());
2352
+ if (result?.exists) {
2353
+ this.knownBuyer = {
2354
+ name: result.buyer?.name,
2355
+ email: result.buyer?.email
2356
+ };
2357
+ this.name = result.buyer?.name || "";
2358
+ this.email = result.buyer?.email || "";
2359
+ await sendOtp();
2360
+ } else {
2361
+ this.knownBuyer = null;
2362
+ this.step = "onboard";
2363
+ this.busy = null;
2364
+ this.render();
2365
+ }
2366
+ } catch (err) {
2367
+ const status = err?.status;
2368
+ const message = String(err?.message || "");
2369
+ if (status === 404 || /Cannot POST .*buyer\/lookup/i.test(message)) {
2370
+ this.knownBuyer = { name: void 0 };
2371
+ await sendOtp();
2372
+ return;
2373
+ }
2374
+ this.formError = message || t(this.elementLocale, "error");
2375
+ this.busy = null;
2376
+ this.render();
2377
+ }
471
2378
  };
472
- this.root.querySelector("#send")?.addEventListener("click", async () => {
473
- sync();
474
- await this.controller?.sendOtp(this.phone);
2379
+ this.root.querySelector("#continue")?.addEventListener("click", () => {
2380
+ void continueFlow();
2381
+ });
2382
+ this.root.querySelector("#change-phone")?.addEventListener("click", () => {
2383
+ this.step = "phone";
2384
+ this.knownBuyer = null;
2385
+ this.code = "";
2386
+ this.formError = "";
2387
+ this.render();
2388
+ });
2389
+ this.root.querySelector("#resend")?.addEventListener("click", () => {
2390
+ void sendOtp();
2391
+ });
2392
+ this.root.querySelector("[data-close]")?.addEventListener("click", () => {
2393
+ this.step = this.knownBuyer ? "phone" : "onboard";
2394
+ this.render();
2395
+ });
2396
+ this.root.querySelector("[data-sheet]")?.addEventListener("click", (e) => {
2397
+ if (e.target.hasAttribute("data-sheet")) {
2398
+ this.step = this.knownBuyer ? "phone" : "onboard";
2399
+ this.render();
2400
+ }
475
2401
  });
476
- this.root.querySelector("#verify")?.addEventListener("click", async () => {
477
- sync();
478
- await this.controller?.verifyOtp({
479
- phone: this.phone,
480
- code: this.code,
481
- name: this.name || void 0,
482
- email: this.email || void 0
2402
+ const boxes = [
2403
+ ...this.root.querySelectorAll("[data-otp-i]")
2404
+ ];
2405
+ const readCode = () => boxes.map((b) => b.value.replace(/\D/g, "")).join("");
2406
+ const verify = async () => {
2407
+ this.code = readCode();
2408
+ if (this.code.length < 4) return;
2409
+ this.busy = "verify";
2410
+ this.formError = "";
2411
+ this.render();
2412
+ try {
2413
+ await this.controller?.verifyOtp({
2414
+ phone: this.fullPhone(),
2415
+ code: this.code,
2416
+ name: this.knownBuyer?.name ? void 0 : this.name || void 0,
2417
+ email: this.knownBuyer?.email ? void 0 : this.email || void 0
2418
+ });
2419
+ } catch (err) {
2420
+ const message = String(err?.message || "");
2421
+ if (/name and email are required/i.test(message)) {
2422
+ this.knownBuyer = null;
2423
+ this.step = "onboard";
2424
+ this.formError = t(this.elementLocale, "onboardRequired");
2425
+ } else {
2426
+ this.formError = message || t(this.elementLocale, "error");
2427
+ }
2428
+ } finally {
2429
+ this.busy = null;
2430
+ this.render();
2431
+ }
2432
+ };
2433
+ boxes.forEach((box, i) => {
2434
+ box.addEventListener("input", () => {
2435
+ const v = box.value.replace(/\D/g, "").slice(-1);
2436
+ box.value = v;
2437
+ if (v && boxes[i + 1]) boxes[i + 1].focus();
2438
+ this.code = readCode();
2439
+ if (this.code.length === 6) void verify();
2440
+ });
2441
+ box.addEventListener("keydown", (e) => {
2442
+ if (e.key === "Backspace" && !box.value && boxes[i - 1]) {
2443
+ boxes[i - 1].focus();
2444
+ }
483
2445
  });
2446
+ box.addEventListener("paste", (e) => {
2447
+ const text = e.clipboardData?.getData("text") || "";
2448
+ const digits = text.replace(/\D/g, "").slice(0, 6);
2449
+ if (!digits) return;
2450
+ e.preventDefault();
2451
+ digits.split("").forEach((d, idx) => {
2452
+ if (boxes[idx]) boxes[idx].value = d;
2453
+ });
2454
+ this.code = readCode();
2455
+ if (this.code.length === 6) void verify();
2456
+ });
2457
+ });
2458
+ this.root.querySelector("#verify")?.addEventListener("click", () => {
2459
+ void verify();
484
2460
  });
2461
+ if (this.step === "otp" && boxes[0]) {
2462
+ boxes[0].focus();
2463
+ }
485
2464
  }
486
2465
  };
487
2466
  function escapeAttr2(value) {
@@ -491,62 +2470,438 @@ function escapeHtml3(value) {
491
2470
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
492
2471
  }
493
2472
  if (typeof customElements !== "undefined" && !customElements.get("tickean-buyer-verification")) {
494
- if (typeof customElements !== "undefined") customElements.define("tickean-buyer-verification", TickeanBuyerVerification);
2473
+ customElements.define("tickean-buyer-verification", TickeanBuyerVerification);
2474
+ }
2475
+
2476
+ // src/elements/attendees.ts
2477
+ var TickeanAttendees = class extends TickeanElementBase {
2478
+ constructor() {
2479
+ super(...arguments);
2480
+ this.slots = [];
2481
+ this.hydrated = false;
2482
+ this.termsAccepted = false;
2483
+ }
2484
+ optionName(id) {
2485
+ for (const show of this.state?.event?.shows || []) {
2486
+ for (const opt of show.showOptions || []) {
2487
+ if (opt.id === id) return opt.name || id;
2488
+ }
2489
+ }
2490
+ return id;
2491
+ }
2492
+ rebuildSlots() {
2493
+ const cart = this.state?.cart || [];
2494
+ const prev = new Map(this.slots.map((s) => [s.key, s]));
2495
+ const next = [];
2496
+ for (const item of cart) {
2497
+ for (let i = 0; i < item.amount; i++) {
2498
+ const key = `${item.showOptionId}:${i}`;
2499
+ const existing = prev.get(key);
2500
+ next.push({
2501
+ key,
2502
+ showOptionId: item.showOptionId,
2503
+ label: this.optionName(item.showOptionId),
2504
+ firstName: existing?.firstName || "",
2505
+ lastName: existing?.lastName || "",
2506
+ email: existing?.email || "",
2507
+ phone: existing?.phone || ""
2508
+ });
2509
+ }
2510
+ }
2511
+ this.slots = next;
2512
+ this.emitAttendees();
2513
+ }
2514
+ emitAttendees() {
2515
+ const settings = this.state?.event?.registrationSettings;
2516
+ const attendees = this.slots.map((s) => ({
2517
+ showOptionId: s.showOptionId,
2518
+ firstName: s.firstName || void 0,
2519
+ lastName: s.lastName || void 0,
2520
+ email: settings?.requireAttendeeEmail ? s.email || void 0 : void 0,
2521
+ phone: settings?.requireAttendeePhone ? s.phone || void 0 : void 0
2522
+ }));
2523
+ this.dispatchEvent(
2524
+ new CustomEvent("attendees-change", {
2525
+ detail: { attendees, complete: this.isComplete() },
2526
+ bubbles: true,
2527
+ composed: true
2528
+ })
2529
+ );
2530
+ }
2531
+ isComplete() {
2532
+ const settings = this.state?.event?.registrationSettings;
2533
+ if (!this.slots.length) return false;
2534
+ if (settings?.terms?.required && !this.termsAccepted) return false;
2535
+ return this.slots.every((s) => {
2536
+ if (!s.firstName.trim() || !s.lastName.trim()) return false;
2537
+ if (settings?.requireAttendeeEmail && !s.email.trim()) return false;
2538
+ if (settings?.requireAttendeePhone && !s.phone.trim()) return false;
2539
+ return true;
2540
+ });
2541
+ }
2542
+ getAttendees() {
2543
+ const termsRequired = Boolean(
2544
+ this.state?.event?.registrationSettings?.terms?.required
2545
+ );
2546
+ return this.slots.map((s) => ({
2547
+ showOptionId: s.showOptionId,
2548
+ firstName: s.firstName || void 0,
2549
+ lastName: s.lastName || void 0,
2550
+ email: s.email || void 0,
2551
+ phone: s.phone || void 0,
2552
+ termsAccepted: termsRequired ? this.termsAccepted : void 0
2553
+ }));
2554
+ }
2555
+ renderBody() {
2556
+ if (!this.hydrated || this.slots.length === 0) {
2557
+ this.rebuildSlots();
2558
+ this.hydrated = true;
2559
+ } else {
2560
+ const expected = (this.state?.cart || []).reduce((n, i) => n + i.amount, 0);
2561
+ if (expected !== this.slots.length) this.rebuildSlots();
2562
+ }
2563
+ const locale = this.elementLocale;
2564
+ const settings = this.state?.event?.registrationSettings;
2565
+ if (!this.state?.event?.collectAttendeeData) {
2566
+ return `<div class="muted">${escapeHtml4(t(locale, "attendees"))}</div>`;
2567
+ }
2568
+ return `
2569
+ <div class="stack" part="attendees" style="gap:10px">
2570
+ <p class="section-title">${escapeHtml4(t(locale, "attendees"))}</p>
2571
+ ${this.slots.map(
2572
+ (slot, index) => `
2573
+ <div class="wrap stack" style="gap:8px;padding:10px 12px" data-slot="${escapeAttr3(slot.key)}">
2574
+ <div class="row">
2575
+ <strong style="font-size:0.9rem">${escapeHtml4(tFormat(locale, "attendeeN", { n: index + 1 }))}</strong>
2576
+ <span class="muted" style="font-size:0.78rem">${escapeHtml4(slot.label)}</span>
2577
+ </div>
2578
+ <div class="phone-row">
2579
+ <label class="field">
2580
+ <span>${escapeHtml4(t(locale, "firstName"))}</span>
2581
+ <input data-f="firstName" value="${escapeAttr3(slot.firstName)}" autocomplete="given-name" />
2582
+ </label>
2583
+ <label class="field">
2584
+ <span>${escapeHtml4(t(locale, "lastName"))}</span>
2585
+ <input data-f="lastName" value="${escapeAttr3(slot.lastName)}" autocomplete="family-name" />
2586
+ </label>
2587
+ </div>
2588
+ ${settings?.requireAttendeeEmail ? `<label class="field"><span>${escapeHtml4(t(locale, "email"))}</span>
2589
+ <input data-f="email" type="email" value="${escapeAttr3(slot.email)}" autocomplete="email" /></label>` : ""}
2590
+ ${settings?.requireAttendeePhone ? `<label class="field"><span>${escapeHtml4(t(locale, "phone"))}</span>
2591
+ <input data-f="phone" type="tel" value="${escapeAttr3(slot.phone)}" autocomplete="tel" /></label>` : ""}
2592
+ </div>
2593
+ `
2594
+ ).join("")}
2595
+ ${settings?.terms?.required ? `<label class="terms-row">
2596
+ <input type="checkbox" id="terms" ${this.termsAccepted ? "checked" : ""} />
2597
+ <span>
2598
+ ${escapeHtml4(settings.terms.label || t(locale, "acceptTerms"))}
2599
+ ${settings.terms.pdfUrl ? ` <a href="${escapeAttr3(settings.terms.pdfUrl)}" target="_blank" rel="noopener noreferrer">PDF</a>` : ""}
2600
+ </span>
2601
+ </label>` : ""}
2602
+ </div>
2603
+ `;
2604
+ }
2605
+ afterRender() {
2606
+ this.root.querySelectorAll("[data-slot]").forEach((el) => {
2607
+ const key = el.getAttribute("data-slot");
2608
+ const slot = this.slots.find((s) => s.key === key);
2609
+ if (!slot) return;
2610
+ el.querySelectorAll("[data-f]").forEach((input) => {
2611
+ input.addEventListener("input", () => {
2612
+ const field = input.getAttribute("data-f");
2613
+ if (field === "firstName" || field === "lastName" || field === "email" || field === "phone") {
2614
+ slot[field] = input.value;
2615
+ this.emitAttendees();
2616
+ }
2617
+ });
2618
+ });
2619
+ });
2620
+ this.root.querySelector("#terms")?.addEventListener("change", (e) => {
2621
+ this.termsAccepted = Boolean(e.target.checked);
2622
+ this.emitAttendees();
2623
+ });
2624
+ }
2625
+ };
2626
+ function escapeAttr3(value) {
2627
+ return value.replace(/"/g, "&quot;");
2628
+ }
2629
+ function escapeHtml4(value) {
2630
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
2631
+ }
2632
+ if (typeof customElements !== "undefined" && !customElements.get("tickean-attendees")) {
2633
+ customElements.define("tickean-attendees", TickeanAttendees);
495
2634
  }
496
2635
 
497
2636
  // src/elements/payment.ts
2637
+ var METHOD_ORDER = ["TRANSFER", "MERCADO_PAGO", "CARD", "FREE"];
2638
+ var METHOD_META = {
2639
+ TRANSFER: { titleKey: "payTransfer", descKey: "payTransferDesc" },
2640
+ MERCADO_PAGO: { titleKey: "payMercadoPago", descKey: "payMercadoPagoDesc" },
2641
+ CARD: { titleKey: "payCard", descKey: "payCardDesc" },
2642
+ FREE: { titleKey: "payFree", descKey: "payFreeDesc" }
2643
+ };
498
2644
  var TickeanPayment = class extends TickeanElementBase {
2645
+ constructor() {
2646
+ super(...arguments);
2647
+ this.busy = false;
2648
+ this.selectedMethod = null;
2649
+ this.copiedKey = null;
2650
+ this.copiedTimer = null;
2651
+ this.redirectedUrl = null;
2652
+ }
499
2653
  static get observedAttributes() {
500
- return [...TickeanElementBase.observedAttributes, "payment-method", "currency"];
2654
+ return [
2655
+ ...TickeanElementBase.observedAttributes,
2656
+ "payment-method",
2657
+ "currency",
2658
+ "view"
2659
+ ];
2660
+ }
2661
+ disconnectedCallback() {
2662
+ if (this.copiedTimer) clearTimeout(this.copiedTimer);
2663
+ super.disconnectedCallback();
2664
+ }
2665
+ view() {
2666
+ return this.getAttribute("view") === "transfer" ? "transfer" : "method";
2667
+ }
2668
+ methods() {
2669
+ const fromEvent = this.state?.event?.availablePaymentMethods || [];
2670
+ const attr = this.getAttribute("payment-method");
2671
+ if (fromEvent.length) {
2672
+ return [...fromEvent].sort(
2673
+ (a, b) => METHOD_ORDER.indexOf(a) - METHOD_ORDER.indexOf(b)
2674
+ );
2675
+ }
2676
+ return [attr || "TRANSFER"];
2677
+ }
2678
+ resolveMethod() {
2679
+ const methods = this.methods();
2680
+ const attr = this.getAttribute("payment-method");
2681
+ const purchaseMethod = this.state?.purchase?.paymentMethod;
2682
+ if (this.selectedMethod && methods.includes(this.selectedMethod)) {
2683
+ return this.selectedMethod;
2684
+ }
2685
+ if (purchaseMethod && methods.includes(purchaseMethod)) return purchaseMethod;
2686
+ if (attr && methods.includes(attr)) return attr;
2687
+ if (methods.length === 1) return methods[0];
2688
+ return this.selectedMethod || "";
501
2689
  }
502
2690
  renderBody() {
503
2691
  const state = this.state;
504
- const nextAction = state?.nextAction || { type: "none" };
505
- const paymentMethod = this.getAttribute("payment-method") || state?.event?.availablePaymentMethods?.[0] || "TRANSFER";
2692
+ const locale = this.elementLocale;
506
2693
  const currency = this.getAttribute("currency") || "ARS";
2694
+ const view = this.view();
507
2695
  if (state?.phase === "completed") {
508
- return `<div class="wrap" part="payment"><div role="status">${t(this.elementLocale, "completed")}</div></div>`;
2696
+ return `<div class="wrap reveal" part="payment"><div class="badge" role="status">\u2713 ${t(locale, "completed")}</div></div>`;
2697
+ }
2698
+ if (view === "transfer") {
2699
+ return this.renderTransferView(locale, currency);
2700
+ }
2701
+ return this.renderMethodView(locale, currency);
2702
+ }
2703
+ redirectUrl() {
2704
+ const next = this.state?.nextAction;
2705
+ if (next?.type === "redirect" && next.url) return next.url;
2706
+ const payment = this.state?.payment;
2707
+ return payment?.redirectUrl || payment?.initPoint || null;
2708
+ }
2709
+ renderMethodView(locale, currency) {
2710
+ const state = this.state;
2711
+ const methods = this.methods();
2712
+ const paymentMethod = this.resolveMethod();
2713
+ const multi = methods.length > 1;
2714
+ const needsPick = multi && !paymentMethod;
2715
+ const hasPurchase = Boolean(state?.purchase);
2716
+ const redirectUrl = this.redirectUrl();
2717
+ const isRedirecting = Boolean(redirectUrl) && (state?.phase === "requires_action" || state?.nextAction?.type === "redirect");
2718
+ if (isRedirecting && redirectUrl) {
2719
+ return `
2720
+ <div class="stack" part="payment">
2721
+ <p class="section-title reveal reveal-1">${t(locale, "stepPayment")}</p>
2722
+ <div class="wrap stack reveal reveal-2" style="gap:12px;align-items:center;text-align:center">
2723
+ <div class="transfer-waiting" role="status">
2724
+ <span class="transfer-waiting-dot" aria-hidden="true"></span>
2725
+ <span>${escapeHtml5(t(locale, "redirectingPay"))}</span>
2726
+ </div>
2727
+ <a class="muted" id="redirect" href="${escapeAttr4(redirectUrl)}" target="_top" rel="noopener noreferrer" style="font-size:0.78rem">
2728
+ ${escapeHtml5(t(locale, "redirectPay"))}
2729
+ </a>
2730
+ <div data-payment-method="${escapeAttr4(paymentMethod)}" data-currency="${escapeAttr4(currency)}" hidden></div>
2731
+ </div>
2732
+ </div>
2733
+ `;
509
2734
  }
2735
+ const locked = this.busy || state?.phase === "purchasing";
2736
+ const canPay = Boolean(
2737
+ state?.cart.length && state?.buyerVerified && !locked && paymentMethod
2738
+ );
2739
+ const methodPicker = `
2740
+ <div class="pay-methods" role="radiogroup" aria-label="${escapeAttr4(t(locale, "paymentMethods"))}" data-locked="${locked}">
2741
+ ${methods.map((method) => {
2742
+ const meta = METHOD_META[method] || METHOD_META.TRANSFER;
2743
+ const selected = method === paymentMethod;
2744
+ return `
2745
+ <button type="button" class="pay-method" role="radio" aria-checked="${selected}"
2746
+ data-method="${escapeAttr4(method)}" data-selected="${selected}" ${locked ? "disabled" : ""}>
2747
+ <span class="pay-method-radio" aria-hidden="true"></span>
2748
+ <span class="pay-method-copy">
2749
+ <strong>${escapeHtml5(t(locale, meta.titleKey))}</strong>
2750
+ <span class="muted">${escapeHtml5(t(locale, meta.descKey))}</span>
2751
+ </span>
2752
+ </button>`;
2753
+ }).join("")}
2754
+ </div>`;
2755
+ const prePayTransferHint = paymentMethod === "TRANSFER" ? `<p class="muted" style="margin:0;font-size:0.78rem">${escapeHtml5(t(locale, "payTransferHint"))}</p>` : "";
2756
+ const payLabel = locked ? t(locale, "creatingPayment") : needsPick ? t(locale, "choosePayment") : t(locale, "pay");
2757
+ return `
2758
+ <div class="stack" part="payment">
2759
+ <p class="section-title reveal reveal-1">${t(locale, "stepPayment")}</p>
2760
+ <div class="wrap stack reveal reveal-2" style="gap:12px">
2761
+ ${methodPicker}
2762
+ ${prePayTransferHint}
2763
+ <button type="button" id="pay" ${canPay ? "" : "disabled"}>
2764
+ ${escapeHtml5(payLabel)}
2765
+ </button>
2766
+ <div data-payment-method="${escapeAttr4(paymentMethod)}" data-currency="${escapeAttr4(currency)}" hidden></div>
2767
+ </div>
2768
+ </div>
2769
+ `;
2770
+ }
2771
+ renderTransferView(locale, currency) {
2772
+ const nextAction = this.state?.nextAction || { type: "none" };
2773
+ const paymentMethod = this.resolveMethod();
2774
+ const methodTitle = t(
2775
+ locale,
2776
+ (METHOD_META[paymentMethod] || METHOD_META.TRANSFER).titleKey
2777
+ );
510
2778
  return `
511
- <div class="wrap stack" part="payment">
512
- ${renderNextAction(nextAction, this.elementLocale, (n) => this.money(n))}
513
- ${!state?.purchase ? `<button type="button" id="pay" ${!state?.cart.length || !state?.buyerVerified ? "disabled" : ""}>${t(this.elementLocale, "pay")}</button>` : ""}
514
- <div data-payment-method="${escapeAttr3(paymentMethod)}" data-currency="${escapeAttr3(currency)}" hidden></div>
2779
+ <div class="stack" part="payment">
2780
+ <p class="section-title reveal reveal-1">${t(locale, "stepTransfer")}</p>
2781
+ <div class="wrap stack reveal reveal-2" style="gap:12px">
2782
+ <div class="pay-method-locked row">
2783
+ <div>
2784
+ <div class="muted" style="font-size:0.72rem">${escapeHtml5(t(locale, "paymentMethods"))}</div>
2785
+ <strong style="font-size:0.9rem">${escapeHtml5(methodTitle)}</strong>
2786
+ </div>
2787
+ <button type="button" class="ghost-btn" id="change-method">${escapeHtml5(t(locale, "changePaymentMethod"))}</button>
2788
+ </div>
2789
+ ${renderNextAction(nextAction, locale, (n) => this.money(n), this.copiedKey)}
2790
+ ${nextAction.type === "display_instructions" ? `<div class="transfer-waiting" role="status">
2791
+ <span class="transfer-waiting-dot" aria-hidden="true"></span>
2792
+ <span>${escapeHtml5(t(locale, "transferWaiting"))}</span>
2793
+ </div>
2794
+ <p class="muted" style="margin:0;font-size:0.78rem">${escapeHtml5(t(locale, "transferPendingHint"))}</p>` : ""}
2795
+ <div data-payment-method="${escapeAttr4(paymentMethod)}" data-currency="${escapeAttr4(currency)}" hidden></div>
2796
+ </div>
515
2797
  </div>
516
2798
  `;
517
2799
  }
518
2800
  afterRender() {
519
- const paymentMethod = this.getAttribute("payment-method") || this.state?.event?.availablePaymentMethods?.[0] || "TRANSFER";
520
2801
  const currency = this.getAttribute("currency") || "ARS";
521
- this.root.querySelector("#pay")?.addEventListener("click", async () => {
522
- await this.controller?.purchaseAndPay({
523
- paymentMethod,
524
- currency
2802
+ const view = this.view();
2803
+ this.root.querySelector("#change-method")?.addEventListener("click", () => {
2804
+ this.dispatchEvent(
2805
+ new CustomEvent("tickean-change-payment-method", {
2806
+ bubbles: true,
2807
+ composed: true
2808
+ })
2809
+ );
2810
+ });
2811
+ this.root.querySelectorAll("[data-method]").forEach((btn) => {
2812
+ btn.addEventListener("click", () => {
2813
+ if (btn.disabled || this.busy || this.state?.phase === "purchasing") return;
2814
+ this.selectedMethod = btn.getAttribute("data-method");
2815
+ this.render();
525
2816
  });
526
2817
  });
527
- this.root.querySelector("#redirect")?.addEventListener("click", () => {
528
- const url = this.state?.nextAction?.url;
529
- if (url) window.location.assign(url);
2818
+ this.root.querySelectorAll("[data-copy]").forEach((btn) => {
2819
+ btn.addEventListener("click", async () => {
2820
+ const value = btn.getAttribute("data-copy") || "";
2821
+ const key = btn.getAttribute("data-copy-key") || value;
2822
+ const ok = await copyText(value);
2823
+ if (!ok) return;
2824
+ this.copiedKey = key;
2825
+ if (this.copiedTimer) clearTimeout(this.copiedTimer);
2826
+ this.copiedTimer = setTimeout(() => {
2827
+ this.copiedKey = null;
2828
+ this.render();
2829
+ }, 1600);
2830
+ this.render();
2831
+ });
530
2832
  });
2833
+ if (view === "method") {
2834
+ this.root.querySelector("#pay")?.addEventListener("click", async () => {
2835
+ const paymentMethod = this.resolveMethod();
2836
+ if (!paymentMethod) return;
2837
+ this.busy = true;
2838
+ this.render();
2839
+ try {
2840
+ const root = this.getRootNode();
2841
+ const host = root.host || this.closest("tickean-checkout");
2842
+ const attendees = host?.getAttendees?.();
2843
+ await this.controller?.purchaseAndPay({
2844
+ paymentMethod,
2845
+ currency,
2846
+ attendees: attendees?.length ? attendees : void 0
2847
+ });
2848
+ } finally {
2849
+ this.busy = false;
2850
+ this.render();
2851
+ }
2852
+ });
2853
+ }
2854
+ const redirectUrl = this.redirectUrl();
2855
+ if (redirectUrl && this.redirectedUrl !== redirectUrl && (this.state?.phase === "requires_action" || this.state?.nextAction?.type === "redirect")) {
2856
+ this.redirectedUrl = redirectUrl;
2857
+ navigateTopLevel(redirectUrl);
2858
+ }
531
2859
  }
532
2860
  };
533
- function renderNextAction(nextAction, locale, money) {
2861
+ function navigateTopLevel(url) {
2862
+ if (typeof window === "undefined" || !url) return;
2863
+ try {
2864
+ const topWin = window.top;
2865
+ if (topWin && topWin !== window) {
2866
+ topWin.location.assign(url);
2867
+ return;
2868
+ }
2869
+ } catch {
2870
+ }
2871
+ window.location.assign(url);
2872
+ }
2873
+ function renderNextAction(nextAction, locale, money, copiedKey) {
534
2874
  switch (nextAction.type) {
535
2875
  case "display_instructions": {
536
2876
  const instructions = nextAction.paymentInstructions || {};
2877
+ const alias = instructions.alias ? String(instructions.alias) : "";
2878
+ const cvu = instructions.cvu ? String(instructions.cvu) : instructions.accountIdentifier ? String(instructions.accountIdentifier) : "";
2879
+ const amount = instructions.amount != null ? Number(instructions.amount) : null;
537
2880
  return `
538
- <div>
539
- <strong>${t(locale, "transferInstructions")}</strong>
540
- <div class="stack" style="margin-top:8px">
541
- ${instructions.alias ? `<div><span class="muted">${t(locale, "alias")}</span>: <code>${escapeHtml4(String(instructions.alias))}</code></div>` : ""}
542
- ${instructions.cvu ? `<div><span class="muted">${t(locale, "cvu")}</span>: <code>${escapeHtml4(String(instructions.cvu))}</code></div>` : ""}
543
- ${instructions.amount != null ? `<div><span class="muted">${t(locale, "amount")}</span>: ${money(Number(instructions.amount))}</div>` : ""}
2881
+ <div class="transfer-box stack" style="gap:12px">
2882
+ ${amount != null && Number.isFinite(amount) ? `<div class="transfer-amount">
2883
+ <span class="transfer-amount-label">${escapeHtml5(t(locale, "transferExactAmount"))}</span>
2884
+ <strong class="transfer-amount-value">${escapeHtml5(money(amount))}</strong>
2885
+ </div>` : ""}
2886
+ <div class="stack" style="gap:8px">
2887
+ <strong>${escapeHtml5(t(locale, "transferInstructions"))}</strong>
2888
+ ${alias ? renderCopyRow(locale, "alias", alias, "alias", copiedKey) : ""}
2889
+ ${cvu ? renderCopyRow(locale, "cvu", cvu, "cvu", copiedKey) : ""}
544
2890
  </div>
545
2891
  </div>
546
2892
  `;
547
2893
  }
548
- case "redirect":
549
- return `<button type="button" id="redirect">${t(locale, "redirectPay")}</button>`;
2894
+ case "redirect": {
2895
+ const url = nextAction.url || "";
2896
+ return `
2897
+ <div class="stack" style="gap:10px;align-items:center;text-align:center">
2898
+ <div class="transfer-waiting" role="status">
2899
+ <span class="transfer-waiting-dot" aria-hidden="true"></span>
2900
+ <span>${escapeHtml5(t(locale, "redirectingPay"))}</span>
2901
+ </div>
2902
+ ${url ? `<a class="muted" id="redirect" href="${escapeAttr4(url)}" target="_top" rel="noopener noreferrer" style="font-size:0.78rem">${escapeHtml5(t(locale, "redirectPay"))}</a>` : ""}
2903
+ </div>`;
2904
+ }
550
2905
  case "stripe_elements":
551
2906
  return `<div class="provider-slot" data-provider="stripe" part="stripe-mount">${t(locale, "providerPlaceholder")} (Stripe)</div>`;
552
2907
  case "airwallex_dropin":
@@ -556,14 +2911,60 @@ function renderNextAction(nextAction, locale, money) {
556
2911
  case "fintoc_widget":
557
2912
  return `<div class="provider-slot" data-provider="fintoc" part="fintoc-mount">${t(locale, "providerPlaceholder")} (Fintoc)</div>`;
558
2913
  default:
559
- return "";
2914
+ return `<p class="muted" style="margin:0">${escapeHtml5(t(locale, "transferWaiting"))}</p>`;
560
2915
  }
561
2916
  }
562
- function escapeAttr3(value) {
2917
+ function renderCopyRow(locale, labelKey, value, copyKey, copiedKey) {
2918
+ const copied = copiedKey === copyKey;
2919
+ return `
2920
+ <div class="copy-row">
2921
+ <div class="copy-row-text">
2922
+ <span class="muted">${escapeHtml5(t(locale, labelKey))}</span>
2923
+ <code>${escapeHtml5(value)}</code>
2924
+ </div>
2925
+ <button type="button" class="copy-btn" data-copy="${escapeAttr4(value)}" data-copy-key="${escapeAttr4(copyKey)}" aria-label="${escapeAttr4(
2926
+ copied ? t(locale, "copied") : t(locale, "copy")
2927
+ )}">
2928
+ ${copied ? checkIcon() : copyIcon()}
2929
+ <span>${escapeHtml5(copied ? t(locale, "copied") : t(locale, "copy"))}</span>
2930
+ </button>
2931
+ </div>
2932
+ `;
2933
+ }
2934
+ function copyIcon() {
2935
+ return `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" aria-hidden="true"><rect x="9" y="9" width="13" height="13" rx="2" stroke="currentColor" stroke-width="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10" stroke="currentColor" stroke-width="2"/></svg>`;
2936
+ }
2937
+ function checkIcon() {
2938
+ return `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M20 6 9 17l-5-5" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg>`;
2939
+ }
2940
+ async function copyText(value) {
2941
+ try {
2942
+ if (navigator?.clipboard?.writeText) {
2943
+ await navigator.clipboard.writeText(value);
2944
+ return true;
2945
+ }
2946
+ } catch {
2947
+ }
2948
+ try {
2949
+ const ta = document.createElement("textarea");
2950
+ ta.value = value;
2951
+ ta.setAttribute("readonly", "");
2952
+ ta.style.position = "fixed";
2953
+ ta.style.opacity = "0";
2954
+ document.body.appendChild(ta);
2955
+ ta.select();
2956
+ const ok = document.execCommand("copy");
2957
+ document.body.removeChild(ta);
2958
+ return ok;
2959
+ } catch {
2960
+ return false;
2961
+ }
2962
+ }
2963
+ function escapeAttr4(value) {
563
2964
  return value.replace(/"/g, "&quot;");
564
2965
  }
565
- function escapeHtml4(value) {
566
- return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
2966
+ function escapeHtml5(value) {
2967
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
567
2968
  }
568
2969
  if (typeof customElements !== "undefined" && !customElements.get("tickean-payment")) {
569
2970
  customElements.define("tickean-payment", TickeanPayment);
@@ -571,34 +2972,70 @@ if (typeof customElements !== "undefined" && !customElements.get("tickean-paymen
571
2972
 
572
2973
  // src/elements/order-summary.ts
573
2974
  var TickeanOrderSummary = class extends TickeanElementBase {
2975
+ optionName(id) {
2976
+ for (const show of this.state?.event?.shows || []) {
2977
+ for (const opt of show.showOptions || []) {
2978
+ if (opt.id === id) return opt.name || id;
2979
+ }
2980
+ }
2981
+ return id;
2982
+ }
574
2983
  renderBody() {
575
- const total = this.state?.quote?.totalPrice || 0;
2984
+ const total = Number(this.state?.quote?.totalPrice) || Number(this.state?.purchase?.totalPrice) || 0;
576
2985
  const items = this.state?.cart || [];
2986
+ const locale = this.elementLocale;
2987
+ const breakdown = this.state?.quote?.pricingBreakdown || {};
2988
+ const promo = Number(breakdown.promotionDiscountTotal || 0);
2989
+ const code = Number(breakdown.codeDiscountTotal || 0);
2990
+ const service = Number(breakdown.serviceCharge || 0);
2991
+ const subtotal = Number(breakdown.subtotalBase) || Math.max(0, total - service + promo + code);
577
2992
  return `
578
- <div class="wrap stack" part="order-summary">
579
- <div class="row">
580
- <span>${t(this.elementLocale, "total")}</span>
2993
+ <div class="wrap stack reveal reveal-2" part="order-summary">
2994
+ <p class="section-title">${t(locale, "orderSummary")}</p>
2995
+ <div class="summary-line">
2996
+ <span class="muted">${tFormat(locale, "itemsCount", { count: items.length })}</span>
2997
+ ${this.state?.isQuoting ? `<span class="muted">${t(locale, "loading")}</span>` : ""}
2998
+ </div>
2999
+ ${items.length === 0 ? `<div class="muted">${t(locale, "emptyCart")}</div>` : items.map(
3000
+ (item) => `<div class="summary-line"><span>${escapeHtml6(this.optionName(item.showOptionId))}</span><span class="muted">\xD7${item.amount}</span></div>`
3001
+ ).join("")}
3002
+ ${items.length && subtotal > 0 ? `<div class="summary-line"><span class="muted">${escapeHtml6(t(locale, "subtotal"))}</span><span>${escapeHtml6(this.money(subtotal))}</span></div>` : ""}
3003
+ ${promo > 0 ? `<div class="summary-line"><span class="muted">${escapeHtml6(t(locale, "promoDiscount"))}</span><span>\u2212${escapeHtml6(this.money(promo))}</span></div>` : ""}
3004
+ ${code > 0 ? `<div class="summary-line"><span class="muted">${escapeHtml6(t(locale, "discount"))}</span><span>\u2212${escapeHtml6(this.money(code))}</span></div>` : ""}
3005
+ ${service > 0 ? `<div class="summary-line"><span class="muted">${escapeHtml6(t(locale, "serviceCharge"))}</span><span>${escapeHtml6(this.money(service))}</span></div>` : ""}
3006
+ <div class="row" style="margin-top:2px;padding-top:8px;border-top:1px solid var(--tickean-border)">
3007
+ <span style="font-weight:600">${t(locale, "total")}</span>
581
3008
  <span class="total" aria-live="polite">${this.money(total)}</span>
582
3009
  </div>
583
- ${items.length === 0 ? `<div class="muted">${t(this.elementLocale, "emptyCart")}</div>` : `<div class="muted">${items.length} item(s)</div>`}
584
- ${this.state?.isQuoting ? `<div class="muted">${t(this.elementLocale, "loading")}</div>` : ""}
585
3010
  </div>
586
3011
  `;
587
3012
  }
588
3013
  };
3014
+ function escapeHtml6(value) {
3015
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
3016
+ }
589
3017
  if (typeof customElements !== "undefined" && !customElements.get("tickean-order-summary")) {
590
- if (typeof customElements !== "undefined") customElements.define("tickean-order-summary", TickeanOrderSummary);
3018
+ customElements.define("tickean-order-summary", TickeanOrderSummary);
591
3019
  }
592
3020
 
593
3021
  // src/elements/checkout.ts
594
3022
  import {
595
3023
  createCheckoutController
596
3024
  } from "@tickean/checkout-js";
3025
+ var TICKEAN_HOME = "https://tickean.com";
3026
+ var TICKEAN_TICKETS_URL = "https://mi.tickean.com/tickets";
597
3027
  var TickeanCheckout = class extends TickeanElementBase {
598
3028
  constructor() {
599
3029
  super(...arguments);
600
3030
  this.ownedControllerId = null;
601
3031
  this.ownedController = null;
3032
+ this.stepIndex = 0;
3033
+ this.shellMounted = false;
3034
+ this.shellLayout = null;
3035
+ this.attendees = [];
3036
+ this.attendeesComplete = false;
3037
+ /** After "Cambiar método", stay on payment until they confirm again. */
3038
+ this.suppressTransferAutoAdvance = false;
602
3039
  }
603
3040
  static get observedAttributes() {
604
3041
  return [
@@ -609,81 +3046,501 @@ var TickeanCheckout = class extends TickeanElementBase {
609
3046
  "return-url",
610
3047
  "demo",
611
3048
  "payment-method",
612
- "currency"
3049
+ "currency",
3050
+ "layout"
613
3051
  ];
614
3052
  }
3053
+ /** Used by payment element / hosts. */
3054
+ getAttendees() {
3055
+ const el = this.root.querySelector("tickean-attendees");
3056
+ if (el?.getAttendees) return el.getAttendees();
3057
+ return this.attendees;
3058
+ }
615
3059
  connectedCallback() {
616
3060
  this.ensureController();
617
3061
  super.connectedCallback();
3062
+ this.addEventListener("attendees-change", ((e) => {
3063
+ this.attendees = e.detail?.attendees || [];
3064
+ this.attendeesComplete = Boolean(e.detail?.complete);
3065
+ this.syncStepsChrome();
3066
+ }));
3067
+ this.addEventListener("tickean-change-payment-method", (() => {
3068
+ this.suppressTransferAutoAdvance = true;
3069
+ this.controller?.changePaymentMethod?.();
3070
+ const paymentIdx = this.stepIds().indexOf("payment");
3071
+ if (paymentIdx >= 0) {
3072
+ this.stepIndex = paymentIdx;
3073
+ this.syncStepsChrome();
3074
+ }
3075
+ }));
618
3076
  }
619
3077
  disconnectedCallback() {
620
3078
  super.disconnectedCallback();
621
- if (this.ownedControllerId) {
622
- this.ownedController?.dispose();
623
- unregisterController(this.ownedControllerId);
624
- this.ownedControllerId = null;
625
- this.ownedController = null;
626
- }
3079
+ this.teardownOwnedController();
3080
+ this.shellMounted = false;
3081
+ this.shellLayout = null;
627
3082
  }
628
3083
  attributeChangedCallback(name, oldValue, newValue) {
629
3084
  if (["publishable-key", "event-slug", "api-base-url", "return-url", "demo"].includes(
630
3085
  name
631
3086
  ) && oldValue !== newValue) {
632
- if (this.ownedControllerId) {
633
- this.ownedController?.dispose();
634
- unregisterController(this.ownedControllerId);
635
- this.ownedControllerId = null;
636
- this.ownedController = null;
3087
+ if (this.isConnected) {
3088
+ this.teardownOwnedController();
3089
+ this.ensureController();
637
3090
  }
638
- this.ensureController();
3091
+ }
3092
+ if (name === "layout" && oldValue !== newValue) {
3093
+ this.shellMounted = false;
3094
+ this.shellLayout = null;
3095
+ this.stepIndex = 0;
639
3096
  }
640
3097
  super.attributeChangedCallback(name, oldValue, newValue);
641
3098
  }
3099
+ get layout() {
3100
+ const raw = (this.getAttribute("layout") || "steps").toLowerCase();
3101
+ return raw === "stacked" ? "stacked" : "steps";
3102
+ }
3103
+ needsAttendees() {
3104
+ return Boolean(this.state?.event?.collectAttendeeData);
3105
+ }
3106
+ needsTransferStep() {
3107
+ return this.state?.nextAction?.type === "display_instructions";
3108
+ }
3109
+ stepIds() {
3110
+ const ids = ["tickets", "buyer"];
3111
+ if (this.needsAttendees()) ids.push("attendees");
3112
+ ids.push("payment");
3113
+ if (this.needsTransferStep()) ids.push("transfer");
3114
+ ids.push("done");
3115
+ return ids;
3116
+ }
3117
+ currentStep() {
3118
+ return this.stepIds()[this.stepIndex] || "tickets";
3119
+ }
3120
+ teardownOwnedController() {
3121
+ this.ownedController?.dispose();
3122
+ if (this.ownedControllerId) unregisterController(this.ownedControllerId);
3123
+ this.ownedControllerId = null;
3124
+ this.ownedController = null;
3125
+ if (this.hasAttribute(controllerAttr())) this.removeAttribute(controllerAttr());
3126
+ }
642
3127
  ensureController() {
643
- if (this.getAttribute(controllerAttr())) return;
3128
+ if (this.ownedController) return;
644
3129
  const eventSlug = this.getAttribute("event-slug");
645
3130
  const publishableKey = this.getAttribute("publishable-key") || "";
646
3131
  if (!eventSlug) return;
3132
+ if (this.getAttribute(controllerAttr())) this.removeAttribute(controllerAttr());
3133
+ const resumeCode = typeof window !== "undefined" ? new URLSearchParams(window.location.search).get("resume") || void 0 : void 0;
647
3134
  const options = {
648
3135
  publishableKey,
649
3136
  eventSlug,
650
3137
  apiBaseUrl: this.getAttribute("api-base-url") || void 0,
651
3138
  returnUrl: this.getAttribute("return-url") || void 0,
3139
+ resumeCode: resumeCode || void 0,
652
3140
  demo: this.hasAttribute("demo"),
653
3141
  persistence: false
654
3142
  };
655
3143
  this.ownedController = createCheckoutController(options);
656
3144
  this.ownedControllerId = registerController(this.ownedController);
657
3145
  this.setAttribute(controllerAttr(), this.ownedControllerId);
3146
+ if (resumeCode && typeof window !== "undefined") {
3147
+ void this.ownedController.ready.then(() => {
3148
+ const snap = this.ownedController?.getSnapshot();
3149
+ if (!snap || snap.error) return;
3150
+ try {
3151
+ const url = new URL(window.location.href);
3152
+ if (url.searchParams.has("resume")) {
3153
+ url.searchParams.delete("resume");
3154
+ window.history.replaceState(
3155
+ {},
3156
+ "",
3157
+ `${url.pathname}${url.search}${url.hash}`
3158
+ );
3159
+ }
3160
+ } catch {
3161
+ }
3162
+ const steps = this.stepIds();
3163
+ let target = "tickets";
3164
+ if (snap.phase === "completed") target = "done";
3165
+ else if (snap.nextAction?.type === "display_instructions" || snap.phase === "processing") {
3166
+ target = this.needsTransferStep() ? "transfer" : "payment";
3167
+ } else if (snap.purchase) target = "payment";
3168
+ else if (snap.buyerVerified) {
3169
+ target = this.needsAttendees() ? "attendees" : "payment";
3170
+ } else if ((snap.cart?.length || 0) > 0) target = "buyer";
3171
+ const idx = steps.indexOf(target);
3172
+ if (idx >= 0) {
3173
+ this.stepIndex = idx;
3174
+ this.syncStepsChrome();
3175
+ }
3176
+ });
3177
+ }
3178
+ }
3179
+ restartCheckout() {
3180
+ this.teardownOwnedController();
3181
+ this.stepIndex = 0;
3182
+ this.shellMounted = false;
3183
+ this.shellLayout = null;
3184
+ this.suppressTransferAutoAdvance = false;
3185
+ this.attendees = [];
3186
+ this.attendeesComplete = false;
3187
+ this.ensureController();
3188
+ this.bindController();
3189
+ this.render();
3190
+ }
3191
+ poweredByHtml(locale) {
3192
+ return `
3193
+ <a class="powered" href="${TICKEAN_HOME}" target="_blank" rel="noopener noreferrer">
3194
+ <span class="powered-mark" aria-hidden="true"></span>
3195
+ <span>${escapeHtml7(t(locale, "poweredBy"))} <strong>Tickean</strong></span>
3196
+ </a>
3197
+ `;
3198
+ }
3199
+ eventHeaderHtml() {
3200
+ const event = this.state?.event;
3201
+ if (!event) return "";
3202
+ const locale = this.elementLocale;
3203
+ const location = event.location || {};
3204
+ const timeZone = resolveEventTimeZone(location);
3205
+ const flyer = getEventFlyerUrl(event.images);
3206
+ const { dateLabel, showLines } = buildEventScheduleSummary(
3207
+ event.shows || [],
3208
+ timeZone,
3209
+ locale
3210
+ );
3211
+ const place = [location.venueName, location.city].filter(Boolean).join(" \xB7 ");
3212
+ return `
3213
+ <div class="checkout-header">
3214
+ <div class="event-hero">
3215
+ ${flyer ? `<img class="event-flyer" src="${escapeAttr5(flyer)}" alt="${escapeAttr5(event.title || "")}" loading="lazy" />` : `<div class="event-flyer event-flyer-fallback" aria-hidden="true">\u2726</div>`}
3216
+ <div class="event-hero-copy">
3217
+ <h2>${escapeHtml7(event.title || "")}</h2>
3218
+ ${dateLabel ? `<p class="event-dates">${escapeHtml7(dateLabel)}</p>` : ""}
3219
+ ${place ? `<p class="muted event-place">${escapeHtml7(place)}</p>` : ""}
3220
+ ${showLines.length ? `<details class="event-schedule">
3221
+ <summary>${escapeHtml7(t(locale, "showTimes"))}</summary>
3222
+ <ul class="event-show-times">${showLines.map((line) => `<li>${escapeHtml7(line)}</li>`).join("")}</ul>
3223
+ </details>` : ""}
3224
+ </div>
3225
+ </div>
3226
+ </div>
3227
+ `;
3228
+ }
3229
+ loadingShell(locale) {
3230
+ return `
3231
+ <div class="loading-shell" part="checkout-loading">
3232
+ <div class="loading-label">\u2726 ${escapeHtml7(t(locale, "loading"))}</div>
3233
+ <div class="skeleton lg"></div>
3234
+ <div class="skeleton md"></div>
3235
+ <div class="skeleton" style="width:42%"></div>
3236
+ ${this.poweredByHtml(locale)}
3237
+ </div>
3238
+ `;
3239
+ }
3240
+ render() {
3241
+ const state = this.state;
3242
+ const layout = this.layout;
3243
+ const locale = this.elementLocale;
3244
+ if (!state || state.loading) {
3245
+ this.shellMounted = false;
3246
+ this.shellLayout = null;
3247
+ this.root.innerHTML = `<style>${this.styles()}</style>${this.loadingShell(locale)}`;
3248
+ return;
3249
+ }
3250
+ if (state.phase === "purchasing") {
3251
+ this.suppressTransferAutoAdvance = false;
3252
+ }
3253
+ if (layout === "steps" && state.phase === "completed") {
3254
+ const doneIndex = this.stepIds().indexOf("done");
3255
+ if (doneIndex >= 0) this.stepIndex = doneIndex;
3256
+ } else if (layout === "steps" && this.needsTransferStep() && state.phase !== "completed" && !this.suppressTransferAutoAdvance) {
3257
+ const transferIndex = this.stepIds().indexOf("transfer");
3258
+ if (transferIndex >= 0) this.stepIndex = transferIndex;
3259
+ }
3260
+ const hasTransferPanel = Boolean(
3261
+ this.root.querySelector('[data-panel="transfer"]')
3262
+ );
3263
+ if (layout === "steps" && this.shellMounted && this.needsTransferStep() !== hasTransferPanel) {
3264
+ this.shellMounted = false;
3265
+ }
3266
+ if (layout === "steps" && this.shellMounted && this.shellLayout === "steps" && this.root.querySelector("[data-tickean-shell='steps']")) {
3267
+ this.syncStepsChrome();
3268
+ return;
3269
+ }
3270
+ this.root.innerHTML = `<style>${this.styles()}</style>${this.renderBody()}`;
3271
+ this.shellMounted = true;
3272
+ this.shellLayout = layout;
3273
+ this.setAttribute("data-animated", "");
3274
+ this.afterRender();
658
3275
  }
659
3276
  renderBody() {
3277
+ return this.layout === "stacked" ? this.renderStacked() : this.renderSteps();
3278
+ }
3279
+ sharedChildAttrs(id, locale, appearance) {
3280
+ const apiBase = this.getAttribute("api-base-url") || "https://api.tickean.com";
3281
+ return `controller-id="${escapeAttr5(id)}" locale="${escapeAttr5(locale)}" appearance="${escapeAttr5(appearance)}" api-base-url="${escapeAttr5(apiBase)}"`;
3282
+ }
3283
+ renderStacked() {
660
3284
  const id = this.getAttribute(controllerAttr()) || "";
661
3285
  const locale = this.getAttribute("locale") || "es-AR";
662
3286
  const appearance = this.getAttribute("appearance") || "default";
663
3287
  const paymentMethod = this.getAttribute("payment-method") || "TRANSFER";
664
3288
  const currency = this.getAttribute("currency") || "ARS";
665
3289
  const state = this.state;
666
- if (!state || state.loading) {
667
- return `<div class="wrap muted">${t(this.elementLocale, "loading")}</div>`;
3290
+ const attrs = this.sharedChildAttrs(id, locale, appearance);
3291
+ return `
3292
+ <div class="stack checkout-steps" part="checkout" data-tickean-shell="stacked">
3293
+ ${this.eventHeaderHtml()}
3294
+ <tickean-discount ${attrs} mode="unlock"></tickean-discount>
3295
+ <tickean-ticket-selector ${attrs}></tickean-ticket-selector>
3296
+ <tickean-discount ${attrs} mode="discount"></tickean-discount>
3297
+ <tickean-buyer-verification ${attrs}></tickean-buyer-verification>
3298
+ ${this.needsAttendees() ? `<tickean-attendees ${attrs}></tickean-attendees>` : ""}
3299
+ <details class="summary-drawer">
3300
+ <summary class="summary-drawer-toggle">
3301
+ <span>${escapeHtml7(t(this.elementLocale, "viewSummary"))}</span>
3302
+ <strong>${escapeHtml7(this.money(Number(state.quote?.totalPrice || 0)))}</strong>
3303
+ </summary>
3304
+ <div class="summary-drawer-body">
3305
+ <tickean-order-summary ${attrs}></tickean-order-summary>
3306
+ </div>
3307
+ </details>
3308
+ <tickean-payment ${attrs} view="method" payment-method="${escapeAttr5(paymentMethod)}" currency="${escapeAttr5(currency)}"></tickean-payment>
3309
+ ${this.needsTransferStep() ? `<tickean-payment ${attrs} view="transfer" payment-method="${escapeAttr5(paymentMethod)}" currency="${escapeAttr5(currency)}"></tickean-payment>` : ""}
3310
+ ${this.poweredByHtml(this.elementLocale)}
3311
+ </div>
3312
+ `;
3313
+ }
3314
+ stepLabel(id, locale) {
3315
+ switch (id) {
3316
+ case "tickets":
3317
+ return t(locale, "stepTickets");
3318
+ case "buyer":
3319
+ return t(locale, "stepBuyer");
3320
+ case "attendees":
3321
+ return t(locale, "attendees");
3322
+ case "payment":
3323
+ return t(locale, "stepPayment");
3324
+ case "transfer":
3325
+ return t(locale, "stepTransfer");
3326
+ case "done":
3327
+ return t(locale, "stepDone");
668
3328
  }
3329
+ }
3330
+ renderSteps() {
3331
+ const id = this.getAttribute(controllerAttr()) || "";
3332
+ const locale = this.elementLocale;
3333
+ const appearance = this.getAttribute("appearance") || "default";
3334
+ const paymentMethod = this.getAttribute("payment-method") || "TRANSFER";
3335
+ const currency = this.getAttribute("currency") || "ARS";
3336
+ const state = this.state;
3337
+ const attrs = this.sharedChildAttrs(id, locale, appearance);
3338
+ const steps = this.stepIds();
3339
+ const step = this.currentStep();
3340
+ const progress = Math.round((this.stepIndex + 1) / steps.length * 100);
3341
+ const stepper = steps.map((sid, index) => {
3342
+ const active = sid === step;
3343
+ const done = index < this.stepIndex;
3344
+ return `
3345
+ <li class="stepper-item" data-step-id="${sid}" data-active="${active}" data-done="${done}">
3346
+ <span class="stepper-dot" aria-hidden="true">${done ? "\u2713" : index + 1}</span>
3347
+ <span class="stepper-label">${escapeHtml7(this.stepLabel(sid, locale))}</span>
3348
+ </li>`;
3349
+ }).join("");
3350
+ const dockTotal = this.money(Number(state.quote?.totalPrice || 0));
669
3351
  return `
670
- <div class="stack" part="checkout">
671
- <h2 style="margin:0">${escapeHtml5(state.event?.title || "")}</h2>
672
- <p class="muted" style="margin:0">${escapeHtml5(state.event?.description || "")}</p>
673
- <tickean-ticket-selector controller-id="${escapeAttr4(id)}" locale="${escapeAttr4(locale)}" appearance="${escapeAttr4(appearance)}"></tickean-ticket-selector>
674
- <tickean-discount controller-id="${escapeAttr4(id)}" locale="${escapeAttr4(locale)}" appearance="${escapeAttr4(appearance)}"></tickean-discount>
675
- <tickean-buyer-verification controller-id="${escapeAttr4(id)}" locale="${escapeAttr4(locale)}" appearance="${escapeAttr4(appearance)}"></tickean-buyer-verification>
676
- <tickean-order-summary controller-id="${escapeAttr4(id)}" locale="${escapeAttr4(locale)}" appearance="${escapeAttr4(appearance)}"></tickean-order-summary>
677
- <tickean-payment controller-id="${escapeAttr4(id)}" locale="${escapeAttr4(locale)}" appearance="${escapeAttr4(appearance)}" payment-method="${escapeAttr4(paymentMethod)}" currency="${escapeAttr4(currency)}"></tickean-payment>
678
- ${state.error ? `<div class="danger" role="alert">${escapeHtml5(state.error.message)}</div>` : ""}
3352
+ <div class="checkout-steps compact" part="checkout" data-tickean-shell="steps" style="--tickean-progress:${progress}%">
3353
+ ${this.eventHeaderHtml()}
3354
+ <div class="progress" aria-hidden="true"><span data-progress></span></div>
3355
+ <ol class="stepper" aria-label="Checkout" style="grid-template-columns:repeat(${steps.length},minmax(0,1fr))">${stepper}</ol>
3356
+ <p class="step-caption muted" data-step-caption>
3357
+ ${escapeHtml7(tFormat(locale, "stepOf", { current: this.stepIndex + 1, total: steps.length }))}
3358
+ </p>
3359
+ <div class="checkout-layout">
3360
+ <aside class="checkout-aside compact-aside" ${step === "done" ? "hidden" : ""} data-aside>
3361
+ <details class="summary-drawer">
3362
+ <summary class="summary-drawer-toggle">
3363
+ <span>${escapeHtml7(t(locale, "viewSummary"))}</span>
3364
+ <strong data-dock-total>${escapeHtml7(dockTotal)}</strong>
3365
+ </summary>
3366
+ <div class="summary-drawer-body">
3367
+ <tickean-order-summary ${attrs}></tickean-order-summary>
3368
+ </div>
3369
+ </details>
3370
+ </aside>
3371
+ <div class="step-panels">
3372
+ <section class="step-panel stack" data-panel="tickets" ${step === "tickets" ? "" : "hidden"}>
3373
+ <tickean-discount ${attrs} mode="unlock"></tickean-discount>
3374
+ <tickean-ticket-selector ${attrs}></tickean-ticket-selector>
3375
+ </section>
3376
+ <section class="step-panel stack" data-panel="buyer" ${step === "buyer" ? "" : "hidden"}>
3377
+ <tickean-discount ${attrs} mode="discount"></tickean-discount>
3378
+ <tickean-buyer-verification ${attrs}></tickean-buyer-verification>
3379
+ </section>
3380
+ <section class="step-panel stack" data-panel="attendees" ${step === "attendees" ? "" : "hidden"}>
3381
+ <tickean-attendees ${attrs}></tickean-attendees>
3382
+ </section>
3383
+ <section class="step-panel stack" data-panel="payment" ${step === "payment" ? "" : "hidden"}>
3384
+ <tickean-payment ${attrs} view="method" payment-method="${escapeAttr5(paymentMethod)}" currency="${escapeAttr5(currency)}"></tickean-payment>
3385
+ </section>
3386
+ ${this.needsTransferStep() ? `<section class="step-panel stack" data-panel="transfer" ${step === "transfer" ? "" : "hidden"}>
3387
+ <tickean-payment ${attrs} view="transfer" payment-method="${escapeAttr5(paymentMethod)}" currency="${escapeAttr5(currency)}"></tickean-payment>
3388
+ </section>` : ""}
3389
+ <section class="step-panel" data-panel="done" ${step === "done" ? "" : "hidden"}>
3390
+ <div class="done-card">
3391
+ <div class="done-icon" aria-hidden="true">\u2713</div>
3392
+ <h3>${escapeHtml7(t(locale, "completed"))}</h3>
3393
+ <p class="muted">${escapeHtml7(t(locale, "doneSubtitle"))}</p>
3394
+ <div data-done-summary class="muted"></div>
3395
+ <div class="done-actions">
3396
+ <a class="done-primary" href="${TICKEAN_TICKETS_URL}" target="_blank" rel="noopener noreferrer">
3397
+ ${escapeHtml7(t(locale, "viewTickets"))}
3398
+ </a>
3399
+ <button type="button" class="secondary" data-action="buy-again">
3400
+ ${escapeHtml7(t(locale, "buyAgain"))}
3401
+ </button>
3402
+ </div>
3403
+ </div>
3404
+ </section>
3405
+ <div class="danger" role="alert" data-error ${state.error ? "" : "hidden"}>
3406
+ ${state.error ? escapeHtml7(state.error.message) : ""}
3407
+ </div>
3408
+ </div>
3409
+ </div>
3410
+ <div class="checkout-sticky" data-sticky ${step === "done" ? "hidden" : ""}>
3411
+ <div class="checkout-dock" data-step-nav ${step === "transfer" ? "hidden" : ""}>
3412
+ <div class="dock-side dock-side-start">
3413
+ <button type="button" class="secondary dock-back" data-action="back" ${this.stepIndex <= 0 ? "hidden" : ""}>
3414
+ ${escapeHtml7(t(locale, "back"))}
3415
+ </button>
3416
+ </div>
3417
+ <div class="dock-total">
3418
+ <span class="muted">${escapeHtml7(t(locale, "total"))}</span>
3419
+ <strong data-dock-total>${escapeHtml7(dockTotal)}</strong>
3420
+ </div>
3421
+ <div class="dock-side dock-side-end">
3422
+ <button type="button" class="dock-next" data-action="next" ${step === "payment" || step === "transfer" || step === "done" ? "hidden" : ""} ${this.canGoNext() ? "" : "disabled"}>
3423
+ ${escapeHtml7(t(locale, "next"))}
3424
+ </button>
3425
+ </div>
3426
+ </div>
3427
+ ${this.poweredByHtml(locale)}
3428
+ </div>
679
3429
  </div>
680
3430
  `;
681
3431
  }
3432
+ canGoNext() {
3433
+ const state = this.state;
3434
+ if (!state) return false;
3435
+ const step = this.currentStep();
3436
+ if (step === "tickets") return (state.cart?.length || 0) > 0;
3437
+ if (step === "buyer") return Boolean(state.buyerVerified);
3438
+ if (step === "attendees") return this.attendeesComplete || this.getAttendeesCompleteFromDom();
3439
+ return false;
3440
+ }
3441
+ getAttendeesCompleteFromDom() {
3442
+ const el = this.root.querySelector("tickean-attendees");
3443
+ return Boolean(el?.isComplete?.());
3444
+ }
3445
+ syncStepsChrome() {
3446
+ const locale = this.elementLocale;
3447
+ const steps = this.stepIds();
3448
+ if (this.stepIndex >= steps.length) this.stepIndex = steps.length - 1;
3449
+ const step = this.currentStep();
3450
+ const state = this.state;
3451
+ const progress = Math.round((this.stepIndex + 1) / steps.length * 100);
3452
+ const shell = this.root.querySelector("[data-tickean-shell='steps']");
3453
+ if (shell) shell.style.setProperty("--tickean-progress", `${progress}%`);
3454
+ this.root.querySelectorAll(".stepper-item").forEach((item) => {
3455
+ const sid = item.getAttribute("data-step-id");
3456
+ const index = steps.indexOf(sid);
3457
+ item.dataset.active = String(sid === step);
3458
+ item.dataset.done = String(index < this.stepIndex);
3459
+ const dot = item.querySelector(".stepper-dot");
3460
+ if (dot) dot.textContent = index < this.stepIndex ? "\u2713" : String(index + 1);
3461
+ });
3462
+ const caption = this.root.querySelector("[data-step-caption]");
3463
+ if (caption) {
3464
+ caption.textContent = tFormat(locale, "stepOf", {
3465
+ current: this.stepIndex + 1,
3466
+ total: steps.length
3467
+ });
3468
+ }
3469
+ this.root.querySelectorAll(".step-panel").forEach((panel) => {
3470
+ const id = panel.getAttribute("data-panel");
3471
+ panel.hidden = id !== step;
3472
+ });
3473
+ const sticky = this.root.querySelector("[data-sticky]");
3474
+ if (sticky) sticky.hidden = step === "done";
3475
+ const nav = this.root.querySelector("[data-step-nav]");
3476
+ if (nav) nav.hidden = step === "transfer";
3477
+ const aside = this.root.querySelector("[data-aside]");
3478
+ if (aside) aside.hidden = step === "done";
3479
+ const back = this.root.querySelector('[data-action="back"]');
3480
+ if (back) {
3481
+ back.hidden = this.stepIndex <= 0 || step === "done" || step === "transfer";
3482
+ back.disabled = state?.phase === "purchasing";
3483
+ }
3484
+ const next = this.root.querySelector('[data-action="next"]');
3485
+ if (next) {
3486
+ next.hidden = step === "payment" || step === "transfer" || step === "done";
3487
+ next.disabled = !this.canGoNext();
3488
+ }
3489
+ const drawer = this.root.querySelector("details.summary-drawer");
3490
+ if (drawer && !drawer.dataset.userToggled) {
3491
+ drawer.open = false;
3492
+ }
3493
+ const dockTotal = this.money(Number(state?.quote?.totalPrice || 0));
3494
+ this.root.querySelectorAll("[data-dock-total]").forEach((el) => {
3495
+ el.textContent = dockTotal;
3496
+ });
3497
+ const errorEl = this.root.querySelector("[data-error]");
3498
+ if (errorEl) {
3499
+ if (state?.error) {
3500
+ errorEl.hidden = false;
3501
+ errorEl.textContent = state.error.message;
3502
+ } else {
3503
+ errorEl.hidden = true;
3504
+ errorEl.textContent = "";
3505
+ }
3506
+ }
3507
+ const doneSummary = this.root.querySelector("[data-done-summary]");
3508
+ if (doneSummary && step === "done") {
3509
+ const purchaseId = state?.purchase?.id;
3510
+ doneSummary.textContent = purchaseId ? `#${purchaseId}` : "";
3511
+ }
3512
+ }
3513
+ afterRender() {
3514
+ this.root.querySelector('[data-action="buy-again"]')?.addEventListener("click", () => {
3515
+ this.restartCheckout();
3516
+ });
3517
+ if (this.layout !== "steps") return;
3518
+ this.root.querySelector("details.summary-drawer")?.addEventListener("toggle", (ev) => {
3519
+ const details = ev.currentTarget;
3520
+ details.dataset.userToggled = "1";
3521
+ });
3522
+ this.root.querySelector('[data-action="back"]')?.addEventListener("click", () => {
3523
+ if (this.state?.phase === "purchasing") return;
3524
+ if (this.stepIndex > 0) {
3525
+ this.stepIndex -= 1;
3526
+ this.syncStepsChrome();
3527
+ }
3528
+ });
3529
+ this.root.querySelector('[data-action="next"]')?.addEventListener("click", () => {
3530
+ if (!this.canGoNext()) return;
3531
+ const steps = this.stepIds();
3532
+ if (this.stepIndex < steps.length - 1) {
3533
+ this.stepIndex += 1;
3534
+ if (this.currentStep() === "done") this.stepIndex -= 1;
3535
+ this.syncStepsChrome();
3536
+ }
3537
+ });
3538
+ }
682
3539
  };
683
- function escapeAttr4(value) {
3540
+ function escapeAttr5(value) {
684
3541
  return value.replace(/"/g, "&quot;");
685
3542
  }
686
- function escapeHtml5(value) {
3543
+ function escapeHtml7(value) {
687
3544
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
688
3545
  }
689
3546
  if (typeof customElements !== "undefined" && !customElements.get("tickean-checkout")) {
@@ -696,6 +3553,7 @@ var ELEMENT_TAGS = [
696
3553
  "tickean-ticket-selector",
697
3554
  "tickean-discount",
698
3555
  "tickean-buyer-verification",
3556
+ "tickean-attendees",
699
3557
  "tickean-payment",
700
3558
  "tickean-order-summary"
701
3559
  ];
@@ -717,6 +3575,7 @@ function detachController(id) {
717
3575
  }
718
3576
  defineTickeanElements();
719
3577
  export {
3578
+ TickeanAttendees,
720
3579
  TickeanBuyerVerification,
721
3580
  TickeanCheckout,
722
3581
  TickeanDiscount,