@aranova/tracking-react 1.18.0 → 1.19.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/client.js CHANGED
@@ -34,10 +34,15 @@ __export(client_exports, {
34
34
  AdPlatformTracking: () => AdPlatformTracking,
35
35
  BookingHandoffLink: () => BookingHandoffLink,
36
36
  ConsentBanner: () => ConsentBanner,
37
+ DEFAULT_VIDEO_CHECKPOINTS: () => DEFAULT_VIDEO_CHECKPOINTS,
37
38
  GoogleAdsTracking: () => GoogleAdsTracking,
38
39
  PhoneField: () => PhoneField,
40
+ TrackedVideo: () => TrackedVideo,
41
+ TrackedVideoStage: () => TrackedVideoStage,
39
42
  createLeadFormSubmit: () => createLeadFormSubmit,
40
43
  createTracking: () => createTracking,
44
+ streamManifestUrl: () => streamManifestUrl,
45
+ streamPosterUrl: () => streamPosterUrl,
41
46
  useBookingHandoffUrl: () => useBookingHandoffUrl,
42
47
  useBookingIntegration: () => useBookingIntegration,
43
48
  useBookingSlots: () => useBookingSlots,
@@ -48,6 +53,7 @@ __export(client_exports, {
48
53
  useGclid: () => useGclid,
49
54
  usePhoneConfig: () => usePhoneConfig,
50
55
  usePhoneField: () => usePhoneField,
56
+ useTrackedVideoConfig: () => useTrackedVideoConfig,
51
57
  useTrackingParams: () => useTrackingParams
52
58
  });
53
59
  module.exports = __toCommonJS(client_exports);
@@ -307,7 +313,8 @@ var TRACKING_CAPABILITIES = [
307
313
  "form_capture",
308
314
  "phone_click_capture",
309
315
  "phone_fields",
310
- "typed_form_submit"
316
+ "typed_form_submit",
317
+ "video_tracking"
311
318
  ];
312
319
  var CAPABILITY_DOM_ATTRIBUTE = "data-aranova-capability";
313
320
  var registered = /* @__PURE__ */ new Set();
@@ -1088,8 +1095,48 @@ function parseTrigger(value) {
1088
1095
  if (typeof t.threshold_seconds === "number") spec.threshold_seconds = t.threshold_seconds;
1089
1096
  if (typeof t.page_threshold === "number") spec.page_threshold = t.page_threshold;
1090
1097
  if (typeof t.page_name === "string") spec.page_name = t.page_name;
1098
+ if (t.metric === "percent" || t.metric === "seconds") spec.metric = t.metric;
1099
+ if (typeof t.threshold === "number") spec.threshold = t.threshold;
1100
+ if (typeof t.video_id === "string" || t.video_id === null) spec.video_id = t.video_id;
1091
1101
  return spec;
1092
1102
  }
1103
+ function parseVideoEntry(fallbackKey2, entry) {
1104
+ const key = typeof entry.key === "string" && entry.key ? entry.key : fallbackKey2;
1105
+ if (!key) return null;
1106
+ const host = typeof entry.host === "string" ? entry.host : "";
1107
+ const title = typeof entry.title === "string" && entry.title ? entry.title : key;
1108
+ const sourcesRaw = isRecord(entry.sources) ? entry.sources : null;
1109
+ const desktop = sourcesRaw && typeof sourcesRaw.desktop === "string" ? sourcesRaw.desktop : typeof entry.desktop_uid === "string" ? entry.desktop_uid : "";
1110
+ if (!host || !desktop) return null;
1111
+ const mobile = sourcesRaw && typeof sourcesRaw.mobile === "string" ? sourcesRaw.mobile : typeof entry.mobile_uid === "string" ? entry.mobile_uid : null;
1112
+ const posterTime = typeof entry.poster_time === "number" ? entry.poster_time : typeof entry.poster_time_s === "number" ? entry.poster_time_s : 4;
1113
+ return {
1114
+ key,
1115
+ title,
1116
+ host,
1117
+ sources: { desktop, mobile },
1118
+ poster_time: posterTime,
1119
+ duration_s: typeof entry.duration_s === "number" ? entry.duration_s : null
1120
+ };
1121
+ }
1122
+ function parseVideos(value) {
1123
+ const out = {};
1124
+ if (Array.isArray(value)) {
1125
+ for (const item of value) {
1126
+ if (!isRecord(item)) continue;
1127
+ const parsed = parseVideoEntry("", item);
1128
+ if (parsed) out[parsed.key] = parsed;
1129
+ }
1130
+ return out;
1131
+ }
1132
+ if (!isRecord(value)) return out;
1133
+ for (const [key, entry] of Object.entries(value)) {
1134
+ if (!isRecord(entry)) continue;
1135
+ const parsed = parseVideoEntry(key, entry);
1136
+ if (parsed) out[parsed.key] = parsed;
1137
+ }
1138
+ return out;
1139
+ }
1093
1140
  function parseConversionConfig(raw) {
1094
1141
  if (!raw || typeof raw !== "object") return null;
1095
1142
  const obj = raw;
@@ -1140,7 +1187,8 @@ function parseConversionConfig(raw) {
1140
1187
  goals,
1141
1188
  linked_hostnames: Array.isArray(obj.linked_hostnames) ? obj.linked_hostnames.filter((h) => typeof h === "string" && h.length > 0) : [],
1142
1189
  integrations: parseIntegrations(obj.integrations),
1143
- lead_forms: parseLeadForms(obj.lead_forms)
1190
+ lead_forms: parseLeadForms(obj.lead_forms),
1191
+ videos: parseVideos(obj.videos)
1144
1192
  };
1145
1193
  }
1146
1194
  function parseLeadForms(value) {
@@ -1386,6 +1434,21 @@ function automaticThresholdMet(goal, eventType, metadata) {
1386
1434
  case "form_start":
1387
1435
  case "phone_click":
1388
1436
  return true;
1437
+ case "video_play":
1438
+ return trigger.video_id == null || hasStringField(metadata, "video_id") && metadata.video_id === trigger.video_id;
1439
+ case "video_progress": {
1440
+ if (trigger.video_id != null && (!hasStringField(metadata, "video_id") || metadata.video_id !== trigger.video_id)) {
1441
+ return false;
1442
+ }
1443
+ if (typeof trigger.threshold !== "number") return false;
1444
+ if (trigger.metric === "seconds") {
1445
+ return hasNumberField(metadata, "watched_s") && metadata.watched_s >= trigger.threshold;
1446
+ }
1447
+ if (trigger.metric === "percent") {
1448
+ return hasNumberField(metadata, "max_percent") && metadata.max_percent >= trigger.threshold || hasNumberField(metadata, "watched_percent") && metadata.watched_percent >= trigger.threshold;
1449
+ }
1450
+ return false;
1451
+ }
1389
1452
  default:
1390
1453
  return false;
1391
1454
  }
@@ -1422,6 +1485,9 @@ var RETRY_BASE_MS = 5e3;
1422
1485
  var RETRY_MAX_MS = 5 * 6e4;
1423
1486
  var runtimes = /* @__PURE__ */ new Map();
1424
1487
  var configuredIds = /* @__PURE__ */ new Set();
1488
+ var EMPTY_GOALS = [];
1489
+ var EMPTY_STRINGS = [];
1490
+ var EMPTY_VIDEOS = {};
1425
1491
  var scriptLoad = null;
1426
1492
  var jsInitialized = false;
1427
1493
  function cacheKey2(url) {
@@ -1612,11 +1678,11 @@ var TrackingConfigRuntime = class {
1612
1678
  void this.flush();
1613
1679
  }
1614
1680
  listGoals() {
1615
- return this.current?.goals ?? [];
1681
+ return this.current?.goals ?? EMPTY_GOALS;
1616
1682
  }
1617
1683
  /** Sibling hostnames for the linker — read from the cached object even while unconfirmed. */
1618
1684
  linkedHostnames() {
1619
- return this.current?.linked_hostnames ?? [];
1685
+ return this.current?.linked_hostnames ?? EMPTY_STRINGS;
1620
1686
  }
1621
1687
  /**
1622
1688
  * Integration blocks, from the cached object even while unconfirmed: the booking fallback
@@ -1629,6 +1695,14 @@ var TrackingConfigRuntime = class {
1629
1695
  leadForms() {
1630
1696
  return this.current?.lead_forms ?? null;
1631
1697
  }
1698
+ /** Configured Cloudflare Stream tracked videos for this site. */
1699
+ videos() {
1700
+ return this.current?.videos ?? EMPTY_VIDEOS;
1701
+ }
1702
+ /** Look up one configured Cloudflare Stream tracked video by key. */
1703
+ getVideo(key) {
1704
+ return this.current?.videos?.[key] ?? null;
1705
+ }
1632
1706
  async revalidateNow() {
1633
1707
  try {
1634
1708
  const headers = {};
@@ -1785,6 +1859,16 @@ var TrackingConfigRuntime = class {
1785
1859
  this.scheduleRetry();
1786
1860
  return;
1787
1861
  }
1862
+ if (outcome === "fired" && typeof window !== "undefined" && typeof window.fbq === "function") {
1863
+ try {
1864
+ window.fbq("trackCustom", item.key, {
1865
+ ...value != null ? { value } : {},
1866
+ ...currency ? { currency } : {},
1867
+ ...item.transactionId ? { transaction_id: item.transactionId } : {}
1868
+ });
1869
+ } catch {
1870
+ }
1871
+ }
1788
1872
  this.conversionQueue.shift();
1789
1873
  }
1790
1874
  }
@@ -1928,12 +2012,24 @@ function createConversionAutoFire(store) {
1928
2012
  const firing = goal.firing;
1929
2013
  const cents = firing.value_cents ?? null;
1930
2014
  const currency = firing.currency ?? null;
1931
- fireConversionWithConsent({
2015
+ const value = cents != null && currency ? fromMinor(cents, currency) : null;
2016
+ const outcome = fireConversionWithConsent({
1932
2017
  sendTo: firing.send_to,
1933
- value: cents != null && currency ? fromMinor(cents, currency) : null,
2018
+ value,
1934
2019
  currency,
1935
2020
  transactionId
1936
2021
  });
2022
+ if (outcome === "fired" && typeof window !== "undefined" && typeof window.fbq === "function") {
2023
+ try {
2024
+ window.fbq("trackCustom", goal.key, {
2025
+ event_type: eventType,
2026
+ ...value != null ? { value } : {},
2027
+ ...currency ? { currency } : {},
2028
+ transaction_id: transactionId
2029
+ });
2030
+ } catch {
2031
+ }
2032
+ }
1937
2033
  }
1938
2034
  }
1939
2035
  return {
@@ -2066,6 +2162,18 @@ var EVENT_SEMANTICS = {
2066
2162
  category: "page",
2067
2163
  outcomeRole: "navigation",
2068
2164
  clientVisibility: "simple"
2165
+ },
2166
+ video_play: {
2167
+ label: "Video played",
2168
+ category: "engagement",
2169
+ outcomeRole: "engagement",
2170
+ clientVisibility: "simple"
2171
+ },
2172
+ video_progress: {
2173
+ label: "Video watched",
2174
+ category: "engagement",
2175
+ outcomeRole: "engagement",
2176
+ clientVisibility: "detailed"
2069
2177
  }
2070
2178
  };
2071
2179
  function getEventSemantics(eventName) {
@@ -2653,14 +2761,31 @@ function createTrackingClient(config) {
2653
2761
  metadata: input.metadata ?? null,
2654
2762
  occurred_at: occurredAt
2655
2763
  });
2656
- if (queue.length >= maxQueueSize || input.eventType === "form_details" || getEventSemantics(input.eventType)?.outcomeRole === "lead") {
2764
+ const triggerReason = input.metadata?.trigger;
2765
+ const isUrgentVideoEvent = input.eventType === "video_play" || input.eventType === "video_progress" && (triggerReason === "ended" || triggerReason === "goal" || triggerReason === "checkpoint");
2766
+ if (queue.length >= maxQueueSize || input.eventType === "form_details" || getEventSemantics(input.eventType)?.outcomeRole === "lead" || isUrgentVideoEvent) {
2657
2767
  void flush();
2658
2768
  } else {
2659
2769
  scheduleFlush();
2660
2770
  }
2661
2771
  }
2772
+ const beforeUnloadListeners = /* @__PURE__ */ new Set();
2773
+ let runningBeforeUnload = false;
2662
2774
  function flushOnUnload() {
2663
2775
  if (destroyed) return;
2776
+ if (!runningBeforeUnload && beforeUnloadListeners.size > 0) {
2777
+ runningBeforeUnload = true;
2778
+ try {
2779
+ for (const listener of beforeUnloadListeners) {
2780
+ try {
2781
+ listener();
2782
+ } catch {
2783
+ }
2784
+ }
2785
+ } finally {
2786
+ runningBeforeUnload = false;
2787
+ }
2788
+ }
2664
2789
  bufferQueued();
2665
2790
  clearScheduledFlush();
2666
2791
  if (pending.length === 0) return;
@@ -2681,6 +2806,12 @@ function createTrackingClient(config) {
2681
2806
  trackEvent,
2682
2807
  flush,
2683
2808
  flushBeacon: flushOnUnload,
2809
+ onBeforeUnload: (listener) => {
2810
+ beforeUnloadListeners.add(listener);
2811
+ return () => {
2812
+ beforeUnloadListeners.delete(listener);
2813
+ };
2814
+ },
2684
2815
  getSessionId: () => sessionId,
2685
2816
  getVisitorId: () => visitorId,
2686
2817
  destroy: () => {
@@ -2691,6 +2822,7 @@ function createTrackingClient(config) {
2691
2822
  destroyed = true;
2692
2823
  clearScheduledFlush();
2693
2824
  queue = [];
2825
+ beforeUnloadListeners.clear();
2694
2826
  if (typeof window !== "undefined") {
2695
2827
  window.removeEventListener("pagehide", flushOnUnload);
2696
2828
  window.removeEventListener("visibilitychange", onVisibilityChange);
@@ -2991,6 +3123,86 @@ var timeOnSiteConfigSchema = z15.strictObject({
2991
3123
  thresholdSeconds: z15.int().check(z15.positive())
2992
3124
  });
2993
3125
 
3126
+ // ../tracking-core/src/events/video.ts
3127
+ var z16 = __toESM(require("zod/mini"));
3128
+
3129
+ // ../tracking-core/src/video-ranges.ts
3130
+ var VIDEO_ID_MAX_LENGTH = 64;
3131
+ var VIDEO_ID_PATTERN = /^[A-Za-z0-9_-]+$/;
3132
+ function roundVideoSeconds(value) {
3133
+ if (!Number.isFinite(value) || value < 0) return 0;
3134
+ return Math.round(value * 100) / 100;
3135
+ }
3136
+ function mergeVideoRanges(ranges, epsilonSeconds = 0.25) {
3137
+ const valid2 = ranges.filter((r) => Number.isFinite(r.start_s) && Number.isFinite(r.end_s) && r.end_s > r.start_s).map((r) => ({
3138
+ start_s: roundVideoSeconds(r.start_s),
3139
+ end_s: roundVideoSeconds(r.end_s)
3140
+ })).filter((r) => r.end_s - r.start_s >= 0.05).sort((a, b) => a.start_s - b.start_s || a.end_s - b.end_s);
3141
+ if (valid2.length === 0) return [];
3142
+ const merged = [{ ...valid2[0] }];
3143
+ for (let i = 1; i < valid2.length; i += 1) {
3144
+ const cur = valid2[i];
3145
+ const last = merged[merged.length - 1];
3146
+ if (cur.start_s <= last.end_s + epsilonSeconds) {
3147
+ last.end_s = roundVideoSeconds(Math.max(last.end_s, cur.end_s));
3148
+ } else {
3149
+ merged.push({ ...cur });
3150
+ }
3151
+ }
3152
+ return merged;
3153
+ }
3154
+ function sumVideoRangesSeconds(ranges, durationSeconds = 0) {
3155
+ const merged = mergeVideoRanges(ranges);
3156
+ const raw = merged.reduce((acc, r) => acc + Math.max(0, r.end_s - r.start_s), 0);
3157
+ const clamped = durationSeconds > 0 ? Math.min(raw, durationSeconds) : raw;
3158
+ return roundVideoSeconds(clamped);
3159
+ }
3160
+
3161
+ // ../tracking-core/src/events/video.ts
3162
+ var videoIdSchema = z16.string().check(z16.minLength(1), z16.maxLength(VIDEO_ID_MAX_LENGTH), z16.regex(VIDEO_ID_PATTERN));
3163
+ var videoVariantSchema = z16.enum(["16x9", "9x16"]);
3164
+ var videoProgressTriggerReasonSchema = z16.enum([
3165
+ "checkpoint",
3166
+ "goal",
3167
+ "pause",
3168
+ "seek",
3169
+ "ended",
3170
+ "page_exit"
3171
+ ]);
3172
+ var videoRangeSchema = z16.strictObject({
3173
+ start_s: z16.number().check(z16.gte(0)),
3174
+ end_s: z16.number().check(z16.gte(0))
3175
+ });
3176
+ var videoPlayMetadataSchema = z16.strictObject({
3177
+ video_id: videoIdSchema,
3178
+ variant: videoVariantSchema,
3179
+ current_s: z16.number().check(z16.gte(0)),
3180
+ duration_s: z16.number().check(z16.gte(0)),
3181
+ play_sequence: z16.int().check(z16.gte(1)),
3182
+ page: z16.strictObject({
3183
+ path: z16.string()
3184
+ })
3185
+ });
3186
+ var videoPlayConfigSchema = z16.strictObject({});
3187
+ var videoProgressMetadataSchema = z16.strictObject({
3188
+ video_id: videoIdSchema,
3189
+ variant: videoVariantSchema,
3190
+ trigger: videoProgressTriggerReasonSchema,
3191
+ current_s: z16.number().check(z16.gte(0)),
3192
+ furthest_s: z16.number().check(z16.gte(0)),
3193
+ watched_s: z16.number().check(z16.gte(0)),
3194
+ duration_s: z16.number().check(z16.gte(0)),
3195
+ max_percent: z16.int().check(z16.gte(0), z16.lte(100)),
3196
+ watched_percent: z16.int().check(z16.gte(0), z16.lte(100)),
3197
+ play_count: z16.int().check(z16.gte(1)),
3198
+ seek_count: z16.int().check(z16.gte(0)),
3199
+ ranges: z16.array(videoRangeSchema),
3200
+ page: z16.strictObject({
3201
+ path: z16.string()
3202
+ })
3203
+ });
3204
+ var videoProgressConfigSchema = z16.strictObject({});
3205
+
2994
3206
  // ../tracking-core/src/events/registry.ts
2995
3207
  var EVENT_REGISTRY = {
2996
3208
  // --- automatic triggers ---
@@ -3057,6 +3269,18 @@ var EVENT_REGISTRY = {
3057
3269
  metadataSchema: siteLinkArrivalMetadataSchema,
3058
3270
  configSchema: siteLinkArrivalConfigSchema
3059
3271
  },
3272
+ video_play: {
3273
+ kind: "automatic",
3274
+ semantics: EVENT_SEMANTICS.video_play,
3275
+ metadataSchema: videoPlayMetadataSchema,
3276
+ configSchema: videoPlayConfigSchema
3277
+ },
3278
+ video_progress: {
3279
+ kind: "automatic",
3280
+ semantics: EVENT_SEMANTICS.video_progress,
3281
+ metadataSchema: videoProgressMetadataSchema,
3282
+ configSchema: videoProgressConfigSchema
3283
+ },
3060
3284
  // --- manual triggers ---
3061
3285
  form_submit: {
3062
3286
  kind: "manual",
@@ -3157,6 +3381,17 @@ function currentPath2() {
3157
3381
 
3158
3382
  // ../tracking-core/src/ingest-typed.ts
3159
3383
  var clientSettings = /* @__PURE__ */ new WeakMap();
3384
+ var latestTrackingConfig;
3385
+ var latestRawClient = null;
3386
+ function setDefaultTrackingConfig(ref) {
3387
+ if (ref) latestTrackingConfig = ref;
3388
+ }
3389
+ function getActiveTrackingEnv() {
3390
+ return {
3391
+ rawClient: latestRawClient,
3392
+ trackingConfig: latestTrackingConfig
3393
+ };
3394
+ }
3160
3395
  function typedClientSettings(client) {
3161
3396
  return clientSettings.get(client) ?? {};
3162
3397
  }
@@ -3187,7 +3422,9 @@ function createTypedClient(raw, registry, options = {}) {
3187
3422
  getSessionId: raw.getSessionId.bind(raw),
3188
3423
  getVisitorId: raw.getVisitorId.bind(raw)
3189
3424
  };
3190
- clientSettings.set(client, options);
3425
+ latestRawClient = raw;
3426
+ if (options.trackingConfig) latestTrackingConfig = options.trackingConfig;
3427
+ clientSettings.set(client, { ...options, rawClient: raw });
3191
3428
  return client;
3192
3429
  }
3193
3430
 
@@ -3705,6 +3942,9 @@ function attachPageExit(client) {
3705
3942
  emitSegment(currentPath4, true);
3706
3943
  }
3707
3944
  const unsubscribeNav = onHistoryChange(onNavigate);
3945
+ const unsubscribeBeforeUnload = client.onBeforeUnload?.(() => {
3946
+ emitSegment(currentPath4, false);
3947
+ });
3708
3948
  window.addEventListener("scroll", onScroll, { passive: true });
3709
3949
  document.addEventListener("visibilitychange", onVisibilityChange);
3710
3950
  window.addEventListener("pagehide", onPageHide);
@@ -3713,6 +3953,7 @@ function attachPageExit(client) {
3713
3953
  if (rafId !== null) cancelAnimationFrame(rafId);
3714
3954
  gate.cancel();
3715
3955
  unsubscribeNav();
3956
+ unsubscribeBeforeUnload?.();
3716
3957
  window.removeEventListener("scroll", onScroll);
3717
3958
  document.removeEventListener("visibilitychange", onVisibilityChange);
3718
3959
  window.removeEventListener("pagehide", onPageHide);
@@ -3882,7 +4123,7 @@ function attachPhoneClickCapture(client, config) {
3882
4123
  }
3883
4124
 
3884
4125
  // ../tracking-core/src/lead-forms/lead-forms.gen.ts
3885
- var z16 = __toESM(require("zod/mini"));
4126
+ var z17 = __toESM(require("zod/mini"));
3886
4127
  var LEAD_FORM_REGISTRY_VERSION = 2;
3887
4128
  var LEAD_FORM_ROLES = [
3888
4129
  "given_name",
@@ -3908,28 +4149,28 @@ var LEAD_FORM_ROLES = [
3908
4149
  "attribution"
3909
4150
  ];
3910
4151
  var leadFormPrimitiveSchemas = /* @__PURE__ */ (() => ({
3911
- string: z16.string().check(z16.maxLength(120)),
3912
- text: z16.string().check(z16.maxLength(2e3)),
3913
- enum: z16.string().check(z16.regex(/^[a-z0-9-]{1,48}$/)),
3914
- multi_enum: z16.array(z16.string().check(z16.regex(/^[a-z0-9-]{1,48}$/))).check(z16.maxLength(10)),
3915
- bool: z16.boolean(),
3916
- date: z16.string().check(z16.regex(/^\d{4}-\d{2}-\d{2}$/)),
3917
- time_window: z16.strictObject({
3918
- start: z16.string().check(z16.regex(/^([01]\d|2[0-3]):[0-5]\d$/)),
3919
- end: z16.nullable(z16.string().check(z16.regex(/^([01]\d|2[0-3]):[0-5]\d$/))),
3920
- label: z16.optional(z16.string().check(z16.maxLength(60)))
4152
+ string: z17.string().check(z17.maxLength(120)),
4153
+ text: z17.string().check(z17.maxLength(2e3)),
4154
+ enum: z17.string().check(z17.regex(/^[a-z0-9-]{1,48}$/)),
4155
+ multi_enum: z17.array(z17.string().check(z17.regex(/^[a-z0-9-]{1,48}$/))).check(z17.maxLength(10)),
4156
+ bool: z17.boolean(),
4157
+ date: z17.string().check(z17.regex(/^\d{4}-\d{2}-\d{2}$/)),
4158
+ time_window: z17.strictObject({
4159
+ start: z17.string().check(z17.regex(/^([01]\d|2[0-3]):[0-5]\d$/)),
4160
+ end: z17.nullable(z17.string().check(z17.regex(/^([01]\d|2[0-3]):[0-5]\d$/))),
4161
+ label: z17.optional(z17.string().check(z17.maxLength(60)))
3921
4162
  }),
3922
- phone: z16.string().check(z16.regex(/^\+[1-9]\d{6,14}$/)),
3923
- email: z16.string().check(z16.maxLength(320), z16.regex(/^[^\s@]+@[^\s@]+$/)),
3924
- address: z16.strictObject({
3925
- line1: z16.string().check(z16.maxLength(120)),
3926
- line2: z16.optional(z16.string().check(z16.maxLength(120))),
3927
- city: z16.optional(z16.string().check(z16.maxLength(120))),
3928
- region: z16.optional(z16.string().check(z16.maxLength(120))),
3929
- postal_code: z16.optional(z16.string().check(z16.maxLength(120))),
3930
- country: z16.optional(z16.string().check(z16.maxLength(120)))
4163
+ phone: z17.string().check(z17.regex(/^\+[1-9]\d{6,14}$/)),
4164
+ email: z17.string().check(z17.maxLength(320), z17.regex(/^[^\s@]+@[^\s@]+$/)),
4165
+ address: z17.strictObject({
4166
+ line1: z17.string().check(z17.maxLength(120)),
4167
+ line2: z17.optional(z17.string().check(z17.maxLength(120))),
4168
+ city: z17.optional(z17.string().check(z17.maxLength(120))),
4169
+ region: z17.optional(z17.string().check(z17.maxLength(120))),
4170
+ postal_code: z17.optional(z17.string().check(z17.maxLength(120))),
4171
+ country: z17.optional(z17.string().check(z17.maxLength(120)))
3931
4172
  }),
3932
- number: z16.number()
4173
+ number: z17.number()
3933
4174
  }))();
3934
4175
  var LEAD_FORM_ROLE_SPECS = {
3935
4176
  given_name: { primitive: "string", smsLabel: "", contactIdentity: true },
@@ -4501,7 +4742,7 @@ function GoogleAdsTracking(props) {
4501
4742
  var import_react6 = require("react");
4502
4743
 
4503
4744
  // package.json
4504
- var version = "1.18.0";
4745
+ var version = "1.19.0";
4505
4746
 
4506
4747
  // ../tracking-core/src/phone-react.tsx
4507
4748
  var import_react5 = require("react");
@@ -4612,6 +4853,7 @@ function createTracking(options) {
4612
4853
  trackingConfig
4613
4854
  } = options;
4614
4855
  registerCapability("base_tracking");
4856
+ setDefaultTrackingConfig(trackingConfig);
4615
4857
  if (trackingConfig) registerCapability("conversion_goals_auto");
4616
4858
  if (!apiKey || !endpoint) {
4617
4859
  if (apiKey || endpoint) {
@@ -4879,15 +5121,1422 @@ function useBookingSubmit(bookings, options = {}) {
4879
5121
  const status = submitting ? "submitting" : outcome?.status ?? "idle";
4880
5122
  return { status, outcome, booked, submit, updateBooking, reset };
4881
5123
  }
5124
+
5125
+ // ../tracking-core/src/video-react.tsx
5126
+ var import_react8 = require("react");
5127
+ var import_jsx_runtime5 = require("react/jsx-runtime");
5128
+ var DEFAULT_VIDEO_CHECKPOINTS = [25, 50, 75, 90, 100];
5129
+ var streamManifestUrl = (host, uid) => `https://${host}/${uid}/manifest/video.m3u8`;
5130
+ var streamPosterUrl = (host, uid, time = 4) => `https://${host}/${uid}/thumbnails/thumbnail.jpg?time=${time}s`;
5131
+ var TV_SKIN_STYLE_ATTR = "data-aranova-tv-skin";
5132
+ var TV_SKIN_CSS = `
5133
+ .tv-skin {
5134
+ container-type: inline-size;
5135
+ position: relative;
5136
+ overflow: hidden;
5137
+ border-radius: 18px;
5138
+ border: 1px solid rgba(255, 255, 255, 0.12);
5139
+ background: #0a0e1a;
5140
+ box-shadow: 0 18px 48px rgba(0, 0, 0, 0.32);
5141
+ }
5142
+ .tv-skin-desktop {
5143
+ display: none;
5144
+ aspect-ratio: 16 / 9;
5145
+ width: 100%;
5146
+ }
5147
+ .tv-skin-mobile {
5148
+ display: block;
5149
+ margin-left: auto;
5150
+ margin-right: auto;
5151
+ aspect-ratio: 9 / 16;
5152
+ width: 100%;
5153
+ max-width: 420px;
5154
+ }
5155
+ @media (min-width: 768px) {
5156
+ .tv-skin-desktop {
5157
+ display: block;
5158
+ }
5159
+ .tv-skin-mobile {
5160
+ display: none;
5161
+ }
5162
+ }
5163
+ .tv-skin media-controller {
5164
+ display: block;
5165
+ width: 100%;
5166
+ height: 100%;
5167
+ background: #000;
5168
+ --media-primary-color: #ffffff;
5169
+ --media-text-color: rgba(255, 255, 255, 0.82);
5170
+ --media-icon-color: #ffffff;
5171
+ --media-secondary-color: transparent;
5172
+ --media-control-background: transparent;
5173
+ --media-control-hover-background: transparent;
5174
+ --media-control-padding: 0;
5175
+ --media-control-height: 40px;
5176
+ --media-tooltip-display: none;
5177
+ --media-font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
5178
+ --media-font-size: 11.5px;
5179
+ --media-focus-box-shadow: 0 0 0 2px #74a0ff;
5180
+ --media-button-icon-width: 20px;
5181
+ --media-button-icon-height: 20px;
5182
+ --media-range-padding: 0;
5183
+ --media-range-bar-color: #ffffff;
5184
+ --media-range-track-background: rgba(255, 255, 255, 0.25);
5185
+ --media-range-track-border-radius: 3px;
5186
+ --media-time-range-buffered-color: transparent;
5187
+ --media-time-range-hover-display: none;
5188
+ --media-preview-time-background: transparent;
5189
+ --media-range-thumb-background: #ffffff;
5190
+ --media-range-thumb-width: 12px;
5191
+ --media-range-thumb-height: 12px;
5192
+ --media-range-thumb-opacity: 0;
5193
+ }
5194
+ .tv-skin media-controller::part(vertical-layer),
5195
+ .tv-skin media-controller::part(centered-layer) {
5196
+ background: transparent;
5197
+ }
5198
+ .tv-chrome {
5199
+ position: absolute;
5200
+ inset: auto 0 0 0;
5201
+ padding-top: 56px;
5202
+ background: linear-gradient(to top, rgba(0, 0, 0, 0.62), rgba(0, 0, 0, 0));
5203
+ transition: opacity 0.25s ease;
5204
+ }
5205
+ .tv-skin media-controller[userinactive]:not([mediapaused]) .tv-chrome {
5206
+ opacity: 0;
5207
+ pointer-events: none;
5208
+ }
5209
+ .tv-row {
5210
+ display: flex;
5211
+ align-items: center;
5212
+ gap: 2px;
5213
+ padding: 0 4px;
5214
+ }
5215
+ .tv-spacer {
5216
+ flex: 1 1 auto;
5217
+ }
5218
+ .tv-ctl,
5219
+ .tv-btn {
5220
+ display: inline-flex;
5221
+ align-items: center;
5222
+ justify-content: center;
5223
+ width: 40px;
5224
+ height: 40px;
5225
+ padding: 0;
5226
+ border: 0;
5227
+ border-radius: 0;
5228
+ background: transparent;
5229
+ box-shadow: none;
5230
+ color: rgba(255, 255, 255, 0.92);
5231
+ cursor: pointer;
5232
+ transition: color 0.15s;
5233
+ }
5234
+ .tv-ctl:hover,
5235
+ .tv-btn:hover {
5236
+ background: transparent;
5237
+ color: #ffffff;
5238
+ }
5239
+ .tv-ctl:focus-visible,
5240
+ .tv-btn:focus-visible,
5241
+ .tv-menu-item:focus-visible {
5242
+ outline: 2px solid #74a0ff;
5243
+ outline-offset: -4px;
5244
+ box-shadow: none;
5245
+ }
5246
+ .tv-time {
5247
+ width: auto;
5248
+ padding: 0 4px 0 6px;
5249
+ background: transparent;
5250
+ letter-spacing: 0.02em;
5251
+ white-space: nowrap;
5252
+ --media-control-background: transparent;
5253
+ }
5254
+ .tv-time-cur {
5255
+ display: none;
5256
+ }
5257
+ .tv-seek {
5258
+ display: block;
5259
+ width: 100%;
5260
+ height: 16px;
5261
+ margin: 0;
5262
+ padding: 0;
5263
+ background: transparent;
5264
+ box-shadow: none;
5265
+ border: 0;
5266
+ --media-range-track-height: 3px;
5267
+ --media-control-background: transparent;
5268
+ --media-control-hover-background: transparent;
5269
+ }
5270
+ .tv-seek:hover,
5271
+ .tv-seek:focus-visible {
5272
+ --media-range-track-height: 6px;
5273
+ --media-range-thumb-opacity: 1;
5274
+ }
5275
+ .tv-vol {
5276
+ display: flex;
5277
+ align-items: center;
5278
+ }
5279
+ .tv-volrange {
5280
+ width: 64px;
5281
+ height: 40px;
5282
+ margin-right: 4px;
5283
+ padding: 0;
5284
+ opacity: 0;
5285
+ pointer-events: none;
5286
+ transform: scaleX(0.75);
5287
+ transform-origin: right center;
5288
+ background: transparent;
5289
+ transition: transform 0.2s ease, opacity 0.2s ease;
5290
+ --media-control-background: transparent;
5291
+ --media-range-track-height: 3px;
5292
+ }
5293
+ .tv-vol:hover .tv-volrange,
5294
+ .tv-vol:focus-within .tv-volrange {
5295
+ opacity: 1;
5296
+ pointer-events: auto;
5297
+ transform: scaleX(1);
5298
+ }
5299
+ @media (hover: none) {
5300
+ .tv-volrange {
5301
+ display: none;
5302
+ }
5303
+ }
5304
+ @container (max-width: 479px) {
5305
+ .tv-vol,
5306
+ .tv-time-full {
5307
+ display: none;
5308
+ }
5309
+ .tv-time-cur {
5310
+ display: inline-flex;
5311
+ }
5312
+ }
5313
+ @container (max-width: 767px) {
5314
+ .tv-theater {
5315
+ display: none;
5316
+ }
5317
+ }
5318
+ .tv-menu-wrap {
5319
+ position: relative;
5320
+ display: flex;
5321
+ align-items: center;
5322
+ }
5323
+ .tv-menu {
5324
+ position: absolute;
5325
+ right: 0;
5326
+ bottom: calc(100% + 8px);
5327
+ z-index: 5;
5328
+ width: 136px;
5329
+ max-height: 232px;
5330
+ margin: 0;
5331
+ padding: 4px 0;
5332
+ overflow-y: auto;
5333
+ list-style: none;
5334
+ border: 1px solid rgba(255, 255, 255, 0.2);
5335
+ border-radius: 10px;
5336
+ background: linear-gradient(rgba(255, 255, 255, 0.12), rgba(255, 255, 255, 0.12)), rgba(10, 14, 26, 0.55);
5337
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.12), 0 12px 32px rgba(0, 0, 0, 0.35);
5338
+ }
5339
+ @supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
5340
+ .tv-menu {
5341
+ -webkit-backdrop-filter: blur(16px) saturate(140%);
5342
+ backdrop-filter: blur(16px) saturate(140%);
5343
+ }
5344
+ }
5345
+ .tv-menu-item {
5346
+ position: relative;
5347
+ display: flex;
5348
+ width: 100%;
5349
+ height: 32px;
5350
+ align-items: center;
5351
+ padding: 0 12px 0 24px;
5352
+ border: 0;
5353
+ background: transparent;
5354
+ color: rgba(255, 255, 255, 0.75);
5355
+ font-family: var(--font-sans, system-ui, -apple-system, sans-serif);
5356
+ font-size: 13px;
5357
+ font-weight: 500;
5358
+ text-align: left;
5359
+ cursor: pointer;
5360
+ }
5361
+ .tv-menu-item:hover,
5362
+ .tv-menu-item:focus-visible {
5363
+ background: rgba(255, 255, 255, 0.1);
5364
+ color: #ffffff;
5365
+ outline: none;
5366
+ }
5367
+ .tv-menu-item[aria-checked='true'] {
5368
+ color: #ffffff;
5369
+ font-weight: 600;
5370
+ }
5371
+ .tv-menu-item[aria-checked='true']::before {
5372
+ content: '';
5373
+ position: absolute;
5374
+ left: 12px;
5375
+ width: 5px;
5376
+ height: 5px;
5377
+ border-radius: 50%;
5378
+ background: #ffffff;
5379
+ }
5380
+ .tv-menu-sub {
5381
+ margin-left: 4px;
5382
+ font-size: 11.5px;
5383
+ font-weight: 400;
5384
+ color: rgba(255, 255, 255, 0.55);
5385
+ }
5386
+ .tv-menu-note {
5387
+ height: 20px;
5388
+ padding: 0 12px 0 24px;
5389
+ color: rgba(255, 255, 255, 0.55);
5390
+ font-family: var(--font-sans, system-ui, -apple-system, sans-serif);
5391
+ font-size: 11.5px;
5392
+ line-height: 20px;
5393
+ }
5394
+ .tv-poster-overlay {
5395
+ position: absolute;
5396
+ inset: 0;
5397
+ }
5398
+ .tv-poster-img {
5399
+ width: 100%;
5400
+ height: 100%;
5401
+ object-fit: cover;
5402
+ display: block;
5403
+ }
5404
+ .tv-play-trigger {
5405
+ position: absolute;
5406
+ inset: 0;
5407
+ width: 100%;
5408
+ height: 100%;
5409
+ border: 0;
5410
+ padding: 0;
5411
+ background: transparent;
5412
+ cursor: pointer;
5413
+ display: flex;
5414
+ align-items: center;
5415
+ justify-content: center;
5416
+ transition: filter 0.2s ease;
5417
+ }
5418
+ .tv-play-trigger:hover {
5419
+ filter: brightness(1.1);
5420
+ }
5421
+ .tv-play-trigger:focus-visible {
5422
+ outline: 2px solid #74a0ff;
5423
+ outline-offset: -4px;
5424
+ }
5425
+ .tv-play-pill {
5426
+ display: flex;
5427
+ width: 64px;
5428
+ height: 64px;
5429
+ align-items: center;
5430
+ justify-content: center;
5431
+ border-radius: 9999px;
5432
+ border: 2px solid rgba(255, 255, 255, 0.72);
5433
+ background: rgba(255, 255, 255, 0.15);
5434
+ color: #ffffff;
5435
+ backdrop-filter: blur(2px);
5436
+ -webkit-backdrop-filter: blur(2px);
5437
+ transition: transform 0.3s ease;
5438
+ }
5439
+ .tv-play-trigger:hover .tv-play-pill {
5440
+ transform: scale(1.05);
5441
+ }
5442
+ `;
5443
+ function ensureTvSkinStyles() {
5444
+ if (typeof document === "undefined") return;
5445
+ if (document.querySelector(`style[${TV_SKIN_STYLE_ATTR}]`)) return;
5446
+ const style = document.createElement("style");
5447
+ style.setAttribute(TV_SKIN_STYLE_ATTR, "1");
5448
+ style.textContent = TV_SKIN_CSS;
5449
+ document.head.append(style);
5450
+ }
5451
+ function IconPlay({
5452
+ slot,
5453
+ size = 20,
5454
+ filled = false
5455
+ }) {
5456
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
5457
+ "svg",
5458
+ {
5459
+ slot,
5460
+ width: size,
5461
+ height: size,
5462
+ viewBox: "0 0 24 24",
5463
+ fill: filled ? "currentColor" : "none",
5464
+ stroke: "currentColor",
5465
+ strokeWidth: filled ? 0 : 1.75,
5466
+ strokeLinecap: "round",
5467
+ strokeLinejoin: "round",
5468
+ "aria-hidden": "true",
5469
+ style: filled ? { marginLeft: 3 } : void 0,
5470
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("polygon", { points: "6 3 20 12 6 21 6 3" })
5471
+ }
5472
+ );
5473
+ }
5474
+ function IconPause({ slot }) {
5475
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
5476
+ "svg",
5477
+ {
5478
+ slot,
5479
+ width: 20,
5480
+ height: 20,
5481
+ viewBox: "0 0 24 24",
5482
+ fill: "none",
5483
+ stroke: "currentColor",
5484
+ strokeWidth: 1.75,
5485
+ strokeLinecap: "round",
5486
+ strokeLinejoin: "round",
5487
+ "aria-hidden": "true",
5488
+ children: [
5489
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("rect", { x: "14", y: "4", width: "4", height: "16", rx: "1" }),
5490
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("rect", { x: "6", y: "4", width: "4", height: "16", rx: "1" })
5491
+ ]
5492
+ }
5493
+ );
5494
+ }
5495
+ function IconVolumeHigh({ slot }) {
5496
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
5497
+ "svg",
5498
+ {
5499
+ slot,
5500
+ width: 20,
5501
+ height: 20,
5502
+ viewBox: "0 0 24 24",
5503
+ fill: "none",
5504
+ stroke: "currentColor",
5505
+ strokeWidth: 1.75,
5506
+ strokeLinecap: "round",
5507
+ strokeLinejoin: "round",
5508
+ "aria-hidden": "true",
5509
+ children: [
5510
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("polygon", { points: "11 5 6 9 2 9 2 15 6 15 11 19 11 5" }),
5511
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M15.54 8.46a5 5 0 0 1 0 7.07" }),
5512
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M19.07 4.93a10 10 0 0 1 0 14.14" })
5513
+ ]
5514
+ }
5515
+ );
5516
+ }
5517
+ function IconVolumeLow({ slot }) {
5518
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
5519
+ "svg",
5520
+ {
5521
+ slot,
5522
+ width: 20,
5523
+ height: 20,
5524
+ viewBox: "0 0 24 24",
5525
+ fill: "none",
5526
+ stroke: "currentColor",
5527
+ strokeWidth: 1.75,
5528
+ strokeLinecap: "round",
5529
+ strokeLinejoin: "round",
5530
+ "aria-hidden": "true",
5531
+ children: [
5532
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("polygon", { points: "11 5 6 9 2 9 2 15 6 15 11 19 11 5" }),
5533
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M15.54 8.46a5 5 0 0 1 0 7.07" })
5534
+ ]
5535
+ }
5536
+ );
5537
+ }
5538
+ function IconVolumeOff({ slot }) {
5539
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
5540
+ "svg",
5541
+ {
5542
+ slot,
5543
+ width: 20,
5544
+ height: 20,
5545
+ viewBox: "0 0 24 24",
5546
+ fill: "none",
5547
+ stroke: "currentColor",
5548
+ strokeWidth: 1.75,
5549
+ strokeLinecap: "round",
5550
+ strokeLinejoin: "round",
5551
+ "aria-hidden": "true",
5552
+ children: [
5553
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("polygon", { points: "11 5 6 9 2 9 2 15 6 15 11 19 11 5" }),
5554
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("line", { x1: "22", y1: "9", x2: "16", y2: "15" }),
5555
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("line", { x1: "16", y1: "9", x2: "22", y2: "15" })
5556
+ ]
5557
+ }
5558
+ );
5559
+ }
5560
+ function IconSettings() {
5561
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
5562
+ "svg",
5563
+ {
5564
+ width: 20,
5565
+ height: 20,
5566
+ viewBox: "0 0 24 24",
5567
+ fill: "none",
5568
+ stroke: "currentColor",
5569
+ strokeWidth: 1.75,
5570
+ strokeLinecap: "round",
5571
+ strokeLinejoin: "round",
5572
+ "aria-hidden": "true",
5573
+ children: [
5574
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z" }),
5575
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("circle", { cx: "12", cy: "12", r: "3" })
5576
+ ]
5577
+ }
5578
+ );
5579
+ }
5580
+ function IconTheaterExpand() {
5581
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
5582
+ "svg",
5583
+ {
5584
+ width: 20,
5585
+ height: 20,
5586
+ viewBox: "0 0 24 24",
5587
+ fill: "none",
5588
+ stroke: "currentColor",
5589
+ strokeWidth: 1.75,
5590
+ strokeLinecap: "round",
5591
+ strokeLinejoin: "round",
5592
+ "aria-hidden": "true",
5593
+ children: [
5594
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("polyline", { points: "15 3 21 3 21 9" }),
5595
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("polyline", { points: "9 21 3 21 3 15" }),
5596
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("line", { x1: "21", y1: "3", x2: "14", y2: "10" }),
5597
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("line", { x1: "3", y1: "21", x2: "10", y2: "14" })
5598
+ ]
5599
+ }
5600
+ );
5601
+ }
5602
+ function IconTheaterShrink() {
5603
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
5604
+ "svg",
5605
+ {
5606
+ width: 20,
5607
+ height: 20,
5608
+ viewBox: "0 0 24 24",
5609
+ fill: "none",
5610
+ stroke: "currentColor",
5611
+ strokeWidth: 1.75,
5612
+ strokeLinecap: "round",
5613
+ strokeLinejoin: "round",
5614
+ "aria-hidden": "true",
5615
+ children: [
5616
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("polyline", { points: "4 14 10 14 10 20" }),
5617
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("polyline", { points: "20 10 14 10 14 4" }),
5618
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("line", { x1: "14", y1: "10", x2: "21", y2: "3" }),
5619
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("line", { x1: "3", y1: "21", x2: "10", y2: "14" })
5620
+ ]
5621
+ }
5622
+ );
5623
+ }
5624
+ function IconFullscreenEnter({ slot }) {
5625
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
5626
+ "svg",
5627
+ {
5628
+ slot,
5629
+ width: 20,
5630
+ height: 20,
5631
+ viewBox: "0 0 24 24",
5632
+ fill: "none",
5633
+ stroke: "currentColor",
5634
+ strokeWidth: 1.75,
5635
+ strokeLinecap: "round",
5636
+ strokeLinejoin: "round",
5637
+ "aria-hidden": "true",
5638
+ children: [
5639
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M8 3H5a2 2 0 0 0-2 2v3" }),
5640
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M21 8V5a2 2 0 0 0-2-2h-3" }),
5641
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M3 16v3a2 2 0 0 0 2 2h3" }),
5642
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M16 21h3a2 2 0 0 0 2-2v-3" })
5643
+ ]
5644
+ }
5645
+ );
5646
+ }
5647
+ function IconFullscreenExit({ slot }) {
5648
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
5649
+ "svg",
5650
+ {
5651
+ slot,
5652
+ width: 20,
5653
+ height: 20,
5654
+ viewBox: "0 0 24 24",
5655
+ fill: "none",
5656
+ stroke: "currentColor",
5657
+ strokeWidth: 1.75,
5658
+ strokeLinecap: "round",
5659
+ strokeLinejoin: "round",
5660
+ "aria-hidden": "true",
5661
+ children: [
5662
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M8 3v3a2 2 0 0 1-2 2H3" }),
5663
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M21 8h-3a2 2 0 0 1-2-2V3" }),
5664
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M3 16h3a2 2 0 0 1 2 2v3" }),
5665
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M16 21v-3a2 2 0 0 1 2-2h3" })
5666
+ ]
5667
+ }
5668
+ );
5669
+ }
5670
+ var EMPTY_GOALS2 = [];
5671
+ function serverVideoSnapshot() {
5672
+ return null;
5673
+ }
5674
+ function serverGoalsSnapshot() {
5675
+ return EMPTY_GOALS2;
5676
+ }
5677
+ function useTrackedVideoConfig(videoKey, trackingConfig, fallbackConfig) {
5678
+ registerCapability("video_tracking");
5679
+ const effectiveRef = trackingConfig ?? getActiveTrackingEnv().trackingConfig;
5680
+ const subscribe = (0, import_react8.useCallback)(
5681
+ (listener) => effectiveRef ? getTrackingConfigRuntime(effectiveRef).subscribe(listener) : () => {
5682
+ },
5683
+ [effectiveRef]
5684
+ );
5685
+ const live = (0, import_react8.useSyncExternalStore)(
5686
+ subscribe,
5687
+ () => effectiveRef && videoKey ? getTrackingConfigRuntime(effectiveRef).getVideo(videoKey) : null,
5688
+ serverVideoSnapshot
5689
+ );
5690
+ return live ?? fallbackConfig ?? null;
5691
+ }
5692
+ function useActiveVideoGoals(videoId, trackingConfig) {
5693
+ const effectiveRef = trackingConfig ?? getActiveTrackingEnv().trackingConfig;
5694
+ const subscribe = (0, import_react8.useCallback)(
5695
+ (listener) => effectiveRef ? getTrackingConfigRuntime(effectiveRef).subscribe(listener) : () => {
5696
+ },
5697
+ [effectiveRef]
5698
+ );
5699
+ const goals = (0, import_react8.useSyncExternalStore)(
5700
+ subscribe,
5701
+ () => effectiveRef ? getTrackingConfigRuntime(effectiveRef).listGoals() : EMPTY_GOALS2,
5702
+ serverGoalsSnapshot
5703
+ );
5704
+ return (0, import_react8.useMemo)(() => {
5705
+ const out = [];
5706
+ for (const goal of goals) {
5707
+ if (goal.kind !== "event" || !goal.trigger) continue;
5708
+ if (goal.trigger.event_type !== "video_progress") continue;
5709
+ if (goal.trigger.video_id && goal.trigger.video_id !== videoId) continue;
5710
+ const metric = goal.trigger.metric;
5711
+ const threshold = goal.trigger.threshold;
5712
+ if ((metric === "percent" || metric === "seconds") && typeof threshold === "number") {
5713
+ out.push({ key: goal.key, metric, threshold });
5714
+ }
5715
+ }
5716
+ return out;
5717
+ }, [goals, videoId]);
5718
+ }
5719
+ function renditionsOf(h) {
5720
+ const best = /* @__PURE__ */ new Map();
5721
+ h.levels.forEach((l, index) => {
5722
+ const px = Math.min(l.width || Infinity, l.height || Infinity);
5723
+ if (!Number.isFinite(px)) return;
5724
+ const cur = best.get(px);
5725
+ if (!cur || l.bitrate > cur.bitrate) best.set(px, { height: px, index, bitrate: l.bitrate });
5726
+ });
5727
+ return [...best.values()].sort((a, b) => b.height - a.height).map(({ height, index }) => ({ height, index }));
5728
+ }
5729
+ var MEDIA_CHROME_CDN_URL = "https://cdn.jsdelivr.net/npm/media-chrome@4/+esm";
5730
+ var HLS_JS_CDN_URL = "https://cdn.jsdelivr.net/npm/hls.js@1/dist/hls.min.js";
5731
+ var defaultLibsPromise = null;
5732
+ function loadScriptOnce(src, attr, isModule = false) {
5733
+ if (typeof document === "undefined") return Promise.resolve();
5734
+ const existing = document.querySelector(`script[${attr}]`);
5735
+ if (existing) return Promise.resolve();
5736
+ return new Promise((resolve) => {
5737
+ const s = document.createElement("script");
5738
+ if (isModule) s.type = "module";
5739
+ s.async = true;
5740
+ s.src = src;
5741
+ s.setAttribute(attr, "1");
5742
+ s.addEventListener("load", () => resolve(), { once: true });
5743
+ s.addEventListener("error", () => resolve(), { once: true });
5744
+ document.head.append(s);
5745
+ if (typeof navigator !== "undefined" && /jsdom/i.test(navigator.userAgent)) {
5746
+ setTimeout(resolve, 0);
5747
+ }
5748
+ });
5749
+ }
5750
+ function loadDefaultPlayerLibs() {
5751
+ if (typeof window === "undefined") return Promise.resolve({});
5752
+ if (defaultLibsPromise) return defaultLibsPromise;
5753
+ defaultLibsPromise = Promise.all([
5754
+ typeof customElements !== "undefined" && customElements.get("media-controller") ? Promise.resolve() : loadScriptOnce(MEDIA_CHROME_CDN_URL, "data-aranova-media-chrome", true),
5755
+ window.Hls ? Promise.resolve() : loadScriptOnce(HLS_JS_CDN_URL, "data-aranova-hls-js", false)
5756
+ ]).then(() => ({
5757
+ hls: window.Hls
5758
+ }));
5759
+ return defaultLibsPromise;
5760
+ }
5761
+ function Cut({
5762
+ id,
5763
+ variant,
5764
+ uid,
5765
+ host,
5766
+ title,
5767
+ posterTime,
5768
+ posterUrl,
5769
+ renderPoster,
5770
+ theater,
5771
+ onTheaterChange,
5772
+ autoExpandOnPlay,
5773
+ checkpoints,
5774
+ videoGoals,
5775
+ client,
5776
+ loadPlayerLibs,
5777
+ className,
5778
+ ...cb
5779
+ }) {
5780
+ const root = (0, import_react8.useRef)(null);
5781
+ const video = (0, import_react8.useRef)(null);
5782
+ const hls = (0, import_react8.useRef)(null);
5783
+ const lastTime = (0, import_react8.useRef)(0);
5784
+ const seekFrom = (0, import_react8.useRef)(0);
5785
+ const cbRef = (0, import_react8.useRef)(cb);
5786
+ cbRef.current = cb;
5787
+ const clientRef = (0, import_react8.useRef)(client);
5788
+ clientRef.current = client;
5789
+ const goalsRef = (0, import_react8.useRef)(videoGoals);
5790
+ goalsRef.current = videoGoals;
5791
+ const rangesRef = (0, import_react8.useRef)([]);
5792
+ const activeSegmentStartRef = (0, import_react8.useRef)(null);
5793
+ const furthestSecondsRef = (0, import_react8.useRef)(0);
5794
+ const maxPercentRef = (0, import_react8.useRef)(0);
5795
+ const durationRef = (0, import_react8.useRef)(0);
5796
+ const playCountRef = (0, import_react8.useRef)(0);
5797
+ const seekCountRef = (0, import_react8.useRef)(0);
5798
+ const firedCheckpointsRef = (0, import_react8.useRef)(/* @__PURE__ */ new Set());
5799
+ const firedGoalsRef = (0, import_react8.useRef)(/* @__PURE__ */ new Set());
5800
+ const dirtySinceLastEmitRef = (0, import_react8.useRef)(false);
5801
+ const [started, setStarted] = (0, import_react8.useState)(false);
5802
+ const [ready, setReady] = (0, import_react8.useState)(false);
5803
+ const [playing, setPlaying] = (0, import_react8.useState)(false);
5804
+ const [renditions, setRenditions] = (0, import_react8.useState)([]);
5805
+ const [selected, setSelected] = (0, import_react8.useState)("auto");
5806
+ const [live, setLive] = (0, import_react8.useState)(null);
5807
+ const [menu, setMenu] = (0, import_react8.useState)(false);
5808
+ const [pending, setPending] = (0, import_react8.useState)(null);
5809
+ const pendingRef = (0, import_react8.useRef)(null);
5810
+ const wrap = (0, import_react8.useRef)(null);
5811
+ const gear = (0, import_react8.useRef)(null);
5812
+ const selectedRef = (0, import_react8.useRef)("auto");
5813
+ const pendingTimer = (0, import_react8.useRef)(null);
5814
+ const liveRef = (0, import_react8.useRef)(null);
5815
+ const videoId = `${id}-${variant}`;
5816
+ const getCurrentRanges = (0, import_react8.useCallback)(() => {
5817
+ const v = video.current;
5818
+ const cur = v ? v.currentTime : lastTime.current;
5819
+ const list = [...rangesRef.current];
5820
+ if (activeSegmentStartRef.current !== null && cur > activeSegmentStartRef.current) {
5821
+ list.push({
5822
+ start_s: activeSegmentStartRef.current,
5823
+ end_s: cur
5824
+ });
5825
+ }
5826
+ return mergeVideoRanges(list);
5827
+ }, []);
5828
+ const commitActiveSegment = (0, import_react8.useCallback)((endTime) => {
5829
+ const end = endTime ?? video.current?.currentTime ?? lastTime.current;
5830
+ if (activeSegmentStartRef.current !== null && end > activeSegmentStartRef.current) {
5831
+ rangesRef.current = mergeVideoRanges([
5832
+ ...rangesRef.current,
5833
+ { start_s: activeSegmentStartRef.current, end_s: end }
5834
+ ]);
5835
+ }
5836
+ activeSegmentStartRef.current = null;
5837
+ }, []);
5838
+ const snap = (0, import_react8.useCallback)(() => {
5839
+ const v = video.current;
5840
+ const currentTime = roundVideoSeconds(v?.currentTime ?? lastTime.current);
5841
+ const rawDuration = v?.duration;
5842
+ const duration = typeof rawDuration === "number" && Number.isFinite(rawDuration) && rawDuration > 0 ? roundVideoSeconds(rawDuration) : durationRef.current;
5843
+ if (duration > 0) durationRef.current = duration;
5844
+ if (currentTime > furthestSecondsRef.current) {
5845
+ furthestSecondsRef.current = currentTime;
5846
+ }
5847
+ const ranges = getCurrentRanges();
5848
+ const watchedSeconds = sumVideoRangesSeconds(ranges, duration);
5849
+ const watchedPercent = duration > 0 ? Math.min(100, Math.round(watchedSeconds / duration * 100)) : 0;
5850
+ const curPercent = duration > 0 ? Math.min(100, Math.round(furthestSecondsRef.current / duration * 100)) : 0;
5851
+ if (curPercent > maxPercentRef.current) {
5852
+ maxPercentRef.current = curPercent;
5853
+ }
5854
+ return {
5855
+ id,
5856
+ videoId,
5857
+ variant,
5858
+ currentTime,
5859
+ duration,
5860
+ watchedSeconds,
5861
+ watchedPercent,
5862
+ maxPercent: maxPercentRef.current,
5863
+ ranges
5864
+ };
5865
+ }, [getCurrentRanges, id, variant, videoId]);
5866
+ const emitProgress = (0, import_react8.useCallback)(
5867
+ (trigger, snapshotOverride) => {
5868
+ const activeClient = clientRef.current ?? getActiveTrackingEnv().rawClient;
5869
+ if (!activeClient || playCountRef.current <= 0) return;
5870
+ const s = snapshotOverride ?? snap();
5871
+ if (s.ranges.length === 0 && s.currentTime <= 0 && trigger !== "ended") return;
5872
+ dirtySinceLastEmitRef.current = false;
5873
+ const path = typeof window === "undefined" ? "/" : window.location.pathname;
5874
+ activeClient.trackEvent({
5875
+ eventType: "video_progress",
5876
+ metadata: {
5877
+ video_id: id,
5878
+ variant,
5879
+ trigger,
5880
+ current_s: s.currentTime,
5881
+ furthest_s: roundVideoSeconds(furthestSecondsRef.current),
5882
+ watched_s: s.watchedSeconds,
5883
+ duration_s: s.duration,
5884
+ max_percent: s.maxPercent,
5885
+ watched_percent: s.watchedPercent,
5886
+ play_count: playCountRef.current,
5887
+ seek_count: seekCountRef.current,
5888
+ ranges: s.ranges,
5889
+ page: { path }
5890
+ }
5891
+ });
5892
+ },
5893
+ [id, snap, variant]
5894
+ );
5895
+ const clearPending = () => {
5896
+ if (pendingTimer.current) clearTimeout(pendingTimer.current);
5897
+ pendingTimer.current = null;
5898
+ pendingRef.current = null;
5899
+ setPending(null);
5900
+ };
5901
+ (0, import_react8.useEffect)(() => {
5902
+ ensureTvSkinStyles();
5903
+ registerCapability("video_tracking");
5904
+ return () => {
5905
+ if (pendingTimer.current) clearTimeout(pendingTimer.current);
5906
+ hls.current?.destroy();
5907
+ hls.current = null;
5908
+ };
5909
+ }, []);
5910
+ (0, import_react8.useEffect)(() => {
5911
+ if (!started || typeof window === "undefined") return;
5912
+ const flushExitProgress = (useBeacon) => {
5913
+ if (playCountRef.current <= 0 || !dirtySinceLastEmitRef.current) return;
5914
+ commitActiveSegment();
5915
+ emitProgress("page_exit");
5916
+ if (useBeacon) {
5917
+ const activeClient2 = clientRef.current ?? getActiveTrackingEnv().rawClient;
5918
+ activeClient2?.flushBeacon();
5919
+ }
5920
+ };
5921
+ const activeClient = clientRef.current ?? getActiveTrackingEnv().rawClient;
5922
+ const unsubscribeBeforeUnload = activeClient?.onBeforeUnload?.(() => {
5923
+ flushExitProgress(false);
5924
+ });
5925
+ const onVisibilityChange = () => {
5926
+ if (document.visibilityState === "hidden") {
5927
+ flushExitProgress(true);
5928
+ }
5929
+ };
5930
+ const onPageHide = () => {
5931
+ flushExitProgress(true);
5932
+ };
5933
+ document.addEventListener("visibilitychange", onVisibilityChange);
5934
+ window.addEventListener("pagehide", onPageHide);
5935
+ return () => {
5936
+ unsubscribeBeforeUnload?.();
5937
+ document.removeEventListener("visibilitychange", onVisibilityChange);
5938
+ window.removeEventListener("pagehide", onPageHide);
5939
+ };
5940
+ }, [started, commitActiveSegment, emitProgress]);
5941
+ (0, import_react8.useEffect)(() => {
5942
+ if (!started) return;
5943
+ let was = false;
5944
+ const report = (fullscreen) => {
5945
+ if (fullscreen === was) return;
5946
+ was = fullscreen;
5947
+ cbRef.current.onFullscreenChange?.({ ...snap(), fullscreen });
5948
+ };
5949
+ const onDoc = () => {
5950
+ const el = root.current;
5951
+ const fe = document.fullscreenElement ?? document.webkitFullscreenElement;
5952
+ report(Boolean(el && fe && el.contains(fe)));
5953
+ };
5954
+ const v = video.current;
5955
+ const begin = () => report(true);
5956
+ const end = () => report(false);
5957
+ document.addEventListener("fullscreenchange", onDoc);
5958
+ document.addEventListener("webkitfullscreenchange", onDoc);
5959
+ v?.addEventListener("webkitbeginfullscreen", begin);
5960
+ v?.addEventListener("webkitendfullscreen", end);
5961
+ return () => {
5962
+ document.removeEventListener("fullscreenchange", onDoc);
5963
+ document.removeEventListener("webkitfullscreenchange", onDoc);
5964
+ v?.removeEventListener("webkitbeginfullscreen", begin);
5965
+ v?.removeEventListener("webkitendfullscreen", end);
5966
+ };
5967
+ }, [started, snap]);
5968
+ (0, import_react8.useEffect)(() => {
5969
+ if (!menu) return;
5970
+ const outside = (e) => {
5971
+ if (wrap.current && !wrap.current.contains(e.target)) setMenu(false);
5972
+ };
5973
+ document.addEventListener("pointerdown", outside);
5974
+ return () => document.removeEventListener("pointerdown", outside);
5975
+ }, [menu]);
5976
+ const closeMenu = (refocus) => {
5977
+ setMenu(false);
5978
+ if (refocus) gear.current?.focus();
5979
+ };
5980
+ const onMenuKey = (e) => {
5981
+ if (e.key === "Escape") {
5982
+ e.preventDefault();
5983
+ e.stopPropagation();
5984
+ closeMenu(true);
5985
+ } else if (e.key === "ArrowDown" || e.key === "ArrowUp") {
5986
+ e.preventDefault();
5987
+ const items = [
5988
+ ...wrap.current?.querySelectorAll('[role="menuitemradio"]') ?? []
5989
+ ];
5990
+ const i = items.indexOf(document.activeElement);
5991
+ items[(i + (e.key === "ArrowDown" ? 1 : items.length - 1)) % items.length]?.focus();
5992
+ }
5993
+ };
5994
+ const emitQuality = (from, to, source) => cbRef.current.onQualityChange?.({ ...snap(), from, to, source });
5995
+ const start = async () => {
5996
+ const v = video.current;
5997
+ if (!v || started) return;
5998
+ setStarted(true);
5999
+ if (variant === "16x9" && autoExpandOnPlay) {
6000
+ onTheaterChange?.(true);
6001
+ }
6002
+ const src = streamManifestUrl(host, uid);
6003
+ try {
6004
+ const loaded = await (loadPlayerLibs ? loadPlayerLibs() : loadDefaultPlayerLibs());
6005
+ setReady(true);
6006
+ const rawHls = (loaded && typeof loaded === "object" && "hls" in loaded ? loaded.hls : null) ?? (typeof window !== "undefined" ? window.Hls : void 0);
6007
+ const HlsLib = rawHls && typeof rawHls === "object" && "default" in rawHls ? rawHls.default : rawHls;
6008
+ if (!HlsLib || !HlsLib.isSupported()) {
6009
+ v.src = src;
6010
+ } else {
6011
+ const h = new HlsLib({ capLevelToPlayerSize: true });
6012
+ hls.current = h;
6013
+ h.on(HlsLib.Events.MANIFEST_PARSED, () => setRenditions(renditionsOf(h)));
6014
+ h.on(HlsLib.Events.LEVEL_SWITCHED, (_e, data) => {
6015
+ const lv = h.levels[data.level];
6016
+ const height = lv ? Math.min(lv.width || Infinity, lv.height || Infinity) : null;
6017
+ const from = liveRef.current;
6018
+ liveRef.current = height;
6019
+ setLive(height);
6020
+ const want = pendingRef.current;
6021
+ if (want !== null && want === height) clearPending();
6022
+ if (selectedRef.current === "auto" && height && from && from !== height) {
6023
+ emitQuality(from, height, "auto");
6024
+ }
6025
+ });
6026
+ h.on(HlsLib.Events.ERROR, () => clearPending());
6027
+ h.loadSource(src);
6028
+ h.attachMedia(v);
6029
+ }
6030
+ } catch {
6031
+ setReady(true);
6032
+ v.src = src;
6033
+ }
6034
+ try {
6035
+ await v.play();
6036
+ } catch {
6037
+ }
6038
+ };
6039
+ const pick = (q) => {
6040
+ const h = hls.current;
6041
+ const from = selectedRef.current;
6042
+ setMenu(false);
6043
+ if (!h || q === from) return;
6044
+ if (q === "auto") h.nextLevel = -1;
6045
+ else {
6046
+ const r = renditions.find((x) => x.height === q);
6047
+ if (r) h.nextLevel = r.index;
6048
+ }
6049
+ selectedRef.current = q;
6050
+ setSelected(q);
6051
+ clearPending();
6052
+ if (typeof q === "number" && q !== liveRef.current) {
6053
+ pendingRef.current = q;
6054
+ setPending(q);
6055
+ pendingTimer.current = setTimeout(clearPending, 4e3);
6056
+ }
6057
+ emitQuality(from, q, "manual");
6058
+ };
6059
+ const qualityLabel = selected === "auto" ? live ? `Auto (${live}p)` : "Auto" : `${selected}p`;
6060
+ const resolvedPosterUrl = posterUrl ?? streamPosterUrl(host, uid, posterTime);
6061
+ const posterWidth = variant === "16x9" ? 1280 : 720;
6062
+ const posterHeight = variant === "16x9" ? 720 : 1280;
6063
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
6064
+ "div",
6065
+ {
6066
+ ref: root,
6067
+ className: `tv-skin ${variant === "16x9" ? "tv-skin-desktop" : "tv-skin-mobile"} ${className ?? ""}`,
6068
+ "data-video-id": videoId,
6069
+ children: [
6070
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("media-controller", { class: "absolute inset-0 block", autohide: menu ? "-1" : "2", children: [
6071
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
6072
+ "video",
6073
+ {
6074
+ ref: video,
6075
+ slot: "media",
6076
+ style: { width: "100%", height: "100%", backgroundColor: "#000", objectFit: "contain" },
6077
+ preload: "none",
6078
+ playsInline: true,
6079
+ title,
6080
+ "aria-label": title,
6081
+ "data-video-id": videoId,
6082
+ onPlay: () => {
6083
+ const v = video.current;
6084
+ const cur = roundVideoSeconds(v?.currentTime ?? 0);
6085
+ activeSegmentStartRef.current = cur;
6086
+ playCountRef.current += 1;
6087
+ dirtySinceLastEmitRef.current = true;
6088
+ const s = snap();
6089
+ const activeClient = clientRef.current ?? getActiveTrackingEnv().rawClient;
6090
+ if (activeClient) {
6091
+ const path = typeof window === "undefined" ? "/" : window.location.pathname;
6092
+ activeClient.trackEvent({
6093
+ eventType: "video_play",
6094
+ metadata: {
6095
+ video_id: id,
6096
+ variant,
6097
+ current_s: s.currentTime,
6098
+ duration_s: s.duration,
6099
+ play_sequence: playCountRef.current,
6100
+ page: { path }
6101
+ }
6102
+ });
6103
+ }
6104
+ cbRef.current.onPlay?.(s);
6105
+ },
6106
+ onPlaying: () => setPlaying(true),
6107
+ onPause: () => {
6108
+ const v = video.current;
6109
+ if (v?.ended) return;
6110
+ commitActiveSegment(v?.currentTime);
6111
+ const s = snap();
6112
+ emitProgress("pause", s);
6113
+ cbRef.current.onPause?.(s);
6114
+ },
6115
+ onEnded: () => {
6116
+ const v = video.current;
6117
+ const dur = typeof v?.duration === "number" && Number.isFinite(v.duration) ? v.duration : durationRef.current;
6118
+ commitActiveSegment(dur > 0 ? dur : v?.currentTime);
6119
+ maxPercentRef.current = 100;
6120
+ const s = snap();
6121
+ if (!firedCheckpointsRef.current.has(100) && checkpoints.includes(100)) {
6122
+ firedCheckpointsRef.current.add(100);
6123
+ cbRef.current.onCheckpoint?.({ ...s, checkpoint: 100 });
6124
+ }
6125
+ emitProgress("ended", s);
6126
+ cbRef.current.onEnded?.(s);
6127
+ },
6128
+ onWaiting: () => cbRef.current.onWaiting?.(snap()),
6129
+ onTimeUpdate: () => {
6130
+ const v = video.current;
6131
+ if (v && !v.seeking) {
6132
+ if (activeSegmentStartRef.current === null && !v.paused) {
6133
+ activeSegmentStartRef.current = lastTime.current;
6134
+ }
6135
+ lastTime.current = v.currentTime;
6136
+ dirtySinceLastEmitRef.current = true;
6137
+ }
6138
+ const s = snap();
6139
+ let hitCheckpoint = false;
6140
+ let hitGoal = false;
6141
+ for (const goal of goalsRef.current) {
6142
+ if (firedGoalsRef.current.has(goal.key)) continue;
6143
+ const met = goal.metric === "seconds" ? s.watchedSeconds >= goal.threshold : s.watchedPercent >= goal.threshold;
6144
+ if (met) {
6145
+ firedGoalsRef.current.add(goal.key);
6146
+ hitGoal = true;
6147
+ }
6148
+ }
6149
+ const newlyFiredCheckpoints = [];
6150
+ for (const cp of checkpoints) {
6151
+ if (firedCheckpointsRef.current.has(cp)) continue;
6152
+ if (s.watchedPercent >= cp || s.maxPercent >= cp) {
6153
+ firedCheckpointsRef.current.add(cp);
6154
+ newlyFiredCheckpoints.push(cp);
6155
+ hitCheckpoint = true;
6156
+ }
6157
+ }
6158
+ if (hitCheckpoint || hitGoal) {
6159
+ commitActiveSegment(v?.currentTime);
6160
+ if (v && !v.paused) activeSegmentStartRef.current = v.currentTime;
6161
+ const emittedSnap = snap();
6162
+ emitProgress(hitCheckpoint ? "checkpoint" : "goal", emittedSnap);
6163
+ for (const cp of newlyFiredCheckpoints) {
6164
+ cbRef.current.onCheckpoint?.({ ...emittedSnap, checkpoint: cp });
6165
+ }
6166
+ }
6167
+ cbRef.current.onTimeUpdate?.(s);
6168
+ },
6169
+ onSeeking: () => {
6170
+ seekFrom.current = lastTime.current;
6171
+ commitActiveSegment(seekFrom.current);
6172
+ },
6173
+ onSeeked: () => {
6174
+ const v = video.current;
6175
+ const to = roundVideoSeconds(v?.currentTime ?? 0);
6176
+ const from = roundVideoSeconds(seekFrom.current);
6177
+ lastTime.current = to;
6178
+ if (v && !v.paused) {
6179
+ activeSegmentStartRef.current = to;
6180
+ }
6181
+ if (Math.abs(to - from) >= 1) {
6182
+ seekCountRef.current += 1;
6183
+ const s = snap();
6184
+ emitProgress("seek", s);
6185
+ cbRef.current.onSeek?.({ ...s, from, to });
6186
+ }
6187
+ },
6188
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("track", { kind: "captions" })
6189
+ }
6190
+ ),
6191
+ ready && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
6192
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("media-loading-indicator", { slot: "centered-chrome" }),
6193
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "tv-chrome", children: [
6194
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "tv-row", children: [
6195
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("media-play-button", { class: "tv-ctl", children: [
6196
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(IconPlay, { slot: "play" }),
6197
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(IconPause, { slot: "pause" })
6198
+ ] }),
6199
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("media-time-display", { class: "tv-time tv-time-full", showduration: true }),
6200
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("media-time-display", { class: "tv-time tv-time-cur" }),
6201
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "tv-spacer" }),
6202
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "tv-vol", children: [
6203
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("media-mute-button", { class: "tv-ctl", children: [
6204
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(IconVolumeHigh, { slot: "high" }),
6205
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(IconVolumeLow, { slot: "medium" }),
6206
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(IconVolumeLow, { slot: "low" }),
6207
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(IconVolumeOff, { slot: "off" })
6208
+ ] }),
6209
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("media-volume-range", { class: "tv-volrange" })
6210
+ ] }),
6211
+ renditions.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "tv-menu-wrap", ref: wrap, children: [
6212
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
6213
+ "button",
6214
+ {
6215
+ ref: gear,
6216
+ type: "button",
6217
+ className: "tv-btn",
6218
+ "aria-haspopup": "menu",
6219
+ "aria-expanded": menu,
6220
+ "aria-label": `Settings, quality ${qualityLabel}`,
6221
+ title: "Settings",
6222
+ onClick: () => setMenu((m) => !m),
6223
+ onKeyDown: onMenuKey,
6224
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(IconSettings, {})
6225
+ }
6226
+ ),
6227
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { role: "status", className: "sr-only", children: pending !== null ? `Switching to ${pending}p` : "" }),
6228
+ menu && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
6229
+ "ul",
6230
+ {
6231
+ role: "menu",
6232
+ "aria-label": "Quality",
6233
+ className: "tv-menu",
6234
+ onKeyDown: onMenuKey,
6235
+ children: [
6236
+ ["auto", ...renditions.map((r) => r.height)].map(
6237
+ (q) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("li", { role: "none", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
6238
+ "button",
6239
+ {
6240
+ type: "button",
6241
+ role: "menuitemradio",
6242
+ "aria-checked": selected === q,
6243
+ className: "tv-menu-item",
6244
+ onClick: () => pick(q),
6245
+ children: q === "auto" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
6246
+ "Auto",
6247
+ live ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "tv-menu-sub", children: [
6248
+ " ",
6249
+ live,
6250
+ "p"
6251
+ ] }) : null
6252
+ ] }) : `${q}p`
6253
+ }
6254
+ ) }, String(q))
6255
+ ),
6256
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("li", { "aria-hidden": "true", className: "tv-menu-note", children: pending !== null ? `Switching to ${pending}p\u2026` : "" })
6257
+ ]
6258
+ }
6259
+ )
6260
+ ] }),
6261
+ variant === "16x9" && onTheaterChange && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
6262
+ "button",
6263
+ {
6264
+ type: "button",
6265
+ className: "tv-btn tv-theater",
6266
+ "aria-pressed": theater,
6267
+ "aria-label": theater ? "Shrink the player" : "Expand the player",
6268
+ title: theater ? "Shrink" : "Expand",
6269
+ onClick: () => onTheaterChange(!theater),
6270
+ children: theater ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(IconTheaterShrink, {}) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(IconTheaterExpand, {})
6271
+ }
6272
+ ),
6273
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("media-fullscreen-button", { class: "tv-ctl", children: [
6274
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(IconFullscreenEnter, { slot: "enter" }),
6275
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(IconFullscreenExit, { slot: "exit" })
6276
+ ] })
6277
+ ] }),
6278
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("media-time-range", { class: "tv-seek" })
6279
+ ] })
6280
+ ] })
6281
+ ] }),
6282
+ !playing && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "tv-poster-overlay", children: [
6283
+ renderPoster ? renderPoster({
6284
+ src: resolvedPosterUrl,
6285
+ variant,
6286
+ title,
6287
+ width: posterWidth,
6288
+ height: posterHeight
6289
+ }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
6290
+ "img",
6291
+ {
6292
+ src: resolvedPosterUrl,
6293
+ alt: "",
6294
+ width: posterWidth,
6295
+ height: posterHeight,
6296
+ loading: "lazy",
6297
+ decoding: "async",
6298
+ className: "tv-poster-img"
6299
+ }
6300
+ ),
6301
+ !started && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
6302
+ "button",
6303
+ {
6304
+ type: "button",
6305
+ onClick: start,
6306
+ "aria-label": `Play video: ${title}`,
6307
+ className: "tv-play-trigger",
6308
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "tv-play-pill", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(IconPlay, { size: 26, filled: true }) })
6309
+ }
6310
+ )
6311
+ ] })
6312
+ ]
6313
+ }
6314
+ );
6315
+ }
6316
+ function TrackedVideo(props) {
6317
+ const {
6318
+ id,
6319
+ video,
6320
+ host,
6321
+ sources,
6322
+ title,
6323
+ posterTime,
6324
+ posterUrl,
6325
+ renderPoster,
6326
+ theater: controlledTheater,
6327
+ defaultTheater = false,
6328
+ onTheaterChange,
6329
+ expandMode = "inline",
6330
+ autoExpandOnPlay = true,
6331
+ expandedMaxWidth = 1100,
6332
+ checkpoints = DEFAULT_VIDEO_CHECKPOINTS,
6333
+ trackingConfig,
6334
+ fallbackConfig,
6335
+ client = null,
6336
+ loadPlayerLibs,
6337
+ className = "",
6338
+ style,
6339
+ ...callbacks
6340
+ } = props;
6341
+ const videoKey = id ?? video ?? fallbackConfig?.key ?? "";
6342
+ const r2Video = useTrackedVideoConfig(videoKey || void 0, trackingConfig, fallbackConfig);
6343
+ const videoGoals = useActiveVideoGoals(videoKey, trackingConfig);
6344
+ const [internalTheater, setInternalTheater] = (0, import_react8.useState)(defaultTheater);
6345
+ const isTheaterControlled = controlledTheater !== void 0;
6346
+ const activeTheater = isTheaterControlled ? controlledTheater : internalTheater;
6347
+ const handleTheaterChange = (0, import_react8.useCallback)(
6348
+ (next) => {
6349
+ if (!isTheaterControlled) {
6350
+ setInternalTheater(next);
6351
+ }
6352
+ onTheaterChange?.(next);
6353
+ },
6354
+ [isTheaterControlled, onTheaterChange]
6355
+ );
6356
+ const resolvedHost = host ?? r2Video?.host ?? "";
6357
+ const desktopUid = sources?.desktop ?? r2Video?.sources.desktop ?? "";
6358
+ const mobileUid = sources?.mobile ?? r2Video?.sources.mobile ?? desktopUid;
6359
+ const resolvedTitle = title ?? r2Video?.title ?? videoKey;
6360
+ const resolvedPosterTime = posterTime ?? r2Video?.poster_time ?? r2Video?.poster_time_s ?? 4;
6361
+ if (!videoKey || !resolvedHost || !desktopUid) {
6362
+ return null;
6363
+ }
6364
+ const showTheaterControl = expandMode !== "none" && (expandMode === "theater" || Boolean(onTheaterChange));
6365
+ const wrapperStyle = !isTheaterControlled && expandMode === "theater" && activeTheater ? {
6366
+ maxWidth: typeof expandedMaxWidth === "number" ? `${expandedMaxWidth}px` : expandedMaxWidth,
6367
+ marginLeft: "auto",
6368
+ marginRight: "auto",
6369
+ width: "100%",
6370
+ transition: "max-width 360ms cubic-bezier(0.2, 0.7, 0.2, 1)",
6371
+ ...style
6372
+ } : style;
6373
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className, style: wrapperStyle, "data-tracked-video": videoKey, children: [
6374
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
6375
+ Cut,
6376
+ {
6377
+ ...callbacks,
6378
+ id: videoKey,
6379
+ variant: "16x9",
6380
+ uid: desktopUid,
6381
+ host: resolvedHost,
6382
+ title: resolvedTitle,
6383
+ posterTime: resolvedPosterTime,
6384
+ posterUrl,
6385
+ renderPoster,
6386
+ theater: activeTheater,
6387
+ onTheaterChange: showTheaterControl ? handleTheaterChange : void 0,
6388
+ autoExpandOnPlay,
6389
+ checkpoints,
6390
+ videoGoals,
6391
+ client,
6392
+ loadPlayerLibs
6393
+ }
6394
+ ),
6395
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
6396
+ Cut,
6397
+ {
6398
+ ...callbacks,
6399
+ id: videoKey,
6400
+ variant: "9x16",
6401
+ uid: mobileUid || desktopUid,
6402
+ host: resolvedHost,
6403
+ title: resolvedTitle,
6404
+ posterTime: resolvedPosterTime,
6405
+ posterUrl,
6406
+ renderPoster,
6407
+ theater: false,
6408
+ onTheaterChange: void 0,
6409
+ autoExpandOnPlay: false,
6410
+ checkpoints,
6411
+ videoGoals,
6412
+ client,
6413
+ loadPlayerLibs
6414
+ }
6415
+ )
6416
+ ] });
6417
+ }
6418
+ var STAGE_DURATION_MS = 360;
6419
+ var STAGE_EASE = "cubic-bezier(0.2, 0.7, 0.2, 1)";
6420
+ function TrackedVideoStage({
6421
+ video,
6422
+ children,
6423
+ expandedMaxWidth = 1100,
6424
+ collapsedTextMaxWidth = 760,
6425
+ className = ""
6426
+ }) {
6427
+ const [theater, setTheater] = (0, import_react8.useState)(video.defaultTheater ?? false);
6428
+ const videoBox = (0, import_react8.useRef)(null);
6429
+ const textBox = (0, import_react8.useRef)(null);
6430
+ const first = (0, import_react8.useRef)(null);
6431
+ const activeTheater = video.theater !== void 0 ? video.theater : theater;
6432
+ const change = (0, import_react8.useCallback)(
6433
+ (next) => {
6434
+ if (next === activeTheater) return;
6435
+ if (next && typeof window !== "undefined" && !window.matchMedia("(min-width: 768px)").matches) {
6436
+ return;
6437
+ }
6438
+ if (videoBox.current && textBox.current) {
6439
+ first.current = {
6440
+ v: videoBox.current.getBoundingClientRect(),
6441
+ t: textBox.current.getBoundingClientRect()
6442
+ };
6443
+ }
6444
+ if (video.theater === void 0) {
6445
+ setTheater(next);
6446
+ }
6447
+ video.onTheaterChange?.(next);
6448
+ },
6449
+ [activeTheater, video]
6450
+ );
6451
+ (0, import_react8.useLayoutEffect)(() => {
6452
+ if (typeof window === "undefined") return;
6453
+ const f = first.current;
6454
+ const vb = videoBox.current;
6455
+ const tb = textBox.current;
6456
+ first.current = null;
6457
+ if (!f || !vb || !tb) return;
6458
+ const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
6459
+ const lv = vb.getBoundingClientRect();
6460
+ const lt = tb.getBoundingClientRect();
6461
+ if (!reduce && typeof vb.animate === "function" && lv.width > 0) {
6462
+ const s = f.v.width / lv.width;
6463
+ vb.animate(
6464
+ [
6465
+ {
6466
+ transformOrigin: "top left",
6467
+ transform: `translate(${f.v.left - lv.left}px, ${f.v.top - lv.top}px) scale(${s})`
6468
+ },
6469
+ { transformOrigin: "top left", transform: "none" }
6470
+ ],
6471
+ { duration: STAGE_DURATION_MS, easing: STAGE_EASE }
6472
+ );
6473
+ tb.animate(
6474
+ [
6475
+ {
6476
+ transform: `translate(${f.t.left - lt.left}px, ${f.t.top - lt.top}px)`,
6477
+ opacity: 0.4
6478
+ },
6479
+ { transform: "none", opacity: 1 }
6480
+ ],
6481
+ { duration: STAGE_DURATION_MS, easing: STAGE_EASE }
6482
+ );
6483
+ }
6484
+ if (lv.top < 80 || lv.bottom > window.innerHeight - 24) {
6485
+ const top = window.scrollY + lv.top - Math.max(96, (window.innerHeight - lv.height) / 2);
6486
+ window.scrollTo({ top, behavior: reduce ? "auto" : "smooth" });
6487
+ }
6488
+ }, [activeTheater]);
6489
+ const maxVideoWidthCss = typeof expandedMaxWidth === "number" ? `${expandedMaxWidth}px` : expandedMaxWidth;
6490
+ const maxTextWidthCss = typeof collapsedTextMaxWidth === "number" ? `${collapsedTextMaxWidth}px` : collapsedTextMaxWidth;
6491
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
6492
+ "div",
6493
+ {
6494
+ className: activeTheater ? `grid gap-10 md:gap-12 ${className}` : `grid items-start gap-10 lg:grid-cols-[1fr_1fr] lg:gap-16 ${className}`,
6495
+ children: [
6496
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
6497
+ "div",
6498
+ {
6499
+ ref: textBox,
6500
+ className: activeTheater ? "md:order-2" : "",
6501
+ style: activeTheater ? { maxWidth: maxTextWidthCss } : void 0,
6502
+ children
6503
+ }
6504
+ ),
6505
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
6506
+ "div",
6507
+ {
6508
+ ref: videoBox,
6509
+ className: activeTheater ? "md:order-1 md:mx-auto md:w-full" : "",
6510
+ style: activeTheater ? { maxWidth: maxVideoWidthCss, width: "100%" } : void 0,
6511
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
6512
+ TrackedVideo,
6513
+ {
6514
+ ...video,
6515
+ theater: activeTheater,
6516
+ onTheaterChange: change,
6517
+ className: video.className ?? "w-full"
6518
+ }
6519
+ )
6520
+ }
6521
+ )
6522
+ ]
6523
+ }
6524
+ );
6525
+ }
4882
6526
  // Annotate the CommonJS export names for ESM import in node:
4883
6527
  0 && (module.exports = {
4884
6528
  AdPlatformTracking,
4885
6529
  BookingHandoffLink,
4886
6530
  ConsentBanner,
6531
+ DEFAULT_VIDEO_CHECKPOINTS,
4887
6532
  GoogleAdsTracking,
4888
6533
  PhoneField,
6534
+ TrackedVideo,
6535
+ TrackedVideoStage,
4889
6536
  createLeadFormSubmit,
4890
6537
  createTracking,
6538
+ streamManifestUrl,
6539
+ streamPosterUrl,
4891
6540
  useBookingHandoffUrl,
4892
6541
  useBookingIntegration,
4893
6542
  useBookingSlots,
@@ -4898,6 +6547,7 @@ function useBookingSubmit(bookings, options = {}) {
4898
6547
  useGclid,
4899
6548
  usePhoneConfig,
4900
6549
  usePhoneField,
6550
+ useTrackedVideoConfig,
4901
6551
  useTrackingParams
4902
6552
  });
4903
6553
  //# sourceMappingURL=client.js.map