@flamingo-stack/openframe-frontend-core 0.0.705 → 0.0.706-snapshot.20261006190707

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 (206) hide show
  1. package/dist/{chunk-4KX6OED2.cjs → chunk-2G7UYK35.cjs} +46 -46
  2. package/dist/{chunk-KOWVJD4V.cjs → chunk-2KXTOP6G.cjs} +70 -57
  3. package/dist/{chunk-A75QY6CD.cjs → chunk-2RNB5DTN.cjs} +21 -21
  4. package/dist/{chunk-PN2RKPDF.js → chunk-36BCTE7D.js} +10 -10
  5. package/dist/{chunk-ZSAGLQOD.cjs → chunk-3AHTOSXJ.cjs} +17 -17
  6. package/dist/{chunk-VR2ZLFCP.js → chunk-4IBPRIQH.js} +1 -1
  7. package/dist/{chunk-FCGC42TS.js → chunk-4L6XCMOU.js} +66 -62
  8. package/dist/{chunk-X5MQ7JRP.js → chunk-4UM2ETLM.js} +9 -9
  9. package/dist/{chunk-6NJKKO73.cjs → chunk-6I4ZAFO2.cjs} +15 -15
  10. package/dist/{chunk-RBDP4WDE.js → chunk-6IW7TWPV.js} +6 -6
  11. package/dist/{chunk-MMOODW57.cjs → chunk-6NTPY5QA.cjs} +1887 -94
  12. package/dist/{chunk-QFXRPRFT.js → chunk-6YKAT7NV.js} +3 -3
  13. package/dist/{chunk-HPWWNJE7.cjs → chunk-AOWDNSTM.cjs} +89 -89
  14. package/dist/{chunk-IHERL5V4.cjs → chunk-B73373AG.cjs} +34 -34
  15. package/dist/{chunk-DENB2XJC.js → chunk-CNWOMQQJ.js} +2 -2
  16. package/dist/{chunk-MP2C3LJ4.js → chunk-CUME3VTQ.js} +1 -1
  17. package/dist/{chunk-PDWVPI3S.js → chunk-CZ7Y7UG4.js} +4 -72
  18. package/dist/{chunk-YHM5AWTT.js → chunk-CZSZSDD4.js} +6 -6
  19. package/dist/{chunk-F6I6XZMN.js → chunk-DA5UDTLG.js} +5 -5
  20. package/dist/{chunk-MFFCUVWG.js → chunk-F57ECE2I.js} +1 -1
  21. package/dist/{chunk-CH5232HV.js → chunk-FI6GMDO7.js} +2455 -662
  22. package/dist/{chunk-NDESEPW5.js → chunk-FXJ3WWUB.js} +68 -0
  23. package/dist/{chunk-G5A3PSPV.js → chunk-HDOYSEOJ.js} +1080 -691
  24. package/dist/{chunk-VA5T32XA.js → chunk-I3ST4PLM.js} +2 -2
  25. package/dist/{chunk-5HBHO5TB.js → chunk-JPG7OHEG.js} +1 -1
  26. package/dist/{chunk-YFBM2K6X.cjs → chunk-JRU2CXLB.cjs} +4 -4
  27. package/dist/{chunk-MKXO4Q5P.js → chunk-KUC673HM.js} +383 -142
  28. package/dist/{chunk-JEZKGCZI.cjs → chunk-L6Y6OOKG.cjs} +26 -26
  29. package/dist/{chunk-F6AFPNDF.js → chunk-LFFB6EMB.js} +29 -16
  30. package/dist/{chunk-X72J4ACL.cjs → chunk-LLSUVPQC.cjs} +252 -8
  31. package/dist/{chunk-RKV5B7EU.cjs → chunk-LM7TXMLU.cjs} +72 -4
  32. package/dist/{chunk-6SEANMOU.js → chunk-LNW45N2C.js} +11 -11
  33. package/dist/{chunk-OUY3HHW6.cjs → chunk-LRQTJOF2.cjs} +928 -539
  34. package/dist/{chunk-DW7G4KG3.js → chunk-MR3A4MZV.js} +3 -3
  35. package/dist/{chunk-2YXVWZVF.js → chunk-N45D4ZXQ.js} +959 -715
  36. package/dist/{chunk-Z2VNQQ74.js → chunk-NCPTID44.js} +978 -942
  37. package/dist/{chunk-WA6GUYQ4.cjs → chunk-NSGXARGJ.cjs} +104 -104
  38. package/dist/{chunk-ABU3TGXA.cjs → chunk-OF6GEI5A.cjs} +3 -3
  39. package/dist/{chunk-IKTQ3ZAG.cjs → chunk-OJLR5MEO.cjs} +6 -6
  40. package/dist/{chunk-VESKITRQ.cjs → chunk-P42V6N34.cjs} +736 -732
  41. package/dist/{chunk-OHFS7VOU.cjs → chunk-PLYEKV3N.cjs} +2 -2
  42. package/dist/{chunk-7VVFKJY5.js → chunk-PO33GZVY.js} +1799 -3449
  43. package/dist/{chunk-7LS22BZU.cjs → chunk-QAOFMK2A.cjs} +14 -14
  44. package/dist/{chunk-VHCACDKP.cjs → chunk-RQRX4YC3.cjs} +39 -39
  45. package/dist/{chunk-3MX5S4CF.cjs → chunk-SEXWHIMS.cjs} +899 -2549
  46. package/dist/{chunk-AQWZNP6A.js → chunk-SS5MBHDP.js} +4 -4
  47. package/dist/{chunk-ZSAO2HBW.js → chunk-TDYIREDJ.js} +15 -15
  48. package/dist/{chunk-XOYH2YQE.cjs → chunk-UDFRUKLC.cjs} +2 -2
  49. package/dist/{chunk-TYWLNCNH.cjs → chunk-UVTD52MD.cjs} +14 -82
  50. package/dist/{chunk-N3COXDBH.cjs → chunk-VX4E6YSB.cjs} +41 -5
  51. package/dist/{chunk-2QEUFXNS.cjs → chunk-WCVZCGK5.cjs} +312 -71
  52. package/dist/{chunk-6XIF2Z7I.js → chunk-WMX26HB5.js} +3 -3
  53. package/dist/{chunk-D7BTSVMS.cjs → chunk-Y2GSOUCS.cjs} +2 -2
  54. package/dist/{chunk-4I45Y3DN.cjs → chunk-YBUT4AMG.cjs} +6 -6
  55. package/dist/components/case-studies/index.cjs +30 -32
  56. package/dist/components/case-studies/index.js +14 -16
  57. package/dist/components/chat/entity-cards/case-study-card.d.ts +11 -4
  58. package/dist/components/chat/entity-cards/case-study-card.d.ts.map +1 -1
  59. package/dist/components/chat/index.cjs +11 -11
  60. package/dist/components/chat/index.d.ts +1 -1
  61. package/dist/components/chat/index.d.ts.map +1 -1
  62. package/dist/components/chat/index.js +16 -16
  63. package/dist/components/chat/nav-link-anchor-via-runtime.d.ts +34 -5
  64. package/dist/components/chat/nav-link-anchor-via-runtime.d.ts.map +1 -1
  65. package/dist/components/chat/utils/icon-library.d.ts.map +1 -1
  66. package/dist/components/contact/index.cjs +12 -14
  67. package/dist/components/contact/index.js +11 -13
  68. package/dist/components/docs/index.cjs +15 -17
  69. package/dist/components/docs/index.js +14 -16
  70. package/dist/components/embeds/index.cjs +12 -14
  71. package/dist/components/embeds/index.js +11 -13
  72. package/dist/components/faq/index.cjs +13 -15
  73. package/dist/components/faq/index.js +12 -14
  74. package/dist/components/features/index.cjs +12 -14
  75. package/dist/components/features/index.js +21 -23
  76. package/dist/components/help-center-pages/index.cjs +118 -120
  77. package/dist/components/help-center-pages/index.js +33 -35
  78. package/dist/components/icons/index.cjs +3 -5
  79. package/dist/components/icons/index.js +9 -11
  80. package/dist/components/icons-v2-generated/index.cjs +3 -5
  81. package/dist/components/icons-v2-generated/index.js +2 -4
  82. package/dist/components/index.cjs +87 -158
  83. package/dist/components/index.d.ts +0 -2
  84. package/dist/components/index.d.ts.map +1 -1
  85. package/dist/components/index.js +331 -402
  86. package/dist/components/loading/page-layout-skeleton.d.ts +1 -1
  87. package/dist/components/meeting-scheduler/index.cjs +62 -64
  88. package/dist/components/meeting-scheduler/index.js +12 -14
  89. package/dist/components/navigation/index.cjs +28 -16
  90. package/dist/components/navigation/index.d.ts +14 -10
  91. package/dist/components/navigation/index.d.ts.map +1 -1
  92. package/dist/components/navigation/index.js +38 -26
  93. package/dist/components/navigation/mingo-ai-button.d.ts +38 -3
  94. package/dist/components/navigation/mingo-ai-button.d.ts.map +1 -1
  95. package/dist/components/navigation/mobile-nav-sheet-id.d.ts +3 -0
  96. package/dist/components/navigation/mobile-nav-sheet-id.d.ts.map +1 -0
  97. package/dist/components/navigation/mobile-nav-sheet.d.ts +48 -0
  98. package/dist/components/navigation/mobile-nav-sheet.d.ts.map +1 -0
  99. package/dist/components/navigation/nav-item-row.d.ts +27 -0
  100. package/dist/components/navigation/nav-item-row.d.ts.map +1 -0
  101. package/dist/components/navigation/nav-panel.d.ts +43 -0
  102. package/dist/components/navigation/nav-panel.d.ts.map +1 -0
  103. package/dist/components/navigation/navigation-sidebar-item.d.ts +1 -2
  104. package/dist/components/navigation/navigation-sidebar-item.d.ts.map +1 -1
  105. package/dist/components/navigation/site-footer.d.ts +28 -0
  106. package/dist/components/navigation/site-footer.d.ts.map +1 -0
  107. package/dist/components/navigation/site-header.d.ts +61 -0
  108. package/dist/components/navigation/site-header.d.ts.map +1 -0
  109. package/dist/components/navigation/site-nav-link.d.ts +26 -0
  110. package/dist/components/navigation/site-nav-link.d.ts.map +1 -0
  111. package/dist/components/navigation/top-navigation.d.ts +2 -2
  112. package/dist/components/navigation/top-navigation.d.ts.map +1 -1
  113. package/dist/components/onboarding-guides/index.cjs +15 -17
  114. package/dist/components/onboarding-guides/index.js +14 -16
  115. package/dist/components/platform/ToolBadge.d.ts +6 -0
  116. package/dist/components/platform/ToolBadge.d.ts.map +1 -1
  117. package/dist/components/related-content/index.cjs +10 -8
  118. package/dist/components/related-content/index.js +9 -7
  119. package/dist/components/tickets/index.cjs +16 -18
  120. package/dist/components/tickets/index.js +15 -17
  121. package/dist/components/ui/brand-association-card.d.ts +7 -0
  122. package/dist/components/ui/brand-association-card.d.ts.map +1 -1
  123. package/dist/components/ui/index.cjs +14 -14
  124. package/dist/components/ui/index.js +19 -19
  125. package/dist/hooks/index.cjs +19 -11
  126. package/dist/hooks/index.d.ts +1 -0
  127. package/dist/hooks/index.d.ts.map +1 -1
  128. package/dist/hooks/index.js +19 -11
  129. package/dist/hooks/ui/index.d.ts +3 -0
  130. package/dist/hooks/ui/index.d.ts.map +1 -1
  131. package/dist/hooks/ui/use-active-section.d.ts +10 -0
  132. package/dist/hooks/ui/use-active-section.d.ts.map +1 -0
  133. package/dist/hooks/ui/use-nav-menus.d.ts +48 -0
  134. package/dist/hooks/ui/use-nav-menus.d.ts.map +1 -0
  135. package/dist/hooks/ui/use-visitor-os.d.ts +15 -0
  136. package/dist/hooks/ui/use-visitor-os.d.ts.map +1 -0
  137. package/dist/hooks/use-location-hash.d.ts +9 -0
  138. package/dist/hooks/use-location-hash.d.ts.map +1 -0
  139. package/dist/index.cjs +56 -21
  140. package/dist/index.js +231 -196
  141. package/dist/types/case-study.d.ts +9 -0
  142. package/dist/types/case-study.d.ts.map +1 -1
  143. package/dist/types/navigation.d.ts +95 -65
  144. package/dist/types/navigation.d.ts.map +1 -1
  145. package/dist/utils/index.cjs +55 -0
  146. package/dist/utils/index.d.ts +1 -0
  147. package/dist/utils/index.d.ts.map +1 -1
  148. package/dist/utils/index.js +50 -1
  149. package/dist/utils/source-icons.d.ts.map +1 -1
  150. package/dist/utils/visitor-os.d.ts +45 -0
  151. package/dist/utils/visitor-os.d.ts.map +1 -0
  152. package/package.json +1 -1
  153. package/src/components/chat/entity-cards/case-study-card.tsx +115 -5
  154. package/src/components/chat/index.ts +7 -1
  155. package/src/components/chat/nav-link-anchor-via-runtime.tsx +62 -9
  156. package/src/components/chat/utils/icon-library.tsx +35 -1
  157. package/src/components/index.ts +0 -2
  158. package/src/components/loading/page-layout-skeleton.tsx +1 -1
  159. package/src/components/navigation/index.ts +21 -14
  160. package/src/components/navigation/mingo-ai-button.tsx +139 -63
  161. package/src/components/navigation/mobile-nav-sheet-id.ts +2 -0
  162. package/src/components/navigation/mobile-nav-sheet.tsx +241 -0
  163. package/src/components/navigation/nav-item-row.tsx +129 -0
  164. package/src/components/navigation/nav-panel.tsx +196 -0
  165. package/src/components/navigation/navigation-sidebar-item.tsx +1 -2
  166. package/src/components/navigation/site-footer.tsx +256 -0
  167. package/src/components/navigation/site-header.tsx +339 -0
  168. package/src/components/navigation/site-nav-link.tsx +41 -0
  169. package/src/components/navigation/ticket-alerts-button.tsx +2 -2
  170. package/src/components/navigation/top-navigation.tsx +2 -2
  171. package/src/components/platform/ToolBadge.tsx +18 -1
  172. package/src/components/ui/brand-association-card.tsx +25 -3
  173. package/src/hooks/index.ts +1 -0
  174. package/src/hooks/ui/index.ts +3 -0
  175. package/src/hooks/ui/use-active-section.ts +44 -0
  176. package/src/hooks/ui/use-nav-menus.ts +200 -0
  177. package/src/hooks/ui/use-visitor-os.ts +33 -0
  178. package/src/hooks/use-location-hash.ts +34 -0
  179. package/src/styles/chat-animations.css +57 -2
  180. package/src/types/case-study.ts +16 -0
  181. package/src/types/navigation.ts +80 -66
  182. package/src/utils/index.ts +1 -0
  183. package/src/utils/source-icons.ts +13 -0
  184. package/src/utils/visitor-os.ts +84 -0
  185. package/dist/chunk-LMBC5FFP.js +0 -43
  186. package/dist/chunk-O43V3EOS.js +0 -252
  187. package/dist/chunk-QZVSJF2K.cjs +0 -252
  188. package/dist/chunk-RALRPFDW.cjs +0 -43
  189. package/dist/components/footer-waitlist-button.d.ts +0 -29
  190. package/dist/components/footer-waitlist-button.d.ts.map +0 -1
  191. package/dist/components/footer.d.ts +0 -47
  192. package/dist/components/footer.d.ts.map +0 -1
  193. package/dist/components/navigation/client-only-header.d.ts +0 -8
  194. package/dist/components/navigation/client-only-header.d.ts.map +0 -1
  195. package/dist/components/navigation/header-skeleton.d.ts +0 -12
  196. package/dist/components/navigation/header-skeleton.d.ts.map +0 -1
  197. package/dist/components/navigation/header.d.ts +0 -9
  198. package/dist/components/navigation/header.d.ts.map +0 -1
  199. package/dist/components/navigation/mobile-nav-panel.d.ts +0 -10
  200. package/dist/components/navigation/mobile-nav-panel.d.ts.map +0 -1
  201. package/src/components/footer-waitlist-button.tsx +0 -53
  202. package/src/components/footer.tsx +0 -195
  203. package/src/components/navigation/client-only-header.tsx +0 -22
  204. package/src/components/navigation/header-skeleton.tsx +0 -74
  205. package/src/components/navigation/header.tsx +0 -429
  206. package/src/components/navigation/mobile-nav-panel.tsx +0 -155
@@ -1,8 +1,9 @@
1
1
  'use client';
2
2
 
3
3
  /**
4
- * CaseStudyCard (pure presentation). Two densities — `default` (vertical
5
- * detail) and `sm` (compact horizontal for chat-inline).
4
+ * CaseStudyCard (pure presentation). Four densities — `default` (vertical
5
+ * detail), `sm` (compact horizontal for chat-inline), `portrait` (rails and
6
+ * strips) and `menu` (the site menu's card, hover-plays the highlight cut).
6
7
  *
7
8
  * The card writes NO click logic — callers wrap with their own anchor
8
9
  * and pass the resolved detail URL via `href`.
@@ -12,9 +13,15 @@
12
13
  * `useOgPlaceholderUrl(...)`) → `bg-ods-bg`.
13
14
  */
14
15
 
16
+ import { useState } from 'react';
15
17
  import Image from '../../../embed-shims/next-image';
16
- import type { CaseStudy } from '../../../types/case-study';
18
+ import Link from '../../../embed-shims/next-link';
19
+ import type { CaseStudyCardData } from '../../../types/case-study';
17
20
  import { cn } from '../../../utils/cn';
21
+ import { sortBitesByFeaturedAtDesc } from '../../features/video-bites-shared';
22
+ import { CardHoverPlay } from '../../features/video-center-badge';
23
+ import { VideoHoverPreviewSurface } from '../../features/video-hover-preview';
24
+ import { EntityIcon } from '../../icon-display';
18
25
  import { Card } from '../../ui/card';
19
26
  import {
20
27
  COMPACT_CARD_IMAGE_SLOT,
@@ -36,7 +43,7 @@ import { useEntityCardLink } from './use-entity-card-link';
36
43
  import { useEntityCardPlaceholder } from './use-entity-card-placeholder';
37
44
 
38
45
  export interface CaseStudyCardProps {
39
- study: CaseStudy;
46
+ study: CaseStudyCardData;
40
47
  /** Detail URL resolved by the caller. */
41
48
  href: string;
42
49
  /** When `_blank`, opens in a new tab. Set by chat dispatch via
@@ -46,7 +53,14 @@ export interface CaseStudyCardProps {
46
53
  targetPlatform?: string | null;
47
54
  /** OG placeholder URL, used when `study.featured_image` is missing. */
48
55
  placeholderUrl?: string | null;
49
- size?: 'default' | 'sm' | 'portrait';
56
+ /** `menu` is the site menu's card: the story's video plays muted on hover or
57
+ * focus (the video bites' hover grammar), then who it is about and who tells it. */
58
+ size?: 'default' | 'sm' | 'portrait' | 'menu';
59
+ /** `menu`: mount the hover player (the host passes whether its panel is open,
60
+ * so a closed menu holds no player). Omitted: the card's own viewport gate. */
61
+ mediaMounted?: boolean;
62
+ /** `menu`: runs on click, before the navigation (the host closes its panel). */
63
+ onNavigate?: () => void;
50
64
  /** Portrait density: render the content-type chip. Mixed rails only; single-type rails pass false. Default true. */
51
65
  showTypeBadge?: boolean;
52
66
  className?: string;
@@ -92,6 +106,22 @@ export function CaseStudyCardSkeleton({ size = 'default' }: { size?: 'default' |
92
106
  );
93
107
  }
94
108
 
109
+ /**
110
+ * A story's HIGHLIGHT cut, the short video a card previews: its highlight
111
+ * video, else the bite an admin featured (the newest when several are; featuring
112
+ * is independent of the bite's own `published` flag, as on every featured surface). Never
113
+ * the full video: that plays on the story's own page. Null when it has neither.
114
+ */
115
+ function caseStudyHighlight(study: CaseStudyCardData): { url: string; posterUrl: string | null } | null {
116
+ if (study.highlight_video_url) {
117
+ return { url: study.highlight_video_url, posterUrl: study.highlight_video_thumbnail ?? null };
118
+ }
119
+ const bite = (study.video_bites ?? [])
120
+ .filter(candidate => candidate.featured === true && candidate.url)
121
+ .sort(sortBitesByFeaturedAtDesc)[0];
122
+ return bite ? { url: bite.url, posterUrl: bite.thumbnail_url ?? null } : null;
123
+ }
124
+
95
125
  export function CaseStudyCard({
96
126
  study,
97
127
  href,
@@ -101,8 +131,11 @@ export function CaseStudyCard({
101
131
  placeholderUrl: placeholderUrlProp,
102
132
  size = 'default',
103
133
  showTypeBadge = true,
134
+ mediaMounted,
135
+ onNavigate,
104
136
  className,
105
137
  }: CaseStudyCardProps) {
138
+ const [hovered, setHovered] = useState(false);
106
139
  const { target, rel } = useEntityCardLink({
107
140
  href,
108
141
  targetPlatform,
@@ -149,6 +182,83 @@ export function CaseStudyCard({
149
182
  );
150
183
  }
151
184
 
185
+ if (size === 'menu') {
186
+ const preview = caseStudyHighlight(study);
187
+ const videoUrl = preview?.url ?? null;
188
+ const hasAnyVideo = !!(videoUrl || study.main_video_url);
189
+ // The cover is the FULL video's poster (wide, like the card); the highlight
190
+ // cut's own frame is usually upright and would not fill a 16:9 slot.
191
+ const poster = study.main_video_thumbnail || study.featured_image || preview?.posterUrl || null;
192
+ return (
193
+ <Link
194
+ href={href}
195
+ target={target}
196
+ rel={rel}
197
+ onClick={onNavigate}
198
+ onPointerEnter={() => setHovered(true)}
199
+ onPointerLeave={() => setHovered(false)}
200
+ onFocus={() => setHovered(true)}
201
+ onBlur={() => setHovered(false)}
202
+ className={cn(
203
+ // `group/card` is what the shared play glyph accents on.
204
+ 'group/card group flex min-w-0 flex-col gap-[var(--spacing-system-sf)] rounded-xl border border-ods-border bg-ods-bg p-[var(--spacing-system-mf)] text-ods-text-primary transition-colors hover:bg-ods-bg-hover focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ods-accent',
205
+ className,
206
+ )}
207
+ >
208
+ {/* The media never takes the click: the whole card is the link. */}
209
+ <span className="pointer-events-none relative block aspect-video w-full shrink-0 overflow-hidden rounded-md bg-ods-bg">
210
+ {videoUrl ? (
211
+ // The shared hover surface, filled the way the walkthrough card fills
212
+ // its own 16:9 slot (`cover`): an upright cut is cropped to the slot,
213
+ // never letterboxed. Sound on hover, muted when the browser blocks it.
214
+ <VideoHoverPreviewSurface
215
+ url={videoUrl}
216
+ posterUrl={poster}
217
+ active={hovered}
218
+ playerMounted={mediaMounted}
219
+ fit="cover"
220
+ posterSizes="360px"
221
+ />
222
+ ) : (
223
+ <>
224
+ {(poster ?? placeholderUrl) && (
225
+ <Image
226
+ src={poster ?? placeholderUrl ?? ''}
227
+ alt=""
228
+ className="object-cover"
229
+ fill
230
+ sizes="360px"
231
+ unoptimized
232
+ onError={hideOnError}
233
+ />
234
+ )}
235
+ {hasAnyVideo && <CardHoverPlay size="md" />}
236
+ </>
237
+ )}
238
+ </span>
239
+ {study.msp?.name && (
240
+ <span className="flex items-center gap-[var(--spacing-system-xsf)] text-ods-text-secondary text-h6">
241
+ {study.msp.icon_url && (
242
+ <EntityIcon
243
+ icon={{ url: study.msp.icon_url }}
244
+ size={20}
245
+ className="size-5 shrink-0 rounded object-contain"
246
+ />
247
+ )}
248
+ <span className="min-w-0 truncate">{study.msp.name}</span>
249
+ </span>
250
+ )}
251
+ <span className="line-clamp-2 text-h6">{study.title}</span>
252
+ {study.user?.full_name && (
253
+ <span className="truncate text-ods-text-muted text-h6">
254
+ {study.user.job_title ? `${study.user.full_name}, ${study.user.job_title}` : study.user.full_name}
255
+ </span>
256
+ )}
257
+ <span className="mt-auto text-ods-accent text-h6">{hasAnyVideo ? 'Watch the story' : 'Read the story'}</span>
258
+ </Link>
259
+ );
260
+ }
261
+
152
262
  if (size === 'portrait') {
153
263
  // Rail/strip density — shared <EntityPortraitCard> shell.
154
264
  return (
@@ -61,7 +61,13 @@ export { remarkCardLinks } from './remark-card-links';
61
61
  export { remarkMentionChips } from './remark-mention-chips';
62
62
 
63
63
  // Card-supporting UI migrated from hub `components/shared/*` + `components/blog/*`
64
- export { NavLinkAnchorViaRuntime, type NavLinkAnchorViaRuntimeProps } from './nav-link-anchor-via-runtime';
64
+ export {
65
+ NavLinkAnchorViaRuntime,
66
+ type NavLinkAnchorViaRuntimeProps,
67
+ type NavLinkViaRuntimeInput,
68
+ type NavLinkViaRuntimeProps,
69
+ useNavLinkViaRuntime,
70
+ } from './nav-link-anchor-via-runtime';
65
71
  export { SourceActionButton, type SourceActionButtonProps, type SourceActionDensity } from './source-action-button';
66
72
  export {
67
73
  ChatAttachmentAddButton,
@@ -18,11 +18,14 @@
18
18
  * the lib should never silently fall back when the runtime is missing
19
19
  * (the chat tree always provides one in both host + embed modes).
20
20
  *
21
- * For surfaces OUTSIDE the chat tree (header chrome, marketing pages, …)
22
- * the hub keeps its own `useNavLink`-based anchor.
21
+ * The same component serves a host's own chrome and pages (site header,
22
+ * footer, call-to-action links): the host mounts the runtime app-wide, names
23
+ * the destination's platform in `targetPlatform`, and may pass its own
24
+ * `onClick` (it runs first; `preventDefault()` cancels the navigation) and
25
+ * plain anchor attributes (`aria-*`, `title`, `data-*`, `style`, `rel`).
23
26
  */
24
27
 
25
- import type { ReactNode, MouseEvent } from 'react';
28
+ import type { AnchorHTMLAttributes, ReactNode, MouseEvent } from 'react';
26
29
  import { useRequiredChatRuntime } from '../../contexts/chat-runtime-context';
27
30
  import { useRouter } from '../../embed-shims/next-navigation';
28
31
  import { useChatPanel } from './chat-panel-context';
@@ -30,7 +33,10 @@ import { resolveHrefForRuntime } from './utils/chat-nav-resolution';
30
33
  import { executeNavigation } from './utils/execute-navigation';
31
34
  import { computeIsNewTab, newTabAnchorAttrs } from './utils/nav-anchor-props';
32
35
 
33
- export interface NavLinkAnchorViaRuntimeProps {
36
+ export interface NavLinkAnchorViaRuntimeProps extends Omit<
37
+ AnchorHTMLAttributes<HTMLAnchorElement>,
38
+ 'href' | 'target' | 'onClick' | 'className' | 'children'
39
+ > {
34
40
  href: string;
35
41
  path?: string | null;
36
42
  targetPlatform?: string | null;
@@ -38,15 +44,41 @@ export interface NavLinkAnchorViaRuntimeProps {
38
44
  /** Optional — matches `NavLinkAnchorComponent`'s contract so the
39
45
  * markdown-anchor slot can render an empty anchor (rare but legal). */
40
46
  children?: ReactNode;
47
+ /** Runs before the navigation; `preventDefault()` cancels it. */
48
+ onClick?: (event: MouseEvent<HTMLAnchorElement>) => void;
41
49
  }
42
50
 
43
- export function NavLinkAnchorViaRuntime({
51
+ export interface NavLinkViaRuntimeInput {
52
+ href: string;
53
+ path?: string | null;
54
+ /** The platform that owns `href`, by name. */
55
+ targetPlatform?: string | null;
56
+ /** Runs before the navigation; `preventDefault()` cancels it. */
57
+ onClick?: (event: MouseEvent<HTMLAnchorElement>) => void;
58
+ }
59
+
60
+ export interface NavLinkViaRuntimeProps {
61
+ href: string;
62
+ target?: '_blank';
63
+ rel?: 'noopener' | 'noopener noreferrer';
64
+ onClick: (event: MouseEvent<HTMLAnchorElement>) => void;
65
+ }
66
+
67
+ /**
68
+ * The anchor props of {@link NavLinkAnchorViaRuntime}, for an element that is
69
+ * not a bare `<a>` (a `Button`'s `linkProps`). ONE decision and ONE click
70
+ * handler for both forms.
71
+ *
72
+ * A new-tab link to a NAMED platform is one of our own sites and keeps the
73
+ * Referer (`noopener` alone: the cross-domain analytics attribute the visit by
74
+ * it); any other new-tab link also gets `noreferrer`.
75
+ */
76
+ export function useNavLinkViaRuntime({
44
77
  href,
45
78
  path,
46
79
  targetPlatform,
47
- className,
48
- children,
49
- }: NavLinkAnchorViaRuntimeProps) {
80
+ onClick: onClickProp,
81
+ }: NavLinkViaRuntimeInput): NavLinkViaRuntimeProps {
50
82
  const runtime = useRequiredChatRuntime();
51
83
  const router = useRouter();
52
84
  const panel = useChatPanel();
@@ -54,6 +86,8 @@ export function NavLinkAnchorViaRuntime({
54
86
  const isNewTab = computeIsNewTab(runtime, resolvedHref, targetPlatform ?? null);
55
87
 
56
88
  const onClick = (e: MouseEvent<HTMLAnchorElement>) => {
89
+ onClickProp?.(e);
90
+ if (e.defaultPrevented) return;
57
91
  const handled = executeNavigation({
58
92
  event: e,
59
93
  runtime,
@@ -64,8 +98,27 @@ export function NavLinkAnchorViaRuntime({
64
98
  });
65
99
  if (handled && !isNewTab && panel?.closeChat) panel.closeChat();
66
100
  };
101
+ const attrs = newTabAnchorAttrs(isNewTab);
102
+ return {
103
+ href: resolvedHref,
104
+ ...attrs,
105
+ ...(isNewTab && targetPlatform && { rel: 'noopener' as const }),
106
+ onClick,
107
+ };
108
+ }
109
+
110
+ export function NavLinkAnchorViaRuntime({
111
+ href,
112
+ path,
113
+ targetPlatform,
114
+ className,
115
+ children,
116
+ onClick,
117
+ ...anchorAttrs
118
+ }: NavLinkAnchorViaRuntimeProps) {
119
+ const linkProps = useNavLinkViaRuntime({ href, path, targetPlatform, onClick });
67
120
  return (
68
- <a href={resolvedHref} {...newTabAnchorAttrs(isNewTab)} onClick={onClick} className={className}>
121
+ <a {...linkProps} {...anchorAttrs} className={className}>
69
122
  {children}
70
123
  </a>
71
124
  );
@@ -30,6 +30,7 @@
30
30
 
31
31
  import { Megaphone, Bell, Info, Star, Package as PackageGlyph } from 'lucide-react';
32
32
  import type { ComponentType, CSSProperties } from 'react';
33
+ import { platformAccentVarName } from '../../../utils/platform-identity';
33
34
  import * as IconsV2 from '../../icons-v2-generated';
34
35
  import { HeadphoneIcon } from '../../icons-v2-generated/audio-and-visual/headphone-icon';
35
36
  import { VideoRecorderIcon } from '../../icons-v2-generated/audio-and-visual/video-recorder-icon';
@@ -52,6 +53,8 @@ import { GraduationCapIcon } from '../../icons-v2-generated/school/graduation-ca
52
53
  import { TicketIcon } from '../../icons-v2-generated/shopping/ticket-icon';
53
54
  import { Rocket02Icon } from '../../icons-v2-generated/vehicles-and-delivery/rocket-02-icon';
54
55
  import { FlamingoLogo } from '../../icons/flamingo-logo';
56
+ import { MiamiCyberGangLogoFaceOnly } from '../../icons/miami-cyber-gang-logo-face-only';
57
+ import { MlgLogo } from '../../icons/mlg-logo';
55
58
  import { OpenFrameLogo } from '../../icons/openframe-logo';
56
59
  import { OpenmspLogo } from '../../icons/openmsp-logo';
57
60
  import { getIconComponent } from './icon-registry';
@@ -71,8 +74,37 @@ function sizedLogo(Logo: ComponentType<{ className?: string; style?: CSSProperti
71
74
  return <Logo className={className} style={{ width: size, height: size }} />;
72
75
  };
73
76
  }
74
- const OpenmspLogoIcon = sizedLogo(OpenmspLogo);
77
+ // A sister platform's mark is drawn in THAT platform's brand colour (the
78
+ // platform brand registry), never in the accent of the page showing it: on
79
+ // flamingo.run the OpenMSP mark stays OpenMSP yellow, not flamingo pink.
80
+ const brandColor = (platform: string) => `var(${platformAccentVarName(platform)})`;
81
+ // The OpenMSP mark's own defaults are black and white (made for a light page):
82
+ // on the dark surfaces every glyph sits on, the bubbles take the theme's text
83
+ // and background tokens and the back bubble OpenMSP's brand colour.
84
+ const OpenmspLogoIcon: IconComponent = ({ size = 16, className }) => (
85
+ <OpenmspLogo
86
+ className={className}
87
+ style={{ width: size, height: size }}
88
+ frontBubbleColor="var(--color-text-primary)"
89
+ innerFrontBubbleColor="var(--color-bg)"
90
+ backBubbleColor={brandColor('openmsp')}
91
+ />
92
+ );
75
93
  const FlamingoLogoIcon = sizedLogo(FlamingoLogo);
94
+ // The two community marks size themselves through a `size` prop, the resolver's own contract.
95
+ // Major League GitHub draws its mark in the theme's text colour on its own site
96
+ // (its header, hero and footer), never in an accent: so does every glyph of it.
97
+ const MlgLogoIcon: IconComponent = ({ size = 16, className, color }) => (
98
+ <MlgLogo size={size} className={className} color={color ?? 'var(--color-text-primary)'} />
99
+ );
100
+ const TmcgLogoIcon: IconComponent = ({ size = 16, className }) => (
101
+ <MiamiCyberGangLogoFaceOnly
102
+ size={size}
103
+ className={className}
104
+ originalPink={brandColor('tmcg')}
105
+ originalWhite="var(--color-text-primary)"
106
+ />
107
+ );
76
108
 
77
109
  /**
78
110
  * `OpenFrameLogo` from `components/icons/` predates the v2 set and:
@@ -138,6 +170,8 @@ export const ICON_ALIASES: Record<string, IconComponent> = {
138
170
  // (was the separate `renderSvgIcon` map; now ONE resolver everywhere).
139
171
  'openframe-logo': LogoOpenframeIcon,
140
172
  'openmsp-logo': OpenmspLogoIcon,
173
+ 'mlg-logo': MlgLogoIcon,
174
+ 'tmcg-logo': TmcgLogoIcon,
141
175
  flamingo: FlamingoLogoIcon,
142
176
  'flamingo-logo': FlamingoLogoIcon,
143
177
  megaphone: Megaphone,
@@ -33,10 +33,8 @@ export * from './faq-accordion';
33
33
  export * from './faq';
34
34
  export * from './related-content';
35
35
  export * from './filter-chip';
36
- export * from './footer';
37
36
  export * from './unified-filter-logic';
38
37
  export * from './unified-pagination';
39
- export * from './footer-waitlist-button';
40
38
  export * from './hero-image-uploader';
41
39
  export * from './icons-block';
42
40
  export * from './image-cropper';
@@ -33,7 +33,7 @@ export function AnnouncementBarSkeleton() {
33
33
  }
34
34
 
35
35
  /**
36
- * Header skeleton that matches the ClientOnlyHeader placeholder but with proper animations
36
+ * Header skeleton for a page that is still loading: logo, navigation and action placeholders
37
37
  */
38
38
  export function HeaderSkeleton() {
39
39
  return (
@@ -1,20 +1,22 @@
1
1
  'use client';
2
2
 
3
3
  // Navigation component exports
4
- export { Header } from './header';
5
- export type { HeaderConfig, HeaderProps } from './header';
6
-
7
- export { MingoAiButton } from './mingo-ai-button';
4
+ // Site navigation: one data model (`SiteNav`), rendered by the header, its
5
+ // menu panels, the mobile sheet and the footer.
6
+ export { SiteHeader } from './site-header';
7
+ export type { SiteHeaderProps } from './site-header';
8
+ export { NavPanel, isMegaMenu } from './nav-panel';
9
+ export type { NavPanelProps } from './nav-panel';
10
+ export { NavItemRow } from './nav-item-row';
11
+ export type { NavItemRowProps, NavItemRowVariant } from './nav-item-row';
12
+ export { ASK_AI_OPEN_EVENT, MingoAiButton, openAskAi } from './mingo-ai-button';
8
13
  export type { MingoAiButtonProps } from './mingo-ai-button';
9
-
10
- export { ClientOnlyHeader } from './client-only-header';
11
- export type { ClientOnlyHeaderProps } from './client-only-header';
12
-
13
- export { HeaderSkeleton } from './header-skeleton';
14
- export type { HeaderSkeletonProps } from './header-skeleton';
15
-
16
- export { MobileNavPanel, MOBILE_NAV_PANEL_ID } from './mobile-nav-panel';
17
- export type { MobileNavPanelProps } from './mobile-nav-panel';
14
+ export { MobileNavSheet, MOBILE_NAV_SHEET_ID, siteNavHasMobileMenus } from './mobile-nav-sheet';
15
+ export type { MobileNavSheetProps } from './mobile-nav-sheet';
16
+ export { SiteFooter } from './site-footer';
17
+ export type { SiteFooterProps } from './site-footer';
18
+ export { defaultRenderSiteNavLink, navLinkLabel } from './site-nav-link';
19
+ export type { SiteNavLinkRenderer } from './site-nav-link';
18
20
 
19
21
  export { SlidingSidebar } from './sliding-sidebar';
20
22
  export type { SlidingSidebarProps } from './sliding-sidebar';
@@ -81,10 +83,15 @@ export type { NavigationNode } from './multi-level-navigation';
81
83
 
82
84
  // Re-export types from navigation types
83
85
  export type {
84
- MobileNavConfig,
86
+ NavFeature,
87
+ NavGroup,
88
+ NavLink,
89
+ NavMenu,
85
90
  NavigationItem,
86
91
  NavigationSidebarConfig,
87
92
  NavigationSidebarItem,
93
+ SiteNav,
94
+ SiteNavPrimaryCta,
88
95
  SlidingSidebarConfig,
89
96
  UnifiedSidebarUser,
90
97
  } from '../../types/navigation';