@seatlayer/js 0.47.1 → 0.48.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  SeatManager
3
- } from "./chunk-CF5MS7UR.js";
3
+ } from "./chunk-Q5QT3YLA.js";
4
4
  import {
5
5
  ACCESS_LINK_DEFAULTS,
6
6
  ChannelsMode,
@@ -28,11 +28,11 @@ import {
28
28
  selectionSources,
29
29
  stateBadge,
30
30
  suggestMarker
31
- } from "./chunk-DMEFXZIL.js";
31
+ } from "./chunk-KNMZQZXR.js";
32
32
  import {
33
33
  ManageApi,
34
34
  ManageApiError
35
- } from "./chunk-X2R4AQSZ.js";
35
+ } from "./chunk-H4OJF6LE.js";
36
36
  import {
37
37
  __privateAdd,
38
38
  __privateGet,
@@ -524,9 +524,54 @@ var PubApi = class {
524
524
  }
525
525
  return data;
526
526
  }
527
+ /**
528
+ * Binary counterpart to `request`. Buyer media needs the same in-memory
529
+ * bearer/refresh rules as JSON, but returns bytes that the picker turns into
530
+ * a blob URL. The bearer stays in the Authorization header and is never
531
+ * appended to `path`.
532
+ */
533
+ async requestBlob(path, retried = {}) {
534
+ const headers = {};
535
+ const authorization = await this.access?.authorization(retried.auth ? "unauthorized" : "initial");
536
+ if (authorization) headers.Authorization = authorization;
537
+ const res = await fetch(`${this.base}${path}`, { method: "GET", headers, credentials: "omit" });
538
+ if (res.ok) return res.blob();
539
+ const isJson = (res.headers.get("content-type") ?? "").includes("application/json");
540
+ const data = isJson ? await res.json().catch(() => null) : null;
541
+ const code = data?.code ?? data?.error;
542
+ if (this.access?.configured && (res.status === 401 || res.status === 403 || res.status === 422)) {
543
+ const refreshed = await this.access.handleFailure(res.status, code);
544
+ if (refreshed && !retried.auth) return this.requestBlob(path, { ...retried, auth: true });
545
+ }
546
+ let retryAfterS;
547
+ if (res.status === 429) {
548
+ retryAfterS = parseRetryAfter(res.headers.get("Retry-After"), data?.retryAfterSeconds) ?? DEFAULT_RATE_LIMIT_WAIT_S;
549
+ if (!retried.rateLimit && retryAfterS <= MAX_RATE_LIMIT_WAIT_S) {
550
+ await new Promise((resolve) => setTimeout(resolve, retryAfterS * 1e3));
551
+ return this.requestBlob(path, { ...retried, rateLimit: true });
552
+ }
553
+ }
554
+ throw new ApiError(
555
+ res.status,
556
+ data?.error ?? `request_failed_${res.status}`,
557
+ code,
558
+ void 0,
559
+ void 0,
560
+ retryAfterS
561
+ );
562
+ }
527
563
  chart(key) {
528
564
  return this.request(`/pub/events/${encodeURIComponent(key)}/chart`);
529
565
  }
566
+ /** Authenticated bytes for an Event-scoped authored view image. */
567
+ asset(key, asset) {
568
+ if (!/^[a-zA-Z0-9._-]+$/.test(asset)) {
569
+ return Promise.reject(new ApiError(404, "not_found", "not_found"));
570
+ }
571
+ return this.requestBlob(
572
+ `/pub/events/${encodeURIComponent(key)}/assets/${encodeURIComponent(asset)}`
573
+ );
574
+ }
530
575
  objects(key) {
531
576
  return this.request(`/pub/events/${encodeURIComponent(key)}/objects?compact=1`);
532
577
  }
@@ -577,6 +622,10 @@ var PubApi = class {
577
622
  paymentOptions(key) {
578
623
  return this.request(`/pub/events/${encodeURIComponent(key)}/payment-options`);
579
624
  }
625
+ /** Server-resolved active ticket offers and category prices. */
626
+ availability(key, live = false) {
627
+ return this.request(`/pub/events/${encodeURIComponent(key)}/availability${live ? "?live=1" : ""}`);
628
+ }
580
629
  /**
581
630
  * Turn a live hold into an order and start a payment.
582
631
  *
@@ -625,12 +674,11 @@ var PubApi = class {
625
674
  /**
626
675
  * What PickerController opens its own socket with.
627
676
  *
628
- * Empty for an access-scoped client: a private scope authenticates with a
677
+ * Empty for an access-scoped client: a scoped audience authenticates with a
629
678
  * subprotocol ticket, which a URL-only constructor cannot carry, so the SDK's
630
679
  * BuyerRealtimeClient owns that socket instead and the controller skips its
631
680
  * own (an empty URL is its documented "no live feed" contract). A tokenless
632
- * public client returns exactly the URL it always has, so nothing about the
633
- * public picker's realtime path changes.
681
+ * Managed public client returns exactly the URL it always has.
634
682
  */
635
683
  socketUrl(key) {
636
684
  return this.accessScoped ? "" : this.subscribeUrl(key);
@@ -1073,7 +1121,7 @@ var SeatingChart = class {
1073
1121
  this.tipEl.style.display = "none";
1074
1122
  return;
1075
1123
  }
1076
- const money = (() => {
1124
+ const money2 = (() => {
1077
1125
  try {
1078
1126
  return new Intl.NumberFormat(void 0, { style: "currency", currency: details.currency }).format(details.price);
1079
1127
  } catch {
@@ -1081,7 +1129,7 @@ var SeatingChart = class {
1081
1129
  }
1082
1130
  })();
1083
1131
  const statusLine = details.status === "free" ? "" : `<div style="margin-top:5px;font-size:10.5px;letter-spacing:.08em;text-transform:uppercase;color:#fca5a5;font-weight:700">${details.status === "held" ? t("map.statusHeld") : t("map.statusTaken")}</div>`;
1084
- this.tipEl.innerHTML = `<div style="font-weight:700;font-size:13px">${details.label}</div><div style="display:flex;align-items:center;gap:6px;margin-top:4px;color:#c7cddc"><span style="width:9px;height:9px;border-radius:50%;flex:none;background:${details.categoryColor}"></span><span>${details.categoryLabel}</span><span style="margin-left:auto;font-weight:700;color:#fff">${money}</span></div>` + statusLine;
1132
+ this.tipEl.innerHTML = `<div style="font-weight:700;font-size:13px">${details.label}</div><div style="display:flex;align-items:center;gap:6px;margin-top:4px;color:#c7cddc"><span style="width:9px;height:9px;border-radius:50%;flex:none;background:${details.categoryColor}"></span><span>${details.categoryLabel}</span><span style="margin-left:auto;font-weight:700;color:#fff">${money2}</span></div>` + statusLine;
1085
1133
  this.tipEl.style.display = "block";
1086
1134
  this.placeTooltip();
1087
1135
  }
@@ -1989,6 +2037,211 @@ import {
1989
2037
  planPanoramaDelivery,
1990
2038
  schedulePanoramaUpgrade
1991
2039
  } from "@seatlayer/core/view/panoramaDelivery";
2040
+
2041
+ // src/buyerAssets.ts
2042
+ var SAFE_ASSET = /^[a-zA-Z0-9._-]+$/;
2043
+ function buyerEventAssetReference(value) {
2044
+ let url;
2045
+ try {
2046
+ url = new URL(value, "https://seatlayer.invalid");
2047
+ } catch {
2048
+ return null;
2049
+ }
2050
+ if (url.search || url.hash) return null;
2051
+ const match = /^\/pub\/events\/([^/]+)\/assets\/([^/]+)$/.exec(url.pathname);
2052
+ if (!match) return null;
2053
+ try {
2054
+ const eventKey = decodeURIComponent(match[1]);
2055
+ const asset = decodeURIComponent(match[2]);
2056
+ if (!eventKey || !SAFE_ASSET.test(asset)) return null;
2057
+ return { eventKey, asset };
2058
+ } catch {
2059
+ return null;
2060
+ }
2061
+ }
2062
+ function looksLikeBuyerAsset(value) {
2063
+ try {
2064
+ return /^\/pub\/events\/[^/]+\/assets(?:\/|$)/.test(
2065
+ new URL(value, "https://seatlayer.invalid").pathname
2066
+ );
2067
+ } catch {
2068
+ return false;
2069
+ }
2070
+ }
2071
+ var BuyerAssetObjectUrls = class {
2072
+ constructor(eventKey, load) {
2073
+ this.eventKey = eventKey;
2074
+ this.load = load;
2075
+ this.pending = /* @__PURE__ */ new Map();
2076
+ this.created = /* @__PURE__ */ new Set();
2077
+ this.disposed = false;
2078
+ }
2079
+ /**
2080
+ * External organizer/CDN URLs pass through unchanged. SeatLayer event assets
2081
+ * never do: they require the transport, and a reference for another Event is
2082
+ * refused instead of being loaded anonymously.
2083
+ */
2084
+ resolve(reference) {
2085
+ const parsed = buyerEventAssetReference(reference);
2086
+ if (!parsed) {
2087
+ return Promise.resolve(looksLikeBuyerAsset(reference) ? null : reference);
2088
+ }
2089
+ if (parsed.eventKey !== this.eventKey || !this.load || this.disposed) return Promise.resolve(null);
2090
+ const existing = this.pending.get(reference);
2091
+ if (existing) return existing;
2092
+ const task = this.load(this.eventKey, parsed.asset).then((blob) => {
2093
+ const objectUrl = URL.createObjectURL(blob);
2094
+ if (this.disposed) {
2095
+ URL.revokeObjectURL(objectUrl);
2096
+ return null;
2097
+ }
2098
+ this.created.add(objectUrl);
2099
+ return objectUrl;
2100
+ }).catch((error) => {
2101
+ this.pending.delete(reference);
2102
+ throw error;
2103
+ });
2104
+ this.pending.set(reference, task);
2105
+ return task;
2106
+ }
2107
+ dispose() {
2108
+ if (this.disposed) return;
2109
+ this.disposed = true;
2110
+ for (const url of this.created) URL.revokeObjectURL(url);
2111
+ this.created.clear();
2112
+ this.pending.clear();
2113
+ }
2114
+ };
2115
+
2116
+ // src/offerAvailability.ts
2117
+ var SALE_STATES = [
2118
+ "on-sale",
2119
+ "low",
2120
+ "sold-out",
2121
+ "presale",
2122
+ "closed"
2123
+ ];
2124
+ function money(value) {
2125
+ if (value === null || value === void 0) return null;
2126
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
2127
+ }
2128
+ function timestamp(value) {
2129
+ if (value === null || value === void 0) return null;
2130
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
2131
+ }
2132
+ function parseSummary(value) {
2133
+ if (value == null) return null;
2134
+ if (typeof value !== "object" || Array.isArray(value)) return void 0;
2135
+ const source = value;
2136
+ const count = source.count;
2137
+ const index = source.index;
2138
+ if (typeof index !== "number" || !Number.isInteger(index) || index < 1) return void 0;
2139
+ if (typeof count !== "number" || !Number.isInteger(count) || count < index) return void 0;
2140
+ const remaining = source.remaining;
2141
+ if (remaining != null && (typeof remaining !== "number" || !Number.isInteger(remaining) || remaining < 0)) {
2142
+ return void 0;
2143
+ }
2144
+ const result = {
2145
+ index,
2146
+ count,
2147
+ remaining: remaining == null ? null : remaining
2148
+ };
2149
+ if (source.id !== void 0) {
2150
+ if (typeof source.id !== "string" || !source.id.trim()) return void 0;
2151
+ result.id = source.id.trim();
2152
+ }
2153
+ if (source.name !== void 0) {
2154
+ if (typeof source.name !== "string" || !source.name.trim()) return void 0;
2155
+ result.name = source.name.trim();
2156
+ }
2157
+ if (source.categoryKey !== void 0) {
2158
+ if (source.categoryKey !== null && (typeof source.categoryKey !== "string" || !source.categoryKey.trim())) {
2159
+ return void 0;
2160
+ }
2161
+ result.categoryKey = source.categoryKey == null ? null : source.categoryKey.trim();
2162
+ }
2163
+ for (const key of ["startsAt", "endsAt"]) {
2164
+ if (source[key] === void 0) continue;
2165
+ const parsed = timestamp(source[key]);
2166
+ if (parsed === void 0) return void 0;
2167
+ result[key] = parsed;
2168
+ }
2169
+ return result;
2170
+ }
2171
+ function parseTicketOfferAvailability(body) {
2172
+ if (!body || typeof body !== "object" || Array.isArray(body)) return null;
2173
+ const raw = body;
2174
+ const state = SALE_STATES.find((candidate) => candidate === raw.state);
2175
+ if (!state) return null;
2176
+ const fromPrice = money(raw.fromPrice);
2177
+ const previousPrice = money(raw.previousPrice);
2178
+ if (fromPrice === void 0 || previousPrice === void 0) return null;
2179
+ const currency = raw.currency == null ? null : typeof raw.currency === "string" && raw.currency.trim() ? raw.currency.trim() : void 0;
2180
+ if (currency === void 0) return null;
2181
+ const release = parseSummary(raw.release);
2182
+ if (release === void 0) return null;
2183
+ const upcoming = raw.upcoming === void 0 ? null : parseSummary(raw.upcoming);
2184
+ if (upcoming === void 0) return null;
2185
+ let prices = [];
2186
+ if (raw.prices != null) {
2187
+ if (!Array.isArray(raw.prices)) return null;
2188
+ const parsed = [];
2189
+ for (const entry of raw.prices) {
2190
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) return null;
2191
+ const row = entry;
2192
+ const categoryKey = typeof row.categoryKey === "string" ? row.categoryKey.trim() : "";
2193
+ if (!categoryKey) return null;
2194
+ const price = money(row.price);
2195
+ const previous = money(row.previousPrice);
2196
+ if (price === void 0 || price === null || previous === void 0) return null;
2197
+ const item = { categoryKey, price, previousPrice: previous };
2198
+ if (row.offerId !== void 0) {
2199
+ if (typeof row.offerId !== "string" || !row.offerId.trim()) return null;
2200
+ item.offerId = row.offerId.trim();
2201
+ }
2202
+ if (row.offerName !== void 0) {
2203
+ if (typeof row.offerName !== "string" || !row.offerName.trim()) return null;
2204
+ item.offerName = row.offerName.trim();
2205
+ }
2206
+ if (row.remaining !== void 0) {
2207
+ if (row.remaining !== null && (typeof row.remaining !== "number" || !Number.isInteger(row.remaining) || row.remaining < 0)) return null;
2208
+ item.remaining = row.remaining == null ? null : row.remaining;
2209
+ }
2210
+ for (const key of ["startsAt", "endsAt"]) {
2211
+ if (row[key] === void 0) continue;
2212
+ const at = timestamp(row[key]);
2213
+ if (at === void 0) return null;
2214
+ item[key] = at;
2215
+ }
2216
+ parsed.push(item);
2217
+ }
2218
+ prices = parsed;
2219
+ }
2220
+ return { state, fromPrice, previousPrice, currency, release, upcoming, prices };
2221
+ }
2222
+ function nextOfferTransitionAt(availability, now) {
2223
+ if (!availability) return null;
2224
+ let next = null;
2225
+ const consider = (at) => {
2226
+ if (at != null && at > now && (next === null || at < next)) next = at;
2227
+ };
2228
+ for (const summary of [availability.release, availability.upcoming]) {
2229
+ consider(summary?.startsAt);
2230
+ consider(summary?.endsAt);
2231
+ }
2232
+ for (const price of availability.prices) {
2233
+ consider(price.startsAt);
2234
+ consider(price.endsAt);
2235
+ }
2236
+ return next;
2237
+ }
2238
+ function ticketOfferPrices(availability) {
2239
+ const map = {};
2240
+ for (const entry of availability?.prices ?? []) map[entry.categoryKey] = entry.price;
2241
+ return map;
2242
+ }
2243
+
2244
+ // src/SeatPicker.ts
1992
2245
  var DEFAULT_API_BASE2 = "https://api.seatlayer.io";
1993
2246
  var DEFAULT_MAX_SELECTION2 = 10;
1994
2247
  var EXTEND_PROMPT_MS = 6e4;
@@ -2209,6 +2462,15 @@ var CSS = (
2209
2462
  .sl-cbbtn svg{width:14px;height:14px;stroke:currentColor;stroke-width:2;fill:none;stroke-linecap:round;stroke-linejoin:round}
2210
2463
 
2211
2464
  /* price panel \u2014 one compact filter control replaces the wrapping price-chip row. */
2465
+ .sl-offer{display:none;margin:12px 14px 2px;padding:12px;border:1px solid color-mix(in srgb,var(--sl-accent) 34%,var(--sl-line));
2466
+ border-radius:12px;background:color-mix(in srgb,var(--sl-accent) 8%,var(--sl-surface));color:var(--sl-text)}
2467
+ .sl-offer.has{display:block}.sl-offer-main{display:flex;align-items:flex-start;justify-content:space-between;gap:10px}
2468
+ .sl-offer-copy{min-width:0}.sl-offer-kicker{display:block;font-size:9px;font-weight:800;letter-spacing:.14em;text-transform:uppercase;color:var(--sl-accent)}
2469
+ .sl-offer-name{display:block;margin-top:2px;font-size:13px;font-weight:800;line-height:1.3}.sl-offer-line{display:block;margin-top:3px;font-size:11px;line-height:1.35;color:var(--sl-muted)}
2470
+ .sl-offer-info{position:relative;flex:none}.sl-offer-info>summary{list-style:none;width:25px;height:25px;border:1px solid var(--sl-line);border-radius:999px;
2471
+ display:grid;place-items:center;cursor:pointer;font-size:12px;font-weight:850;color:var(--sl-text);background:var(--sl-surface)}
2472
+ .sl-offer-info>summary::-webkit-details-marker{display:none}.sl-offer-info[open]>summary{border-color:var(--sl-accent);color:var(--sl-accent)}
2473
+ .sl-offer-detail{margin-top:10px;padding-top:9px;border-top:1px solid var(--sl-line);font-size:10.5px;line-height:1.45;color:var(--sl-muted)}
2212
2474
  .sl-sec{padding:14px 14px 4px;font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--sl-muted);font-weight:700}
2213
2475
  .sl-prices-sec{display:flex;align-items:center;justify-content:space-between;gap:10px;padding-top:13px}
2214
2476
  .sl-price-select{min-height:32px;max-width:130px;padding:5px 28px 5px 9px;border:1px solid var(--sl-line);border-radius:9px;
@@ -2219,6 +2481,7 @@ var CSS = (
2219
2481
  padding:0 6px;margin:0 -6px;border-radius:8px;cursor:pointer;transition:background .15s}
2220
2482
  .sl-price-row:hover,.sl-price-row:focus-visible{background:color-mix(in srgb,var(--sl-line) 40%,transparent)}
2221
2483
  .sl-price-row.sl-active{background:color-mix(in srgb,var(--sl-accent) 9%,transparent)}
2484
+ .sl-price-was{margin-left:auto;color:var(--sl-muted);font-size:10px;text-decoration:line-through}.sl-price-offer{display:block;color:var(--sl-accent);font-size:9px;font-weight:750}
2222
2485
  .sl-price-row.sl-active .sl-price-label{color:var(--sl-accent)}
2223
2486
  .sl-dot{width:9px;height:9px;border-radius:50%;flex:none}
2224
2487
  .sl-price-label{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:600}
@@ -2944,10 +3207,17 @@ var SeatPicker = class _SeatPicker {
2944
3207
  this.ro = null;
2945
3208
  this.holdTimer = null;
2946
3209
  this.toastTimer = null;
3210
+ this.offerRefreshTimer = null;
3211
+ /** Armed only when the offer schedule has a known future transition (or as a
3212
+ * bounded retry after a failed read) — never a fixed-cadence poll. */
3213
+ this.offerBoundaryTimer = null;
3214
+ this.offerVisibilityHandler = null;
2947
3215
  /** Short-lived UI motion timers; all are cancelled on destroy. */
2948
3216
  this.motionTimers = /* @__PURE__ */ new Set();
2949
3217
  // state
2950
3218
  this.currency = "USD";
3219
+ this.eventTimezone = null;
3220
+ this.offerAvailability = null;
2951
3221
  this.hold = null;
2952
3222
  /** Latest server expiry for the open hold (moves on extend). */
2953
3223
  this.holdExpiresAt = 0;
@@ -3006,6 +3276,8 @@ var SeatPicker = class _SeatPicker {
3006
3276
  this.secCardEl = null;
3007
3277
  this.viewEl = null;
3008
3278
  this.viewCleanup = null;
3279
+ /** Supersedes an older authored-view byte request when another seat is opened. */
3280
+ this.seatViewGen = 0;
3009
3281
  this.allSeatsCache = null;
3010
3282
  // F3 minimap
3011
3283
  this.miniCanvas = null;
@@ -3061,6 +3333,7 @@ var SeatPicker = class _SeatPicker {
3061
3333
  if (!options.event || typeof options.event !== "string") throw new Error("seatmap: `event` key is required");
3062
3334
  if (!options.container) throw new Error("seatmap: `container` is required (or use SeatPicker.open())");
3063
3335
  this.opts = { ...options, confirmSelection: options.confirmSelection ?? true };
3336
+ this.hostPricing = options.pricing;
3064
3337
  this.apiBase = (options.apiBase ?? DEFAULT_API_BASE2).replace(/\/+$/, "");
3065
3338
  this.access = options.transport ? null : createBuyerAccessContext(options, {
3066
3339
  onExpired: (event) => {
@@ -3077,6 +3350,10 @@ var SeatPicker = class _SeatPicker {
3077
3350
  onObjectUnavailable: (event) => this.opts.onSelectedObjectUnavailable?.(event)
3078
3351
  });
3079
3352
  this.api = options.transport ?? this.pubApi;
3353
+ this.buyerAssetUrls = new BuyerAssetObjectUrls(
3354
+ options.event,
3355
+ this.api.asset ? (key, asset) => this.api.asset(key, asset) : void 0
3356
+ );
3080
3357
  if (options.checkout === "hosted" && !this.pubApi) {
3081
3358
  console.warn(
3082
3359
  'seatlayer: checkout: "hosted" needs the widget\'s own transport \u2014 a custom `transport` owns its backend, so the picker is staying on onCheckout for this mount.'
@@ -3102,6 +3379,7 @@ var SeatPicker = class _SeatPicker {
3102
3379
  },
3103
3380
  onStatusChange: () => {
3104
3381
  this.syncPrices();
3382
+ this.scheduleOfferRefresh(true);
3105
3383
  this.evictTakenSelections();
3106
3384
  this.detectBooked();
3107
3385
  this.refreshMinimap();
@@ -3204,7 +3482,7 @@ var SeatPicker = class _SeatPicker {
3204
3482
  }
3205
3483
  }
3206
3484
  const sightHtml = hasStage && distance != null ? `<div class="sl-confirm-sight">${t2("picker.sightline", { m: distance })}</div>` : "";
3207
- const viewBtn = realPhoto ? `<button type="button" class="sl-confirm-view sl-confirm-thumbwrap" aria-label="${t2("picker.viewFromSeat", { label: seat.label })}"><img class="sl-confirm-thumb" src="${realPhoto}" alt="" /><span class="sl-confirm-thumb-badge">\u{1F52D} ${this.tf("picker.viewFromHere", "View from here")}</span></button>` : `<button type="button" class="sl-confirm-view sl-confirm-viewbtn" aria-label="${t2("picker.viewFromSeat", { label: seat.label })}"><span aria-hidden="true">\u{1F52D}</span><span>${this.tf("picker.viewFromHere", "View from here")}</span></button>`;
3485
+ const viewBtn = realPhoto ? `<button type="button" class="sl-confirm-view sl-confirm-thumbwrap" aria-label="${t2("picker.viewFromSeat", { label: seat.label })}"><img class="sl-confirm-thumb" alt="" /><span class="sl-confirm-thumb-badge">\u{1F52D} ${this.tf("picker.viewFromHere", "View from here")}</span></button>` : `<button type="button" class="sl-confirm-view sl-confirm-viewbtn" aria-label="${t2("picker.viewFromSeat", { label: seat.label })}"><span aria-hidden="true">\u{1F52D}</span><span>${this.tf("picker.viewFromHere", "View from here")}</span></button>`;
3208
3486
  return viewBtn + sightHtml;
3209
3487
  }
3210
3488
  /** "See it in 3D" (2D) / "View from this seat" (already in 3D) action for the
@@ -3523,6 +3801,7 @@ var SeatPicker = class _SeatPicker {
3523
3801
  </div>
3524
3802
  <div class="sl-sec sl-filtersec" data-ref="filtersSec">Filters</div>
3525
3803
  <div class="sl-filters" data-ref="filters"></div>
3804
+ <div class="sl-offer" data-ref="offer" role="status" aria-live="polite"></div>
3526
3805
  <div class="sl-sec sl-prices-sec" data-ref="pricesSec"><span>Ticket prices</span></div>
3527
3806
  <div class="sl-prices" data-ref="prices"></div>
3528
3807
  <div class="sl-live" data-ref="live" role="status" aria-live="polite"><span class="dot" aria-hidden="true"></span><span data-ref="liveText">Live availability \u2014 seats update in real time</span></div>
@@ -3543,6 +3822,13 @@ var SeatPicker = class _SeatPicker {
3543
3822
  this.els[el.dataset.ref] = el;
3544
3823
  });
3545
3824
  this.mapHost = this.els.map;
3825
+ void this.refreshOfferAvailability(false);
3826
+ if (this.api.availability) {
3827
+ this.offerVisibilityHandler = () => {
3828
+ if (!document.hidden && !this.destroyed) void this.refreshOfferAvailability(false);
3829
+ };
3830
+ document.addEventListener("visibilitychange", this.offerVisibilityHandler);
3831
+ }
3546
3832
  const applyLayout = () => {
3547
3833
  const w = root.clientWidth;
3548
3834
  if (w <= 0) return;
@@ -3647,7 +3933,7 @@ var SeatPicker = class _SeatPicker {
3647
3933
  }
3648
3934
  this.els.boot.remove();
3649
3935
  this.startRealtime();
3650
- this.salesClosed = !!info.salesClosed;
3936
+ this.salesClosed = !!info.salesClosed || !!this.opts.readOnly;
3651
3937
  this.controller.setViewMode(this.normalizeInitialView(this.opts.initialView));
3652
3938
  this.buildRegions();
3653
3939
  this.regions["bottom-right"].appendChild(this.els.zoom);
@@ -3662,6 +3948,7 @@ var SeatPicker = class _SeatPicker {
3662
3948
  const chartTheme = this.controller.doc?.theme;
3663
3949
  Object.entries(resolveTokens(chartTheme, this.opts.theme)).forEach(([k, v]) => root.style.setProperty(k, v));
3664
3950
  this.currency = info.currency ?? this.opts.currency ?? "USD";
3951
+ this.eventTimezone = info.timezone ?? null;
3665
3952
  const logoUrl = this.opts.theme?.logoUrl ?? chartTheme?.logoUrl;
3666
3953
  if (logoUrl) this.els.logo.innerHTML = `<img src="${logoUrl}" alt="">`;
3667
3954
  else this.els.logo.textContent = (this.opts.theme?.brandName ?? chartTheme?.brandName ?? info.eventName ?? "?").slice(0, 1).toUpperCase();
@@ -3893,8 +4180,9 @@ var SeatPicker = class _SeatPicker {
3893
4180
  * idempotent DOM apply used at load and on transition.
3894
4181
  */
3895
4182
  setSalesClosed(closed) {
3896
- if (this.salesClosed === closed) return;
3897
- this.salesClosed = closed;
4183
+ const next = closed || !!this.opts.readOnly;
4184
+ if (this.salesClosed === next) return;
4185
+ this.salesClosed = next;
3898
4186
  this.applySalesClosed();
3899
4187
  }
3900
4188
  applySalesClosed() {
@@ -4778,6 +5066,13 @@ var SeatPicker = class _SeatPicker {
4778
5066
  el.innerHTML = `<div class="sl-confirm-grid">` + identityFields + `</div><div class="sl-confirm-cat"><span class="sl-dot" style="background:${cat?.color ?? "#6e7bff"}"></span><span class="sl-confirm-cat-name">${safe(details?.categoryLabel ?? cat?.label ?? seat.categoryKey)}</span>` + (price != null ? `<span class="sl-confirm-price">${this.money(price)}</span>` : "") + `</div><div class="sl-confirm-body">` + this.wheelchairConfirmHtml(details?.wheelchairSpaceType) + this.commercialConfirmHtml(seat.commercial) + (this.seatViewEnabled() ? this.confirmThumbHtml(seat) : "") + this.see3dConfirmHtml() + `<div class="sl-confirm-row"><button type="button" class="sl-confirm-cancel">Cancel</button><button type="button" class="sl-confirm-add"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12.5l4 4L19 7"/></svg>Select</button></div></div>`;
4779
5067
  this.els.map.appendChild(el);
4780
5068
  this.confirmEl = el;
5069
+ const thumb = el.querySelector(".sl-confirm-thumb");
5070
+ if (thumb && seat.viewUrl) {
5071
+ const thumbReference = seat.viewMeta?.previewUrl ?? seat.viewUrl;
5072
+ void this.buyerAssetUrls.resolve(thumbReference).then((url) => {
5073
+ if (url && el.isConnected && this.confirmEl === el) thumb.src = url;
5074
+ }).catch((error) => this.opts.onError?.(error));
5075
+ }
4781
5076
  this.reanchorConfirm();
4782
5077
  el.querySelector(".sl-confirm-view")?.addEventListener("click", () => void this.openSeatView(seat));
4783
5078
  el.querySelector(".sl-confirm-3d")?.addEventListener("click", () => {
@@ -4858,6 +5153,7 @@ var SeatPicker = class _SeatPicker {
4858
5153
  */
4859
5154
  async openSeatView(seat) {
4860
5155
  if (!this.root || !this.seatViewEnabled()) return;
5156
+ const generation = ++this.seatViewGen;
4861
5157
  const doc = this.controller.doc;
4862
5158
  const activeId = this.controller.getActiveFloorId();
4863
5159
  const focal = seat.focalPoint ?? doc?.floors?.find((f) => f.id === activeId)?.focalPoint ?? doc?.focalPoint ?? { x: 0, y: 0 };
@@ -4865,9 +5161,24 @@ var SeatPicker = class _SeatPicker {
4865
5161
  let caption;
4866
5162
  let real = false;
4867
5163
  if (seat.viewUrl) {
5164
+ let resolvedUrl;
5165
+ let resolvedPreviewUrl = null;
5166
+ try {
5167
+ const previewReference = seat.viewMeta?.previewUrl;
5168
+ if (previewReference && previewReference !== seat.viewUrl) {
5169
+ resolvedPreviewUrl = await this.buyerAssetUrls.resolve(previewReference);
5170
+ resolvedUrl = seat.viewUrl;
5171
+ } else {
5172
+ resolvedUrl = await this.buyerAssetUrls.resolve(seat.viewUrl);
5173
+ }
5174
+ } catch (error) {
5175
+ if (generation === this.seatViewGen) this.opts.onError?.(error);
5176
+ return;
5177
+ }
5178
+ if (generation !== this.seatViewGen || !resolvedUrl || seat.viewMeta?.previewUrl && seat.viewMeta.previewUrl !== seat.viewUrl && !resolvedPreviewUrl || !this.root || !this.seatViewEnabled()) return;
4868
5179
  const view = {
4869
- url: seat.viewUrl,
4870
- ...seat.viewMeta?.previewUrl ? { previewUrl: seat.viewMeta.previewUrl } : {},
5180
+ url: resolvedUrl,
5181
+ ...resolvedPreviewUrl ? { previewUrl: resolvedPreviewUrl } : {},
4871
5182
  ...seat.viewMeta?.sourceWidth !== void 0 ? { sourceWidth: seat.viewMeta.sourceWidth } : {},
4872
5183
  ...seat.viewMeta?.sourceHeight !== void 0 ? { sourceHeight: seat.viewMeta.sourceHeight } : {},
4873
5184
  ...seat.viewMeta?.previewWidth !== void 0 ? { previewWidth: seat.viewMeta.previewWidth } : {},
@@ -4885,14 +5196,14 @@ var SeatPicker = class _SeatPicker {
4885
5196
  const { generateSeatPanorama } = await loadPanorama();
4886
5197
  pano2 = generateSeatPanorama(seat, focal, this.allSeats());
4887
5198
  } catch (err) {
4888
- this.opts.onError?.(err);
5199
+ if (generation === this.seatViewGen) this.opts.onError?.(err);
4889
5200
  return;
4890
5201
  }
4891
- if (!this.root || !this.seatViewEnabled()) return;
5202
+ if (generation !== this.seatViewGen || !this.root || !this.seatViewEnabled()) return;
4892
5203
  panoSource = { url: pano2.url, generated: true };
4893
5204
  caption = t2("picker.illustrationCaption", { m: pano2.distanceM });
4894
5205
  }
4895
- this.closeSeatView();
5206
+ this.closeSeatView(false);
4896
5207
  const el = document.createElement("div");
4897
5208
  el.className = "sl-view";
4898
5209
  el.setAttribute("role", "dialog");
@@ -4908,9 +5219,12 @@ var SeatPicker = class _SeatPicker {
4908
5219
  };
4909
5220
  if (delivery.upgradeUrl) {
4910
5221
  cancelUpgrade = schedulePanoramaUpgrade(() => {
4911
- void loadPanoramaImage(delivery.upgradeUrl, loadAbort.signal).then(() => {
4912
- if (!el.isConnected || loadAbort.signal.aborted) return;
4913
- pano.style.backgroundImage = `url("${delivery.upgradeUrl}")`;
5222
+ void this.buyerAssetUrls.resolve(delivery.upgradeUrl).then((url) => {
5223
+ if (!url || loadAbort.signal.aborted) return null;
5224
+ return loadPanoramaImage(url, loadAbort.signal).then(() => url);
5225
+ }).then((url) => {
5226
+ if (!url || !el.isConnected || loadAbort.signal.aborted) return;
5227
+ pano.style.backgroundImage = `url("${url}")`;
4914
5228
  }).catch(() => {
4915
5229
  });
4916
5230
  });
@@ -4986,7 +5300,8 @@ var SeatPicker = class _SeatPicker {
4986
5300
  el.removeEventListener("keydown", onKey);
4987
5301
  };
4988
5302
  }
4989
- closeSeatView() {
5303
+ closeSeatView(cancelPending = true) {
5304
+ if (cancelPending) this.seatViewGen += 1;
4990
5305
  this.viewCleanup?.();
4991
5306
  this.viewCleanup = null;
4992
5307
  this.viewEl?.remove();
@@ -5002,6 +5317,119 @@ var SeatPicker = class _SeatPicker {
5002
5317
  return `${n} ${this.currency}`;
5003
5318
  }
5004
5319
  }
5320
+ /**
5321
+ * Sleep until the offer schedule's next known transition, then re-read.
5322
+ *
5323
+ * A far-away boundary is capped: the wake re-reads, learns the (unchanged)
5324
+ * schedule, and re-arms — so a picker left open for days still tracks an
5325
+ * organizer's schedule edits at a cost of one request every few hours. No
5326
+ * future transition means no timer at all; an event with no releases does
5327
+ * zero background traffic. A wake in a hidden tab fetches nothing — the
5328
+ * visibilitychange handler owns catching that tab up.
5329
+ */
5330
+ scheduleOfferBoundary(availability) {
5331
+ if (this.offerBoundaryTimer) clearTimeout(this.offerBoundaryTimer);
5332
+ this.offerBoundaryTimer = null;
5333
+ if (!this.api.availability || this.destroyed) return;
5334
+ const now = Date.now();
5335
+ const boundary = nextOfferTransitionAt(availability, now);
5336
+ if (boundary == null) return;
5337
+ const MAX_SLEEP_MS = 6 * 36e5;
5338
+ const delay = Math.min(Math.max(boundary - now + 1e3, 1e3), MAX_SLEEP_MS);
5339
+ this.offerBoundaryTimer = setTimeout(() => {
5340
+ this.offerBoundaryTimer = null;
5341
+ if (document.hidden) return;
5342
+ void this.refreshOfferAvailability(false);
5343
+ }, delay);
5344
+ }
5345
+ /** Debounce the no-store offer read behind a burst of seat-status frames. */
5346
+ scheduleOfferRefresh(live) {
5347
+ if (!this.api.availability || this.destroyed) return;
5348
+ if (this.offerRefreshTimer) clearTimeout(this.offerRefreshTimer);
5349
+ this.offerRefreshTimer = setTimeout(() => {
5350
+ this.offerRefreshTimer = null;
5351
+ void this.refreshOfferAvailability(live);
5352
+ }, live ? 180 : 0);
5353
+ }
5354
+ /**
5355
+ * Pull the server's resolved answer. A failed refresh keeps the last truthful
5356
+ * answer: flashing back to a chart price while checkout still charges an
5357
+ * offer is worse than a temporarily stale remaining count.
5358
+ */
5359
+ async refreshOfferAvailability(live) {
5360
+ if (!this.api.availability || this.destroyed) return;
5361
+ try {
5362
+ const body = await this.api.availability(this.opts.event, live);
5363
+ if (this.destroyed) return;
5364
+ const availability = parseTicketOfferAvailability(body);
5365
+ if (!availability) return;
5366
+ this.offerAvailability = availability;
5367
+ this.scheduleOfferBoundary(availability);
5368
+ const server = ticketOfferPrices(availability);
5369
+ const merged = { ...this.hostPricing?.prices ?? {}, ...server };
5370
+ const pricing = Object.keys(merged).length > 0 || this.hostPricing?.formatter ? { prices: merged, ...this.hostPricing?.formatter ? { formatter: this.hostPricing.formatter } : {} } : void 0;
5371
+ this.setPricing(pricing);
5372
+ this.syncOffer();
5373
+ this.opts.onOfferAvailabilityChange?.(availability);
5374
+ } catch {
5375
+ if (!this.destroyed && !this.offerBoundaryTimer && !document.hidden) {
5376
+ this.offerBoundaryTimer = setTimeout(() => {
5377
+ this.offerBoundaryTimer = null;
5378
+ if (document.hidden) return;
5379
+ void this.refreshOfferAvailability(false);
5380
+ }, 3e4);
5381
+ }
5382
+ }
5383
+ }
5384
+ offerPrice(categoryKey) {
5385
+ if (!categoryKey) return null;
5386
+ return this.offerAvailability?.prices.find((entry) => entry.categoryKey === categoryKey) ?? null;
5387
+ }
5388
+ /** The compact current/upcoming offer card above Ticket prices. */
5389
+ syncOffer() {
5390
+ const host = this.els.offer;
5391
+ if (!host) return;
5392
+ const availability = this.offerAvailability;
5393
+ const active = availability?.release ?? null;
5394
+ const upcoming = !active ? availability?.upcoming ?? null : null;
5395
+ if (!availability || availability.state === "closed" || availability.state === "sold-out" || !active && !upcoming) {
5396
+ host.classList.remove("has");
5397
+ host.replaceChildren();
5398
+ return;
5399
+ }
5400
+ const offer = active ?? upcoming;
5401
+ const main = document.createElement("div");
5402
+ main.className = "sl-offer-main";
5403
+ const copy = document.createElement("div");
5404
+ copy.className = "sl-offer-copy";
5405
+ const kicker = document.createElement("span");
5406
+ kicker.className = "sl-offer-kicker";
5407
+ kicker.textContent = active ? "Current ticket offer" : "Upcoming ticket offer";
5408
+ const name = document.createElement("strong");
5409
+ name.className = "sl-offer-name";
5410
+ name.textContent = offer.name || (active ? "Current offer" : "Scheduled offer");
5411
+ const line = document.createElement("span");
5412
+ line.className = "sl-offer-line";
5413
+ const facts = [];
5414
+ if (active && availability.fromPrice != null) facts.push(this.money(availability.fromPrice / 100));
5415
+ if (active && offer.remaining != null) facts.push(`${offer.remaining} available`);
5416
+ if (active && offer.endsAt != null) facts.push(`until ${formatWhen(offer.endsAt, this.eventTimezone, this.opts.locale)}`);
5417
+ if (upcoming?.startsAt != null) facts.push(`starts ${formatWhen(upcoming.startsAt, this.eventTimezone, this.opts.locale)}`);
5418
+ line.textContent = facts.join(" \xB7 ");
5419
+ copy.append(kicker, name, line);
5420
+ const info = document.createElement("details");
5421
+ info.className = "sl-offer-info";
5422
+ const summary = document.createElement("summary");
5423
+ summary.setAttribute("aria-label", `How the ${name.textContent} offer works`);
5424
+ summary.textContent = "i";
5425
+ const detail = document.createElement("div");
5426
+ detail.className = "sl-offer-detail";
5427
+ detail.textContent = active ? `This price applies automatically to eligible seats. Tickets in active carts temporarily reduce the available quantity; released or expired holds return it. When the offer ends, the next matching offer or normal ticket price takes over.` : `Tickets are available at their normal price now. This scheduled offer will apply automatically to eligible seats when it starts.`;
5428
+ info.append(summary, detail);
5429
+ main.append(copy, info);
5430
+ host.replaceChildren(main);
5431
+ host.classList.add("has");
5432
+ }
5005
5433
  /**
5006
5434
  * The price the buyer will actually pay for a category (+tier): the host's
5007
5435
  * `pricing` override when present, else the chart's stored price. Every
@@ -5028,9 +5456,11 @@ var SeatPicker = class _SeatPicker {
5028
5456
  this.els.prices.classList.toggle("sl-expanded", overflow > 1 && this.pricesExpanded);
5029
5457
  this.els.prices.innerHTML = shown.map((c) => {
5030
5458
  const price = this.catPrice(c);
5459
+ const offer = this.offerPrice(c.key);
5460
+ const previous = offer?.previousPrice != null && offer.previousPrice > offer.price ? offer.previousPrice : null;
5031
5461
  const active = this.focusedCatKey === c.key;
5032
5462
  const dim = this.priceBandKeys != null && !this.priceBandKeys.has(c.key);
5033
- return `<div class="sl-price-row${dim ? " sl-dim" : ""}${active ? " sl-active" : ""}" data-cat="${c.key}" role="button" tabindex="0" aria-pressed="${active}" title="${active ? "Show all seats" : `Show ${c.label} seats on the map`}"><span class="sl-dot" style="background:${c.color}"></span><span class="sl-price-label">${c.label}</span><span class="sl-price-left">${left[c.key] ?? 0} left</span>` + (price != null ? `<span class="sl-price-amt">${this.money(price)}</span>` : "") + `</div>`;
5463
+ return `<div class="sl-price-row${dim ? " sl-dim" : ""}${active ? " sl-active" : ""}" data-cat="${escapeOption(c.key)}" role="button" tabindex="0" aria-pressed="${active}" title="${escapeOption(active ? "Show all seats" : `Show ${c.label} seats on the map`)}"><span class="sl-dot" style="background:${escapeOption(c.color)}"></span><span class="sl-price-label">${escapeOption(c.label)}` + (offer?.offerName ? `<small class="sl-price-offer">${escapeOption(offer.offerName)}</small>` : "") + `</span><span class="sl-price-left">${left[c.key] ?? 0} left</span>` + (previous != null ? `<span class="sl-price-was">${escapeOption(this.money(previous))}</span>` : "") + (price != null ? `<span class="sl-price-amt">${this.money(price)}</span>` : "") + `</div>`;
5034
5464
  }).join("") + (overflow > 1 ? `<button type="button" class="sl-price-more" aria-expanded="${!collapsed}">` + (collapsed ? `Show all ${doc.categories.length} ticket types` : "Show fewer") + `</button>` : "") + `<div class="sl-status-key" aria-label="Seat status legend"><span class="sl-status-item"><i class="sl-status-icon" aria-hidden="true"><svg viewBox="0 0 24 24"><rect x="5" y="10" width="14" height="10" rx="2"/><path d="M8 10V7a4 4 0 0 1 8 0v3"/></svg></i>Temporarily held</span><span class="sl-status-item"><i class="sl-status-icon sold" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M7 17L17 7"/></svg></i>Sold</span></div>`;
5035
5465
  this.els.prices.querySelectorAll(".sl-price-row").forEach((row) => {
5036
5466
  row.addEventListener("mouseenter", () => this.controller.getRenderer()?.setCategoryHighlight?.(row.dataset.cat ?? null));
@@ -5686,6 +6116,7 @@ var SeatPicker = class _SeatPicker {
5686
6116
  }
5687
6117
  emitHoldChange() {
5688
6118
  const hold = this.hold;
6119
+ this.scheduleOfferRefresh(true);
5689
6120
  this.opts.onHoldChange?.(
5690
6121
  hold,
5691
6122
  hold?.seats ?? [],
@@ -5953,19 +6384,33 @@ var SeatPicker = class _SeatPicker {
5953
6384
  async seatViewFor3d(seatId) {
5954
6385
  const seat = this.allSeats().find((s) => s.id === seatId);
5955
6386
  if (!seat) return null;
5956
- if (seat.viewUrl) return {
5957
- url: seat.viewUrl,
5958
- ...seat.viewMeta?.previewUrl ? { previewUrl: seat.viewMeta.previewUrl } : {},
5959
- ...seat.viewMeta?.sourceWidth !== void 0 ? { sourceWidth: seat.viewMeta.sourceWidth } : {},
5960
- ...seat.viewMeta?.sourceHeight !== void 0 ? { sourceHeight: seat.viewMeta.sourceHeight } : {},
5961
- ...seat.viewMeta?.previewWidth !== void 0 ? { previewWidth: seat.viewMeta.previewWidth } : {},
5962
- ...seat.viewMeta?.previewHeight !== void 0 ? { previewHeight: seat.viewMeta.previewHeight } : {},
5963
- ...seat.viewMeta?.initialBearingDeg !== void 0 ? { initialBearingDeg: seat.viewMeta.initialBearingDeg } : {},
5964
- ...seat.viewMeta?.initialPitchDeg !== void 0 ? { initialPitchDeg: seat.viewMeta.initialPitchDeg } : {},
5965
- ...seat.viewMeta?.coverage ? { coverage: seat.viewMeta.coverage } : {},
5966
- ...seat.viewMeta?.capturedAt ? { capturedAt: seat.viewMeta.capturedAt } : {},
5967
- ...seat.viewMeta?.sourceLabel ? { sourceLabel: seat.viewMeta.sourceLabel } : {}
5968
- };
6387
+ if (seat.viewUrl) {
6388
+ try {
6389
+ const previewReference = seat.viewMeta?.previewUrl;
6390
+ const progressive = !!previewReference && previewReference !== seat.viewUrl;
6391
+ const previewUrl = progressive ? await this.buyerAssetUrls.resolve(previewReference) : null;
6392
+ const url = progressive ? seat.viewUrl : await this.buyerAssetUrls.resolve(seat.viewUrl);
6393
+ if (!url) return null;
6394
+ if (progressive && !previewUrl) return null;
6395
+ return {
6396
+ url,
6397
+ ...previewUrl ? { previewUrl } : {},
6398
+ ...progressive ? { resolveUrl: (reference) => this.buyerAssetUrls.resolve(reference) } : {},
6399
+ ...seat.viewMeta?.sourceWidth !== void 0 ? { sourceWidth: seat.viewMeta.sourceWidth } : {},
6400
+ ...seat.viewMeta?.sourceHeight !== void 0 ? { sourceHeight: seat.viewMeta.sourceHeight } : {},
6401
+ ...seat.viewMeta?.previewWidth !== void 0 ? { previewWidth: seat.viewMeta.previewWidth } : {},
6402
+ ...seat.viewMeta?.previewHeight !== void 0 ? { previewHeight: seat.viewMeta.previewHeight } : {},
6403
+ ...seat.viewMeta?.initialBearingDeg !== void 0 ? { initialBearingDeg: seat.viewMeta.initialBearingDeg } : {},
6404
+ ...seat.viewMeta?.initialPitchDeg !== void 0 ? { initialPitchDeg: seat.viewMeta.initialPitchDeg } : {},
6405
+ ...seat.viewMeta?.coverage ? { coverage: seat.viewMeta.coverage } : {},
6406
+ ...seat.viewMeta?.capturedAt ? { capturedAt: seat.viewMeta.capturedAt } : {},
6407
+ ...seat.viewMeta?.sourceLabel ? { sourceLabel: seat.viewMeta.sourceLabel } : {}
6408
+ };
6409
+ } catch (error) {
6410
+ this.opts.onError?.(error);
6411
+ return null;
6412
+ }
6413
+ }
5969
6414
  const doc = this.controller.doc;
5970
6415
  if (!doc) return null;
5971
6416
  const activeId = this.controller.getActiveFloorId();
@@ -6415,6 +6860,7 @@ var SeatPicker = class _SeatPicker {
6415
6860
  flashOnLiveChange: true,
6416
6861
  onStatusChange: () => {
6417
6862
  this.syncPrices();
6863
+ this.scheduleOfferRefresh(true);
6418
6864
  this.detectBooked();
6419
6865
  this.refreshMinimap();
6420
6866
  this.pushAvailabilityTo3d();
@@ -6547,9 +6993,18 @@ var SeatPicker = class _SeatPicker {
6547
6993
  this.closeSeatView();
6548
6994
  this.closeCheckoutPanel();
6549
6995
  this.exit3d();
6996
+ this.buyerAssetUrls.dispose();
6550
6997
  this.stopHoldTimer();
6551
6998
  if (this.toastTimer) clearTimeout(this.toastTimer);
6552
6999
  if (this.liveTimer) clearTimeout(this.liveTimer);
7000
+ if (this.offerRefreshTimer) clearTimeout(this.offerRefreshTimer);
7001
+ if (this.offerBoundaryTimer) clearTimeout(this.offerBoundaryTimer);
7002
+ this.offerRefreshTimer = null;
7003
+ this.offerBoundaryTimer = null;
7004
+ if (this.offerVisibilityHandler) {
7005
+ document.removeEventListener("visibilitychange", this.offerVisibilityHandler);
7006
+ this.offerVisibilityHandler = null;
7007
+ }
6553
7008
  for (const timer of this.motionTimers) clearTimeout(timer);
6554
7009
  this.motionTimers.clear();
6555
7010
  this.ro?.disconnect();
@@ -6690,10 +7145,12 @@ export {
6690
7145
  markerOf,
6691
7146
  mutationCount,
6692
7147
  needsMoveConfirmation,
7148
+ parseTicketOfferAvailability,
6693
7149
  planAssignment,
6694
7150
  retryAfterCopy,
6695
7151
  selectionSources,
6696
7152
  stateBadge,
6697
- suggestMarker
7153
+ suggestMarker,
7154
+ ticketOfferPrices
6698
7155
  };
6699
7156
  //# sourceMappingURL=index.js.map