@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.
@@ -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';