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

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 (208) hide show
  1. package/dist/chat-protocol/index.cjs +23 -14
  2. package/dist/chat-protocol/index.js +23 -14
  3. package/dist/chat-protocol/nats-decoder.d.ts.map +1 -1
  4. package/dist/chat-protocol/source-metadata.d.ts +11 -2
  5. package/dist/chat-protocol/source-metadata.d.ts.map +1 -1
  6. package/dist/{chunk-QFXRPRFT.js → chunk-2YHNQAMU.js} +3 -3
  7. package/dist/{chunk-YHM5AWTT.js → chunk-3FZP73R4.js} +6 -6
  8. package/dist/{chunk-VR2ZLFCP.js → chunk-4IBPRIQH.js} +1 -1
  9. package/dist/{chunk-AQWZNP6A.js → chunk-4RVC6TCE.js} +4 -4
  10. package/dist/{chunk-OUY3HHW6.cjs → chunk-4UMOD5NU.cjs} +928 -539
  11. package/dist/{chunk-IKTQ3ZAG.cjs → chunk-54L6NMKQ.cjs} +6 -6
  12. package/dist/{chunk-A75QY6CD.cjs → chunk-56LOCS37.cjs} +21 -21
  13. package/dist/{chunk-WA6GUYQ4.cjs → chunk-5CMYEGHE.cjs} +104 -104
  14. package/dist/{chunk-VA5T32XA.js → chunk-632NGOIH.js} +2 -2
  15. package/dist/{chunk-MMOODW57.cjs → chunk-6NTPY5QA.cjs} +1887 -94
  16. package/dist/{chunk-VESKITRQ.cjs → chunk-7XVBJDH7.cjs} +728 -732
  17. package/dist/{chunk-3MX5S4CF.cjs → chunk-AGNG62O2.cjs} +905 -2564
  18. package/dist/{chunk-ZSAO2HBW.js → chunk-BETJB7UV.js} +15 -15
  19. package/dist/{chunk-MP2C3LJ4.js → chunk-CUME3VTQ.js} +1 -1
  20. package/dist/{chunk-PDWVPI3S.js → chunk-CZ7Y7UG4.js} +4 -72
  21. package/dist/{chunk-F6I6XZMN.js → chunk-E2TQZIOJ.js} +5 -5
  22. package/dist/{chunk-X5MQ7JRP.js → chunk-ECXW3RIH.js} +9 -9
  23. package/dist/{chunk-JEZKGCZI.cjs → chunk-EUSDLX5W.cjs} +26 -26
  24. package/dist/{chunk-6NJKKO73.cjs → chunk-F4E3L3KV.cjs} +15 -15
  25. package/dist/{chunk-MFFCUVWG.js → chunk-F57ECE2I.js} +1 -1
  26. package/dist/{chunk-F6AFPNDF.js → chunk-FBY4V3YS.js} +15 -15
  27. package/dist/{chunk-CH5232HV.js → chunk-FI6GMDO7.js} +2455 -662
  28. package/dist/{chunk-NDESEPW5.js → chunk-FXJ3WWUB.js} +68 -0
  29. package/dist/{chunk-FCGC42TS.js → chunk-HTBN3XYF.js} +57 -61
  30. package/dist/{chunk-ZSAGLQOD.cjs → chunk-I43ZVJRL.cjs} +17 -17
  31. package/dist/{chunk-7VVFKJY5.js → chunk-IBQZMRWO.js} +1796 -3455
  32. package/dist/{chunk-4KX6OED2.cjs → chunk-JFSLQ2XS.cjs} +46 -46
  33. package/dist/{chunk-5HBHO5TB.js → chunk-JPG7OHEG.js} +1 -1
  34. package/dist/{chunk-MKXO4Q5P.js → chunk-KUC673HM.js} +383 -142
  35. package/dist/{chunk-4I45Y3DN.cjs → chunk-L3Q7R7CQ.cjs} +6 -6
  36. package/dist/{chunk-X72J4ACL.cjs → chunk-LLSUVPQC.cjs} +252 -8
  37. package/dist/{chunk-RKV5B7EU.cjs → chunk-LM7TXMLU.cjs} +72 -4
  38. package/dist/{chunk-PN2RKPDF.js → chunk-LSQPH73L.js} +10 -10
  39. package/dist/{chunk-HPWWNJE7.cjs → chunk-MY3JYZQY.cjs} +89 -89
  40. package/dist/{chunk-2YXVWZVF.js → chunk-N45D4ZXQ.js} +959 -715
  41. package/dist/{chunk-Z2VNQQ74.js → chunk-NCPTID44.js} +978 -942
  42. package/dist/{chunk-KOWVJD4V.cjs → chunk-O6WNIA6W.cjs} +56 -56
  43. package/dist/{chunk-ABU3TGXA.cjs → chunk-OF6GEI5A.cjs} +3 -3
  44. package/dist/{chunk-OHFS7VOU.cjs → chunk-PLYEKV3N.cjs} +2 -2
  45. package/dist/{chunk-VHCACDKP.cjs → chunk-RQRX4YC3.cjs} +39 -39
  46. package/dist/{chunk-DW7G4KG3.js → chunk-RZNRVXHH.js} +3 -3
  47. package/dist/{chunk-G5A3PSPV.js → chunk-SGJP2WKD.js} +1080 -691
  48. package/dist/{chunk-7LS22BZU.cjs → chunk-STXYW25T.cjs} +14 -14
  49. package/dist/{chunk-YFBM2K6X.cjs → chunk-TB5EDWRC.cjs} +4 -4
  50. package/dist/{chunk-RBDP4WDE.js → chunk-U6BWEKJC.js} +6 -6
  51. package/dist/{chunk-XOYH2YQE.cjs → chunk-UDFRUKLC.cjs} +2 -2
  52. package/dist/{chunk-TYWLNCNH.cjs → chunk-UVTD52MD.cjs} +14 -82
  53. package/dist/{chunk-N3COXDBH.cjs → chunk-VX4E6YSB.cjs} +41 -5
  54. package/dist/{chunk-2QEUFXNS.cjs → chunk-WCVZCGK5.cjs} +312 -71
  55. package/dist/{chunk-6XIF2Z7I.js → chunk-WMX26HB5.js} +3 -3
  56. package/dist/{chunk-IHERL5V4.cjs → chunk-WXAT5C4C.cjs} +34 -34
  57. package/dist/{chunk-D7BTSVMS.cjs → chunk-Y2GSOUCS.cjs} +2 -2
  58. package/dist/{chunk-6SEANMOU.js → chunk-Y5VSOYPS.js} +11 -11
  59. package/dist/{chunk-DENB2XJC.js → chunk-YQWTGQG5.js} +2 -2
  60. package/dist/components/case-studies/index.cjs +30 -32
  61. package/dist/components/case-studies/index.js +14 -16
  62. package/dist/components/chat/entity-cards/case-study-card.d.ts +11 -4
  63. package/dist/components/chat/entity-cards/case-study-card.d.ts.map +1 -1
  64. package/dist/components/chat/index.cjs +9 -11
  65. package/dist/components/chat/index.js +14 -16
  66. package/dist/components/chat/types/message.types.d.ts +20 -16
  67. package/dist/components/chat/types/message.types.d.ts.map +1 -1
  68. package/dist/components/chat/types/unified-chat-state.types.d.ts +1 -1
  69. package/dist/components/chat/utils/icon-library.d.ts.map +1 -1
  70. package/dist/components/chat/utils/process-historical-messages.d.ts.map +1 -1
  71. package/dist/components/contact/index.cjs +12 -14
  72. package/dist/components/contact/index.js +11 -13
  73. package/dist/components/docs/index.cjs +15 -17
  74. package/dist/components/docs/index.js +14 -16
  75. package/dist/components/embeds/index.cjs +12 -14
  76. package/dist/components/embeds/index.js +11 -13
  77. package/dist/components/faq/index.cjs +13 -15
  78. package/dist/components/faq/index.js +12 -14
  79. package/dist/components/features/index.cjs +12 -14
  80. package/dist/components/features/index.js +21 -23
  81. package/dist/components/help-center-pages/index.cjs +118 -120
  82. package/dist/components/help-center-pages/index.js +33 -35
  83. package/dist/components/icons/index.cjs +3 -5
  84. package/dist/components/icons/index.js +9 -11
  85. package/dist/components/icons-v2-generated/index.cjs +3 -5
  86. package/dist/components/icons-v2-generated/index.js +2 -4
  87. package/dist/components/index.cjs +86 -159
  88. package/dist/components/index.d.ts +0 -2
  89. package/dist/components/index.d.ts.map +1 -1
  90. package/dist/components/index.js +329 -402
  91. package/dist/components/loading/page-layout-skeleton.d.ts +1 -1
  92. package/dist/components/meeting-scheduler/index.cjs +62 -64
  93. package/dist/components/meeting-scheduler/index.js +12 -14
  94. package/dist/components/navigation/index.cjs +28 -16
  95. package/dist/components/navigation/index.d.ts +14 -10
  96. package/dist/components/navigation/index.d.ts.map +1 -1
  97. package/dist/components/navigation/index.js +38 -26
  98. package/dist/components/navigation/mingo-ai-button.d.ts +38 -3
  99. package/dist/components/navigation/mingo-ai-button.d.ts.map +1 -1
  100. package/dist/components/navigation/mobile-nav-sheet-id.d.ts +3 -0
  101. package/dist/components/navigation/mobile-nav-sheet-id.d.ts.map +1 -0
  102. package/dist/components/navigation/mobile-nav-sheet.d.ts +48 -0
  103. package/dist/components/navigation/mobile-nav-sheet.d.ts.map +1 -0
  104. package/dist/components/navigation/nav-item-row.d.ts +27 -0
  105. package/dist/components/navigation/nav-item-row.d.ts.map +1 -0
  106. package/dist/components/navigation/nav-panel.d.ts +43 -0
  107. package/dist/components/navigation/nav-panel.d.ts.map +1 -0
  108. package/dist/components/navigation/navigation-sidebar-item.d.ts +1 -2
  109. package/dist/components/navigation/navigation-sidebar-item.d.ts.map +1 -1
  110. package/dist/components/navigation/site-footer.d.ts +28 -0
  111. package/dist/components/navigation/site-footer.d.ts.map +1 -0
  112. package/dist/components/navigation/site-header.d.ts +61 -0
  113. package/dist/components/navigation/site-header.d.ts.map +1 -0
  114. package/dist/components/navigation/site-nav-link.d.ts +26 -0
  115. package/dist/components/navigation/site-nav-link.d.ts.map +1 -0
  116. package/dist/components/navigation/top-navigation.d.ts +2 -2
  117. package/dist/components/navigation/top-navigation.d.ts.map +1 -1
  118. package/dist/components/onboarding-guides/index.cjs +15 -17
  119. package/dist/components/onboarding-guides/index.js +14 -16
  120. package/dist/components/related-content/index.cjs +10 -8
  121. package/dist/components/related-content/index.js +9 -7
  122. package/dist/components/tickets/index.cjs +16 -18
  123. package/dist/components/tickets/index.js +15 -17
  124. package/dist/components/ui/index.cjs +12 -14
  125. package/dist/components/ui/index.js +17 -19
  126. package/dist/hooks/index.cjs +19 -11
  127. package/dist/hooks/index.d.ts +1 -0
  128. package/dist/hooks/index.d.ts.map +1 -1
  129. package/dist/hooks/index.js +19 -11
  130. package/dist/hooks/ui/index.d.ts +3 -0
  131. package/dist/hooks/ui/index.d.ts.map +1 -1
  132. package/dist/hooks/ui/use-active-section.d.ts +10 -0
  133. package/dist/hooks/ui/use-active-section.d.ts.map +1 -0
  134. package/dist/hooks/ui/use-nav-menus.d.ts +48 -0
  135. package/dist/hooks/ui/use-nav-menus.d.ts.map +1 -0
  136. package/dist/hooks/ui/use-visitor-os.d.ts +15 -0
  137. package/dist/hooks/ui/use-visitor-os.d.ts.map +1 -0
  138. package/dist/hooks/use-location-hash.d.ts +9 -0
  139. package/dist/hooks/use-location-hash.d.ts.map +1 -0
  140. package/dist/index.cjs +55 -22
  141. package/dist/index.js +229 -196
  142. package/dist/types/case-study.d.ts +9 -0
  143. package/dist/types/case-study.d.ts.map +1 -1
  144. package/dist/types/navigation.d.ts +95 -65
  145. package/dist/types/navigation.d.ts.map +1 -1
  146. package/dist/utils/index.cjs +55 -0
  147. package/dist/utils/index.d.ts +1 -0
  148. package/dist/utils/index.d.ts.map +1 -1
  149. package/dist/utils/index.js +50 -1
  150. package/dist/utils/source-icons.d.ts.map +1 -1
  151. package/dist/utils/visitor-os.d.ts +45 -0
  152. package/dist/utils/visitor-os.d.ts.map +1 -0
  153. package/package.json +1 -1
  154. package/src/chat-protocol/nats-decoder.ts +10 -8
  155. package/src/chat-protocol/source-metadata.ts +25 -2
  156. package/src/components/chat/entity-cards/case-study-card.tsx +115 -5
  157. package/src/components/chat/types/message.types.ts +21 -15
  158. package/src/components/chat/types/unified-chat-state.types.ts +1 -1
  159. package/src/components/chat/utils/icon-library.tsx +35 -1
  160. package/src/components/chat/utils/process-historical-messages.ts +12 -4
  161. package/src/components/index.ts +0 -2
  162. package/src/components/loading/page-layout-skeleton.tsx +1 -1
  163. package/src/components/navigation/index.ts +21 -14
  164. package/src/components/navigation/mingo-ai-button.tsx +139 -63
  165. package/src/components/navigation/mobile-nav-sheet-id.ts +2 -0
  166. package/src/components/navigation/mobile-nav-sheet.tsx +241 -0
  167. package/src/components/navigation/nav-item-row.tsx +129 -0
  168. package/src/components/navigation/nav-panel.tsx +196 -0
  169. package/src/components/navigation/navigation-sidebar-item.tsx +1 -2
  170. package/src/components/navigation/site-footer.tsx +256 -0
  171. package/src/components/navigation/site-header.tsx +339 -0
  172. package/src/components/navigation/site-nav-link.tsx +41 -0
  173. package/src/components/navigation/ticket-alerts-button.tsx +2 -2
  174. package/src/components/navigation/top-navigation.tsx +2 -2
  175. package/src/hooks/index.ts +1 -0
  176. package/src/hooks/ui/index.ts +3 -0
  177. package/src/hooks/ui/use-active-section.ts +44 -0
  178. package/src/hooks/ui/use-nav-menus.ts +200 -0
  179. package/src/hooks/ui/use-visitor-os.ts +33 -0
  180. package/src/hooks/use-location-hash.ts +34 -0
  181. package/src/styles/chat-animations.css +57 -2
  182. package/src/types/case-study.ts +16 -0
  183. package/src/types/navigation.ts +80 -66
  184. package/src/utils/index.ts +1 -0
  185. package/src/utils/source-icons.ts +13 -0
  186. package/src/utils/visitor-os.ts +84 -0
  187. package/dist/chunk-LMBC5FFP.js +0 -43
  188. package/dist/chunk-O43V3EOS.js +0 -252
  189. package/dist/chunk-QZVSJF2K.cjs +0 -252
  190. package/dist/chunk-RALRPFDW.cjs +0 -43
  191. package/dist/components/footer-waitlist-button.d.ts +0 -29
  192. package/dist/components/footer-waitlist-button.d.ts.map +0 -1
  193. package/dist/components/footer.d.ts +0 -47
  194. package/dist/components/footer.d.ts.map +0 -1
  195. package/dist/components/navigation/client-only-header.d.ts +0 -8
  196. package/dist/components/navigation/client-only-header.d.ts.map +0 -1
  197. package/dist/components/navigation/header-skeleton.d.ts +0 -12
  198. package/dist/components/navigation/header-skeleton.d.ts.map +0 -1
  199. package/dist/components/navigation/header.d.ts +0 -9
  200. package/dist/components/navigation/header.d.ts.map +0 -1
  201. package/dist/components/navigation/mobile-nav-panel.d.ts +0 -10
  202. package/dist/components/navigation/mobile-nav-panel.d.ts.map +0 -1
  203. package/src/components/footer-waitlist-button.tsx +0 -53
  204. package/src/components/footer.tsx +0 -195
  205. package/src/components/navigation/client-only-header.tsx +0 -22
  206. package/src/components/navigation/header-skeleton.tsx +0 -74
  207. package/src/components/navigation/header.tsx +0 -429
  208. 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 (
@@ -12,15 +12,13 @@ export const MESSAGE_TYPE = {
12
12
  TEXT: 'TEXT',
13
13
  THINKING: 'THINKING',
14
14
  ASK: 'ASK',
15
- /** Per-answer source metadata (documents, videos, entity cards).
15
+ /** Per-answer attachments (cited documents, videos, entity cards).
16
16
  *
17
- * TWO names for ONE payload, on purpose. `SOURCES` is the contract the
18
- * backend is moving to; `GUIDE` is the envelope it ships in today, reused
19
- * from Guide Mode v2 so a released frontend keeps working during the v2→v3
20
- * rollout. Both decode through the same path, so dropping `GUIDE` later is a
21
- * deletion here and nothing else. */
17
+ * `ATTACHMENTS` carries `sources` / `videos` / `cards` on the record itself.
18
+ * `GUIDE` is the envelope it replaced — the same arrays under `payload` —
19
+ * and stays only until every backend ships `ATTACHMENTS`. */
20
+ ATTACHMENTS: 'ATTACHMENTS',
22
21
  GUIDE: 'GUIDE',
23
- SOURCES: 'SOURCES',
24
22
  EXECUTING_TOOL: 'EXECUTING_TOOL',
25
23
  EXECUTED_TOOL: 'EXECUTED_TOOL',
26
24
  APPROVAL_REQUEST: 'APPROVAL_REQUEST',
@@ -412,7 +410,7 @@ export interface ThinkingMessageData extends MessageDataBase {
412
410
  *
413
411
  * Lives here, beside `Message`, because BOTH transports produce it now: the SSE
414
412
  * adapter reads it off the per-turn metadata frame, and the NATS path decodes it
415
- * out of a `GUIDE`/`SOURCES` chunk. It used to be declared inside
413
+ * out of an `ATTACHMENTS` chunk. It used to be declared inside
416
414
  * `use-sse-chat-adapter`, which made "a Guide answer's citations" structurally a
417
415
  * property of one transport — `use-sse-chat-adapter` still re-exports the name
418
416
  * so existing imports keep working.
@@ -451,13 +449,20 @@ export interface ChatSource {
451
449
  label?: string;
452
450
  }
453
451
 
454
- /** Persisted source-metadata row. `payload` is the SAME object the live chunk
455
- * carries, so history and realtime share one decoder (`sourceMetadataEvent`)
456
- * and cannot drift apart. Left as an open record here rather than typed
457
- * structurally: it is unvalidated wire data, and the decoder is what turns it
458
- * into `ChatSource[]` / `ChatRef[]`. */
452
+ /** Persisted attachments row (GraphQL `AttachmentsData`). The arrays are the
453
+ * SAME ones the live chunk carries, so history and realtime share one decoder
454
+ * (`attachmentsEvent`) and cannot drift apart. Left untyped: it is unvalidated
455
+ * wire data, and the decoder is what turns it into `ChatSource[]` / `ChatRef[]`. */
456
+ export interface AttachmentsMessageData extends MessageDataBase {
457
+ type: 'ATTACHMENTS';
458
+ sources?: unknown;
459
+ videos?: unknown;
460
+ cards?: unknown;
461
+ }
462
+
463
+ /** The envelope `ATTACHMENTS` replaced: the same arrays under `payload`. */
459
464
  export interface SourceMetadataMessageData extends MessageDataBase {
460
- type: 'GUIDE' | 'SOURCES';
465
+ type: 'GUIDE';
461
466
  payload?: unknown;
462
467
  }
463
468
 
@@ -603,6 +608,7 @@ export type MessageData =
603
608
  | TextMessageData
604
609
  | ThinkingMessageData
605
610
  | AskMessageData
611
+ | AttachmentsMessageData
606
612
  | SourceMetadataMessageData
607
613
  | ExecutingToolMessageData
608
614
  | ExecutedToolMessageData
@@ -654,7 +660,7 @@ export interface ProcessedMessage {
654
660
  * Absent when the source row(s) carried no seq. */
655
661
  streamSeq?: number;
656
662
  /** Documents this answer cited — see `Message.sources`, the same field.
657
- * Replayed from the turn's persisted `GUIDE`/`SOURCES` row, so a reloaded
663
+ * Replayed from the turn's persisted `ATTACHMENTS` row, so a reloaded
658
664
  * answer carries the citations the live one did. */
659
665
  sources?: ChatSource[];
660
666
  /** Entity references this answer's metadata described — see `Message.refs`. */
@@ -239,7 +239,7 @@ export interface UnifiedChatMessage {
239
239
  /**
240
240
  * Documents this answer cited, rendered as chips beneath it. Produced by
241
241
  * BOTH transports now — SSE reads them off the per-turn metadata frame, NATS
242
- * decodes them out of a `GUIDE`/`SOURCES` chunk.
242
+ * decodes them out of an `ATTACHMENTS` chunk.
243
243
  */
244
244
  sources?: ChatSource[];
245
245
  /**
@@ -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,
@@ -34,7 +34,12 @@ import {
34
34
  // One normalizer for ask rows, shared with the live decoder — history and the
35
35
  // stream must agree on which options are usable.
36
36
  import { normalizeAskOptions } from '../../../chat-protocol/nats-decoder';
37
- import { mergeSourceMetadata, sourceMetadataEvent, type SourceMetadata } from '../../../chat-protocol/source-metadata';
37
+ import {
38
+ attachmentsEvent,
39
+ mergeSourceMetadata,
40
+ sourceMetadataEvent,
41
+ type SourceMetadata,
42
+ } from '../../../chat-protocol/source-metadata';
38
43
  import { applyApprovalStatusToSegment } from '../stream/message-mutations';
39
44
  import {
40
45
  MESSAGE_TYPE,
@@ -122,11 +127,14 @@ export function decodeHistoricalMessageData(data: MessageData): ChatStreamEvent
122
127
  }
123
128
  return null;
124
129
 
125
- // Persisted source metadata, through the SAME decoder the live chunk uses
126
- // (`sourceMetadataEvent`). That shared parse is the whole reason a reloaded
130
+ // Persisted attachments, through the SAME decoder the live chunk uses
131
+ // (`attachmentsEvent`). That shared parse is the whole reason a reloaded
127
132
  // answer renders identically to the one the reader watched arrive.
133
+ case MESSAGE_TYPE.ATTACHMENTS:
134
+ return attachmentsEvent(data);
135
+
136
+ // The envelope `ATTACHMENTS` replaced (see `MESSAGE_TYPE.GUIDE`).
128
137
  case MESSAGE_TYPE.GUIDE:
129
- case MESSAGE_TYPE.SOURCES:
130
138
  return 'payload' in data ? sourceMetadataEvent(data.payload) : null;
131
139
 
132
140
  // Same completeness gate as the live decoder (`decodeNatsChunk`): a
@@ -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';