@flamingo-stack/openframe-frontend-core 0.0.388-snapshot.20260709030118 → 0.0.388

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/dist/{chunk-LFLFQ422.js → chunk-23BJ64AY.js} +2 -2
  2. package/dist/{chunk-WIMXB5EU.js → chunk-35C3PUUN.js} +4 -4
  3. package/dist/{chunk-VXJBE5NX.cjs → chunk-6ZAQOYW4.cjs} +59 -59
  4. package/dist/{chunk-VXJBE5NX.cjs.map → chunk-6ZAQOYW4.cjs.map} +1 -1
  5. package/dist/{chunk-4OLIDOLO.js → chunk-7CEJO3FE.js} +2 -2
  6. package/dist/{chunk-NULFIYK2.js → chunk-7UQ74ZJN.js} +2 -2
  7. package/dist/{chunk-BJPQJNSC.js → chunk-ATKRYY4U.js} +4 -4
  8. package/dist/{chunk-2JCSORCJ.cjs → chunk-B4JRV4FB.cjs} +26 -26
  9. package/dist/{chunk-2JCSORCJ.cjs.map → chunk-B4JRV4FB.cjs.map} +1 -1
  10. package/dist/{chunk-KA4WXCN3.js → chunk-BGYWTU2E.js} +4 -4
  11. package/dist/{chunk-XGOX5UQ2.js → chunk-DM3JIOFA.js} +4 -4
  12. package/dist/{chunk-CQXHHVMR.js → chunk-EQKTBIKH.js} +2 -2
  13. package/dist/{chunk-ZFZAHLI4.cjs → chunk-EYQESL6X.cjs} +7 -7
  14. package/dist/{chunk-ZFZAHLI4.cjs.map → chunk-EYQESL6X.cjs.map} +1 -1
  15. package/dist/{chunk-M6ODI2F5.js → chunk-FS4FVNOU.js} +2 -2
  16. package/dist/{chunk-3JQUNKWY.cjs → chunk-HIODHJCJ.cjs} +15 -15
  17. package/dist/{chunk-3JQUNKWY.cjs.map → chunk-HIODHJCJ.cjs.map} +1 -1
  18. package/dist/{chunk-MPOP47ME.cjs → chunk-HQG5AY64.cjs} +12 -12
  19. package/dist/{chunk-MPOP47ME.cjs.map → chunk-HQG5AY64.cjs.map} +1 -1
  20. package/dist/{chunk-Q4YGG3CL.cjs → chunk-KYN2GYUI.cjs} +31 -31
  21. package/dist/{chunk-Q4YGG3CL.cjs.map → chunk-KYN2GYUI.cjs.map} +1 -1
  22. package/dist/{chunk-OBKCQ7LA.cjs → chunk-ONXN77KP.cjs} +62 -62
  23. package/dist/{chunk-OBKCQ7LA.cjs.map → chunk-ONXN77KP.cjs.map} +1 -1
  24. package/dist/{chunk-44Q3IXZM.js → chunk-SENYQRPF.js} +2 -2
  25. package/dist/{chunk-TQZLC3BQ.cjs → chunk-SNFSLYVA.cjs} +5 -5
  26. package/dist/{chunk-TQZLC3BQ.cjs.map → chunk-SNFSLYVA.cjs.map} +1 -1
  27. package/dist/{chunk-JSFHTIKA.js → chunk-UCQWQ7LM.js} +2 -2
  28. package/dist/{chunk-IDWKP5H5.js → chunk-VCRUAKOJ.js} +245 -521
  29. package/dist/chunk-VCRUAKOJ.js.map +1 -0
  30. package/dist/{chunk-GNB5ERZP.cjs → chunk-VOPTCR5I.cjs} +735 -1011
  31. package/dist/chunk-VOPTCR5I.cjs.map +1 -0
  32. package/dist/{chunk-MIEODAAV.cjs → chunk-WIKFWION.cjs} +9 -9
  33. package/dist/{chunk-MIEODAAV.cjs.map → chunk-WIKFWION.cjs.map} +1 -1
  34. package/dist/{chunk-7MKU2EB4.cjs → chunk-YKPQINSZ.cjs} +26 -26
  35. package/dist/{chunk-7MKU2EB4.cjs.map → chunk-YKPQINSZ.cjs.map} +1 -1
  36. package/dist/{chunk-EHTE3CTT.cjs → chunk-ZVFDJ35S.cjs} +37 -37
  37. package/dist/{chunk-EHTE3CTT.cjs.map → chunk-ZVFDJ35S.cjs.map} +1 -1
  38. package/dist/components/case-studies/index.cjs +8 -8
  39. package/dist/components/case-studies/index.js +2 -2
  40. package/dist/components/chat/index.cjs +2 -2
  41. package/dist/components/chat/index.js +1 -1
  42. package/dist/components/contact/index.cjs +3 -3
  43. package/dist/components/contact/index.js +2 -2
  44. package/dist/components/docs/index.cjs +5 -5
  45. package/dist/components/docs/index.js +4 -4
  46. package/dist/components/embeds/index.cjs +3 -3
  47. package/dist/components/embeds/index.js +2 -2
  48. package/dist/components/faq/index.cjs +3 -3
  49. package/dist/components/faq/index.js +2 -2
  50. package/dist/components/features/index.cjs +2 -4
  51. package/dist/components/features/index.cjs.map +1 -1
  52. package/dist/components/features/index.js +1 -3
  53. package/dist/components/features/video-bites-strip.d.ts +4 -43
  54. package/dist/components/features/video-bites-strip.d.ts.map +1 -1
  55. package/dist/components/features/video.d.ts +0 -6
  56. package/dist/components/features/video.d.ts.map +1 -1
  57. package/dist/components/help-center-pages/index.cjs +20 -20
  58. package/dist/components/help-center-pages/index.js +11 -11
  59. package/dist/components/index.cjs +120 -122
  60. package/dist/components/index.cjs.map +1 -1
  61. package/dist/components/index.js +9 -11
  62. package/dist/components/index.js.map +1 -1
  63. package/dist/components/navigation/index.cjs +2 -2
  64. package/dist/components/navigation/index.js +1 -1
  65. package/dist/components/onboarding-guides/index.cjs +5 -5
  66. package/dist/components/onboarding-guides/index.js +4 -4
  67. package/dist/components/related-content/index.cjs +3 -3
  68. package/dist/components/related-content/index.js +2 -2
  69. package/dist/components/tickets/index.cjs +5 -5
  70. package/dist/components/tickets/index.js +4 -4
  71. package/dist/components/ui/index.cjs +2 -2
  72. package/dist/components/ui/index.js +1 -1
  73. package/dist/index.cjs +2 -4
  74. package/dist/index.cjs.map +1 -1
  75. package/dist/index.js +1 -3
  76. package/dist/types/case-study.d.ts +1 -1
  77. package/dist/types/case-study.d.ts.map +1 -1
  78. package/package.json +1 -1
  79. package/src/components/features/video-bites-strip.tsx +72 -317
  80. package/src/components/features/video.tsx +37 -167
  81. package/src/components/user-display.tsx +2 -2
  82. package/src/types/case-study.ts +1 -1
  83. package/dist/chunk-GNB5ERZP.cjs.map +0 -1
  84. package/dist/chunk-IDWKP5H5.js.map +0 -1
  85. /package/dist/{chunk-LFLFQ422.js.map → chunk-23BJ64AY.js.map} +0 -0
  86. /package/dist/{chunk-WIMXB5EU.js.map → chunk-35C3PUUN.js.map} +0 -0
  87. /package/dist/{chunk-4OLIDOLO.js.map → chunk-7CEJO3FE.js.map} +0 -0
  88. /package/dist/{chunk-NULFIYK2.js.map → chunk-7UQ74ZJN.js.map} +0 -0
  89. /package/dist/{chunk-BJPQJNSC.js.map → chunk-ATKRYY4U.js.map} +0 -0
  90. /package/dist/{chunk-KA4WXCN3.js.map → chunk-BGYWTU2E.js.map} +0 -0
  91. /package/dist/{chunk-XGOX5UQ2.js.map → chunk-DM3JIOFA.js.map} +0 -0
  92. /package/dist/{chunk-CQXHHVMR.js.map → chunk-EQKTBIKH.js.map} +0 -0
  93. /package/dist/{chunk-M6ODI2F5.js.map → chunk-FS4FVNOU.js.map} +0 -0
  94. /package/dist/{chunk-44Q3IXZM.js.map → chunk-SENYQRPF.js.map} +0 -0
  95. /package/dist/{chunk-JSFHTIKA.js.map → chunk-UCQWQ7LM.js.map} +0 -0
@@ -30,10 +30,9 @@
30
30
  * layout="native" → intrinsic aspect ratio. Bites grid, blog cards.
31
31
  */
32
32
 
33
- import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
33
+ import React, { 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';
37
36
  import { fetchPriorityProp } from '../../utils/fetch-priority';
38
37
 
39
38
  // =============================================================================
@@ -106,32 +105,6 @@ if (typeof window !== 'undefined' && typeof console !== 'undefined') {
106
105
  }
107
106
  }
108
107
 
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
-
135
108
  // =============================================================================
136
109
  // URL classifiers (private — `<Video>` is the only consumer)
137
110
  // =============================================================================
@@ -245,11 +218,6 @@ interface VideoFileProps extends VideoCommonProps {
245
218
  * sound at 50% volume first (bite-strip behavior); falls back to muted when
246
219
  * the browser's autoplay policy rejects unmuted hover playback. */
247
220
  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;
253
221
  /** Hide the bottom control bar, keep only the CENTER play/pause control
254
222
  * (bite-strip cards per Figma). */
255
223
  centerControlsOnly?: boolean;
@@ -271,7 +239,6 @@ interface VideoAutoProps extends VideoCommonProps {
271
239
  loop?: boolean;
272
240
  chromeless?: boolean;
273
241
  playOnHover?: boolean;
274
- playWhenHovered?: boolean;
275
242
  centerControlsOnly?: boolean;
276
243
  }
277
244
 
@@ -308,7 +275,6 @@ export function Video(props: VideoProps): React.ReactElement | null {
308
275
  loop={'loop' in props ? props.loop : undefined}
309
276
  chromeless={'chromeless' in props ? props.chromeless : undefined}
310
277
  playOnHover={'playOnHover' in props ? props.playOnHover : undefined}
311
- playWhenHovered={'playWhenHovered' in props ? props.playWhenHovered : undefined}
312
278
  centerControlsOnly={'centerControlsOnly' in props ? props.centerControlsOnly : undefined}
313
279
  className={props.className}
314
280
  />
@@ -387,7 +353,6 @@ interface FilePlayerProps {
387
353
  loop?: boolean;
388
354
  chromeless?: boolean;
389
355
  playOnHover?: boolean;
390
- playWhenHovered?: boolean;
391
356
  centerControlsOnly?: boolean;
392
357
  className?: string;
393
358
  }
@@ -402,18 +367,12 @@ function FilePlayer({
402
367
  loop,
403
368
  chromeless,
404
369
  playOnHover,
405
- playWhenHovered,
406
370
  centerControlsOnly,
407
371
  className,
408
372
  }: FilePlayerProps): React.ReactElement {
409
373
  // centerControlsOnly: the center play button shows at REST only — while
410
374
  // playing, ALL chrome hides (no pause sign over the hover-preview).
411
375
  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);
417
376
  // playOnHover drives the underlying mux-player element imperatively — the
418
377
  // element exposes native play()/pause()/muted/volume; the chrome stays as
419
378
  // configured. Sound-first: volume 0.5 unmuted, muted fallback when the
@@ -434,102 +393,42 @@ function FilePlayer({
434
393
  // Per-enter generation token: a LATE NotAllowedError from a previous enter
435
394
  // must not fire the muted fallback into a newer (intended-sound) session.
436
395
  const hoverGenerationRef = useRef(0);
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);
396
+ const handleHoverEnter = playOnHover
397
+ ? () => {
398
+ hoverActiveRef.current = true;
399
+ const generation = ++hoverGenerationRef.current;
400
+ const el = hoverPlayerRef.current;
401
+ if (!el) return;
402
+ try {
403
+ el.volume = 0.5;
404
+ el.muted = false;
405
+ const attempt = el.play?.();
406
+ if (attempt && typeof (attempt as Promise<void>).catch === 'function') {
407
+ (attempt as Promise<void>).catch((err: unknown) => {
408
+ const name = (err as { name?: string } | null)?.name;
409
+ if (
410
+ name === 'NotAllowedError' &&
411
+ hoverActiveRef.current &&
412
+ generation === hoverGenerationRef.current
413
+ ) {
414
+ try {
415
+ el.muted = true;
416
+ // Swallow the retry's own rejection too (a hover-out mid-retry
417
+ // aborts it that must not surface as an unhandled rejection).
418
+ (el.play?.() as Promise<void> | undefined)?.catch?.(() => {});
419
+ } catch { /* give up silently */ }
420
+ }
421
+ });
490
422
  }
491
- };
492
- activationWaiterRef.current = waiter;
493
- activationWaiters.add(waiter);
423
+ } catch { /* ignore */ }
494
424
  }
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?.(() => {});
425
+ : undefined;
426
+ const handleHoverLeave = playOnHover
427
+ ? () => {
428
+ hoverActiveRef.current = false;
429
+ try { hoverPlayerRef.current?.pause?.(); } catch { /* already torn down */ }
516
430
  }
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;
431
+ : undefined;
533
432
  // Raw SRT text is unusable without a custom overlay — and we just deleted
534
433
  // the 900-LOC custom-controls layer that owned that overlay. Consumers
535
434
  // pass `captionsUrl` (the API-side VTT conversion) alongside `srtContent`
@@ -604,45 +503,16 @@ function FilePlayer({
604
503
  </MuxPlayer>
605
504
  );
606
505
 
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
-
625
506
  // MuxPlayerProps has no pointer-event props — the hover-play handlers live
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) {
507
+ // on a full-size wrapper instead (only rendered in playOnHover mode).
508
+ if (playOnHover) {
630
509
  return (
631
510
  <div
632
- className="relative w-full h-full"
511
+ className="w-full h-full"
633
512
  onPointerEnter={handleHoverEnter}
634
513
  onPointerLeave={handleHoverLeave}
635
514
  >
636
515
  {player}
637
- {unmuteBadge}
638
- </div>
639
- );
640
- }
641
- if (hoverControlled) {
642
- return (
643
- <div className="relative w-full h-full">
644
- {player}
645
- {unmuteBadge}
646
516
  </div>
647
517
  );
648
518
  }
@@ -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-xs leading-4 font-medium' : 'text-lg leading-[22px]',
32
+ compact ? 'text-sm leading-5 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-xs leading-4 font-medium' : 'text-md leading-[16px]',
39
+ compact ? 'text-sm leading-5 font-medium' : 'text-md leading-[16px]',
40
40
  )}>
41
41
  {subtitle}
42
42
  </span>
@@ -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?: 'manual' | 'ai_generated' | null
109
+ highlight_video_source?: string | null
110
110
  highlight_video_duration_ms?: number | null
111
111
  // SEO
112
112
  seo_title?: string