@aranova/tracking-react 1.17.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.mjs CHANGED
@@ -255,7 +255,8 @@ var TRACKING_CAPABILITIES = [
255
255
  "form_capture",
256
256
  "phone_click_capture",
257
257
  "phone_fields",
258
- "typed_form_submit"
258
+ "typed_form_submit",
259
+ "video_tracking"
259
260
  ];
260
261
  var CAPABILITY_DOM_ATTRIBUTE = "data-aranova-capability";
261
262
  var registered = /* @__PURE__ */ new Set();
@@ -1036,8 +1037,48 @@ function parseTrigger(value) {
1036
1037
  if (typeof t.threshold_seconds === "number") spec.threshold_seconds = t.threshold_seconds;
1037
1038
  if (typeof t.page_threshold === "number") spec.page_threshold = t.page_threshold;
1038
1039
  if (typeof t.page_name === "string") spec.page_name = t.page_name;
1040
+ if (t.metric === "percent" || t.metric === "seconds") spec.metric = t.metric;
1041
+ if (typeof t.threshold === "number") spec.threshold = t.threshold;
1042
+ if (typeof t.video_id === "string" || t.video_id === null) spec.video_id = t.video_id;
1039
1043
  return spec;
1040
1044
  }
1045
+ function parseVideoEntry(fallbackKey2, entry) {
1046
+ const key = typeof entry.key === "string" && entry.key ? entry.key : fallbackKey2;
1047
+ if (!key) return null;
1048
+ const host = typeof entry.host === "string" ? entry.host : "";
1049
+ const title = typeof entry.title === "string" && entry.title ? entry.title : key;
1050
+ const sourcesRaw = isRecord(entry.sources) ? entry.sources : null;
1051
+ const desktop = sourcesRaw && typeof sourcesRaw.desktop === "string" ? sourcesRaw.desktop : typeof entry.desktop_uid === "string" ? entry.desktop_uid : "";
1052
+ if (!host || !desktop) return null;
1053
+ const mobile = sourcesRaw && typeof sourcesRaw.mobile === "string" ? sourcesRaw.mobile : typeof entry.mobile_uid === "string" ? entry.mobile_uid : null;
1054
+ const posterTime = typeof entry.poster_time === "number" ? entry.poster_time : typeof entry.poster_time_s === "number" ? entry.poster_time_s : 4;
1055
+ return {
1056
+ key,
1057
+ title,
1058
+ host,
1059
+ sources: { desktop, mobile },
1060
+ poster_time: posterTime,
1061
+ duration_s: typeof entry.duration_s === "number" ? entry.duration_s : null
1062
+ };
1063
+ }
1064
+ function parseVideos(value) {
1065
+ const out = {};
1066
+ if (Array.isArray(value)) {
1067
+ for (const item of value) {
1068
+ if (!isRecord(item)) continue;
1069
+ const parsed = parseVideoEntry("", item);
1070
+ if (parsed) out[parsed.key] = parsed;
1071
+ }
1072
+ return out;
1073
+ }
1074
+ if (!isRecord(value)) return out;
1075
+ for (const [key, entry] of Object.entries(value)) {
1076
+ if (!isRecord(entry)) continue;
1077
+ const parsed = parseVideoEntry(key, entry);
1078
+ if (parsed) out[parsed.key] = parsed;
1079
+ }
1080
+ return out;
1081
+ }
1041
1082
  function parseConversionConfig(raw) {
1042
1083
  if (!raw || typeof raw !== "object") return null;
1043
1084
  const obj = raw;
@@ -1088,7 +1129,8 @@ function parseConversionConfig(raw) {
1088
1129
  goals,
1089
1130
  linked_hostnames: Array.isArray(obj.linked_hostnames) ? obj.linked_hostnames.filter((h) => typeof h === "string" && h.length > 0) : [],
1090
1131
  integrations: parseIntegrations(obj.integrations),
1091
- lead_forms: parseLeadForms(obj.lead_forms)
1132
+ lead_forms: parseLeadForms(obj.lead_forms),
1133
+ videos: parseVideos(obj.videos)
1092
1134
  };
1093
1135
  }
1094
1136
  function parseLeadForms(value) {
@@ -1334,6 +1376,21 @@ function automaticThresholdMet(goal, eventType, metadata) {
1334
1376
  case "form_start":
1335
1377
  case "phone_click":
1336
1378
  return true;
1379
+ case "video_play":
1380
+ return trigger.video_id == null || hasStringField(metadata, "video_id") && metadata.video_id === trigger.video_id;
1381
+ case "video_progress": {
1382
+ if (trigger.video_id != null && (!hasStringField(metadata, "video_id") || metadata.video_id !== trigger.video_id)) {
1383
+ return false;
1384
+ }
1385
+ if (typeof trigger.threshold !== "number") return false;
1386
+ if (trigger.metric === "seconds") {
1387
+ return hasNumberField(metadata, "watched_s") && metadata.watched_s >= trigger.threshold;
1388
+ }
1389
+ if (trigger.metric === "percent") {
1390
+ return hasNumberField(metadata, "max_percent") && metadata.max_percent >= trigger.threshold || hasNumberField(metadata, "watched_percent") && metadata.watched_percent >= trigger.threshold;
1391
+ }
1392
+ return false;
1393
+ }
1337
1394
  default:
1338
1395
  return false;
1339
1396
  }
@@ -1370,6 +1427,9 @@ var RETRY_BASE_MS = 5e3;
1370
1427
  var RETRY_MAX_MS = 5 * 6e4;
1371
1428
  var runtimes = /* @__PURE__ */ new Map();
1372
1429
  var configuredIds = /* @__PURE__ */ new Set();
1430
+ var EMPTY_GOALS = [];
1431
+ var EMPTY_STRINGS = [];
1432
+ var EMPTY_VIDEOS = {};
1373
1433
  var scriptLoad = null;
1374
1434
  var jsInitialized = false;
1375
1435
  function cacheKey2(url) {
@@ -1560,11 +1620,11 @@ var TrackingConfigRuntime = class {
1560
1620
  void this.flush();
1561
1621
  }
1562
1622
  listGoals() {
1563
- return this.current?.goals ?? [];
1623
+ return this.current?.goals ?? EMPTY_GOALS;
1564
1624
  }
1565
1625
  /** Sibling hostnames for the linker — read from the cached object even while unconfirmed. */
1566
1626
  linkedHostnames() {
1567
- return this.current?.linked_hostnames ?? [];
1627
+ return this.current?.linked_hostnames ?? EMPTY_STRINGS;
1568
1628
  }
1569
1629
  /**
1570
1630
  * Integration blocks, from the cached object even while unconfirmed: the booking fallback
@@ -1577,6 +1637,14 @@ var TrackingConfigRuntime = class {
1577
1637
  leadForms() {
1578
1638
  return this.current?.lead_forms ?? null;
1579
1639
  }
1640
+ /** Configured Cloudflare Stream tracked videos for this site. */
1641
+ videos() {
1642
+ return this.current?.videos ?? EMPTY_VIDEOS;
1643
+ }
1644
+ /** Look up one configured Cloudflare Stream tracked video by key. */
1645
+ getVideo(key) {
1646
+ return this.current?.videos?.[key] ?? null;
1647
+ }
1580
1648
  async revalidateNow() {
1581
1649
  try {
1582
1650
  const headers = {};
@@ -1733,6 +1801,16 @@ var TrackingConfigRuntime = class {
1733
1801
  this.scheduleRetry();
1734
1802
  return;
1735
1803
  }
1804
+ if (outcome === "fired" && typeof window !== "undefined" && typeof window.fbq === "function") {
1805
+ try {
1806
+ window.fbq("trackCustom", item.key, {
1807
+ ...value != null ? { value } : {},
1808
+ ...currency ? { currency } : {},
1809
+ ...item.transactionId ? { transaction_id: item.transactionId } : {}
1810
+ });
1811
+ } catch {
1812
+ }
1813
+ }
1736
1814
  this.conversionQueue.shift();
1737
1815
  }
1738
1816
  }
@@ -1876,12 +1954,24 @@ function createConversionAutoFire(store) {
1876
1954
  const firing = goal.firing;
1877
1955
  const cents = firing.value_cents ?? null;
1878
1956
  const currency = firing.currency ?? null;
1879
- fireConversionWithConsent({
1957
+ const value = cents != null && currency ? fromMinor(cents, currency) : null;
1958
+ const outcome = fireConversionWithConsent({
1880
1959
  sendTo: firing.send_to,
1881
- value: cents != null && currency ? fromMinor(cents, currency) : null,
1960
+ value,
1882
1961
  currency,
1883
1962
  transactionId
1884
1963
  });
1964
+ if (outcome === "fired" && typeof window !== "undefined" && typeof window.fbq === "function") {
1965
+ try {
1966
+ window.fbq("trackCustom", goal.key, {
1967
+ event_type: eventType,
1968
+ ...value != null ? { value } : {},
1969
+ ...currency ? { currency } : {},
1970
+ transaction_id: transactionId
1971
+ });
1972
+ } catch {
1973
+ }
1974
+ }
1885
1975
  }
1886
1976
  }
1887
1977
  return {
@@ -1978,6 +2068,13 @@ var EVENT_SEMANTICS = {
1978
2068
  outcomeRole: "engagement",
1979
2069
  clientVisibility: "detailed"
1980
2070
  },
2071
+ // Adds to the submit it names; counting it as a lead would double every lead.
2072
+ form_details: {
2073
+ label: "Form details added",
2074
+ category: "lead",
2075
+ outcomeRole: "engagement",
2076
+ clientVisibility: "detailed"
2077
+ },
1981
2078
  form_submit: {
1982
2079
  label: "Form submitted",
1983
2080
  category: "lead",
@@ -2007,6 +2104,18 @@ var EVENT_SEMANTICS = {
2007
2104
  category: "page",
2008
2105
  outcomeRole: "navigation",
2009
2106
  clientVisibility: "simple"
2107
+ },
2108
+ video_play: {
2109
+ label: "Video played",
2110
+ category: "engagement",
2111
+ outcomeRole: "engagement",
2112
+ clientVisibility: "simple"
2113
+ },
2114
+ video_progress: {
2115
+ label: "Video watched",
2116
+ category: "engagement",
2117
+ outcomeRole: "engagement",
2118
+ clientVisibility: "detailed"
2010
2119
  }
2011
2120
  };
2012
2121
  function getEventSemantics(eventName) {
@@ -2594,14 +2703,31 @@ function createTrackingClient(config) {
2594
2703
  metadata: input.metadata ?? null,
2595
2704
  occurred_at: occurredAt
2596
2705
  });
2597
- if (queue.length >= maxQueueSize || getEventSemantics(input.eventType)?.outcomeRole === "lead") {
2706
+ const triggerReason = input.metadata?.trigger;
2707
+ const isUrgentVideoEvent = input.eventType === "video_play" || input.eventType === "video_progress" && (triggerReason === "ended" || triggerReason === "goal" || triggerReason === "checkpoint");
2708
+ if (queue.length >= maxQueueSize || input.eventType === "form_details" || getEventSemantics(input.eventType)?.outcomeRole === "lead" || isUrgentVideoEvent) {
2598
2709
  void flush();
2599
2710
  } else {
2600
2711
  scheduleFlush();
2601
2712
  }
2602
2713
  }
2714
+ const beforeUnloadListeners = /* @__PURE__ */ new Set();
2715
+ let runningBeforeUnload = false;
2603
2716
  function flushOnUnload() {
2604
2717
  if (destroyed) return;
2718
+ if (!runningBeforeUnload && beforeUnloadListeners.size > 0) {
2719
+ runningBeforeUnload = true;
2720
+ try {
2721
+ for (const listener of beforeUnloadListeners) {
2722
+ try {
2723
+ listener();
2724
+ } catch {
2725
+ }
2726
+ }
2727
+ } finally {
2728
+ runningBeforeUnload = false;
2729
+ }
2730
+ }
2605
2731
  bufferQueued();
2606
2732
  clearScheduledFlush();
2607
2733
  if (pending.length === 0) return;
@@ -2622,6 +2748,12 @@ function createTrackingClient(config) {
2622
2748
  trackEvent,
2623
2749
  flush,
2624
2750
  flushBeacon: flushOnUnload,
2751
+ onBeforeUnload: (listener) => {
2752
+ beforeUnloadListeners.add(listener);
2753
+ return () => {
2754
+ beforeUnloadListeners.delete(listener);
2755
+ };
2756
+ },
2625
2757
  getSessionId: () => sessionId,
2626
2758
  getVisitorId: () => visitorId,
2627
2759
  destroy: () => {
@@ -2632,6 +2764,7 @@ function createTrackingClient(config) {
2632
2764
  destroyed = true;
2633
2765
  clearScheduledFlush();
2634
2766
  queue = [];
2767
+ beforeUnloadListeners.clear();
2635
2768
  if (typeof window !== "undefined") {
2636
2769
  window.removeEventListener("pagehide", flushOnUnload);
2637
2770
  window.removeEventListener("visibilitychange", onVisibilityChange);
@@ -2698,110 +2831,133 @@ var formStartConfigSchema = z5.strictObject({
2698
2831
  selector: z5.optional(z5.string())
2699
2832
  });
2700
2833
 
2701
- // ../tracking-core/src/events/form-step.ts
2834
+ // ../tracking-core/src/events/form-details.ts
2835
+ import * as z7 from "zod/mini";
2836
+
2837
+ // ../tracking-core/src/events/form-submit.ts
2702
2838
  import * as z6 from "zod/mini";
2703
- var formStepMetadataSchema = z6.strictObject({
2839
+ var jsonValueSchema = z6.lazy(
2840
+ () => z6.union([
2841
+ z6.string(),
2842
+ z6.number(),
2843
+ z6.boolean(),
2844
+ z6.null(),
2845
+ z6.array(jsonValueSchema),
2846
+ z6.record(z6.string(), jsonValueSchema)
2847
+ ])
2848
+ ).register(z6.globalRegistry, { id: "JsonSerializableValue" });
2849
+ var contractNameSchema = z6.string().check(z6.regex(/^[a-z][a-z0-9_]{0,47}(:[a-z0-9_-]{1,48})?$/));
2850
+ var violationRole = z6.string().check(
2851
+ z6.maxLength(200),
2852
+ z6.regex(
2853
+ /^[a-z][a-z0-9_]{0,47}(:[a-z0-9_-]{1,48})?(\|[a-z][a-z0-9_]{0,47}(:[a-z0-9_-]{1,48})?)*$/
2854
+ )
2855
+ );
2856
+ var MAX_CONTRACT_VIOLATIONS = 50;
2857
+ var contractViolationSchema = z6.strictObject({
2858
+ role: z6.nullable(violationRole),
2859
+ rule: z6.string().check(z6.regex(/^[a-z][a-z_]{0,47}$/))
2860
+ });
2861
+ var formFieldSchema = z6.strictObject({
2862
+ name: z6.string(),
2863
+ type: z6.string(),
2864
+ label: z6.nullable(z6.string()),
2865
+ value: jsonValueSchema,
2866
+ role: z6.optional(z6.nullable(contractNameSchema))
2867
+ });
2868
+ var formExtraSchema = z6.optional(
2869
+ z6.nullable(
2870
+ z6.array(
2871
+ z6.strictObject({
2872
+ key: z6.string().check(z6.maxLength(64)),
2873
+ label: z6.string().check(z6.maxLength(200)),
2874
+ value: jsonValueSchema
2875
+ })
2876
+ ).check(z6.maxLength(20))
2877
+ )
2878
+ );
2879
+ var contractVersionSchema = z6.optional(z6.nullable(z6.int().check(z6.gte(1))));
2880
+ var contractHashSchema = z6.optional(z6.nullable(z6.string().check(z6.maxLength(64))));
2881
+ var formContractViolationsSchema = z6.optional(
2882
+ z6.nullable(z6.array(contractViolationSchema).check(z6.maxLength(MAX_CONTRACT_VIOLATIONS)))
2883
+ );
2884
+ var formSubmitMetadataSchema = z6.strictObject({
2704
2885
  form: z6.strictObject({
2705
2886
  id: z6.string(),
2706
- action: z6.nullable(z6.string())
2887
+ action: z6.nullable(z6.string()),
2888
+ fields: z6.optional(z6.array(formFieldSchema)),
2889
+ kind: z6.optional(z6.nullable(contractNameSchema)),
2890
+ contract_version: contractVersionSchema,
2891
+ contract_hash: contractHashSchema,
2892
+ extra: formExtraSchema,
2893
+ contract_violations: formContractViolationsSchema,
2894
+ // Minted per submit; a later `form_details` names it to add answers to this lead.
2895
+ submission_id: z6.optional(z6.nullable(z6.uuidv4()))
2707
2896
  }),
2708
- step: z6.strictObject({
2709
- id: z6.string().check(z6.minLength(1), z6.maxLength(64), z6.regex(/^[A-Za-z0-9_-]+$/)),
2710
- // 1-based position in the flow the visitor is on right now.
2711
- index: z6.int().check(z6.gte(1), z6.lte(50)),
2712
- // Null when the number of steps depends on earlier answers.
2713
- total: z6.nullable(z6.int().check(z6.gte(1), z6.lte(50)))
2714
- }),
2715
- direction: z6.enum(["forward", "back"]),
2716
2897
  page: z6.strictObject({
2717
2898
  path: z6.string()
2718
2899
  })
2719
2900
  });
2720
- var formStepConfigSchema = z6.strictObject({});
2901
+ var formSubmitConfigSchema = z6.strictObject({});
2721
2902
 
2722
- // ../tracking-core/src/events/form-submit.ts
2723
- import * as z7 from "zod/mini";
2724
- var jsonValueSchema = z7.lazy(
2725
- () => z7.union([
2726
- z7.string(),
2727
- z7.number(),
2728
- z7.boolean(),
2729
- z7.null(),
2730
- z7.array(jsonValueSchema),
2731
- z7.record(z7.string(), jsonValueSchema)
2732
- ])
2733
- ).register(z7.globalRegistry, { id: "JsonSerializableValue" });
2734
- var contractNameSchema = z7.string().check(z7.regex(/^[a-z][a-z0-9_]{0,47}(:[a-z0-9_-]{1,48})?$/));
2735
- var contractName = contractNameSchema;
2736
- var violationRole = z7.string().check(
2737
- z7.maxLength(200),
2738
- z7.regex(
2739
- /^[a-z][a-z0-9_]{0,47}(:[a-z0-9_-]{1,48})?(\|[a-z][a-z0-9_]{0,47}(:[a-z0-9_-]{1,48})?)*$/
2740
- )
2741
- );
2742
- var MAX_CONTRACT_VIOLATIONS = 50;
2743
- var contractViolationSchema = z7.strictObject({
2744
- role: z7.nullable(violationRole),
2745
- rule: z7.string().check(z7.regex(/^[a-z][a-z_]{0,47}$/))
2746
- });
2747
- var formSubmitMetadataSchema = z7.strictObject({
2903
+ // ../tracking-core/src/events/form-details.ts
2904
+ var formDetailsMetadataSchema = z7.strictObject({
2748
2905
  form: z7.strictObject({
2749
2906
  id: z7.string(),
2750
- action: z7.nullable(z7.string()),
2751
- fields: z7.optional(
2752
- z7.array(
2753
- z7.strictObject({
2754
- name: z7.string(),
2755
- type: z7.string(),
2756
- label: z7.nullable(z7.string()),
2757
- value: jsonValueSchema,
2758
- role: z7.optional(z7.nullable(contractName))
2759
- })
2760
- )
2761
- ),
2762
- kind: z7.optional(z7.nullable(contractName)),
2763
- contract_version: z7.optional(z7.nullable(z7.int().check(z7.gte(1)))),
2764
- contract_hash: z7.optional(z7.nullable(z7.string().check(z7.maxLength(64)))),
2765
- extra: z7.optional(
2766
- z7.nullable(
2767
- z7.array(
2768
- z7.strictObject({
2769
- key: z7.string().check(z7.maxLength(64)),
2770
- label: z7.string().check(z7.maxLength(200)),
2771
- value: jsonValueSchema
2772
- })
2773
- ).check(z7.maxLength(20))
2774
- )
2775
- ),
2776
- contract_violations: z7.optional(
2777
- z7.nullable(z7.array(contractViolationSchema).check(z7.maxLength(MAX_CONTRACT_VIOLATIONS)))
2778
- )
2907
+ submission_id: z7.uuidv4(),
2908
+ kind: z7.optional(z7.nullable(contractNameSchema)),
2909
+ fields: z7.array(formFieldSchema),
2910
+ contract_version: contractVersionSchema,
2911
+ contract_hash: contractHashSchema,
2912
+ extra: formExtraSchema,
2913
+ contract_violations: formContractViolationsSchema
2779
2914
  }),
2780
2915
  page: z7.strictObject({
2781
2916
  path: z7.string()
2782
2917
  })
2783
2918
  });
2784
- var formSubmitConfigSchema = z7.strictObject({});
2919
+ var formDetailsConfigSchema = z7.strictObject({});
2785
2920
 
2786
- // ../tracking-core/src/events/multi-page-session.ts
2921
+ // ../tracking-core/src/events/form-step.ts
2787
2922
  import * as z8 from "zod/mini";
2788
- var multiPageSessionMetadataSchema = z8.strictObject({
2789
- page_count: z8.int().check(z8.gte(2)),
2923
+ var formStepMetadataSchema = z8.strictObject({
2924
+ form: z8.strictObject({
2925
+ id: z8.string(),
2926
+ action: z8.nullable(z8.string())
2927
+ }),
2928
+ step: z8.strictObject({
2929
+ id: z8.string().check(z8.minLength(1), z8.maxLength(64), z8.regex(/^[A-Za-z0-9_-]+$/)),
2930
+ // 1-based position in the flow the visitor is on right now.
2931
+ index: z8.int().check(z8.gte(1), z8.lte(50)),
2932
+ // Null when the number of steps depends on earlier answers.
2933
+ total: z8.nullable(z8.int().check(z8.gte(1), z8.lte(50)))
2934
+ }),
2935
+ direction: z8.enum(["forward", "back"]),
2790
2936
  page: z8.strictObject({
2791
2937
  path: z8.string()
2792
2938
  })
2793
2939
  });
2794
- var multiPageSessionConfigSchema = z8.strictObject({
2795
- pageThreshold: z8.int().check(z8.gte(2))
2940
+ var formStepConfigSchema = z8.strictObject({});
2941
+
2942
+ // ../tracking-core/src/events/multi-page-session.ts
2943
+ import * as z9 from "zod/mini";
2944
+ var multiPageSessionMetadataSchema = z9.strictObject({
2945
+ page_count: z9.int().check(z9.gte(2)),
2946
+ page: z9.strictObject({
2947
+ path: z9.string()
2948
+ })
2949
+ });
2950
+ var multiPageSessionConfigSchema = z9.strictObject({
2951
+ pageThreshold: z9.int().check(z9.gte(2))
2796
2952
  });
2797
2953
 
2798
2954
  // ../tracking-core/src/events/page-exit.ts
2799
- import * as z9 from "zod/mini";
2800
- var pageExitMetadataSchema = z9.strictObject({
2801
- dwell_ms: z9.int().check(z9.gte(0)),
2955
+ import * as z10 from "zod/mini";
2956
+ var pageExitMetadataSchema = z10.strictObject({
2957
+ dwell_ms: z10.int().check(z10.gte(0)),
2802
2958
  // null = left without any scroll signal; floor is 0 so a valid 0% is never
2803
2959
  // rejected (a single bad field 422s the whole keepalive beacon batch).
2804
- max_scroll_percent: z9.nullable(z9.int().check(z9.gte(0), z9.lte(100))),
2960
+ max_scroll_percent: z10.nullable(z10.int().check(z10.gte(0), z10.lte(100))),
2805
2961
  // The gating baseline: the fraction of the page visible at load with
2806
2962
  // zero scrolling — or, for pages that grew after the post-paint snapshot
2807
2963
  // (skeleton/streaming renders), the first-scroll position that
@@ -2809,106 +2965,186 @@ var pageExitMetadataSchema = z9.strictObject({
2809
2965
  // ended before the snapshot. Optionality is load-bearing: SDK builds
2810
2966
  // predating this field keep POSTing page_exit without the key —
2811
2967
  // requiring it would 422 whole keepalive beacon batches.
2812
- scroll_baseline_percent: z9.optional(z9.nullable(z9.int().check(z9.gte(0), z9.lte(100)))),
2813
- page: z9.strictObject({
2814
- path: z9.string()
2968
+ scroll_baseline_percent: z10.optional(z10.nullable(z10.int().check(z10.gte(0), z10.lte(100)))),
2969
+ page: z10.strictObject({
2970
+ path: z10.string()
2815
2971
  })
2816
2972
  });
2817
- var pageExitConfigSchema = z9.strictObject({});
2973
+ var pageExitConfigSchema = z10.strictObject({});
2818
2974
 
2819
2975
  // ../tracking-core/src/events/phone-click.ts
2820
- import * as z10 from "zod/mini";
2821
- var phoneClickMetadataSchema = z10.strictObject({
2822
- phone_number: z10.string(),
2823
- page: z10.strictObject({
2824
- path: z10.string()
2976
+ import * as z11 from "zod/mini";
2977
+ var phoneClickMetadataSchema = z11.strictObject({
2978
+ phone_number: z11.string(),
2979
+ page: z11.strictObject({
2980
+ path: z11.string()
2825
2981
  }),
2826
- section: z10.optional(z10.nullable(z10.string()))
2982
+ section: z11.optional(z11.nullable(z11.string()))
2827
2983
  });
2828
- var phoneClickConfigSchema = z10.strictObject({
2829
- autoCapture: z10.optional(
2830
- z10.strictObject({
2831
- selector: z10.optional(z10.string())
2984
+ var phoneClickConfigSchema = z11.strictObject({
2985
+ autoCapture: z11.optional(
2986
+ z11.strictObject({
2987
+ selector: z11.optional(z11.string())
2832
2988
  })
2833
2989
  )
2834
2990
  });
2835
2991
 
2836
2992
  // ../tracking-core/src/events/scroll-depth.ts
2837
- import * as z11 from "zod/mini";
2838
- var scrollDepthMetadataSchema = z11.strictObject({
2839
- depth_percent: z11.int().check(z11.gte(1), z11.lte(100)),
2840
- page: z11.strictObject({
2841
- path: z11.string()
2993
+ import * as z12 from "zod/mini";
2994
+ var scrollDepthMetadataSchema = z12.strictObject({
2995
+ depth_percent: z12.int().check(z12.gte(1), z12.lte(100)),
2996
+ page: z12.strictObject({
2997
+ path: z12.string()
2842
2998
  })
2843
2999
  });
2844
- var scrollDepthConfigSchema = z11.strictObject({
2845
- thresholds: z11.array(z11.int().check(z11.gte(1), z11.lte(100))).check(z11.minLength(1))
3000
+ var scrollDepthConfigSchema = z12.strictObject({
3001
+ thresholds: z12.array(z12.int().check(z12.gte(1), z12.lte(100))).check(z12.minLength(1))
2846
3002
  });
2847
3003
 
2848
3004
  // ../tracking-core/src/events/specific-page-visit.ts
2849
- import * as z12 from "zod/mini";
3005
+ import * as z13 from "zod/mini";
2850
3006
  var PAGE_IDENTITY_MAX_LENGTH = 64;
2851
3007
  var PAGE_IDENTITY_PATTERN = /^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$/;
2852
- var pageIdentitySchema = z12.string().check(
2853
- z12.maxLength(PAGE_IDENTITY_MAX_LENGTH),
2854
- z12.regex(PAGE_IDENTITY_PATTERN, {
3008
+ var pageIdentitySchema = z13.string().check(
3009
+ z13.maxLength(PAGE_IDENTITY_MAX_LENGTH),
3010
+ z13.regex(PAGE_IDENTITY_PATTERN, {
2855
3011
  message: "page identity must be lowercase snake_case, start with a letter, and contain at most 64 characters"
2856
3012
  })
2857
3013
  );
2858
3014
  var specificPageNameSchema = pageIdentitySchema;
2859
- var specificPageVisitMetadataSchema = z12.strictObject({
3015
+ var specificPageVisitMetadataSchema = z13.strictObject({
2860
3016
  page_name: specificPageNameSchema,
2861
- page: z12.strictObject({
2862
- path: z12.string()
3017
+ page: z13.strictObject({
3018
+ path: z13.string()
2863
3019
  })
2864
3020
  });
2865
- var specificPageVisitConfigSchema = z12.strictObject({
2866
- pages: z12.array(
2867
- z12.strictObject({
3021
+ var specificPageVisitConfigSchema = z13.strictObject({
3022
+ pages: z13.array(
3023
+ z13.strictObject({
2868
3024
  name: specificPageNameSchema,
2869
- pathPattern: z12.custom((value) => value instanceof RegExp, {
3025
+ pathPattern: z13.custom((value) => value instanceof RegExp, {
2870
3026
  message: "pathPattern must be a RegExp"
2871
3027
  })
2872
3028
  })
2873
- ).check(z12.minLength(1))
3029
+ ).check(z13.minLength(1))
2874
3030
  });
2875
3031
 
2876
3032
  // ../tracking-core/src/events/site-link.ts
2877
- import * as z13 from "zod/mini";
2878
- var siteLinkClickMetadataSchema = z13.strictObject({
2879
- destination_host: z13.string(),
2880
- destination_url: z13.string(),
2881
- page: z13.strictObject({
2882
- path: z13.string()
3033
+ import * as z14 from "zod/mini";
3034
+ var siteLinkClickMetadataSchema = z14.strictObject({
3035
+ destination_host: z14.string(),
3036
+ destination_url: z14.string(),
3037
+ page: z14.strictObject({
3038
+ path: z14.string()
2883
3039
  })
2884
3040
  });
2885
- var siteLinkClickConfigSchema = z13.strictObject({});
2886
- var siteLinkArrivalMetadataSchema = z13.strictObject({
2887
- outcome: z13.enum(["linked", "stale", "invalid"]),
2888
- origin_session_id: z13.optional(z13.nullable(z13.string())),
2889
- origin_visitor_id: z13.optional(z13.nullable(z13.string())),
3041
+ var siteLinkClickConfigSchema = z14.strictObject({});
3042
+ var siteLinkArrivalMetadataSchema = z14.strictObject({
3043
+ outcome: z14.enum(["linked", "stale", "invalid"]),
3044
+ origin_session_id: z14.optional(z14.nullable(z14.string())),
3045
+ origin_visitor_id: z14.optional(z14.nullable(z14.string())),
2890
3046
  // Milliseconds between the origin click and this page load.
2891
- transit_ms: z13.optional(z13.nullable(z13.number())),
3047
+ transit_ms: z14.optional(z14.nullable(z14.number())),
2892
3048
  // True when this browser had no visitor id and took the origin's.
2893
- adopted_visitor: z13.boolean(),
2894
- page: z13.strictObject({
2895
- path: z13.string()
3049
+ adopted_visitor: z14.boolean(),
3050
+ page: z14.strictObject({
3051
+ path: z14.string()
2896
3052
  })
2897
3053
  });
2898
- var siteLinkArrivalConfigSchema = z13.strictObject({});
3054
+ var siteLinkArrivalConfigSchema = z14.strictObject({});
2899
3055
 
2900
3056
  // ../tracking-core/src/events/time-on-site.ts
2901
- import * as z14 from "zod/mini";
2902
- var timeOnSiteMetadataSchema = z14.strictObject({
2903
- duration_ms: z14.int().check(z14.nonnegative()),
2904
- page: z14.strictObject({
2905
- path: z14.string()
3057
+ import * as z15 from "zod/mini";
3058
+ var timeOnSiteMetadataSchema = z15.strictObject({
3059
+ duration_ms: z15.int().check(z15.nonnegative()),
3060
+ page: z15.strictObject({
3061
+ path: z15.string()
2906
3062
  })
2907
3063
  });
2908
- var timeOnSiteConfigSchema = z14.strictObject({
2909
- thresholdSeconds: z14.int().check(z14.positive())
3064
+ var timeOnSiteConfigSchema = z15.strictObject({
3065
+ thresholdSeconds: z15.int().check(z15.positive())
2910
3066
  });
2911
3067
 
3068
+ // ../tracking-core/src/events/video.ts
3069
+ import * as z16 from "zod/mini";
3070
+
3071
+ // ../tracking-core/src/video-ranges.ts
3072
+ var VIDEO_ID_MAX_LENGTH = 64;
3073
+ var VIDEO_ID_PATTERN = /^[A-Za-z0-9_-]+$/;
3074
+ function roundVideoSeconds(value) {
3075
+ if (!Number.isFinite(value) || value < 0) return 0;
3076
+ return Math.round(value * 100) / 100;
3077
+ }
3078
+ function mergeVideoRanges(ranges, epsilonSeconds = 0.25) {
3079
+ const valid2 = ranges.filter((r) => Number.isFinite(r.start_s) && Number.isFinite(r.end_s) && r.end_s > r.start_s).map((r) => ({
3080
+ start_s: roundVideoSeconds(r.start_s),
3081
+ end_s: roundVideoSeconds(r.end_s)
3082
+ })).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);
3083
+ if (valid2.length === 0) return [];
3084
+ const merged = [{ ...valid2[0] }];
3085
+ for (let i = 1; i < valid2.length; i += 1) {
3086
+ const cur = valid2[i];
3087
+ const last = merged[merged.length - 1];
3088
+ if (cur.start_s <= last.end_s + epsilonSeconds) {
3089
+ last.end_s = roundVideoSeconds(Math.max(last.end_s, cur.end_s));
3090
+ } else {
3091
+ merged.push({ ...cur });
3092
+ }
3093
+ }
3094
+ return merged;
3095
+ }
3096
+ function sumVideoRangesSeconds(ranges, durationSeconds = 0) {
3097
+ const merged = mergeVideoRanges(ranges);
3098
+ const raw = merged.reduce((acc, r) => acc + Math.max(0, r.end_s - r.start_s), 0);
3099
+ const clamped = durationSeconds > 0 ? Math.min(raw, durationSeconds) : raw;
3100
+ return roundVideoSeconds(clamped);
3101
+ }
3102
+
3103
+ // ../tracking-core/src/events/video.ts
3104
+ var videoIdSchema = z16.string().check(z16.minLength(1), z16.maxLength(VIDEO_ID_MAX_LENGTH), z16.regex(VIDEO_ID_PATTERN));
3105
+ var videoVariantSchema = z16.enum(["16x9", "9x16"]);
3106
+ var videoProgressTriggerReasonSchema = z16.enum([
3107
+ "checkpoint",
3108
+ "goal",
3109
+ "pause",
3110
+ "seek",
3111
+ "ended",
3112
+ "page_exit"
3113
+ ]);
3114
+ var videoRangeSchema = z16.strictObject({
3115
+ start_s: z16.number().check(z16.gte(0)),
3116
+ end_s: z16.number().check(z16.gte(0))
3117
+ });
3118
+ var videoPlayMetadataSchema = z16.strictObject({
3119
+ video_id: videoIdSchema,
3120
+ variant: videoVariantSchema,
3121
+ current_s: z16.number().check(z16.gte(0)),
3122
+ duration_s: z16.number().check(z16.gte(0)),
3123
+ play_sequence: z16.int().check(z16.gte(1)),
3124
+ page: z16.strictObject({
3125
+ path: z16.string()
3126
+ })
3127
+ });
3128
+ var videoPlayConfigSchema = z16.strictObject({});
3129
+ var videoProgressMetadataSchema = z16.strictObject({
3130
+ video_id: videoIdSchema,
3131
+ variant: videoVariantSchema,
3132
+ trigger: videoProgressTriggerReasonSchema,
3133
+ current_s: z16.number().check(z16.gte(0)),
3134
+ furthest_s: z16.number().check(z16.gte(0)),
3135
+ watched_s: z16.number().check(z16.gte(0)),
3136
+ duration_s: z16.number().check(z16.gte(0)),
3137
+ max_percent: z16.int().check(z16.gte(0), z16.lte(100)),
3138
+ watched_percent: z16.int().check(z16.gte(0), z16.lte(100)),
3139
+ play_count: z16.int().check(z16.gte(1)),
3140
+ seek_count: z16.int().check(z16.gte(0)),
3141
+ ranges: z16.array(videoRangeSchema),
3142
+ page: z16.strictObject({
3143
+ path: z16.string()
3144
+ })
3145
+ });
3146
+ var videoProgressConfigSchema = z16.strictObject({});
3147
+
2912
3148
  // ../tracking-core/src/events/registry.ts
2913
3149
  var EVENT_REGISTRY = {
2914
3150
  // --- automatic triggers ---
@@ -2975,6 +3211,18 @@ var EVENT_REGISTRY = {
2975
3211
  metadataSchema: siteLinkArrivalMetadataSchema,
2976
3212
  configSchema: siteLinkArrivalConfigSchema
2977
3213
  },
3214
+ video_play: {
3215
+ kind: "automatic",
3216
+ semantics: EVENT_SEMANTICS.video_play,
3217
+ metadataSchema: videoPlayMetadataSchema,
3218
+ configSchema: videoPlayConfigSchema
3219
+ },
3220
+ video_progress: {
3221
+ kind: "automatic",
3222
+ semantics: EVENT_SEMANTICS.video_progress,
3223
+ metadataSchema: videoProgressMetadataSchema,
3224
+ configSchema: videoProgressConfigSchema
3225
+ },
2978
3226
  // --- manual triggers ---
2979
3227
  form_submit: {
2980
3228
  kind: "manual",
@@ -2988,6 +3236,12 @@ var EVENT_REGISTRY = {
2988
3236
  metadataSchema: formStepMetadataSchema,
2989
3237
  configSchema: formStepConfigSchema
2990
3238
  },
3239
+ form_details: {
3240
+ kind: "manual",
3241
+ semantics: EVENT_SEMANTICS.form_details,
3242
+ metadataSchema: formDetailsMetadataSchema,
3243
+ configSchema: formDetailsConfigSchema
3244
+ },
2991
3245
  phone_click: {
2992
3246
  kind: "manual",
2993
3247
  semantics: EVENT_SEMANTICS.phone_click,
@@ -3026,6 +3280,7 @@ function salvageFormSubmit(metadata) {
3026
3280
  ...valid("contract_version", form.contract_version),
3027
3281
  ...valid("contract_hash", form.contract_hash),
3028
3282
  ...valid("extra", form.extra),
3283
+ ...valid("submission_id", form.submission_id),
3029
3284
  contract_violations: [...reported, INVALID_SHAPE].slice(-MAX_CONTRACT_VIOLATIONS)
3030
3285
  },
3031
3286
  page: { path: typeof page.path === "string" ? page.path : currentPath2() }
@@ -3068,6 +3323,17 @@ function currentPath2() {
3068
3323
 
3069
3324
  // ../tracking-core/src/ingest-typed.ts
3070
3325
  var clientSettings = /* @__PURE__ */ new WeakMap();
3326
+ var latestTrackingConfig;
3327
+ var latestRawClient = null;
3328
+ function setDefaultTrackingConfig(ref) {
3329
+ if (ref) latestTrackingConfig = ref;
3330
+ }
3331
+ function getActiveTrackingEnv() {
3332
+ return {
3333
+ rawClient: latestRawClient,
3334
+ trackingConfig: latestTrackingConfig
3335
+ };
3336
+ }
3071
3337
  function typedClientSettings(client) {
3072
3338
  return clientSettings.get(client) ?? {};
3073
3339
  }
@@ -3098,7 +3364,9 @@ function createTypedClient(raw, registry, options = {}) {
3098
3364
  getSessionId: raw.getSessionId.bind(raw),
3099
3365
  getVisitorId: raw.getVisitorId.bind(raw)
3100
3366
  };
3101
- clientSettings.set(client, options);
3367
+ latestRawClient = raw;
3368
+ if (options.trackingConfig) latestTrackingConfig = options.trackingConfig;
3369
+ clientSettings.set(client, { ...options, rawClient: raw });
3102
3370
  return client;
3103
3371
  }
3104
3372
 
@@ -3306,7 +3574,7 @@ function attachScrollDepth(client, config) {
3306
3574
  }
3307
3575
  const thresholds = new Set(config.thresholds);
3308
3576
  let firedForPath = /* @__PURE__ */ new Set();
3309
- let currentPath3 = window.location.pathname;
3577
+ let currentPath4 = window.location.pathname;
3310
3578
  let rafId = null;
3311
3579
  const gate = createBaselineGate();
3312
3580
  function checkThresholds() {
@@ -3322,7 +3590,7 @@ function attachScrollDepth(client, config) {
3322
3590
  eventType: "scroll_depth",
3323
3591
  metadata: {
3324
3592
  depth_percent: threshold,
3325
- page: { path: currentPath3 }
3593
+ page: { path: currentPath4 }
3326
3594
  },
3327
3595
  pageUrl: window.location.href,
3328
3596
  occurredAt: null
@@ -3339,8 +3607,8 @@ function attachScrollDepth(client, config) {
3339
3607
  }
3340
3608
  function resetIfPathChanged() {
3341
3609
  const newPath = window.location.pathname;
3342
- if (newPath === currentPath3) return;
3343
- currentPath3 = newPath;
3610
+ if (newPath === currentPath4) return;
3611
+ currentPath4 = newPath;
3344
3612
  firedForPath = /* @__PURE__ */ new Set();
3345
3613
  gate.rebaseline();
3346
3614
  }
@@ -3403,13 +3671,13 @@ function attachMultiPageSession(client, config) {
3403
3671
  return storage.getItem(FIRED_KEY) === "1";
3404
3672
  }
3405
3673
  function check2() {
3406
- const currentPath3 = window.location.pathname;
3407
- if (currentPath3 === lastCheckedPath) return;
3408
- lastCheckedPath = currentPath3;
3674
+ const currentPath4 = window.location.pathname;
3675
+ if (currentPath4 === lastCheckedPath) return;
3676
+ lastCheckedPath = currentPath4;
3409
3677
  resetIfSessionChanged();
3410
3678
  if (hasFired()) return;
3411
3679
  const paths = getDistinctPaths();
3412
- paths.add(currentPath3);
3680
+ paths.add(currentPath4);
3413
3681
  saveDistinctPaths(paths);
3414
3682
  if (paths.size >= pageThreshold) {
3415
3683
  storage.setItem(FIRED_KEY, "1");
@@ -3454,7 +3722,7 @@ function attachFormStart(client, config) {
3454
3722
  }
3455
3723
  const selector = config.selector ?? "form";
3456
3724
  let firedForms = /* @__PURE__ */ new Set();
3457
- let currentPath3 = window.location.pathname;
3725
+ let currentPath4 = window.location.pathname;
3458
3726
  let pendingTap = null;
3459
3727
  function getFormKey(form) {
3460
3728
  if (form.id) return `id:${form.id}`;
@@ -3505,8 +3773,8 @@ function attachFormStart(client, config) {
3505
3773
  }
3506
3774
  function resetIfPathChanged() {
3507
3775
  const newPath = window.location.pathname;
3508
- if (newPath === currentPath3) return;
3509
- currentPath3 = newPath;
3776
+ if (newPath === currentPath4) return;
3777
+ currentPath4 = newPath;
3510
3778
  firedForms = /* @__PURE__ */ new Set();
3511
3779
  }
3512
3780
  const originalPushState = history.pushState.bind(history);
@@ -3547,7 +3815,7 @@ function attachPageExit(client) {
3547
3815
  return () => {
3548
3816
  };
3549
3817
  }
3550
- let currentPath3 = window.location.pathname;
3818
+ let currentPath4 = window.location.pathname;
3551
3819
  let activeSince = document.visibilityState === "visible" ? Date.now() : null;
3552
3820
  let accumulatedMs = 0;
3553
3821
  let maxScrollPercent = null;
@@ -3597,9 +3865,9 @@ function attachPageExit(client) {
3597
3865
  }
3598
3866
  function onNavigate() {
3599
3867
  const newPath = window.location.pathname;
3600
- if (newPath === currentPath3) return;
3601
- emitSegment(currentPath3, false);
3602
- currentPath3 = newPath;
3868
+ if (newPath === currentPath4) return;
3869
+ emitSegment(currentPath4, false);
3870
+ currentPath4 = newPath;
3603
3871
  maxScrollPercent = null;
3604
3872
  gate.rebaseline();
3605
3873
  accumulatedMs = 0;
@@ -3607,15 +3875,18 @@ function attachPageExit(client) {
3607
3875
  }
3608
3876
  function onVisibilityChange() {
3609
3877
  if (document.visibilityState === "hidden") {
3610
- emitSegment(currentPath3, true);
3878
+ emitSegment(currentPath4, true);
3611
3879
  } else {
3612
3880
  activeSince = Date.now();
3613
3881
  }
3614
3882
  }
3615
3883
  function onPageHide() {
3616
- emitSegment(currentPath3, true);
3884
+ emitSegment(currentPath4, true);
3617
3885
  }
3618
3886
  const unsubscribeNav = onHistoryChange(onNavigate);
3887
+ const unsubscribeBeforeUnload = client.onBeforeUnload?.(() => {
3888
+ emitSegment(currentPath4, false);
3889
+ });
3619
3890
  window.addEventListener("scroll", onScroll, { passive: true });
3620
3891
  document.addEventListener("visibilitychange", onVisibilityChange);
3621
3892
  window.addEventListener("pagehide", onPageHide);
@@ -3624,6 +3895,7 @@ function attachPageExit(client) {
3624
3895
  if (rafId !== null) cancelAnimationFrame(rafId);
3625
3896
  gate.cancel();
3626
3897
  unsubscribeNav();
3898
+ unsubscribeBeforeUnload?.();
3627
3899
  window.removeEventListener("scroll", onScroll);
3628
3900
  document.removeEventListener("visibilitychange", onVisibilityChange);
3629
3901
  window.removeEventListener("pagehide", onPageHide);
@@ -3793,7 +4065,7 @@ function attachPhoneClickCapture(client, config) {
3793
4065
  }
3794
4066
 
3795
4067
  // ../tracking-core/src/lead-forms/lead-forms.gen.ts
3796
- import * as z15 from "zod/mini";
4068
+ import * as z17 from "zod/mini";
3797
4069
  var LEAD_FORM_REGISTRY_VERSION = 2;
3798
4070
  var LEAD_FORM_ROLES = [
3799
4071
  "given_name",
@@ -3819,28 +4091,28 @@ var LEAD_FORM_ROLES = [
3819
4091
  "attribution"
3820
4092
  ];
3821
4093
  var leadFormPrimitiveSchemas = /* @__PURE__ */ (() => ({
3822
- string: z15.string().check(z15.maxLength(120)),
3823
- text: z15.string().check(z15.maxLength(2e3)),
3824
- enum: z15.string().check(z15.regex(/^[a-z0-9-]{1,48}$/)),
3825
- multi_enum: z15.array(z15.string().check(z15.regex(/^[a-z0-9-]{1,48}$/))).check(z15.maxLength(10)),
3826
- bool: z15.boolean(),
3827
- date: z15.string().check(z15.regex(/^\d{4}-\d{2}-\d{2}$/)),
3828
- time_window: z15.strictObject({
3829
- start: z15.string().check(z15.regex(/^([01]\d|2[0-3]):[0-5]\d$/)),
3830
- end: z15.nullable(z15.string().check(z15.regex(/^([01]\d|2[0-3]):[0-5]\d$/))),
3831
- label: z15.optional(z15.string().check(z15.maxLength(60)))
4094
+ string: z17.string().check(z17.maxLength(120)),
4095
+ text: z17.string().check(z17.maxLength(2e3)),
4096
+ enum: z17.string().check(z17.regex(/^[a-z0-9-]{1,48}$/)),
4097
+ multi_enum: z17.array(z17.string().check(z17.regex(/^[a-z0-9-]{1,48}$/))).check(z17.maxLength(10)),
4098
+ bool: z17.boolean(),
4099
+ date: z17.string().check(z17.regex(/^\d{4}-\d{2}-\d{2}$/)),
4100
+ time_window: z17.strictObject({
4101
+ start: z17.string().check(z17.regex(/^([01]\d|2[0-3]):[0-5]\d$/)),
4102
+ end: z17.nullable(z17.string().check(z17.regex(/^([01]\d|2[0-3]):[0-5]\d$/))),
4103
+ label: z17.optional(z17.string().check(z17.maxLength(60)))
3832
4104
  }),
3833
- phone: z15.string().check(z15.regex(/^\+[1-9]\d{6,14}$/)),
3834
- email: z15.string().check(z15.maxLength(320), z15.regex(/^[^\s@]+@[^\s@]+$/)),
3835
- address: z15.strictObject({
3836
- line1: z15.string().check(z15.maxLength(120)),
3837
- line2: z15.optional(z15.string().check(z15.maxLength(120))),
3838
- city: z15.optional(z15.string().check(z15.maxLength(120))),
3839
- region: z15.optional(z15.string().check(z15.maxLength(120))),
3840
- postal_code: z15.optional(z15.string().check(z15.maxLength(120))),
3841
- country: z15.optional(z15.string().check(z15.maxLength(120)))
4105
+ phone: z17.string().check(z17.regex(/^\+[1-9]\d{6,14}$/)),
4106
+ email: z17.string().check(z17.maxLength(320), z17.regex(/^[^\s@]+@[^\s@]+$/)),
4107
+ address: z17.strictObject({
4108
+ line1: z17.string().check(z17.maxLength(120)),
4109
+ line2: z17.optional(z17.string().check(z17.maxLength(120))),
4110
+ city: z17.optional(z17.string().check(z17.maxLength(120))),
4111
+ region: z17.optional(z17.string().check(z17.maxLength(120))),
4112
+ postal_code: z17.optional(z17.string().check(z17.maxLength(120))),
4113
+ country: z17.optional(z17.string().check(z17.maxLength(120)))
3842
4114
  }),
3843
- number: z15.number()
4115
+ number: z17.number()
3844
4116
  }))();
3845
4117
  var LEAD_FORM_ROLE_SPECS = {
3846
4118
  given_name: { primitive: "string", smsLabel: "", contactIdentity: true },
@@ -3922,34 +4194,70 @@ function createLeadFormSubmit(tracking, contracts, options = {}) {
3922
4194
  contracts,
3923
4195
  settings
3924
4196
  );
4197
+ const submission = { submissionId: uuidV4(), formId: input.formId, kind: input.kind };
3925
4198
  const metadata = {
3926
4199
  form: {
3927
4200
  id: input.formId,
3928
4201
  action: input.action ?? null,
3929
- fields: fields.map(([role, value]) => ({
3930
- name: role,
3931
- type: INPUT_TYPES[primitiveOf(role) ?? "string"],
3932
- label: labelOf(role),
3933
- value,
3934
- role
3935
- })),
4202
+ fields: fields.map(fieldEntry),
3936
4203
  kind: input.kind,
3937
4204
  contract_version: LEAD_FORM_REGISTRY_VERSION,
3938
4205
  ...hash ? { contract_hash: hash } : {},
3939
4206
  ...input.extra?.length ? { extra: input.extra } : {},
3940
- ...violations.length > 0 ? { contract_violations: violations.slice(0, MAX_CONTRACT_VIOLATIONS) } : {}
4207
+ ...violations.length > 0 ? { contract_violations: violations.slice(0, MAX_CONTRACT_VIOLATIONS) } : {},
4208
+ submission_id: submission.submissionId
3941
4209
  },
3942
- page: { path: typeof window === "undefined" ? "/" : window.location.pathname }
4210
+ page: { path: currentPath3() }
3943
4211
  };
3944
4212
  tracking.trackEvent("form_submit", metadata);
3945
- return metadata;
4213
+ return { ...metadata, submission };
4214
+ },
4215
+ addDetails(submission, input) {
4216
+ const fields = presentFields(input.fields);
4217
+ const extra = (input.extra ?? []).filter((item) => !isBlank(item.value));
4218
+ if (fields.length === 0 && extra.length === 0) return { sent: false, reason: "no_fields" };
4219
+ const { violations, hash } = judgeLeadForm(
4220
+ submission.kind,
4221
+ submission.formId,
4222
+ fields,
4223
+ contracts,
4224
+ settings,
4225
+ { partial: true }
4226
+ );
4227
+ const metadata = {
4228
+ form: {
4229
+ id: submission.formId,
4230
+ submission_id: submission.submissionId,
4231
+ kind: submission.kind,
4232
+ fields: fields.map(fieldEntry),
4233
+ contract_version: LEAD_FORM_REGISTRY_VERSION,
4234
+ ...hash ? { contract_hash: hash } : {},
4235
+ ...extra.length > 0 ? { extra } : {},
4236
+ ...violations.length > 0 ? { contract_violations: violations.slice(0, MAX_CONTRACT_VIOLATIONS) } : {}
4237
+ },
4238
+ page: { path: currentPath3() }
4239
+ };
4240
+ tracking.trackEvent("form_details", metadata);
4241
+ return { sent: true };
3946
4242
  }
3947
4243
  };
3948
4244
  }
3949
- function judgeLeadForm(kind, formId, fields, contracts, settings) {
4245
+ function fieldEntry([role, value]) {
4246
+ return {
4247
+ name: role,
4248
+ type: INPUT_TYPES[primitiveOf(role) ?? "string"],
4249
+ label: labelOf(role),
4250
+ value,
4251
+ role
4252
+ };
4253
+ }
4254
+ function currentPath3() {
4255
+ return typeof window === "undefined" ? "/" : window.location.pathname;
4256
+ }
4257
+ function judgeLeadForm(kind, formId, fields, contracts, settings, { partial = false } = {}) {
3950
4258
  const generated = contracts?.contracts[kind];
3951
4259
  const live = liveContract(settings.trackingConfig, kind);
3952
- const violations = check(live === void 0 ? generated : fromLive(live), fields);
4260
+ const violations = check(live === void 0 ? generated : fromLive(live), fields, partial);
3953
4261
  const liveHash = live === void 0 ? void 0 : live?.contract_hash ?? null;
3954
4262
  if (contracts && (liveHash !== void 0 && (generated?.hash ?? null) !== liveHash || contracts.registryVersion !== LEAD_FORM_REGISTRY_VERSION)) {
3955
4263
  violations.push({ role: null, rule: "contract_behind" });
@@ -3995,7 +4303,7 @@ function normalize(role, value) {
3995
4303
  function isBlank(value) {
3996
4304
  return value === void 0 || value === null || typeof value === "string" && value.trim() === "" || Array.isArray(value) && value.length === 0;
3997
4305
  }
3998
- function check(contract, fields) {
4306
+ function check(contract, fields, partial) {
3999
4307
  const answered = new Map(fields);
4000
4308
  const violations = [];
4001
4309
  for (const [role, value] of fields) {
@@ -4013,6 +4321,7 @@ function check(contract, fields) {
4013
4321
  violations.push({ role, rule: "unknown_option" });
4014
4322
  }
4015
4323
  }
4324
+ if (partial) return violations;
4016
4325
  for (const { role, requiredWhen } of contract?.requires ?? []) {
4017
4326
  const owing = requiredWhen === null || optionKeys(answered.get(requiredWhen.role)).some((key) => requiredWhen.anyOf.includes(key));
4018
4327
  if (owing && !answered.has(role)) violations.push({ role, rule: "missing_required" });
@@ -4375,7 +4684,7 @@ function GoogleAdsTracking(props) {
4375
4684
  import { createContext as createContext2, useContext as useContext2, useEffect as useEffect5, useMemo as useMemo4 } from "react";
4376
4685
 
4377
4686
  // package.json
4378
- var version = "1.17.0";
4687
+ var version = "1.19.0";
4379
4688
 
4380
4689
  // ../tracking-core/src/phone-react.tsx
4381
4690
  import {
@@ -4493,6 +4802,7 @@ function createTracking(options) {
4493
4802
  trackingConfig
4494
4803
  } = options;
4495
4804
  registerCapability("base_tracking");
4805
+ setDefaultTrackingConfig(trackingConfig);
4496
4806
  if (trackingConfig) registerCapability("conversion_goals_auto");
4497
4807
  if (!apiKey || !endpoint) {
4498
4808
  if (apiKey || endpoint) {
@@ -4760,14 +5070,1429 @@ function useBookingSubmit(bookings, options = {}) {
4760
5070
  const status = submitting ? "submitting" : outcome?.status ?? "idle";
4761
5071
  return { status, outcome, booked, submit, updateBooking, reset };
4762
5072
  }
5073
+
5074
+ // ../tracking-core/src/video-react.tsx
5075
+ import {
5076
+ useCallback as useCallback4,
5077
+ useEffect as useEffect6,
5078
+ useLayoutEffect,
5079
+ useMemo as useMemo6,
5080
+ useRef as useRef2,
5081
+ useState as useState5,
5082
+ useSyncExternalStore as useSyncExternalStore2
5083
+ } from "react";
5084
+ import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
5085
+ var DEFAULT_VIDEO_CHECKPOINTS = [25, 50, 75, 90, 100];
5086
+ var streamManifestUrl = (host, uid) => `https://${host}/${uid}/manifest/video.m3u8`;
5087
+ var streamPosterUrl = (host, uid, time = 4) => `https://${host}/${uid}/thumbnails/thumbnail.jpg?time=${time}s`;
5088
+ var TV_SKIN_STYLE_ATTR = "data-aranova-tv-skin";
5089
+ var TV_SKIN_CSS = `
5090
+ .tv-skin {
5091
+ container-type: inline-size;
5092
+ position: relative;
5093
+ overflow: hidden;
5094
+ border-radius: 18px;
5095
+ border: 1px solid rgba(255, 255, 255, 0.12);
5096
+ background: #0a0e1a;
5097
+ box-shadow: 0 18px 48px rgba(0, 0, 0, 0.32);
5098
+ }
5099
+ .tv-skin-desktop {
5100
+ display: none;
5101
+ aspect-ratio: 16 / 9;
5102
+ width: 100%;
5103
+ }
5104
+ .tv-skin-mobile {
5105
+ display: block;
5106
+ margin-left: auto;
5107
+ margin-right: auto;
5108
+ aspect-ratio: 9 / 16;
5109
+ width: 100%;
5110
+ max-width: 420px;
5111
+ }
5112
+ @media (min-width: 768px) {
5113
+ .tv-skin-desktop {
5114
+ display: block;
5115
+ }
5116
+ .tv-skin-mobile {
5117
+ display: none;
5118
+ }
5119
+ }
5120
+ .tv-skin media-controller {
5121
+ display: block;
5122
+ width: 100%;
5123
+ height: 100%;
5124
+ background: #000;
5125
+ --media-primary-color: #ffffff;
5126
+ --media-text-color: rgba(255, 255, 255, 0.82);
5127
+ --media-icon-color: #ffffff;
5128
+ --media-secondary-color: transparent;
5129
+ --media-control-background: transparent;
5130
+ --media-control-hover-background: transparent;
5131
+ --media-control-padding: 0;
5132
+ --media-control-height: 40px;
5133
+ --media-tooltip-display: none;
5134
+ --media-font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
5135
+ --media-font-size: 11.5px;
5136
+ --media-focus-box-shadow: 0 0 0 2px #74a0ff;
5137
+ --media-button-icon-width: 20px;
5138
+ --media-button-icon-height: 20px;
5139
+ --media-range-padding: 0;
5140
+ --media-range-bar-color: #ffffff;
5141
+ --media-range-track-background: rgba(255, 255, 255, 0.25);
5142
+ --media-range-track-border-radius: 3px;
5143
+ --media-time-range-buffered-color: transparent;
5144
+ --media-time-range-hover-display: none;
5145
+ --media-preview-time-background: transparent;
5146
+ --media-range-thumb-background: #ffffff;
5147
+ --media-range-thumb-width: 12px;
5148
+ --media-range-thumb-height: 12px;
5149
+ --media-range-thumb-opacity: 0;
5150
+ }
5151
+ .tv-skin media-controller::part(vertical-layer),
5152
+ .tv-skin media-controller::part(centered-layer) {
5153
+ background: transparent;
5154
+ }
5155
+ .tv-chrome {
5156
+ position: absolute;
5157
+ inset: auto 0 0 0;
5158
+ padding-top: 56px;
5159
+ background: linear-gradient(to top, rgba(0, 0, 0, 0.62), rgba(0, 0, 0, 0));
5160
+ transition: opacity 0.25s ease;
5161
+ }
5162
+ .tv-skin media-controller[userinactive]:not([mediapaused]) .tv-chrome {
5163
+ opacity: 0;
5164
+ pointer-events: none;
5165
+ }
5166
+ .tv-row {
5167
+ display: flex;
5168
+ align-items: center;
5169
+ gap: 2px;
5170
+ padding: 0 4px;
5171
+ }
5172
+ .tv-spacer {
5173
+ flex: 1 1 auto;
5174
+ }
5175
+ .tv-ctl,
5176
+ .tv-btn {
5177
+ display: inline-flex;
5178
+ align-items: center;
5179
+ justify-content: center;
5180
+ width: 40px;
5181
+ height: 40px;
5182
+ padding: 0;
5183
+ border: 0;
5184
+ border-radius: 0;
5185
+ background: transparent;
5186
+ box-shadow: none;
5187
+ color: rgba(255, 255, 255, 0.92);
5188
+ cursor: pointer;
5189
+ transition: color 0.15s;
5190
+ }
5191
+ .tv-ctl:hover,
5192
+ .tv-btn:hover {
5193
+ background: transparent;
5194
+ color: #ffffff;
5195
+ }
5196
+ .tv-ctl:focus-visible,
5197
+ .tv-btn:focus-visible,
5198
+ .tv-menu-item:focus-visible {
5199
+ outline: 2px solid #74a0ff;
5200
+ outline-offset: -4px;
5201
+ box-shadow: none;
5202
+ }
5203
+ .tv-time {
5204
+ width: auto;
5205
+ padding: 0 4px 0 6px;
5206
+ background: transparent;
5207
+ letter-spacing: 0.02em;
5208
+ white-space: nowrap;
5209
+ --media-control-background: transparent;
5210
+ }
5211
+ .tv-time-cur {
5212
+ display: none;
5213
+ }
5214
+ .tv-seek {
5215
+ display: block;
5216
+ width: 100%;
5217
+ height: 16px;
5218
+ margin: 0;
5219
+ padding: 0;
5220
+ background: transparent;
5221
+ box-shadow: none;
5222
+ border: 0;
5223
+ --media-range-track-height: 3px;
5224
+ --media-control-background: transparent;
5225
+ --media-control-hover-background: transparent;
5226
+ }
5227
+ .tv-seek:hover,
5228
+ .tv-seek:focus-visible {
5229
+ --media-range-track-height: 6px;
5230
+ --media-range-thumb-opacity: 1;
5231
+ }
5232
+ .tv-vol {
5233
+ display: flex;
5234
+ align-items: center;
5235
+ }
5236
+ .tv-volrange {
5237
+ width: 64px;
5238
+ height: 40px;
5239
+ margin-right: 4px;
5240
+ padding: 0;
5241
+ opacity: 0;
5242
+ pointer-events: none;
5243
+ transform: scaleX(0.75);
5244
+ transform-origin: right center;
5245
+ background: transparent;
5246
+ transition: transform 0.2s ease, opacity 0.2s ease;
5247
+ --media-control-background: transparent;
5248
+ --media-range-track-height: 3px;
5249
+ }
5250
+ .tv-vol:hover .tv-volrange,
5251
+ .tv-vol:focus-within .tv-volrange {
5252
+ opacity: 1;
5253
+ pointer-events: auto;
5254
+ transform: scaleX(1);
5255
+ }
5256
+ @media (hover: none) {
5257
+ .tv-volrange {
5258
+ display: none;
5259
+ }
5260
+ }
5261
+ @container (max-width: 479px) {
5262
+ .tv-vol,
5263
+ .tv-time-full {
5264
+ display: none;
5265
+ }
5266
+ .tv-time-cur {
5267
+ display: inline-flex;
5268
+ }
5269
+ }
5270
+ @container (max-width: 767px) {
5271
+ .tv-theater {
5272
+ display: none;
5273
+ }
5274
+ }
5275
+ .tv-menu-wrap {
5276
+ position: relative;
5277
+ display: flex;
5278
+ align-items: center;
5279
+ }
5280
+ .tv-menu {
5281
+ position: absolute;
5282
+ right: 0;
5283
+ bottom: calc(100% + 8px);
5284
+ z-index: 5;
5285
+ width: 136px;
5286
+ max-height: 232px;
5287
+ margin: 0;
5288
+ padding: 4px 0;
5289
+ overflow-y: auto;
5290
+ list-style: none;
5291
+ border: 1px solid rgba(255, 255, 255, 0.2);
5292
+ border-radius: 10px;
5293
+ background: linear-gradient(rgba(255, 255, 255, 0.12), rgba(255, 255, 255, 0.12)), rgba(10, 14, 26, 0.55);
5294
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.12), 0 12px 32px rgba(0, 0, 0, 0.35);
5295
+ }
5296
+ @supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
5297
+ .tv-menu {
5298
+ -webkit-backdrop-filter: blur(16px) saturate(140%);
5299
+ backdrop-filter: blur(16px) saturate(140%);
5300
+ }
5301
+ }
5302
+ .tv-menu-item {
5303
+ position: relative;
5304
+ display: flex;
5305
+ width: 100%;
5306
+ height: 32px;
5307
+ align-items: center;
5308
+ padding: 0 12px 0 24px;
5309
+ border: 0;
5310
+ background: transparent;
5311
+ color: rgba(255, 255, 255, 0.75);
5312
+ font-family: var(--font-sans, system-ui, -apple-system, sans-serif);
5313
+ font-size: 13px;
5314
+ font-weight: 500;
5315
+ text-align: left;
5316
+ cursor: pointer;
5317
+ }
5318
+ .tv-menu-item:hover,
5319
+ .tv-menu-item:focus-visible {
5320
+ background: rgba(255, 255, 255, 0.1);
5321
+ color: #ffffff;
5322
+ outline: none;
5323
+ }
5324
+ .tv-menu-item[aria-checked='true'] {
5325
+ color: #ffffff;
5326
+ font-weight: 600;
5327
+ }
5328
+ .tv-menu-item[aria-checked='true']::before {
5329
+ content: '';
5330
+ position: absolute;
5331
+ left: 12px;
5332
+ width: 5px;
5333
+ height: 5px;
5334
+ border-radius: 50%;
5335
+ background: #ffffff;
5336
+ }
5337
+ .tv-menu-sub {
5338
+ margin-left: 4px;
5339
+ font-size: 11.5px;
5340
+ font-weight: 400;
5341
+ color: rgba(255, 255, 255, 0.55);
5342
+ }
5343
+ .tv-menu-note {
5344
+ height: 20px;
5345
+ padding: 0 12px 0 24px;
5346
+ color: rgba(255, 255, 255, 0.55);
5347
+ font-family: var(--font-sans, system-ui, -apple-system, sans-serif);
5348
+ font-size: 11.5px;
5349
+ line-height: 20px;
5350
+ }
5351
+ .tv-poster-overlay {
5352
+ position: absolute;
5353
+ inset: 0;
5354
+ }
5355
+ .tv-poster-img {
5356
+ width: 100%;
5357
+ height: 100%;
5358
+ object-fit: cover;
5359
+ display: block;
5360
+ }
5361
+ .tv-play-trigger {
5362
+ position: absolute;
5363
+ inset: 0;
5364
+ width: 100%;
5365
+ height: 100%;
5366
+ border: 0;
5367
+ padding: 0;
5368
+ background: transparent;
5369
+ cursor: pointer;
5370
+ display: flex;
5371
+ align-items: center;
5372
+ justify-content: center;
5373
+ transition: filter 0.2s ease;
5374
+ }
5375
+ .tv-play-trigger:hover {
5376
+ filter: brightness(1.1);
5377
+ }
5378
+ .tv-play-trigger:focus-visible {
5379
+ outline: 2px solid #74a0ff;
5380
+ outline-offset: -4px;
5381
+ }
5382
+ .tv-play-pill {
5383
+ display: flex;
5384
+ width: 64px;
5385
+ height: 64px;
5386
+ align-items: center;
5387
+ justify-content: center;
5388
+ border-radius: 9999px;
5389
+ border: 2px solid rgba(255, 255, 255, 0.72);
5390
+ background: rgba(255, 255, 255, 0.15);
5391
+ color: #ffffff;
5392
+ backdrop-filter: blur(2px);
5393
+ -webkit-backdrop-filter: blur(2px);
5394
+ transition: transform 0.3s ease;
5395
+ }
5396
+ .tv-play-trigger:hover .tv-play-pill {
5397
+ transform: scale(1.05);
5398
+ }
5399
+ `;
5400
+ function ensureTvSkinStyles() {
5401
+ if (typeof document === "undefined") return;
5402
+ if (document.querySelector(`style[${TV_SKIN_STYLE_ATTR}]`)) return;
5403
+ const style = document.createElement("style");
5404
+ style.setAttribute(TV_SKIN_STYLE_ATTR, "1");
5405
+ style.textContent = TV_SKIN_CSS;
5406
+ document.head.append(style);
5407
+ }
5408
+ function IconPlay({
5409
+ slot,
5410
+ size = 20,
5411
+ filled = false
5412
+ }) {
5413
+ return /* @__PURE__ */ jsx5(
5414
+ "svg",
5415
+ {
5416
+ slot,
5417
+ width: size,
5418
+ height: size,
5419
+ viewBox: "0 0 24 24",
5420
+ fill: filled ? "currentColor" : "none",
5421
+ stroke: "currentColor",
5422
+ strokeWidth: filled ? 0 : 1.75,
5423
+ strokeLinecap: "round",
5424
+ strokeLinejoin: "round",
5425
+ "aria-hidden": "true",
5426
+ style: filled ? { marginLeft: 3 } : void 0,
5427
+ children: /* @__PURE__ */ jsx5("polygon", { points: "6 3 20 12 6 21 6 3" })
5428
+ }
5429
+ );
5430
+ }
5431
+ function IconPause({ slot }) {
5432
+ return /* @__PURE__ */ jsxs2(
5433
+ "svg",
5434
+ {
5435
+ slot,
5436
+ width: 20,
5437
+ height: 20,
5438
+ viewBox: "0 0 24 24",
5439
+ fill: "none",
5440
+ stroke: "currentColor",
5441
+ strokeWidth: 1.75,
5442
+ strokeLinecap: "round",
5443
+ strokeLinejoin: "round",
5444
+ "aria-hidden": "true",
5445
+ children: [
5446
+ /* @__PURE__ */ jsx5("rect", { x: "14", y: "4", width: "4", height: "16", rx: "1" }),
5447
+ /* @__PURE__ */ jsx5("rect", { x: "6", y: "4", width: "4", height: "16", rx: "1" })
5448
+ ]
5449
+ }
5450
+ );
5451
+ }
5452
+ function IconVolumeHigh({ slot }) {
5453
+ return /* @__PURE__ */ jsxs2(
5454
+ "svg",
5455
+ {
5456
+ slot,
5457
+ width: 20,
5458
+ height: 20,
5459
+ viewBox: "0 0 24 24",
5460
+ fill: "none",
5461
+ stroke: "currentColor",
5462
+ strokeWidth: 1.75,
5463
+ strokeLinecap: "round",
5464
+ strokeLinejoin: "round",
5465
+ "aria-hidden": "true",
5466
+ children: [
5467
+ /* @__PURE__ */ jsx5("polygon", { points: "11 5 6 9 2 9 2 15 6 15 11 19 11 5" }),
5468
+ /* @__PURE__ */ jsx5("path", { d: "M15.54 8.46a5 5 0 0 1 0 7.07" }),
5469
+ /* @__PURE__ */ jsx5("path", { d: "M19.07 4.93a10 10 0 0 1 0 14.14" })
5470
+ ]
5471
+ }
5472
+ );
5473
+ }
5474
+ function IconVolumeLow({ slot }) {
5475
+ return /* @__PURE__ */ jsxs2(
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__ */ jsx5("polygon", { points: "11 5 6 9 2 9 2 15 6 15 11 19 11 5" }),
5490
+ /* @__PURE__ */ jsx5("path", { d: "M15.54 8.46a5 5 0 0 1 0 7.07" })
5491
+ ]
5492
+ }
5493
+ );
5494
+ }
5495
+ function IconVolumeOff({ slot }) {
5496
+ return /* @__PURE__ */ jsxs2(
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__ */ jsx5("polygon", { points: "11 5 6 9 2 9 2 15 6 15 11 19 11 5" }),
5511
+ /* @__PURE__ */ jsx5("line", { x1: "22", y1: "9", x2: "16", y2: "15" }),
5512
+ /* @__PURE__ */ jsx5("line", { x1: "16", y1: "9", x2: "22", y2: "15" })
5513
+ ]
5514
+ }
5515
+ );
5516
+ }
5517
+ function IconSettings() {
5518
+ return /* @__PURE__ */ jsxs2(
5519
+ "svg",
5520
+ {
5521
+ width: 20,
5522
+ height: 20,
5523
+ viewBox: "0 0 24 24",
5524
+ fill: "none",
5525
+ stroke: "currentColor",
5526
+ strokeWidth: 1.75,
5527
+ strokeLinecap: "round",
5528
+ strokeLinejoin: "round",
5529
+ "aria-hidden": "true",
5530
+ children: [
5531
+ /* @__PURE__ */ jsx5("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" }),
5532
+ /* @__PURE__ */ jsx5("circle", { cx: "12", cy: "12", r: "3" })
5533
+ ]
5534
+ }
5535
+ );
5536
+ }
5537
+ function IconTheaterExpand() {
5538
+ return /* @__PURE__ */ jsxs2(
5539
+ "svg",
5540
+ {
5541
+ width: 20,
5542
+ height: 20,
5543
+ viewBox: "0 0 24 24",
5544
+ fill: "none",
5545
+ stroke: "currentColor",
5546
+ strokeWidth: 1.75,
5547
+ strokeLinecap: "round",
5548
+ strokeLinejoin: "round",
5549
+ "aria-hidden": "true",
5550
+ children: [
5551
+ /* @__PURE__ */ jsx5("polyline", { points: "15 3 21 3 21 9" }),
5552
+ /* @__PURE__ */ jsx5("polyline", { points: "9 21 3 21 3 15" }),
5553
+ /* @__PURE__ */ jsx5("line", { x1: "21", y1: "3", x2: "14", y2: "10" }),
5554
+ /* @__PURE__ */ jsx5("line", { x1: "3", y1: "21", x2: "10", y2: "14" })
5555
+ ]
5556
+ }
5557
+ );
5558
+ }
5559
+ function IconTheaterShrink() {
5560
+ return /* @__PURE__ */ jsxs2(
5561
+ "svg",
5562
+ {
5563
+ width: 20,
5564
+ height: 20,
5565
+ viewBox: "0 0 24 24",
5566
+ fill: "none",
5567
+ stroke: "currentColor",
5568
+ strokeWidth: 1.75,
5569
+ strokeLinecap: "round",
5570
+ strokeLinejoin: "round",
5571
+ "aria-hidden": "true",
5572
+ children: [
5573
+ /* @__PURE__ */ jsx5("polyline", { points: "4 14 10 14 10 20" }),
5574
+ /* @__PURE__ */ jsx5("polyline", { points: "20 10 14 10 14 4" }),
5575
+ /* @__PURE__ */ jsx5("line", { x1: "14", y1: "10", x2: "21", y2: "3" }),
5576
+ /* @__PURE__ */ jsx5("line", { x1: "3", y1: "21", x2: "10", y2: "14" })
5577
+ ]
5578
+ }
5579
+ );
5580
+ }
5581
+ function IconFullscreenEnter({ slot }) {
5582
+ return /* @__PURE__ */ jsxs2(
5583
+ "svg",
5584
+ {
5585
+ slot,
5586
+ width: 20,
5587
+ height: 20,
5588
+ viewBox: "0 0 24 24",
5589
+ fill: "none",
5590
+ stroke: "currentColor",
5591
+ strokeWidth: 1.75,
5592
+ strokeLinecap: "round",
5593
+ strokeLinejoin: "round",
5594
+ "aria-hidden": "true",
5595
+ children: [
5596
+ /* @__PURE__ */ jsx5("path", { d: "M8 3H5a2 2 0 0 0-2 2v3" }),
5597
+ /* @__PURE__ */ jsx5("path", { d: "M21 8V5a2 2 0 0 0-2-2h-3" }),
5598
+ /* @__PURE__ */ jsx5("path", { d: "M3 16v3a2 2 0 0 0 2 2h3" }),
5599
+ /* @__PURE__ */ jsx5("path", { d: "M16 21h3a2 2 0 0 0 2-2v-3" })
5600
+ ]
5601
+ }
5602
+ );
5603
+ }
5604
+ function IconFullscreenExit({ slot }) {
5605
+ return /* @__PURE__ */ jsxs2(
5606
+ "svg",
5607
+ {
5608
+ slot,
5609
+ width: 20,
5610
+ height: 20,
5611
+ viewBox: "0 0 24 24",
5612
+ fill: "none",
5613
+ stroke: "currentColor",
5614
+ strokeWidth: 1.75,
5615
+ strokeLinecap: "round",
5616
+ strokeLinejoin: "round",
5617
+ "aria-hidden": "true",
5618
+ children: [
5619
+ /* @__PURE__ */ jsx5("path", { d: "M8 3v3a2 2 0 0 1-2 2H3" }),
5620
+ /* @__PURE__ */ jsx5("path", { d: "M21 8h-3a2 2 0 0 1-2-2V3" }),
5621
+ /* @__PURE__ */ jsx5("path", { d: "M3 16h3a2 2 0 0 1 2 2v3" }),
5622
+ /* @__PURE__ */ jsx5("path", { d: "M16 21v-3a2 2 0 0 1 2-2h3" })
5623
+ ]
5624
+ }
5625
+ );
5626
+ }
5627
+ var EMPTY_GOALS2 = [];
5628
+ function serverVideoSnapshot() {
5629
+ return null;
5630
+ }
5631
+ function serverGoalsSnapshot() {
5632
+ return EMPTY_GOALS2;
5633
+ }
5634
+ function useTrackedVideoConfig(videoKey, trackingConfig, fallbackConfig) {
5635
+ registerCapability("video_tracking");
5636
+ const effectiveRef = trackingConfig ?? getActiveTrackingEnv().trackingConfig;
5637
+ const subscribe = useCallback4(
5638
+ (listener) => effectiveRef ? getTrackingConfigRuntime(effectiveRef).subscribe(listener) : () => {
5639
+ },
5640
+ [effectiveRef]
5641
+ );
5642
+ const live = useSyncExternalStore2(
5643
+ subscribe,
5644
+ () => effectiveRef && videoKey ? getTrackingConfigRuntime(effectiveRef).getVideo(videoKey) : null,
5645
+ serverVideoSnapshot
5646
+ );
5647
+ return live ?? fallbackConfig ?? null;
5648
+ }
5649
+ function useActiveVideoGoals(videoId, trackingConfig) {
5650
+ const effectiveRef = trackingConfig ?? getActiveTrackingEnv().trackingConfig;
5651
+ const subscribe = useCallback4(
5652
+ (listener) => effectiveRef ? getTrackingConfigRuntime(effectiveRef).subscribe(listener) : () => {
5653
+ },
5654
+ [effectiveRef]
5655
+ );
5656
+ const goals = useSyncExternalStore2(
5657
+ subscribe,
5658
+ () => effectiveRef ? getTrackingConfigRuntime(effectiveRef).listGoals() : EMPTY_GOALS2,
5659
+ serverGoalsSnapshot
5660
+ );
5661
+ return useMemo6(() => {
5662
+ const out = [];
5663
+ for (const goal of goals) {
5664
+ if (goal.kind !== "event" || !goal.trigger) continue;
5665
+ if (goal.trigger.event_type !== "video_progress") continue;
5666
+ if (goal.trigger.video_id && goal.trigger.video_id !== videoId) continue;
5667
+ const metric = goal.trigger.metric;
5668
+ const threshold = goal.trigger.threshold;
5669
+ if ((metric === "percent" || metric === "seconds") && typeof threshold === "number") {
5670
+ out.push({ key: goal.key, metric, threshold });
5671
+ }
5672
+ }
5673
+ return out;
5674
+ }, [goals, videoId]);
5675
+ }
5676
+ function renditionsOf(h) {
5677
+ const best = /* @__PURE__ */ new Map();
5678
+ h.levels.forEach((l, index) => {
5679
+ const px = Math.min(l.width || Infinity, l.height || Infinity);
5680
+ if (!Number.isFinite(px)) return;
5681
+ const cur = best.get(px);
5682
+ if (!cur || l.bitrate > cur.bitrate) best.set(px, { height: px, index, bitrate: l.bitrate });
5683
+ });
5684
+ return [...best.values()].sort((a, b) => b.height - a.height).map(({ height, index }) => ({ height, index }));
5685
+ }
5686
+ var MEDIA_CHROME_CDN_URL = "https://cdn.jsdelivr.net/npm/media-chrome@4/+esm";
5687
+ var HLS_JS_CDN_URL = "https://cdn.jsdelivr.net/npm/hls.js@1/dist/hls.min.js";
5688
+ var defaultLibsPromise = null;
5689
+ function loadScriptOnce(src, attr, isModule = false) {
5690
+ if (typeof document === "undefined") return Promise.resolve();
5691
+ const existing = document.querySelector(`script[${attr}]`);
5692
+ if (existing) return Promise.resolve();
5693
+ return new Promise((resolve) => {
5694
+ const s = document.createElement("script");
5695
+ if (isModule) s.type = "module";
5696
+ s.async = true;
5697
+ s.src = src;
5698
+ s.setAttribute(attr, "1");
5699
+ s.addEventListener("load", () => resolve(), { once: true });
5700
+ s.addEventListener("error", () => resolve(), { once: true });
5701
+ document.head.append(s);
5702
+ if (typeof navigator !== "undefined" && /jsdom/i.test(navigator.userAgent)) {
5703
+ setTimeout(resolve, 0);
5704
+ }
5705
+ });
5706
+ }
5707
+ function loadDefaultPlayerLibs() {
5708
+ if (typeof window === "undefined") return Promise.resolve({});
5709
+ if (defaultLibsPromise) return defaultLibsPromise;
5710
+ defaultLibsPromise = Promise.all([
5711
+ typeof customElements !== "undefined" && customElements.get("media-controller") ? Promise.resolve() : loadScriptOnce(MEDIA_CHROME_CDN_URL, "data-aranova-media-chrome", true),
5712
+ window.Hls ? Promise.resolve() : loadScriptOnce(HLS_JS_CDN_URL, "data-aranova-hls-js", false)
5713
+ ]).then(() => ({
5714
+ hls: window.Hls
5715
+ }));
5716
+ return defaultLibsPromise;
5717
+ }
5718
+ function Cut({
5719
+ id,
5720
+ variant,
5721
+ uid,
5722
+ host,
5723
+ title,
5724
+ posterTime,
5725
+ posterUrl,
5726
+ renderPoster,
5727
+ theater,
5728
+ onTheaterChange,
5729
+ autoExpandOnPlay,
5730
+ checkpoints,
5731
+ videoGoals,
5732
+ client,
5733
+ loadPlayerLibs,
5734
+ className,
5735
+ ...cb
5736
+ }) {
5737
+ const root = useRef2(null);
5738
+ const video = useRef2(null);
5739
+ const hls = useRef2(null);
5740
+ const lastTime = useRef2(0);
5741
+ const seekFrom = useRef2(0);
5742
+ const cbRef = useRef2(cb);
5743
+ cbRef.current = cb;
5744
+ const clientRef = useRef2(client);
5745
+ clientRef.current = client;
5746
+ const goalsRef = useRef2(videoGoals);
5747
+ goalsRef.current = videoGoals;
5748
+ const rangesRef = useRef2([]);
5749
+ const activeSegmentStartRef = useRef2(null);
5750
+ const furthestSecondsRef = useRef2(0);
5751
+ const maxPercentRef = useRef2(0);
5752
+ const durationRef = useRef2(0);
5753
+ const playCountRef = useRef2(0);
5754
+ const seekCountRef = useRef2(0);
5755
+ const firedCheckpointsRef = useRef2(/* @__PURE__ */ new Set());
5756
+ const firedGoalsRef = useRef2(/* @__PURE__ */ new Set());
5757
+ const dirtySinceLastEmitRef = useRef2(false);
5758
+ const [started, setStarted] = useState5(false);
5759
+ const [ready, setReady] = useState5(false);
5760
+ const [playing, setPlaying] = useState5(false);
5761
+ const [renditions, setRenditions] = useState5([]);
5762
+ const [selected, setSelected] = useState5("auto");
5763
+ const [live, setLive] = useState5(null);
5764
+ const [menu, setMenu] = useState5(false);
5765
+ const [pending, setPending] = useState5(null);
5766
+ const pendingRef = useRef2(null);
5767
+ const wrap = useRef2(null);
5768
+ const gear = useRef2(null);
5769
+ const selectedRef = useRef2("auto");
5770
+ const pendingTimer = useRef2(null);
5771
+ const liveRef = useRef2(null);
5772
+ const videoId = `${id}-${variant}`;
5773
+ const getCurrentRanges = useCallback4(() => {
5774
+ const v = video.current;
5775
+ const cur = v ? v.currentTime : lastTime.current;
5776
+ const list = [...rangesRef.current];
5777
+ if (activeSegmentStartRef.current !== null && cur > activeSegmentStartRef.current) {
5778
+ list.push({
5779
+ start_s: activeSegmentStartRef.current,
5780
+ end_s: cur
5781
+ });
5782
+ }
5783
+ return mergeVideoRanges(list);
5784
+ }, []);
5785
+ const commitActiveSegment = useCallback4((endTime) => {
5786
+ const end = endTime ?? video.current?.currentTime ?? lastTime.current;
5787
+ if (activeSegmentStartRef.current !== null && end > activeSegmentStartRef.current) {
5788
+ rangesRef.current = mergeVideoRanges([
5789
+ ...rangesRef.current,
5790
+ { start_s: activeSegmentStartRef.current, end_s: end }
5791
+ ]);
5792
+ }
5793
+ activeSegmentStartRef.current = null;
5794
+ }, []);
5795
+ const snap = useCallback4(() => {
5796
+ const v = video.current;
5797
+ const currentTime = roundVideoSeconds(v?.currentTime ?? lastTime.current);
5798
+ const rawDuration = v?.duration;
5799
+ const duration = typeof rawDuration === "number" && Number.isFinite(rawDuration) && rawDuration > 0 ? roundVideoSeconds(rawDuration) : durationRef.current;
5800
+ if (duration > 0) durationRef.current = duration;
5801
+ if (currentTime > furthestSecondsRef.current) {
5802
+ furthestSecondsRef.current = currentTime;
5803
+ }
5804
+ const ranges = getCurrentRanges();
5805
+ const watchedSeconds = sumVideoRangesSeconds(ranges, duration);
5806
+ const watchedPercent = duration > 0 ? Math.min(100, Math.round(watchedSeconds / duration * 100)) : 0;
5807
+ const curPercent = duration > 0 ? Math.min(100, Math.round(furthestSecondsRef.current / duration * 100)) : 0;
5808
+ if (curPercent > maxPercentRef.current) {
5809
+ maxPercentRef.current = curPercent;
5810
+ }
5811
+ return {
5812
+ id,
5813
+ videoId,
5814
+ variant,
5815
+ currentTime,
5816
+ duration,
5817
+ watchedSeconds,
5818
+ watchedPercent,
5819
+ maxPercent: maxPercentRef.current,
5820
+ ranges
5821
+ };
5822
+ }, [getCurrentRanges, id, variant, videoId]);
5823
+ const emitProgress = useCallback4(
5824
+ (trigger, snapshotOverride) => {
5825
+ const activeClient = clientRef.current ?? getActiveTrackingEnv().rawClient;
5826
+ if (!activeClient || playCountRef.current <= 0) return;
5827
+ const s = snapshotOverride ?? snap();
5828
+ if (s.ranges.length === 0 && s.currentTime <= 0 && trigger !== "ended") return;
5829
+ dirtySinceLastEmitRef.current = false;
5830
+ const path = typeof window === "undefined" ? "/" : window.location.pathname;
5831
+ activeClient.trackEvent({
5832
+ eventType: "video_progress",
5833
+ metadata: {
5834
+ video_id: id,
5835
+ variant,
5836
+ trigger,
5837
+ current_s: s.currentTime,
5838
+ furthest_s: roundVideoSeconds(furthestSecondsRef.current),
5839
+ watched_s: s.watchedSeconds,
5840
+ duration_s: s.duration,
5841
+ max_percent: s.maxPercent,
5842
+ watched_percent: s.watchedPercent,
5843
+ play_count: playCountRef.current,
5844
+ seek_count: seekCountRef.current,
5845
+ ranges: s.ranges,
5846
+ page: { path }
5847
+ }
5848
+ });
5849
+ },
5850
+ [id, snap, variant]
5851
+ );
5852
+ const clearPending = () => {
5853
+ if (pendingTimer.current) clearTimeout(pendingTimer.current);
5854
+ pendingTimer.current = null;
5855
+ pendingRef.current = null;
5856
+ setPending(null);
5857
+ };
5858
+ useEffect6(() => {
5859
+ ensureTvSkinStyles();
5860
+ registerCapability("video_tracking");
5861
+ return () => {
5862
+ if (pendingTimer.current) clearTimeout(pendingTimer.current);
5863
+ hls.current?.destroy();
5864
+ hls.current = null;
5865
+ };
5866
+ }, []);
5867
+ useEffect6(() => {
5868
+ if (!started || typeof window === "undefined") return;
5869
+ const flushExitProgress = (useBeacon) => {
5870
+ if (playCountRef.current <= 0 || !dirtySinceLastEmitRef.current) return;
5871
+ commitActiveSegment();
5872
+ emitProgress("page_exit");
5873
+ if (useBeacon) {
5874
+ const activeClient2 = clientRef.current ?? getActiveTrackingEnv().rawClient;
5875
+ activeClient2?.flushBeacon();
5876
+ }
5877
+ };
5878
+ const activeClient = clientRef.current ?? getActiveTrackingEnv().rawClient;
5879
+ const unsubscribeBeforeUnload = activeClient?.onBeforeUnload?.(() => {
5880
+ flushExitProgress(false);
5881
+ });
5882
+ const onVisibilityChange = () => {
5883
+ if (document.visibilityState === "hidden") {
5884
+ flushExitProgress(true);
5885
+ }
5886
+ };
5887
+ const onPageHide = () => {
5888
+ flushExitProgress(true);
5889
+ };
5890
+ document.addEventListener("visibilitychange", onVisibilityChange);
5891
+ window.addEventListener("pagehide", onPageHide);
5892
+ return () => {
5893
+ unsubscribeBeforeUnload?.();
5894
+ document.removeEventListener("visibilitychange", onVisibilityChange);
5895
+ window.removeEventListener("pagehide", onPageHide);
5896
+ };
5897
+ }, [started, commitActiveSegment, emitProgress]);
5898
+ useEffect6(() => {
5899
+ if (!started) return;
5900
+ let was = false;
5901
+ const report = (fullscreen) => {
5902
+ if (fullscreen === was) return;
5903
+ was = fullscreen;
5904
+ cbRef.current.onFullscreenChange?.({ ...snap(), fullscreen });
5905
+ };
5906
+ const onDoc = () => {
5907
+ const el = root.current;
5908
+ const fe = document.fullscreenElement ?? document.webkitFullscreenElement;
5909
+ report(Boolean(el && fe && el.contains(fe)));
5910
+ };
5911
+ const v = video.current;
5912
+ const begin = () => report(true);
5913
+ const end = () => report(false);
5914
+ document.addEventListener("fullscreenchange", onDoc);
5915
+ document.addEventListener("webkitfullscreenchange", onDoc);
5916
+ v?.addEventListener("webkitbeginfullscreen", begin);
5917
+ v?.addEventListener("webkitendfullscreen", end);
5918
+ return () => {
5919
+ document.removeEventListener("fullscreenchange", onDoc);
5920
+ document.removeEventListener("webkitfullscreenchange", onDoc);
5921
+ v?.removeEventListener("webkitbeginfullscreen", begin);
5922
+ v?.removeEventListener("webkitendfullscreen", end);
5923
+ };
5924
+ }, [started, snap]);
5925
+ useEffect6(() => {
5926
+ if (!menu) return;
5927
+ const outside = (e) => {
5928
+ if (wrap.current && !wrap.current.contains(e.target)) setMenu(false);
5929
+ };
5930
+ document.addEventListener("pointerdown", outside);
5931
+ return () => document.removeEventListener("pointerdown", outside);
5932
+ }, [menu]);
5933
+ const closeMenu = (refocus) => {
5934
+ setMenu(false);
5935
+ if (refocus) gear.current?.focus();
5936
+ };
5937
+ const onMenuKey = (e) => {
5938
+ if (e.key === "Escape") {
5939
+ e.preventDefault();
5940
+ e.stopPropagation();
5941
+ closeMenu(true);
5942
+ } else if (e.key === "ArrowDown" || e.key === "ArrowUp") {
5943
+ e.preventDefault();
5944
+ const items = [
5945
+ ...wrap.current?.querySelectorAll('[role="menuitemradio"]') ?? []
5946
+ ];
5947
+ const i = items.indexOf(document.activeElement);
5948
+ items[(i + (e.key === "ArrowDown" ? 1 : items.length - 1)) % items.length]?.focus();
5949
+ }
5950
+ };
5951
+ const emitQuality = (from, to, source) => cbRef.current.onQualityChange?.({ ...snap(), from, to, source });
5952
+ const start = async () => {
5953
+ const v = video.current;
5954
+ if (!v || started) return;
5955
+ setStarted(true);
5956
+ if (variant === "16x9" && autoExpandOnPlay) {
5957
+ onTheaterChange?.(true);
5958
+ }
5959
+ const src = streamManifestUrl(host, uid);
5960
+ try {
5961
+ const loaded = await (loadPlayerLibs ? loadPlayerLibs() : loadDefaultPlayerLibs());
5962
+ setReady(true);
5963
+ const rawHls = (loaded && typeof loaded === "object" && "hls" in loaded ? loaded.hls : null) ?? (typeof window !== "undefined" ? window.Hls : void 0);
5964
+ const HlsLib = rawHls && typeof rawHls === "object" && "default" in rawHls ? rawHls.default : rawHls;
5965
+ if (!HlsLib || !HlsLib.isSupported()) {
5966
+ v.src = src;
5967
+ } else {
5968
+ const h = new HlsLib({ capLevelToPlayerSize: true });
5969
+ hls.current = h;
5970
+ h.on(HlsLib.Events.MANIFEST_PARSED, () => setRenditions(renditionsOf(h)));
5971
+ h.on(HlsLib.Events.LEVEL_SWITCHED, (_e, data) => {
5972
+ const lv = h.levels[data.level];
5973
+ const height = lv ? Math.min(lv.width || Infinity, lv.height || Infinity) : null;
5974
+ const from = liveRef.current;
5975
+ liveRef.current = height;
5976
+ setLive(height);
5977
+ const want = pendingRef.current;
5978
+ if (want !== null && want === height) clearPending();
5979
+ if (selectedRef.current === "auto" && height && from && from !== height) {
5980
+ emitQuality(from, height, "auto");
5981
+ }
5982
+ });
5983
+ h.on(HlsLib.Events.ERROR, () => clearPending());
5984
+ h.loadSource(src);
5985
+ h.attachMedia(v);
5986
+ }
5987
+ } catch {
5988
+ setReady(true);
5989
+ v.src = src;
5990
+ }
5991
+ try {
5992
+ await v.play();
5993
+ } catch {
5994
+ }
5995
+ };
5996
+ const pick = (q) => {
5997
+ const h = hls.current;
5998
+ const from = selectedRef.current;
5999
+ setMenu(false);
6000
+ if (!h || q === from) return;
6001
+ if (q === "auto") h.nextLevel = -1;
6002
+ else {
6003
+ const r = renditions.find((x) => x.height === q);
6004
+ if (r) h.nextLevel = r.index;
6005
+ }
6006
+ selectedRef.current = q;
6007
+ setSelected(q);
6008
+ clearPending();
6009
+ if (typeof q === "number" && q !== liveRef.current) {
6010
+ pendingRef.current = q;
6011
+ setPending(q);
6012
+ pendingTimer.current = setTimeout(clearPending, 4e3);
6013
+ }
6014
+ emitQuality(from, q, "manual");
6015
+ };
6016
+ const qualityLabel = selected === "auto" ? live ? `Auto (${live}p)` : "Auto" : `${selected}p`;
6017
+ const resolvedPosterUrl = posterUrl ?? streamPosterUrl(host, uid, posterTime);
6018
+ const posterWidth = variant === "16x9" ? 1280 : 720;
6019
+ const posterHeight = variant === "16x9" ? 720 : 1280;
6020
+ return /* @__PURE__ */ jsxs2(
6021
+ "div",
6022
+ {
6023
+ ref: root,
6024
+ className: `tv-skin ${variant === "16x9" ? "tv-skin-desktop" : "tv-skin-mobile"} ${className ?? ""}`,
6025
+ "data-video-id": videoId,
6026
+ children: [
6027
+ /* @__PURE__ */ jsxs2("media-controller", { class: "absolute inset-0 block", autohide: menu ? "-1" : "2", children: [
6028
+ /* @__PURE__ */ jsx5(
6029
+ "video",
6030
+ {
6031
+ ref: video,
6032
+ slot: "media",
6033
+ style: { width: "100%", height: "100%", backgroundColor: "#000", objectFit: "contain" },
6034
+ preload: "none",
6035
+ playsInline: true,
6036
+ title,
6037
+ "aria-label": title,
6038
+ "data-video-id": videoId,
6039
+ onPlay: () => {
6040
+ const v = video.current;
6041
+ const cur = roundVideoSeconds(v?.currentTime ?? 0);
6042
+ activeSegmentStartRef.current = cur;
6043
+ playCountRef.current += 1;
6044
+ dirtySinceLastEmitRef.current = true;
6045
+ const s = snap();
6046
+ const activeClient = clientRef.current ?? getActiveTrackingEnv().rawClient;
6047
+ if (activeClient) {
6048
+ const path = typeof window === "undefined" ? "/" : window.location.pathname;
6049
+ activeClient.trackEvent({
6050
+ eventType: "video_play",
6051
+ metadata: {
6052
+ video_id: id,
6053
+ variant,
6054
+ current_s: s.currentTime,
6055
+ duration_s: s.duration,
6056
+ play_sequence: playCountRef.current,
6057
+ page: { path }
6058
+ }
6059
+ });
6060
+ }
6061
+ cbRef.current.onPlay?.(s);
6062
+ },
6063
+ onPlaying: () => setPlaying(true),
6064
+ onPause: () => {
6065
+ const v = video.current;
6066
+ if (v?.ended) return;
6067
+ commitActiveSegment(v?.currentTime);
6068
+ const s = snap();
6069
+ emitProgress("pause", s);
6070
+ cbRef.current.onPause?.(s);
6071
+ },
6072
+ onEnded: () => {
6073
+ const v = video.current;
6074
+ const dur = typeof v?.duration === "number" && Number.isFinite(v.duration) ? v.duration : durationRef.current;
6075
+ commitActiveSegment(dur > 0 ? dur : v?.currentTime);
6076
+ maxPercentRef.current = 100;
6077
+ const s = snap();
6078
+ if (!firedCheckpointsRef.current.has(100) && checkpoints.includes(100)) {
6079
+ firedCheckpointsRef.current.add(100);
6080
+ cbRef.current.onCheckpoint?.({ ...s, checkpoint: 100 });
6081
+ }
6082
+ emitProgress("ended", s);
6083
+ cbRef.current.onEnded?.(s);
6084
+ },
6085
+ onWaiting: () => cbRef.current.onWaiting?.(snap()),
6086
+ onTimeUpdate: () => {
6087
+ const v = video.current;
6088
+ if (v && !v.seeking) {
6089
+ if (activeSegmentStartRef.current === null && !v.paused) {
6090
+ activeSegmentStartRef.current = lastTime.current;
6091
+ }
6092
+ lastTime.current = v.currentTime;
6093
+ dirtySinceLastEmitRef.current = true;
6094
+ }
6095
+ const s = snap();
6096
+ let hitCheckpoint = false;
6097
+ let hitGoal = false;
6098
+ for (const goal of goalsRef.current) {
6099
+ if (firedGoalsRef.current.has(goal.key)) continue;
6100
+ const met = goal.metric === "seconds" ? s.watchedSeconds >= goal.threshold : s.watchedPercent >= goal.threshold;
6101
+ if (met) {
6102
+ firedGoalsRef.current.add(goal.key);
6103
+ hitGoal = true;
6104
+ }
6105
+ }
6106
+ const newlyFiredCheckpoints = [];
6107
+ for (const cp of checkpoints) {
6108
+ if (firedCheckpointsRef.current.has(cp)) continue;
6109
+ if (s.watchedPercent >= cp || s.maxPercent >= cp) {
6110
+ firedCheckpointsRef.current.add(cp);
6111
+ newlyFiredCheckpoints.push(cp);
6112
+ hitCheckpoint = true;
6113
+ }
6114
+ }
6115
+ if (hitCheckpoint || hitGoal) {
6116
+ commitActiveSegment(v?.currentTime);
6117
+ if (v && !v.paused) activeSegmentStartRef.current = v.currentTime;
6118
+ const emittedSnap = snap();
6119
+ emitProgress(hitCheckpoint ? "checkpoint" : "goal", emittedSnap);
6120
+ for (const cp of newlyFiredCheckpoints) {
6121
+ cbRef.current.onCheckpoint?.({ ...emittedSnap, checkpoint: cp });
6122
+ }
6123
+ }
6124
+ cbRef.current.onTimeUpdate?.(s);
6125
+ },
6126
+ onSeeking: () => {
6127
+ seekFrom.current = lastTime.current;
6128
+ commitActiveSegment(seekFrom.current);
6129
+ },
6130
+ onSeeked: () => {
6131
+ const v = video.current;
6132
+ const to = roundVideoSeconds(v?.currentTime ?? 0);
6133
+ const from = roundVideoSeconds(seekFrom.current);
6134
+ lastTime.current = to;
6135
+ if (v && !v.paused) {
6136
+ activeSegmentStartRef.current = to;
6137
+ }
6138
+ if (Math.abs(to - from) >= 1) {
6139
+ seekCountRef.current += 1;
6140
+ const s = snap();
6141
+ emitProgress("seek", s);
6142
+ cbRef.current.onSeek?.({ ...s, from, to });
6143
+ }
6144
+ },
6145
+ children: /* @__PURE__ */ jsx5("track", { kind: "captions" })
6146
+ }
6147
+ ),
6148
+ ready && /* @__PURE__ */ jsxs2(Fragment2, { children: [
6149
+ /* @__PURE__ */ jsx5("media-loading-indicator", { slot: "centered-chrome" }),
6150
+ /* @__PURE__ */ jsxs2("div", { className: "tv-chrome", children: [
6151
+ /* @__PURE__ */ jsxs2("div", { className: "tv-row", children: [
6152
+ /* @__PURE__ */ jsxs2("media-play-button", { class: "tv-ctl", children: [
6153
+ /* @__PURE__ */ jsx5(IconPlay, { slot: "play" }),
6154
+ /* @__PURE__ */ jsx5(IconPause, { slot: "pause" })
6155
+ ] }),
6156
+ /* @__PURE__ */ jsx5("media-time-display", { class: "tv-time tv-time-full", showduration: true }),
6157
+ /* @__PURE__ */ jsx5("media-time-display", { class: "tv-time tv-time-cur" }),
6158
+ /* @__PURE__ */ jsx5("span", { className: "tv-spacer" }),
6159
+ /* @__PURE__ */ jsxs2("div", { className: "tv-vol", children: [
6160
+ /* @__PURE__ */ jsxs2("media-mute-button", { class: "tv-ctl", children: [
6161
+ /* @__PURE__ */ jsx5(IconVolumeHigh, { slot: "high" }),
6162
+ /* @__PURE__ */ jsx5(IconVolumeLow, { slot: "medium" }),
6163
+ /* @__PURE__ */ jsx5(IconVolumeLow, { slot: "low" }),
6164
+ /* @__PURE__ */ jsx5(IconVolumeOff, { slot: "off" })
6165
+ ] }),
6166
+ /* @__PURE__ */ jsx5("media-volume-range", { class: "tv-volrange" })
6167
+ ] }),
6168
+ renditions.length > 1 && /* @__PURE__ */ jsxs2("div", { className: "tv-menu-wrap", ref: wrap, children: [
6169
+ /* @__PURE__ */ jsx5(
6170
+ "button",
6171
+ {
6172
+ ref: gear,
6173
+ type: "button",
6174
+ className: "tv-btn",
6175
+ "aria-haspopup": "menu",
6176
+ "aria-expanded": menu,
6177
+ "aria-label": `Settings, quality ${qualityLabel}`,
6178
+ title: "Settings",
6179
+ onClick: () => setMenu((m) => !m),
6180
+ onKeyDown: onMenuKey,
6181
+ children: /* @__PURE__ */ jsx5(IconSettings, {})
6182
+ }
6183
+ ),
6184
+ /* @__PURE__ */ jsx5("span", { role: "status", className: "sr-only", children: pending !== null ? `Switching to ${pending}p` : "" }),
6185
+ menu && /* @__PURE__ */ jsxs2(
6186
+ "ul",
6187
+ {
6188
+ role: "menu",
6189
+ "aria-label": "Quality",
6190
+ className: "tv-menu",
6191
+ onKeyDown: onMenuKey,
6192
+ children: [
6193
+ ["auto", ...renditions.map((r) => r.height)].map(
6194
+ (q) => /* @__PURE__ */ jsx5("li", { role: "none", children: /* @__PURE__ */ jsx5(
6195
+ "button",
6196
+ {
6197
+ type: "button",
6198
+ role: "menuitemradio",
6199
+ "aria-checked": selected === q,
6200
+ className: "tv-menu-item",
6201
+ onClick: () => pick(q),
6202
+ children: q === "auto" ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
6203
+ "Auto",
6204
+ live ? /* @__PURE__ */ jsxs2("span", { className: "tv-menu-sub", children: [
6205
+ " ",
6206
+ live,
6207
+ "p"
6208
+ ] }) : null
6209
+ ] }) : `${q}p`
6210
+ }
6211
+ ) }, String(q))
6212
+ ),
6213
+ /* @__PURE__ */ jsx5("li", { "aria-hidden": "true", className: "tv-menu-note", children: pending !== null ? `Switching to ${pending}p\u2026` : "" })
6214
+ ]
6215
+ }
6216
+ )
6217
+ ] }),
6218
+ variant === "16x9" && onTheaterChange && /* @__PURE__ */ jsx5(
6219
+ "button",
6220
+ {
6221
+ type: "button",
6222
+ className: "tv-btn tv-theater",
6223
+ "aria-pressed": theater,
6224
+ "aria-label": theater ? "Shrink the player" : "Expand the player",
6225
+ title: theater ? "Shrink" : "Expand",
6226
+ onClick: () => onTheaterChange(!theater),
6227
+ children: theater ? /* @__PURE__ */ jsx5(IconTheaterShrink, {}) : /* @__PURE__ */ jsx5(IconTheaterExpand, {})
6228
+ }
6229
+ ),
6230
+ /* @__PURE__ */ jsxs2("media-fullscreen-button", { class: "tv-ctl", children: [
6231
+ /* @__PURE__ */ jsx5(IconFullscreenEnter, { slot: "enter" }),
6232
+ /* @__PURE__ */ jsx5(IconFullscreenExit, { slot: "exit" })
6233
+ ] })
6234
+ ] }),
6235
+ /* @__PURE__ */ jsx5("media-time-range", { class: "tv-seek" })
6236
+ ] })
6237
+ ] })
6238
+ ] }),
6239
+ !playing && /* @__PURE__ */ jsxs2("div", { className: "tv-poster-overlay", children: [
6240
+ renderPoster ? renderPoster({
6241
+ src: resolvedPosterUrl,
6242
+ variant,
6243
+ title,
6244
+ width: posterWidth,
6245
+ height: posterHeight
6246
+ }) : /* @__PURE__ */ jsx5(
6247
+ "img",
6248
+ {
6249
+ src: resolvedPosterUrl,
6250
+ alt: "",
6251
+ width: posterWidth,
6252
+ height: posterHeight,
6253
+ loading: "lazy",
6254
+ decoding: "async",
6255
+ className: "tv-poster-img"
6256
+ }
6257
+ ),
6258
+ !started && /* @__PURE__ */ jsx5(
6259
+ "button",
6260
+ {
6261
+ type: "button",
6262
+ onClick: start,
6263
+ "aria-label": `Play video: ${title}`,
6264
+ className: "tv-play-trigger",
6265
+ children: /* @__PURE__ */ jsx5("span", { className: "tv-play-pill", children: /* @__PURE__ */ jsx5(IconPlay, { size: 26, filled: true }) })
6266
+ }
6267
+ )
6268
+ ] })
6269
+ ]
6270
+ }
6271
+ );
6272
+ }
6273
+ function TrackedVideo(props) {
6274
+ const {
6275
+ id,
6276
+ video,
6277
+ host,
6278
+ sources,
6279
+ title,
6280
+ posterTime,
6281
+ posterUrl,
6282
+ renderPoster,
6283
+ theater: controlledTheater,
6284
+ defaultTheater = false,
6285
+ onTheaterChange,
6286
+ expandMode = "inline",
6287
+ autoExpandOnPlay = true,
6288
+ expandedMaxWidth = 1100,
6289
+ checkpoints = DEFAULT_VIDEO_CHECKPOINTS,
6290
+ trackingConfig,
6291
+ fallbackConfig,
6292
+ client = null,
6293
+ loadPlayerLibs,
6294
+ className = "",
6295
+ style,
6296
+ ...callbacks
6297
+ } = props;
6298
+ const videoKey = id ?? video ?? fallbackConfig?.key ?? "";
6299
+ const r2Video = useTrackedVideoConfig(videoKey || void 0, trackingConfig, fallbackConfig);
6300
+ const videoGoals = useActiveVideoGoals(videoKey, trackingConfig);
6301
+ const [internalTheater, setInternalTheater] = useState5(defaultTheater);
6302
+ const isTheaterControlled = controlledTheater !== void 0;
6303
+ const activeTheater = isTheaterControlled ? controlledTheater : internalTheater;
6304
+ const handleTheaterChange = useCallback4(
6305
+ (next) => {
6306
+ if (!isTheaterControlled) {
6307
+ setInternalTheater(next);
6308
+ }
6309
+ onTheaterChange?.(next);
6310
+ },
6311
+ [isTheaterControlled, onTheaterChange]
6312
+ );
6313
+ const resolvedHost = host ?? r2Video?.host ?? "";
6314
+ const desktopUid = sources?.desktop ?? r2Video?.sources.desktop ?? "";
6315
+ const mobileUid = sources?.mobile ?? r2Video?.sources.mobile ?? desktopUid;
6316
+ const resolvedTitle = title ?? r2Video?.title ?? videoKey;
6317
+ const resolvedPosterTime = posterTime ?? r2Video?.poster_time ?? r2Video?.poster_time_s ?? 4;
6318
+ if (!videoKey || !resolvedHost || !desktopUid) {
6319
+ return null;
6320
+ }
6321
+ const showTheaterControl = expandMode !== "none" && (expandMode === "theater" || Boolean(onTheaterChange));
6322
+ const wrapperStyle = !isTheaterControlled && expandMode === "theater" && activeTheater ? {
6323
+ maxWidth: typeof expandedMaxWidth === "number" ? `${expandedMaxWidth}px` : expandedMaxWidth,
6324
+ marginLeft: "auto",
6325
+ marginRight: "auto",
6326
+ width: "100%",
6327
+ transition: "max-width 360ms cubic-bezier(0.2, 0.7, 0.2, 1)",
6328
+ ...style
6329
+ } : style;
6330
+ return /* @__PURE__ */ jsxs2("div", { className, style: wrapperStyle, "data-tracked-video": videoKey, children: [
6331
+ /* @__PURE__ */ jsx5(
6332
+ Cut,
6333
+ {
6334
+ ...callbacks,
6335
+ id: videoKey,
6336
+ variant: "16x9",
6337
+ uid: desktopUid,
6338
+ host: resolvedHost,
6339
+ title: resolvedTitle,
6340
+ posterTime: resolvedPosterTime,
6341
+ posterUrl,
6342
+ renderPoster,
6343
+ theater: activeTheater,
6344
+ onTheaterChange: showTheaterControl ? handleTheaterChange : void 0,
6345
+ autoExpandOnPlay,
6346
+ checkpoints,
6347
+ videoGoals,
6348
+ client,
6349
+ loadPlayerLibs
6350
+ }
6351
+ ),
6352
+ /* @__PURE__ */ jsx5(
6353
+ Cut,
6354
+ {
6355
+ ...callbacks,
6356
+ id: videoKey,
6357
+ variant: "9x16",
6358
+ uid: mobileUid || desktopUid,
6359
+ host: resolvedHost,
6360
+ title: resolvedTitle,
6361
+ posterTime: resolvedPosterTime,
6362
+ posterUrl,
6363
+ renderPoster,
6364
+ theater: false,
6365
+ onTheaterChange: void 0,
6366
+ autoExpandOnPlay: false,
6367
+ checkpoints,
6368
+ videoGoals,
6369
+ client,
6370
+ loadPlayerLibs
6371
+ }
6372
+ )
6373
+ ] });
6374
+ }
6375
+ var STAGE_DURATION_MS = 360;
6376
+ var STAGE_EASE = "cubic-bezier(0.2, 0.7, 0.2, 1)";
6377
+ function TrackedVideoStage({
6378
+ video,
6379
+ children,
6380
+ expandedMaxWidth = 1100,
6381
+ collapsedTextMaxWidth = 760,
6382
+ className = ""
6383
+ }) {
6384
+ const [theater, setTheater] = useState5(video.defaultTheater ?? false);
6385
+ const videoBox = useRef2(null);
6386
+ const textBox = useRef2(null);
6387
+ const first = useRef2(null);
6388
+ const activeTheater = video.theater !== void 0 ? video.theater : theater;
6389
+ const change = useCallback4(
6390
+ (next) => {
6391
+ if (next === activeTheater) return;
6392
+ if (next && typeof window !== "undefined" && !window.matchMedia("(min-width: 768px)").matches) {
6393
+ return;
6394
+ }
6395
+ if (videoBox.current && textBox.current) {
6396
+ first.current = {
6397
+ v: videoBox.current.getBoundingClientRect(),
6398
+ t: textBox.current.getBoundingClientRect()
6399
+ };
6400
+ }
6401
+ if (video.theater === void 0) {
6402
+ setTheater(next);
6403
+ }
6404
+ video.onTheaterChange?.(next);
6405
+ },
6406
+ [activeTheater, video]
6407
+ );
6408
+ useLayoutEffect(() => {
6409
+ if (typeof window === "undefined") return;
6410
+ const f = first.current;
6411
+ const vb = videoBox.current;
6412
+ const tb = textBox.current;
6413
+ first.current = null;
6414
+ if (!f || !vb || !tb) return;
6415
+ const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
6416
+ const lv = vb.getBoundingClientRect();
6417
+ const lt = tb.getBoundingClientRect();
6418
+ if (!reduce && typeof vb.animate === "function" && lv.width > 0) {
6419
+ const s = f.v.width / lv.width;
6420
+ vb.animate(
6421
+ [
6422
+ {
6423
+ transformOrigin: "top left",
6424
+ transform: `translate(${f.v.left - lv.left}px, ${f.v.top - lv.top}px) scale(${s})`
6425
+ },
6426
+ { transformOrigin: "top left", transform: "none" }
6427
+ ],
6428
+ { duration: STAGE_DURATION_MS, easing: STAGE_EASE }
6429
+ );
6430
+ tb.animate(
6431
+ [
6432
+ {
6433
+ transform: `translate(${f.t.left - lt.left}px, ${f.t.top - lt.top}px)`,
6434
+ opacity: 0.4
6435
+ },
6436
+ { transform: "none", opacity: 1 }
6437
+ ],
6438
+ { duration: STAGE_DURATION_MS, easing: STAGE_EASE }
6439
+ );
6440
+ }
6441
+ if (lv.top < 80 || lv.bottom > window.innerHeight - 24) {
6442
+ const top = window.scrollY + lv.top - Math.max(96, (window.innerHeight - lv.height) / 2);
6443
+ window.scrollTo({ top, behavior: reduce ? "auto" : "smooth" });
6444
+ }
6445
+ }, [activeTheater]);
6446
+ const maxVideoWidthCss = typeof expandedMaxWidth === "number" ? `${expandedMaxWidth}px` : expandedMaxWidth;
6447
+ const maxTextWidthCss = typeof collapsedTextMaxWidth === "number" ? `${collapsedTextMaxWidth}px` : collapsedTextMaxWidth;
6448
+ return /* @__PURE__ */ jsxs2(
6449
+ "div",
6450
+ {
6451
+ className: activeTheater ? `grid gap-10 md:gap-12 ${className}` : `grid items-start gap-10 lg:grid-cols-[1fr_1fr] lg:gap-16 ${className}`,
6452
+ children: [
6453
+ /* @__PURE__ */ jsx5(
6454
+ "div",
6455
+ {
6456
+ ref: textBox,
6457
+ className: activeTheater ? "md:order-2" : "",
6458
+ style: activeTheater ? { maxWidth: maxTextWidthCss } : void 0,
6459
+ children
6460
+ }
6461
+ ),
6462
+ /* @__PURE__ */ jsx5(
6463
+ "div",
6464
+ {
6465
+ ref: videoBox,
6466
+ className: activeTheater ? "md:order-1 md:mx-auto md:w-full" : "",
6467
+ style: activeTheater ? { maxWidth: maxVideoWidthCss, width: "100%" } : void 0,
6468
+ children: /* @__PURE__ */ jsx5(
6469
+ TrackedVideo,
6470
+ {
6471
+ ...video,
6472
+ theater: activeTheater,
6473
+ onTheaterChange: change,
6474
+ className: video.className ?? "w-full"
6475
+ }
6476
+ )
6477
+ }
6478
+ )
6479
+ ]
6480
+ }
6481
+ );
6482
+ }
4763
6483
  export {
4764
6484
  AdPlatformTracking,
4765
6485
  BookingHandoffLink,
4766
6486
  ConsentBanner,
6487
+ DEFAULT_VIDEO_CHECKPOINTS,
4767
6488
  GoogleAdsTracking,
4768
6489
  PhoneField,
6490
+ TrackedVideo,
6491
+ TrackedVideoStage,
4769
6492
  createLeadFormSubmit,
4770
6493
  createTracking,
6494
+ streamManifestUrl,
6495
+ streamPosterUrl,
4771
6496
  useBookingHandoffUrl,
4772
6497
  useBookingIntegration,
4773
6498
  useBookingSlots,
@@ -4778,6 +6503,7 @@ export {
4778
6503
  useGclid,
4779
6504
  usePhoneConfig,
4780
6505
  usePhoneField,
6506
+ useTrackedVideoConfig,
4781
6507
  useTrackingParams
4782
6508
  };
4783
6509
  //# sourceMappingURL=client.mjs.map