@flamingo-stack/openframe-frontend-core 0.0.844 → 0.0.846

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 (71) hide show
  1. package/dist/{case-study-card-VN3JBNF5.cjs → case-study-card-DJOHIHYU.cjs} +3 -3
  2. package/dist/{chunk-KSOBM66X.cjs → chunk-2FUTZKLB.cjs} +4 -4
  3. package/dist/{chunk-SVI3AB3A.cjs → chunk-57JYB3E2.cjs} +3 -3
  4. package/dist/{chunk-23DVVS4I.cjs → chunk-ABL5OBSF.cjs} +110 -110
  5. package/dist/{chunk-EUEPGHY4.cjs → chunk-BIOSHDXQ.cjs} +14 -11
  6. package/dist/{chunk-4ASUUB3A.cjs → chunk-BZZE4KIV.cjs} +71 -71
  7. package/dist/{chunk-MWQOM2EI.cjs → chunk-D5DMWXF2.cjs} +4 -4
  8. package/dist/{chunk-B4FUDHRE.cjs → chunk-DEEQSVLX.cjs} +62 -62
  9. package/dist/{chunk-NMLNZCK2.cjs → chunk-DGQDGCQH.cjs} +2 -2
  10. package/dist/{chunk-6HFTSVVC.cjs → chunk-FLW4WULE.cjs} +8 -8
  11. package/dist/{chunk-GBB7NLN7.cjs → chunk-HBDLJUI3.cjs} +3 -3
  12. package/dist/{chunk-SOTTMZ6U.cjs → chunk-HPXET3LH.cjs} +17 -17
  13. package/dist/{chunk-QPJUQ5YU.cjs → chunk-J2ZSGOUC.cjs} +14 -14
  14. package/dist/{chunk-M7QI23GE.cjs → chunk-MPVOPSU6.cjs} +74 -74
  15. package/dist/{chunk-EGOCAIZJ.cjs → chunk-N2A24FDF.cjs} +183 -181
  16. package/dist/{chunk-6CZVQ72C.cjs → chunk-PGAMWM6C.cjs} +3 -3
  17. package/dist/{chunk-KSPMB4GV.cjs → chunk-PQDCSZ3W.cjs} +52 -51
  18. package/dist/{chunk-7ERU4NGF.cjs → chunk-PV3NKXWS.cjs} +39 -39
  19. package/dist/{chunk-6M764K5Z.cjs → chunk-QK2A334V.cjs} +5 -5
  20. package/dist/{chunk-SC36S7DJ.cjs → chunk-RAJ5FYFL.cjs} +2 -2
  21. package/dist/{chunk-WPMJUYW3.cjs → chunk-SIFRIUPW.cjs} +79 -79
  22. package/dist/{chunk-5CPEMPNP.cjs → chunk-TBOWFTEN.cjs} +2 -2
  23. package/dist/{chunk-RSZZTB5T.cjs → chunk-URP6GYTD.cjs} +49 -49
  24. package/dist/{chunk-USUVHAQ7.cjs → chunk-X432Y6RM.cjs} +29 -1
  25. package/dist/{chunk-WUJGPHF2.cjs → chunk-Y2OEC7UW.cjs} +20 -20
  26. package/dist/{chunk-C2X73N3E.cjs → chunk-YC52LSPP.cjs} +34 -34
  27. package/dist/{chunk-IUWCOK3X.cjs → chunk-ZTO7KNYV.cjs} +11 -11
  28. package/dist/{cloud-tenants-5C32YDIE.cjs → cloud-tenants-XEUUMABT.cjs} +10 -10
  29. package/dist/components/case-studies/index.cjs +25 -25
  30. package/dist/components/chat/index.cjs +8 -8
  31. package/dist/components/contact/index.cjs +11 -11
  32. package/dist/components/docs/index.cjs +14 -14
  33. package/dist/components/embeds/index.cjs +11 -11
  34. package/dist/components/faq/index.cjs +12 -12
  35. package/dist/components/features/index.cjs +10 -10
  36. package/dist/components/features/video-hover-preview.d.ts +7 -1
  37. package/dist/components/features/video-hover-preview.d.ts.map +1 -1
  38. package/dist/components/features/video-hover-preview.js +4 -1
  39. package/dist/components/features/video-shared.d.ts +2 -0
  40. package/dist/components/features/video-shared.d.ts.map +1 -1
  41. package/dist/components/features/video-shared.js +29 -1
  42. package/dist/components/help-center-pages/index.cjs +85 -85
  43. package/dist/components/index.cjs +55 -55
  44. package/dist/components/meeting-scheduler/index.cjs +36 -36
  45. package/dist/components/navigation/index.cjs +13 -13
  46. package/dist/components/navigation/site-footer.d.ts.map +1 -1
  47. package/dist/components/navigation/site-footer.js +2 -1
  48. package/dist/components/onboarding-guides/index.cjs +13 -13
  49. package/dist/components/product-demo/index.cjs +19 -19
  50. package/dist/components/related-content/index.cjs +6 -6
  51. package/dist/components/social-icon-row.d.ts.map +1 -1
  52. package/dist/components/social-icon-row.js +2 -0
  53. package/dist/components/tickets/index.cjs +15 -15
  54. package/dist/components/ui/index.cjs +10 -10
  55. package/dist/{devices-F2ENCTJM.cjs → devices-7YDIHVVP.cjs} +11 -11
  56. package/dist/{embeddable-chat-C6DUH2JL.cjs → embeddable-chat-Z3S37SSM.cjs} +78 -78
  57. package/dist/index.cjs +14 -14
  58. package/dist/{knowledge-ZNUNREKL.cjs → knowledge-SQVPQSKM.cjs} +11 -11
  59. package/dist/{logs-DHZFCX2I.cjs → logs-M5DFGUBC.cjs} +17 -17
  60. package/dist/{policies-2FKRLSOC.cjs → policies-T3VQEJUD.cjs} +14 -14
  61. package/dist/{scripts-J3J7XCT4.cjs → scripts-JFRFJKPH.cjs} +15 -15
  62. package/dist/{software-update-AIQHLJ5O.cjs → software-update-E7W3AAIY.cjs} +13 -13
  63. package/dist/types/navigation.d.ts +2 -0
  64. package/dist/types/navigation.d.ts.map +1 -1
  65. package/dist/{video-file-player-IHO66P2Q.cjs → video-file-player-XMOB234V.cjs} +11 -11
  66. package/package.json +1 -1
  67. package/src/components/features/video-hover-preview.tsx +48 -38
  68. package/src/components/features/video-shared.tsx +36 -0
  69. package/src/components/navigation/site-footer.tsx +3 -2
  70. package/src/components/social-icon-row.tsx +2 -0
  71. package/src/types/navigation.ts +8 -1
@@ -18,6 +18,12 @@
18
18
  * - Player mount is controlled via `playerMounted` (the strip's shared gate)
19
19
  * OR self-managed here via a two-way near-viewport observer (standalone
20
20
  * editor cards). Exactly one path is ever active.
21
+ * - Inside that gate the player waits for the visitor's first input: until then
22
+ * the card is its poster, and a visitor who only reads the first screen loads
23
+ * no player (the largest script a page can load, 1.1 MB to parse). From that
24
+ * input on it mounts near the screen, because a hover must find a player with
25
+ * its stream loaded: mounting on the first hover made the bites strip start
26
+ * late and stutter, and was removed (CU-17tkuw5xp4q).
21
27
  */
22
28
 
23
29
  import type React from 'react';
@@ -30,7 +36,7 @@ import { useCoverImageFallback } from '../chat/entity-cards/use-cover-image-fall
30
36
  import { toMuxPreviewUrl } from './mux-origins';
31
37
  import { Video } from './video';
32
38
  import { VideoPlayBadge } from './video-center-badge';
33
- import type { VideoPlayerHandle, VideoMutedFallbackState } from './video-shared';
39
+ import { useVisitorActed, type VideoPlayerHandle, type VideoMutedFallbackState } from './video-shared';
34
40
 
35
41
  export interface VideoHoverPreviewSurfaceProps {
36
42
  /** Source URL (Mux HLS / Supabase mp4). Rendition-capped internally. */
@@ -126,6 +132,8 @@ export function VideoHoverPreviewSurface({
126
132
  }, [gateControlled]);
127
133
  // Continuation forces the media + player on regardless of hover/viewport gate.
128
134
  const showMedia = continuation || (gateControlled ? playerMounted : isNear);
135
+ const visitorActed = useVisitorActed();
136
+ const showPlayer = continuation || active || visitorActed;
129
137
 
130
138
  // Poster resolution — the shared entity-card cover fallback chain: the real
131
139
  // thumbnail, dropped on load error. Null → first-frame facade (unless the
@@ -160,43 +168,45 @@ export function VideoHoverPreviewSurface({
160
168
 
161
169
  {badge === 'play' && !active && !continuation && <VideoPlayBadge className="absolute inset-0 z-10 m-auto" />}
162
170
 
163
- <div
164
- className="absolute inset-0"
165
- style={{ '--media-background-color': 'transparent' } as React.CSSProperties}
166
- >
167
- <Video
168
- // Remount between modes: `startTime` + autoplay are LOAD-TIME
169
- // props (playback-core binds them to one-shot durationchange /
170
- // loadstart handlers), so re-propping an already-loaded element
171
- // silently drops the seek. Only a Mux preview rewrite changed
172
- // `src` before, which is why HLS resumed and plain MP4 did not.
173
- key={continuation ? 'continuation' : 'preview'}
174
- kind="file"
175
- // Rendition-capped in BOTH modes: the cap was only skipped
176
- // because changing `src` used to be the sole trigger for the
177
- // seek; the explicit key handles that now, and a 320px mini
178
- // player should never pull the uncapped manifest.
179
- url={previewUrl}
180
- poster={posterUrl}
181
- {...(continuation
182
- ? {
183
- startTime,
184
- autoPlay: autoPlay && startMuted,
185
- startMuted,
186
- autoPlayUnmuted: autoPlay && !startMuted,
187
- }
188
- : { playWhenHovered: active })}
189
- chromeless
190
- layout="fill"
191
- fit={fit}
192
- preload={preload}
193
- mutedIntent={mutedIntent}
194
- hideMutedBadge={hideMutedBadge}
195
- onMutedFallbackChange={onMutedFallbackChange}
196
- onEnded={onEnded}
197
- playerHandleRef={previewHandleRef}
198
- />
199
- </div>
171
+ {showPlayer && (
172
+ <div
173
+ className="absolute inset-0"
174
+ style={{ '--media-background-color': 'transparent' } as React.CSSProperties}
175
+ >
176
+ <Video
177
+ // Remount between modes: `startTime` + autoplay are LOAD-TIME
178
+ // props (playback-core binds them to one-shot durationchange /
179
+ // loadstart handlers), so re-propping an already-loaded element
180
+ // silently drops the seek. Only a Mux preview rewrite changed
181
+ // `src` before, which is why HLS resumed and plain MP4 did not.
182
+ key={continuation ? 'continuation' : 'preview'}
183
+ kind="file"
184
+ // Rendition-capped in BOTH modes: the cap was only skipped
185
+ // because changing `src` used to be the sole trigger for the
186
+ // seek; the explicit key handles that now, and a 320px mini
187
+ // player should never pull the uncapped manifest.
188
+ url={previewUrl}
189
+ poster={posterUrl}
190
+ {...(continuation
191
+ ? {
192
+ startTime,
193
+ autoPlay: autoPlay && startMuted,
194
+ startMuted,
195
+ autoPlayUnmuted: autoPlay && !startMuted,
196
+ }
197
+ : { playWhenHovered: active })}
198
+ chromeless
199
+ layout="fill"
200
+ fit={fit}
201
+ preload={preload}
202
+ mutedIntent={mutedIntent}
203
+ hideMutedBadge={hideMutedBadge}
204
+ onMutedFallbackChange={onMutedFallbackChange}
205
+ onEnded={onEnded}
206
+ playerHandleRef={previewHandleRef}
207
+ />
208
+ </div>
209
+ )}
200
210
  </>
201
211
  ) : (
202
212
  <div className="absolute inset-0 bg-ods-card" />
@@ -12,6 +12,7 @@
12
12
  */
13
13
 
14
14
  import type React from 'react';
15
+ import { useSyncExternalStore } from 'react';
15
16
  import { Button } from '../ui/button';
16
17
 
17
18
  // =============================================================================
@@ -51,6 +52,41 @@ if (typeof window !== 'undefined') {
51
52
  }
52
53
  }
53
54
 
55
+ // =============================================================================
56
+ // The visitor's first input (module scope). Wider than activation above: a mouse
57
+ // that moves or a wheel that turns counts, because a hover comes after one. Until
58
+ // it, a hover preview draws its poster and loads no player, the largest script a
59
+ // page can load (`VideoHoverPreviewSurface`).
60
+ // =============================================================================
61
+
62
+ const VISITOR_INPUT_EVENTS = ['pointermove', 'pointerdown', 'keydown', 'wheel'] as const;
63
+ let visitorActed = false;
64
+ const visitorActedListeners = new Set<() => void>();
65
+ if (typeof window !== 'undefined') {
66
+ const markActed = () => {
67
+ visitorActed = true;
68
+ for (const type of VISITOR_INPUT_EVENTS) window.removeEventListener(type, markActed, true);
69
+ for (const listener of visitorActedListeners) listener();
70
+ visitorActedListeners.clear();
71
+ };
72
+ for (const type of VISITOR_INPUT_EVENTS) window.addEventListener(type, markActed, { capture: true, passive: true });
73
+ }
74
+ function subscribeToVisitorActed(listener: () => void): () => void {
75
+ if (visitorActed) return () => undefined;
76
+ visitorActedListeners.add(listener);
77
+ return () => {
78
+ visitorActedListeners.delete(listener);
79
+ };
80
+ }
81
+ /** True once the visitor has moved the pointer, touched the page, turned the wheel or pressed a key. False on the server. */
82
+ export function useVisitorActed(): boolean {
83
+ return useSyncExternalStore(
84
+ subscribeToVisitorActed,
85
+ () => visitorActed,
86
+ () => false,
87
+ );
88
+ }
89
+
54
90
  // =============================================================================
55
91
  // Playback-failure handling
56
92
  // =============================================================================
@@ -155,9 +155,10 @@ export function SiteFooter({
155
155
 
156
156
  const columns = nav.footerColumns;
157
157
  const accordion = columns.length > ACCORDION_OVER;
158
+ // The site's feeds close the footer's row: the header draws `brand.social` alone.
159
+ const footerSocial = [...nav.brand.social, ...(nav.brand.feeds ?? [])];
158
160
  const socialRow =
159
- social ??
160
- (nav.brand.social.length > 0 ? <SocialIconRow compact links={siteNavSocialLinks(nav.brand.social)} /> : null);
161
+ social ?? (footerSocial.length > 0 ? <SocialIconRow compact links={siteNavSocialLinks(footerSocial)} /> : null);
161
162
 
162
163
  return (
163
164
  <footer
@@ -64,6 +64,8 @@ const SOCIAL_ICONS: Readonly<Record<string, IconRef>> = {
64
64
  instagram: { set: 'InstagramIcon', color: MARK },
65
65
  facebook: { set: 'FacebookIcon', color: MARK },
66
66
  tiktok: { lucide: 'music' },
67
+ // A site's own feed (`brand.feeds`, a share row). Like CopyIcon, its default fill is grey.
68
+ rss: { set: 'RssIcon', color: MARK },
67
69
  // CopyIcon's default fill is grey and would mismatch its row-mates.
68
70
  copy: { set: 'CopyIcon', color: MARK },
69
71
  };
@@ -100,7 +100,14 @@ export interface SiteNav {
100
100
  primaryCta: SiteNavPrimaryCta;
101
101
  closingBand?: { heading: string; subheading: string; secondary?: NavLink };
102
102
  legal: { company: string; notes: string[]; links: NavLink[] };
103
- brand: { name: string; tagline: string; social: NavLink[]; statusUrl?: string };
103
+ brand: {
104
+ name: string;
105
+ tagline: string;
106
+ social: NavLink[];
107
+ /** The site's own RSS feeds (`iconName: 'rss'`). Drawn at the end of the FOOTER's social row only, never in the header. */
108
+ feeds?: NavLink[];
109
+ statusUrl?: string;
110
+ };
104
111
  }
105
112
 
106
113
  /**