@xhub-short/ui 0.1.0-beta.9 → 1.0.0-beta.18

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 (74) hide show
  1. package/dist/CommentSheet.css-DuBy01rU.d.ts +219 -0
  2. package/dist/VideoSlotPlayIndicator-DPs8Xt5C.d.ts +51 -0
  3. package/dist/chunk-3OB3OVYR.js +349 -0
  4. package/dist/chunk-4RIMQOBR.js +58 -0
  5. package/dist/chunk-5MKYDI4X.js +1 -0
  6. package/dist/chunk-5Y43HNRG.js +296 -0
  7. package/dist/chunk-7WXAQHJI.js +350 -0
  8. package/dist/chunk-BADA7OLG.js +564 -0
  9. package/dist/chunk-BEJAJFV6.js +191 -0
  10. package/dist/chunk-BNI7CYRI.js +334 -0
  11. package/dist/{chunk-RMLTPW5S.js → chunk-CAWE42LH.js} +4 -3
  12. package/dist/chunk-DGKMO3AE.js +717 -0
  13. package/dist/chunk-DR7KR7OT.js +103 -0
  14. package/dist/chunk-GSNIZ6DF.js +605 -0
  15. package/dist/chunk-HXQPEZRG.js +105 -0
  16. package/dist/{chunk-FNXTPQ6L.js → chunk-IC2KUU4V.js} +31 -1340
  17. package/dist/chunk-IWSBYOSS.js +91 -0
  18. package/dist/chunk-MFJS65C5.js +368 -0
  19. package/dist/{chunk-AC2IFAJR.js → chunk-NJXIYSDZ.js} +12 -1
  20. package/dist/{chunk-CL6BS7GB.js → chunk-OM4L7RE5.js} +12 -2
  21. package/dist/chunk-QCRRF76W.js +75 -0
  22. package/dist/chunk-QUEJHA24.js +508 -0
  23. package/dist/chunk-UYBQTE4M.js +337 -0
  24. package/dist/chunk-VJ744W5N.js +603 -0
  25. package/dist/chunk-VXW7AOGM.js +285 -0
  26. package/dist/{chunk-AQHD6LPS.js → chunk-YB7AXTX7.js} +1 -1
  27. package/dist/components/ActionBar/index.d.ts +7 -2
  28. package/dist/components/ActionBar/index.js +1 -1
  29. package/dist/components/AdvanceMenu/index.d.ts +80 -0
  30. package/dist/components/AdvanceMenu/index.js +1 -0
  31. package/dist/components/ArticleSlot/index.d.ts +213 -0
  32. package/dist/components/ArticleSlot/index.js +1 -0
  33. package/dist/components/AuthorInfo/index.js +1 -1
  34. package/dist/components/BottomSheet/index.d.ts +87 -0
  35. package/dist/components/BottomSheet/index.js +1 -0
  36. package/dist/components/CleanModeOverlay/index.d.ts +60 -0
  37. package/dist/components/CleanModeOverlay/index.js +1 -0
  38. package/dist/components/CommentSheet/index.d.ts +1 -1
  39. package/dist/components/CommentSheet/index.js +1 -1
  40. package/dist/components/DetailView/index.d.ts +314 -0
  41. package/dist/components/DetailView/index.js +1 -0
  42. package/dist/components/ErrorBoundary/index.js +1 -1
  43. package/dist/components/{VideoFeed → Feed}/index.d.ts +64 -45
  44. package/dist/components/Feed/index.js +1 -0
  45. package/dist/components/ImageCarousel/index.d.ts +50 -0
  46. package/dist/components/ImageCarousel/index.js +1 -0
  47. package/dist/components/Playlist/index.d.ts +117 -0
  48. package/dist/components/Playlist/index.js +1 -0
  49. package/dist/components/ProgressBar/index.js +1 -1
  50. package/dist/components/QualityPicker/index.d.ts +35 -0
  51. package/dist/components/QualityPicker/index.js +1 -0
  52. package/dist/components/ReportSheet/index.d.ts +74 -0
  53. package/dist/components/ReportSheet/index.js +1 -0
  54. package/dist/components/Skeleton/index.js +1 -1
  55. package/dist/components/SpeedPicker/index.d.ts +32 -0
  56. package/dist/components/SpeedPicker/index.js +1 -0
  57. package/dist/components/VideoInfo/index.d.ts +7 -3
  58. package/dist/components/VideoInfo/index.js +1 -1
  59. package/dist/components/VideoPlayer/index.js +1 -1
  60. package/dist/components/VideoSlot/index.d.ts +31 -68
  61. package/dist/components/VideoSlot/index.js +2 -1
  62. package/dist/components/VirtualSlider/index.js +1 -1
  63. package/dist/index.d.ts +40 -10
  64. package/dist/index.js +23 -13
  65. package/package.json +8 -4
  66. package/dist/CommentSheet.css-BeCrEaUG.d.ts +0 -221
  67. package/dist/chunk-2FSDVYER.js +0 -737
  68. package/dist/chunk-ECR42RKK.js +0 -571
  69. package/dist/chunk-KWHMZ6H5.js +0 -562
  70. package/dist/chunk-SZXFH334.js +0 -204
  71. package/dist/chunk-UNV3NWN6.js +0 -148
  72. package/dist/chunk-WCRDTBCZ.js +0 -357
  73. package/dist/chunk-XDIH66C4.js +0 -1519
  74. package/dist/components/VideoFeed/index.js +0 -1
@@ -0,0 +1,603 @@
1
+ import { useDoubleTap, VideoSlotContext, useVideoSlotContext } from './chunk-MFJS65C5.js';
2
+ import { HeartFilledIcon } from './chunk-ANCP53F3.js';
3
+ import { clsx2 } from './chunk-EDWS2IPH.js';
4
+ import { injectComponentCSS } from './chunk-CAWE42LH.js';
5
+ import { memo, useMemo, useState, useCallback, useEffect, useInsertionEffect, useRef } from 'react';
6
+ import { jsx, jsxs } from 'react/jsx-runtime';
7
+
8
+ // src/components/VideoSlot/VideoSlot.css.ts
9
+ var VIDEO_SLOT_CSS = `.sv-video-slot{position:relative;width:100%;height:100%;overflow:hidden;background-color:var(--sv-bg-primary,#000);touch-action:none;user-select:none;-webkit-user-select:none;will-change:contents;contain:layout style paint}.sv-video-slot--active{z-index:1}.sv-video-slot--loading .sv-video-slot__video{opacity:0}.sv-video-slot--loading .sv-video-slot__poster{opacity:1}.sv-video-slot--playing .sv-video-slot__video{opacity:1}.sv-video-slot--playing .sv-video-slot__poster{opacity:0}.sv-video-slot--paused .sv-video-slot__video{opacity:1}.sv-video-slot--paused .sv-video-slot__poster{opacity:0}.sv-video-slot__poster--hidden{opacity:0;pointer-events:none}.sv-video-slot--error{display:flex;align-items:center;justify-content:center}.sv-video-slot__video-container{position:absolute;inset:0;display:flex;align-items:center;justify-content:center}.sv-video-slot__video{width:100%;height:100%;object-fit:cover;background-color:transparent;transition:opacity var(--sv-transition-duration,300ms)ease-out}.sv-video-slot__video[data-loaded="true"]{opacity:1}.sv-video-slot__poster{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background-color:var(--sv-bg-primary,#000);z-index:var(--sv-z-poster,3);transition:opacity var(--sv-transition-duration,300ms)ease-out;pointer-events:none}.sv-video-slot__poster-image{width:100%;height:100%;object-fit:cover}.sv-video-slot__poster--first-frame .sv-video-slot__poster-image{image-rendering:auto}.sv-video-slot__poster--skeleton{background:linear-gradient(110deg,var(--sv-skeleton-bg,rgba(255,255,255,.05))0%,var(--sv-skeleton-shimmer,rgba(255,255,255,.1))50%,var(--sv-skeleton-bg,rgba(255,255,255,.05))100%);background-size:200% 100%;animation:sv-slot-skeleton-shimmer 1.5s ease-in-out infinite}@keyframes sv-slot-skeleton-shimmer{0%{background-position:200% 0}100%{background-position:-200% 0}}.sv-video-slot__overlay{position:absolute;inset:0;z-index:var(--sv-z-overlay,2);background:linear-gradient(to top,rgba(0,0,0,.6)0%,rgba(0,0,0,.3)20%,transparent 40%,transparent 60%,rgba(0,0,0,.2)80%,rgba(0,0,0,.4)100%);pointer-events:none}.sv-video-slot__overlay-top,.sv-video-slot__overlay-bottom,.sv-video-slot__overlay-left,.sv-video-slot__overlay-right,.sv-video-slot__actions,.sv-video-slot__author{pointer-events:none}.sv-video-slot__overlay button,.sv-video-slot__overlay a,.sv-video-slot__overlay [role="button"],.sv-video-slot__overlay input,.sv-video-slot__overlay label,.sv-video-slot__overlay .sv-progress-bar,.sv-video-slot__overlay .sv-author-info,.sv-video-slot__overlay .sv-video-info,.sv-video-slot__overlay .sv-action-bar{pointer-events:auto}.sv-video-slot__overlay-top{position:absolute;top:0;left:0;right:0;padding:var(--sv-spacing-md,16px);padding-top:calc(var(--sv-safe-area-top,0)+var(--sv-spacing-md,16px))}.sv-video-slot__overlay-bottom{position:absolute;bottom:0;left:0;right:0;padding:var(--sv-spacing-md,16px);padding-bottom:calc(var(--sv-safe-area-bottom,0)+var(--sv-spacing-md,16px));display:flex;flex-direction:column;gap:var(--sv-spacing-sm,8px)}.sv-video-slot__overlay-left{position:absolute;top:50%;left:0;transform:translateY(-50%);padding:var(--sv-spacing-md,16px)}.sv-video-slot__overlay-right{position:absolute;top:50%;right:0;transform:translateY(-50%);padding:var(--sv-spacing-md,16px)}.sv-video-slot__actions{position:absolute;right:var(--sv-spacing-sm,8px);bottom:calc(var(--sv-safe-area-bottom,0)+var(--sv-actions-bottom,80px)+var(--sv-playlist-bar-offset,0));display:flex;flex-direction:column;align-items:center;transition:bottom .3s ease-in-out}.sv-video-slot__author{position:absolute;left:var(--sv-spacing-md,16px);bottom:calc(var(--sv-safe-area-bottom,0)+var(--sv-author-bottom,100px)+var(--sv-playlist-bar-offset,0));right:80px;max-width:calc(100% - 100px);transition:bottom .3s ease-in-out}.sv-video-slot__error{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;background-color:var(--sv-bg-primary,#000);color:var(--sv-text-secondary,#999);text-align:center;padding:var(--sv-spacing-lg,20px);gap:var(--sv-spacing-md,16px);z-index:var(--sv-z-indicator,3)}.sv-video-slot__error-icon{font-size:48px;opacity:.6}.sv-video-slot__error-message{font-size:var(--sv-font-size-md,14px);max-width:200px}.sv-video-slot__error-retry{padding:var(--sv-spacing-sm,8px)var(--sv-spacing-md,16px);background-color:var(--sv-color-primary,#fe2c55);color:#fff;border:0;border-radius:var(--sv-radius-md,8px);font-size:var(--sv-font-size-sm,12px);font-weight:600;cursor:pointer;transition:background-color 150ms ease}.sv-video-slot__error-retry:hover{background-color:var(--sv-color-primary-hover,#e02850)}.sv-video-slot__error-retry:active{transform:scale(.96)}.sv-video-slot__spinner{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);z-index:var(--sv-z-slot-spinner,2)}.sv-video-slot__spinner-circle{width:40px;height:40px;border:3px solid rgba(255,255,255,.2);border-top-color:var(--sv-color-primary,#fe2c55);border-radius:50%;animation:sv-slot-spin .8s linear infinite}@keyframes sv-slot-spin{to{transform:rotate(360deg)}}.sv-video-slot__play-indicator{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:64px;height:64px;display:flex;align-items:center;justify-content:center;border-radius:50%;z-index:var(--sv-z-indicator,3);opacity:0;transition:opacity 200ms ease-out;pointer-events:none}.sv-video-slot__play-indicator--visible{opacity:.65}.sv-video-slot__play-indicator--persist{opacity:.65}.sv-video-slot__play-indicator--animating-out{animation:sv-slot-indicator-out var(--sv-indicator-duration,200ms)ease-out forwards}@keyframes sv-slot-indicator-out{0%{opacity:1;transform:translate(-50%,-50%)scale(1)}100%{opacity:0;transform:translate(-50%,-50%)scale(1.5)}}.sv-video-slot__play-indicator-icon{color:#fff;font-size:28px;width:100%;height:100%;display:flex;align-items:center;justify-content:center}.sv-video-slot__progress{position:absolute;bottom:0;left:0;right:0;height:3px;background-color:rgba(255,255,255,.2);z-index:var(--sv-z-progress,4)}.sv-video-slot__progress-bar{height:100%;background-color:var(--sv-color-primary,#fe2c55);transition:width 100ms linear}.sv-video-slot__progress-buffered{position:absolute;top:0;left:0;height:100%;background-color:rgba(255,255,255,.3)}`;
10
+
11
+ // src/components/VideoSlot/constants.ts
12
+ var VIDEO_ID_ATTR = "data-video-id";
13
+ var VIDEO_ID_DATASET_KEY = "videoId";
14
+ var SLOT_ACTIVE_ATTR = "data-slot-active";
15
+ var SLOT_ACTIVE_DATASET_KEY = "slotActive";
16
+ var PLAYER_STATE_ATTR = "data-player-state";
17
+ var PLAYER_STATE_DATASET_KEY = "playerState";
18
+ var LOADING_ATTR = "data-loading";
19
+ var LOADING_DATASET_KEY = "loading";
20
+ var SLOT_CLASS = "sv-video-slot";
21
+ var SLOT_ACTIVE_CLASS = "sv-video-slot--active";
22
+ var SLOT_LOADING_CLASS = "sv-video-slot--loading";
23
+ var SLOT_ERROR_CLASS = "sv-video-slot--error";
24
+ var SLOT_PLAYING_CLASS = "sv-video-slot--playing";
25
+ var SLOT_PAUSED_CLASS = "sv-video-slot--paused";
26
+ var Z_INDEX = {
27
+ /** Base layer - slot container */
28
+ SLOT: 0,
29
+ /** Video element */
30
+ VIDEO: 1,
31
+ /** Loading spinner */
32
+ SPINNER: 2,
33
+ /** Poster/thumbnail (above spinner so thumbnail shows during loading) */
34
+ POSTER: 3,
35
+ /** Error UI and play/pause indicator */
36
+ INDICATOR: 4,
37
+ /** Progress bar (always on top) */
38
+ PROGRESS: 5
39
+ };
40
+ var Z_INDEX_CSS_VARS = {
41
+ POSTER: "--sv-z-poster",
42
+ SPINNER: "--sv-z-slot-spinner",
43
+ INDICATOR: "--sv-z-indicator",
44
+ PROGRESS: "--sv-z-progress"
45
+ };
46
+ var ANIMATION_SELECTOR = '[data-like-animation="true"]';
47
+ function VideoSlotHeadlessBase({
48
+ video,
49
+ resourceState,
50
+ playerState,
51
+ playerControls,
52
+ className,
53
+ children,
54
+ // TODO: Implement visibility detection with IntersectionObserver in v2
55
+ // These callbacks are reserved for future implementation:
56
+ // - onVisible: Called when slot enters viewport (for analytics, autoplay)
57
+ // - onHidden: Called when slot leaves viewport (for pause, cleanup)
58
+ onVisible: _onVisible,
59
+ onHidden: _onHidden,
60
+ onTap,
61
+ onDoubleTap,
62
+ onLongPress,
63
+ disableTap,
64
+ renderError,
65
+ renderLoading,
66
+ restoreFrame,
67
+ style
68
+ }) {
69
+ useInsertionEffect(() => {
70
+ return injectComponentCSS("video-slot", VIDEO_SLOT_CSS);
71
+ }, []);
72
+ const containerRef = useRef(null);
73
+ const animationElementRef = useRef(null);
74
+ const prevVideoIdRef = useRef(video.id);
75
+ const [firstFrame, setFirstFrame] = useState(null);
76
+ const [lastUserToggleTime, setLastUserToggleTime] = useState(0);
77
+ if (prevVideoIdRef.current !== video.id) {
78
+ prevVideoIdRef.current = video.id;
79
+ animationElementRef.current = null;
80
+ if (firstFrame !== null) {
81
+ setFirstFrame(null);
82
+ }
83
+ }
84
+ const getAnimationElement = useCallback(() => {
85
+ if (animationElementRef.current) {
86
+ return animationElementRef.current;
87
+ }
88
+ const container = containerRef.current;
89
+ if (!container) return null;
90
+ const element = container.querySelector(ANIMATION_SELECTOR);
91
+ if (element) {
92
+ animationElementRef.current = element;
93
+ }
94
+ return element;
95
+ }, []);
96
+ const triggerLikeAnimation = useCallback(
97
+ (position) => {
98
+ const animationElement = getAnimationElement();
99
+ if (animationElement?.__likeAnimation) {
100
+ animationElement.__likeAnimation.triggerHeart(position.x, position.y);
101
+ }
102
+ },
103
+ [getAnimationElement]
104
+ );
105
+ const shouldShowAnimationWhenLiked = useCallback(() => {
106
+ const animationElement = getAnimationElement();
107
+ return animationElement?.__showWhenAlreadyLiked ?? true;
108
+ }, [getAnimationElement]);
109
+ const handleSingleTap = useCallback(() => {
110
+ if (disableTap) return;
111
+ setLastUserToggleTime(Date.now());
112
+ if (onTap) {
113
+ onTap();
114
+ } else {
115
+ playerControls.toggle();
116
+ }
117
+ }, [disableTap, onTap, playerControls]);
118
+ const handleDoubleTap = useCallback(
119
+ (position) => {
120
+ if (shouldShowAnimationWhenLiked() || !playerState.isPlaying) {
121
+ triggerLikeAnimation(position);
122
+ }
123
+ onDoubleTap?.();
124
+ },
125
+ [onDoubleTap, shouldShowAnimationWhenLiked, playerState.isPlaying, triggerLikeAnimation]
126
+ );
127
+ const handleLongPress = useCallback(
128
+ (_position) => {
129
+ onLongPress?.();
130
+ },
131
+ [onLongPress]
132
+ );
133
+ const { handlers: tapHandlers } = useDoubleTap({
134
+ containerRef,
135
+ onSingleTap: handleSingleTap,
136
+ onDoubleTap: handleDoubleTap,
137
+ onLongPress: onLongPress ? handleLongPress : void 0
138
+ });
139
+ const handleRetry = useCallback(() => {
140
+ playerControls.play();
141
+ }, [playerControls]);
142
+ const contextValue = useMemo(
143
+ () => ({
144
+ video,
145
+ resourceState,
146
+ playerState,
147
+ playerControls,
148
+ firstFrame,
149
+ setFirstFrame,
150
+ lastUserToggleTime,
151
+ setLastUserToggleTime
152
+ }),
153
+ [video, resourceState, playerState, playerControls, firstFrame, lastUserToggleTime]
154
+ );
155
+ const stateClasses = useMemo(() => {
156
+ const classes = [SLOT_CLASS];
157
+ if (resourceState.isActive) {
158
+ classes.push(SLOT_ACTIVE_CLASS);
159
+ }
160
+ if (playerState.isLoading) {
161
+ classes.push(SLOT_LOADING_CLASS);
162
+ }
163
+ if (playerState.error) {
164
+ classes.push(SLOT_ERROR_CLASS);
165
+ }
166
+ if (playerState.isPlaying) {
167
+ classes.push(SLOT_PLAYING_CLASS);
168
+ } else {
169
+ classes.push(SLOT_PAUSED_CLASS);
170
+ }
171
+ return classes;
172
+ }, [resourceState.isActive, playerState.isLoading, playerState.error, playerState.isPlaying]);
173
+ const playerStateString = useMemo(() => {
174
+ if (playerState.error) return "error";
175
+ if (playerState.isLoading) return "loading";
176
+ if (playerState.isPlaying) return "playing";
177
+ return "paused";
178
+ }, [playerState]);
179
+ return /* @__PURE__ */ jsx(VideoSlotContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxs(
180
+ "div",
181
+ {
182
+ ref: containerRef,
183
+ className: clsx2(...stateClasses, className),
184
+ ...tapHandlers,
185
+ ...{
186
+ [VIDEO_ID_ATTR]: video.id,
187
+ [SLOT_ACTIVE_ATTR]: resourceState.isActive ? "true" : "false",
188
+ [PLAYER_STATE_ATTR]: playerStateString,
189
+ [LOADING_ATTR]: playerState.isLoading ? "true" : "false"
190
+ },
191
+ style,
192
+ children: [
193
+ restoreFrame && !playerState.isPlaying && !playerState.error && /* @__PURE__ */ jsx(
194
+ "div",
195
+ {
196
+ className: "sv-video-slot__restore-frame",
197
+ style: {
198
+ position: "absolute",
199
+ inset: 0,
200
+ zIndex: 5,
201
+ // Above video, below overlay
202
+ backgroundImage: `url(${restoreFrame})`,
203
+ backgroundSize: "contain",
204
+ backgroundPosition: "center",
205
+ backgroundRepeat: "no-repeat",
206
+ backgroundColor: "#000"
207
+ },
208
+ "aria-hidden": "true"
209
+ }
210
+ ),
211
+ playerState.error && (renderError ? renderError(playerState.error, handleRetry) : /* @__PURE__ */ jsx(DefaultErrorUI, { error: playerState.error, onRetry: handleRetry })),
212
+ playerState.isLoading && !playerState.error && !restoreFrame && (renderLoading ? renderLoading() : /* @__PURE__ */ jsx(DefaultLoadingUI, {})),
213
+ children
214
+ ]
215
+ }
216
+ ) });
217
+ }
218
+ function getUserFriendlyErrorMessage(error) {
219
+ const message = error.message?.toLowerCase() ?? "";
220
+ if (message.includes("network") || message.includes("fetch") || message.includes("offline")) {
221
+ return "Unable to connect. Please check your internet connection.";
222
+ }
223
+ if (message.includes("timeout") || message.includes("timed out")) {
224
+ return "Video took too long to load. Please try again.";
225
+ }
226
+ if (message.includes("format") || message.includes("codec") || message.includes("unsupported") || message.includes("media")) {
227
+ return "This video format is not supported.";
228
+ }
229
+ if (message.includes("blocked") || message.includes("denied") || message.includes("cors")) {
230
+ return "Video playback is restricted.";
231
+ }
232
+ if (message.includes("404") || message.includes("not found")) {
233
+ return "Video not available.";
234
+ }
235
+ if (message.includes("500") || message.includes("server")) {
236
+ return "Server error. Please try again later.";
237
+ }
238
+ if (message.includes("hls") || message.includes("manifest") || message.includes("segment")) {
239
+ return "Video streaming error. Please try again.";
240
+ }
241
+ return "Video failed to load. Tap to retry.";
242
+ }
243
+ function DefaultErrorUI({
244
+ error,
245
+ onRetry
246
+ }) {
247
+ const displayMessage = getUserFriendlyErrorMessage(error);
248
+ return /* @__PURE__ */ jsxs("div", { className: "sv-video-slot__error", children: [
249
+ /* @__PURE__ */ jsx("div", { className: "sv-video-slot__error-icon", children: "\u26A0\uFE0F" }),
250
+ /* @__PURE__ */ jsx("div", { className: "sv-video-slot__error-message", children: displayMessage }),
251
+ /* @__PURE__ */ jsx(
252
+ "button",
253
+ {
254
+ type: "button",
255
+ className: "sv-video-slot__error-retry",
256
+ onClick: (e) => {
257
+ e.stopPropagation();
258
+ onRetry();
259
+ },
260
+ children: "Retry"
261
+ }
262
+ )
263
+ ] });
264
+ }
265
+ function DefaultLoadingUI() {
266
+ return /* @__PURE__ */ jsx("div", { className: "sv-video-slot__spinner", children: /* @__PURE__ */ jsx("div", { className: "sv-video-slot__spinner-circle", "aria-label": "Loading video" }) });
267
+ }
268
+ var VideoSlotHeadless = memo(VideoSlotHeadlessBase);
269
+ VideoSlotHeadless.displayName = "VideoSlotHeadless";
270
+ var VideoSlotPosterInner = memo(function VideoSlotPosterInner2({
271
+ className,
272
+ forceSkeleton = false,
273
+ posterUrl,
274
+ loading = "eager",
275
+ alt,
276
+ onError,
277
+ videoPoster,
278
+ videoTitle,
279
+ firstFrame,
280
+ isLoading,
281
+ currentTime
282
+ }) {
283
+ const imageSource = useMemo(() => {
284
+ if (forceSkeleton) return null;
285
+ if (posterUrl) return posterUrl;
286
+ if (firstFrame) return firstFrame;
287
+ if (videoPoster) return videoPoster;
288
+ return null;
289
+ }, [forceSkeleton, posterUrl, firstFrame, videoPoster]);
290
+ const sourceKey = imageSource ?? "skeleton";
291
+ const [errorForSource, setErrorForSource] = useState(null);
292
+ const imageError = errorForSource === sourceKey;
293
+ const handleImageError = useCallback(() => {
294
+ setErrorForSource(sourceKey);
295
+ onError?.(new Error("Failed to load poster image"));
296
+ }, [sourceKey, onError]);
297
+ const showSkeleton = !imageSource || imageError;
298
+ const posterType = useMemo(() => {
299
+ if (showSkeleton) return "skeleton";
300
+ if (imageSource === firstFrame) return "first-frame";
301
+ return "poster";
302
+ }, [showSkeleton, imageSource, firstFrame]);
303
+ const imageAlt = alt ?? videoTitle ?? "";
304
+ const isVisible = isLoading && currentTime === 0;
305
+ return /* @__PURE__ */ jsx(
306
+ "div",
307
+ {
308
+ className: clsx2(
309
+ "sv-video-slot__poster",
310
+ `sv-video-slot__poster--${posterType}`,
311
+ !isVisible && "sv-video-slot__poster--hidden",
312
+ className
313
+ ),
314
+ "aria-hidden": !isVisible,
315
+ children: imageSource && !imageError ? /* @__PURE__ */ jsx(
316
+ "img",
317
+ {
318
+ className: "sv-video-slot__poster-image",
319
+ src: imageSource,
320
+ alt: imageAlt,
321
+ loading,
322
+ decoding: "async",
323
+ onError: handleImageError
324
+ }
325
+ ) : (
326
+ // Skeleton shimmer - pure CSS animation, no JS
327
+ /* @__PURE__ */ jsx("div", { className: "sv-video-slot__poster-skeleton", "aria-hidden": "true" })
328
+ )
329
+ }
330
+ );
331
+ });
332
+ VideoSlotPosterInner.displayName = "VideoSlotPosterInner";
333
+ function VideoSlotPoster(props) {
334
+ const { video, firstFrame, playerState } = useVideoSlotContext();
335
+ return /* @__PURE__ */ jsx(
336
+ VideoSlotPosterInner,
337
+ {
338
+ ...props,
339
+ videoPoster: video.poster,
340
+ videoTitle: video.title,
341
+ firstFrame,
342
+ isLoading: playerState.isLoading,
343
+ currentTime: playerState.currentTime
344
+ }
345
+ );
346
+ }
347
+ VideoSlotPoster.displayName = "VideoSlotPoster";
348
+ function VideoSlotSkeleton({
349
+ className
350
+ }) {
351
+ return /* @__PURE__ */ jsx(
352
+ "div",
353
+ {
354
+ className: clsx2("sv-video-slot__poster", "sv-video-slot__poster--skeleton", className),
355
+ "aria-hidden": "true",
356
+ children: /* @__PURE__ */ jsx("div", { className: "sv-video-slot__poster-skeleton" })
357
+ }
358
+ );
359
+ }
360
+ VideoSlotSkeleton.displayName = "VideoSlotSkeleton";
361
+ function VideoSlotOverlayRoot({
362
+ children,
363
+ className,
364
+ noGradient = false
365
+ }) {
366
+ return /* @__PURE__ */ jsx(
367
+ "div",
368
+ {
369
+ className: clsx2(
370
+ "sv-video-slot__overlay",
371
+ noGradient && "sv-video-slot__overlay--no-gradient",
372
+ className
373
+ ),
374
+ children
375
+ }
376
+ );
377
+ }
378
+ function VideoSlotOverlayTop({
379
+ children,
380
+ className
381
+ }) {
382
+ return /* @__PURE__ */ jsx("div", { className: clsx2("sv-video-slot__overlay-top", className), children });
383
+ }
384
+ function VideoSlotOverlayBottom({
385
+ children,
386
+ className
387
+ }) {
388
+ return /* @__PURE__ */ jsx("div", { className: clsx2("sv-video-slot__overlay-bottom", className), children });
389
+ }
390
+ function VideoSlotOverlayLeft({
391
+ children,
392
+ className
393
+ }) {
394
+ return /* @__PURE__ */ jsx("div", { className: clsx2("sv-video-slot__overlay-left", className), children });
395
+ }
396
+ function VideoSlotOverlayRight({
397
+ children,
398
+ className
399
+ }) {
400
+ return /* @__PURE__ */ jsx("div", { className: clsx2("sv-video-slot__overlay-right", className), children });
401
+ }
402
+ function VideoSlotOverlayActions({
403
+ children,
404
+ className
405
+ }) {
406
+ return /* @__PURE__ */ jsx("div", { className: clsx2("sv-video-slot__actions", className), children });
407
+ }
408
+ function VideoSlotOverlayAuthor({
409
+ children,
410
+ className
411
+ }) {
412
+ return /* @__PURE__ */ jsx("div", { className: clsx2("sv-video-slot__author", className), children });
413
+ }
414
+ VideoSlotOverlayRoot.displayName = "VideoSlotOverlay";
415
+ VideoSlotOverlayTop.displayName = "VideoSlotOverlay.Top";
416
+ VideoSlotOverlayBottom.displayName = "VideoSlotOverlay.Bottom";
417
+ VideoSlotOverlayLeft.displayName = "VideoSlotOverlay.Left";
418
+ VideoSlotOverlayRight.displayName = "VideoSlotOverlay.Right";
419
+ VideoSlotOverlayActions.displayName = "VideoSlotOverlay.Actions";
420
+ VideoSlotOverlayAuthor.displayName = "VideoSlotOverlay.Author";
421
+ var VideoSlotOverlay = Object.assign(VideoSlotOverlayRoot, {
422
+ Top: VideoSlotOverlayTop,
423
+ Bottom: VideoSlotOverlayBottom,
424
+ Left: VideoSlotOverlayLeft,
425
+ Right: VideoSlotOverlayRight,
426
+ Actions: VideoSlotOverlayActions,
427
+ Author: VideoSlotOverlayAuthor
428
+ });
429
+
430
+ // src/components/VideoSlot/VideoSlotLikeAnimation.css.ts
431
+ var VIDEO_SLOT_LIKE_ANIMATION_CSS = `.sv-like-animation-container{position:absolute;inset:0;overflow:hidden;pointer-events:none;z-index:var(--sv-z-indicator,3)}.sv-like-heart{position:absolute;left:var(--sv-heart-x,50%);top:var(--sv-heart-y,50%);transform:translate(-50%,-50%);pointer-events:none;will-change:transform,opacity}.sv-like-heart__icon{display:flex;align-items:center;justify-content:center;width:var(--sv-heart-size,80px);height:var(--sv-heart-size,80px);transform-origin:center;animation-duration:var(--sv-heart-duration,1000ms);animation-fill-mode:forwards;animation-timing-function:cubic-bezier(.25,.46,.45,.94)}.sv-like-heart--float .sv-like-heart__icon{animation-name:sv-heart-float;animation-timing-function:ease-out}@keyframes sv-heart-float{0%{opacity:0;transform:scale(0)rotate(var(--sv-heart-rotation,0deg))}10%{opacity:1;transform:scale(calc(var(--sv-heart-scale,1)* 1.2))rotate(var(--sv-heart-rotation,0deg))}20%{transform:scale(var(--sv-heart-scale,1))rotate(var(--sv-heart-rotation,0deg))translateY(-20px)translateX(10px)}40%{transform:scale(var(--sv-heart-scale,1))rotate(calc(var(--sv-heart-rotation,0deg)- 10deg))translateY(-60px)translateX(-15px)}60%{opacity:1;transform:scale(var(--sv-heart-scale,1))rotate(calc(var(--sv-heart-rotation,0deg)+15deg))translateY(-100px)translateX(10px)}80%{opacity:.6;transform:scale(calc(var(--sv-heart-scale,1)* .9))rotate(calc(var(--sv-heart-rotation,0deg)- 5deg))translateY(-140px)translateX(-5px)}100%{opacity:0;transform:scale(calc(var(--sv-heart-scale,1)* .7))rotate(var(--sv-heart-rotation,0deg))translateY(-180px)translateX(0)}}.sv-like-heart--burst .sv-like-heart__icon{animation-name:sv-heart-burst;animation-timing-function:cubic-bezier(.175,.885,.32,1.275)}@keyframes sv-heart-burst{0%{opacity:0;transform:scale(0)rotate(var(--sv-heart-rotation,0deg))}20%{opacity:1;transform:scale(calc(var(--sv-heart-scale,1)* 1.5))rotate(calc(var(--sv-heart-rotation,0deg)+15deg))}40%{transform:scale(calc(var(--sv-heart-scale,1)* 1.2))rotate(calc(var(--sv-heart-rotation,0deg)- 10deg))}60%{opacity:1;transform:scale(var(--sv-heart-scale,1))rotate(var(--sv-heart-rotation,0deg))translateY(-30px)}80%{opacity:.5;transform:scale(calc(var(--sv-heart-scale,1)* .9))rotate(var(--sv-heart-rotation,0deg))translateY(-60px)}100%{opacity:0;transform:scale(calc(var(--sv-heart-scale,1)* .6))rotate(var(--sv-heart-rotation,0deg))translateY(-80px)}}.sv-like-heart--scale .sv-like-heart__icon{animation-name:sv-heart-scale;animation-timing-function:ease-out}@keyframes sv-heart-scale{0%{opacity:0;transform:scale(0)}20%{opacity:1;transform:scale(calc(var(--sv-heart-scale,1)* 1.3))}40%{transform:scale(var(--sv-heart-scale,1))}70%{opacity:1;transform:scale(var(--sv-heart-scale,1))}100%{opacity:0;transform:scale(calc(var(--sv-heart-scale,1)* 1.1))}}.sv-like-heart--bounce .sv-like-heart__icon{animation-name:sv-heart-bounce;animation-timing-function:cubic-bezier(.68,-.55,.265,1.55)}@keyframes sv-heart-bounce{0%{opacity:0;transform:scale(0)rotate(var(--sv-heart-rotation,0deg))translateY(0)}15%{opacity:1;transform:scale(calc(var(--sv-heart-scale,1)* 1.4))rotate(calc(var(--sv-heart-rotation,0deg)+10deg))translateY(-30px)}30%{transform:scale(calc(var(--sv-heart-scale,1)* .9))rotate(calc(var(--sv-heart-rotation,0deg)- 5deg))translateY(10px)}45%{transform:scale(calc(var(--sv-heart-scale,1)* 1.15))rotate(calc(var(--sv-heart-rotation,0deg)+5deg))translateY(-15px)}60%{opacity:1;transform:scale(var(--sv-heart-scale,1))rotate(var(--sv-heart-rotation,0deg))translateY(0)}80%{opacity:.7;transform:scale(var(--sv-heart-scale,1))rotate(var(--sv-heart-rotation,0deg))translateY(-10px)}100%{opacity:0;transform:scale(calc(var(--sv-heart-scale,1)* .8))rotate(var(--sv-heart-rotation,0deg))translateY(-20px)}}@media(prefers-reduced-motion:reduce){.sv-like-heart__icon{animation-duration:.5s !important}.sv-like-heart--float .sv-like-heart__icon,.sv-like-heart--burst .sv-like-heart__icon,.sv-like-heart--bounce .sv-like-heart__icon{animation-name:sv-heart-scale}}`;
432
+ var SingleHeart = memo(function SingleHeart2({
433
+ heart,
434
+ icon,
435
+ size,
436
+ animation,
437
+ duration,
438
+ onComplete
439
+ }) {
440
+ useEffect(() => {
441
+ const timer = setTimeout(() => {
442
+ onComplete(heart.id);
443
+ }, duration);
444
+ return () => clearTimeout(timer);
445
+ }, [heart.id, duration, onComplete]);
446
+ return /* @__PURE__ */ jsx(
447
+ "div",
448
+ {
449
+ className: clsx2("sv-like-heart", `sv-like-heart--${animation}`),
450
+ style: {
451
+ "--sv-heart-x": `${heart.x}%`,
452
+ "--sv-heart-y": `${heart.y}%`,
453
+ "--sv-heart-rotation": `${heart.rotation}deg`,
454
+ "--sv-heart-scale": heart.scale,
455
+ "--sv-heart-duration": `${duration}ms`,
456
+ "--sv-heart-size": `${size}px`
457
+ },
458
+ "aria-hidden": "true",
459
+ children: /* @__PURE__ */ jsx("div", { className: "sv-like-heart__icon", children: icon })
460
+ }
461
+ );
462
+ });
463
+ function VideoSlotLikeAnimation({
464
+ icon,
465
+ duration = 1e3,
466
+ animation = "float",
467
+ size = 80,
468
+ color = "#fe2c55",
469
+ showWhenAlreadyLiked = true,
470
+ maxHearts = 10,
471
+ className,
472
+ onHeartCreated
473
+ }) {
474
+ useInsertionEffect(() => {
475
+ return injectComponentCSS("VideoSlotLikeAnimation", VIDEO_SLOT_LIKE_ANIMATION_CSS);
476
+ }, []);
477
+ const heartIdCounterRef = useRef(0);
478
+ const [hearts, setHearts] = useState([]);
479
+ const containerRef = useRef(null);
480
+ const triggerHeart = useCallback(
481
+ (x, y) => {
482
+ heartIdCounterRef.current += 1;
483
+ const newHeart = {
484
+ id: heartIdCounterRef.current,
485
+ x,
486
+ y,
487
+ createdAt: Date.now(),
488
+ rotation: Math.random() * 60 - 30,
489
+ // -30 to 30 degrees
490
+ scale: 0.8 + Math.random() * 0.4
491
+ // 0.8 to 1.2
492
+ };
493
+ setHearts((prev) => {
494
+ const updated = prev.length >= maxHearts ? prev.slice(1) : prev;
495
+ return [...updated, newHeart];
496
+ });
497
+ onHeartCreated?.(newHeart);
498
+ },
499
+ [maxHearts, onHeartCreated]
500
+ );
501
+ const handleHeartComplete = useCallback((id) => {
502
+ setHearts((prev) => prev.filter((h) => h.id !== id));
503
+ }, []);
504
+ const clearAll = useCallback(() => {
505
+ setHearts([]);
506
+ }, []);
507
+ useEffect(() => {
508
+ const container = containerRef.current;
509
+ if (!container) {
510
+ if (process.env.NODE_ENV !== "production") {
511
+ console.warn("[VideoSlotLikeAnimation] Container ref is null. Animation will not work.");
512
+ }
513
+ return;
514
+ }
515
+ const el = container;
516
+ el.__likeAnimation = {
517
+ triggerHeart,
518
+ clearAll
519
+ };
520
+ el.__showWhenAlreadyLiked = showWhenAlreadyLiked;
521
+ return () => {
522
+ el.__likeAnimation = void 0;
523
+ el.__showWhenAlreadyLiked = void 0;
524
+ };
525
+ }, [triggerHeart, clearAll, showWhenAlreadyLiked]);
526
+ const iconElement = icon ?? (typeof HeartFilledIcon === "function" ? /* @__PURE__ */ jsx(
527
+ HeartFilledIcon,
528
+ {
529
+ width: size,
530
+ height: size,
531
+ style: { color, filter: "drop-shadow(0 2px 4px rgba(0,0,0,0.3))" }
532
+ }
533
+ ) : /* @__PURE__ */ jsx("span", { style: { fontSize: size, color, textShadow: "0 2px 4px rgba(0,0,0,0.3)" }, children: "\u2764\uFE0F" }));
534
+ return /* @__PURE__ */ jsx(
535
+ "div",
536
+ {
537
+ ref: containerRef,
538
+ className: clsx2("sv-like-animation-container", className),
539
+ "data-like-animation": "true",
540
+ "aria-hidden": "true",
541
+ children: hearts.map((heart) => /* @__PURE__ */ jsx(
542
+ SingleHeart,
543
+ {
544
+ heart,
545
+ icon: iconElement,
546
+ size,
547
+ animation,
548
+ duration,
549
+ onComplete: handleHeartComplete
550
+ },
551
+ heart.id
552
+ ))
553
+ }
554
+ );
555
+ }
556
+ VideoSlotLikeAnimation.displayName = "VideoSlotLikeAnimation";
557
+
558
+ // src/components/VideoSlot/ReportedVideoOverlay.css.ts
559
+ var REPORTED_VIDEO_OVERLAY_CSS = `.sv-reported-overlay{position:absolute;top:0;left:0;right:0;bottom:0;z-index:var(--sv-z-reported-overlay,10);background:rgba(0,0,0,.85);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);display:flex;align-items:center;justify-content:center;padding:24px}.sv-reported-overlay .sv-reported-content{display:flex;flex-direction:column;align-items:center;text-align:center;max-width:300px;gap:16px}.sv-reported-overlay .sv-reported-icon{width:64px;height:64px;display:flex;align-items:center;justify-content:center;color:var(--sv-color-warning,#ffa500);opacity:.9}.sv-reported-overlay .sv-reported-icon svg{width:100%;height:100%}.sv-reported-overlay .sv-reported-title{margin:0;font-size:18px;font-weight:600;color:#fff}.sv-reported-overlay .sv-reported-message{margin:0;font-size:14px;color:rgba(255,255,255,.7);line-height:1.5}.sv-reported-overlay .sv-reported-button{display:inline-block;margin-top:8px;padding:12px 24px;font-size:14px;font-weight:600;color:#fff;background:transparent;border:1px solid rgba(255,255,255,.3);border-radius:8px;cursor:pointer;transition:all 150ms ease;text-decoration:none;text-transform:none;letter-spacing:normal;line-height:1.2;box-sizing:border-box;font-family:inherit;outline:none}.sv-reported-overlay .sv-reported-button:hover{background:rgba(255,255,255,.1);border-color:rgba(255,255,255,.5)}.sv-reported-overlay .sv-reported-button:active{transform:scale(.98)}.sv-reported-overlay .sv-reported-button:focus{border-color:rgba(255,255,255,.6);box-shadow:0 0 0 2px rgba(255,255,255,.2)}`;
560
+ function ReportedIcon() {
561
+ return /* @__PURE__ */ jsxs(
562
+ "svg",
563
+ {
564
+ "aria-hidden": "true",
565
+ width: "48",
566
+ height: "48",
567
+ viewBox: "0 0 24 24",
568
+ fill: "none",
569
+ stroke: "currentColor",
570
+ strokeWidth: "1.5",
571
+ strokeLinecap: "round",
572
+ strokeLinejoin: "round",
573
+ children: [
574
+ /* @__PURE__ */ jsx("path", { d: "M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z" }),
575
+ /* @__PURE__ */ jsx("line", { x1: "4", y1: "22", x2: "4", y2: "15" })
576
+ ]
577
+ }
578
+ );
579
+ }
580
+ function ReportedVideoOverlayComponent({
581
+ onWatchVideo,
582
+ title = "Video Reported",
583
+ message = "You have reported this video. It will be reviewed by our team.",
584
+ buttonText = "Watch Video",
585
+ icon,
586
+ className
587
+ }) {
588
+ useEffect(() => {
589
+ return injectComponentCSS("reported-video-overlay", REPORTED_VIDEO_OVERLAY_CSS, {
590
+ layered: false
591
+ });
592
+ }, []);
593
+ return /* @__PURE__ */ jsx("div", { className: `sv-reported-overlay ${className ?? ""}`, children: /* @__PURE__ */ jsxs("div", { className: "sv-reported-content", children: [
594
+ /* @__PURE__ */ jsx("div", { className: "sv-reported-icon", children: icon ?? /* @__PURE__ */ jsx(ReportedIcon, {}) }),
595
+ /* @__PURE__ */ jsx("h3", { className: "sv-reported-title", children: title }),
596
+ /* @__PURE__ */ jsx("p", { className: "sv-reported-message", children: message }),
597
+ /* @__PURE__ */ jsx("button", { type: "button", className: "sv-reported-button", onClick: onWatchVideo, children: buttonText })
598
+ ] }) });
599
+ }
600
+ var ReportedVideoOverlay = memo(ReportedVideoOverlayComponent);
601
+ ReportedVideoOverlay.displayName = "ReportedVideoOverlay";
602
+
603
+ export { LOADING_ATTR, LOADING_DATASET_KEY, PLAYER_STATE_ATTR, PLAYER_STATE_DATASET_KEY, ReportedVideoOverlay, SLOT_ACTIVE_ATTR, SLOT_ACTIVE_CLASS, SLOT_ACTIVE_DATASET_KEY, SLOT_CLASS, SLOT_ERROR_CLASS, SLOT_LOADING_CLASS, SLOT_PAUSED_CLASS, SLOT_PLAYING_CLASS, VIDEO_ID_ATTR, VIDEO_ID_DATASET_KEY, VIDEO_SLOT_CSS, VIDEO_SLOT_LIKE_ANIMATION_CSS, VideoSlotHeadless, VideoSlotLikeAnimation, VideoSlotOverlay, VideoSlotPoster, VideoSlotSkeleton, Z_INDEX, Z_INDEX_CSS_VARS };