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

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 (194) hide show
  1. package/dist/{chunk-WCVZCGK5.cjs → chunk-2QEUFXNS.cjs} +71 -312
  2. package/dist/{chunk-N45D4ZXQ.js → chunk-2YXVWZVF.js} +715 -959
  3. package/dist/{chunk-7OFPU7WE.cjs → chunk-3MX5S4CF.cjs} +2547 -877
  4. package/dist/{chunk-MB4PNY7C.cjs → chunk-4I45Y3DN.cjs} +6 -6
  5. package/dist/{chunk-K6O43WDL.cjs → chunk-4KX6OED2.cjs} +46 -46
  6. package/dist/{chunk-JPG7OHEG.js → chunk-5HBHO5TB.js} +1 -1
  7. package/dist/{chunk-3PLDQDE6.cjs → chunk-6NJKKO73.cjs} +15 -15
  8. package/dist/{chunk-O2RSAA5M.js → chunk-6SEANMOU.js} +11 -11
  9. package/dist/{chunk-WMX26HB5.js → chunk-6XIF2Z7I.js} +3 -3
  10. package/dist/{chunk-TQ4LB2DS.cjs → chunk-7LS22BZU.cjs} +14 -14
  11. package/dist/{chunk-7GEBFJT2.js → chunk-7VVFKJY5.js} +3429 -1759
  12. package/dist/{chunk-DDVBX42E.cjs → chunk-A75QY6CD.cjs} +21 -21
  13. package/dist/{chunk-OF6GEI5A.cjs → chunk-ABU3TGXA.cjs} +3 -3
  14. package/dist/{chunk-4CJMQLB5.js → chunk-AQWZNP6A.js} +4 -4
  15. package/dist/{chunk-FI6GMDO7.js → chunk-CH5232HV.js} +662 -2455
  16. package/dist/{chunk-Y2GSOUCS.cjs → chunk-D7BTSVMS.cjs} +2 -2
  17. package/dist/{chunk-4ILM7W2R.js → chunk-DENB2XJC.js} +2 -2
  18. package/dist/{chunk-6232S532.js → chunk-DW7G4KG3.js} +3 -3
  19. package/dist/{chunk-3NCM4ZQB.js → chunk-F6AFPNDF.js} +15 -15
  20. package/dist/{chunk-PXOQIA5K.js → chunk-F6I6XZMN.js} +5 -5
  21. package/dist/{chunk-G6UMNAVI.js → chunk-FCGC42TS.js} +61 -57
  22. package/dist/{chunk-BHI6GVC4.js → chunk-G5A3PSPV.js} +691 -1080
  23. package/dist/{chunk-HZ7CBXPL.cjs → chunk-HPWWNJE7.cjs} +89 -89
  24. package/dist/{chunk-SW5ZV2BN.cjs → chunk-IHERL5V4.cjs} +34 -34
  25. package/dist/{chunk-UJ33ZXDT.cjs → chunk-IKTQ3ZAG.cjs} +6 -6
  26. package/dist/{chunk-SEKYEWQG.cjs → chunk-JEZKGCZI.cjs} +26 -26
  27. package/dist/{chunk-7SCNMP2B.cjs → chunk-KOWVJD4V.cjs} +56 -56
  28. package/dist/chunk-LMBC5FFP.js +43 -0
  29. package/dist/{chunk-F57ECE2I.js → chunk-MFFCUVWG.js} +1 -1
  30. package/dist/{chunk-KUC673HM.js → chunk-MKXO4Q5P.js} +142 -383
  31. package/dist/{chunk-6NTPY5QA.cjs → chunk-MMOODW57.cjs} +94 -1887
  32. package/dist/{chunk-CUME3VTQ.js → chunk-MP2C3LJ4.js} +1 -1
  33. package/dist/{chunk-VX4E6YSB.cjs → chunk-N3COXDBH.cjs} +5 -41
  34. package/dist/{chunk-FXJ3WWUB.js → chunk-NDESEPW5.js} +0 -68
  35. package/dist/chunk-O43V3EOS.js +252 -0
  36. package/dist/{chunk-PLYEKV3N.cjs → chunk-OHFS7VOU.cjs} +2 -2
  37. package/dist/{chunk-M75XEEGT.cjs → chunk-OUY3HHW6.cjs} +539 -928
  38. package/dist/{chunk-CZ7Y7UG4.js → chunk-PDWVPI3S.js} +72 -4
  39. package/dist/{chunk-TE773HEH.js → chunk-PN2RKPDF.js} +10 -10
  40. package/dist/{chunk-3EZ4HHAP.js → chunk-QFXRPRFT.js} +3 -3
  41. package/dist/chunk-QZVSJF2K.cjs +252 -0
  42. package/dist/chunk-RALRPFDW.cjs +43 -0
  43. package/dist/{chunk-YGJKKDDM.js → chunk-RBDP4WDE.js} +6 -6
  44. package/dist/{chunk-LM7TXMLU.cjs → chunk-RKV5B7EU.cjs} +4 -72
  45. package/dist/{chunk-UVTD52MD.cjs → chunk-TYWLNCNH.cjs} +82 -14
  46. package/dist/{chunk-IKJA3Y7Z.js → chunk-VA5T32XA.js} +2 -2
  47. package/dist/{chunk-GWQEYPYM.cjs → chunk-VESKITRQ.cjs} +732 -728
  48. package/dist/{chunk-RQRX4YC3.cjs → chunk-VHCACDKP.cjs} +39 -39
  49. package/dist/{chunk-4IBPRIQH.js → chunk-VR2ZLFCP.js} +1 -1
  50. package/dist/{chunk-RTIPR53L.cjs → chunk-WA6GUYQ4.cjs} +104 -104
  51. package/dist/{chunk-T5TMCHWM.js → chunk-X5MQ7JRP.js} +9 -9
  52. package/dist/{chunk-LLSUVPQC.cjs → chunk-X72J4ACL.cjs} +8 -252
  53. package/dist/{chunk-UDFRUKLC.cjs → chunk-XOYH2YQE.cjs} +2 -2
  54. package/dist/{chunk-LVSFKENJ.cjs → chunk-YFBM2K6X.cjs} +4 -4
  55. package/dist/{chunk-DWPAAWMR.js → chunk-YHM5AWTT.js} +6 -6
  56. package/dist/{chunk-NCPTID44.js → chunk-Z2VNQQ74.js} +942 -978
  57. package/dist/{chunk-STHZBOQI.cjs → chunk-ZSAGLQOD.cjs} +17 -17
  58. package/dist/{chunk-VZICIQW2.js → chunk-ZSAO2HBW.js} +15 -15
  59. package/dist/components/case-studies/index.cjs +32 -30
  60. package/dist/components/case-studies/index.js +16 -14
  61. package/dist/components/chat/entity-cards/case-study-card.d.ts +4 -11
  62. package/dist/components/chat/entity-cards/case-study-card.d.ts.map +1 -1
  63. package/dist/components/chat/index.cjs +11 -9
  64. package/dist/components/chat/index.js +16 -14
  65. package/dist/components/chat/utils/icon-library.d.ts.map +1 -1
  66. package/dist/components/contact/index.cjs +14 -12
  67. package/dist/components/contact/index.js +13 -11
  68. package/dist/components/docs/index.cjs +17 -15
  69. package/dist/components/docs/index.js +16 -14
  70. package/dist/components/embeds/index.cjs +14 -12
  71. package/dist/components/embeds/index.js +13 -11
  72. package/dist/components/faq/index.cjs +15 -13
  73. package/dist/components/faq/index.js +14 -12
  74. package/dist/components/features/index.cjs +14 -12
  75. package/dist/components/features/index.js +23 -21
  76. package/dist/components/footer-waitlist-button.d.ts +29 -0
  77. package/dist/components/footer-waitlist-button.d.ts.map +1 -0
  78. package/dist/components/footer.d.ts +47 -0
  79. package/dist/components/footer.d.ts.map +1 -0
  80. package/dist/components/help-center-pages/index.cjs +120 -118
  81. package/dist/components/help-center-pages/index.js +35 -33
  82. package/dist/components/icons/index.cjs +5 -3
  83. package/dist/components/icons/index.js +11 -9
  84. package/dist/components/icons-v2-generated/index.cjs +5 -3
  85. package/dist/components/icons-v2-generated/index.js +4 -2
  86. package/dist/components/index.cjs +159 -86
  87. package/dist/components/index.d.ts +2 -0
  88. package/dist/components/index.d.ts.map +1 -1
  89. package/dist/components/index.js +402 -329
  90. package/dist/components/loading/page-layout-skeleton.d.ts +1 -1
  91. package/dist/components/meeting-scheduler/index.cjs +64 -62
  92. package/dist/components/meeting-scheduler/index.js +14 -12
  93. package/dist/components/navigation/client-only-header.d.ts +8 -0
  94. package/dist/components/navigation/client-only-header.d.ts.map +1 -0
  95. package/dist/components/navigation/header-skeleton.d.ts +12 -0
  96. package/dist/components/navigation/header-skeleton.d.ts.map +1 -0
  97. package/dist/components/navigation/header.d.ts +9 -0
  98. package/dist/components/navigation/header.d.ts.map +1 -0
  99. package/dist/components/navigation/index.cjs +16 -28
  100. package/dist/components/navigation/index.d.ts +10 -14
  101. package/dist/components/navigation/index.d.ts.map +1 -1
  102. package/dist/components/navigation/index.js +26 -38
  103. package/dist/components/navigation/mingo-ai-button.d.ts +3 -38
  104. package/dist/components/navigation/mingo-ai-button.d.ts.map +1 -1
  105. package/dist/components/navigation/mobile-nav-panel.d.ts +10 -0
  106. package/dist/components/navigation/mobile-nav-panel.d.ts.map +1 -0
  107. package/dist/components/navigation/navigation-sidebar-item.d.ts +2 -1
  108. package/dist/components/navigation/navigation-sidebar-item.d.ts.map +1 -1
  109. package/dist/components/navigation/top-navigation.d.ts +2 -2
  110. package/dist/components/navigation/top-navigation.d.ts.map +1 -1
  111. package/dist/components/onboarding-guides/index.cjs +17 -15
  112. package/dist/components/onboarding-guides/index.js +16 -14
  113. package/dist/components/related-content/index.cjs +8 -10
  114. package/dist/components/related-content/index.js +7 -9
  115. package/dist/components/tickets/index.cjs +18 -16
  116. package/dist/components/tickets/index.js +17 -15
  117. package/dist/components/ui/index.cjs +14 -12
  118. package/dist/components/ui/index.js +19 -17
  119. package/dist/hooks/index.cjs +11 -19
  120. package/dist/hooks/index.d.ts +0 -1
  121. package/dist/hooks/index.d.ts.map +1 -1
  122. package/dist/hooks/index.js +11 -19
  123. package/dist/hooks/ui/index.d.ts +0 -3
  124. package/dist/hooks/ui/index.d.ts.map +1 -1
  125. package/dist/index.cjs +22 -55
  126. package/dist/index.js +196 -229
  127. package/dist/types/case-study.d.ts +0 -9
  128. package/dist/types/case-study.d.ts.map +1 -1
  129. package/dist/types/navigation.d.ts +65 -95
  130. package/dist/types/navigation.d.ts.map +1 -1
  131. package/dist/utils/index.cjs +0 -55
  132. package/dist/utils/index.d.ts +0 -1
  133. package/dist/utils/index.d.ts.map +1 -1
  134. package/dist/utils/index.js +1 -50
  135. package/dist/utils/source-icons.d.ts.map +1 -1
  136. package/package.json +1 -1
  137. package/src/components/chat/entity-cards/case-study-card.tsx +5 -115
  138. package/src/components/chat/utils/icon-library.tsx +1 -35
  139. package/src/components/footer-waitlist-button.tsx +53 -0
  140. package/src/components/footer.tsx +195 -0
  141. package/src/components/index.ts +2 -0
  142. package/src/components/loading/page-layout-skeleton.tsx +1 -1
  143. package/src/components/navigation/client-only-header.tsx +22 -0
  144. package/src/components/navigation/header-skeleton.tsx +74 -0
  145. package/src/components/navigation/header.tsx +429 -0
  146. package/src/components/navigation/index.ts +14 -21
  147. package/src/components/navigation/mingo-ai-button.tsx +63 -139
  148. package/src/components/navigation/mobile-nav-panel.tsx +155 -0
  149. package/src/components/navigation/navigation-sidebar-item.tsx +2 -1
  150. package/src/components/navigation/ticket-alerts-button.tsx +2 -2
  151. package/src/components/navigation/top-navigation.tsx +2 -2
  152. package/src/hooks/index.ts +0 -1
  153. package/src/hooks/ui/index.ts +0 -3
  154. package/src/styles/chat-animations.css +2 -57
  155. package/src/types/case-study.ts +0 -16
  156. package/src/types/navigation.ts +66 -80
  157. package/src/utils/index.ts +0 -1
  158. package/src/utils/source-icons.ts +0 -13
  159. package/dist/components/navigation/mobile-nav-sheet-id.d.ts +0 -3
  160. package/dist/components/navigation/mobile-nav-sheet-id.d.ts.map +0 -1
  161. package/dist/components/navigation/mobile-nav-sheet.d.ts +0 -48
  162. package/dist/components/navigation/mobile-nav-sheet.d.ts.map +0 -1
  163. package/dist/components/navigation/nav-item-row.d.ts +0 -27
  164. package/dist/components/navigation/nav-item-row.d.ts.map +0 -1
  165. package/dist/components/navigation/nav-panel.d.ts +0 -43
  166. package/dist/components/navigation/nav-panel.d.ts.map +0 -1
  167. package/dist/components/navigation/site-footer.d.ts +0 -28
  168. package/dist/components/navigation/site-footer.d.ts.map +0 -1
  169. package/dist/components/navigation/site-header.d.ts +0 -61
  170. package/dist/components/navigation/site-header.d.ts.map +0 -1
  171. package/dist/components/navigation/site-nav-link.d.ts +0 -26
  172. package/dist/components/navigation/site-nav-link.d.ts.map +0 -1
  173. package/dist/hooks/ui/use-active-section.d.ts +0 -10
  174. package/dist/hooks/ui/use-active-section.d.ts.map +0 -1
  175. package/dist/hooks/ui/use-nav-menus.d.ts +0 -48
  176. package/dist/hooks/ui/use-nav-menus.d.ts.map +0 -1
  177. package/dist/hooks/ui/use-visitor-os.d.ts +0 -15
  178. package/dist/hooks/ui/use-visitor-os.d.ts.map +0 -1
  179. package/dist/hooks/use-location-hash.d.ts +0 -9
  180. package/dist/hooks/use-location-hash.d.ts.map +0 -1
  181. package/dist/utils/visitor-os.d.ts +0 -45
  182. package/dist/utils/visitor-os.d.ts.map +0 -1
  183. package/src/components/navigation/mobile-nav-sheet-id.ts +0 -2
  184. package/src/components/navigation/mobile-nav-sheet.tsx +0 -241
  185. package/src/components/navigation/nav-item-row.tsx +0 -129
  186. package/src/components/navigation/nav-panel.tsx +0 -196
  187. package/src/components/navigation/site-footer.tsx +0 -256
  188. package/src/components/navigation/site-header.tsx +0 -339
  189. package/src/components/navigation/site-nav-link.tsx +0 -41
  190. package/src/hooks/ui/use-active-section.ts +0 -44
  191. package/src/hooks/ui/use-nav-menus.ts +0 -200
  192. package/src/hooks/ui/use-visitor-os.ts +0 -33
  193. package/src/hooks/use-location-hash.ts +0 -34
  194. package/src/utils/visitor-os.ts +0 -84
@@ -1,180 +1,104 @@
1
1
  'use client';
2
2
 
3
3
  import type React from 'react';
4
- import { useEffect } from 'react';
5
- import { useVisitorOs } from '../../hooks/ui/use-visitor-os';
6
4
  import { cn } from '../../utils';
7
- import { shortcutLabel, usesCommandKey } from '../../utils/visitor-os';
8
5
  import { MingoIcon } from '../icons';
9
- import { Button } from '../ui/button';
10
6
 
11
7
  export interface MingoAiButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
12
8
  source?: string;
13
- /** The platform's Mingo identity glyph: pass the SAME server-configured
9
+ /** The platform's Mingo identity glyph — pass the SAME server-configured
14
10
  * icon the chat panel renders (host-side: `EntityIcon` fed by the admin
15
11
  * `assistantIcon`), so the launcher and the panel can never diverge.
16
12
  * Falls back to the packaged Mingo mark when the server has none. */
17
13
  icon?: React.ReactNode;
18
- /** The launcher's wordmark + aria-label: pass the server-configured
14
+ /** The launcher's wordmark + aria-label — pass the server-configured
19
15
  * assistant name (same `assistantName` the chat panel shows) so the
20
16
  * launcher never hardcodes an identity the admin has renamed. */
21
17
  label?: string;
22
- /** Bind Cmd+K (Ctrl+K off Apple platforms) to open the chat, and show the
23
- * key cap beside the label. Default false. */
24
- shortcutHint?: boolean;
25
- /** `inline` (default): the header launcher, the height and type of the menu
26
- * items beside it. `field`: the full-width row that opens the mobile menu
27
- * (the name reads as the field's prompt). ONE component, so every Mingo
28
- * launcher carries the same identity, ring and event. */
29
- variant?: 'inline' | 'field';
30
- }
31
-
32
- const MINGO_ACCENT = 'var(--ods-flamingo-cyan-base)';
33
-
34
- /** The event the mounted chat panel (`EmbeddableChat`) opens on. */
35
- export const ASK_AI_OPEN_EVENT = 'ask-ai:open';
36
-
37
- /**
38
- * Open the chat of `source`. Coalesced to '' so a source-less call still
39
- * matches the panel's own `runtime.source ?? ''` comparison (undefined !== ''
40
- * would make the panel silently ignore the event).
41
- */
42
- export function openAskAi(source?: string): void {
43
- window.dispatchEvent(new CustomEvent(ASK_AI_OPEN_EVENT, { detail: { source: source ?? '' } }));
44
18
  }
45
19
 
46
20
  /**
47
- * THE Mingo AI launcher, in the site header's right cluster and at the top of
48
- * the mobile menu (`variant="field"`): the round identity glyph, the
49
- * assistant's name and an optional shortcut hint, sized like the menu items
50
- * beside it. Stateless: clicking dispatches an `ask-ai:open`
51
- * CustomEvent (source-filtered) that the mounted `EmbeddableChat` panel
52
- * listens for.
53
- *
54
- * It carries the AI edge light (`.mingo-edge-frame` / `.mingo-edge` in
55
- * `styles/chat-animations.css`): an accent arc travelling around its outline,
56
- * with a one-shot shimmer on hover. Static under reduced motion.
21
+ * Marketing-header Mingo AI launcher: the flush, full-height button rendered
22
+ * at the far right edge of the public `Header` (`config.mingo`). Stateless:
23
+ * clicking dispatches an `ask-ai:open` CustomEvent (source-filtered) that the
24
+ * mounted `EmbeddableChat` panel listens for.
57
25
  *
58
26
  * Distinct from `header-mingo-button.tsx` (`HeaderMingoButton`), the
59
27
  * dashboard/AppHeader controlled toggle; different surface and contract, do
60
28
  * not merge them.
61
29
  *
62
- * It is the lib `Button` (`transparent`, or `glyph` for the menu row, at the
63
- * `wrap` size): the launcher's own box, ring and collapse are classes on it.
30
+ * Deliberately a raw `<button>` rather than the ui-kit `Button`: it needs
31
+ * full-height flush cell layout, an absolutely-positioned animated ring, and
32
+ * icon-only collapse that `Button` cannot express (same precedent as
33
+ * `header-mingo-button.tsx`).
64
34
  */
65
- export function MingoAiButton({
66
- source,
67
- icon,
68
- label = 'Mingo AI',
69
- shortcutHint = false,
70
- variant = 'inline',
71
- className,
72
- onClick,
73
- ...props
74
- }: MingoAiButtonProps) {
75
- // Known after hydration only: until then the hint keeps its space, empty.
76
- const visitor = useVisitorOs();
77
- const field = variant === 'field';
78
-
79
- const commandKey = visitor.known && usesCommandKey(visitor.os);
80
-
81
- // The shortcut the key cap shows opens the same chat the click opens.
82
- useEffect(() => {
83
- if (!shortcutHint) return undefined;
84
- const onKeyDown = (event: KeyboardEvent) => {
85
- // Something on the page already took the shortcut (an editor's link shortcut): leave it alone.
86
- if (event.defaultPrevented || event.altKey || event.shiftKey) return;
87
- // The modifier the key cap shows, and only that one: on a Mac Ctrl+K is
88
- // the text fields' own "delete to the end of the line".
89
- const modifier = commandKey ? event.metaKey && !event.ctrlKey : event.ctrlKey && !event.metaKey;
90
- if (event.key.toLowerCase() !== 'k' || !modifier) return;
91
- event.preventDefault();
92
- openAskAi(source);
93
- };
94
- window.addEventListener('keydown', onKeyDown);
95
- return () => window.removeEventListener('keydown', onKeyDown);
96
- }, [shortcutHint, source, commandKey]);
35
+ const MINGO_ACCENT = 'var(--ods-flamingo-cyan-base)';
97
36
 
37
+ export function MingoAiButton({ source, icon, label = 'Mingo AI', className, onClick, ...props }: MingoAiButtonProps) {
98
38
  return (
99
- <Button
39
+ <button
100
40
  {...props}
101
41
  type="button"
102
- variant={field ? 'glyph' : 'transparent'}
103
- size="wrap"
104
- font="regular"
105
42
  aria-label={label}
106
- aria-keyshortcuts={shortcutHint && visitor.known ? (commandKey ? 'Meta+K' : 'Control+K') : undefined}
107
43
  onClick={e => {
108
- openAskAi(source);
44
+ // Coalesce to '' so a source-less mount still matches EmbeddableChat's
45
+ // own `runtime.source ?? ''` comparison (undefined !== '' would make
46
+ // the panel silently ignore the event).
47
+ window.dispatchEvent(new CustomEvent('ask-ai:open', { detail: { source: source ?? '' } }));
109
48
  onClick?.(e);
110
49
  }}
111
50
  className={cn(
112
- 'group/mingo flex shrink-0 justify-start focus-visible:ring-ods-accent',
113
- field
114
- ? // A row of the mobile menu like the groups under it: same height,
115
- // type and divider, no surface and no ring of its own.
116
- 'min-h-14 w-full gap-[var(--spacing-system-sf)] rounded-none border-b border-ods-border text-left text-h4 [&_svg]:h-6 [&_svg]:w-6'
117
- : // Transparent at rest so it inherits the bar's background; the hover
118
- // wash is the menu items' own.
119
- 'h-10 gap-[var(--spacing-system-xsf)] rounded-md pl-[var(--spacing-system-xsf)] pr-2.5 text-ods-text-secondary hover:text-ods-text-primary',
51
+ // Unified ODS top-navigation cell (Figma 2797-6808 desktop /
52
+ // 2797-7275 mobile): full-height cell with a leading divider,
53
+ // transparent at rest so it inherits the bar's background.
54
+ // No hover background on the cell: the animated ring + shimmer are
55
+ // the ONLY hover treatment (a bg flip on top read as a double
56
+ // animation).
57
+ 'group/mingo relative flex h-full shrink-0 items-center border-l border-ods-border bg-transparent px-[var(--spacing-system-m)] focus:outline-none focus-visible:ring-2 focus-visible:ring-ods-accent',
120
58
  className,
121
59
  )}
122
60
  >
123
- {!field && (
124
- <>
125
- {/* AI edge light (Apple-Intelligence-style): a rotating accent-gradient
126
- arc clipped to a hairline ring on the outline via CSS mask
127
- (.mingo-edge-frame). NO opaque cover, so the launcher inherits
128
- whatever background sits behind it. Platform-tinted via the accent
129
- token. */}
130
- <span aria-hidden="true" className="mingo-edge-frame pointer-events-none absolute inset-0 rounded-md">
131
- <span className="mingo-edge" />
132
- </span>
133
- {/* One-shot light-streak shimmer on hover, clipped to the outline. */}
134
- <span aria-hidden="true" className="pointer-events-none absolute inset-0 overflow-hidden rounded-md">
135
- <span
136
- className="mingo-shimmer absolute inset-y-0 left-0 w-1/2"
137
- style={{
138
- background:
139
- 'linear-gradient(105deg, transparent, color-mix(in srgb, var(--ods-system-greys-white) 12%, transparent), transparent)',
140
- }}
141
- />
142
- </span>
143
- </>
144
- )}
145
- {icon ? (
146
- <span
147
- className={cn(
148
- 'relative inline-flex shrink-0 items-center justify-center overflow-hidden rounded-full',
149
- field ? 'size-6' : 'size-5',
150
- )}
151
- >
152
- {icon}
61
+ {/* Inner box = 1:1 twin of the DEFAULT-size Button geometry (rounded-md,
62
+ h-11 md:h-12, px-m) — the same footprint as the header's "Try for
63
+ Free" CTA — so the animated ring traces exactly where a full button
64
+ border would sit inside the 72px bar. */}
65
+ <span className="relative flex h-11 items-center gap-[var(--spacing-system-xsf)] rounded-md px-[var(--spacing-system-m)] md:h-12">
66
+ {/* AI edge light (Apple-Intelligence-style): a rotating accent-
67
+ gradient arc clipped to a 3px ring on the box outline via CSS
68
+ mask (.mingo-edge-frame) — NO opaque cover, so the launcher
69
+ inherits whatever background sits behind it. Platform-tinted via
70
+ the accent token. */}
71
+ <span aria-hidden="true" className="mingo-edge-frame pointer-events-none absolute inset-0 rounded-md">
72
+ <span className="mingo-edge" />
153
73
  </span>
154
- ) : (
155
- <MingoIcon
156
- color="currentColor"
157
- eyesColor={MINGO_ACCENT}
158
- cornerColor={MINGO_ACCENT}
159
- className={cn('relative shrink-0 text-ods-text-primary', field ? 'size-6' : 'size-5')}
160
- />
161
- )}
162
- {/* In the header the name collapses below lg, with the menus: on a phone
163
- the launcher is its glyph alone (the name stays the accessible name). */}
164
- <span
165
- className={cn('relative whitespace-nowrap', field ? 'min-w-0 flex-1 truncate' : 'hidden text-h6 lg:inline')}
166
- >
167
- {label}
74
+ {/* One-shot light-streak shimmer on hover, clipped to the box. */}
75
+ <span aria-hidden="true" className="pointer-events-none absolute inset-0 overflow-hidden rounded-md">
76
+ <span
77
+ className="mingo-shimmer absolute inset-y-0 left-0 w-1/2"
78
+ style={{
79
+ background:
80
+ 'linear-gradient(105deg, transparent, color-mix(in srgb, var(--ods-system-greys-white) 12%, transparent), transparent)',
81
+ }}
82
+ />
83
+ </span>
84
+ {icon ? (
85
+ <span className="relative inline-flex size-6 shrink-0 items-center justify-center">{icon}</span>
86
+ ) : (
87
+ <MingoIcon
88
+ color="currentColor"
89
+ eyesColor={MINGO_ACCENT}
90
+ cornerColor={MINGO_ACCENT}
91
+ className="relative h-6 w-6 shrink-0 text-ods-text-primary"
92
+ />
93
+ )}
94
+ {/* Wordmark collapses below md — the mobile cell is icon-only per
95
+ spec. h3 bold, matching the default-size CTA label next to it
96
+ (Figma 2936-6825). */}
97
+ {/* Bare `text-h3` — the exact composite the default-size Button label
98
+ uses (bold 700 and -0.02em tracking are built into the utility). */}
99
+ <span className="relative hidden whitespace-nowrap text-ods-text-primary text-h3 md:inline">{label}</span>
168
100
  </span>
169
- {shortcutHint && !field && (
170
- // A key cap in the label's own type (never smaller: a hint nobody can
171
- // read is decoration) and the label's colour, which clears 4.5:1 on
172
- // the bar. It keeps its width before the platform is known.
173
- <kbd className="relative hidden h-6 min-w-9 items-center justify-center rounded border border-ods-border px-[var(--spacing-system-xxs)] text-h6 lg:inline-flex">
174
- {visitor.known ? shortcutLabel(visitor.os, 'K') : ''}
175
- </kbd>
176
- )}
177
- </Button>
101
+ </button>
178
102
  );
179
103
  }
180
104
 
@@ -0,0 +1,155 @@
1
+ 'use client';
2
+
3
+ import { usePreventScroll } from '@react-aria/overlays';
4
+ import { X } from 'lucide-react';
5
+ import type React from 'react';
6
+ import { useRef } from 'react';
7
+ import { useFocusTrap } from '../../hooks/ui/use-focus-trap';
8
+ import { useHeaderHeight } from '../../hooks/ui/use-header-height';
9
+ import type { MobileNavConfig, NavigationItem } from '../../types/navigation';
10
+ import { cn } from '../../utils';
11
+ import { Button } from '../ui/button';
12
+ import { OVERLAY_BACKDROP_CLASS } from '../ui/drawer';
13
+
14
+ /** DOM id of the panel — referenced by the header hamburger's `aria-controls`. */
15
+ export const MOBILE_NAV_PANEL_ID = 'mobile-nav-panel';
16
+
17
+ export interface MobileNavPanelProps {
18
+ isOpen: boolean;
19
+ config: MobileNavConfig;
20
+ }
21
+
22
+ export function MobileNavPanel({ isOpen, config }: MobileNavPanelProps) {
23
+ const panelRef = useRef<HTMLDivElement>(null);
24
+
25
+ // Shared ref-counted, iOS-aware scroll lock (react-aria) — one counter with
26
+ // the modals and chat overlays, restores prior styles on release.
27
+ usePreventScroll({ isDisabled: !isOpen });
28
+ // Initial focus, Tab containment, Escape-to-close, guarded focus restore.
29
+ useFocusTrap(panelRef, isOpen, { onEscape: config.onClose });
30
+ // The panel is layout-mounted on every page — observers only while open.
31
+ const headerHeight = useHeaderHeight(64, { enabled: isOpen });
32
+
33
+ if (!isOpen) return null;
34
+
35
+ const renderNavigationItem = (item: NavigationItem) => {
36
+ if (item.element) {
37
+ return <div key={item.id}>{item.element}</div>;
38
+ }
39
+
40
+ const handleClick = () => {
41
+ if (item.onClick) {
42
+ item.onClick();
43
+ }
44
+ config.onClose?.();
45
+ };
46
+
47
+ if (item.href) {
48
+ return (
49
+ <Button
50
+ key={item.id}
51
+ variant="outline"
52
+ size="small-legacy"
53
+ href={item.href}
54
+ onClick={handleClick}
55
+ leftIcon={item.icon}
56
+ rightIcon={item.badge}
57
+ className={cn(
58
+ 'h-12 justify-start gap-3 rounded-md border-none bg-transparent px-4 text-ods-text-primary transition-colors hover:bg-ods-bg-hover',
59
+ item.isActive ? 'bg-ods-bg-hover' : '',
60
+ )}
61
+ >
62
+ {item.label}
63
+ </Button>
64
+ );
65
+ }
66
+
67
+ return (
68
+ <Button
69
+ key={item.id}
70
+ variant="outline"
71
+ size="small-legacy"
72
+ onClick={handleClick}
73
+ leftIcon={item.icon}
74
+ className={cn(
75
+ 'h-12 justify-start gap-3 rounded-md border-none bg-transparent px-4 text-ods-text-primary transition-colors hover:bg-ods-bg-hover',
76
+ item.isActive ? 'bg-ods-bg-hover' : '',
77
+ )}
78
+ >
79
+ <span className="flex-1 text-left">{item.label}</span>
80
+ {item.badge !== undefined && <span className="ml-auto">{item.badge}</span>}
81
+ </Button>
82
+ );
83
+ };
84
+
85
+ return (
86
+ <>
87
+ {/* Backdrop - closes nav when clicked outside */}
88
+ <div className={cn('fixed inset-0 z-[9998]', OVERLAY_BACKDROP_CLASS)} onClick={config.onClose} />
89
+
90
+ {/* Navigation Panel - top-anchored floating card */}
91
+ <div
92
+ ref={panelRef}
93
+ id={MOBILE_NAV_PANEL_ID}
94
+ role="dialog"
95
+ aria-modal="true"
96
+ aria-label="Navigation menu"
97
+ tabIndex={-1}
98
+ style={{ '--mobile-nav-top': `${headerHeight + 16}px` } as React.CSSProperties}
99
+ className={cn(
100
+ 'fixed z-[9999] rounded-lg shadow-xl',
101
+ config.className ? '' : 'border border-ods-border bg-ods-card',
102
+ // Responsive positioning and sizing — LEFT-anchored from md, flush
103
+ // under the burger cell at the left edge of the unified
104
+ // top-navigation (a larger inset read as a stray gap between the
105
+ // burger and its panel).
106
+ 'left-2 right-2 md:right-auto',
107
+ 'md:left-2 md:w-[400px] md:max-w-[calc(100vw-1rem)]',
108
+ // Small-viewport (svh) sizing anchored below the measured header —
109
+ // 100vh overflowed under mobile browser chrome, leaving the bottom
110
+ // entries and footer unreachable while the body was scroll-locked.
111
+ 'top-[var(--mobile-nav-top)]',
112
+ 'max-h-[calc(100svh-var(--mobile-nav-top)-50px)] md:max-h-[calc(100svh-var(--mobile-nav-top)-8px)]',
113
+ 'flex flex-col',
114
+ config.className || '',
115
+ )}
116
+ onClick={e => e.stopPropagation()} // Prevent closing when clicking inside nav
117
+ >
118
+ {/* Header with close button - fixed at top */}
119
+ <div className="flex flex-shrink-0 justify-end border-b border-ods-border p-2">
120
+ <Button
121
+ aria-label="Close menu"
122
+ size="icon"
123
+ variant="transparent"
124
+ leftIcon={<X className="h-4 w-4 text-ods-text-primary" />}
125
+ onClick={config.onClose}
126
+ className="hover:bg-ods-bg-hover"
127
+ />
128
+ </div>
129
+
130
+ {/* Scrollable content area */}
131
+ <div className="flex-1 overflow-y-auto overflow-x-hidden overscroll-contain px-2 py-2">
132
+ <div className="flex flex-col space-y-1">
133
+ {config.sections.map((section, index) => (
134
+ <div key={index}>
135
+ {section.title && (
136
+ <div className="px-4 pb-1 pt-2 font-semibold uppercase text-ods-text-secondary text-h6">
137
+ {section.title}
138
+ </div>
139
+ )}
140
+ <div className="flex flex-col gap-1">{section.items.map(renderNavigationItem)}</div>
141
+ </div>
142
+ ))}
143
+ </div>
144
+ </div>
145
+
146
+ {/* Footer with action button - fixed at bottom, clears the iOS home indicator */}
147
+ {config.footer && (
148
+ <div className="flex-shrink-0 border-t border-ods-border p-4 pb-[max(1rem,env(safe-area-inset-bottom))]">
149
+ {config.footer}
150
+ </div>
151
+ )}
152
+ </div>
153
+ </>
154
+ );
155
+ }
@@ -155,7 +155,8 @@ export const NavigationSidebarItemButton = memo(function NavigationSidebarItemBu
155
155
  *
156
156
  * Deliberately in this file, beside `NavigationSidebarItemButton`: the two must
157
157
  * agree on `h-14`, the `p-[var(--spacing-system-m)]` inset, the 24px icon box and
158
- * the label column, or the nav resizes on the handoff.
158
+ * the label column, or the nav resizes on the handoff. Same reason
159
+ * `header-skeleton.tsx` sits beside `header.tsx`.
159
160
  *
160
161
  * `showLabel` is the sidebar's own minimized state, so the caller passes what it
161
162
  * already passes the real row — no separate width handling.
@@ -2,8 +2,8 @@
2
2
 
3
3
  /**
4
4
  * TicketAlertsButton — the unified header affordance for support-ticket
5
- * unread indication. Mounted by BOTH header shells (the `SiteHeader` through its
6
- * `sideActions` slot, console `AppHeader` via `showTicketAlerts`).
5
+ * unread indication. Mounted by BOTH header shells (hub `Header` via
6
+ * `HeaderConfig.tickets`, console `AppHeader` via `showTicketAlerts`).
7
7
  *
8
8
  * ATTENTION-ONLY element, deliberately unlike the always-on header cells:
9
9
  * - renders NOTHING unless there are unread support replies (and a
@@ -59,7 +59,7 @@ export interface TopNavigationProps extends React.HTMLAttributes<HTMLElement> {
59
59
  sideActions?: React.ReactNode;
60
60
  /** Opaque ODS background class. Defaults to `bg-ods-card`. Keep it opaque —
61
61
  * translucent backgrounds under a sticky bar need a deliberate
62
- * backdrop-blur treatment (see the note in `site-header.tsx`). */
62
+ * backdrop-blur treatment (see the note in `header.tsx`). */
63
63
  backgroundClassName?: string;
64
64
  /** Top border on mobile (the ODS spec bar shows it below md). Default true. */
65
65
  mobileTopBorder?: boolean;
@@ -73,7 +73,7 @@ const hasContent = (node: React.ReactNode): boolean => node !== null && node !==
73
73
  /**
74
74
  * Unified ODS top-navigation bar (Figma `[UPD] top-navigation`, node
75
75
  * 2797-5978): the single 48px/56px (mobile / md+) cell-based header shell
76
- * shared by the console `AppHeader` and the `SiteHeader` across all
76
+ * shared by the console `AppHeader` and the marketing `Header` across all
77
77
  * platforms.
78
78
  *
79
79
  * The shell owns the bar geometry only — height, borders, background, and the
@@ -39,7 +39,6 @@ export * from './use-claude-mirror-src';
39
39
  // Deep-link "scroll to URL hash" after data loads. Pairs with URL
40
40
  // composers that emit `?<filter>=<id>#<prefix>-<id>` — the filter
41
41
  // narrows the list, the hash scrolls the matching DOM id.
42
- export * from './use-location-hash';
43
42
  export * from './use-scroll-to-hash';
44
43
 
45
44
  // Invisible bot-protection client primitive (honeypot ref + submit-timing).
@@ -31,6 +31,3 @@ export { usePreventScroll } from '@react-aria/overlays';
31
31
  export * from './use-prefers-reduced-motion';
32
32
  export * from './use-in-view';
33
33
  export * from './use-autoplay';
34
- export * from './use-active-section';
35
- export * from './use-nav-menus';
36
- export * from './use-visitor-os';
@@ -134,12 +134,12 @@
134
134
  }
135
135
  }
136
136
 
137
- /* Ring mask: clips the rotating conic layer to a 1px band along the frame's
137
+ /* Ring mask: clips the rotating conic layer to a 3px band along the frame's
138
138
  rounded outline (padding-box XOR), so the interior stays fully transparent
139
139
  and the host background shows through — no opaque cover element. */
140
140
  .mingo-edge-frame {
141
141
  overflow: hidden;
142
- padding: 1px;
142
+ padding: 3px;
143
143
  -webkit-mask:
144
144
  linear-gradient(#fff 0 0) content-box,
145
145
  linear-gradient(#fff 0 0);
@@ -198,58 +198,3 @@
198
198
  opacity: 0;
199
199
  }
200
200
  }
201
-
202
- /* -----------------------------------------------------------------------------
203
- * Site header mega menus: how a panel's CONTENT arrives.
204
- * open from a closed bar: a short fade and settle.
205
- * from-start from the menu before it in the row: slides in from the left.
206
- * from-end from the menu after it: slides in from the right.
207
- * Between two mega menus the surface itself does not animate (NavPanel hands it
208
- * over in one frame), so only the content moves, in the direction the pointer
209
- * travelled. Short and ease-out: it must never make the menu feel slower.
210
- * Reduced motion: no movement, a plain fade.
211
- * ---------------------------------------------------------------------------*/
212
- @keyframes nav-panel-open {
213
- from {
214
- opacity: 0;
215
- transform: translateY(-4px);
216
- }
217
- }
218
-
219
- @keyframes nav-panel-from-start {
220
- from {
221
- opacity: 0;
222
- transform: translateX(-24px);
223
- }
224
- }
225
-
226
- @keyframes nav-panel-from-end {
227
- from {
228
- opacity: 0;
229
- transform: translateX(24px);
230
- }
231
- }
232
-
233
- @keyframes nav-panel-fade {
234
- from {
235
- opacity: 0;
236
- }
237
- }
238
-
239
- .nav-panel-content[data-motion='open'] {
240
- animation: nav-panel-open 180ms cubic-bezier(0.2, 0, 0, 1);
241
- }
242
-
243
- .nav-panel-content[data-motion='from-start'] {
244
- animation: nav-panel-from-start 220ms cubic-bezier(0.2, 0, 0, 1);
245
- }
246
-
247
- .nav-panel-content[data-motion='from-end'] {
248
- animation: nav-panel-from-end 220ms cubic-bezier(0.2, 0, 0, 1);
249
- }
250
-
251
- @media (prefers-reduced-motion: reduce) {
252
- .nav-panel-content[data-motion] {
253
- animation: nav-panel-fade 120ms linear;
254
- }
255
- }
@@ -82,22 +82,6 @@ export interface CaseStudy {
82
82
  case_study_tags?: TagAssoc[];
83
83
  }
84
84
 
85
- /**
86
- * What a case study card reads: the story, its cover and video, whose story it
87
- * is and who tells it. A full `CaseStudy` row satisfies it; a surface that ships
88
- * the card's data to the browser (the site menu) sends only this.
89
- */
90
- export type CaseStudyCardData = Pick<CaseStudy, 'id' | 'title' | 'summary' | 'featured_image'> &
91
- Partial<
92
- Pick<
93
- CaseStudy,
94
- 'main_video_url' | 'main_video_thumbnail' | 'highlight_video_url' | 'highlight_video_thumbnail' | 'video_bites'
95
- >
96
- > & {
97
- user?: Pick<UserProfile, 'full_name' | 'job_title'> & Partial<Pick<UserProfile, 'avatar_url'>>;
98
- msp?: Pick<MSP, 'name' | 'icon_url'>;
99
- };
100
-
101
85
  export interface CreateCaseStudyData {
102
86
  title: string;
103
87
  slug: string;