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