@flamingo-stack/openframe-frontend-core 0.0.388 → 0.0.389
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-B4JRV4FB.cjs → chunk-2JCSORCJ.cjs} +26 -26
- package/dist/{chunk-B4JRV4FB.cjs.map → chunk-2JCSORCJ.cjs.map} +1 -1
- package/dist/{chunk-HIODHJCJ.cjs → chunk-3JQUNKWY.cjs} +15 -15
- package/dist/{chunk-HIODHJCJ.cjs.map → chunk-3JQUNKWY.cjs.map} +1 -1
- package/dist/{chunk-SENYQRPF.js → chunk-44Q3IXZM.js} +2 -2
- package/dist/{chunk-7CEJO3FE.js → chunk-4OLIDOLO.js} +2 -2
- package/dist/{chunk-YKPQINSZ.cjs → chunk-7MKU2EB4.cjs} +26 -26
- package/dist/{chunk-YKPQINSZ.cjs.map → chunk-7MKU2EB4.cjs.map} +1 -1
- package/dist/{chunk-ATKRYY4U.js → chunk-BJPQJNSC.js} +4 -4
- package/dist/{chunk-EQKTBIKH.js → chunk-CQXHHVMR.js} +2 -2
- package/dist/{chunk-ZVFDJ35S.cjs → chunk-EHTE3CTT.cjs} +37 -37
- package/dist/{chunk-ZVFDJ35S.cjs.map → chunk-EHTE3CTT.cjs.map} +1 -1
- package/dist/{chunk-VOPTCR5I.cjs → chunk-GNB5ERZP.cjs} +1011 -735
- package/dist/chunk-GNB5ERZP.cjs.map +1 -0
- package/dist/{chunk-VCRUAKOJ.js → chunk-IDWKP5H5.js} +521 -245
- package/dist/chunk-IDWKP5H5.js.map +1 -0
- package/dist/{chunk-UCQWQ7LM.js → chunk-JSFHTIKA.js} +2 -2
- package/dist/{chunk-BGYWTU2E.js → chunk-KA4WXCN3.js} +4 -4
- package/dist/{chunk-23BJ64AY.js → chunk-LFLFQ422.js} +2 -2
- package/dist/{chunk-FS4FVNOU.js → chunk-M6ODI2F5.js} +2 -2
- package/dist/{chunk-WIKFWION.cjs → chunk-MIEODAAV.cjs} +9 -9
- package/dist/{chunk-WIKFWION.cjs.map → chunk-MIEODAAV.cjs.map} +1 -1
- package/dist/{chunk-HQG5AY64.cjs → chunk-MPOP47ME.cjs} +12 -12
- package/dist/{chunk-HQG5AY64.cjs.map → chunk-MPOP47ME.cjs.map} +1 -1
- package/dist/{chunk-7UQ74ZJN.js → chunk-NULFIYK2.js} +2 -2
- package/dist/{chunk-ONXN77KP.cjs → chunk-OBKCQ7LA.cjs} +62 -62
- package/dist/{chunk-ONXN77KP.cjs.map → chunk-OBKCQ7LA.cjs.map} +1 -1
- package/dist/{chunk-KYN2GYUI.cjs → chunk-Q4YGG3CL.cjs} +31 -31
- package/dist/{chunk-KYN2GYUI.cjs.map → chunk-Q4YGG3CL.cjs.map} +1 -1
- package/dist/{chunk-SNFSLYVA.cjs → chunk-TQZLC3BQ.cjs} +5 -5
- package/dist/{chunk-SNFSLYVA.cjs.map → chunk-TQZLC3BQ.cjs.map} +1 -1
- package/dist/{chunk-6ZAQOYW4.cjs → chunk-VXJBE5NX.cjs} +59 -59
- package/dist/{chunk-6ZAQOYW4.cjs.map → chunk-VXJBE5NX.cjs.map} +1 -1
- package/dist/{chunk-35C3PUUN.js → chunk-WIMXB5EU.js} +4 -4
- package/dist/{chunk-DM3JIOFA.js → chunk-XGOX5UQ2.js} +4 -4
- package/dist/{chunk-EYQESL6X.cjs → chunk-ZFZAHLI4.cjs} +7 -7
- package/dist/{chunk-EYQESL6X.cjs.map → chunk-ZFZAHLI4.cjs.map} +1 -1
- package/dist/components/case-studies/index.cjs +8 -8
- package/dist/components/case-studies/index.js +2 -2
- package/dist/components/chat/index.cjs +2 -2
- package/dist/components/chat/index.js +1 -1
- package/dist/components/contact/index.cjs +3 -3
- package/dist/components/contact/index.js +2 -2
- package/dist/components/docs/index.cjs +5 -5
- package/dist/components/docs/index.js +4 -4
- package/dist/components/embeds/index.cjs +3 -3
- package/dist/components/embeds/index.js +2 -2
- package/dist/components/faq/index.cjs +3 -3
- package/dist/components/faq/index.js +2 -2
- package/dist/components/features/index.cjs +4 -2
- package/dist/components/features/index.cjs.map +1 -1
- package/dist/components/features/index.js +3 -1
- package/dist/components/features/video-bites-strip.d.ts +43 -4
- package/dist/components/features/video-bites-strip.d.ts.map +1 -1
- package/dist/components/features/video.d.ts +6 -0
- package/dist/components/features/video.d.ts.map +1 -1
- package/dist/components/help-center-pages/index.cjs +20 -20
- package/dist/components/help-center-pages/index.js +11 -11
- package/dist/components/index.cjs +122 -120
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +11 -9
- package/dist/components/index.js.map +1 -1
- package/dist/components/navigation/index.cjs +2 -2
- package/dist/components/navigation/index.js +1 -1
- package/dist/components/onboarding-guides/index.cjs +5 -5
- package/dist/components/onboarding-guides/index.js +4 -4
- package/dist/components/related-content/index.cjs +3 -3
- package/dist/components/related-content/index.js +2 -2
- package/dist/components/tickets/index.cjs +5 -5
- package/dist/components/tickets/index.js +4 -4
- package/dist/components/ui/index.cjs +2 -2
- package/dist/components/ui/index.js +1 -1
- package/dist/index.cjs +4 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +3 -1
- package/dist/types/case-study.d.ts +1 -1
- package/dist/types/case-study.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/features/video-bites-strip.tsx +317 -72
- package/src/components/features/video.tsx +167 -37
- package/src/components/user-display.tsx +2 -2
- package/src/types/case-study.ts +1 -1
- package/dist/chunk-VCRUAKOJ.js.map +0 -1
- package/dist/chunk-VOPTCR5I.cjs.map +0 -1
- /package/dist/{chunk-SENYQRPF.js.map → chunk-44Q3IXZM.js.map} +0 -0
- /package/dist/{chunk-7CEJO3FE.js.map → chunk-4OLIDOLO.js.map} +0 -0
- /package/dist/{chunk-ATKRYY4U.js.map → chunk-BJPQJNSC.js.map} +0 -0
- /package/dist/{chunk-EQKTBIKH.js.map → chunk-CQXHHVMR.js.map} +0 -0
- /package/dist/{chunk-UCQWQ7LM.js.map → chunk-JSFHTIKA.js.map} +0 -0
- /package/dist/{chunk-BGYWTU2E.js.map → chunk-KA4WXCN3.js.map} +0 -0
- /package/dist/{chunk-23BJ64AY.js.map → chunk-LFLFQ422.js.map} +0 -0
- /package/dist/{chunk-FS4FVNOU.js.map → chunk-M6ODI2F5.js.map} +0 -0
- /package/dist/{chunk-7UQ74ZJN.js.map → chunk-NULFIYK2.js.map} +0 -0
- /package/dist/{chunk-35C3PUUN.js.map → chunk-WIMXB5EU.js.map} +0 -0
- /package/dist/{chunk-DM3JIOFA.js.map → chunk-XGOX5UQ2.js.map} +0 -0
|
@@ -30,9 +30,10 @@
|
|
|
30
30
|
* layout="native" → intrinsic aspect ratio. Bites grid, blog cards.
|
|
31
31
|
*/
|
|
32
32
|
|
|
33
|
-
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
|
33
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
34
34
|
import MuxPlayer from '@mux/mux-player-react';
|
|
35
35
|
import { PlayIcon } from '../icons-v2-generated/media-playback/play-icon';
|
|
36
|
+
import { VolumeXmarkIcon } from '../icons-v2-generated/audio-and-visual/volume-xmark-icon';
|
|
36
37
|
import { fetchPriorityProp } from '../../utils/fetch-priority';
|
|
37
38
|
|
|
38
39
|
// =============================================================================
|
|
@@ -105,6 +106,32 @@ if (typeof window !== 'undefined' && typeof console !== 'undefined') {
|
|
|
105
106
|
}
|
|
106
107
|
}
|
|
107
108
|
|
|
109
|
+
// =============================================================================
|
|
110
|
+
// User-activation tracker (module scope) — Chrome's autoplay policy rejects
|
|
111
|
+
// UNMUTED play() until the user has interacted with the page (click/keydown;
|
|
112
|
+
// pointer MOVEMENT does not count). Hover-preview surfaces use this to pick
|
|
113
|
+
// the right first move (sound vs muted) WITHOUT a rejection round-trip, and
|
|
114
|
+
// to unmute a live muted preview the instant the first gesture lands.
|
|
115
|
+
// =============================================================================
|
|
116
|
+
|
|
117
|
+
let userHasInteracted = false;
|
|
118
|
+
const activationWaiters = new Set<() => void>();
|
|
119
|
+
if (typeof window !== 'undefined') {
|
|
120
|
+
const w = window as unknown as { __VIDEO_ACTIVATION_TRACKED__?: boolean };
|
|
121
|
+
if (!w.__VIDEO_ACTIVATION_TRACKED__) {
|
|
122
|
+
w.__VIDEO_ACTIVATION_TRACKED__ = true;
|
|
123
|
+
const markActivated = () => {
|
|
124
|
+
userHasInteracted = true;
|
|
125
|
+
activationWaiters.forEach(fn => { try { fn(); } catch { /* ignore */ } });
|
|
126
|
+
activationWaiters.clear();
|
|
127
|
+
window.removeEventListener('pointerdown', markActivated, true);
|
|
128
|
+
window.removeEventListener('keydown', markActivated, true);
|
|
129
|
+
};
|
|
130
|
+
window.addEventListener('pointerdown', markActivated, true);
|
|
131
|
+
window.addEventListener('keydown', markActivated, true);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
108
135
|
// =============================================================================
|
|
109
136
|
// URL classifiers (private — `<Video>` is the only consumer)
|
|
110
137
|
// =============================================================================
|
|
@@ -218,6 +245,11 @@ interface VideoFileProps extends VideoCommonProps {
|
|
|
218
245
|
* sound at 50% volume first (bite-strip behavior); falls back to muted when
|
|
219
246
|
* the browser's autoplay policy rejects unmuted hover playback. */
|
|
220
247
|
playOnHover?: boolean;
|
|
248
|
+
/** CONTROLLED variant of playOnHover: the host owns the hover state (e.g.
|
|
249
|
+
* the bite-strip card, whose overlay also counts as "hovering the card").
|
|
250
|
+
* true → start hover playback, false → pause. When provided, the internal
|
|
251
|
+
* pointer handlers are disabled. */
|
|
252
|
+
playWhenHovered?: boolean;
|
|
221
253
|
/** Hide the bottom control bar, keep only the CENTER play/pause control
|
|
222
254
|
* (bite-strip cards per Figma). */
|
|
223
255
|
centerControlsOnly?: boolean;
|
|
@@ -239,6 +271,7 @@ interface VideoAutoProps extends VideoCommonProps {
|
|
|
239
271
|
loop?: boolean;
|
|
240
272
|
chromeless?: boolean;
|
|
241
273
|
playOnHover?: boolean;
|
|
274
|
+
playWhenHovered?: boolean;
|
|
242
275
|
centerControlsOnly?: boolean;
|
|
243
276
|
}
|
|
244
277
|
|
|
@@ -275,6 +308,7 @@ export function Video(props: VideoProps): React.ReactElement | null {
|
|
|
275
308
|
loop={'loop' in props ? props.loop : undefined}
|
|
276
309
|
chromeless={'chromeless' in props ? props.chromeless : undefined}
|
|
277
310
|
playOnHover={'playOnHover' in props ? props.playOnHover : undefined}
|
|
311
|
+
playWhenHovered={'playWhenHovered' in props ? props.playWhenHovered : undefined}
|
|
278
312
|
centerControlsOnly={'centerControlsOnly' in props ? props.centerControlsOnly : undefined}
|
|
279
313
|
className={props.className}
|
|
280
314
|
/>
|
|
@@ -353,6 +387,7 @@ interface FilePlayerProps {
|
|
|
353
387
|
loop?: boolean;
|
|
354
388
|
chromeless?: boolean;
|
|
355
389
|
playOnHover?: boolean;
|
|
390
|
+
playWhenHovered?: boolean;
|
|
356
391
|
centerControlsOnly?: boolean;
|
|
357
392
|
className?: string;
|
|
358
393
|
}
|
|
@@ -367,12 +402,18 @@ function FilePlayer({
|
|
|
367
402
|
loop,
|
|
368
403
|
chromeless,
|
|
369
404
|
playOnHover,
|
|
405
|
+
playWhenHovered,
|
|
370
406
|
centerControlsOnly,
|
|
371
407
|
className,
|
|
372
408
|
}: FilePlayerProps): React.ReactElement {
|
|
373
409
|
// centerControlsOnly: the center play button shows at REST only — while
|
|
374
410
|
// playing, ALL chrome hides (no pause sign over the hover-preview).
|
|
375
411
|
const [isPlaying, setIsPlaying] = useState(false);
|
|
412
|
+
// True while hover playback is running MUTED because the browser's autoplay
|
|
413
|
+
// policy blocked sound (no user activation yet). Drives the center unmute
|
|
414
|
+
// control — the industry pattern (muted autoplay + explicit unmute button)
|
|
415
|
+
// instead of silently waiting for a click somewhere.
|
|
416
|
+
const [hoverMutedFallback, setHoverMutedFallback] = useState(false);
|
|
376
417
|
// playOnHover drives the underlying mux-player element imperatively — the
|
|
377
418
|
// element exposes native play()/pause()/muted/volume; the chrome stays as
|
|
378
419
|
// configured. Sound-first: volume 0.5 unmuted, muted fallback when the
|
|
@@ -393,42 +434,102 @@ function FilePlayer({
|
|
|
393
434
|
// Per-enter generation token: a LATE NotAllowedError from a previous enter
|
|
394
435
|
// must not fire the muted fallback into a newer (intended-sound) session.
|
|
395
436
|
const hoverGenerationRef = useRef(0);
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
437
|
+
// This instance's pending activation waiter — pruned on hover-leave,
|
|
438
|
+
// re-enter, and unmount so pre-activation hovers don't accumulate stale
|
|
439
|
+
// closures in the module-level set (they'd otherwise setState against
|
|
440
|
+
// unmounted instances when the first user gesture finally lands).
|
|
441
|
+
const activationWaiterRef = useRef<(() => void) | null>(null);
|
|
442
|
+
const clearActivationWaiter = useCallback(() => {
|
|
443
|
+
if (activationWaiterRef.current) {
|
|
444
|
+
activationWaiters.delete(activationWaiterRef.current);
|
|
445
|
+
activationWaiterRef.current = null;
|
|
446
|
+
}
|
|
447
|
+
}, []);
|
|
448
|
+
useEffect(() => clearActivationWaiter, [clearActivationWaiter]);
|
|
449
|
+
const startHoverPlayback = useCallback(() => {
|
|
450
|
+
hoverActiveRef.current = true;
|
|
451
|
+
const generation = ++hoverGenerationRef.current;
|
|
452
|
+
const el = hoverPlayerRef.current;
|
|
453
|
+
if (!el) return;
|
|
454
|
+
try {
|
|
455
|
+
el.volume = 0.5;
|
|
456
|
+
if (userHasInteracted) {
|
|
457
|
+
// Post-activation: unmuted playback is allowed — play with sound.
|
|
458
|
+
// The NotAllowedError guard stays as a belt-and-suspenders fallback;
|
|
459
|
+
// a fast hover-out's pause() rejects with AbortError and must not
|
|
460
|
+
// restart playback (name mismatch + cleared hoverActiveRef).
|
|
461
|
+
el.muted = false;
|
|
462
|
+
(el.play?.() as Promise<void> | undefined)?.catch?.((err: unknown) => {
|
|
463
|
+
const name = (err as { name?: string } | null)?.name;
|
|
464
|
+
if (
|
|
465
|
+
name === 'NotAllowedError' &&
|
|
466
|
+
hoverActiveRef.current &&
|
|
467
|
+
generation === hoverGenerationRef.current
|
|
468
|
+
) {
|
|
469
|
+
try {
|
|
470
|
+
el.muted = true;
|
|
471
|
+
(el.play?.() as Promise<void> | undefined)?.catch?.(() => {});
|
|
472
|
+
setHoverMutedFallback(true);
|
|
473
|
+
} catch { /* give up silently */ }
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
} else {
|
|
477
|
+
// Pre-activation: unmuted WOULD be rejected (hover isn't a gesture in
|
|
478
|
+
// Chrome's activation model) — start muted immediately with no
|
|
479
|
+
// rejection round-trip, and UNMUTE LIVE the instant the user's first
|
|
480
|
+
// click/keydown lands anywhere while this hover is still active.
|
|
481
|
+
el.muted = true;
|
|
482
|
+
(el.play?.() as Promise<void> | undefined)?.catch?.(() => {});
|
|
483
|
+
setHoverMutedFallback(true);
|
|
484
|
+
clearActivationWaiter();
|
|
485
|
+
const waiter = () => {
|
|
486
|
+
activationWaiterRef.current = null;
|
|
487
|
+
if (hoverActiveRef.current && generation === hoverGenerationRef.current) {
|
|
488
|
+
try { el.muted = false; el.volume = 0.5; } catch { /* ignore */ }
|
|
489
|
+
setHoverMutedFallback(false);
|
|
422
490
|
}
|
|
423
|
-
}
|
|
491
|
+
};
|
|
492
|
+
activationWaiterRef.current = waiter;
|
|
493
|
+
activationWaiters.add(waiter);
|
|
424
494
|
}
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
495
|
+
} catch { /* ignore */ }
|
|
496
|
+
}, []);
|
|
497
|
+
const stopHoverPlayback = useCallback(() => {
|
|
498
|
+
hoverActiveRef.current = false;
|
|
499
|
+
setHoverMutedFallback(false);
|
|
500
|
+
clearActivationWaiter();
|
|
501
|
+
try { hoverPlayerRef.current?.pause?.(); } catch { /* already torn down */ }
|
|
502
|
+
}, [clearActivationWaiter]);
|
|
503
|
+
|
|
504
|
+
// Explicit unmute affordance: the click IS the user activation the autoplay
|
|
505
|
+
// policy wants, so unmuting here always succeeds (and the window-level
|
|
506
|
+
// activation listener flips the module flag for every other player too).
|
|
507
|
+
const unmuteNow = useCallback((e: React.MouseEvent) => {
|
|
508
|
+
e.stopPropagation();
|
|
509
|
+
e.preventDefault();
|
|
510
|
+
const el = hoverPlayerRef.current;
|
|
511
|
+
try {
|
|
512
|
+
if (el) {
|
|
513
|
+
el.muted = false;
|
|
514
|
+
el.volume = 0.5;
|
|
515
|
+
(el.play?.() as Promise<void> | undefined)?.catch?.(() => {});
|
|
430
516
|
}
|
|
431
|
-
|
|
517
|
+
} catch { /* ignore */ }
|
|
518
|
+
setHoverMutedFallback(false);
|
|
519
|
+
}, []);
|
|
520
|
+
|
|
521
|
+
// Controlled hover mode (playWhenHovered): the HOST owns hover detection —
|
|
522
|
+
// e.g. the bite-strip card, where the detail overlay is part of the card and
|
|
523
|
+
// must NOT pause playback when the pointer moves onto it.
|
|
524
|
+
const hoverControlled = typeof playWhenHovered === 'boolean';
|
|
525
|
+
useEffect(() => {
|
|
526
|
+
if (!hoverControlled) return;
|
|
527
|
+
if (playWhenHovered) startHoverPlayback();
|
|
528
|
+
else stopHoverPlayback();
|
|
529
|
+
}, [hoverControlled, playWhenHovered, startHoverPlayback, stopHoverPlayback]);
|
|
530
|
+
|
|
531
|
+
const handleHoverEnter = playOnHover && !hoverControlled ? startHoverPlayback : undefined;
|
|
532
|
+
const handleHoverLeave = playOnHover && !hoverControlled ? stopHoverPlayback : undefined;
|
|
432
533
|
// Raw SRT text is unusable without a custom overlay — and we just deleted
|
|
433
534
|
// the 900-LOC custom-controls layer that owned that overlay. Consumers
|
|
434
535
|
// pass `captionsUrl` (the API-side VTT conversion) alongside `srtContent`
|
|
@@ -503,16 +604,45 @@ function FilePlayer({
|
|
|
503
604
|
</MuxPlayer>
|
|
504
605
|
);
|
|
505
606
|
|
|
607
|
+
// Center unmute control — shown while hover playback runs muted because the
|
|
608
|
+
// autoplay policy blocked sound. Best-practice pattern (Mux / FB / IG):
|
|
609
|
+
// muted autoplay + an explicit unmute affordance, never forced sound.
|
|
610
|
+
// Styled to match media-chrome's center controls exactly (the play glyph in
|
|
611
|
+
// the same slot): plain large white glyph, no circle/border/background,
|
|
612
|
+
// slight dim on hover — so unmute reads as just another center control.
|
|
613
|
+
const unmuteBadge = hoverMutedFallback ? (
|
|
614
|
+
<button
|
|
615
|
+
type="button"
|
|
616
|
+
aria-label="Unmute"
|
|
617
|
+
title="Unmute"
|
|
618
|
+
onClick={unmuteNow}
|
|
619
|
+
className="absolute inset-0 z-10 m-auto flex h-14 w-14 items-center justify-center text-ods-text-primary transition-opacity hover:opacity-75"
|
|
620
|
+
>
|
|
621
|
+
<VolumeXmarkIcon size={56} />
|
|
622
|
+
</button>
|
|
623
|
+
) : null;
|
|
624
|
+
|
|
506
625
|
// MuxPlayerProps has no pointer-event props — the hover-play handlers live
|
|
507
|
-
// on a full-size wrapper instead (only
|
|
508
|
-
|
|
626
|
+
// on a full-size wrapper instead (only in UNCONTROLLED playOnHover mode;
|
|
627
|
+
// controlled playWhenHovered hosts own their hover detection). Either
|
|
628
|
+
// hover-capable mode gets a relative wrapper so the unmute badge can dock.
|
|
629
|
+
if (playOnHover && !hoverControlled) {
|
|
509
630
|
return (
|
|
510
631
|
<div
|
|
511
|
-
className="w-full h-full"
|
|
632
|
+
className="relative w-full h-full"
|
|
512
633
|
onPointerEnter={handleHoverEnter}
|
|
513
634
|
onPointerLeave={handleHoverLeave}
|
|
514
635
|
>
|
|
515
636
|
{player}
|
|
637
|
+
{unmuteBadge}
|
|
638
|
+
</div>
|
|
639
|
+
);
|
|
640
|
+
}
|
|
641
|
+
if (hoverControlled) {
|
|
642
|
+
return (
|
|
643
|
+
<div className="relative w-full h-full">
|
|
644
|
+
{player}
|
|
645
|
+
{unmuteBadge}
|
|
516
646
|
</div>
|
|
517
647
|
);
|
|
518
648
|
}
|
|
@@ -29,14 +29,14 @@ export function UserDisplay({ name, avatarUrl, subtitle, size = 32, shape = 'squ
|
|
|
29
29
|
<div className="min-w-0 flex-1">
|
|
30
30
|
<p className={cn(
|
|
31
31
|
"font-['DM_Sans'] text-ods-text-primary truncate",
|
|
32
|
-
compact ? 'text-
|
|
32
|
+
compact ? 'text-xs leading-4 font-medium' : 'text-lg leading-[22px]',
|
|
33
33
|
)}>
|
|
34
34
|
{name}
|
|
35
35
|
</p>
|
|
36
36
|
{subtitle && (
|
|
37
37
|
<span className={cn(
|
|
38
38
|
"font-['DM_Sans'] text-ods-text-secondary truncate block",
|
|
39
|
-
compact ? 'text-
|
|
39
|
+
compact ? 'text-xs leading-4 font-medium' : 'text-md leading-[16px]',
|
|
40
40
|
)}>
|
|
41
41
|
{subtitle}
|
|
42
42
|
</span>
|
package/src/types/case-study.ts
CHANGED
|
@@ -106,7 +106,7 @@ export interface CreateCaseStudyData {
|
|
|
106
106
|
config?: Record<string, unknown> | null
|
|
107
107
|
highlight_video_url?: string | null
|
|
108
108
|
highlight_video_thumbnail?: string | null
|
|
109
|
-
highlight_video_source?:
|
|
109
|
+
highlight_video_source?: 'manual' | 'ai_generated' | null
|
|
110
110
|
highlight_video_duration_ms?: number | null
|
|
111
111
|
// SEO
|
|
112
112
|
seo_title?: string
|