@flamingo-stack/openframe-frontend-core 0.0.397-snapshot.20260710042522 → 0.0.397

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 (109) hide show
  1. package/dist/{chunk-LHICE5MK.js → chunk-2OOZASQE.js} +2 -2
  2. package/dist/{chunk-5SGIFYSA.js → chunk-3E4BDAHJ.js} +2 -2
  3. package/dist/{chunk-WRS7OVIT.cjs → chunk-43C5ECNT.cjs} +37 -37
  4. package/dist/{chunk-WRS7OVIT.cjs.map → chunk-43C5ECNT.cjs.map} +1 -1
  5. package/dist/{chunk-QOMEGWOM.cjs → chunk-55SNCMFS.cjs} +31 -31
  6. package/dist/{chunk-QOMEGWOM.cjs.map → chunk-55SNCMFS.cjs.map} +1 -1
  7. package/dist/{chunk-YSI6FXZH.cjs → chunk-BB7TZ6KS.cjs} +59 -59
  8. package/dist/{chunk-YSI6FXZH.cjs.map → chunk-BB7TZ6KS.cjs.map} +1 -1
  9. package/dist/{chunk-NLYOIYUB.js → chunk-CK5UZLIN.js} +3174 -3302
  10. package/dist/chunk-CK5UZLIN.js.map +1 -0
  11. package/dist/{chunk-PKRHMZBN.js → chunk-D4F7ZLJG.js} +4 -4
  12. package/dist/{chunk-XFOSQQKD.cjs → chunk-DB6CUWTL.cjs} +26 -26
  13. package/dist/{chunk-XFOSQQKD.cjs.map → chunk-DB6CUWTL.cjs.map} +1 -1
  14. package/dist/{chunk-XQ35TKEM.cjs → chunk-HPQKX5IW.cjs} +15 -15
  15. package/dist/{chunk-XQ35TKEM.cjs.map → chunk-HPQKX5IW.cjs.map} +1 -1
  16. package/dist/{chunk-KOIH637Y.js → chunk-IVXOR2IA.js} +2 -2
  17. package/dist/{chunk-WX3MHQRH.js → chunk-J6TAK6HE.js} +4 -4
  18. package/dist/{chunk-3MMYFUWX.js → chunk-JNU3YPLP.js} +4 -4
  19. package/dist/{chunk-QCUHIPG3.js → chunk-K5FRWPWF.js} +2 -2
  20. package/dist/{chunk-MBWSBKSG.cjs → chunk-M6NPDNVB.cjs} +26 -26
  21. package/dist/{chunk-MBWSBKSG.cjs.map → chunk-M6NPDNVB.cjs.map} +1 -1
  22. package/dist/{chunk-BIQH2W35.cjs → chunk-OAENYOKC.cjs} +9 -9
  23. package/dist/{chunk-BIQH2W35.cjs.map → chunk-OAENYOKC.cjs.map} +1 -1
  24. package/dist/{chunk-I4D6KJLI.cjs → chunk-SBWTMJPQ.cjs} +5 -5
  25. package/dist/{chunk-I4D6KJLI.cjs.map → chunk-SBWTMJPQ.cjs.map} +1 -1
  26. package/dist/{chunk-5IZDYGPU.cjs → chunk-T5F54MN4.cjs} +12 -12
  27. package/dist/{chunk-5IZDYGPU.cjs.map → chunk-T5F54MN4.cjs.map} +1 -1
  28. package/dist/{chunk-WR74UUVM.js → chunk-TTHTO5H5.js} +4 -4
  29. package/dist/{chunk-D3EAVXAQ.js → chunk-VTR4QDL2.js} +2 -2
  30. package/dist/{chunk-JAO2PKOU.js → chunk-WCDM226S.js} +2 -2
  31. package/dist/{chunk-THE2AFUN.cjs → chunk-XNAYZHPK.cjs} +62 -62
  32. package/dist/{chunk-THE2AFUN.cjs.map → chunk-XNAYZHPK.cjs.map} +1 -1
  33. package/dist/{chunk-Y2QAEMGF.cjs → chunk-Z6PAD2UN.cjs} +7 -7
  34. package/dist/{chunk-Y2QAEMGF.cjs.map → chunk-Z6PAD2UN.cjs.map} +1 -1
  35. package/dist/{chunk-MH5U4ZMV.js → chunk-ZFK4HHRX.js} +2 -2
  36. package/dist/{chunk-PZIZOFPY.cjs → chunk-ZUKBC2ZX.cjs} +23 -151
  37. package/dist/chunk-ZUKBC2ZX.cjs.map +1 -0
  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 -6
  51. package/dist/components/features/index.cjs.map +1 -1
  52. package/dist/components/features/index.d.ts +0 -1
  53. package/dist/components/features/index.d.ts.map +1 -1
  54. package/dist/components/features/index.js +1 -5
  55. package/dist/components/features/video-bites-strip.d.ts.map +1 -1
  56. package/dist/components/features/video.d.ts +0 -7
  57. package/dist/components/features/video.d.ts.map +1 -1
  58. package/dist/components/help-center-pages/index.cjs +20 -20
  59. package/dist/components/help-center-pages/index.js +11 -11
  60. package/dist/components/index.cjs +120 -126
  61. package/dist/components/index.cjs.map +1 -1
  62. package/dist/components/index.js +9 -15
  63. package/dist/components/index.js.map +1 -1
  64. package/dist/components/media-carousel.d.ts.map +1 -1
  65. package/dist/components/navigation/index.cjs +2 -2
  66. package/dist/components/navigation/index.js +1 -1
  67. package/dist/components/onboarding-guides/index.cjs +5 -5
  68. package/dist/components/onboarding-guides/index.js +4 -4
  69. package/dist/components/related-content/index.cjs +3 -3
  70. package/dist/components/related-content/index.js +2 -2
  71. package/dist/components/tickets/index.cjs +5 -5
  72. package/dist/components/tickets/index.js +4 -4
  73. package/dist/components/ui/index.cjs +2 -4
  74. package/dist/components/ui/index.cjs.map +1 -1
  75. package/dist/components/ui/index.d.ts +0 -1
  76. package/dist/components/ui/index.d.ts.map +1 -1
  77. package/dist/components/ui/index.js +1 -3
  78. package/dist/components/ui/tab-navigation.d.ts +2 -6
  79. package/dist/components/ui/tab-navigation.d.ts.map +1 -1
  80. package/dist/index.cjs +2 -8
  81. package/dist/index.cjs.map +1 -1
  82. package/dist/index.js +1 -7
  83. package/package.json +4 -4
  84. package/src/components/features/index.ts +0 -1
  85. package/src/components/features/video-bites-strip.tsx +15 -73
  86. package/src/components/features/video.tsx +7 -51
  87. package/src/components/media-carousel.tsx +4 -2
  88. package/src/components/ui/index.ts +0 -1
  89. package/src/components/ui/tab-navigation.tsx +14 -31
  90. package/src/styles/app-globals.css +0 -23
  91. package/dist/chunk-NLYOIYUB.js.map +0 -1
  92. package/dist/chunk-PZIZOFPY.cjs.map +0 -1
  93. package/dist/components/features/video-center-badge.d.ts +0 -36
  94. package/dist/components/features/video-center-badge.d.ts.map +0 -1
  95. package/dist/components/ui/crossfade-panels.d.ts +0 -52
  96. package/dist/components/ui/crossfade-panels.d.ts.map +0 -1
  97. package/src/components/features/video-center-badge.tsx +0 -76
  98. package/src/components/ui/crossfade-panels.tsx +0 -88
  99. /package/dist/{chunk-LHICE5MK.js.map → chunk-2OOZASQE.js.map} +0 -0
  100. /package/dist/{chunk-5SGIFYSA.js.map → chunk-3E4BDAHJ.js.map} +0 -0
  101. /package/dist/{chunk-PKRHMZBN.js.map → chunk-D4F7ZLJG.js.map} +0 -0
  102. /package/dist/{chunk-KOIH637Y.js.map → chunk-IVXOR2IA.js.map} +0 -0
  103. /package/dist/{chunk-WX3MHQRH.js.map → chunk-J6TAK6HE.js.map} +0 -0
  104. /package/dist/{chunk-3MMYFUWX.js.map → chunk-JNU3YPLP.js.map} +0 -0
  105. /package/dist/{chunk-QCUHIPG3.js.map → chunk-K5FRWPWF.js.map} +0 -0
  106. /package/dist/{chunk-WR74UUVM.js.map → chunk-TTHTO5H5.js.map} +0 -0
  107. /package/dist/{chunk-D3EAVXAQ.js.map → chunk-VTR4QDL2.js.map} +0 -0
  108. /package/dist/{chunk-JAO2PKOU.js.map → chunk-WCDM226S.js.map} +0 -0
  109. /package/dist/{chunk-MH5U4ZMV.js.map → chunk-ZFK4HHRX.js.map} +0 -0
@@ -36,12 +36,9 @@ import {
36
36
  sortBitesByCreatedAtDesc,
37
37
  type VideoBiteStripProfile,
38
38
  } from './video-bites-shared';
39
- import Image from '../../embed-shims/next-image';
40
39
  import { CardsStrip, STRIP_CELL_MAX_WIDTH } from './cards-strip';
41
- import { useCoverImageFallback } from '../chat/entity-cards/use-cover-image-fallback';
42
40
  import { UserDisplay } from '../user-display';
43
41
  import { Chevron02RightIcon } from '../icons-v2-generated/arrows/chevron-02-right-icon';
44
- import { VideoPlayBadge } from './video-center-badge';
45
42
 
46
43
  // NOTE: the title constant / profile adapter / sort comparator live in the
47
44
  // server-safe leaf `video-bites-shared.ts` (its own published subpath). The
@@ -255,23 +252,7 @@ export function VideoBiteCard({
255
252
  io.observe(el);
256
253
  return () => io.disconnect();
257
254
  }, [gateControlled]);
258
- const showMedia = gateControlled ? playerMounted : isNear;
259
-
260
- // Player mounts at NEAR-VIEWPORT (not on first hover) so hover playback is
261
- // INSTANT for any card the user can reach — mounting on activation made
262
- // freshly-scrolled cards stall for seconds while MuxPlayer initialized and
263
- // buffered. Always-mounted players are visually safe now that every bite
264
- // carries a poster: the player renders with a transparent media background
265
- // over the card's poster layer, so a not-yet-decoded player never shows as
266
- // a black rectangle (the original wrap-seam flash came from POSTERLESS
267
- // mounted players, not from mounting itself).
268
- const showPlayer = showMedia;
269
-
270
- // Poster resolution — THE shared entity-card cover fallback chain
271
- // (use-cover-image-fallback): the bite's real thumbnail_url, dropped on
272
- // load error. When it resolves to null (no thumbnail yet, or it failed)
273
- // the media zone falls back to the first-frame <video> facade below.
274
- const { src: posterSrc, onError: onPosterError } = useCoverImageFallback(bite.thumbnail_url);
255
+ const showPlayer = gateControlled ? playerMounted : isNear;
275
256
 
276
257
  const navigate = () => {
277
258
  if (bite.onNavigate) bite.onNavigate();
@@ -361,65 +342,26 @@ export function VideoBiteCard({
361
342
  // same as the old single-box layout). Editor mode: width-driven.
362
343
  style={{ aspectRatio: cssAspect, ...(height !== undefined ? { height, maxWidth: STRIP_CELL_MAX_WIDTH } : {}) }}
363
344
  >
364
- {showMedia ? (
345
+ {showPlayer ? (
365
346
  // Clones: `inert` (not just the wrapper's aria-hidden) — the player's
366
347
  // shadow-DOM center control is otherwise still tab-reachable inside a
367
348
  // hidden subtree (axe aria-hidden-focus). Hover preview on clones
368
349
  // keeps working: playback is driven imperatively from the CARD's own
369
350
  // pointer handlers, never from focus/clicks on the player chrome.
370
351
  <div className="absolute inset-0" inert={isClone || undefined}>
371
- {/* Poster layer. Preferred: the bite's REAL thumbnail (generated at
372
- Vizard-ingestion time by vizard-persistence-utils via Shotstack
373
- capture the provider sends no cover), resolved through the
374
- shared cover fallback chain. Fallback when it's absent or fails
375
- to load: the <Video> component's `firstFrameOnly` facade paints
376
- the `#t=0.1` frame. Either way the box is never a black
377
- rectangle, and original + clone paint the IDENTICAL image so the
378
- marquee's wrap seam stays pixel-invisible. */}
379
- {posterSrc ? (
380
- <Image
381
- src={posterSrc}
382
- alt=""
383
- fill
384
- unoptimized
385
- onError={onPosterError}
386
- className="object-cover"
387
- />
388
- ) : (
389
- <Video kind="file" url={bite.url} firstFrameOnly layout="fill" />
390
- )}
391
-
392
- {/* Play affordance for every non-playing state (resting poster,
393
- facade, mounted-but-paused): <VideoPlayBadge> — a pixel replica
394
- of MuxPlayer's pre-play center button (media-chrome's own PAUSED
395
- center control is a different, smaller bare glyph, so the
396
- mounted player runs chromeless and the replica keeps all states
397
- identical to the mux button). Hidden while playing. */}
398
- {!isActive && <VideoPlayBadge className="absolute inset-0 z-10 m-auto" />}
399
- {showPlayer && (
400
- // `--media-background-color: transparent` (inherited into
401
- // media-chrome) — the freshly-mounted player must not blank the
402
- // facade behind it with its default black fill while it loads.
403
- <div
404
- className="absolute inset-0"
405
- style={{ '--media-background-color': 'transparent' } as React.CSSProperties}
406
- >
407
- {/* CONTROLLED hover playback keyed to CARD hover (`isActive`): the
408
- detail overlay is part of the card, so moving the pointer onto
409
- it keeps playing. Sound at 50% (pre-activation: muted start +
410
- live unmute on the user's first gesture); CHROMELESS — the
411
- card's mux-replica badge above is the play affordance for
412
- every non-playing state. */}
413
- <Video
414
- kind="file"
415
- url={bite.url}
416
- poster={bite.thumbnail_url}
417
- playWhenHovered={isActive}
418
- chromeless
419
- layout="fill"
420
- />
421
- </div>
422
- )}
352
+ {/* CONTROLLED hover playback keyed to CARD hover (`isActive`): the
353
+ detail overlay is part of the card, so moving the pointer onto
354
+ it keeps playing. Sound at 50% (pre-activation: muted start +
355
+ live unmute on the user's first gesture); chrome = center
356
+ play/pause only (Figma card look). */}
357
+ <Video
358
+ kind="file"
359
+ url={bite.url}
360
+ poster={bite.thumbnail_url}
361
+ playWhenHovered={isActive}
362
+ centerControlsOnly
363
+ layout="fill"
364
+ />
423
365
  </div>
424
366
  ) : (
425
367
  // Aspect-matched placeholder until the card nears the viewport (CLS-free).
@@ -32,7 +32,8 @@
32
32
 
33
33
  import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
34
34
  import MuxPlayer from '@mux/mux-player-react';
35
- import { VideoPlayBadge, VideoUnmuteGlyph } from './video-center-badge';
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
  // =============================================================================
@@ -252,12 +253,6 @@ interface VideoFileProps extends VideoCommonProps {
252
253
  /** Hide the bottom control bar, keep only the CENTER play/pause control
253
254
  * (bite-strip cards per Figma). */
254
255
  centerControlsOnly?: boolean;
255
- /** Render ONLY a lightweight first-frame preview — a metadata-only element
256
- * seeked to `#t=0.1` (media-fragment trick; paints on iOS Safari where a
257
- * fragmentless metadata load stays blank). No chrome, no playback, no
258
- * MuxPlayer cost — the resting facade layer under strip cards when no
259
- * poster asset exists. All player props are ignored. */
260
- firstFrameOnly?: boolean;
261
256
  }
262
257
 
263
258
  interface VideoYouTubeProps extends VideoCommonProps {
@@ -278,7 +273,6 @@ interface VideoAutoProps extends VideoCommonProps {
278
273
  playOnHover?: boolean;
279
274
  playWhenHovered?: boolean;
280
275
  centerControlsOnly?: boolean;
281
- firstFrameOnly?: boolean;
282
276
  }
283
277
 
284
278
  export type VideoProps = VideoFileProps | VideoYouTubeProps | VideoAutoProps;
@@ -303,8 +297,6 @@ export function Video(props: VideoProps): React.ReactElement | null {
303
297
  className={props.className}
304
298
  minimalControls={props.minimalControls}
305
299
  />
306
- ) : 'firstFrameOnly' in props && props.firstFrameOnly ? (
307
- <FirstFramePreview url={url} className={props.className} />
308
300
  ) : (
309
301
  <FilePlayer
310
302
  url={url}
@@ -381,35 +373,6 @@ function wrapWithLayout(
381
373
  }
382
374
  }
383
375
 
384
- // -----------------------------------------------------------------------------
385
- // First-frame preview — the `firstFrameOnly` facade branch
386
- // -----------------------------------------------------------------------------
387
-
388
- /** First-frame paint through the SAME FilePlayer/MuxPlayer pipeline as
389
- * playback (one rendering path for every file source — HLS included, which a
390
- * plain `<video>` couldn't decode in Chromium): a chromeless, muted,
391
- * metadata-only instance whose `#t=0.1` media fragment seeks-and-paints the
392
- * first frame (also fixes iOS Safari, which stays blank on a fragmentless
393
- * metadata load). The transparent media background keeps the box showing the
394
- * card surface (never black) until the frame decodes. */
395
- function FirstFramePreview({ url, className }: { url: string; className?: string }): React.ReactElement {
396
- return (
397
- <div
398
- aria-hidden
399
- className="h-full w-full"
400
- style={{ '--media-background-color': 'transparent' } as React.CSSProperties}
401
- >
402
- <FilePlayer
403
- url={`${url}#t=0.1`}
404
- preload="metadata"
405
- muted
406
- chromeless
407
- className={className}
408
- />
409
- </div>
410
- );
411
- }
412
-
413
376
  // -----------------------------------------------------------------------------
414
377
  // File branch — MuxPlayer (handles both .m3u8 HLS and plain .mp4)
415
378
  // -----------------------------------------------------------------------------
@@ -426,8 +389,6 @@ interface FilePlayerProps {
426
389
  playOnHover?: boolean;
427
390
  playWhenHovered?: boolean;
428
391
  centerControlsOnly?: boolean;
429
- /** Media preload hint — the firstFrameOnly facade passes 'metadata'. */
430
- preload?: 'none' | 'metadata' | 'auto';
431
392
  className?: string;
432
393
  }
433
394
 
@@ -443,7 +404,6 @@ function FilePlayer({
443
404
  playOnHover,
444
405
  playWhenHovered,
445
406
  centerControlsOnly,
446
- preload,
447
407
  className,
448
408
  }: FilePlayerProps): React.ReactElement {
449
409
  // centerControlsOnly: the center play button shows at REST only — while
@@ -591,7 +551,6 @@ function FilePlayer({
591
551
  src={url}
592
552
  poster={poster || undefined}
593
553
  streamType="on-demand"
594
- preload={preload}
595
554
  playsInline
596
555
  muted={muted}
597
556
  preferCmcd="header"
@@ -657,11 +616,9 @@ function FilePlayer({
657
616
  aria-label="Unmute"
658
617
  title="Unmute"
659
618
  onClick={unmuteNow}
660
- // White at rest, ACCENT while the icon itself is hovered — the same
661
- // hover language as every mux control icon (see app-globals.css).
662
- className="absolute inset-0 z-10 m-auto flex h-14 w-14 items-center justify-center text-ods-text-primary transition-colors hover:text-ods-accent"
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"
663
620
  >
664
- <VideoUnmuteGlyph />
621
+ <VolumeXmarkIcon size={56} />
665
622
  </button>
666
623
  ) : null;
667
624
 
@@ -925,10 +882,9 @@ function YouTubeFacadeInner({
925
882
  />
926
883
  </picture>
927
884
  <div className="absolute inset-0 flex items-center justify-center bg-ods-bg-inverse bg-opacity-20 transition-opacity duration-200 group-hover:bg-opacity-30">
928
- {/* THE shared center play badge (video-center-badge.tsx) same disc
929
- as strip cards / carousel thumbs / unmute chip; hero size + the
930
- facade's hover-scale affordance. */}
931
- <VideoPlayBadge size="lg" className="transition-transform duration-200 group-hover:scale-110 group-hover:text-ods-accent" />
885
+ <span className="flex items-center justify-center w-16 h-16 rounded-full bg-ods-accent text-ods-text-on-accent shadow-lg transition-transform duration-200 group-hover:scale-110">
886
+ <PlayIcon size={24} color="currentColor" className="ml-1" />
887
+ </span>
932
888
  </div>
933
889
  </button>
934
890
  </div>
@@ -4,7 +4,7 @@ import { useState, useRef, useEffect, memo, useCallback } from 'react';
4
4
  import { cn } from "../utils/cn";
5
5
  import { MediaItem } from '../utils/media-carousel-utils-stub';
6
6
  import { Video, extractYouTubeId } from './features/video';
7
- import { VideoPlayBadge } from './features/video-center-badge';
7
+ import { PlayIcon } from './icons-v2-generated/media-playback/play-icon';
8
8
 
9
9
  // Navigation icons
10
10
  const ChevronLeftIcon = () => (
@@ -215,7 +215,9 @@ export const MediaCarousel = memo(function MediaCarousel({
215
215
  {/* Play icon overlay for videos */}
216
216
  {(item.type === 'video' || item.type === 'youtube') && (
217
217
  <div className="absolute inset-0 flex items-center justify-center bg-black/30">
218
- <VideoPlayBadge size="sm" />
218
+ <div className="bg-black/70 rounded-full p-1">
219
+ <PlayIcon size={12} color="white" />
220
+ </div>
219
221
  </div>
220
222
  )}
221
223
 
@@ -47,7 +47,6 @@ export * from './breadcrumb'
47
47
  export * from './dropdown-menu'
48
48
  export * from './menubar'
49
49
  export * from './navigation-menu'
50
- export * from './crossfade-panels'
51
50
  export * from './tab-content'
52
51
  export * from './tab-navigation'
53
52
  // Feedback components
@@ -7,8 +7,7 @@ import { cn } from '../../utils/cn'
7
7
  export interface TabItem {
8
8
  id: string
9
9
  label: string
10
- /** Optional — text-only tabs (e.g. the homepage content-strip switcher) omit it. */
11
- icon?: React.ComponentType<React.SVGProps<SVGSVGElement>>
10
+ icon: React.ComponentType<React.SVGProps<SVGSVGElement>>
12
11
  component?: React.ComponentType<any>
13
12
  indicator?: 'success' | 'warning' | 'error'
14
13
  }
@@ -30,9 +29,6 @@ interface TabNavigationProps {
30
29
  showRightGradient?: boolean
31
30
  /** Force the left-edge gradient to always render, independent of scroll state. */
32
31
  showLeftGradient?: boolean
33
- /** Tabs grow to share the bar's full width equally (Figma segmented-underline
34
- * look, e.g. the 480px homepage strip switcher). Default: natural width. */
35
- stretchTabs?: boolean
36
32
 
37
33
  // URL sync mode
38
34
  urlSync?: boolean | TabNavigationUrlSyncOptions
@@ -50,7 +46,6 @@ export function TabNavigation({
50
46
  shadowClassName,
51
47
  showRightGradient = false,
52
48
  showLeftGradient = false,
53
- stretchTabs = false,
54
49
  urlSync = false,
55
50
  defaultTab,
56
51
  children
@@ -227,36 +222,24 @@ export function TabNavigation({
227
222
  className={cn(
228
223
  "flex gap-[var(--spacing-system-xxs)] items-center justify-center p-[var(--spacing-system-m)] relative shrink-0 cursor-pointer",
229
224
  "transition-all duration-200 bg-transparent border-none outline-none",
230
- stretchTabs && 'flex-1',
231
225
  isActive
232
226
  ? 'bg-gradient-to-b from-[rgba(255,255,255,0)] to-[rgba(255,255,255,0.1)]'
233
227
  : 'hover:bg-gradient-to-b hover:from-[rgba(255,255,255,0)] hover:to-[rgba(255,255,255,0.1)]'
234
228
  )}
235
229
  >
236
- {tab.icon ? (
237
- <div className="relative flex items-center justify-center">
238
- <tab.icon
239
- className={cn("h-4 w-4 md:h-6 md:w-6 transition-colors", isActive ? 'text-ods-accent' : 'text-ods-text-secondary')}
240
- />
241
- {tab.indicator && (
242
- <div className={cn(
243
- "absolute right-0 top-[-3px] w-3 h-3 rounded-full border-2 border-ods-bg",
244
- tab.indicator === 'error' && 'bg-ods-error',
245
- tab.indicator === 'warning' && 'bg-ods-accent',
246
- tab.indicator === 'success' && 'bg-green-500'
247
- )} />
248
- )}
249
- </div>
250
- ) : tab.indicator ? (
251
- // Text-only tabs keep their status badge — inline dot before
252
- // the label (there's no icon corner to anchor to).
253
- <div className={cn(
254
- "w-3 h-3 shrink-0 rounded-full border-2 border-ods-bg",
255
- tab.indicator === 'error' && 'bg-ods-error',
256
- tab.indicator === 'warning' && 'bg-ods-accent',
257
- tab.indicator === 'success' && 'bg-green-500'
258
- )} />
259
- ) : null}
230
+ <div className="relative flex items-center justify-center">
231
+ <tab.icon
232
+ className={cn("h-4 w-4 md:h-6 md:w-6 transition-colors", isActive ? 'text-ods-accent' : 'text-ods-text-secondary')}
233
+ />
234
+ {tab.indicator && (
235
+ <div className={cn(
236
+ "absolute right-0 top-[-3px] w-3 h-3 rounded-full border-2 border-ods-bg",
237
+ tab.indicator === 'error' && 'bg-ods-error',
238
+ tab.indicator === 'warning' && 'bg-ods-accent',
239
+ tab.indicator === 'success' && 'bg-green-500'
240
+ )} />
241
+ )}
242
+ </div>
260
243
 
261
244
  <span className={cn(
262
245
  "text-h4 whitespace-nowrap transition-colors",
@@ -182,26 +182,3 @@ body {
182
182
  --ring: 180 100% 69%;
183
183
  --radius: 0.5rem;
184
184
  }
185
-
186
- /* ============================================================================
187
- Video center-control identity — ONE look across every video surface:
188
- a bare solid-white glyph, NO background disc (see
189
- components/features/video-center-badge.tsx for the React consumers).
190
- This strips media-chrome's default dark circle off MuxPlayer's big
191
- center pre-play button so full players match the strip/facade glyphs.
192
- ========================================================================== */
193
- /* REAL players (any mounted MuxPlayer): icons are white at rest and turn
194
- the platform ACCENT color while the icon's control is hovered — every
195
- media-chrome control carries the `button` part token, so this covers
196
- center play, bar play/mute/fullscreen/etc. uniformly. */
197
- mux-player::part(button):hover {
198
- --media-icon-color: var(--ods-accent, var(--color-accent-primary));
199
- }
200
- mux-player::part(center play button) {
201
- background: transparent;
202
- filter: drop-shadow(0 1px 4px rgba(0, 0, 0, 0.5));
203
- }
204
- mux-player::part(center play button):hover {
205
- background: transparent;
206
- opacity: 0.85;
207
- }