@xhub-short/ui 0.1.0-beta.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/README.md +194 -0
- package/dist/chunk-4MN72OZH.js +148 -0
- package/dist/chunk-MMTAPG2C.js +201 -0
- package/dist/chunk-OXY5JHVJ.js +524 -0
- package/dist/chunk-QKQUXR3H.js +1318 -0
- package/dist/chunk-SSJDO24Q.js +204 -0
- package/dist/chunk-UECU42WC.js +1110 -0
- package/dist/chunk-UXMA4KJZ.js +45 -0
- package/dist/chunk-WKX2WBVO.js +98 -0
- package/dist/chunk-YW23IBKF.js +530 -0
- package/dist/chunk-ZZDQKP4R.js +418 -0
- package/dist/components/ActionBar/index.d.ts +244 -0
- package/dist/components/ActionBar/index.js +1 -0
- package/dist/components/ErrorBoundary/index.d.ts +81 -0
- package/dist/components/ErrorBoundary/index.js +1 -0
- package/dist/components/ProgressBar/index.d.ts +84 -0
- package/dist/components/ProgressBar/index.js +1 -0
- package/dist/components/Skeleton/index.d.ts +62 -0
- package/dist/components/Skeleton/index.js +1 -0
- package/dist/components/VideoFeed/index.d.ts +274 -0
- package/dist/components/VideoFeed/index.js +1 -0
- package/dist/components/VideoPlayer/index.d.ts +457 -0
- package/dist/components/VideoPlayer/index.js +1 -0
- package/dist/components/VideoSlot/index.d.ts +592 -0
- package/dist/components/VideoSlot/index.js +1 -0
- package/dist/components/icons/index.d.ts +58 -0
- package/dist/components/icons/index.js +1 -0
- package/dist/index.d.ts +595 -0
- package/dist/index.js +687 -0
- package/dist/use-gesture-react.esm-3SV4QLEJ.js +1893 -0
- package/package.json +65 -0
|
@@ -0,0 +1,592 @@
|
|
|
1
|
+
import { VideoSlotHeadlessProps, VideoItem, UIResourceState, UIPlayerState, UIPlayerControls } from '@xhub-short/contracts';
|
|
2
|
+
import * as react from 'react';
|
|
3
|
+
import { ReactNode } from 'react';
|
|
4
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* VideoSlotHeadless - Headless Video Slot Component
|
|
8
|
+
*
|
|
9
|
+
* A headless component for displaying a single video slot in the feed.
|
|
10
|
+
* Receives ALL data via props (no SDK dependency).
|
|
11
|
+
*
|
|
12
|
+
* Architecture (Two-Tier Export Pattern):
|
|
13
|
+
* - @xhub-short/ui: Exports this headless component
|
|
14
|
+
* - @xhub-short/sdk: Wraps with SDK hooks (wired/VideoSlot.tsx)
|
|
15
|
+
*
|
|
16
|
+
* Responsibilities:
|
|
17
|
+
* - Display video poster/first frame/skeleton
|
|
18
|
+
* - Provide context for compound components
|
|
19
|
+
* - Handle tap-to-play/pause interaction
|
|
20
|
+
* - Display loading/error states
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```tsx
|
|
24
|
+
* // Headless usage (requires all props)
|
|
25
|
+
* <VideoSlotHeadless
|
|
26
|
+
* video={video}
|
|
27
|
+
* resourceState={resourceState}
|
|
28
|
+
* playerState={playerState}
|
|
29
|
+
* playerControls={playerControls}
|
|
30
|
+
* >
|
|
31
|
+
* <VideoSlotPoster />
|
|
32
|
+
* <VideoSlotOverlay>
|
|
33
|
+
* <AuthorInfo />
|
|
34
|
+
* <ActionBar />
|
|
35
|
+
* </VideoSlotOverlay>
|
|
36
|
+
* </VideoSlotHeadless>
|
|
37
|
+
*
|
|
38
|
+
* // Wired usage (SDK injects state)
|
|
39
|
+
* import { VideoSlot } from '@xhub-short/sdk';
|
|
40
|
+
* <VideoSlot video={video}>
|
|
41
|
+
* <VideoSlotOverlay>...</VideoSlotOverlay>
|
|
42
|
+
* </VideoSlot>
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
interface VideoSlotHeadlessExtendedProps extends VideoSlotHeadlessProps {
|
|
47
|
+
/** Children (overlay, poster, etc.) */
|
|
48
|
+
children?: ReactNode;
|
|
49
|
+
/** Handle tap on video area */
|
|
50
|
+
onTap?: () => void;
|
|
51
|
+
/** Handle double tap */
|
|
52
|
+
onDoubleTap?: () => void;
|
|
53
|
+
/** Custom error UI */
|
|
54
|
+
renderError?: (error: Error, retry: () => void) => ReactNode;
|
|
55
|
+
/** Custom loading UI */
|
|
56
|
+
renderLoading?: () => ReactNode;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* VideoSlotHeadless Component
|
|
60
|
+
*
|
|
61
|
+
* Headless component for a single video slot.
|
|
62
|
+
* Provides context to compound children (Poster, Overlay, etc.)
|
|
63
|
+
*
|
|
64
|
+
* ## SSR Safety (ITEM #3)
|
|
65
|
+
*
|
|
66
|
+
* This component is SSR-safe because:
|
|
67
|
+
* - CSS injection via `injectComponentCSS` checks `typeof document === 'undefined'`
|
|
68
|
+
* - All DOM queries are guarded by refs that are null on server
|
|
69
|
+
* - No direct window/document access in render path
|
|
70
|
+
*
|
|
71
|
+
* ## DOM Attributes Contract
|
|
72
|
+
*
|
|
73
|
+
* The component renders these data attributes for external access:
|
|
74
|
+
*
|
|
75
|
+
* | Attribute | Value | Purpose |
|
|
76
|
+
* |-----------|-------|---------|
|
|
77
|
+
* | `data-video-id` | string | Video identifier for analytics/testing |
|
|
78
|
+
* | `data-slot-active` | "true" \| "false" | Whether slot has resources allocated |
|
|
79
|
+
* | `data-player-state` | "playing" \| "paused" \| "loading" \| "error" | Current player state |
|
|
80
|
+
* | `data-loading` | "true" \| "false" | Whether video is loading |
|
|
81
|
+
*
|
|
82
|
+
* These attributes are used by:
|
|
83
|
+
* - Wired layer (VideoSlot in SDK) for DOM manipulation
|
|
84
|
+
* - Testing libraries for element selection
|
|
85
|
+
* - Analytics for event tracking
|
|
86
|
+
*/
|
|
87
|
+
declare function VideoSlotHeadless({ video, resourceState, playerState, playerControls, className, children, onVisible: _onVisible, onHidden: _onHidden, onTap, onDoubleTap, renderError, renderLoading, }: VideoSlotHeadlessExtendedProps): React.ReactElement;
|
|
88
|
+
declare namespace VideoSlotHeadless {
|
|
89
|
+
var displayName: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* VideoSlotPoster - Poster/Thumbnail/First Frame Compound Component
|
|
94
|
+
*
|
|
95
|
+
* Displays visual placeholder while video loads.
|
|
96
|
+
* Priority order:
|
|
97
|
+
* 1. First frame (client-side captured)
|
|
98
|
+
* 2. Poster image (from video.poster)
|
|
99
|
+
* 3. Skeleton shimmer (fallback)
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* ```tsx
|
|
103
|
+
* <VideoSlotHeadless {...props}>
|
|
104
|
+
* <VideoSlotPoster />
|
|
105
|
+
* <VideoSlotOverlay />
|
|
106
|
+
* </VideoSlotHeadless>
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
interface VideoSlotPosterProps {
|
|
110
|
+
/** Additional CSS classes */
|
|
111
|
+
className?: string;
|
|
112
|
+
/** Force show skeleton (ignore first frame and poster) */
|
|
113
|
+
forceSkeleton?: boolean;
|
|
114
|
+
/** Custom poster image URL (overrides video.poster and firstFrame) */
|
|
115
|
+
posterUrl?: string;
|
|
116
|
+
/** Image loading priority */
|
|
117
|
+
loading?: 'eager' | 'lazy';
|
|
118
|
+
/** Alt text for the image */
|
|
119
|
+
alt?: string;
|
|
120
|
+
/** Called when image fails to load */
|
|
121
|
+
onError?: (error: Error) => void;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* VideoSlotPoster Component
|
|
125
|
+
*
|
|
126
|
+
* Compound component that renders the poster/thumbnail layer.
|
|
127
|
+
* Must be used within a VideoSlotHeadless component.
|
|
128
|
+
*
|
|
129
|
+
* Automatically hides when video is playing via CSS.
|
|
130
|
+
*/
|
|
131
|
+
declare function VideoSlotPoster({ className, forceSkeleton, posterUrl, loading, alt, onError, }: VideoSlotPosterProps): React.ReactElement;
|
|
132
|
+
declare namespace VideoSlotPoster {
|
|
133
|
+
var displayName: string;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* VideoSlotSkeleton - Skeleton-only poster
|
|
137
|
+
*
|
|
138
|
+
* Use when you always want a skeleton placeholder regardless of video data.
|
|
139
|
+
*/
|
|
140
|
+
declare function VideoSlotSkeleton({ className, }: {
|
|
141
|
+
className?: string;
|
|
142
|
+
}): React.ReactElement;
|
|
143
|
+
declare namespace VideoSlotSkeleton {
|
|
144
|
+
var displayName: string;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* VideoSlotOverlay - Overlay Container Compound Component
|
|
149
|
+
*
|
|
150
|
+
* Provides a container for overlay content (author info, action bar, captions, etc.)
|
|
151
|
+
* with a gradient background for better text visibility.
|
|
152
|
+
*
|
|
153
|
+
* Also provides sub-components for positioning content in specific regions.
|
|
154
|
+
*
|
|
155
|
+
* @example
|
|
156
|
+
* ```tsx
|
|
157
|
+
* <VideoSlotHeadless {...props}>
|
|
158
|
+
* <VideoSlotPoster />
|
|
159
|
+
* <VideoSlotOverlay>
|
|
160
|
+
* <VideoSlotOverlay.Top>
|
|
161
|
+
* <HeaderContent />
|
|
162
|
+
* </VideoSlotOverlay.Top>
|
|
163
|
+
* <VideoSlotOverlay.Bottom>
|
|
164
|
+
* <AuthorInfo />
|
|
165
|
+
* </VideoSlotOverlay.Bottom>
|
|
166
|
+
* <VideoSlotOverlay.Right>
|
|
167
|
+
* <ActionBar />
|
|
168
|
+
* </VideoSlotOverlay.Right>
|
|
169
|
+
* </VideoSlotOverlay>
|
|
170
|
+
* </VideoSlotHeadless>
|
|
171
|
+
* ```
|
|
172
|
+
*/
|
|
173
|
+
|
|
174
|
+
interface VideoSlotOverlayProps {
|
|
175
|
+
/** Children (overlay content) */
|
|
176
|
+
children?: ReactNode;
|
|
177
|
+
/** Additional CSS classes */
|
|
178
|
+
className?: string;
|
|
179
|
+
/** Disable gradient background */
|
|
180
|
+
noGradient?: boolean;
|
|
181
|
+
}
|
|
182
|
+
interface VideoSlotOverlayRegionProps {
|
|
183
|
+
/** Children (region content) */
|
|
184
|
+
children?: ReactNode;
|
|
185
|
+
/** Additional CSS classes */
|
|
186
|
+
className?: string;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* VideoSlotOverlay Component
|
|
190
|
+
*
|
|
191
|
+
* Container for overlay content with gradient background.
|
|
192
|
+
* Does NOT require VideoSlotContext - can be used standalone.
|
|
193
|
+
*
|
|
194
|
+
* The gradient provides better visibility for text/icons over video.
|
|
195
|
+
*/
|
|
196
|
+
declare function VideoSlotOverlayRoot({ children, className, noGradient, }: VideoSlotOverlayProps): React.ReactElement;
|
|
197
|
+
declare namespace VideoSlotOverlayRoot {
|
|
198
|
+
var displayName: string;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Top region of overlay
|
|
202
|
+
* Typically used for video title, hashtags, or status indicators.
|
|
203
|
+
*/
|
|
204
|
+
declare function VideoSlotOverlayTop({ children, className, }: VideoSlotOverlayRegionProps): React.ReactElement;
|
|
205
|
+
declare namespace VideoSlotOverlayTop {
|
|
206
|
+
var displayName: string;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Bottom region of overlay
|
|
210
|
+
* Typically used for author info, caption, music info.
|
|
211
|
+
*/
|
|
212
|
+
declare function VideoSlotOverlayBottom({ children, className, }: VideoSlotOverlayRegionProps): React.ReactElement;
|
|
213
|
+
declare namespace VideoSlotOverlayBottom {
|
|
214
|
+
var displayName: string;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Left region of overlay
|
|
218
|
+
* Rarely used, but available for custom layouts.
|
|
219
|
+
*/
|
|
220
|
+
declare function VideoSlotOverlayLeft({ children, className, }: VideoSlotOverlayRegionProps): React.ReactElement;
|
|
221
|
+
declare namespace VideoSlotOverlayLeft {
|
|
222
|
+
var displayName: string;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Right region of overlay
|
|
226
|
+
* Typically used for action buttons (like, comment, share).
|
|
227
|
+
*/
|
|
228
|
+
declare function VideoSlotOverlayRight({ children, className, }: VideoSlotOverlayRegionProps): React.ReactElement;
|
|
229
|
+
declare namespace VideoSlotOverlayRight {
|
|
230
|
+
var displayName: string;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Actions region (alias for right, with specific styling)
|
|
234
|
+
* Optimized for vertical action button layout (TikTok style).
|
|
235
|
+
*/
|
|
236
|
+
declare function VideoSlotOverlayActions({ children, className, }: VideoSlotOverlayRegionProps): React.ReactElement;
|
|
237
|
+
declare namespace VideoSlotOverlayActions {
|
|
238
|
+
var displayName: string;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Author region (alias for bottom-left area)
|
|
242
|
+
* Optimized for author info layout.
|
|
243
|
+
*/
|
|
244
|
+
declare function VideoSlotOverlayAuthor({ children, className, }: VideoSlotOverlayRegionProps): React.ReactElement;
|
|
245
|
+
declare namespace VideoSlotOverlayAuthor {
|
|
246
|
+
var displayName: string;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* VideoSlotOverlay with compound sub-components
|
|
250
|
+
*
|
|
251
|
+
* @example
|
|
252
|
+
* ```tsx
|
|
253
|
+
* <VideoSlotOverlay>
|
|
254
|
+
* <VideoSlotOverlay.Top>Header</VideoSlotOverlay.Top>
|
|
255
|
+
* <VideoSlotOverlay.Author>@username</VideoSlotOverlay.Author>
|
|
256
|
+
* <VideoSlotOverlay.Actions>
|
|
257
|
+
* <LikeButton />
|
|
258
|
+
* </VideoSlotOverlay.Actions>
|
|
259
|
+
* </VideoSlotOverlay>
|
|
260
|
+
* ```
|
|
261
|
+
*/
|
|
262
|
+
declare const VideoSlotOverlay: typeof VideoSlotOverlayRoot & {
|
|
263
|
+
Top: typeof VideoSlotOverlayTop;
|
|
264
|
+
Bottom: typeof VideoSlotOverlayBottom;
|
|
265
|
+
Left: typeof VideoSlotOverlayLeft;
|
|
266
|
+
Right: typeof VideoSlotOverlayRight;
|
|
267
|
+
Actions: typeof VideoSlotOverlayActions;
|
|
268
|
+
Author: typeof VideoSlotOverlayAuthor;
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
type LikeAnimationStyle = 'float' | 'burst' | 'scale' | 'bounce';
|
|
272
|
+
interface HeartInstance {
|
|
273
|
+
/** Unique ID for this heart */
|
|
274
|
+
id: number;
|
|
275
|
+
/** X position (percentage 0-100) */
|
|
276
|
+
x: number;
|
|
277
|
+
/** Y position (percentage 0-100) */
|
|
278
|
+
y: number;
|
|
279
|
+
/** Timestamp when created */
|
|
280
|
+
createdAt: number;
|
|
281
|
+
/** Random rotation for variety (-30 to 30 degrees) */
|
|
282
|
+
rotation: number;
|
|
283
|
+
/** Random scale factor (0.8 to 1.2) */
|
|
284
|
+
scale: number;
|
|
285
|
+
}
|
|
286
|
+
interface VideoSlotLikeAnimationProps {
|
|
287
|
+
/**
|
|
288
|
+
* Custom icon to display
|
|
289
|
+
* @default HeartFilledIcon (red)
|
|
290
|
+
*/
|
|
291
|
+
icon?: ReactNode;
|
|
292
|
+
/**
|
|
293
|
+
* Animation duration in milliseconds
|
|
294
|
+
* @default 1000
|
|
295
|
+
*/
|
|
296
|
+
duration?: number;
|
|
297
|
+
/**
|
|
298
|
+
* Animation style
|
|
299
|
+
* @default 'float'
|
|
300
|
+
*/
|
|
301
|
+
animation?: LikeAnimationStyle;
|
|
302
|
+
/**
|
|
303
|
+
* Icon size in pixels
|
|
304
|
+
* @default 80
|
|
305
|
+
*/
|
|
306
|
+
size?: number;
|
|
307
|
+
/**
|
|
308
|
+
* Icon color (only applies to default icon)
|
|
309
|
+
* @default '#fe2c55' (TikTok red)
|
|
310
|
+
*/
|
|
311
|
+
color?: string;
|
|
312
|
+
/**
|
|
313
|
+
* Whether to show animation even when video is already liked
|
|
314
|
+
* If true: double-tap always shows heart animation
|
|
315
|
+
* If false: double-tap only shows heart on first like
|
|
316
|
+
* @default true
|
|
317
|
+
*/
|
|
318
|
+
showWhenAlreadyLiked?: boolean;
|
|
319
|
+
/**
|
|
320
|
+
* Maximum number of hearts visible at once
|
|
321
|
+
* @default 10
|
|
322
|
+
*/
|
|
323
|
+
maxHearts?: number;
|
|
324
|
+
/**
|
|
325
|
+
* Additional CSS class
|
|
326
|
+
*/
|
|
327
|
+
className?: string;
|
|
328
|
+
/**
|
|
329
|
+
* Callback when a heart animation starts
|
|
330
|
+
*/
|
|
331
|
+
onHeartCreated?: (heart: HeartInstance) => void;
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Imperative handle for controlling animation from parent.
|
|
335
|
+
*
|
|
336
|
+
* Used by VideoSlotHeadless to trigger hearts via DOM contract.
|
|
337
|
+
* This interface is attached to the DOM element as `__likeAnimation`.
|
|
338
|
+
*/
|
|
339
|
+
interface VideoSlotLikeAnimationRef {
|
|
340
|
+
/**
|
|
341
|
+
* Trigger a heart at specific position
|
|
342
|
+
* @param x X position (0-100 percentage)
|
|
343
|
+
* @param y Y position (0-100 percentage)
|
|
344
|
+
*/
|
|
345
|
+
triggerHeart: (x: number, y: number) => void;
|
|
346
|
+
/**
|
|
347
|
+
* Clear all hearts immediately
|
|
348
|
+
*/
|
|
349
|
+
clearAll: () => void;
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Type helper for DOM element with imperative contract.
|
|
353
|
+
* Use this for type-safe access to animation methods.
|
|
354
|
+
*
|
|
355
|
+
* @example
|
|
356
|
+
* ```ts
|
|
357
|
+
* const el = container.querySelector('[data-like-animation]') as LikeAnimationElement | null;
|
|
358
|
+
* el?.__likeAnimation?.triggerHeart(50, 50);
|
|
359
|
+
* ```
|
|
360
|
+
*/
|
|
361
|
+
type LikeAnimationElement = HTMLDivElement & {
|
|
362
|
+
__likeAnimation?: VideoSlotLikeAnimationRef;
|
|
363
|
+
__showWhenAlreadyLiked?: boolean;
|
|
364
|
+
};
|
|
365
|
+
/**
|
|
366
|
+
* VideoSlotLikeAnimation
|
|
367
|
+
*
|
|
368
|
+
* This component manages multiple heart animations that appear at tap positions.
|
|
369
|
+
* It's designed to be used as a compound child of VideoSlot.
|
|
370
|
+
*
|
|
371
|
+
* ## Imperative Control
|
|
372
|
+
*
|
|
373
|
+
* Instead of React ref forwarding, this component uses a DOM-based contract
|
|
374
|
+
* for imperative control. This is intentional - it allows VideoSlotHeadless
|
|
375
|
+
* to trigger animations without tight coupling or prop drilling.
|
|
376
|
+
*
|
|
377
|
+
* The parent (VideoSlotHeadless) accesses methods via:
|
|
378
|
+
* ```ts
|
|
379
|
+
* const el = container.querySelector('[data-like-animation="true"]');
|
|
380
|
+
* el.__likeAnimation.triggerHeart(x, y);
|
|
381
|
+
* ```
|
|
382
|
+
*/
|
|
383
|
+
declare function VideoSlotLikeAnimation({ icon, duration, animation, size, color, showWhenAlreadyLiked, maxHearts, className, onHeartCreated, }: VideoSlotLikeAnimationProps): react_jsx_runtime.JSX.Element;
|
|
384
|
+
declare namespace VideoSlotLikeAnimation {
|
|
385
|
+
var displayName: string;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
interface VideoSlotContextValue {
|
|
389
|
+
/** Video item data */
|
|
390
|
+
video: VideoItem;
|
|
391
|
+
/** Resource allocation state */
|
|
392
|
+
resourceState: UIResourceState;
|
|
393
|
+
/** Player state */
|
|
394
|
+
playerState: UIPlayerState;
|
|
395
|
+
/** Player controls */
|
|
396
|
+
playerControls: UIPlayerControls;
|
|
397
|
+
/** First frame data URL (if captured) */
|
|
398
|
+
firstFrame: string | null;
|
|
399
|
+
/** Set first frame data URL */
|
|
400
|
+
setFirstFrame: (dataUrl: string) => void;
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* VideoSlot Context
|
|
404
|
+
*
|
|
405
|
+
* Provides access to video data and player controls for compound components.
|
|
406
|
+
*/
|
|
407
|
+
declare const VideoSlotContext: react.Context<VideoSlotContextValue | null>;
|
|
408
|
+
/**
|
|
409
|
+
* Access VideoSlot context (throws if not in provider)
|
|
410
|
+
*
|
|
411
|
+
* This hook includes dev-only invariant checks with helpful debugging info.
|
|
412
|
+
*
|
|
413
|
+
* @example
|
|
414
|
+
* ```tsx
|
|
415
|
+
* function VideoSlotOverlay() {
|
|
416
|
+
* const { video, playerState } = useVideoSlotContext();
|
|
417
|
+
* return <div>{video.title} - {playerState.currentTime}s</div>;
|
|
418
|
+
* }
|
|
419
|
+
* ```
|
|
420
|
+
*
|
|
421
|
+
* @throws {Error} If used outside of VideoSlot provider (with debug info in dev)
|
|
422
|
+
*/
|
|
423
|
+
declare function useVideoSlotContext(): VideoSlotContextValue;
|
|
424
|
+
/**
|
|
425
|
+
* Access VideoSlot context (returns null if not in provider)
|
|
426
|
+
*
|
|
427
|
+
* Useful for components that can work both inside and outside VideoSlot.
|
|
428
|
+
*
|
|
429
|
+
* @example
|
|
430
|
+
* ```tsx
|
|
431
|
+
* function MaybeInSlot() {
|
|
432
|
+
* const ctx = useOptionalVideoSlotContext();
|
|
433
|
+
* if (ctx) {
|
|
434
|
+
* return <div>In slot: {ctx.video.title}</div>;
|
|
435
|
+
* }
|
|
436
|
+
* return <div>Not in slot</div>;
|
|
437
|
+
* }
|
|
438
|
+
* ```
|
|
439
|
+
*/
|
|
440
|
+
declare function useOptionalVideoSlotContext(): VideoSlotContextValue | null;
|
|
441
|
+
/**
|
|
442
|
+
* Get video item from context
|
|
443
|
+
*/
|
|
444
|
+
declare function useVideoSlotVideo(): VideoItem;
|
|
445
|
+
/**
|
|
446
|
+
* Get player state from context
|
|
447
|
+
*/
|
|
448
|
+
declare function useVideoSlotPlayer(): {
|
|
449
|
+
state: UIPlayerState;
|
|
450
|
+
controls: UIPlayerControls;
|
|
451
|
+
};
|
|
452
|
+
/**
|
|
453
|
+
* Get resource state from context
|
|
454
|
+
*/
|
|
455
|
+
declare function useVideoSlotResource(): UIResourceState;
|
|
456
|
+
/**
|
|
457
|
+
* Check if slot is currently active
|
|
458
|
+
*/
|
|
459
|
+
declare function useVideoSlotIsActive(): boolean;
|
|
460
|
+
/**
|
|
461
|
+
* Check if video is playing
|
|
462
|
+
*/
|
|
463
|
+
declare function useVideoSlotIsPlaying(): boolean;
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* VideoSlot DOM Contract Constants
|
|
467
|
+
*
|
|
468
|
+
* Defines data attributes used for DOM querying and direct manipulation.
|
|
469
|
+
* These constants ensure consistency between headless and wired components.
|
|
470
|
+
*
|
|
471
|
+
* @see packages/ui/src/components/VideoFeed/README.md for DOM Contract documentation
|
|
472
|
+
*/
|
|
473
|
+
/**
|
|
474
|
+
* Data attribute for video ID
|
|
475
|
+
* Used to identify which video this slot is displaying
|
|
476
|
+
* @example data-video-id="video-123"
|
|
477
|
+
*/
|
|
478
|
+
declare const VIDEO_ID_ATTR = "data-video-id";
|
|
479
|
+
/**
|
|
480
|
+
* Dataset key for video ID (camelCase)
|
|
481
|
+
* @example element.dataset.videoId
|
|
482
|
+
*/
|
|
483
|
+
declare const VIDEO_ID_DATASET_KEY = "videoId";
|
|
484
|
+
/**
|
|
485
|
+
* Data attribute for slot active state
|
|
486
|
+
* @example data-slot-active="true"
|
|
487
|
+
*/
|
|
488
|
+
declare const SLOT_ACTIVE_ATTR = "data-slot-active";
|
|
489
|
+
/**
|
|
490
|
+
* Dataset key for slot active state (camelCase)
|
|
491
|
+
* @example element.dataset.slotActive
|
|
492
|
+
*/
|
|
493
|
+
declare const SLOT_ACTIVE_DATASET_KEY = "slotActive";
|
|
494
|
+
/**
|
|
495
|
+
* Data attribute for player state
|
|
496
|
+
* @example data-player-state="playing"
|
|
497
|
+
*/
|
|
498
|
+
declare const PLAYER_STATE_ATTR = "data-player-state";
|
|
499
|
+
/**
|
|
500
|
+
* Dataset key for player state (camelCase)
|
|
501
|
+
* @example element.dataset.playerState
|
|
502
|
+
*/
|
|
503
|
+
declare const PLAYER_STATE_DATASET_KEY = "playerState";
|
|
504
|
+
/**
|
|
505
|
+
* Data attribute for loading state
|
|
506
|
+
* @example data-loading="true"
|
|
507
|
+
*/
|
|
508
|
+
declare const LOADING_ATTR = "data-loading";
|
|
509
|
+
/**
|
|
510
|
+
* Dataset key for loading state (camelCase)
|
|
511
|
+
* @example element.dataset.loading
|
|
512
|
+
*/
|
|
513
|
+
declare const LOADING_DATASET_KEY = "loading";
|
|
514
|
+
declare const SLOT_CLASS = "sv-video-slot";
|
|
515
|
+
declare const SLOT_ACTIVE_CLASS = "sv-video-slot--active";
|
|
516
|
+
declare const SLOT_LOADING_CLASS = "sv-video-slot--loading";
|
|
517
|
+
declare const SLOT_ERROR_CLASS = "sv-video-slot--error";
|
|
518
|
+
declare const SLOT_PLAYING_CLASS = "sv-video-slot--playing";
|
|
519
|
+
declare const SLOT_PAUSED_CLASS = "sv-video-slot--paused";
|
|
520
|
+
/**
|
|
521
|
+
* Z-Index layering contract for VideoSlot components.
|
|
522
|
+
*
|
|
523
|
+
* These values define the stacking order within a single video slot.
|
|
524
|
+
* Host apps can override via CSS custom properties.
|
|
525
|
+
*
|
|
526
|
+
* Layer Stack (bottom to top):
|
|
527
|
+
* ┌─────────────────────────────────────────────┐
|
|
528
|
+
* │ z-index: 4 - Progress Bar (topmost) │
|
|
529
|
+
* │ z-index: 3 - Error UI / Play Indicator │
|
|
530
|
+
* │ z-index: 2 - Overlay / Spinner │
|
|
531
|
+
* │ z-index: 1 - Poster / Video │
|
|
532
|
+
* │ z-index: 0 - Slot Container (base) │
|
|
533
|
+
* └─────────────────────────────────────────────┘
|
|
534
|
+
*
|
|
535
|
+
* @example Override via CSS variables
|
|
536
|
+
* ```css
|
|
537
|
+
* :root {
|
|
538
|
+
* --sv-z-poster: 1;
|
|
539
|
+
* --sv-z-overlay: 2;
|
|
540
|
+
* --sv-z-indicator: 3;
|
|
541
|
+
* --sv-z-progress: 4;
|
|
542
|
+
* }
|
|
543
|
+
* ```
|
|
544
|
+
*/
|
|
545
|
+
declare const Z_INDEX: {
|
|
546
|
+
/** Base layer - slot container */
|
|
547
|
+
readonly SLOT: 0;
|
|
548
|
+
/** Poster image and video element */
|
|
549
|
+
readonly POSTER: 1;
|
|
550
|
+
/** Overlay content and loading spinner */
|
|
551
|
+
readonly OVERLAY: 2;
|
|
552
|
+
/** Error UI and play/pause indicator */
|
|
553
|
+
readonly INDICATOR: 3;
|
|
554
|
+
/** Progress bar (always on top) */
|
|
555
|
+
readonly PROGRESS: 4;
|
|
556
|
+
};
|
|
557
|
+
/**
|
|
558
|
+
* CSS custom property names for z-index layers.
|
|
559
|
+
* Use these to override default z-index values.
|
|
560
|
+
*/
|
|
561
|
+
declare const Z_INDEX_CSS_VARS: {
|
|
562
|
+
readonly POSTER: "--sv-z-poster";
|
|
563
|
+
readonly OVERLAY: "--sv-z-overlay";
|
|
564
|
+
readonly INDICATOR: "--sv-z-indicator";
|
|
565
|
+
readonly PROGRESS: "--sv-z-progress";
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* VideoSlot Component CSS
|
|
570
|
+
*
|
|
571
|
+
* Per-component CSS following the bundle optimization strategy.
|
|
572
|
+
* This CSS is injected via useInsertionEffect when component mounts.
|
|
573
|
+
*
|
|
574
|
+
* Architecture:
|
|
575
|
+
* - Uses CSS Variables for theming (--sv-* prefix)
|
|
576
|
+
* - Avoids global selectors
|
|
577
|
+
* - Follows BEM-like naming: sv-video-slot__[element]--[modifier]
|
|
578
|
+
*/
|
|
579
|
+
declare const VIDEO_SLOT_CSS = "\n/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n * Z-INDEX LAYERING CONTRACT\n * \n * Override these variables to customize layer stacking:\n * --sv-z-poster: Poster/Video layer (default: 1)\n * --sv-z-overlay: Overlay/Spinner layer (default: 2)\n * --sv-z-indicator: Error/Play indicator layer (default: 3)\n * --sv-z-progress: Progress bar layer (default: 4)\n *\n * Layer Stack (bottom to top):\n * \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n * \u2502 z-index: 4 - Progress Bar (topmost) \u2502\n * \u2502 z-index: 3 - Error UI / Play Indicator \u2502\n * \u2502 z-index: 2 - Overlay / Spinner \u2502\n * \u2502 z-index: 1 - Poster / Video \u2502\n * \u2502 z-index: 0 - Slot Container (base) \u2502\n * \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n * \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n * VideoSlot - Container for individual video in feed\n * \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.sv-video-slot {\n position: relative;\n width: 100%;\n height: 100%;\n overflow: hidden;\n background-color: var(--sv-bg-primary, #000);\n /* Touch optimization */\n touch-action: none;\n user-select: none;\n -webkit-user-select: none;\n /* GPU acceleration */\n will-change: contents;\n contain: layout style paint;\n}\n\n/* Active slot (currently visible) */\n.sv-video-slot--active {\n z-index: 1;\n}\n\n/* Loading state */\n.sv-video-slot--loading .sv-video-slot__video {\n opacity: 0;\n}\n\n.sv-video-slot--loading .sv-video-slot__poster {\n opacity: 1;\n}\n\n/* Playing state */\n.sv-video-slot--playing .sv-video-slot__video {\n opacity: 1;\n}\n\n.sv-video-slot--playing .sv-video-slot__poster {\n opacity: 0;\n}\n\n/* Paused state - keep poster hidden (show paused video frame, not poster) */\n.sv-video-slot--paused .sv-video-slot__video {\n opacity: 1;\n}\n\n.sv-video-slot--paused .sv-video-slot__poster {\n opacity: 0;\n}\n\n/* Hidden poster (explicit class applied by VideoSlotPoster component) */\n.sv-video-slot__poster--hidden {\n opacity: 0;\n pointer-events: none;\n}\n\n/* Error state */\n.sv-video-slot--error {\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n * Video Element\n * \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.sv-video-slot__video-container {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.sv-video-slot__video {\n width: 100%;\n height: 100%;\n object-fit: cover;\n background-color: transparent;\n /* Smooth fade transition */\n transition: opacity var(--sv-transition-duration, 300ms) ease-out;\n}\n\n/* Video loaded but not playing yet */\n.sv-video-slot__video[data-loaded=\"true\"] {\n opacity: 1;\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n * Poster / Thumbnail / First Frame\n * \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.sv-video-slot__poster {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n background-color: var(--sv-bg-primary, #000);\n z-index: var(--sv-z-poster, 1);\n /* Smooth fade transition */\n transition: opacity var(--sv-transition-duration, 300ms) ease-out;\n pointer-events: none;\n}\n\n.sv-video-slot__poster-image {\n width: 100%;\n height: 100%;\n object-fit: cover;\n}\n\n/* First frame (client-captured) */\n.sv-video-slot__poster--first-frame .sv-video-slot__poster-image {\n image-rendering: auto;\n}\n\n/* Skeleton placeholder */\n.sv-video-slot__poster--skeleton {\n background: linear-gradient(\n 110deg,\n var(--sv-skeleton-bg, rgba(255, 255, 255, 0.05)) 0%,\n var(--sv-skeleton-shimmer, rgba(255, 255, 255, 0.1)) 50%,\n var(--sv-skeleton-bg, rgba(255, 255, 255, 0.05)) 100%\n );\n background-size: 200% 100%;\n animation: sv-slot-skeleton-shimmer 1.5s ease-in-out infinite;\n}\n\n@keyframes sv-slot-skeleton-shimmer {\n 0% {\n background-position: 200% 0;\n }\n 100% {\n background-position: -200% 0;\n }\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n * Overlay Container\n * \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.sv-video-slot__overlay {\n position: absolute;\n inset: 0;\n z-index: var(--sv-z-overlay, 2);\n /* Gradient background for better text visibility */\n background: linear-gradient(\n to top,\n rgba(0, 0, 0, 0.6) 0%,\n rgba(0, 0, 0, 0.3) 20%,\n transparent 40%,\n transparent 60%,\n rgba(0, 0, 0, 0.2) 80%,\n rgba(0, 0, 0, 0.4) 100%\n );\n pointer-events: none;\n}\n\n/* \n * Overlay sections should NOT block clicks (pass-through to container for play/pause)\n * Only actual interactive elements (buttons, links) inside sections should receive clicks.\n * \n * DO NOT use: .sv-video-slot__overlay > * { pointer-events: auto; }\n * This would make entire sections block clicks even on \"empty\" areas.\n */\n.sv-video-slot__overlay-top,\n.sv-video-slot__overlay-bottom,\n.sv-video-slot__overlay-left,\n.sv-video-slot__overlay-right,\n.sv-video-slot__actions,\n.sv-video-slot__author {\n pointer-events: none;\n}\n\n/* Only interactive elements inside overlay sections should receive clicks */\n.sv-video-slot__overlay button,\n.sv-video-slot__overlay a,\n.sv-video-slot__overlay [role=\"button\"],\n.sv-video-slot__overlay input,\n.sv-video-slot__overlay label {\n pointer-events: auto;\n}\n\n/* Overlay sections */\n.sv-video-slot__overlay-top {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n padding: var(--sv-spacing-md, 16px);\n padding-top: calc(var(--sv-safe-area-top, 0px) + var(--sv-spacing-md, 16px));\n}\n\n.sv-video-slot__overlay-bottom {\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n padding: var(--sv-spacing-md, 16px);\n padding-bottom: calc(var(--sv-safe-area-bottom, 0px) + var(--sv-spacing-md, 16px));\n}\n\n.sv-video-slot__overlay-left {\n position: absolute;\n top: 50%;\n left: 0;\n transform: translateY(-50%);\n padding: var(--sv-spacing-md, 16px);\n}\n\n.sv-video-slot__overlay-right {\n position: absolute;\n top: 50%;\n right: 0;\n transform: translateY(-50%);\n padding: var(--sv-spacing-md, 16px);\n}\n\n/* Action bar (typically right side) */\n.sv-video-slot__actions {\n position: absolute;\n right: var(--sv-spacing-sm, 8px);\n bottom: calc(var(--sv-safe-area-bottom, 0px) + 80px);\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: var(--sv-spacing-lg, 20px);\n}\n\n/* Author info (typically bottom left) */\n.sv-video-slot__author {\n position: absolute;\n left: var(--sv-spacing-md, 16px);\n bottom: calc(var(--sv-safe-area-bottom, 0px) + var(--sv-spacing-md, 16px));\n right: 80px;\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n * Error State\n * \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.sv-video-slot__error {\n position: absolute;\n inset: 0;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n background-color: var(--sv-bg-primary, #000);\n color: var(--sv-text-secondary, #999);\n text-align: center;\n padding: var(--sv-spacing-lg, 20px);\n gap: var(--sv-spacing-md, 16px);\n z-index: var(--sv-z-indicator, 3);\n}\n\n.sv-video-slot__error-icon {\n font-size: 48px;\n opacity: 0.6;\n}\n\n.sv-video-slot__error-message {\n font-size: var(--sv-font-size-md, 14px);\n max-width: 200px;\n}\n\n.sv-video-slot__error-retry {\n padding: var(--sv-spacing-sm, 8px) var(--sv-spacing-md, 16px);\n background-color: var(--sv-color-primary, #fe2c55);\n color: #fff;\n border: none;\n border-radius: var(--sv-radius-md, 8px);\n font-size: var(--sv-font-size-sm, 12px);\n font-weight: 600;\n cursor: pointer;\n transition: background-color 150ms ease;\n}\n\n.sv-video-slot__error-retry:hover {\n background-color: var(--sv-color-primary-hover, #e02850);\n}\n\n.sv-video-slot__error-retry:active {\n transform: scale(0.96);\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n * Loading Spinner\n * \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.sv-video-slot__spinner {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n z-index: var(--sv-z-overlay, 2);\n}\n\n.sv-video-slot__spinner-circle {\n width: 40px;\n height: 40px;\n border: 3px solid rgba(255, 255, 255, 0.2);\n border-top-color: var(--sv-color-primary, #fe2c55);\n border-radius: 50%;\n animation: sv-slot-spin 0.8s linear infinite;\n}\n\n@keyframes sv-slot-spin {\n to {\n transform: rotate(360deg);\n }\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n * Play/Pause Visual Indicator\n * \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.sv-video-slot__play-indicator {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 64px;\n height: 64px;\n display: flex;\n align-items: center;\n justify-content: center;\n background-color: rgba(0, 0, 0, 0.5);\n border-radius: 50%;\n z-index: var(--sv-z-indicator, 3);\n opacity: 0;\n transition: opacity 200ms ease;\n pointer-events: none;\n}\n\n.sv-video-slot__play-indicator--visible {\n opacity: 1;\n animation: sv-slot-play-indicator 400ms ease-out forwards;\n}\n\n@keyframes sv-slot-play-indicator {\n 0% {\n opacity: 1;\n transform: translate(-50%, -50%) scale(1);\n }\n 100% {\n opacity: 0;\n transform: translate(-50%, -50%) scale(1.5);\n }\n}\n\n.sv-video-slot__play-indicator-icon {\n color: #fff;\n font-size: 28px;\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n * Progress Bar\n * \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.sv-video-slot__progress {\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n height: 3px;\n background-color: rgba(255, 255, 255, 0.2);\n z-index: var(--sv-z-progress, 4);\n}\n\n.sv-video-slot__progress-bar {\n height: 100%;\n background-color: var(--sv-color-primary, #fe2c55);\n transition: width 100ms linear;\n}\n\n.sv-video-slot__progress-buffered {\n position: absolute;\n top: 0;\n left: 0;\n height: 100%;\n background-color: rgba(255, 255, 255, 0.3);\n}\n";
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* VideoSlotLikeAnimation CSS Styles
|
|
583
|
+
*
|
|
584
|
+
* Beautiful heart animations with multiple styles:
|
|
585
|
+
* - float: Hearts float up and fade (like TikTok)
|
|
586
|
+
* - burst: Hearts burst outward with particles
|
|
587
|
+
* - scale: Hearts scale up and fade
|
|
588
|
+
* - bounce: Hearts bounce then fade
|
|
589
|
+
*/
|
|
590
|
+
declare const VIDEO_SLOT_LIKE_ANIMATION_CSS = "\n/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n Container\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.sv-like-animation-container {\n position: absolute;\n inset: 0;\n overflow: hidden;\n pointer-events: none;\n z-index: var(--sv-z-indicator, 3);\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n Single Heart\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.sv-like-heart {\n position: absolute;\n left: var(--sv-heart-x, 50%);\n top: var(--sv-heart-y, 50%);\n transform: translate(-50%, -50%);\n pointer-events: none;\n will-change: transform, opacity;\n}\n\n.sv-like-heart__icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: var(--sv-heart-size, 80px);\n height: var(--sv-heart-size, 80px);\n transform-origin: center;\n animation-duration: var(--sv-heart-duration, 1000ms);\n animation-fill-mode: forwards;\n animation-timing-function: cubic-bezier(0.25, 0.46, 0.45, 0.94);\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n Float Animation (TikTok style)\n - Hearts float upward while wiggling\n - Fades out near the top\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.sv-like-heart--float .sv-like-heart__icon {\n animation-name: sv-heart-float;\n animation-timing-function: ease-out;\n}\n\n@keyframes sv-heart-float {\n 0% {\n opacity: 0;\n transform: scale(0) rotate(var(--sv-heart-rotation, 0deg));\n }\n 10% {\n opacity: 1;\n transform: scale(calc(var(--sv-heart-scale, 1) * 1.2)) rotate(var(--sv-heart-rotation, 0deg));\n }\n 20% {\n transform: \n scale(var(--sv-heart-scale, 1)) \n rotate(var(--sv-heart-rotation, 0deg)) \n translateY(-20px) \n translateX(10px);\n }\n 40% {\n transform: \n scale(var(--sv-heart-scale, 1)) \n rotate(calc(var(--sv-heart-rotation, 0deg) - 10deg)) \n translateY(-60px) \n translateX(-15px);\n }\n 60% {\n opacity: 1;\n transform: \n scale(var(--sv-heart-scale, 1)) \n rotate(calc(var(--sv-heart-rotation, 0deg) + 15deg)) \n translateY(-100px) \n translateX(10px);\n }\n 80% {\n opacity: 0.6;\n transform: \n scale(calc(var(--sv-heart-scale, 1) * 0.9)) \n rotate(calc(var(--sv-heart-rotation, 0deg) - 5deg)) \n translateY(-140px) \n translateX(-5px);\n }\n 100% {\n opacity: 0;\n transform: \n scale(calc(var(--sv-heart-scale, 1) * 0.7)) \n rotate(var(--sv-heart-rotation, 0deg)) \n translateY(-180px) \n translateX(0);\n }\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n Burst Animation\n - Heart bursts outward with energy\n - Scales up quickly then fades\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.sv-like-heart--burst .sv-like-heart__icon {\n animation-name: sv-heart-burst;\n animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1.275);\n}\n\n@keyframes sv-heart-burst {\n 0% {\n opacity: 0;\n transform: scale(0) rotate(var(--sv-heart-rotation, 0deg));\n }\n 20% {\n opacity: 1;\n transform: scale(calc(var(--sv-heart-scale, 1) * 1.5)) rotate(calc(var(--sv-heart-rotation, 0deg) + 15deg));\n }\n 40% {\n transform: scale(calc(var(--sv-heart-scale, 1) * 1.2)) rotate(calc(var(--sv-heart-rotation, 0deg) - 10deg));\n }\n 60% {\n opacity: 1;\n transform: scale(var(--sv-heart-scale, 1)) rotate(var(--sv-heart-rotation, 0deg)) translateY(-30px);\n }\n 80% {\n opacity: 0.5;\n transform: scale(calc(var(--sv-heart-scale, 1) * 0.9)) rotate(var(--sv-heart-rotation, 0deg)) translateY(-60px);\n }\n 100% {\n opacity: 0;\n transform: scale(calc(var(--sv-heart-scale, 1) * 0.6)) rotate(var(--sv-heart-rotation, 0deg)) translateY(-80px);\n }\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n Scale Animation\n - Simple scale up and fade\n - Clean and minimal\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.sv-like-heart--scale .sv-like-heart__icon {\n animation-name: sv-heart-scale;\n animation-timing-function: ease-out;\n}\n\n@keyframes sv-heart-scale {\n 0% {\n opacity: 0;\n transform: scale(0);\n }\n 20% {\n opacity: 1;\n transform: scale(calc(var(--sv-heart-scale, 1) * 1.3));\n }\n 40% {\n transform: scale(var(--sv-heart-scale, 1));\n }\n 70% {\n opacity: 1;\n transform: scale(var(--sv-heart-scale, 1));\n }\n 100% {\n opacity: 0;\n transform: scale(calc(var(--sv-heart-scale, 1) * 1.1));\n }\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n Bounce Animation\n - Heart bounces in place with energy\n - Playful and fun\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.sv-like-heart--bounce .sv-like-heart__icon {\n animation-name: sv-heart-bounce;\n animation-timing-function: cubic-bezier(0.68, -0.55, 0.265, 1.55);\n}\n\n@keyframes sv-heart-bounce {\n 0% {\n opacity: 0;\n transform: scale(0) rotate(var(--sv-heart-rotation, 0deg)) translateY(0);\n }\n 15% {\n opacity: 1;\n transform: scale(calc(var(--sv-heart-scale, 1) * 1.4)) rotate(calc(var(--sv-heart-rotation, 0deg) + 10deg)) translateY(-30px);\n }\n 30% {\n transform: scale(calc(var(--sv-heart-scale, 1) * 0.9)) rotate(calc(var(--sv-heart-rotation, 0deg) - 5deg)) translateY(10px);\n }\n 45% {\n transform: scale(calc(var(--sv-heart-scale, 1) * 1.15)) rotate(calc(var(--sv-heart-rotation, 0deg) + 5deg)) translateY(-15px);\n }\n 60% {\n opacity: 1;\n transform: scale(var(--sv-heart-scale, 1)) rotate(var(--sv-heart-rotation, 0deg)) translateY(0);\n }\n 80% {\n opacity: 0.7;\n transform: scale(var(--sv-heart-scale, 1)) rotate(var(--sv-heart-rotation, 0deg)) translateY(-10px);\n }\n 100% {\n opacity: 0;\n transform: scale(calc(var(--sv-heart-scale, 1) * 0.8)) rotate(var(--sv-heart-rotation, 0deg)) translateY(-20px);\n }\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n Accessibility: Reduce motion\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\n\n@media (prefers-reduced-motion: reduce) {\n .sv-like-heart__icon {\n animation-duration: 0.5s !important;\n }\n \n .sv-like-heart--float .sv-like-heart__icon,\n .sv-like-heart--burst .sv-like-heart__icon,\n .sv-like-heart--bounce .sv-like-heart__icon {\n animation-name: sv-heart-scale;\n }\n}\n";
|
|
591
|
+
|
|
592
|
+
export { type HeartInstance, LOADING_ATTR, LOADING_DATASET_KEY, type LikeAnimationElement, type LikeAnimationStyle, PLAYER_STATE_ATTR, PLAYER_STATE_DATASET_KEY, 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, VideoSlotContext, type VideoSlotContextValue, VideoSlotHeadless, type VideoSlotHeadlessExtendedProps, VideoSlotLikeAnimation, type VideoSlotLikeAnimationProps, type VideoSlotLikeAnimationRef, VideoSlotOverlay, type VideoSlotOverlayProps, type VideoSlotOverlayRegionProps, VideoSlotPoster, type VideoSlotPosterProps, VideoSlotSkeleton, Z_INDEX, Z_INDEX_CSS_VARS, useOptionalVideoSlotContext, useVideoSlotContext, useVideoSlotIsActive, useVideoSlotIsPlaying, useVideoSlotPlayer, useVideoSlotResource, useVideoSlotVideo };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { LOADING_ATTR, LOADING_DATASET_KEY, PLAYER_STATE_ATTR, PLAYER_STATE_DATASET_KEY, 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, VideoSlotContext, VideoSlotHeadless, VideoSlotLikeAnimation, VideoSlotOverlay, VideoSlotPoster, VideoSlotSkeleton, Z_INDEX, Z_INDEX_CSS_VARS, useOptionalVideoSlotContext, useVideoSlotContext, useVideoSlotIsActive, useVideoSlotIsPlaying, useVideoSlotPlayer, useVideoSlotResource, useVideoSlotVideo } from '../../chunk-QKQUXR3H.js';
|