@se-studio/core-ui 1.17.1 → 1.18.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.
Files changed (72) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/analytics/video/classifySeek.d.ts +15 -0
  3. package/dist/analytics/video/classifySeek.d.ts.map +1 -0
  4. package/dist/analytics/video/classifySeek.js +32 -0
  5. package/dist/analytics/video/classifySeek.js.map +1 -0
  6. package/dist/analytics/video/createVideoEngagementSession.d.ts +51 -0
  7. package/dist/analytics/video/createVideoEngagementSession.d.ts.map +1 -0
  8. package/dist/analytics/video/createVideoEngagementSession.js +225 -0
  9. package/dist/analytics/video/createVideoEngagementSession.js.map +1 -0
  10. package/dist/analytics/video/index.d.ts +6 -0
  11. package/dist/analytics/video/index.d.ts.map +1 -0
  12. package/dist/analytics/video/index.js +6 -0
  13. package/dist/analytics/video/index.js.map +1 -0
  14. package/dist/analytics/video/loadYouTubeIframeApi.d.ts +50 -0
  15. package/dist/analytics/video/loadYouTubeIframeApi.d.ts.map +1 -0
  16. package/dist/analytics/video/loadYouTubeIframeApi.js +52 -0
  17. package/dist/analytics/video/loadYouTubeIframeApi.js.map +1 -0
  18. package/dist/analytics/video/useHtml5VideoEngagement.d.ts +8 -0
  19. package/dist/analytics/video/useHtml5VideoEngagement.d.ts.map +1 -0
  20. package/dist/analytics/video/useHtml5VideoEngagement.js +67 -0
  21. package/dist/analytics/video/useHtml5VideoEngagement.js.map +1 -0
  22. package/dist/analytics/video/useVideoEngagement.d.ts +19 -0
  23. package/dist/analytics/video/useVideoEngagement.d.ts.map +1 -0
  24. package/dist/analytics/video/useVideoEngagement.js +80 -0
  25. package/dist/analytics/video/useVideoEngagement.js.map +1 -0
  26. package/dist/analytics/video/videoEngagementTypes.d.ts +52 -0
  27. package/dist/analytics/video/videoEngagementTypes.d.ts.map +1 -0
  28. package/dist/analytics/video/videoEngagementTypes.js +45 -0
  29. package/dist/analytics/video/videoEngagementTypes.js.map +1 -0
  30. package/dist/components/ExternalVimeoEmbed.d.ts +1 -1
  31. package/dist/components/ExternalVimeoEmbed.d.ts.map +1 -1
  32. package/dist/components/ExternalVimeoEmbed.js +134 -9
  33. package/dist/components/ExternalVimeoEmbed.js.map +1 -1
  34. package/dist/components/ExternalYouTubeEmbed.d.ts +1 -1
  35. package/dist/components/ExternalYouTubeEmbed.d.ts.map +1 -1
  36. package/dist/components/ExternalYouTubeEmbed.js +167 -9
  37. package/dist/components/ExternalYouTubeEmbed.js.map +1 -1
  38. package/dist/components/ImageKitClientVideo.d.ts +6 -1
  39. package/dist/components/ImageKitClientVideo.d.ts.map +1 -1
  40. package/dist/components/ImageKitClientVideo.js +69 -7
  41. package/dist/components/ImageKitClientVideo.js.map +1 -1
  42. package/dist/components/ImageKitHlsVideoClient.d.ts +7 -1
  43. package/dist/components/ImageKitHlsVideoClient.d.ts.map +1 -1
  44. package/dist/components/ImageKitHlsVideoClient.js +65 -8
  45. package/dist/components/ImageKitHlsVideoClient.js.map +1 -1
  46. package/dist/components/LazyVideo.d.ts +5 -0
  47. package/dist/components/LazyVideo.d.ts.map +1 -1
  48. package/dist/components/LazyVideo.js +61 -36
  49. package/dist/components/LazyVideo.js.map +1 -1
  50. package/dist/components/LocalVideo.d.ts.map +1 -1
  51. package/dist/components/LocalVideo.js +8 -2
  52. package/dist/components/LocalVideo.js.map +1 -1
  53. package/dist/components/LocalVideoPlayChrome.d.ts +20 -0
  54. package/dist/components/LocalVideoPlayChrome.d.ts.map +1 -0
  55. package/dist/components/LocalVideoPlayChrome.js +22 -0
  56. package/dist/components/LocalVideoPlayChrome.js.map +1 -0
  57. package/dist/components/LocalVideoPlayOverlay.d.ts +19 -0
  58. package/dist/components/LocalVideoPlayOverlay.d.ts.map +1 -0
  59. package/dist/components/LocalVideoPlayOverlay.js +49 -0
  60. package/dist/components/LocalVideoPlayOverlay.js.map +1 -0
  61. package/dist/components/Video.d.ts +5 -0
  62. package/dist/components/Video.d.ts.map +1 -1
  63. package/dist/index.d.ts +4 -0
  64. package/dist/index.d.ts.map +1 -1
  65. package/dist/index.js +3 -0
  66. package/dist/index.js.map +1 -1
  67. package/dist/utils/componentUtils.d.ts +3 -2
  68. package/dist/utils/componentUtils.d.ts.map +1 -1
  69. package/dist/utils/componentUtils.js +21 -3
  70. package/dist/utils/componentUtils.js.map +1 -1
  71. package/docs/llms.md +15 -0
  72. package/package.json +2 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @se-studio/core-ui
2
2
 
3
+ ## 1.18.0
4
+
5
+ ### Minor Changes
6
+
7
+ - f909658: Add first-party video engagement tracking for interactive Vimeo, YouTube, and local (ImageKit) videos via `trackEvent`. Non-autoplay local videos get built-in click-to-play chrome (same `ExternalVideoPlayButtonProvider` as embeds). Export `LocalVideoPlayOverlay`, `LocalVideoPlayChrome`, and `VIDEO_ENGAGEMENT_EVENTS`. LazyVideo shells show a play affordance on first paint. Autoplay/background videos remain untracked.
8
+
9
+ ## 1.17.2
10
+
11
+ ### Patch Changes
12
+
13
+ - d54f872: Ambient related-articles tags also exclude `hidden: true` and `indexed: false` (not only `show: false`), matching card chip visibility used by customer sites.
14
+
3
15
  ## 1.17.1
4
16
 
5
17
  ### Patch Changes
@@ -0,0 +1,15 @@
1
+ import { VIDEO_ENGAGEMENT_THRESHOLDS } from './videoEngagementTypes';
2
+ export interface SeekClassification {
3
+ /** Large enough jump to count as a seek (forward or back). */
4
+ material: boolean;
5
+ /** Forward jump past seek thresholds. */
6
+ largeForward: boolean;
7
+ fromPercent: number;
8
+ toPercent: number;
9
+ direction: 'forward' | 'back';
10
+ }
11
+ /**
12
+ * Classify a playback position jump for analytics (material seek vs noise).
13
+ */
14
+ export declare function classifySeek(fromSeconds: number, toSeconds: number, durationSeconds: number, thresholds?: typeof VIDEO_ENGAGEMENT_THRESHOLDS): SeekClassification;
15
+ //# sourceMappingURL=classifySeek.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"classifySeek.d.ts","sourceRoot":"","sources":["../../../src/analytics/video/classifySeek.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,MAAM,wBAAwB,CAAC;AAErE,MAAM,WAAW,kBAAkB;IACjC,8DAA8D;IAC9D,QAAQ,EAAE,OAAO,CAAC;IAClB,yCAAyC;IACzC,YAAY,EAAE,OAAO,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,SAAS,GAAG,MAAM,CAAC;CAC/B;AAaD;;GAEG;AACH,wBAAgB,YAAY,CAC1B,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,eAAe,EAAE,MAAM,EACvB,UAAU,GAAE,OAAO,2BAAyD,GAC3E,kBAAkB,CAoBpB"}
@@ -0,0 +1,32 @@
1
+ import { VIDEO_ENGAGEMENT_THRESHOLDS } from './videoEngagementTypes';
2
+ function clampPercent(value) {
3
+ if (!Number.isFinite(value) || value < 0)
4
+ return 0;
5
+ if (value > 100)
6
+ return 100;
7
+ return value;
8
+ }
9
+ function percentOf(current, duration) {
10
+ if (!duration || duration <= 0 || !Number.isFinite(current))
11
+ return 0;
12
+ return clampPercent((current / duration) * 100);
13
+ }
14
+ /**
15
+ * Classify a playback position jump for analytics (material seek vs noise).
16
+ */
17
+ export function classifySeek(fromSeconds, toSeconds, durationSeconds, thresholds = VIDEO_ENGAGEMENT_THRESHOLDS) {
18
+ const duration = durationSeconds > 0 ? durationSeconds : 0;
19
+ const delta = toSeconds - fromSeconds;
20
+ const absDelta = Math.abs(delta);
21
+ const minByPercent = duration > 0 ? (thresholds.seekMinPercent / 100) * duration : thresholds.seekMinSeconds;
22
+ const material = absDelta >= thresholds.seekMinSeconds || (duration > 0 && absDelta >= minByPercent);
23
+ const largeForward = delta > 0 && (delta >= thresholds.seekMinSeconds || (duration > 0 && delta >= minByPercent));
24
+ return {
25
+ material,
26
+ largeForward,
27
+ fromPercent: Math.round(percentOf(fromSeconds, duration) * 10) / 10,
28
+ toPercent: Math.round(percentOf(toSeconds, duration) * 10) / 10,
29
+ direction: delta >= 0 ? 'forward' : 'back',
30
+ };
31
+ }
32
+ //# sourceMappingURL=classifySeek.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"classifySeek.js","sourceRoot":"","sources":["../../../src/analytics/video/classifySeek.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,MAAM,wBAAwB,CAAC;AAYrE,SAAS,YAAY,CAAC,KAAa;IACjC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC;QAAE,OAAO,CAAC,CAAC;IACnD,IAAI,KAAK,GAAG,GAAG;QAAE,OAAO,GAAG,CAAC;IAC5B,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,SAAS,CAAC,OAAe,EAAE,QAAgB;IAClD,IAAI,CAAC,QAAQ,IAAI,QAAQ,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC;QAAE,OAAO,CAAC,CAAC;IACtE,OAAO,YAAY,CAAC,CAAC,OAAO,GAAG,QAAQ,CAAC,GAAG,GAAG,CAAC,CAAC;AAClD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY,CAC1B,WAAmB,EACnB,SAAiB,EACjB,eAAuB,EACvB,aAAiD,2BAA2B;IAE5E,MAAM,QAAQ,GAAG,eAAe,GAAG,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3D,MAAM,KAAK,GAAG,SAAS,GAAG,WAAW,CAAC;IACtC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACjC,MAAM,YAAY,GAChB,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,cAAc,GAAG,GAAG,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,cAAc,CAAC;IAE1F,MAAM,QAAQ,GACZ,QAAQ,IAAI,UAAU,CAAC,cAAc,IAAI,CAAC,QAAQ,GAAG,CAAC,IAAI,QAAQ,IAAI,YAAY,CAAC,CAAC;IAEtF,MAAM,YAAY,GAChB,KAAK,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,UAAU,CAAC,cAAc,IAAI,CAAC,QAAQ,GAAG,CAAC,IAAI,KAAK,IAAI,YAAY,CAAC,CAAC,CAAC;IAE/F,OAAO;QACL,QAAQ;QACR,YAAY;QACZ,WAAW,EAAE,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,WAAW,EAAE,QAAQ,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE;QACnE,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,SAAS,EAAE,QAAQ,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE;QAC/D,SAAS,EAAE,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM;KAC3C,CAAC;AACJ,CAAC"}
@@ -0,0 +1,51 @@
1
+ import { type VideoEngagementIdentity, type VideoEngagementTrackFn } from './videoEngagementTypes';
2
+ export interface CreateVideoEngagementSessionOptions {
3
+ identity: VideoEngagementIdentity;
4
+ track: VideoEngagementTrackFn;
5
+ /** Page URL for payload; defaults to window.location.href when available. */
6
+ getPageUrl?: () => string;
7
+ }
8
+ export interface VideoEngagementSession {
9
+ /** Fire video_impression once (e.g. from IntersectionObserver). */
10
+ markImpression: () => void;
11
+ onPlay: (muted: boolean) => void;
12
+ onPause: () => void;
13
+ onTime: (currentTimeSeconds: number, durationSeconds: number) => void;
14
+ onSeek: (fromSeconds: number, toSeconds: number, durationSeconds: number) => void;
15
+ onMuteChange: (muted: boolean) => void;
16
+ onFullscreen: (fullscreen: boolean) => void;
17
+ onEnded: (durationSeconds: number) => void;
18
+ /** Call on unmount / pagehide — may emit video_exit. */
19
+ dispose: () => void;
20
+ /**
21
+ * Snapshot for unit tests / debugging only — not part of the public analytics
22
+ * surface. Returns live internal state (mutable Sets, etc.).
23
+ */
24
+ getState: () => Readonly<SessionState>;
25
+ }
26
+ interface SessionState {
27
+ impressed: boolean;
28
+ started: boolean;
29
+ completed: boolean;
30
+ exited: boolean;
31
+ muted: boolean;
32
+ mutedAtStart: boolean | null;
33
+ usedFullscreen: boolean;
34
+ fullscreen: boolean;
35
+ maxPercent: number;
36
+ watchSeconds: number;
37
+ durationSeconds: number;
38
+ seekCount: number;
39
+ largeForwardSeekCount: number;
40
+ quartilesFired: Set<number>;
41
+ unmutedFired: boolean;
42
+ playing: boolean;
43
+ playStartedAtMs: number | null;
44
+ }
45
+ /**
46
+ * Pure engagement session: provider adapters translate player events into these methods.
47
+ * Fires at most one start/complete/exit/impression; quartiles once each; unmute once.
48
+ */
49
+ export declare function createVideoEngagementSession(options: CreateVideoEngagementSessionOptions): VideoEngagementSession;
50
+ export {};
51
+ //# sourceMappingURL=createVideoEngagementSession.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createVideoEngagementSession.d.ts","sourceRoot":"","sources":["../../../src/analytics/video/createVideoEngagementSession.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,EAC5B,MAAM,wBAAwB,CAAC;AAEhC,MAAM,WAAW,mCAAmC;IAClD,QAAQ,EAAE,uBAAuB,CAAC;IAClC,KAAK,EAAE,sBAAsB,CAAC;IAC9B,6EAA6E;IAC7E,UAAU,CAAC,EAAE,MAAM,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,sBAAsB;IACrC,mEAAmE;IACnE,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,MAAM,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACjC,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,MAAM,EAAE,CAAC,kBAAkB,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,KAAK,IAAI,CAAC;IACtE,MAAM,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,KAAK,IAAI,CAAC;IAClF,YAAY,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACvC,YAAY,EAAE,CAAC,UAAU,EAAE,OAAO,KAAK,IAAI,CAAC;IAC5C,OAAO,EAAE,CAAC,eAAe,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,wDAAwD;IACxD,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB;;;OAGG;IACH,QAAQ,EAAE,MAAM,QAAQ,CAAC,YAAY,CAAC,CAAC;CACxC;AAED,UAAU,YAAY;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,EAAE,OAAO,CAAC;IACf,YAAY,EAAE,OAAO,GAAG,IAAI,CAAC;IAC7B,cAAc,EAAE,OAAO,CAAC;IACxB,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC5B,YAAY,EAAE,OAAO,CAAC;IACtB,OAAO,EAAE,OAAO,CAAC;IACjB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAkBD;;;GAGG;AACH,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,mCAAmC,GAC3C,sBAAsB,CAuNxB"}
@@ -0,0 +1,225 @@
1
+ import { classifySeek } from './classifySeek';
2
+ import { VIDEO_ENGAGEMENT_EVENTS, VIDEO_ENGAGEMENT_THRESHOLDS, } from './videoEngagementTypes';
3
+ function clampPercent(value) {
4
+ if (!Number.isFinite(value) || value < 0)
5
+ return 0;
6
+ if (value > 100)
7
+ return 100;
8
+ return value;
9
+ }
10
+ function percentOf(current, duration) {
11
+ if (!duration || duration <= 0 || !Number.isFinite(current))
12
+ return 0;
13
+ return clampPercent((current / duration) * 100);
14
+ }
15
+ function defaultPageUrl() {
16
+ if (typeof window === 'undefined')
17
+ return '';
18
+ return window.location.href;
19
+ }
20
+ /**
21
+ * Pure engagement session: provider adapters translate player events into these methods.
22
+ * Fires at most one start/complete/exit/impression; quartiles once each; unmute once.
23
+ */
24
+ export function createVideoEngagementSession(options) {
25
+ const { identity, track } = options;
26
+ const getPageUrl = options.getPageUrl ?? defaultPageUrl;
27
+ const state = {
28
+ impressed: false,
29
+ started: false,
30
+ completed: false,
31
+ exited: false,
32
+ muted: false,
33
+ mutedAtStart: null,
34
+ usedFullscreen: false,
35
+ fullscreen: false,
36
+ maxPercent: 0,
37
+ watchSeconds: 0,
38
+ durationSeconds: 0,
39
+ seekCount: 0,
40
+ largeForwardSeekCount: 0,
41
+ quartilesFired: new Set(),
42
+ unmutedFired: false,
43
+ playing: false,
44
+ playStartedAtMs: null,
45
+ };
46
+ function baseProps() {
47
+ return {
48
+ ...identity,
49
+ url: getPageUrl(),
50
+ autoplay: false,
51
+ max_percent: Math.round(state.maxPercent * 10) / 10,
52
+ watch_seconds: Math.round(state.watchSeconds * 10) / 10,
53
+ duration_seconds: state.durationSeconds > 0 ? Math.round(state.durationSeconds * 10) / 10 : undefined,
54
+ muted: state.muted,
55
+ muted_at_start: state.mutedAtStart ?? undefined,
56
+ used_fullscreen: state.usedFullscreen,
57
+ seek_count: state.seekCount,
58
+ completed: state.completed,
59
+ };
60
+ }
61
+ function emit(event, extra) {
62
+ track(event, { ...baseProps(), ...extra });
63
+ }
64
+ function accumulateWatchTime() {
65
+ if (state.playStartedAtMs == null)
66
+ return;
67
+ const now = Date.now();
68
+ const delta = (now - state.playStartedAtMs) / 1000;
69
+ if (delta > 0 && Number.isFinite(delta)) {
70
+ state.watchSeconds += delta;
71
+ }
72
+ state.playStartedAtMs = state.playing ? now : null;
73
+ }
74
+ function updateMaxPercent(currentTime, duration) {
75
+ if (duration > 0) {
76
+ state.durationSeconds = duration;
77
+ }
78
+ const pct = percentOf(currentTime, duration || state.durationSeconds);
79
+ if (pct > state.maxPercent) {
80
+ state.maxPercent = pct;
81
+ }
82
+ }
83
+ function maybeFireQuartiles() {
84
+ if (!state.started || state.completed)
85
+ return;
86
+ for (const q of VIDEO_ENGAGEMENT_THRESHOLDS.quartiles) {
87
+ if (state.maxPercent >= q && !state.quartilesFired.has(q)) {
88
+ state.quartilesFired.add(q);
89
+ emit(VIDEO_ENGAGEMENT_EVENTS.progress, { percent: q });
90
+ }
91
+ }
92
+ }
93
+ function maybeComplete(reason) {
94
+ if (state.completed || !state.started)
95
+ return;
96
+ const hitThreshold = state.maxPercent >= VIDEO_ENGAGEMENT_THRESHOLDS.completePercent;
97
+ if (reason === 'ended' || hitThreshold) {
98
+ state.completed = true;
99
+ if (reason === 'ended' && state.maxPercent < 100) {
100
+ state.maxPercent = Math.max(state.maxPercent, 100);
101
+ }
102
+ accumulateWatchTime();
103
+ emit(VIDEO_ENGAGEMENT_EVENTS.complete, {
104
+ percent: 100,
105
+ completed: true,
106
+ skipped: false,
107
+ });
108
+ }
109
+ }
110
+ function isSkipped() {
111
+ if (state.completed)
112
+ return false;
113
+ const lowEngagement = state.maxPercent < VIDEO_ENGAGEMENT_THRESHOLDS.skipMaxPercent &&
114
+ state.watchSeconds < VIDEO_ENGAGEMENT_THRESHOLDS.skipMaxWatchSeconds;
115
+ const seekSkip = state.largeForwardSeekCount >= 1 &&
116
+ state.maxPercent < VIDEO_ENGAGEMENT_THRESHOLDS.skipAfterSeekMaxPercent;
117
+ return lowEngagement || seekSkip;
118
+ }
119
+ return {
120
+ markImpression() {
121
+ if (state.impressed)
122
+ return;
123
+ state.impressed = true;
124
+ emit(VIDEO_ENGAGEMENT_EVENTS.impression);
125
+ },
126
+ onPlay(muted) {
127
+ state.muted = muted;
128
+ if (state.mutedAtStart === null) {
129
+ state.mutedAtStart = muted;
130
+ }
131
+ if (!state.started) {
132
+ state.started = true;
133
+ emit(VIDEO_ENGAGEMENT_EVENTS.start, { muted });
134
+ }
135
+ state.playing = true;
136
+ state.playStartedAtMs = Date.now();
137
+ },
138
+ onPause() {
139
+ if (!state.playing)
140
+ return;
141
+ accumulateWatchTime();
142
+ state.playing = false;
143
+ state.playStartedAtMs = null;
144
+ },
145
+ onTime(currentTimeSeconds, durationSeconds) {
146
+ // Keep watch_seconds current during continuous play (not only on pause/exit).
147
+ if (state.playing) {
148
+ accumulateWatchTime();
149
+ }
150
+ updateMaxPercent(currentTimeSeconds, durationSeconds);
151
+ maybeFireQuartiles();
152
+ maybeComplete('threshold');
153
+ },
154
+ onSeek(fromSeconds, toSeconds, durationSeconds) {
155
+ if (durationSeconds > 0) {
156
+ state.durationSeconds = durationSeconds;
157
+ }
158
+ const duration = durationSeconds || state.durationSeconds;
159
+ const classification = classifySeek(fromSeconds, toSeconds, duration);
160
+ updateMaxPercent(toSeconds, duration);
161
+ if (!classification.material) {
162
+ return;
163
+ }
164
+ state.seekCount += 1;
165
+ if (classification.largeForward) {
166
+ state.largeForwardSeekCount += 1;
167
+ }
168
+ if (!state.started)
169
+ return;
170
+ emit(VIDEO_ENGAGEMENT_EVENTS.seek, {
171
+ from_percent: classification.fromPercent,
172
+ to_percent: classification.toPercent,
173
+ direction: classification.direction,
174
+ });
175
+ maybeFireQuartiles();
176
+ maybeComplete('threshold');
177
+ },
178
+ onMuteChange(muted) {
179
+ const wasMuted = state.muted;
180
+ state.muted = muted;
181
+ if (wasMuted && !muted && !state.unmutedFired && state.started) {
182
+ state.unmutedFired = true;
183
+ emit(VIDEO_ENGAGEMENT_EVENTS.unmute, { muted: false });
184
+ }
185
+ },
186
+ onFullscreen(fullscreen) {
187
+ state.fullscreen = fullscreen;
188
+ if (fullscreen) {
189
+ state.usedFullscreen = true;
190
+ }
191
+ if (!state.started && !state.impressed)
192
+ return;
193
+ emit(VIDEO_ENGAGEMENT_EVENTS.fullscreen, { fullscreen });
194
+ },
195
+ onEnded(durationSeconds) {
196
+ accumulateWatchTime();
197
+ state.playing = false;
198
+ state.playStartedAtMs = null;
199
+ if (durationSeconds > 0) {
200
+ state.durationSeconds = durationSeconds;
201
+ state.maxPercent = Math.max(state.maxPercent, 100);
202
+ }
203
+ maybeComplete('ended');
204
+ },
205
+ dispose() {
206
+ accumulateWatchTime();
207
+ state.playing = false;
208
+ state.playStartedAtMs = null;
209
+ if (state.exited)
210
+ return;
211
+ state.exited = true;
212
+ // Exit only if the user started playback (impressions without play stay silent).
213
+ if (!state.started)
214
+ return;
215
+ emit(VIDEO_ENGAGEMENT_EVENTS.exit, {
216
+ completed: state.completed,
217
+ skipped: isSkipped(),
218
+ });
219
+ },
220
+ getState() {
221
+ return state;
222
+ },
223
+ };
224
+ }
225
+ //# sourceMappingURL=createVideoEngagementSession.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createVideoEngagementSession.js","sourceRoot":"","sources":["../../../src/analytics/video/createVideoEngagementSession.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ export { classifySeek, type SeekClassification } from './classifySeek';
2
+ export { type CreateVideoEngagementSessionOptions, createVideoEngagementSession, type VideoEngagementSession, } from './createVideoEngagementSession';
3
+ export { useHtml5VideoEngagement } from './useHtml5VideoEngagement';
4
+ export { type UseVideoEngagementOptions, useVideoEngagement } from './useVideoEngagement';
5
+ export { VIDEO_ENGAGEMENT_EVENT_NAMES, VIDEO_ENGAGEMENT_EVENTS, VIDEO_ENGAGEMENT_THRESHOLDS, type VideoEngagementEventName, type VideoEngagementIdentity, type VideoEngagementTrackFn, type VideoProvider, } from './videoEngagementTypes';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/analytics/video/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,KAAK,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACvE,OAAO,EACL,KAAK,mCAAmC,EACxC,4BAA4B,EAC5B,KAAK,sBAAsB,GAC5B,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,EAAE,KAAK,yBAAyB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1F,OAAO,EACL,4BAA4B,EAC5B,uBAAuB,EACvB,2BAA2B,EAC3B,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,EAC3B,KAAK,aAAa,GACnB,MAAM,wBAAwB,CAAC"}
@@ -0,0 +1,6 @@
1
+ export { classifySeek } from './classifySeek';
2
+ export { createVideoEngagementSession, } from './createVideoEngagementSession';
3
+ export { useHtml5VideoEngagement } from './useHtml5VideoEngagement';
4
+ export { useVideoEngagement } from './useVideoEngagement';
5
+ export { VIDEO_ENGAGEMENT_EVENT_NAMES, VIDEO_ENGAGEMENT_EVENTS, VIDEO_ENGAGEMENT_THRESHOLDS, } from './videoEngagementTypes';
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/analytics/video/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAA2B,MAAM,gBAAgB,CAAC;AACvE,OAAO,EAEL,4BAA4B,GAE7B,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,EAAkC,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1F,OAAO,EACL,4BAA4B,EAC5B,uBAAuB,EACvB,2BAA2B,GAK5B,MAAM,wBAAwB,CAAC"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Singleton loader for the YouTube IFrame Player API.
3
+ * @see https://developers.google.com/youtube/iframe_api_reference
4
+ */
5
+ export interface YouTubePlayer {
6
+ destroy: () => void;
7
+ getCurrentTime: () => number;
8
+ getDuration: () => number;
9
+ isMuted: () => boolean;
10
+ getPlayerState: () => number;
11
+ getIframe: () => HTMLIFrameElement;
12
+ }
13
+ export interface YouTubePlayerEvent {
14
+ target: YouTubePlayer;
15
+ data: number;
16
+ }
17
+ export interface YouTubePlayerConstructor {
18
+ new (element: HTMLElement | string, options: {
19
+ events?: {
20
+ onReady?: (event: YouTubePlayerEvent) => void;
21
+ onStateChange?: (event: YouTubePlayerEvent) => void;
22
+ onError?: (event: YouTubePlayerEvent) => void;
23
+ };
24
+ }): YouTubePlayer;
25
+ PlayerState: {
26
+ UNSTARTED: number;
27
+ ENDED: number;
28
+ PLAYING: number;
29
+ PAUSED: number;
30
+ BUFFERING: number;
31
+ CUED: number;
32
+ };
33
+ }
34
+ declare global {
35
+ interface Window {
36
+ YT?: {
37
+ Player: YouTubePlayerConstructor;
38
+ PlayerState: YouTubePlayerConstructor['PlayerState'];
39
+ };
40
+ onYouTubeIframeAPIReady?: () => void;
41
+ }
42
+ }
43
+ export declare function loadYouTubeIframeApi(): Promise<YouTubePlayerConstructor>;
44
+ /** YT.PlayerState.PLAYING */
45
+ export declare const YT_PLAYING = 1;
46
+ /** YT.PlayerState.PAUSED */
47
+ export declare const YT_PAUSED = 2;
48
+ /** YT.PlayerState.ENDED */
49
+ export declare const YT_ENDED = 0;
50
+ //# sourceMappingURL=loadYouTubeIframeApi.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loadYouTubeIframeApi.d.ts","sourceRoot":"","sources":["../../../src/analytics/video/loadYouTubeIframeApi.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,cAAc,EAAE,MAAM,MAAM,CAAC;IAC7B,WAAW,EAAE,MAAM,MAAM,CAAC;IAC1B,OAAO,EAAE,MAAM,OAAO,CAAC;IACvB,cAAc,EAAE,MAAM,MAAM,CAAC;IAC7B,SAAS,EAAE,MAAM,iBAAiB,CAAC;CACpC;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,aAAa,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,wBAAwB;IACvC,KACE,OAAO,EAAE,WAAW,GAAG,MAAM,EAC7B,OAAO,EAAE;QACP,MAAM,CAAC,EAAE;YACP,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,CAAC;YAC9C,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,CAAC;YACpD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,CAAC;SAC/C,CAAC;KACH,GACA,aAAa,CAAC;IACjB,WAAW,EAAE;QACX,SAAS,EAAE,MAAM,CAAC;QAClB,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,MAAM,CAAC;QAClB,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;CACH;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,EAAE,CAAC,EAAE;YACH,MAAM,EAAE,wBAAwB,CAAC;YACjC,WAAW,EAAE,wBAAwB,CAAC,aAAa,CAAC,CAAC;SACtD,CAAC;QACF,uBAAuB,CAAC,EAAE,MAAM,IAAI,CAAC;KACtC;CACF;AAID,wBAAgB,oBAAoB,IAAI,OAAO,CAAC,wBAAwB,CAAC,CA4CxE;AAED,6BAA6B;AAC7B,eAAO,MAAM,UAAU,IAAI,CAAC;AAC5B,4BAA4B;AAC5B,eAAO,MAAM,SAAS,IAAI,CAAC;AAC3B,2BAA2B;AAC3B,eAAO,MAAM,QAAQ,IAAI,CAAC"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Singleton loader for the YouTube IFrame Player API.
3
+ * @see https://developers.google.com/youtube/iframe_api_reference
4
+ */
5
+ let loadPromise = null;
6
+ export function loadYouTubeIframeApi() {
7
+ if (typeof window === 'undefined') {
8
+ return Promise.reject(new Error('YouTube IFrame API requires a browser'));
9
+ }
10
+ if (window.YT?.Player) {
11
+ return Promise.resolve(window.YT.Player);
12
+ }
13
+ if (loadPromise) {
14
+ return loadPromise;
15
+ }
16
+ loadPromise = new Promise((resolve, reject) => {
17
+ const previous = window.onYouTubeIframeAPIReady;
18
+ window.onYouTubeIframeAPIReady = () => {
19
+ previous?.();
20
+ if (window.YT?.Player) {
21
+ resolve(window.YT.Player);
22
+ }
23
+ else {
24
+ reject(new Error('YouTube IFrame API loaded without YT.Player'));
25
+ }
26
+ };
27
+ const existing = document.querySelector('script[src="https://www.youtube.com/iframe_api"]');
28
+ if (existing) {
29
+ // Script already requested; onYouTubeIframeAPIReady will fire (or already did)
30
+ if (window.YT?.Player) {
31
+ resolve(window.YT.Player);
32
+ }
33
+ return;
34
+ }
35
+ const script = document.createElement('script');
36
+ script.src = 'https://www.youtube.com/iframe_api';
37
+ script.async = true;
38
+ script.onerror = () => {
39
+ loadPromise = null;
40
+ reject(new Error('Failed to load YouTube IFrame API'));
41
+ };
42
+ document.head.appendChild(script);
43
+ });
44
+ return loadPromise;
45
+ }
46
+ /** YT.PlayerState.PLAYING */
47
+ export const YT_PLAYING = 1;
48
+ /** YT.PlayerState.PAUSED */
49
+ export const YT_PAUSED = 2;
50
+ /** YT.PlayerState.ENDED */
51
+ export const YT_ENDED = 0;
52
+ //# sourceMappingURL=loadYouTubeIframeApi.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loadYouTubeIframeApi.js","sourceRoot":"","sources":["../../../src/analytics/video/loadYouTubeIframeApi.ts"],"names":[],"mappings":"AAAA;;;GAGG;AA+CH,IAAI,WAAW,GAA6C,IAAI,CAAC;AAEjE,MAAM,UAAU,oBAAoB;IAClC,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;QAClC,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC,CAAC;IAC5E,CAAC;IAED,IAAI,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC;QACtB,OAAO,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC;IAC3C,CAAC;IAED,IAAI,WAAW,EAAE,CAAC;QAChB,OAAO,WAAW,CAAC;IACrB,CAAC;IAED,WAAW,GAAG,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC5C,MAAM,QAAQ,GAAG,MAAM,CAAC,uBAAuB,CAAC;QAChD,MAAM,CAAC,uBAAuB,GAAG,GAAG,EAAE;YACpC,QAAQ,EAAE,EAAE,CAAC;YACb,IAAI,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC;gBACtB,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC;YAC5B,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC,CAAC;YACnE,CAAC;QACH,CAAC,CAAC;QAEF,MAAM,QAAQ,GAAG,QAAQ,CAAC,aAAa,CAAC,kDAAkD,CAAC,CAAC;QAC5F,IAAI,QAAQ,EAAE,CAAC;YACb,+EAA+E;YAC/E,IAAI,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC;gBACtB,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC;YAC5B,CAAC;YACD,OAAO;QACT,CAAC;QAED,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QAChD,MAAM,CAAC,GAAG,GAAG,oCAAoC,CAAC;QAClD,MAAM,CAAC,KAAK,GAAG,IAAI,CAAC;QACpB,MAAM,CAAC,OAAO,GAAG,GAAG,EAAE;YACpB,WAAW,GAAG,IAAI,CAAC;YACnB,MAAM,CAAC,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC,CAAC;QACzD,CAAC,CAAC;QACF,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;IAEH,OAAO,WAAW,CAAC;AACrB,CAAC;AAED,6BAA6B;AAC7B,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,CAAC;AAC5B,4BAA4B;AAC5B,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,CAAC;AAC3B,2BAA2B;AAC3B,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { RefObject } from 'react';
2
+ import type { VideoEngagementSession } from './createVideoEngagementSession';
3
+ /**
4
+ * Wires native HTMLMediaElement events into a video engagement session.
5
+ * No-ops when sessionRef.current is null (autoplay / no analytics).
6
+ */
7
+ export declare function useHtml5VideoEngagement(videoRef: RefObject<HTMLVideoElement | null>, sessionRef: RefObject<VideoEngagementSession | null>, enabled: boolean): void;
8
+ //# sourceMappingURL=useHtml5VideoEngagement.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useHtml5VideoEngagement.d.ts","sourceRoot":"","sources":["../../../src/analytics/video/useHtml5VideoEngagement.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AAE7E;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,SAAS,CAAC,gBAAgB,GAAG,IAAI,CAAC,EAC5C,UAAU,EAAE,SAAS,CAAC,sBAAsB,GAAG,IAAI,CAAC,EACpD,OAAO,EAAE,OAAO,GACf,IAAI,CA8DN"}
@@ -0,0 +1,67 @@
1
+ 'use client';
2
+ import { useEffect, useRef } from 'react';
3
+ /**
4
+ * Wires native HTMLMediaElement events into a video engagement session.
5
+ * No-ops when sessionRef.current is null (autoplay / no analytics).
6
+ */
7
+ export function useHtml5VideoEngagement(videoRef, sessionRef, enabled) {
8
+ const lastTimeRef = useRef(0);
9
+ useEffect(() => {
10
+ if (!enabled)
11
+ return;
12
+ const video = videoRef.current;
13
+ if (!video)
14
+ return;
15
+ const session = () => sessionRef.current;
16
+ const onPlay = () => {
17
+ session()?.onPlay(video.muted || video.volume === 0);
18
+ lastTimeRef.current = video.currentTime;
19
+ };
20
+ const onPause = () => {
21
+ session()?.onPause();
22
+ };
23
+ const onTimeUpdate = () => {
24
+ session()?.onTime(video.currentTime, video.duration || 0);
25
+ lastTimeRef.current = video.currentTime;
26
+ };
27
+ const onSeeking = () => {
28
+ // capture pre-seek time is already in lastTimeRef from timeupdate
29
+ };
30
+ const onSeeked = () => {
31
+ const from = lastTimeRef.current;
32
+ const to = video.currentTime;
33
+ session()?.onSeek(from, to, video.duration || 0);
34
+ lastTimeRef.current = to;
35
+ };
36
+ const onVolumeChange = () => {
37
+ session()?.onMuteChange(video.muted || video.volume === 0);
38
+ };
39
+ const onEnded = () => {
40
+ session()?.onEnded(video.duration || 0);
41
+ };
42
+ const onFullscreenChange = () => {
43
+ const fsEl = document.fullscreenElement;
44
+ const isFs = fsEl === video || !!fsEl?.contains(video);
45
+ session()?.onFullscreen(isFs);
46
+ };
47
+ video.addEventListener('play', onPlay);
48
+ video.addEventListener('pause', onPause);
49
+ video.addEventListener('timeupdate', onTimeUpdate);
50
+ video.addEventListener('seeking', onSeeking);
51
+ video.addEventListener('seeked', onSeeked);
52
+ video.addEventListener('volumechange', onVolumeChange);
53
+ video.addEventListener('ended', onEnded);
54
+ document.addEventListener('fullscreenchange', onFullscreenChange);
55
+ return () => {
56
+ video.removeEventListener('play', onPlay);
57
+ video.removeEventListener('pause', onPause);
58
+ video.removeEventListener('timeupdate', onTimeUpdate);
59
+ video.removeEventListener('seeking', onSeeking);
60
+ video.removeEventListener('seeked', onSeeked);
61
+ video.removeEventListener('volumechange', onVolumeChange);
62
+ video.removeEventListener('ended', onEnded);
63
+ document.removeEventListener('fullscreenchange', onFullscreenChange);
64
+ };
65
+ }, [videoRef, sessionRef, enabled]);
66
+ }
67
+ //# sourceMappingURL=useHtml5VideoEngagement.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useHtml5VideoEngagement.js","sourceRoot":"","sources":["../../../src/analytics/video/useHtml5VideoEngagement.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import type { IAnalyticsContext } from '@se-studio/core-data-types';
2
+ import { type MutableRefObject, type RefObject } from 'react';
3
+ import { type VideoEngagementSession } from './createVideoEngagementSession';
4
+ import { type VideoEngagementIdentity } from './videoEngagementTypes';
5
+ export interface UseVideoEngagementOptions {
6
+ identity: VideoEngagementIdentity;
7
+ analyticsContext: IAnalyticsContext | null | undefined;
8
+ /** When false, no session is created (autoplay / background). */
9
+ enabled?: boolean;
10
+ }
11
+ /**
12
+ * Creates a video engagement session bound to `trackEvent` when analytics context is present.
13
+ * Observes `rootRef` for impression; disposes on unmount / pagehide.
14
+ */
15
+ export declare function useVideoEngagement({ identity, analyticsContext, enabled, }: UseVideoEngagementOptions): {
16
+ sessionRef: MutableRefObject<VideoEngagementSession | null>;
17
+ rootRef: RefObject<HTMLElement | null>;
18
+ };
19
+ //# sourceMappingURL=useVideoEngagement.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useVideoEngagement.d.ts","sourceRoot":"","sources":["../../../src/analytics/video/useVideoEngagement.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AACpE,OAAO,EAAE,KAAK,gBAAgB,EAAE,KAAK,SAAS,EAAqB,MAAM,OAAO,CAAC;AAEjF,OAAO,EAEL,KAAK,sBAAsB,EAC5B,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAA+B,KAAK,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAEnG,MAAM,WAAW,yBAAyB;IACxC,QAAQ,EAAE,uBAAuB,CAAC;IAClC,gBAAgB,EAAE,iBAAiB,GAAG,IAAI,GAAG,SAAS,CAAC;IACvD,iEAAiE;IACjE,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,QAAQ,EACR,gBAAgB,EAChB,OAAc,GACf,EAAE,yBAAyB,GAAG;IAC7B,UAAU,EAAE,gBAAgB,CAAC,sBAAsB,GAAG,IAAI,CAAC,CAAC;IAC5D,OAAO,EAAE,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;CACxC,CA+EA"}