@flamingo-stack/openframe-frontend-core 0.0.686-2464.7467.1 → 0.0.686
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.
- package/dist/{chunk-JTC4RPS3.js → chunk-2G2CB7ZZ.js} +5 -5
- package/dist/{chunk-QIYQMEP3.cjs → chunk-3DO76UAB.cjs} +20 -20
- package/dist/{chunk-QIYQMEP3.cjs.map → chunk-3DO76UAB.cjs.map} +1 -1
- package/dist/{chunk-S6C5GBQ4.js → chunk-3NIZ2VLQ.js} +3 -3
- package/dist/{chunk-P2AE46BQ.cjs → chunk-4LBCUBVL.cjs} +50 -50
- package/dist/{chunk-P2AE46BQ.cjs.map → chunk-4LBCUBVL.cjs.map} +1 -1
- package/dist/{chunk-U5LNGS5P.js → chunk-4RJQMOQW.js} +8 -8
- package/dist/{chunk-TV6YNAVH.cjs → chunk-AF4XOURN.cjs} +3 -3
- package/dist/{chunk-TV6YNAVH.cjs.map → chunk-AF4XOURN.cjs.map} +1 -1
- package/dist/{chunk-637VBBHC.js → chunk-ASBBGP36.js} +7 -7
- package/dist/{chunk-6MDNGGCJ.js → chunk-C5TFPMOM.js} +6 -6
- package/dist/{chunk-J5O5KIHL.cjs → chunk-GETQTNRV.cjs} +22 -22
- package/dist/{chunk-J5O5KIHL.cjs.map → chunk-GETQTNRV.cjs.map} +1 -1
- package/dist/{chunk-XML4CLL3.cjs → chunk-IBUFFKAK.cjs} +411 -411
- package/dist/{chunk-XML4CLL3.cjs.map → chunk-IBUFFKAK.cjs.map} +1 -1
- package/dist/{chunk-74TJJQMT.js → chunk-IELOPDTA.js} +3 -3
- package/dist/{chunk-RMNWN5RC.cjs → chunk-II5GVLTN.cjs} +5 -5
- package/dist/{chunk-RMNWN5RC.cjs.map → chunk-II5GVLTN.cjs.map} +1 -1
- package/dist/{chunk-D2XLLWIU.cjs → chunk-IJUQQTMV.cjs} +2 -4
- package/dist/chunk-IJUQQTMV.cjs.map +1 -0
- package/dist/{chunk-LMZW2KZF.js → chunk-JBJZL5PI.js} +4 -4
- package/dist/{chunk-WYB27NKC.js → chunk-JNUL3ZWK.js} +3 -5
- package/dist/chunk-JNUL3ZWK.js.map +1 -0
- package/dist/{chunk-JA6NYUF3.js → chunk-LCOMNQLX.js} +5 -5
- package/dist/{chunk-NRH4IHI4.js → chunk-MHTB67ZH.js} +4 -4
- package/dist/{chunk-HSWEUDKR.cjs → chunk-MZAZUSPQ.cjs} +10 -10
- package/dist/{chunk-HSWEUDKR.cjs.map → chunk-MZAZUSPQ.cjs.map} +1 -1
- package/dist/{chunk-IQDUMR3K.cjs → chunk-N3I53B5W.cjs} +5 -5
- package/dist/{chunk-IQDUMR3K.cjs.map → chunk-N3I53B5W.cjs.map} +1 -1
- package/dist/{chunk-3NBKFWHG.js → chunk-NINO3PI7.js} +3 -3
- package/dist/{chunk-CCLO5R5A.cjs → chunk-P4SBHO7U.cjs} +17 -17
- package/dist/{chunk-CCLO5R5A.cjs.map → chunk-P4SBHO7U.cjs.map} +1 -1
- package/dist/{chunk-BOIYZ2L6.js → chunk-PG65AE3I.js} +5 -5
- package/dist/{chunk-SFYBD456.js → chunk-QOH45QSE.js} +4 -4
- package/dist/{chunk-KBH7EWFW.cjs → chunk-QTXDXHMD.cjs} +26 -26
- package/dist/{chunk-KBH7EWFW.cjs.map → chunk-QTXDXHMD.cjs.map} +1 -1
- package/dist/{chunk-3XZHZ4LD.cjs → chunk-RBCMBRI7.cjs} +39 -39
- package/dist/{chunk-3XZHZ4LD.cjs.map → chunk-RBCMBRI7.cjs.map} +1 -1
- package/dist/{chunk-CJNYQ6FK.cjs → chunk-RKH7DEDF.cjs} +12 -12
- package/dist/{chunk-CJNYQ6FK.cjs.map → chunk-RKH7DEDF.cjs.map} +1 -1
- package/dist/{chunk-PLLY62XF.js → chunk-RSMAVO2Z.js} +2 -2
- package/dist/{chunk-SIAGFCKP.cjs → chunk-SI77HF4U.cjs} +53 -53
- package/dist/{chunk-SIAGFCKP.cjs.map → chunk-SI77HF4U.cjs.map} +1 -1
- package/dist/{chunk-NMFBC7CC.js → chunk-U6JC3RS2.js} +2 -2
- package/dist/{chunk-OWSFP7LX.cjs → chunk-UBXEETWQ.cjs} +80 -80
- package/dist/{chunk-OWSFP7LX.cjs.map → chunk-UBXEETWQ.cjs.map} +1 -1
- package/dist/{chunk-ZQDJPLRS.js → chunk-V5REIMSE.js} +2 -2
- package/dist/{chunk-VAHO2DTJ.js → chunk-WFH52OVQ.js} +6 -6
- package/dist/{chunk-GQ4FKLFC.cjs → chunk-WJX3ZSOE.cjs} +43 -43
- package/dist/{chunk-GQ4FKLFC.cjs.map → chunk-WJX3ZSOE.cjs.map} +1 -1
- package/dist/{chunk-IWJEJBML.cjs → chunk-XF5TJ4P2.cjs} +12 -12
- package/dist/{chunk-IWJEJBML.cjs.map → chunk-XF5TJ4P2.cjs.map} +1 -1
- package/dist/{chunk-IXLO6K3T.js → chunk-YPXXXX6R.js} +2 -2
- package/dist/{chunk-RHBJIFAJ.cjs → chunk-ZS3YCJYY.cjs} +7 -7
- package/dist/{chunk-RHBJIFAJ.cjs.map → chunk-ZS3YCJYY.cjs.map} +1 -1
- package/dist/components/case-studies/index.cjs +17 -17
- package/dist/components/case-studies/index.js +5 -5
- package/dist/components/chat/index.cjs +3 -3
- package/dist/components/chat/index.js +2 -2
- package/dist/components/contact/index.cjs +6 -6
- package/dist/components/contact/index.js +5 -5
- package/dist/components/docs/index.cjs +9 -9
- package/dist/components/docs/index.js +8 -8
- package/dist/components/embeds/index.cjs +6 -6
- package/dist/components/embeds/index.js +5 -5
- package/dist/components/faq/index.cjs +6 -6
- package/dist/components/faq/index.js +5 -5
- package/dist/components/features/index.cjs +5 -5
- package/dist/components/features/index.js +4 -4
- package/dist/components/help-center-pages/index.cjs +83 -83
- package/dist/components/help-center-pages/index.js +16 -16
- package/dist/components/index.cjs +46 -46
- package/dist/components/index.js +15 -15
- package/dist/components/meeting-scheduler/index.cjs +32 -32
- package/dist/components/meeting-scheduler/index.js +4 -4
- package/dist/components/navigation/index.cjs +8 -8
- package/dist/components/navigation/index.js +7 -7
- package/dist/components/onboarding-guides/index.cjs +8 -8
- package/dist/components/onboarding-guides/index.js +7 -7
- package/dist/components/tickets/index.cjs +9 -9
- package/dist/components/tickets/index.js +8 -8
- package/dist/components/tool-icon.d.ts.map +1 -1
- package/dist/components/ui/index.cjs +5 -5
- package/dist/components/ui/index.js +4 -4
- package/dist/hooks/index.cjs +3 -3
- package/dist/hooks/index.js +2 -2
- package/dist/index.cjs +10 -10
- package/dist/index.js +8 -8
- package/package.json +1 -1
- package/src/components/tool-icon.tsx +2 -4
- package/dist/chunk-D2XLLWIU.cjs.map +0 -1
- package/dist/chunk-WYB27NKC.js.map +0 -1
- /package/dist/{chunk-JTC4RPS3.js.map → chunk-2G2CB7ZZ.js.map} +0 -0
- /package/dist/{chunk-S6C5GBQ4.js.map → chunk-3NIZ2VLQ.js.map} +0 -0
- /package/dist/{chunk-U5LNGS5P.js.map → chunk-4RJQMOQW.js.map} +0 -0
- /package/dist/{chunk-637VBBHC.js.map → chunk-ASBBGP36.js.map} +0 -0
- /package/dist/{chunk-6MDNGGCJ.js.map → chunk-C5TFPMOM.js.map} +0 -0
- /package/dist/{chunk-74TJJQMT.js.map → chunk-IELOPDTA.js.map} +0 -0
- /package/dist/{chunk-LMZW2KZF.js.map → chunk-JBJZL5PI.js.map} +0 -0
- /package/dist/{chunk-JA6NYUF3.js.map → chunk-LCOMNQLX.js.map} +0 -0
- /package/dist/{chunk-NRH4IHI4.js.map → chunk-MHTB67ZH.js.map} +0 -0
- /package/dist/{chunk-3NBKFWHG.js.map → chunk-NINO3PI7.js.map} +0 -0
- /package/dist/{chunk-BOIYZ2L6.js.map → chunk-PG65AE3I.js.map} +0 -0
- /package/dist/{chunk-SFYBD456.js.map → chunk-QOH45QSE.js.map} +0 -0
- /package/dist/{chunk-PLLY62XF.js.map → chunk-RSMAVO2Z.js.map} +0 -0
- /package/dist/{chunk-NMFBC7CC.js.map → chunk-U6JC3RS2.js.map} +0 -0
- /package/dist/{chunk-ZQDJPLRS.js.map → chunk-V5REIMSE.js.map} +0 -0
- /package/dist/{chunk-VAHO2DTJ.js.map → chunk-WFH52OVQ.js.map} +0 -0
- /package/dist/{chunk-IXLO6K3T.js.map → chunk-YPXXXX6R.js.map} +0 -0
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
MeshcentralLogoGreyIcon,
|
|
6
6
|
FleetMdmLogoGreyIcon,
|
|
7
7
|
AuthentikLogoGreyIcon,
|
|
8
|
-
|
|
8
|
+
MicrosoftLogoGreyIcon,
|
|
9
9
|
GoogleIcon,
|
|
10
10
|
OpenframeLogoGreyIcon,
|
|
11
11
|
} from './icons-v2-generated';
|
|
@@ -28,9 +28,7 @@ const toolIconMap: Record<ToolType, ToolIconRenderer> = {
|
|
|
28
28
|
[ToolTypeValues.AUTHENTIK]: (size, className) => <AuthentikLogoGreyIcon size={size} className={className} />,
|
|
29
29
|
[ToolTypeValues.OSQUERY]: (size, className) => <OsqueryLogoGreyIcon size={size} className={className} />,
|
|
30
30
|
[ToolTypeValues.SYSTEM]: () => null,
|
|
31
|
-
|
|
32
|
-
// own brand is what the logs and the directory pickers name.
|
|
33
|
-
[ToolTypeValues.MICROSOFT_365]: (size, className) => <Office365LogoGreyIcon size={size} className={className} />,
|
|
31
|
+
[ToolTypeValues.MICROSOFT_365]: (size, className) => <MicrosoftLogoGreyIcon size={size} className={className} />,
|
|
34
32
|
[ToolTypeValues.GOOGLE_WORKSPACE]: (size, className) => <GoogleIcon size={size} className={className} />,
|
|
35
33
|
};
|
|
36
34
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-D2XLLWIU.cjs","../src/hooks/ui/use-is-truncated.ts","../src/hooks/ui/use-debounce.ts","../src/hooks/ui/use-isomorphic-layout-effect.ts","../src/hooks/ui/use-media-query.ts","../src/hooks/ui/use-header-height.ts","../src/hooks/ui/use-marquee-engine.ts","../src/hooks/ui/use-reset-on-page-hidden.ts","../src/hooks/ui/use-suppress-clone-focus.ts","../src/types/tool.types.ts","../src/components/tool-icon.tsx","../src/utils/common.ts","../src/hooks/use-near-viewport.ts","../src/utils/markdown-fences.ts","../src/utils/markdown-heading-id.ts","../src/utils/anchor-id.ts","../src/utils/scroll-into-view.ts","../src/utils/same-page-hash-nav.ts"],"names":["delay","useState","useEffect","header","bar","useCallback","useRef"],"mappings":"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AACN,EAAA;AAEA,EAAA;AACM,IAAA;AACN,EAAA;AAIO,EAAA;AACT;AfueU;AACA;AgB5gBN;AACA;AAEK;AACH,EAAA;AACF,IAAA;AACA,IAAA;AACF,EAAA;AACI,EAAA;AACF,IAAA;AACA,IAAA;AACF,EAAA;AACF;AAEM;AAWU;AACd,EAAA;AACQ,IAAA;AAEH,IAAA;AAGD,MAAA;AACF,IAAA;AACF,EAAA;AACO,EAAA;AACT;AAOgB;AAIV,EAAA;AACI,EAAA;AAKF,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACC,IAAA;AACP,EAAA;AAKM,EAAA;AACE,IAAA;AAKA,IAAA;AACA,IAAA;AAGN,IAAA;AACF,EAAA;AAGA,EAAA;AAEM,EAAA;AAIF,EAAA;AACF,IAAA;AACA,IAAA;AACF,EAAA;AAGI,EAAA;AACA,EAAA;AAKE,EAAA;AACC,EAAA;AACA,EAAA;AACP,EAAA;AACE,IAAA;AACA,IAAA;AACF,EAAA;AAEM,EAAA;AACA,IAAA;AACF,MAAA;AACA,MAAA;AACF,IAAA;AACM,IAAA;AACA,IAAA;AACA,IAAA;AACN,IAAA;AACI,IAAA;AACF,MAAA;AACF,IAAA;AAEE,MAAA;AACA,MAAA;AACI,MAAA;AACF,QAAA;AACA,QAAA;AACF,MAAA;AACF,IAAA;AACF,EAAA;AACA,EAAA;AACF;AhB0dU;AACA;AiBtpBG;AAIA;AAUG;AACT,EAAA;AACC,EAAA;AACC,EAAA;AACT;AAqBgB;AACL,EAAA;AACH,EAAA;AACF,EAAA;AAEA,EAAA;AACA,EAAA;AACF,IAAA;AACI,IAAA;AACF,MAAA;AACI,MAAA;AACN,IAAA;AACM,EAAA;AAEN,IAAA;AACF,EAAA;AAEO,EAAA;AACT;AAsBgB;AACV,EAAA;AACI,EAAA;AACF,EAAA;AAKF,EAAA;AACA,EAAA;AACI,IAAA;AACA,EAAA;AACN,IAAA;AACF,EAAA;AAEM,EAAA;AAIJ,IAAA;AACF,EAAA;AACM,EAAA;AAGA,EAAA;AACF,EAAA;AACF,IAAA;AACE,MAAA;AACF,IAAA;AACF,EAAA;AACM,EAAA;AACA,EAAA;AAED,EAAA;AACD,EAAA;AACI,IAAA;AACF,IAAA;AACF,MAAA;AACF,IAAA;AACE,MAAA;AACF,IAAA;AAGA,IAAA;AACM,MAAA;AACF,QAAA;AACA,QAAA;AACD,MAAA;AACH,IAAA;AACF,EAAA;AACM,EAAA;AACF,EAAA;AACF,IAAA;AACF,EAAA;AAGA,EAAA;AACE,IAAA;AACA,IAAA;AACD,EAAA;AACM,EAAA;AACT;AjBilBU;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-D2XLLWIU.cjs","sourcesContent":[null,"'use client';\n\nimport { useCallback, useEffect, useState } from 'react';\n\nexport interface UseIsTruncatedOptions {\n /** Measure vertical clipping (`line-clamp-N`) instead of the single-line ellipsis. */\n multiline?: boolean;\n}\n\n/**\n * Tracks whether an element's text is ACTUALLY clipped — the condition for\n * offering a \"full value\" tooltip. Text that fits needs no tooltip, and showing\n * one anyway just repeats what the user is already reading.\n *\n * Three things can change the answer, and all three are watched: the box\n * resizing (`ResizeObserver`), the text itself changing, and the measured\n * ELEMENT changing. None of them implies the others — new text inside a\n * width-constrained box leaves the box exactly as it was, so the observer never\n * fires; and a caller that swaps between render branches (adornment vs plain)\n * mounts a different node under the same text, which a `useRef` would leave\n * measured against the old, detached node. Pass the rendered value as `value`.\n *\n * `ref` is a CALLBACK ref, not a `RefObject` — that is what makes the element\n * itself a dependency. Attach it exactly like a normal ref; there is no\n * `.current` to read.\n *\n * ```tsx\n * const { ref, isTruncated } = useIsTruncated<HTMLHeadingElement>(title)\n * <FloatingTooltip content={title} disabled={!isTruncated}>\n * <h1 ref={ref} className=\"truncate\">{title}</h1>\n * </FloatingTooltip>\n * ```\n */\nexport function useIsTruncated<T extends HTMLElement>(value: unknown, options?: UseIsTruncatedOptions) {\n const multiline = options?.multiline ?? false;\n const [element, setElement] = useState<T | null>(null);\n const [isTruncated, setIsTruncated] = useState(false);\n\n // Wrapped so the identity stays stable across renders: passing the raw\n // `useState` setter as a ref would be stable too, but this keeps the public\n // shape a plain `RefCallback<T>` instead of a state dispatcher.\n const ref = useCallback((node: T | null) => setElement(node), []);\n\n useEffect(() => {\n if (!element) return undefined;\n // The 1px tolerance absorbs sub-pixel layout rounding, which otherwise\n // reports a permanent fractional \"overflow\" at some zoom levels / DPRs and\n // leaves a tooltip armed on text that is fully visible.\n const check = () => {\n setIsTruncated(\n multiline ? element.scrollHeight > element.clientHeight + 1 : element.scrollWidth > element.clientWidth + 1,\n );\n };\n check();\n const observer = new ResizeObserver(check);\n observer.observe(element);\n return () => observer.disconnect();\n }, [element, value, multiline]);\n\n // Masked rather than reset from the effect: with no element there is nothing\n // to clip, and deriving that during render avoids a second render pass every\n // time the measured node unmounts.\n return { ref, isTruncated: element ? isTruncated : false };\n}\n","'use client';\n\nimport { useState, useEffect } from 'react';\n\n/**\n * Hook to debounce a value\n * @param value - Value to debounce\n * @param delay - Delay in milliseconds\n * @returns Debounced value\n */\nexport function useDebounce<T>(value: T, delay = 500): T {\n const [debouncedValue, setDebouncedValue] = useState<T>(value);\n\n useEffect(() => {\n // Set up timeout to update debounced value\n const timer = setTimeout(() => {\n setDebouncedValue(value);\n }, delay);\n\n // Clean up timeout on value change or unmount\n return () => {\n clearTimeout(timer);\n };\n }, [value, delay]);\n\n return debouncedValue;\n}\n","'use client';\n\nimport { useEffect, useLayoutEffect } from 'react';\n\n/**\n * `useLayoutEffect` in the browser, `useEffect` on the server.\n *\n * `useLayoutEffect` never runs during server rendering — React cannot measure or\n * mutate a layout that has not been laid out — so calling it in a component that\n * SSRs logs a warning on every render pass. Client components still render on the\n * server in Next.js, so `'use client'` alone does not avoid it.\n *\n * Use this wherever the effect writes something the FIRST paint depends on (a CSS\n * custom property, a scroll position, a measured size). A plain `useEffect` there\n * runs after paint, which shows one frame of the pre-effect value; a plain\n * `useLayoutEffect` gets the timing right and pays for it with the warning. This\n * gets both: paint-blocking where there is a paint, a no-op where there is not.\n */\nexport const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;\n","'use client';\n\nimport { useState } from 'react';\nimport { useIsomorphicLayoutEffect } from './use-isomorphic-layout-effect';\n\n/**\n * Hook to check if a media query matches\n * @param query - Media query to check\n * @returns Whether the media query matches, or undefined during SSR/initial render\n */\nexport function useMediaQuery(query: string): boolean | undefined {\n const [matches, setMatches] = useState<boolean | undefined>(undefined);\n\n useIsomorphicLayoutEffect(() => {\n const matchMedia = window.matchMedia(query);\n\n const handleChange = () => {\n setMatches(matchMedia.matches);\n };\n\n // Set initial value\n handleChange();\n\n matchMedia.addEventListener('change', handleChange);\n\n return () => {\n matchMedia.removeEventListener('change', handleChange);\n };\n }, [query]);\n\n return matches;\n}\n\n/**\n * Predefined breakpoints for common screen sizes\n */\nexport const breakpoints = {\n md: '(min-width: 800px)', // Tablet: 50rem\n lg: '(min-width: 1280px)', // Desktop: 80rem\n};\n\n/** @deprecated Use useMdUp instead */\nexport function useSmUp(): boolean | undefined {\n return useMdUp();\n}\n\nexport function useMdUp(): boolean | undefined {\n const matches = useMediaQuery(breakpoints.md);\n return matches === undefined ? undefined : matches;\n}\n\nexport function useLgUp(): boolean | undefined {\n const matches = useMediaQuery(breakpoints.lg);\n return matches === undefined ? undefined : matches;\n}\n","'use client';\n\nimport { useEffect, useState } from 'react';\n\n/**\n * Returns the combined height (px) of the sticky page header and announcement\n * bar (if present), updated live via ResizeObserver / MutationObserver.\n * Useful for offsetting fixed/absolute-positioned panels so they don't\n * overlap the header.\n */\nexport function useHeaderHeight(\n // First-paint fallback = the unified top-navigation `big` bar (72px), the\n // size every hub/marketing site renders; replaced by the live measurement.\n defaultHeight = 72,\n options: {\n /**\n * When false, no observers are attached and the default height is\n * returned — for consumers that stay mounted on every page but only need\n * the measurement while an overlay is open (avoids an always-on\n * document-wide MutationObserver + forced reflow).\n * @default true\n */\n enabled?: boolean;\n } = {},\n): number {\n const { enabled = true } = options;\n const [height, setHeight] = useState(defaultHeight);\n\n useEffect(() => {\n if (!enabled) return undefined;\n\n const measure = () => {\n let total = 0;\n const header = document.querySelector('header');\n if (header) total += header.offsetHeight;\n const bar = document.querySelector('[data-announcement-bar]');\n if (bar instanceof HTMLElement) total += bar.offsetHeight;\n setHeight(total > 0 ? total : defaultHeight);\n };\n\n measure();\n\n const resizeObserver = new ResizeObserver(measure);\n const header = document.querySelector('header');\n if (header) resizeObserver.observe(header);\n const bar = document.querySelector('[data-announcement-bar]');\n if (bar) resizeObserver.observe(bar);\n\n const mutationObserver = new MutationObserver(mutations => {\n for (const mutation of mutations) {\n if (mutation.type === 'childList' || mutation.type === 'attributes') {\n measure();\n const newBar = document.querySelector('[data-announcement-bar]');\n if (newBar) resizeObserver.observe(newBar);\n }\n }\n });\n mutationObserver.observe(document.body, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: ['data-announcement-bar'],\n });\n\n return () => {\n resizeObserver.disconnect();\n mutationObserver.disconnect();\n };\n }, [defaultHeight, enabled]);\n\n return height;\n}\n","'use client';\n\nimport { useCallback, useEffect, useRef, type MutableRefObject } from 'react';\n\n/**\n * THE marquee animation core (single source of truth) — extracted verbatim\n * from <CardsStrip> so every endless-scroll surface (card strips, logo strips,\n * quick-action walls, chip marquees) shares ONE rAF position engine (\"unify\n * means delete variations\").\n *\n * The engine is axis- and driver-agnostic: it owns a FLOAT position ref, a\n * velocity envelope, seam wrapping, and an optional chevron-style glide; the\n * caller decides what a position \"means\" via `apply` — writing\n * `scroller.scrollLeft` (CardsStrip: a real scroller the user can also drag)\n * or a `translate` transform (MarqueeWall: decorative tracks, incl. vertical —\n * mandatory inside surfaces that forbid inner scrollers, e.g. the deck's\n * mobile single-fold panels).\n *\n * Engine semantics (do not \"simplify\"):\n * - Position lives in a FLOAT ref, never read-modify-write from the DOM:\n * scrollLeft reads back rounded values, so sub-pixel increments\n * (speed/fps < 1px) get eaten by rounding — stutter + measurably slower\n * than the configured speed.\n * - Velocity envelope (~250ms time constant) eases between 0 and `speed`\n * instead of binary stop/start — pause decelerates, resume accelerates from\n * the current position (GSAP-marquee behavior; hard cuts read as jank).\n * - Glide consumes a signed remaining-distance, wrapping modulo the copy size\n * each frame, so seam warps are invisible to it (browser smooth-scroll\n * animates toward an ABSOLUTE target and lunges after a warp).\n * - `isPaused` is evaluated every frame — the caller owns the pause-reason\n * set (hover, viewport, tab visibility, suppress windows); the engine only\n * turns it into a smooth velocity target.\n */\nexport interface MarqueeEngineOptions {\n /** Master switch — the rAF runs only while true. On (re)activation the\n * position resyncs from `readBack` when provided. */\n active: boolean;\n /** Cruise speed in px/s. */\n speed: number;\n /** Per-frame pause verdict (caller-owned reason set). `now` is the rAF\n * timestamp, for suppress-until comparisons. */\n isPaused: (now: number) => boolean;\n /** Single-copy size in px (the wrap modulus). Return <= 0 to disable\n * wrapping (position advances unwrapped — callers should size tracks so\n * this never happens while active). */\n getWrapSize: () => number;\n /** Lower bound of the wrap range: positions stay in\n * [wrapMin, wrapMin + wrapSize). Default 0. Scroller drivers with USER\n * scrolling pass their seam-warp buffer here so engine-written positions\n * never enter the re-center zones — an exact-edge warp check is racy\n * against the rAF (the engine nudges the scroller off the edge before the\n * scroll handler samples it → \"stuck at the left edge\"). */\n getWrapMin?: () => number;\n /** Write the position to the DOM (scrollLeft assignment or transform). */\n apply: (pos: number) => void;\n /** Read the externally-mutated position back (real scrollers: user drag /\n * native smooth scroll move scrollLeft under the engine). Omit for\n * transform drivers — nothing external can move them. */\n readBack?: () => number;\n /** Called at the end of every frame (e.g. hover re-resolution after the\n * track moved under a stationary pointer). */\n onAfterFrame?: () => void;\n}\n\nexport interface MarqueeEngineHandle {\n /** The engine's float position (px along the axis, 0 ≤ pos < wrap while\n * wrapping). Callers may write it together with their own DOM write (seam\n * warp on manual scroll). */\n posRef: MutableRefObject<number>;\n /** Accumulate a signed glide distance (chevron navigation). Runs through\n * the engine's own tween — never native smooth scroll while wrapping. */\n glideBy: (px: number) => void;\n /** Wrap an arbitrary position into [0, wrapSize). */\n wrap: (pos: number) => number;\n}\n\n// Resync threshold (px): external movement beyond this adopts the DOM value.\nconst RESYNC_EPSILON = 1.5;\n\nexport function useMarqueeEngine(options: MarqueeEngineOptions): MarqueeEngineHandle {\n const posRef = useRef(0);\n const glideRemainingRef = useRef(0);\n const speedEnvRef = useRef(0);\n\n // Latest-options ref: the rAF loop always reads current values without the\n // effect restarting on every render. Refreshed in an unconditional effect,\n // declared before every effect that starts the loop, rather than in the\n // render body — a render attempt React discards must not be able to hand the\n // loop options that were never committed.\n const optsRef = useRef(options);\n useEffect(() => {\n optsRef.current = options;\n });\n\n const wrap = useCallback((pos: number) => {\n const half = optsRef.current.getWrapSize();\n if (half <= 0) return pos;\n const min = optsRef.current.getWrapMin?.() ?? 0;\n let wrapped = pos;\n while (wrapped >= min + half) wrapped -= half;\n while (wrapped < min) wrapped += half;\n return wrapped;\n }, []);\n\n const glideBy = useCallback((px: number) => {\n glideRemainingRef.current += px;\n }, []);\n\n const active = options.active;\n useEffect(() => {\n if (!active) return undefined;\n let raf = 0;\n let last = performance.now();\n const readBack = optsRef.current.readBack;\n if (readBack) posRef.current = readBack();\n\n const tick = (now: number) => {\n const opts = optsRef.current;\n const dt = Math.min(now - last, 100) / 1000; // clamp tab-wake jumps\n last = now;\n\n const paused = opts.isPaused(now);\n // Ease the velocity toward its target (0 when paused) — smooth decel on\n // pause, smooth accel on resume, always from the CURRENT position.\n const targetSpeed = paused ? 0 : opts.speed;\n speedEnvRef.current += (targetSpeed - speedEnvRef.current) * Math.min(1, dt / 0.25);\n if (targetSpeed === 0 && speedEnvRef.current < 0.5) speedEnvRef.current = 0;\n\n const external = opts.readBack ? opts.readBack() : null;\n const resync = () => {\n if (external !== null && Math.abs(external - posRef.current) > RESYNC_EPSILON) {\n posRef.current = external;\n }\n };\n\n const glide = glideRemainingRef.current;\n if (glide !== 0) {\n // Ease-out toward the glide target; runs even while \"paused\" (the\n // interaction that queued it sets its own suppress window).\n resync();\n const speed = Math.max(Math.abs(glide) * 6, 240); // px/s, proportional\n const step = Math.sign(glide) * Math.min(Math.abs(glide), speed * dt);\n glideRemainingRef.current = Math.abs(glide - step) < 0.5 ? 0 : glide - step;\n posRef.current = wrap(posRef.current + step);\n opts.apply(posRef.current);\n } else if (speedEnvRef.current > 0) {\n // Resync after external movement (user scroll, seam warp).\n resync();\n posRef.current = wrap(posRef.current + speedEnvRef.current * dt);\n opts.apply(posRef.current);\n } else if (external !== null) {\n // Idle: follow external movement so resumes start from where the\n // user left the scroller.\n posRef.current = external;\n }\n opts.onAfterFrame?.();\n raf = requestAnimationFrame(tick);\n };\n raf = requestAnimationFrame(tick);\n return () => cancelAnimationFrame(raf);\n }, [active, wrap]);\n\n return { posRef, glideBy, wrap };\n}\n","import { useEffect } from 'react';\n\n/**\n * Runs `reset` whenever the tab is hidden or the window loses focus — exactly the\n * moments a `pointerleave` / `pointercancel` can be SWALLOWED (tab blur, an\n * overlay mounting under the cursor, an interrupted pointer), which otherwise\n * leaves a hover/\"inside\" flag stuck `true` and freezes a marquee FOREVER. The\n * next real pointer/hover re-arms the flag.\n *\n * SSOT self-heal for every marquee surface (MarqueeWall, CardsStrip) so the\n * blur + `visibilitychange` wiring lives in one place. `reset` almost always\n * only UN-sticks state. The one edge: on `blur` WITHOUT the tab hiding (a second\n * window focused beside this one), clearing the hover-pause can let the marquee\n * resume under a still-stationary cursor in the now-unfocused window — it\n * re-pauses on the next `pointermove`/`pointerenter`. We keep the `blur` reset\n * anyway because it's the only signal that catches a tab-blur that swallows the\n * `pointerleave` without a `visibilitychange`. Pass a STABLE `reset` (memoize\n * with `useCallback`) — the listeners re-attach whenever its identity changes.\n */\nexport function useResetOnPageHidden(reset: () => void): void {\n useEffect(() => {\n const onVisibility = () => {\n if (document.visibilityState === 'hidden') reset();\n };\n window.addEventListener('blur', reset);\n document.addEventListener('visibilitychange', onVisibility);\n return () => {\n window.removeEventListener('blur', reset);\n document.removeEventListener('visibilitychange', onVisibility);\n };\n }, [reset]);\n}\n","'use client';\n\nimport { useEffect, useRef } from 'react';\n\n// Every focusable a clone might contain — matched so it can be pulled out of the\n// keyboard tab order (see useSuppressCloneFocus). `[contenteditable]:not(\n// [contenteditable=\"false\"])` catches the empty/`\"true\"`/`\"plaintext-only\"`\n// editable forms; `summary` is focusable inside a <details>.\nconst CLONE_FOCUSABLE_SELECTOR =\n 'a[href],button,input,select,textarea,iframe,summary,audio[controls],video[controls],[tabindex],[contenteditable]:not([contenteditable=\"false\"])';\n\n/**\n * Keep a marquee clone's content pointer-CLICKABLE while taking it out of the\n * accessibility tree and the keyboard tab order. Shared by every endless-loop\n * surface that renders a second track copy (<CardsStrip> managed cells,\n * <MarqueeWall> clone copies).\n *\n * `inert` (the previous approach) does all three at once: a11y-hidden,\n * un-focusable AND un-clickable. That third effect was the \"news marquee stops\n * being clickable after you scroll it for a while\" bug — the 2-copy endless\n * loop ALWAYS paints clone cards inside the viewport near the copy seam, so a\n * user who manually scrolls and parks there is looking at real-looking cards\n * that silently swallow every click. Clones must therefore stay clickable (a\n * click on ANY visible card must open its link — mouse, ⌘-click, middle-click\n * and context-menu all handled natively by the clone's own anchor).\n *\n * The a11y contract is still met without `inert`: the caller keeps\n * `aria-hidden` on the clone wrapper (out of the a11y tree) and this hook\n * forces every focusable descendant to `tabindex=\"-1\"` (out of the tab order →\n * no duplicate tab stops, no aria-hidden-focus violation). Re-applied on\n * subtree mutation. This is the standard loop-clone treatment used by\n * Swiper/Splide.\n */\nexport function useSuppressCloneFocus(active: boolean) {\n const ref = useRef<HTMLDivElement | null>(null);\n useEffect(() => {\n const root = ref.current;\n if (!root || !active) return undefined;\n const suppress = () => {\n root.querySelectorAll<HTMLElement>(CLONE_FOCUSABLE_SELECTOR).forEach(el => {\n // Only stamp the ones not already stamped — avoids touching the DOM (and\n // clobbering an intentional tabindex) on every mutation.\n if (el.getAttribute('tabindex') !== '-1') el.setAttribute('tabindex', '-1');\n });\n };\n suppress();\n // Watch structure AND the attributes that can turn a descendant tabbable\n // (an anchor gaining `href`, React restoring `tabindex=\"0\"`, an element\n // becoming editable, media gaining `controls`). The `!== '-1'` guard above\n // makes our own tabindex writes a no-op, so the observer never loops.\n const mo = new MutationObserver(suppress);\n mo.observe(root, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: ['href', 'tabindex', 'contenteditable', 'controls'],\n });\n return () => mo.disconnect();\n }, [active]);\n return ref;\n}\n","/**\n * Centralized Tool Types\n *\n * Single source of truth for all tool-related types across the entire platform.\n * Used by ToolBadge, ToolIcon, and any component that needs tool type information.\n */\n\nexport const ToolTypeValues = {\n FLEET_MDM: 'FLEET_MDM',\n MESHCENTRAL: 'MESHCENTRAL',\n AUTHENTIK: 'AUTHENTIK',\n OPENFRAME: 'OPENFRAME',\n OPENFRAME_CHAT: 'OPENFRAME_CHAT',\n OPENFRAME_CLIENT: 'OPENFRAME_CLIENT',\n OPENFRAME_RMM: 'OPENFRAME_RMM',\n OSQUERY: 'OSQUERY',\n SYSTEM: 'SYSTEM',\n // Directory tenants (Tenant Management): the same identifiers the gateway's\n // DirectoryProvider enum uses, so a directory sync's log rows resolve here.\n MICROSOFT_365: 'MICROSOFT_365',\n GOOGLE_WORKSPACE: 'GOOGLE_WORKSPACE',\n} as const;\n\nexport type ToolType = (typeof ToolTypeValues)[keyof typeof ToolTypeValues];\n\n/**\n * Maps tool types to display labels\n */\nexport const toolLabels: Record<ToolType, string> = {\n FLEET_MDM: 'Fleet',\n MESHCENTRAL: 'MeshCentral',\n AUTHENTIK: 'Authentik',\n OPENFRAME: 'OpenFrame',\n OPENFRAME_CHAT: 'OpenFrame Chat',\n OPENFRAME_CLIENT: 'OpenFrame Client',\n OPENFRAME_RMM: 'RMM',\n OSQUERY: 'Osquery',\n SYSTEM: 'System',\n MICROSOFT_365: 'Microsoft 365',\n GOOGLE_WORKSPACE: 'Google Workspace',\n};\n","import type { FC, ReactNode } from 'react';\nimport { type ToolType, ToolTypeValues } from '../types/tool.types';\nimport {\n OsqueryLogoGreyIcon,\n MeshcentralLogoGreyIcon,\n FleetMdmLogoGreyIcon,\n AuthentikLogoGreyIcon,\n Office365LogoGreyIcon,\n GoogleIcon,\n OpenframeLogoGreyIcon,\n} from './icons-v2-generated';\n\ntype ToolIconRenderer = (size: number, className?: string) => ReactNode;\n\n// Every mark is the grey (`currentColor`) cut of the brand logo, the OpenFrame one included, so a\n// row of tool icons reads as one set and takes the text colour of wherever it sits.\nconst renderOpenFrameLogo: ToolIconRenderer = (size, className) => (\n <OpenframeLogoGreyIcon size={size} className={className} />\n);\n\nconst toolIconMap: Record<ToolType, ToolIconRenderer> = {\n [ToolTypeValues.FLEET_MDM]: (size, className) => <FleetMdmLogoGreyIcon size={size} className={className} />,\n [ToolTypeValues.MESHCENTRAL]: (size, className) => <MeshcentralLogoGreyIcon size={size} className={className} />,\n [ToolTypeValues.OPENFRAME]: renderOpenFrameLogo,\n [ToolTypeValues.OPENFRAME_CHAT]: renderOpenFrameLogo,\n [ToolTypeValues.OPENFRAME_CLIENT]: renderOpenFrameLogo,\n [ToolTypeValues.OPENFRAME_RMM]: renderOpenFrameLogo,\n [ToolTypeValues.AUTHENTIK]: (size, className) => <AuthentikLogoGreyIcon size={size} className={className} />,\n [ToolTypeValues.OSQUERY]: (size, className) => <OsqueryLogoGreyIcon size={size} className={className} />,\n [ToolTypeValues.SYSTEM]: () => null,\n // The Microsoft 365 tool wears the Office 365 mark, not the Microsoft company logo: the product's\n // own brand is what the logs and the directory pickers name.\n [ToolTypeValues.MICROSOFT_365]: (size, className) => <Office365LogoGreyIcon size={size} className={className} />,\n [ToolTypeValues.GOOGLE_WORKSPACE]: (size, className) => <GoogleIcon size={size} className={className} />,\n};\n\nexport interface ToolIconProps {\n toolType: ToolType;\n size?: number;\n className?: string;\n}\n\nexport const ToolIcon: FC<ToolIconProps> = ({ toolType, size = 16, className }) => (\n <>{toolIconMap[toolType]?.(size, className) ?? null}</>\n);\n\nToolIcon.displayName = 'ToolIcon';\n","import { clsx, type ClassValue } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\n/**\n * Utility functions for common operations\n */\n\n/**\n * Merge class names with Tailwind CSS\n * @param inputs - Class names to merge\n * @returns Merged class names\n */\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n\n/**\n * Human-readable message for a caught value.\n *\n * `catch (e)` binds `unknown` under this tsconfig, and a thrown value is not\n * guaranteed to be an `Error`. Use this instead of `catch (e: any)` +\n * `e.message`, which crashes on a thrown string/object with no `message`.\n *\n * @param error - The caught value\n * @param fallback - Returned when the value carries no usable message\n */\nexport function errorMessage(error: unknown, fallback = 'Something went wrong'): string {\n if (error instanceof Error && error.message) return error.message;\n if (typeof error === 'string' && error) return error;\n if (typeof error === 'object' && error !== null) {\n const message = (error as { message?: unknown }).message;\n if (typeof message === 'string' && message) return message;\n }\n return fallback;\n}\n\n/**\n * Human-readable message for a FAILED API response body.\n *\n * `Response.json()` is typed `any`, so `body.error` used to be read off an\n * `any` at every call site. The platform's route helpers answer errors with\n * `{ error, code, details }` (see `errorResponse` in the hub's\n * `lib/api/route-base.ts`); a few endpoints answer `{ message }` instead, so\n * both are accepted before giving up.\n *\n * @param payload - The parsed response body (or whatever came back)\n * @param fallback - Returned when the body carries no usable message\n */\nexport function apiErrorMessage(payload: unknown, fallback: string): string {\n if (typeof payload === 'object' && payload !== null && 'error' in payload) {\n const { error } = payload;\n if (typeof error === 'string' && error) return error;\n }\n return errorMessage(payload, fallback);\n}\n\n/**\n * Delay execution for a specified time\n * @param ms - Milliseconds to delay\n * @returns Promise that resolves after the delay\n */\nexport function delay(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n\n/**\n * Generate a random string of specified length\n * @param length - Length of the string\n * @returns Random string\n */\nexport function generateRandomString(length = 8): string {\n const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';\n let result = '';\n for (let i = 0; i < length; i++) {\n result += chars.charAt(Math.floor(Math.random() * chars.length));\n }\n return result;\n}\n\n/**\n * Truncate a string to a specified length\n * @param str - String to truncate\n * @param maxLength - Maximum length\n * @param suffix - Suffix to add to truncated string\n * @returns Truncated string\n */\nexport function truncateString(str: string, maxLength: number, suffix = '...'): string {\n if (str.length <= maxLength) return str;\n return str.substring(0, maxLength - suffix.length) + suffix;\n}\n\n/**\n * Serialize a JSON-LD schema for a `<script type=\"application/ld+json\">`\n * block — THE one home of the escape rule (the hub re-exports this from\n * lib/utils/breadcrumbs). JSON.stringify does NOT HTML-escape: a stored\n * \"</script>\" inside any admin/user-entered field would terminate the tag\n * early (stored XSS). Every \"<\" becomes the JSON escape sequence\n * backslash-u003c — still valid JSON, inert in HTML.\n */\nexport function serializeJsonLd(schema: unknown): string {\n return JSON.stringify(schema).replace(/</g, '\\\\u003c');\n}\n\n/**\n * Deep clone an object through a JSON round-trip.\n *\n * Lossy by construction, and deliberately so — this is the JSON projection of\n * `T`: `undefined` members and functions are dropped, `Date` becomes an ISO\n * string, `Map`/`Set` become `{}`, and a cyclic graph throws. Reach for\n * `structuredClone` when any of that matters.\n *\n * `JSON.parse` is typed `any`; the assertion states the contract above, which\n * is the closest a JSON clone can get to \"still a `T`\".\n *\n * @param obj - Object to clone (must be JSON-serializable)\n * @returns Cloned object\n */\nexport function deepClone<T>(obj: T): T {\n return JSON.parse(JSON.stringify(obj)) as T;\n}\n\n/**\n * Get the Slack community join URL from environment variables\n * @returns Slack community join URL or fallback URL\n */\nexport function getSlackCommunityJoinUrl(): string {\n const url = process.env.NEXT_PUBLIC_SLACK_COMMUNITY_JOIN_URL;\n if (!url) {\n console.warn('NEXT_PUBLIC_SLACK_COMMUNITY_JOIN_URL is not defined in environment variables');\n return 'https://join.slack.com/t/openmsp/shared_invite/zt-36bl7mx0h-3~U2nFH6nqHqoTPXMaHEHA';\n }\n return url;\n}\n\n/**\n * Clamp a number into an inclusive range. THE one clamp for the lib and the hub\n * (step/page/contrast math all bottom out here).\n */\nexport function clamp(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), max);\n}\n\n/**\n * Typed key-subset of an object. Keys whose value is `undefined` on the source\n * are still copied when present, so `pick(params, dropped)` writes explicit\n * `null`s (the URL-state writer's \"remove this key\" signal).\n */\nexport function pick<T extends object, K extends keyof T>(source: T, keys: readonly K[]): Pick<T, K> {\n const out = {} as Pick<T, K>;\n for (const key of keys) {\n if (key in source) out[key] = source[key];\n }\n return out;\n}\n\n/**\n * React Query options for data that MUST NOT be served from the client cache.\n *\n * The rule (hub memory `feedback_no_client_query_cache_for_mutable_entities`):\n * anything a user can mutate, or a cron can rewrite under them, is refetched on\n * mount. Spread this instead of retyping `staleTime: 0` — it is also the hub\n * QueryClient's default, so a hook that spreads it is explicit rather than\n * silently inheriting.\n */\nexport const NO_CLIENT_CACHE = { staleTime: 0 } as const;\n","/**\n * useNearViewport — module-level shared IntersectionObserver in hook form.\n *\n * Single IO instance per `rootMargin` value, shared across every component\n * that mounts the hook. Reduces overhead vs. one IO per component on\n * grid/list pages where many subscribers observe the viewport with the same\n * margin. Promoted from the inline singleton at\n * `multi-platform-hub/components/shared/video-bites-display.tsx:21-43`,\n * which is the only IO pattern in either repo today.\n *\n * Usage:\n * ```tsx\n * function MyCard() {\n * const { ref, isNear } = useNearViewport('500px');\n * return <div ref={ref}>{isNear ? <HeavyChild /> : <Placeholder />}</div>;\n * }\n * ```\n *\n * StrictMode safety: cleanup uses an identity check on the registered\n * callback so React's dev double-mount (mount → cleanup → re-mount) does\n * not drop the second mount's freshly-set subscription. The IO callback\n * also checks `subscribers.get(target)` before invoking so a fire that\n * races with unmount cannot crash on a torn-down component.\n *\n * The hook fires once — on first intersection it sets `isNear=true` and\n * unobserves the element. Callers that need re-observation should\n * unmount and remount (or fork the hook for two-way behavior).\n */\n\nimport { useEffect, useRef, useState, useCallback } from 'react';\n\n// Per-(rootMargin, threshold) IO map. Multiple call sites with different\n// margins/thresholds each get their own singleton observer.\nconst observers = new Map<string, IntersectionObserver>();\nconst subscribers = new WeakMap<Element, () => void>();\n\n// Slack for the explicit ratio check: an element sized to exactly meet the\n// threshold can report a ratio a hair under it (sub-pixel / float rounding), so\n// accept `ratio >= threshold - EPSILON`. With `threshold: 0` this is a no-op\n// (ratio is always ≥ 0 > -EPSILON) — the `isIntersecting` check alone gates.\nconst THRESHOLD_EPSILON = 0.01;\n\n/** Stable map key for an observer config. */\nfunction observerKey(rootMargin: string, threshold: number): string {\n return `${rootMargin}|${threshold}`;\n}\n\nfunction getObserverFor(rootMargin: string, threshold: number): IntersectionObserver {\n const key = observerKey(rootMargin, threshold);\n const existing = observers.get(key);\n if (existing) return existing;\n\n const io = new IntersectionObserver(\n entries => {\n entries.forEach(entry => {\n // Enforce the threshold EXPLICITLY. `isIntersecting` is spec-defined as\n // `intersectionRatio > 0` (any overlap), so the initial `observe()`\n // firing can report `true` at, say, 20% visible even with\n // `threshold: 0.5` — which would prematurely pass the gate. Require both\n // an intersection AND the ratio to actually meet the threshold (minus a\n // small epsilon for the float rounding where the ratio lands a hair\n // under the exact value).\n if (!entry.isIntersecting) return;\n if (entry.intersectionRatio < threshold - THRESHOLD_EPSILON) return;\n // Race-safe: re-read the callback at fire time. A late IO firing\n // after cleanup must not invoke a stale callback.\n const cb = subscribers.get(entry.target);\n if (cb) {\n cb();\n io.unobserve(entry.target);\n subscribers.delete(entry.target);\n }\n });\n },\n { rootMargin, threshold },\n );\n observers.set(key, io);\n return io;\n}\n\n/**\n * Default near-viewport lookahead — the SSOT for \"mount media on approach\".\n * Shared by this hook's default AND the raw two-way IntersectionObservers in\n * `cards-strip.tsx` / `video-bites-strip.tsx` (which need mount+unmount, not\n * this hook's fire-once semantics, but must agree on the distance).\n */\nexport const NEAR_VIEWPORT_ROOT_MARGIN = '500px';\n\nexport interface UseNearViewportResult<T extends Element = HTMLElement> {\n /** Ref to attach to the element you want to gate on visibility. */\n ref: (node: T | null) => void;\n /** Flips to `true` once the element enters within `rootMargin` of the viewport. Never flips back. */\n isNear: boolean;\n}\n\n/**\n * @param rootMargin Margin around the viewport (CSS-style string).\n * '500px' = element starts mounting 500px before scroll-in.\n * '1000px' = a full viewport's worth of lookahead.\n * '0px' = strict on-screen detection.\n * @param threshold Fraction of the element that must be visible before firing\n * (0 = any pixel — the default; 0.5 = at least half on-screen).\n */\nexport function useNearViewport<T extends Element = HTMLElement>(\n rootMargin: string = NEAR_VIEWPORT_ROOT_MARGIN,\n threshold: number = 0,\n): UseNearViewportResult<T> {\n const [isNear, setIsNear] = useState(false);\n const elRef = useRef<T | null>(null);\n const key = observerKey(rootMargin, threshold);\n\n // Subscribe/unsubscribe on element change.\n const ref = useCallback(\n (node: T | null) => {\n const prev = elRef.current;\n\n // Unsubscribe previous, if any. Identity-check the callback so a\n // StrictMode re-mount that has already re-registered keeps its sub.\n if (prev) {\n const stillOurs = subscribers.get(prev);\n if (stillOurs) {\n subscribers.delete(prev);\n observers.get(key)?.unobserve(prev);\n }\n }\n\n elRef.current = node;\n if (!node) return;\n\n const cb = () => setIsNear(true);\n subscribers.set(node, cb);\n getObserverFor(rootMargin, threshold).observe(node);\n },\n [key, rootMargin, threshold],\n );\n\n // Unsubscribe on unmount. Identity check guards the StrictMode race.\n useEffect(() => {\n return () => {\n const el = elRef.current;\n if (!el) return;\n if (subscribers.get(el)) {\n subscribers.delete(el);\n observers.get(key)?.unobserve(el);\n }\n };\n }, [key]);\n\n return { ref, isNear };\n}\n","/**\n * Fenced-code tracker — THE definition of \"am I inside a code fence\".\n * Server-safe: no React, no DOM.\n *\n * Lives in its own module because it is a GENERAL CommonMark fence machine\n * with two unrelated consumers: the heading scanner\n * (`utils/markdown-heading-id`) and the streaming block splitter\n * (`components/ui/markdown/streaming`). It used to live inside\n * `markdown-heading-id`, which meant a streaming renderer imported its fence\n * state machine from a module named after heading slugs. `markdown-heading-id`\n * re-exports these names so the public `utils` surface is unchanged.\n */\n\n/**\n * CommonMark fenced-code opener/closer. Fence indent is capped at 3 spaces\n * (4+ is an indented code block, whose backticks are literal content), and the\n * run length is captured so a closer can be required to be at least as long as\n * its opener. Matched on the RAW line — `trimStart()` would let a fence inside\n * 4-space-indented code toggle the state.\n *\n * `\\r`-TOLERANT BY CONSTRUCTION. The previous spelling ended `(.*)$`, and `.`\n * excludes `\\r` while `$` (no `m` flag) anchors at end of input — so on a CRLF\n * document, split on `\\n`, NO line ever matched and the tracker was completely\n * fence-blind: `['```js\\r','const a = 1\\r','```\\r','after\\r']` classified as\n * four `text` lines where the LF spelling gives `open/inside/close/text`. The\n * sanitizer's closer haystack then never blanked the fenced region, so a\n * `</textarea>` written as a CRLF code sample satisfied `hasLaterCloser` and a\n * prose `<textarea>` above it stayed LIVE (reproduced in the DOM; the LF twin\n * of the same fixture escapes correctly). The `\\r` is absorbed by `\\r?$` rather\n * than normalized out of the text, because the sanitizer's mask is\n * LENGTH-PRESERVING and every offset in it is an index into the original\n * string — stripping `\\r` would shift them all.\n *\n * THE ONLY COPY. This constant plus its open/close state machine used to be\n * duplicated verbatim in `components/ui/markdown/streaming.ts`, kept in sync\n * by a code comment — which is exactly how the `~~~`-blind extractor drifted\n * in the first place. Both consumers now instantiate `createFenceTracker()`.\n */\nconst FENCE_RE = /^ {0,3}(`{3,}|~{3,})([^\\n]*?)\\r?$/;\n\n/**\n * CommonMark's BLANK LINE — spaces and tabs only. The single definition, shared\n * by every consumer of this module (the sanitizer's mask passes, the heading\n * scanner, the HTML-block range walk).\n *\n * NOT `String.prototype.trim()`. JS `trim()` strips the full Unicode\n * White_Space set — U+00A0 NBSP, U+000B VT, U+000C FF, U+2028/9, U+FEFF BOM —\n * none of which CommonMark (or remark) treats as blank. Every place that meant\n * \"is this a CommonMark blank line?\" and asked `trim() === ''` therefore\n * TERMINATED a construct one line early while remark kept it open. Verified\n * end-to-end: `<div>\\n\\u00A0\\n\\`<textarea>\\`\\n\\nrest\\n` left a LIVE `<textarea>`\n * in the DOM (`escapeUnknownHtmlTags` returned the input byte-identical),\n * because the tracked HTML-block range ended at the NBSP filler line and the\n * inline-code shelter below it stopped being inside a tracked block. Same for\n * VT / FF / BOM, for `<pre>` / `<details>` wrappers, and for a live third-party\n * `<iframe>`.\n *\n * `\\r` IS accepted: a CRLF document split on `\\n` spells its blank lines\n * `\"\\r\"`, and the mask is length-preserving so stripping `\\r` from the source\n * is not an option (see `FENCE_RE`'s `\\r?$`).\n */\nconst BLANK_LINE_RE = /^[ \\t\\r]*$/;\n\n/** True when `line` is a CommonMark blank line (spaces/tabs only, `\\r`-tolerant). */\nexport function isBlankLine(line: string): boolean {\n return BLANK_LINE_RE.test(line);\n}\n\n/**\n * What one source line is, relative to fenced code:\n * - `open` — this line opened a fence\n * - `close` — this line closed the open fence\n * - `inside`— this line is fence content (including a would-be delimiter that\n * does NOT close the open fence: wrong marker char, too short, or\n * carrying an info string)\n * - `text` — this line is ordinary markdown outside any fence\n *\n * NOTE: no consumer discriminates the three non-`text` roles today — both the\n * heading scanner and the streaming splitter only test `!== 'text'`, and the\n * closer-haystack mask below only tests `=== 'open'` / `=== 'close'`. The\n * four-value union is kept because the state machine already computes the\n * distinction for free and a future consumer (e.g. fence-aware syntax\n * post-processing) would otherwise have to re-derive it.\n */\nexport type FenceLineRole = 'open' | 'close' | 'inside' | 'text';\n\nexport interface FenceTracker {\n /** Advance across one raw source line (mutates) and classify it. */\n push(line: string): FenceLineRole;\n /** Marker CHARACTER of the currently open fence (`` ` `` / `~`), or null. */\n openChar(): string | null;\n /** Run length of the currently open fence (a closer must be ≥ this). */\n openLength(): number;\n}\n\n/** A fresh fenced-code state machine. Server-safe: no React, no DOM. */\nexport function createFenceTracker(): FenceTracker {\n let fenceChar: string | null = null;\n let fenceLength = 0;\n return {\n push(line: string): FenceLineRole {\n const fence = FENCE_RE.exec(line);\n if (fence) {\n const run = fence[1];\n if (fenceChar === null) {\n fenceChar = run[0];\n fenceLength = run.length;\n return 'open';\n }\n // A closer must use the SAME marker char, be at least as long as the\n // opener, and (per CommonMark) carry no info string. \"No info string\"\n // means spaces/tabs only — `trim()` would accept an NBSP tail and close\n // a fence CommonMark keeps open, i.e. blank LESS of the haystack\n // (fail-OPEN). Hence `isBlankLine`, not `trim()`.\n if (run[0] === fenceChar && run.length >= fenceLength && isBlankLine(fence[2])) {\n fenceChar = null;\n fenceLength = 0;\n return 'close';\n }\n return 'inside';\n }\n return fenceChar === null ? 'text' : 'inside';\n },\n openChar: () => fenceChar,\n openLength: () => fenceLength,\n };\n}\n","/**\n * Markdown heading-id slug SSOT (server-safe: no React, no DOM).\n *\n * This algorithm previously existed in THREE byte-identical copies:\n * - `components/ui/simple-markdown-renderer.tsx` (generateHeadingId)\n * - `components/ui/rich-markdown-renderer.tsx` (generateHeadingId)\n * - `utils/markdown-section-extractor.ts` (extractSections)\n *\n * The extractor is the PRODUCER of `sectionIds` and the renderers are the\n * CONSUMERS — if the two ever drift, deep-link anchors and scroll-spy\n * targets silently diverge. All three now call these helpers; the parity\n * test `components/ui/__tests__/markdown-parity.test.tsx` asserts\n * extractor-vs-renderer ID agreement over the fixture corpus.\n */\n\n/** Emoji ranges stripped from heading text before slugification. */\nexport const HEADING_EMOJI_RE = /[\\u{1F300}-\\u{1F9FF}]|[\\u{2600}-\\u{26FF}]|[\\u{2700}-\\u{27BF}]/gu;\n\n/** Remove emoji characters and trim. */\nexport function stripHeadingEmojis(text: string): string {\n return text.replace(HEADING_EMOJI_RE, '').trim();\n}\n\n/**\n * Core slug chain WITHOUT the emoji strip (the extractor exposes emoji\n * stripping as an option, so the two steps are kept separable):\n * lowercase → drop non-word/space/hyphen chars → spaces to hyphens →\n * trim leading/trailing hyphens. May return `''` for symbol-only input —\n * callers apply their own fallback (`section-N`).\n */\nexport function slugifyHeadingBase(text: string): string {\n return text\n .trim()\n .toLowerCase()\n .replace(/[^\\w\\s-]/g, '')\n .replace(/\\s+/g, '-')\n .replace(/^-+|-+$/g, '');\n}\n\n/** The full default chain used by the renderers: emoji strip + slugify. */\nexport function slugifyHeadingText(text: string): string {\n return slugifyHeadingBase(stripHeadingEmojis(text));\n}\n\n// ---------------------------------------------------------------------------\n// Heading SCANNER + DEDUPER (the other half of the producer/consumer contract)\n// ---------------------------------------------------------------------------\n/**\n * Sharing the slug chain alone was not enough. The PRODUCER\n * (`utils/markdown-section-extractor`) and the CONSUMER\n * (`components/ui/markdown/heading-ids`) each carried their own copy of\n * \"which lines are headings\" and \"how do duplicates get suffixed\", and the\n * copies drifted:\n * - the extractor toggled its code-block state on a bare\n * `line.startsWith('```')`, so a `~~~`-fenced (or wider-backtick, or\n * indented) block containing `## Setup` produced a SECTION from the\n * extractor and NO id from the renderer — `sectionIdMap` then missed\n * silently and the deep-link anchor pointed nowhere;\n * - the extractor anchored ATX at column 0 while the renderer allowed the\n * CommonMark 0..3-space indent;\n * - the dedupe counter was hand-copied in both files, each with a comment\n * saying \"the two must agree\".\n * Both now call `scanHeadings` + `createHeadingIdDeduper`. Server-safe: no\n * React, no DOM.\n */\n\n// The CommonMark fence machine used to live in THIS file, which meant a\n// streaming renderer imported its fence state from a module named after\n// heading slugs. It now lives in `./markdown-fences`; re-exported here so the\n// public `utils` surface (and every existing import) is unchanged.\nexport { createFenceTracker, isBlankLine, type FenceTracker, type FenceLineRole } from './markdown-fences';\nimport { createFenceTracker, isBlankLine } from './markdown-fences';\n\n/** A heading the renderer will emit, located in the source. */\nexport interface ScannedHeading {\n /** 1-based line of the heading's FIRST line (setext: the title line). */\n line: number;\n level: number;\n /** Raw title text, before `stripInlineMarkdown` / slugification. */\n text: string;\n}\n\nexport interface ScanHeadingsOptions {\n /**\n * Skip a leading YAML frontmatter block (`---` on line 1 through the next\n * `---`). Only a DOCUMENT-LEADING block counts: a bare `---` mid-document\n * is a thematic break or a setext underline, and treating it as a\n * frontmatter toggle (what the extractor used to do) silently swallowed\n * every heading until the next one.\n */\n skipFrontmatter?: boolean;\n /** Include raw-HTML headings (`<h2>Title</h2>`). Default true. */\n includeRawHtml?: boolean;\n /**\n * Skip fenced-code blocks. Default true — a `##` inside a code fence is\n * code, not a heading, and the renderer emits no id for it.\n */\n skipFences?: boolean;\n}\n\n/**\n * EVERY line-anchored regex in this module ends `\\r?$`, not `$`.\n *\n * Lines here come from `content.split('\\n')`, so on a CRLF document each one\n * carries a trailing `\\r`. `$` (no `m` flag) anchors at end of string and `\\r`\n * is not in `[ \\t]`, so a `$`-anchored pattern matches NOTHING on such a\n * document. That was a live REGRESSION: `main`'s looser `^(#{1,6})\\s+(.+)`\n * had no end anchor at all and tolerated the `\\r` (the title was `.trim()`ed\n * afterwards), so `'# Real\\r\\n\\r\\nbody\\r\\n\\r\\n## Second\\r\\n'` yielded\n * `[\"Real\",\"Second\"]` on `main` and `[]` here — every CRLF-stored doc / blog /\n * release body silently lost its TOC, its in-page anchors and its doc-SEO\n * heading links.\n *\n * The `\\r` is ABSORBED, not normalized away: `scanHeadings` reports 1-based\n * LINE numbers that the renderer's id map is keyed by, and the sanitizer's mask\n * (which shares `markdown-fences`) is length-preserving — neither may be fed a\n * rewritten source.\n */\n/** CommonMark ATX heading (closing `#` run is not part of the title). */\nconst ATX_HEADING_RE = /^ {0,3}(#{1,6})[ \\t]+(.*?)(?:[ \\t]+#+)?[ \\t]*\\r?$/;\n/** ATX with no title at all (`###`, `## ###`) — still a heading, empty text. */\nconst ATX_EMPTY_RE = /^ {0,3}(#{1,6})[ \\t]*#*[ \\t]*\\r?$/;\n/**\n * Raw-HTML heading (`<h2 id=\"x\">Title</h2>`) — rehype-raw renders these too.\n * GLOBAL: two `<h3>`s on one line are two headings, and a non-global scan\n * silently dropped the second (it then fell through to the renderer's\n * suffix-free fallback and emitted a DUPLICATE DOM id).\n */\nconst RAW_HEADING_RE = /<h([1-6])\\b[^>]*>([\\s\\S]*?)(?:<\\/h\\1>|$)/gi;\n/**\n * Blockquote markers and list-item markers preceding an ATX heading\n * (`> ## Setup`, `- ## Setup`, `1. ## Setup`). mdast emits a REAL `<h2>` for\n * these; scanning only column-0..3 missed them entirely, so two identical\n * `> ## Setup` headings both hit the suffix-free fallback and emitted the\n * duplicate DOM ids this whole module exists to prevent.\n */\nconst CONTAINER_PREFIX_RE = /^ {0,3}(?:(?:>[ \\t]?)+|(?:[-+*]|\\d{1,9}[.)])[ \\t]+)+/;\n/** Setext underline (`===` → h1, `---` → h2), AFTER container-prefix strip. */\nconst SETEXT_UNDERLINE_RE = /^ {0,3}(=+|-+)[ \\t]*\\r?$/;\n/** Thematic break — ends a paragraph run, never underlines it. */\nconst THEMATIC_BREAK_RE = /^ {0,3}(\\*|_){3,}[ \\t]*\\r?$/;\n/**\n * CommonMark HTML-block type 6 tag names — these open a raw HTML block no\n * matter what follows them on the line.\n */\nconst HTML_BLOCK_TAGS =\n 'address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h1|h2|h3|h4|h5|h6|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|nav|noframes|ol|optgroup|option|p|param|search|section|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul';\n\n/**\n * A line that OPENS a raw HTML block. mdast hands the whole block to the HTML\n * parser, so a `---` inside it is not a setext underline and the lines above\n * it are not a paragraph: `<div>\\nText\\n---\\n</div>` emits NO heading. The\n * scanner used to publish a phantom `Text` h2 there.\n *\n * The naive `^ {0,3}<` this replaced disqualified any run whose first line\n * merely STARTED with a tag, so `<span>x</span> more text` + `---` (a plain\n * paragraph in mdast, yielding an h2) was missed. The alternatives are spelled\n * out per CommonMark:\n * - types 1-5: `<script|pre|style|textarea`, `<!--`, `<?`, `<!X`, `<![CDATA[`\n * - type 6: a `HTML_BLOCK_TAGS` name, whatever follows it\n * - type 7: ANY complete open/closing tag ALONE on the line (trailing\n * whitespace only) — this is the clause the old regex got wrong\n * Attribute soup inside a type-7 tag is approximated with `[^>]*` rather than\n * a full attribute grammar; the error bias is unchanged (a missed run costs a\n * TOC entry, never a wrong id — the same direction as the documented\n * list-item-indent gap in `SetextRun.prefix`).\n */\nconst HTML_BLOCK_OPENER_RE = new RegExp(\n '^ {0,3}(?:' +\n // types 1-5\n '<(?:script|pre|style|textarea)(?:[\\\\s>]|$)|<!--|<\\\\?|<![a-zA-Z]|<!\\\\[CDATA\\\\[' +\n // type 6\n `|</?(?:${HTML_BLOCK_TAGS})(?:[\\\\s/>]|$)` +\n // type 7 — a complete tag standing alone on the line\n '|<[a-zA-Z][a-zA-Z0-9-]*(?:\\\\s[^>]*)?/?>[ \\\\t]*\\\\r?$' +\n '|</[a-zA-Z][a-zA-Z0-9-]*[ \\\\t]*>[ \\\\t]*\\\\r?$' +\n ')',\n 'i',\n);\n\n/**\n * The paragraph RUN that a setext underline would convert into a heading.\n *\n * mdast positions a setext heading at the FIRST line of the paragraph and its\n * text is the WHOLE run (`Foo\\nbar\\n===` → `{line:1, text:\"Foo\\nbar\"}`). The\n * scanner used to remember only the LAST line, so the renderer's line-keyed\n * lookup missed the heading entirely (falling back to a slug of the rendered\n * children, `foo-bar`) while the extractor published `bar` — a dead deep\n * link, plus a phantom TOC entry at the wrong line.\n */\ninterface SetextRun {\n /** 1-based line of the run's FIRST line — where mdast puts the heading. */\n line: number;\n /**\n * Container prefix (`> `, `- `, …) every line of the run shares. The\n * underline must carry the IDENTICAL prefix, which is why `> Quote` +\n * `> ---` scans as an h2 while `> Quote` + a bare `---` does not.\n *\n * KNOWN, DELIBERATE GAP: a setext underline indented to a LIST ITEM's\n * content column (`- item` / ` ---`) is a heading in mdast but has no\n * container prefix at all, so this equality test misses it. Closing that\n * would mean re-deriving list-item content columns here — the scanner\n * simply emits nothing (the pre-existing behavior), which costs a TOC entry\n * and never produces a WRONG id.\n */\n prefix: string;\n /** Run lines with the container prefix stripped, in order. */\n lines: string[];\n /** Run opened with a raw-HTML block line — mdast emits no heading for it. */\n disqualified: boolean;\n}\n\n/**\n * Collect every heading the markdown renderer will emit, in document order.\n * THE single definition of \"which lines are headings\" for both the extractor\n * and the renderer's id map.\n */\nexport function scanHeadings(content: string, options: ScanHeadingsOptions = {}): ScannedHeading[] {\n const { skipFrontmatter = true, includeRawHtml = true, skipFences = true } = options;\n const out: ScannedHeading[] = [];\n const lines = content.split('\\n');\n // ONE fenced-code state machine (utils/markdown-fences), shared with\n // markdown/streaming.ts.\n const fences = skipFences ? createFenceTracker() : null;\n\n let start = 0;\n if (skipFrontmatter && lines[0]?.trim() === '---') {\n for (let i = 1; i < lines.length; i++) {\n if (lines[i].trim() === '---' || lines[i].trim() === '...') {\n start = i + 1;\n break;\n }\n }\n }\n\n // The paragraph run a setext underline would convert (see SetextRun).\n let setextRun: SetextRun | null = null;\n\n for (let i = start; i < lines.length; i++) {\n const line = lines[i];\n if (fences) {\n const role = fences.push(line);\n if (role !== 'text') {\n setextRun = null;\n continue;\n }\n }\n\n // Container prefix (`> `, `- `, `1. `) is stripped from EVERY construct\n // below, so a blockquote/list-nested heading is scanned in both its ATX\n // (`> ## Setup`) and setext (`> Quote title` / `> ---`) spellings.\n const prefix = CONTAINER_PREFIX_RE.exec(line)?.[0] ?? '';\n const body = prefix ? line.slice(prefix.length) : line;\n\n // --- setext (`Title` / `===`): a REAL h1/h2 in mdast, positioned at the\n // run's FIRST line — which is the line the heading-id map is keyed by.\n // The underline must carry the SAME container prefix as the run it\n // closes; `> Quote` followed by a bare `---` is a quote and a thematic\n // break, not a heading.\n const setext = SETEXT_UNDERLINE_RE.exec(body);\n if (setext) {\n if (setextRun && !setextRun.disqualified && setextRun.prefix === prefix) {\n out.push({\n line: setextRun.line,\n level: setext[1][0] === '=' ? 1 : 2,\n text: setextRun.lines.join('\\n'),\n });\n }\n setextRun = null;\n continue;\n }\n\n // --- ATX, including inside a blockquote / list item.\n const atx = ATX_HEADING_RE.exec(body) ?? ATX_EMPTY_RE.exec(body);\n if (atx) {\n out.push({ line: i + 1, level: atx[1].length, text: (atx[2] ?? '').trim() });\n setextRun = null;\n continue;\n }\n\n if (includeRawHtml && line.indexOf('<') !== -1) {\n RAW_HEADING_RE.lastIndex = 0;\n let raw: RegExpExecArray | null;\n let matched = false;\n while ((raw = RAW_HEADING_RE.exec(line)) !== null) {\n matched = true;\n out.push({\n line: i + 1,\n level: Number(raw[1]),\n text: raw[2].replace(/<[^>]*>/g, '').trim(),\n });\n }\n if (matched) {\n setextRun = null;\n continue;\n }\n }\n\n // Paragraph accumulation. A blank line or a thematic break ends the run;\n // a change of container prefix starts a new block (a list or blockquote\n // interrupts a paragraph in CommonMark).\n // CommonMark's blank line (spaces/tabs), NOT `trim()` — an NBSP-only line\n // is paragraph CONTENT to remark, so ending the run there would move a\n // setext heading's reported line off the run's first line.\n if (isBlankLine(body) || THEMATIC_BREAK_RE.test(body)) {\n setextRun = null;\n continue;\n }\n if (setextRun && setextRun.prefix === prefix) {\n setextRun.lines.push(body.trim());\n } else {\n setextRun = {\n line: i + 1,\n prefix,\n lines: [body.trim()],\n disqualified: HTML_BLOCK_OPENER_RE.test(body),\n };\n }\n }\n\n return out;\n}\n\n/**\n * The duplicate-id suffix counter, as ONE closure both sides instantiate.\n * First occurrence keeps the bare slug; the Nth gets `-N`. Extractor and\n * renderer MUST use the same counter or `#anchor` deep links diverge.\n */\nexport function createHeadingIdDeduper(): (cleanId: string) => string {\n const counts: Record<string, number> = {};\n return (cleanId: string): string => {\n if (counts[cleanId]) {\n counts[cleanId]++;\n return `${cleanId}-${counts[cleanId]}`;\n }\n counts[cleanId] = 1;\n return cleanId;\n };\n}\n","/**\n * GitHub-compatible anchor resolution for in-page `#hash` links.\n *\n * ## The mismatch this exists to bridge\n *\n * Doc authors write their table of contents against GitHub's slugger, which\n * keeps the separator left behind by a stripped emoji:\n *\n * `## 📚 Table of Contents` → GitHub anchor `#-table-of-contents`\n *\n * Our heading ids come from the shared slug SSOT (`markdown-heading-id`),\n * whose `slugifyHeadingBase` trims leading and trailing hyphens — so the same\n * heading renders as `id=\"table-of-contents\"`. The \"On this page\" rail\n * navigates by those ids and works; the in-body TOC link points at an id that\n * does not exist and the browser silently does nothing.\n *\n * Rather than change the id shape (it is already baked into shared URLs and\n * the rail), resolution is made tolerant: compare a *normalized* form of both\n * sides. This module owns ONLY that tolerance — the slug chain itself is\n * imported, never re-implemented, so a change to the id shape lands in one\n * place and both sides move together.\n *\n * Callers do not reach for this directly: `getHashTargetElement`\n * (`utils/same-page-hash-nav`) is the ONE resolver every hash consumer goes\n * through, and it applies this as its last pass.\n */\nimport { slugifyHeadingText } from './markdown-heading-id';\n\n/**\n * Reduce an anchor id (ours or GitHub's) to a comparable form.\n *\n * Everything but the final hyphen-run collapse is the shared heading-slug\n * chain: emoji strip → lowercase → drop non-word chars → spaces to hyphens →\n * trim edge hyphens. The collapse is this module's own addition, covering\n * GitHub's *interior* divergence — `## 💬 Community & Support` slugs to\n * `#-community--support` (a double hyphen around the dropped `&`) against our\n * `community-support`.\n *\n * `'-getting-started'` → `'getting-started'`\n * `'-community--support'` → `'community-support'`\n * `'Getting Started'` → `'getting-started'`\n */\nexport function normalizeAnchorId(raw: string): string {\n return slugifyHeadingText(raw)\n .replace(/-+/g, '-')\n .replace(/^-+|-+$/g, '');\n}\n\n/** Heading elements carrying an id — the candidate set for the fuzzy pass. */\nconst HEADING_SELECTOR = 'h1[id], h2[id], h3[id], h4[id], h5[id], h6[id]';\n\n/** A `base-N` id, split into its base and ordinal. */\nconst DUPLICATE_SUFFIX_RE = /^(.+)-(\\d+)$/;\n\n/**\n * The two sides also disagree on where duplicate-heading numbering STARTS.\n * For a document with two `## Setup` headings GitHub emits `setup` and\n * `setup-1`, while `createHeadingIdDeduper` emits `setup` and `setup-2` (its\n * suffix is the occurrence COUNT, not the offset). So a GitHub `#setup-1`\n * needs our `setup-2` — every duplicate is off by exactly one.\n *\n * Runs LAST, after the exact lookup and the heading scan have both missed: a\n * heading that genuinely slugs to `step-2` is resolved long before this, and\n * the shift is the one pass that can match an id it was not asked for.\n */\nfunction findDuplicateShifted(normalized: string, doc: Document): HTMLElement | null {\n const parts = DUPLICATE_SUFFIX_RE.exec(normalized);\n if (!parts) return null;\n const ordinal = Number(parts[2]);\n if (!Number.isSafeInteger(ordinal)) return null;\n return doc.getElementById(`${parts[1]}-${ordinal + 1}`);\n}\n\n/**\n * Resolve a `#hash` id that no exact lookup matched, by comparing normalized\n * forms. Returns `null` when nothing in the document matches, so the caller\n * can leave the browser's default behavior alone rather than hijacking a\n * click that was never ours to handle.\n *\n * Three passes, in order of how much they assume:\n * 1. a single `getElementById` on the normalized id — catches the whole\n * emoji-hyphen family without walking the DOM;\n * 2. a heading scan — catches ids whose own raw form normalizes differently\n * (e.g. `a---b`, from a title containing a literal ` - `);\n * 3. the duplicate-heading shift (see `findDuplicateShifted`) — last,\n * because it is the only pass that resolves to an id the fragment did\n * not name.\n */\nexport function findAnchorElementByNormalizedId(rawId: string, doc: Document): HTMLElement | null {\n const normalized = normalizeAnchorId(rawId);\n if (!normalized) return null;\n\n if (normalized !== rawId) {\n const direct = doc.getElementById(normalized);\n if (direct) return direct;\n }\n\n for (const heading of Array.from(doc.querySelectorAll<HTMLElement>(HEADING_SELECTOR))) {\n if (normalizeAnchorId(heading.id) === normalized) return heading;\n }\n\n // Last: the shift would happily match an unrelated `x-3` for a `x-2` that\n // has no heading at all, so it runs only once the scan has come up empty.\n return findDuplicateShifted(normalized, doc);\n}\n","/**\n * `scrollElementIntoView` — canonical \"scroll an element to the top of the\n * viewport, account for sticky chrome, survive layout shifts\" helper.\n *\n * One shared implementation so every caller (the ticket drawer expand, the\n * hub's `useUnifiedNav` / `use-nav-link` hash scroll, doc-tree, delivery\n * `?focus=`, sticky-section-nav, …) inherits the SAME cancellation-proof\n * motion.\n *\n * WHY A SELF-DRIVEN rAF TWEEN INSTEAD OF `window.scrollTo({behavior:'smooth'})`:\n * the native smooth scroll is CANCELLABLE, and in real pages it gets cancelled\n * constantly:\n *\n * - Browser SCROLL ANCHORING: when content is inserted/removed above or\n * around the target (a collapsible drawer expanding, an async image\n * loading, a list re-rendering) the browser issues a synchronous scrollTop\n * correction to keep the anchored element stable. Per CSSOM-View \"perform a\n * scroll\" step 1 (\"abort any ongoing smooth scroll\"), that correction\n * ABORTS an in-flight native smooth scroll — so it lands as an instant jump.\n * Anchoring is suppressed when the scroll offset is 0, which is exactly why\n * a native smooth scroll appears to work the FIRST time (page at top) and\n * jumps on every repeat (page already scrolled). This was a multi-day\n * \"smooth only works once\" bug on the /tickets drawer.\n * - A second programmatic scroll on the same frame, or a `focus()` without\n * `{preventScroll:true}`, cancels it the same way.\n *\n * A tween that re-asserts the position with INSTANT writes every frame is\n * immune: there is no \"ongoing native smooth scroll\" for anchoring/focus to\n * abort, and any correction that lands between our frames is overwritten on the\n * next frame. We also RECOMPUTE the target each frame, so an element whose\n * final position is still settling (drawer still expanding, images loading)\n * is tracked to its resting place instead of animating to a stale pixel.\n *\n * Honors `prefers-reduced-motion` (jumps instantly) and cancels on genuine user\n * scroll intent (wheel / touch) so we never fight the user.\n *\n * WINDOW *OR* A SCROLLABLE ANCESTOR: the helper is not hard-wired to the window\n * scroller. It walks up from the target to the nearest ancestor that is an\n * actual scroll container (`overflow-y: auto | scroll | overlay` AND\n * `scrollHeight > clientHeight`) and drives THAT element; only when none exists\n * does it fall back to `window`. This is what makes it work inside app shells\n * that put page content in a fixed-height `<main class=\"overflow-y-auto\">`\n * (e.g. OpenFrame's `AppLayout`) where the document/window never scrolls — the\n * old window-only version was a silent no-op there. Note `overflow: clip` /\n * `hidden` are deliberately NOT treated as scroll containers, so a list wrapper\n * that uses `overflow-clip` only to round its corners still bubbles the scroll\n * up to the real container (matches the `<HelpCenterCard>` list intent).\n */\n\nexport interface ScrollElementIntoViewOptions {\n /** Pixels to subtract from the target element's `top` so it lands BELOW\n * sticky chrome. Defaults to 0. Pass `96` for the standard hub header. */\n headerOffset?: number;\n /** `'smooth'` (default) runs the self-driven tween; `'instant'` / `'auto'`\n * jump in one synchronous write (deep-link land, programmatic focus moves). */\n behavior?: ScrollBehavior;\n /** Optional adjustment applied to the computed pixel target each frame. The\n * callback receives the \"raw\" Y (`element.top + scrollY - headerOffset`) and\n * returns the FINAL target. Use when the caller knows about a layout shift\n * (e.g. a sibling drawer collapsing) the geometry can't yet reflect. */\n adjustTargetY?: (rawTargetY: number) => number;\n /** Tween duration in ms (smooth only). Default 320. */\n durationMs?: number;\n}\n\n/** Module-level handle to the in-flight tween so a new call (or a user\n * gesture) cancels the previous one — only ever one page-scroll animation at\n * a time. */\nlet activeRaf = 0;\nlet teardownActive: (() => void) | null = null;\n\nfunction cancelActiveScroll(): void {\n if (activeRaf) {\n cancelAnimationFrame(activeRaf);\n activeRaf = 0;\n }\n if (teardownActive) {\n teardownActive();\n teardownActive = null;\n }\n}\n\nconst easeOutCubic = (t: number): number => 1 - Math.pow(1 - t, 3);\n\n/** Nearest ancestor that is a *real* scroll container, or `null` when the\n * window/document is the scroller. Only `auto | scroll | overlay` count —\n * `clip` / `hidden` are intentionally excluded (a wrapper using `overflow-clip`\n * purely to round corners must let the scroll bubble to the page).\n *\n * THE one rule for \"what scrolls this element\": `useScrollSpy` resolves the\n * scroller it listens to with it, so the section a spy highlights is the\n * section this helper scrolls to — inside a `<main overflow-y-auto>` shell as\n * much as on a window-scrolled page. */\nexport function getScrollableAncestor(el: HTMLElement): HTMLElement | null {\n for (let node = el.parentElement; node; node = node.parentElement) {\n const overflowY = getComputedStyle(node).overflowY;\n if (\n (overflowY === 'auto' || overflowY === 'scroll' || overflowY === 'overlay') &&\n node.scrollHeight > node.clientHeight\n ) {\n return node;\n }\n }\n return null;\n}\n\n/**\n * Scroll the page so `target` lands at the top of the viewport (below sticky\n * chrome via `headerOffset`). SSR-safe; `null`/`undefined` target is a no-op so\n * callers can pass refs without defensive branching.\n */\nexport function scrollElementIntoView(\n target: HTMLElement | null | undefined,\n options: ScrollElementIntoViewOptions = {},\n): void {\n if (typeof window === 'undefined' || !target) return;\n const { headerOffset = 0, behavior = 'smooth', adjustTargetY, durationMs = 320 } = options;\n\n // Pick the scroller ONCE: a fixed-height `<main overflow-y-auto>` shell scrolls\n // the element, a plain document scrolls the window. The choice can't change\n // mid-tween, so resolve it up front and route every read/write through it.\n const container = getScrollableAncestor(target);\n const readCurrent = (): number => (container ? container.scrollTop : window.scrollY);\n const writeTo = (y: number): void => {\n if (container) container.scrollTop = y;\n else window.scrollTo(0, y);\n };\n\n // Target is recomputed every frame: the row's absolute position can move as\n // the page reflows (a sibling drawer collapsing) and the reachable max grows\n // as the just-opened drawer expands. Clamp to the LIVE max each frame.\n const computeTarget = (): number => {\n const raw = container\n ? container.scrollTop +\n (target.getBoundingClientRect().top - container.getBoundingClientRect().top) -\n headerOffset\n : target.getBoundingClientRect().top + window.scrollY - headerOffset;\n const adjusted = adjustTargetY ? adjustTargetY(raw) : raw;\n const maxScroll = container\n ? Math.max(0, container.scrollHeight - container.clientHeight)\n : Math.max(0, document.documentElement.scrollHeight - window.innerHeight);\n return Math.min(Math.max(0, adjusted), maxScroll);\n };\n\n // Any prior animation loses — one page scroll at a time.\n cancelActiveScroll();\n\n const prefersReduced =\n typeof window.matchMedia === 'function' && window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n\n // Instant paths: a single synchronous write. No tween, no anchoring race.\n if (behavior === 'instant' || behavior === 'auto' || prefersReduced) {\n writeTo(computeTarget());\n return;\n }\n\n // Smooth: self-driven tween with instant per-frame writes (anchoring-proof).\n let startY: number | null = null;\n let startTime = 0;\n\n // Bail the moment the user takes over with a real scroll gesture — we must\n // never fight them. (Not keydown: the ticket composer auto-focuses on open,\n // and typing there should not abort the scroll.)\n const onUserGesture = () => cancelActiveScroll();\n window.addEventListener('wheel', onUserGesture, { passive: true });\n window.addEventListener('touchmove', onUserGesture, { passive: true });\n teardownActive = () => {\n window.removeEventListener('wheel', onUserGesture);\n window.removeEventListener('touchmove', onUserGesture);\n };\n\n const step = (now: number) => {\n if (startY === null) {\n startY = readCurrent();\n startTime = now;\n }\n const targetY = computeTarget();\n const t = Math.min(1, (now - startTime) / durationMs);\n const y = startY + (targetY - startY) * easeOutCubic(t);\n writeTo(y);\n if (t < 1) {\n activeRaf = requestAnimationFrame(step);\n } else {\n // Final exact write in case easing left a sub-pixel gap, then teardown.\n writeTo(computeTarget());\n activeRaf = 0;\n if (teardownActive) {\n teardownActive();\n teardownActive = null;\n }\n }\n };\n activeRaf = requestAnimationFrame(step);\n}\n","import { findAnchorElementByNormalizedId } from './anchor-id';\nimport { scrollElementIntoView } from './scroll-into-view';\n\n/** Pages with a section-nav STRIP on top of the global hub header\n * (dev-center roadmap/delivery/tickets, FAQ category-pill nav).\n * Anchor lands BELOW both layers. */\nexport const STICKY_HEADER_OFFSET_PX = 96;\n\n/** Pages with only the global hub header (docs, blog, vendor detail).\n * Anchor lands BELOW the header bar. */\nexport const HUB_HEADER_OFFSET_PX = 80;\n\n/**\n * Take only the FIRST hash segment from a fragment that may contain extra\n * `#` characters. `'' → ''`, `'#a' → '#a'`, `'#a#b' → '#a'`.\n *\n * No real DOM id contains `#`, so a multi-fragment hash is always a bug at\n * the composer site; `navigateSamePageHash` + `useScrollToHash` both call\n * this so URL bar and `getElementById` stay in sync.\n */\nexport function normalizeHashFragment(hash: string): string {\n if (!hash) return '';\n const second = hash.indexOf('#', 1);\n return second < 0 ? hash : hash.slice(0, second);\n}\n\n/**\n * Resolve the DOM element a hash fragment id points at — THE hash resolver;\n * `navigateSamePageHash`, `useScrollToHash` and the markdown link renderer\n * all go through this one, so a fragment that resolves for a deep link\n * resolves for an in-body click too.\n *\n * Three passes, cheapest first:\n * 1. the raw id (ordinary anchors — the overwhelming common case);\n * 2. the percent-decoded form — href composers like `buildTicketOpenHref`\n * encode the id into the fragment while rows render the raw id, so\n * `#ticket-a%2Fb` must still find `id=\"ticket-a/b\"`. A malformed escape\n * sequence just falls through;\n * 3. a NORMALIZED match against the document's headings — doc bodies are\n * authored on GitHub, whose slugger keeps the hyphen left behind by a\n * stripped emoji (`## 📚 Table of Contents` → `#-table-of-contents`)\n * while our ids trim it. Without this every in-body TOC link in every\n * GitHub-authored doc resolves to nothing and clicking one does nothing.\n * See `utils/anchor-id`.\n */\nexport function getHashTargetElement(id: string): HTMLElement | null {\n if (!id) return null;\n const direct = document.getElementById(id);\n if (direct) return direct;\n\n let decoded = id;\n try {\n decoded = decodeURIComponent(id);\n if (decoded !== id) {\n const decodedEl = document.getElementById(decoded);\n if (decodedEl) return decodedEl;\n }\n } catch {\n // malformed escape — the raw lookup above already covered it\n decoded = id;\n }\n\n return findAnchorElementByNormalizedId(decoded, document);\n}\n\nexport interface NavigateSamePageHashOptions {\n /** Pixels to subtract for sticky chrome. */\n headerOffset?: number;\n /** `'push'` (default) — new history entry; `'replace'` — overwrite\n * current entry (use for TOC-style in-page navigators). */\n history?: 'push' | 'replace';\n}\n\n/**\n * Same-page hash navigation primitive: pushState + synthetic `hashchange`\n * + anchoring-proof smooth scroll. Replaces `router.push` for hash CTAs\n * (Next.js suppresses smooth-scroll during navigation; `router.push` on\n * an exact-URL match is a no-op). Returns `true` when the helper claimed\n * the nav (same pathname + search); `false` for cross-page targets so\n * callers fall through to `router.push`.\n *\n * `target` accepts an origin-stripped path (`/x#anchor`) or a bare hash\n * (`#anchor`); bare-hash callers don't need to reconstruct `pathname +\n * search` themselves.\n */\nexport function navigateSamePageHash(target: string, options: NavigateSamePageHashOptions = {}): boolean {\n if (typeof window === 'undefined') return false;\n const { headerOffset = 0, history: historyMode = 'push' } = options;\n const normalizedTarget = target.startsWith('#') ? window.location.pathname + window.location.search + target : target;\n // `new URL(absoluteUrl, base)` ignores `base` per RFC 3986; an absolute\n // cross-origin target sharing pathname/search would otherwise pass the\n // check below and trip pushState's same-origin enforcement. Parse with\n // an explicit base so malformed inputs cleanly fall through.\n let url: URL;\n try {\n url = new URL(normalizedTarget, window.location.href);\n } catch {\n return false;\n }\n if (\n url.origin !== window.location.origin ||\n url.pathname !== window.location.pathname ||\n url.search !== window.location.search\n ) {\n return false;\n }\n const current = window.location.pathname + window.location.search + window.location.hash;\n // Heal a malformed multi-fragment hash so the URL bar is clean and\n // `getElementById` resolves. Dev-warn fingers the upstream composer.\n const normalizedHash = normalizeHashFragment(url.hash);\n if (process.env.NODE_ENV === 'development' && normalizedHash !== url.hash) {\n console.warn(\n `[navigateSamePageHash] malformed fragment \"${url.hash}\" → normalizing to \"${normalizedHash}\". Fix the upstream composer.`,\n );\n }\n const next = url.pathname + url.search + normalizedHash;\n const id = normalizedHash && normalizedHash !== '#' ? normalizedHash.slice(1) : '';\n // Hash-less targets are only ours on an EXACT URL re-click.\n if (!id && next !== current) return false;\n if (next !== current) {\n const oldURL = window.location.href;\n if (historyMode === 'replace') {\n window.history.replaceState(null, '', next);\n } else {\n window.history.pushState(null, '', next);\n }\n // Synthetic `hashchange` — `pushState` doesn't fire it (HTML spec),\n // so URL-hash-bound listeners (FAQ auto-expand, etc.) wouldn't react.\n window.dispatchEvent(\n new HashChangeEvent('hashchange', {\n oldURL,\n newURL: window.location.href,\n }),\n );\n }\n const el = id ? getHashTargetElement(id) : null;\n if (id && !el && process.env.NODE_ENV === 'development') {\n console.warn(`[navigateSamePageHash] anchor \"#${id}\" not found — scrolling to top.`);\n }\n // Missing anchor → tween to page top. `documentElement` is at 0 by\n // definition, so one tween covers both branches.\n scrollElementIntoView(el ?? document.documentElement, {\n behavior: 'smooth',\n headerOffset,\n });\n return true;\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/hooks/ui/use-is-truncated.ts","../src/hooks/ui/use-debounce.ts","../src/hooks/ui/use-isomorphic-layout-effect.ts","../src/hooks/ui/use-media-query.ts","../src/hooks/ui/use-header-height.ts","../src/hooks/ui/use-marquee-engine.ts","../src/hooks/ui/use-reset-on-page-hidden.ts","../src/hooks/ui/use-suppress-clone-focus.ts","../src/types/tool.types.ts","../src/components/tool-icon.tsx","../src/utils/common.ts","../src/hooks/use-near-viewport.ts","../src/utils/markdown-fences.ts","../src/utils/markdown-heading-id.ts","../src/utils/anchor-id.ts","../src/utils/scroll-into-view.ts","../src/utils/same-page-hash-nav.ts"],"sourcesContent":["'use client';\n\nimport { useCallback, useEffect, useState } from 'react';\n\nexport interface UseIsTruncatedOptions {\n /** Measure vertical clipping (`line-clamp-N`) instead of the single-line ellipsis. */\n multiline?: boolean;\n}\n\n/**\n * Tracks whether an element's text is ACTUALLY clipped — the condition for\n * offering a \"full value\" tooltip. Text that fits needs no tooltip, and showing\n * one anyway just repeats what the user is already reading.\n *\n * Three things can change the answer, and all three are watched: the box\n * resizing (`ResizeObserver`), the text itself changing, and the measured\n * ELEMENT changing. None of them implies the others — new text inside a\n * width-constrained box leaves the box exactly as it was, so the observer never\n * fires; and a caller that swaps between render branches (adornment vs plain)\n * mounts a different node under the same text, which a `useRef` would leave\n * measured against the old, detached node. Pass the rendered value as `value`.\n *\n * `ref` is a CALLBACK ref, not a `RefObject` — that is what makes the element\n * itself a dependency. Attach it exactly like a normal ref; there is no\n * `.current` to read.\n *\n * ```tsx\n * const { ref, isTruncated } = useIsTruncated<HTMLHeadingElement>(title)\n * <FloatingTooltip content={title} disabled={!isTruncated}>\n * <h1 ref={ref} className=\"truncate\">{title}</h1>\n * </FloatingTooltip>\n * ```\n */\nexport function useIsTruncated<T extends HTMLElement>(value: unknown, options?: UseIsTruncatedOptions) {\n const multiline = options?.multiline ?? false;\n const [element, setElement] = useState<T | null>(null);\n const [isTruncated, setIsTruncated] = useState(false);\n\n // Wrapped so the identity stays stable across renders: passing the raw\n // `useState` setter as a ref would be stable too, but this keeps the public\n // shape a plain `RefCallback<T>` instead of a state dispatcher.\n const ref = useCallback((node: T | null) => setElement(node), []);\n\n useEffect(() => {\n if (!element) return undefined;\n // The 1px tolerance absorbs sub-pixel layout rounding, which otherwise\n // reports a permanent fractional \"overflow\" at some zoom levels / DPRs and\n // leaves a tooltip armed on text that is fully visible.\n const check = () => {\n setIsTruncated(\n multiline ? element.scrollHeight > element.clientHeight + 1 : element.scrollWidth > element.clientWidth + 1,\n );\n };\n check();\n const observer = new ResizeObserver(check);\n observer.observe(element);\n return () => observer.disconnect();\n }, [element, value, multiline]);\n\n // Masked rather than reset from the effect: with no element there is nothing\n // to clip, and deriving that during render avoids a second render pass every\n // time the measured node unmounts.\n return { ref, isTruncated: element ? isTruncated : false };\n}\n","'use client';\n\nimport { useState, useEffect } from 'react';\n\n/**\n * Hook to debounce a value\n * @param value - Value to debounce\n * @param delay - Delay in milliseconds\n * @returns Debounced value\n */\nexport function useDebounce<T>(value: T, delay = 500): T {\n const [debouncedValue, setDebouncedValue] = useState<T>(value);\n\n useEffect(() => {\n // Set up timeout to update debounced value\n const timer = setTimeout(() => {\n setDebouncedValue(value);\n }, delay);\n\n // Clean up timeout on value change or unmount\n return () => {\n clearTimeout(timer);\n };\n }, [value, delay]);\n\n return debouncedValue;\n}\n","'use client';\n\nimport { useEffect, useLayoutEffect } from 'react';\n\n/**\n * `useLayoutEffect` in the browser, `useEffect` on the server.\n *\n * `useLayoutEffect` never runs during server rendering — React cannot measure or\n * mutate a layout that has not been laid out — so calling it in a component that\n * SSRs logs a warning on every render pass. Client components still render on the\n * server in Next.js, so `'use client'` alone does not avoid it.\n *\n * Use this wherever the effect writes something the FIRST paint depends on (a CSS\n * custom property, a scroll position, a measured size). A plain `useEffect` there\n * runs after paint, which shows one frame of the pre-effect value; a plain\n * `useLayoutEffect` gets the timing right and pays for it with the warning. This\n * gets both: paint-blocking where there is a paint, a no-op where there is not.\n */\nexport const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;\n","'use client';\n\nimport { useState } from 'react';\nimport { useIsomorphicLayoutEffect } from './use-isomorphic-layout-effect';\n\n/**\n * Hook to check if a media query matches\n * @param query - Media query to check\n * @returns Whether the media query matches, or undefined during SSR/initial render\n */\nexport function useMediaQuery(query: string): boolean | undefined {\n const [matches, setMatches] = useState<boolean | undefined>(undefined);\n\n useIsomorphicLayoutEffect(() => {\n const matchMedia = window.matchMedia(query);\n\n const handleChange = () => {\n setMatches(matchMedia.matches);\n };\n\n // Set initial value\n handleChange();\n\n matchMedia.addEventListener('change', handleChange);\n\n return () => {\n matchMedia.removeEventListener('change', handleChange);\n };\n }, [query]);\n\n return matches;\n}\n\n/**\n * Predefined breakpoints for common screen sizes\n */\nexport const breakpoints = {\n md: '(min-width: 800px)', // Tablet: 50rem\n lg: '(min-width: 1280px)', // Desktop: 80rem\n};\n\n/** @deprecated Use useMdUp instead */\nexport function useSmUp(): boolean | undefined {\n return useMdUp();\n}\n\nexport function useMdUp(): boolean | undefined {\n const matches = useMediaQuery(breakpoints.md);\n return matches === undefined ? undefined : matches;\n}\n\nexport function useLgUp(): boolean | undefined {\n const matches = useMediaQuery(breakpoints.lg);\n return matches === undefined ? undefined : matches;\n}\n","'use client';\n\nimport { useEffect, useState } from 'react';\n\n/**\n * Returns the combined height (px) of the sticky page header and announcement\n * bar (if present), updated live via ResizeObserver / MutationObserver.\n * Useful for offsetting fixed/absolute-positioned panels so they don't\n * overlap the header.\n */\nexport function useHeaderHeight(\n // First-paint fallback = the unified top-navigation `big` bar (72px), the\n // size every hub/marketing site renders; replaced by the live measurement.\n defaultHeight = 72,\n options: {\n /**\n * When false, no observers are attached and the default height is\n * returned — for consumers that stay mounted on every page but only need\n * the measurement while an overlay is open (avoids an always-on\n * document-wide MutationObserver + forced reflow).\n * @default true\n */\n enabled?: boolean;\n } = {},\n): number {\n const { enabled = true } = options;\n const [height, setHeight] = useState(defaultHeight);\n\n useEffect(() => {\n if (!enabled) return undefined;\n\n const measure = () => {\n let total = 0;\n const header = document.querySelector('header');\n if (header) total += header.offsetHeight;\n const bar = document.querySelector('[data-announcement-bar]');\n if (bar instanceof HTMLElement) total += bar.offsetHeight;\n setHeight(total > 0 ? total : defaultHeight);\n };\n\n measure();\n\n const resizeObserver = new ResizeObserver(measure);\n const header = document.querySelector('header');\n if (header) resizeObserver.observe(header);\n const bar = document.querySelector('[data-announcement-bar]');\n if (bar) resizeObserver.observe(bar);\n\n const mutationObserver = new MutationObserver(mutations => {\n for (const mutation of mutations) {\n if (mutation.type === 'childList' || mutation.type === 'attributes') {\n measure();\n const newBar = document.querySelector('[data-announcement-bar]');\n if (newBar) resizeObserver.observe(newBar);\n }\n }\n });\n mutationObserver.observe(document.body, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: ['data-announcement-bar'],\n });\n\n return () => {\n resizeObserver.disconnect();\n mutationObserver.disconnect();\n };\n }, [defaultHeight, enabled]);\n\n return height;\n}\n","'use client';\n\nimport { useCallback, useEffect, useRef, type MutableRefObject } from 'react';\n\n/**\n * THE marquee animation core (single source of truth) — extracted verbatim\n * from <CardsStrip> so every endless-scroll surface (card strips, logo strips,\n * quick-action walls, chip marquees) shares ONE rAF position engine (\"unify\n * means delete variations\").\n *\n * The engine is axis- and driver-agnostic: it owns a FLOAT position ref, a\n * velocity envelope, seam wrapping, and an optional chevron-style glide; the\n * caller decides what a position \"means\" via `apply` — writing\n * `scroller.scrollLeft` (CardsStrip: a real scroller the user can also drag)\n * or a `translate` transform (MarqueeWall: decorative tracks, incl. vertical —\n * mandatory inside surfaces that forbid inner scrollers, e.g. the deck's\n * mobile single-fold panels).\n *\n * Engine semantics (do not \"simplify\"):\n * - Position lives in a FLOAT ref, never read-modify-write from the DOM:\n * scrollLeft reads back rounded values, so sub-pixel increments\n * (speed/fps < 1px) get eaten by rounding — stutter + measurably slower\n * than the configured speed.\n * - Velocity envelope (~250ms time constant) eases between 0 and `speed`\n * instead of binary stop/start — pause decelerates, resume accelerates from\n * the current position (GSAP-marquee behavior; hard cuts read as jank).\n * - Glide consumes a signed remaining-distance, wrapping modulo the copy size\n * each frame, so seam warps are invisible to it (browser smooth-scroll\n * animates toward an ABSOLUTE target and lunges after a warp).\n * - `isPaused` is evaluated every frame — the caller owns the pause-reason\n * set (hover, viewport, tab visibility, suppress windows); the engine only\n * turns it into a smooth velocity target.\n */\nexport interface MarqueeEngineOptions {\n /** Master switch — the rAF runs only while true. On (re)activation the\n * position resyncs from `readBack` when provided. */\n active: boolean;\n /** Cruise speed in px/s. */\n speed: number;\n /** Per-frame pause verdict (caller-owned reason set). `now` is the rAF\n * timestamp, for suppress-until comparisons. */\n isPaused: (now: number) => boolean;\n /** Single-copy size in px (the wrap modulus). Return <= 0 to disable\n * wrapping (position advances unwrapped — callers should size tracks so\n * this never happens while active). */\n getWrapSize: () => number;\n /** Lower bound of the wrap range: positions stay in\n * [wrapMin, wrapMin + wrapSize). Default 0. Scroller drivers with USER\n * scrolling pass their seam-warp buffer here so engine-written positions\n * never enter the re-center zones — an exact-edge warp check is racy\n * against the rAF (the engine nudges the scroller off the edge before the\n * scroll handler samples it → \"stuck at the left edge\"). */\n getWrapMin?: () => number;\n /** Write the position to the DOM (scrollLeft assignment or transform). */\n apply: (pos: number) => void;\n /** Read the externally-mutated position back (real scrollers: user drag /\n * native smooth scroll move scrollLeft under the engine). Omit for\n * transform drivers — nothing external can move them. */\n readBack?: () => number;\n /** Called at the end of every frame (e.g. hover re-resolution after the\n * track moved under a stationary pointer). */\n onAfterFrame?: () => void;\n}\n\nexport interface MarqueeEngineHandle {\n /** The engine's float position (px along the axis, 0 ≤ pos < wrap while\n * wrapping). Callers may write it together with their own DOM write (seam\n * warp on manual scroll). */\n posRef: MutableRefObject<number>;\n /** Accumulate a signed glide distance (chevron navigation). Runs through\n * the engine's own tween — never native smooth scroll while wrapping. */\n glideBy: (px: number) => void;\n /** Wrap an arbitrary position into [0, wrapSize). */\n wrap: (pos: number) => number;\n}\n\n// Resync threshold (px): external movement beyond this adopts the DOM value.\nconst RESYNC_EPSILON = 1.5;\n\nexport function useMarqueeEngine(options: MarqueeEngineOptions): MarqueeEngineHandle {\n const posRef = useRef(0);\n const glideRemainingRef = useRef(0);\n const speedEnvRef = useRef(0);\n\n // Latest-options ref: the rAF loop always reads current values without the\n // effect restarting on every render. Refreshed in an unconditional effect,\n // declared before every effect that starts the loop, rather than in the\n // render body — a render attempt React discards must not be able to hand the\n // loop options that were never committed.\n const optsRef = useRef(options);\n useEffect(() => {\n optsRef.current = options;\n });\n\n const wrap = useCallback((pos: number) => {\n const half = optsRef.current.getWrapSize();\n if (half <= 0) return pos;\n const min = optsRef.current.getWrapMin?.() ?? 0;\n let wrapped = pos;\n while (wrapped >= min + half) wrapped -= half;\n while (wrapped < min) wrapped += half;\n return wrapped;\n }, []);\n\n const glideBy = useCallback((px: number) => {\n glideRemainingRef.current += px;\n }, []);\n\n const active = options.active;\n useEffect(() => {\n if (!active) return undefined;\n let raf = 0;\n let last = performance.now();\n const readBack = optsRef.current.readBack;\n if (readBack) posRef.current = readBack();\n\n const tick = (now: number) => {\n const opts = optsRef.current;\n const dt = Math.min(now - last, 100) / 1000; // clamp tab-wake jumps\n last = now;\n\n const paused = opts.isPaused(now);\n // Ease the velocity toward its target (0 when paused) — smooth decel on\n // pause, smooth accel on resume, always from the CURRENT position.\n const targetSpeed = paused ? 0 : opts.speed;\n speedEnvRef.current += (targetSpeed - speedEnvRef.current) * Math.min(1, dt / 0.25);\n if (targetSpeed === 0 && speedEnvRef.current < 0.5) speedEnvRef.current = 0;\n\n const external = opts.readBack ? opts.readBack() : null;\n const resync = () => {\n if (external !== null && Math.abs(external - posRef.current) > RESYNC_EPSILON) {\n posRef.current = external;\n }\n };\n\n const glide = glideRemainingRef.current;\n if (glide !== 0) {\n // Ease-out toward the glide target; runs even while \"paused\" (the\n // interaction that queued it sets its own suppress window).\n resync();\n const speed = Math.max(Math.abs(glide) * 6, 240); // px/s, proportional\n const step = Math.sign(glide) * Math.min(Math.abs(glide), speed * dt);\n glideRemainingRef.current = Math.abs(glide - step) < 0.5 ? 0 : glide - step;\n posRef.current = wrap(posRef.current + step);\n opts.apply(posRef.current);\n } else if (speedEnvRef.current > 0) {\n // Resync after external movement (user scroll, seam warp).\n resync();\n posRef.current = wrap(posRef.current + speedEnvRef.current * dt);\n opts.apply(posRef.current);\n } else if (external !== null) {\n // Idle: follow external movement so resumes start from where the\n // user left the scroller.\n posRef.current = external;\n }\n opts.onAfterFrame?.();\n raf = requestAnimationFrame(tick);\n };\n raf = requestAnimationFrame(tick);\n return () => cancelAnimationFrame(raf);\n }, [active, wrap]);\n\n return { posRef, glideBy, wrap };\n}\n","import { useEffect } from 'react';\n\n/**\n * Runs `reset` whenever the tab is hidden or the window loses focus — exactly the\n * moments a `pointerleave` / `pointercancel` can be SWALLOWED (tab blur, an\n * overlay mounting under the cursor, an interrupted pointer), which otherwise\n * leaves a hover/\"inside\" flag stuck `true` and freezes a marquee FOREVER. The\n * next real pointer/hover re-arms the flag.\n *\n * SSOT self-heal for every marquee surface (MarqueeWall, CardsStrip) so the\n * blur + `visibilitychange` wiring lives in one place. `reset` almost always\n * only UN-sticks state. The one edge: on `blur` WITHOUT the tab hiding (a second\n * window focused beside this one), clearing the hover-pause can let the marquee\n * resume under a still-stationary cursor in the now-unfocused window — it\n * re-pauses on the next `pointermove`/`pointerenter`. We keep the `blur` reset\n * anyway because it's the only signal that catches a tab-blur that swallows the\n * `pointerleave` without a `visibilitychange`. Pass a STABLE `reset` (memoize\n * with `useCallback`) — the listeners re-attach whenever its identity changes.\n */\nexport function useResetOnPageHidden(reset: () => void): void {\n useEffect(() => {\n const onVisibility = () => {\n if (document.visibilityState === 'hidden') reset();\n };\n window.addEventListener('blur', reset);\n document.addEventListener('visibilitychange', onVisibility);\n return () => {\n window.removeEventListener('blur', reset);\n document.removeEventListener('visibilitychange', onVisibility);\n };\n }, [reset]);\n}\n","'use client';\n\nimport { useEffect, useRef } from 'react';\n\n// Every focusable a clone might contain — matched so it can be pulled out of the\n// keyboard tab order (see useSuppressCloneFocus). `[contenteditable]:not(\n// [contenteditable=\"false\"])` catches the empty/`\"true\"`/`\"plaintext-only\"`\n// editable forms; `summary` is focusable inside a <details>.\nconst CLONE_FOCUSABLE_SELECTOR =\n 'a[href],button,input,select,textarea,iframe,summary,audio[controls],video[controls],[tabindex],[contenteditable]:not([contenteditable=\"false\"])';\n\n/**\n * Keep a marquee clone's content pointer-CLICKABLE while taking it out of the\n * accessibility tree and the keyboard tab order. Shared by every endless-loop\n * surface that renders a second track copy (<CardsStrip> managed cells,\n * <MarqueeWall> clone copies).\n *\n * `inert` (the previous approach) does all three at once: a11y-hidden,\n * un-focusable AND un-clickable. That third effect was the \"news marquee stops\n * being clickable after you scroll it for a while\" bug — the 2-copy endless\n * loop ALWAYS paints clone cards inside the viewport near the copy seam, so a\n * user who manually scrolls and parks there is looking at real-looking cards\n * that silently swallow every click. Clones must therefore stay clickable (a\n * click on ANY visible card must open its link — mouse, ⌘-click, middle-click\n * and context-menu all handled natively by the clone's own anchor).\n *\n * The a11y contract is still met without `inert`: the caller keeps\n * `aria-hidden` on the clone wrapper (out of the a11y tree) and this hook\n * forces every focusable descendant to `tabindex=\"-1\"` (out of the tab order →\n * no duplicate tab stops, no aria-hidden-focus violation). Re-applied on\n * subtree mutation. This is the standard loop-clone treatment used by\n * Swiper/Splide.\n */\nexport function useSuppressCloneFocus(active: boolean) {\n const ref = useRef<HTMLDivElement | null>(null);\n useEffect(() => {\n const root = ref.current;\n if (!root || !active) return undefined;\n const suppress = () => {\n root.querySelectorAll<HTMLElement>(CLONE_FOCUSABLE_SELECTOR).forEach(el => {\n // Only stamp the ones not already stamped — avoids touching the DOM (and\n // clobbering an intentional tabindex) on every mutation.\n if (el.getAttribute('tabindex') !== '-1') el.setAttribute('tabindex', '-1');\n });\n };\n suppress();\n // Watch structure AND the attributes that can turn a descendant tabbable\n // (an anchor gaining `href`, React restoring `tabindex=\"0\"`, an element\n // becoming editable, media gaining `controls`). The `!== '-1'` guard above\n // makes our own tabindex writes a no-op, so the observer never loops.\n const mo = new MutationObserver(suppress);\n mo.observe(root, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: ['href', 'tabindex', 'contenteditable', 'controls'],\n });\n return () => mo.disconnect();\n }, [active]);\n return ref;\n}\n","/**\n * Centralized Tool Types\n *\n * Single source of truth for all tool-related types across the entire platform.\n * Used by ToolBadge, ToolIcon, and any component that needs tool type information.\n */\n\nexport const ToolTypeValues = {\n FLEET_MDM: 'FLEET_MDM',\n MESHCENTRAL: 'MESHCENTRAL',\n AUTHENTIK: 'AUTHENTIK',\n OPENFRAME: 'OPENFRAME',\n OPENFRAME_CHAT: 'OPENFRAME_CHAT',\n OPENFRAME_CLIENT: 'OPENFRAME_CLIENT',\n OPENFRAME_RMM: 'OPENFRAME_RMM',\n OSQUERY: 'OSQUERY',\n SYSTEM: 'SYSTEM',\n // Directory tenants (Tenant Management): the same identifiers the gateway's\n // DirectoryProvider enum uses, so a directory sync's log rows resolve here.\n MICROSOFT_365: 'MICROSOFT_365',\n GOOGLE_WORKSPACE: 'GOOGLE_WORKSPACE',\n} as const;\n\nexport type ToolType = (typeof ToolTypeValues)[keyof typeof ToolTypeValues];\n\n/**\n * Maps tool types to display labels\n */\nexport const toolLabels: Record<ToolType, string> = {\n FLEET_MDM: 'Fleet',\n MESHCENTRAL: 'MeshCentral',\n AUTHENTIK: 'Authentik',\n OPENFRAME: 'OpenFrame',\n OPENFRAME_CHAT: 'OpenFrame Chat',\n OPENFRAME_CLIENT: 'OpenFrame Client',\n OPENFRAME_RMM: 'RMM',\n OSQUERY: 'Osquery',\n SYSTEM: 'System',\n MICROSOFT_365: 'Microsoft 365',\n GOOGLE_WORKSPACE: 'Google Workspace',\n};\n","import type { FC, ReactNode } from 'react';\nimport { type ToolType, ToolTypeValues } from '../types/tool.types';\nimport {\n OsqueryLogoGreyIcon,\n MeshcentralLogoGreyIcon,\n FleetMdmLogoGreyIcon,\n AuthentikLogoGreyIcon,\n Office365LogoGreyIcon,\n GoogleIcon,\n OpenframeLogoGreyIcon,\n} from './icons-v2-generated';\n\ntype ToolIconRenderer = (size: number, className?: string) => ReactNode;\n\n// Every mark is the grey (`currentColor`) cut of the brand logo, the OpenFrame one included, so a\n// row of tool icons reads as one set and takes the text colour of wherever it sits.\nconst renderOpenFrameLogo: ToolIconRenderer = (size, className) => (\n <OpenframeLogoGreyIcon size={size} className={className} />\n);\n\nconst toolIconMap: Record<ToolType, ToolIconRenderer> = {\n [ToolTypeValues.FLEET_MDM]: (size, className) => <FleetMdmLogoGreyIcon size={size} className={className} />,\n [ToolTypeValues.MESHCENTRAL]: (size, className) => <MeshcentralLogoGreyIcon size={size} className={className} />,\n [ToolTypeValues.OPENFRAME]: renderOpenFrameLogo,\n [ToolTypeValues.OPENFRAME_CHAT]: renderOpenFrameLogo,\n [ToolTypeValues.OPENFRAME_CLIENT]: renderOpenFrameLogo,\n [ToolTypeValues.OPENFRAME_RMM]: renderOpenFrameLogo,\n [ToolTypeValues.AUTHENTIK]: (size, className) => <AuthentikLogoGreyIcon size={size} className={className} />,\n [ToolTypeValues.OSQUERY]: (size, className) => <OsqueryLogoGreyIcon size={size} className={className} />,\n [ToolTypeValues.SYSTEM]: () => null,\n // The Microsoft 365 tool wears the Office 365 mark, not the Microsoft company logo: the product's\n // own brand is what the logs and the directory pickers name.\n [ToolTypeValues.MICROSOFT_365]: (size, className) => <Office365LogoGreyIcon size={size} className={className} />,\n [ToolTypeValues.GOOGLE_WORKSPACE]: (size, className) => <GoogleIcon size={size} className={className} />,\n};\n\nexport interface ToolIconProps {\n toolType: ToolType;\n size?: number;\n className?: string;\n}\n\nexport const ToolIcon: FC<ToolIconProps> = ({ toolType, size = 16, className }) => (\n <>{toolIconMap[toolType]?.(size, className) ?? null}</>\n);\n\nToolIcon.displayName = 'ToolIcon';\n","import { clsx, type ClassValue } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\n/**\n * Utility functions for common operations\n */\n\n/**\n * Merge class names with Tailwind CSS\n * @param inputs - Class names to merge\n * @returns Merged class names\n */\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n\n/**\n * Human-readable message for a caught value.\n *\n * `catch (e)` binds `unknown` under this tsconfig, and a thrown value is not\n * guaranteed to be an `Error`. Use this instead of `catch (e: any)` +\n * `e.message`, which crashes on a thrown string/object with no `message`.\n *\n * @param error - The caught value\n * @param fallback - Returned when the value carries no usable message\n */\nexport function errorMessage(error: unknown, fallback = 'Something went wrong'): string {\n if (error instanceof Error && error.message) return error.message;\n if (typeof error === 'string' && error) return error;\n if (typeof error === 'object' && error !== null) {\n const message = (error as { message?: unknown }).message;\n if (typeof message === 'string' && message) return message;\n }\n return fallback;\n}\n\n/**\n * Human-readable message for a FAILED API response body.\n *\n * `Response.json()` is typed `any`, so `body.error` used to be read off an\n * `any` at every call site. The platform's route helpers answer errors with\n * `{ error, code, details }` (see `errorResponse` in the hub's\n * `lib/api/route-base.ts`); a few endpoints answer `{ message }` instead, so\n * both are accepted before giving up.\n *\n * @param payload - The parsed response body (or whatever came back)\n * @param fallback - Returned when the body carries no usable message\n */\nexport function apiErrorMessage(payload: unknown, fallback: string): string {\n if (typeof payload === 'object' && payload !== null && 'error' in payload) {\n const { error } = payload;\n if (typeof error === 'string' && error) return error;\n }\n return errorMessage(payload, fallback);\n}\n\n/**\n * Delay execution for a specified time\n * @param ms - Milliseconds to delay\n * @returns Promise that resolves after the delay\n */\nexport function delay(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n\n/**\n * Generate a random string of specified length\n * @param length - Length of the string\n * @returns Random string\n */\nexport function generateRandomString(length = 8): string {\n const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';\n let result = '';\n for (let i = 0; i < length; i++) {\n result += chars.charAt(Math.floor(Math.random() * chars.length));\n }\n return result;\n}\n\n/**\n * Truncate a string to a specified length\n * @param str - String to truncate\n * @param maxLength - Maximum length\n * @param suffix - Suffix to add to truncated string\n * @returns Truncated string\n */\nexport function truncateString(str: string, maxLength: number, suffix = '...'): string {\n if (str.length <= maxLength) return str;\n return str.substring(0, maxLength - suffix.length) + suffix;\n}\n\n/**\n * Serialize a JSON-LD schema for a `<script type=\"application/ld+json\">`\n * block — THE one home of the escape rule (the hub re-exports this from\n * lib/utils/breadcrumbs). JSON.stringify does NOT HTML-escape: a stored\n * \"</script>\" inside any admin/user-entered field would terminate the tag\n * early (stored XSS). Every \"<\" becomes the JSON escape sequence\n * backslash-u003c — still valid JSON, inert in HTML.\n */\nexport function serializeJsonLd(schema: unknown): string {\n return JSON.stringify(schema).replace(/</g, '\\\\u003c');\n}\n\n/**\n * Deep clone an object through a JSON round-trip.\n *\n * Lossy by construction, and deliberately so — this is the JSON projection of\n * `T`: `undefined` members and functions are dropped, `Date` becomes an ISO\n * string, `Map`/`Set` become `{}`, and a cyclic graph throws. Reach for\n * `structuredClone` when any of that matters.\n *\n * `JSON.parse` is typed `any`; the assertion states the contract above, which\n * is the closest a JSON clone can get to \"still a `T`\".\n *\n * @param obj - Object to clone (must be JSON-serializable)\n * @returns Cloned object\n */\nexport function deepClone<T>(obj: T): T {\n return JSON.parse(JSON.stringify(obj)) as T;\n}\n\n/**\n * Get the Slack community join URL from environment variables\n * @returns Slack community join URL or fallback URL\n */\nexport function getSlackCommunityJoinUrl(): string {\n const url = process.env.NEXT_PUBLIC_SLACK_COMMUNITY_JOIN_URL;\n if (!url) {\n console.warn('NEXT_PUBLIC_SLACK_COMMUNITY_JOIN_URL is not defined in environment variables');\n return 'https://join.slack.com/t/openmsp/shared_invite/zt-36bl7mx0h-3~U2nFH6nqHqoTPXMaHEHA';\n }\n return url;\n}\n\n/**\n * Clamp a number into an inclusive range. THE one clamp for the lib and the hub\n * (step/page/contrast math all bottom out here).\n */\nexport function clamp(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), max);\n}\n\n/**\n * Typed key-subset of an object. Keys whose value is `undefined` on the source\n * are still copied when present, so `pick(params, dropped)` writes explicit\n * `null`s (the URL-state writer's \"remove this key\" signal).\n */\nexport function pick<T extends object, K extends keyof T>(source: T, keys: readonly K[]): Pick<T, K> {\n const out = {} as Pick<T, K>;\n for (const key of keys) {\n if (key in source) out[key] = source[key];\n }\n return out;\n}\n\n/**\n * React Query options for data that MUST NOT be served from the client cache.\n *\n * The rule (hub memory `feedback_no_client_query_cache_for_mutable_entities`):\n * anything a user can mutate, or a cron can rewrite under them, is refetched on\n * mount. Spread this instead of retyping `staleTime: 0` — it is also the hub\n * QueryClient's default, so a hook that spreads it is explicit rather than\n * silently inheriting.\n */\nexport const NO_CLIENT_CACHE = { staleTime: 0 } as const;\n","/**\n * useNearViewport — module-level shared IntersectionObserver in hook form.\n *\n * Single IO instance per `rootMargin` value, shared across every component\n * that mounts the hook. Reduces overhead vs. one IO per component on\n * grid/list pages where many subscribers observe the viewport with the same\n * margin. Promoted from the inline singleton at\n * `multi-platform-hub/components/shared/video-bites-display.tsx:21-43`,\n * which is the only IO pattern in either repo today.\n *\n * Usage:\n * ```tsx\n * function MyCard() {\n * const { ref, isNear } = useNearViewport('500px');\n * return <div ref={ref}>{isNear ? <HeavyChild /> : <Placeholder />}</div>;\n * }\n * ```\n *\n * StrictMode safety: cleanup uses an identity check on the registered\n * callback so React's dev double-mount (mount → cleanup → re-mount) does\n * not drop the second mount's freshly-set subscription. The IO callback\n * also checks `subscribers.get(target)` before invoking so a fire that\n * races with unmount cannot crash on a torn-down component.\n *\n * The hook fires once — on first intersection it sets `isNear=true` and\n * unobserves the element. Callers that need re-observation should\n * unmount and remount (or fork the hook for two-way behavior).\n */\n\nimport { useEffect, useRef, useState, useCallback } from 'react';\n\n// Per-(rootMargin, threshold) IO map. Multiple call sites with different\n// margins/thresholds each get their own singleton observer.\nconst observers = new Map<string, IntersectionObserver>();\nconst subscribers = new WeakMap<Element, () => void>();\n\n// Slack for the explicit ratio check: an element sized to exactly meet the\n// threshold can report a ratio a hair under it (sub-pixel / float rounding), so\n// accept `ratio >= threshold - EPSILON`. With `threshold: 0` this is a no-op\n// (ratio is always ≥ 0 > -EPSILON) — the `isIntersecting` check alone gates.\nconst THRESHOLD_EPSILON = 0.01;\n\n/** Stable map key for an observer config. */\nfunction observerKey(rootMargin: string, threshold: number): string {\n return `${rootMargin}|${threshold}`;\n}\n\nfunction getObserverFor(rootMargin: string, threshold: number): IntersectionObserver {\n const key = observerKey(rootMargin, threshold);\n const existing = observers.get(key);\n if (existing) return existing;\n\n const io = new IntersectionObserver(\n entries => {\n entries.forEach(entry => {\n // Enforce the threshold EXPLICITLY. `isIntersecting` is spec-defined as\n // `intersectionRatio > 0` (any overlap), so the initial `observe()`\n // firing can report `true` at, say, 20% visible even with\n // `threshold: 0.5` — which would prematurely pass the gate. Require both\n // an intersection AND the ratio to actually meet the threshold (minus a\n // small epsilon for the float rounding where the ratio lands a hair\n // under the exact value).\n if (!entry.isIntersecting) return;\n if (entry.intersectionRatio < threshold - THRESHOLD_EPSILON) return;\n // Race-safe: re-read the callback at fire time. A late IO firing\n // after cleanup must not invoke a stale callback.\n const cb = subscribers.get(entry.target);\n if (cb) {\n cb();\n io.unobserve(entry.target);\n subscribers.delete(entry.target);\n }\n });\n },\n { rootMargin, threshold },\n );\n observers.set(key, io);\n return io;\n}\n\n/**\n * Default near-viewport lookahead — the SSOT for \"mount media on approach\".\n * Shared by this hook's default AND the raw two-way IntersectionObservers in\n * `cards-strip.tsx` / `video-bites-strip.tsx` (which need mount+unmount, not\n * this hook's fire-once semantics, but must agree on the distance).\n */\nexport const NEAR_VIEWPORT_ROOT_MARGIN = '500px';\n\nexport interface UseNearViewportResult<T extends Element = HTMLElement> {\n /** Ref to attach to the element you want to gate on visibility. */\n ref: (node: T | null) => void;\n /** Flips to `true` once the element enters within `rootMargin` of the viewport. Never flips back. */\n isNear: boolean;\n}\n\n/**\n * @param rootMargin Margin around the viewport (CSS-style string).\n * '500px' = element starts mounting 500px before scroll-in.\n * '1000px' = a full viewport's worth of lookahead.\n * '0px' = strict on-screen detection.\n * @param threshold Fraction of the element that must be visible before firing\n * (0 = any pixel — the default; 0.5 = at least half on-screen).\n */\nexport function useNearViewport<T extends Element = HTMLElement>(\n rootMargin: string = NEAR_VIEWPORT_ROOT_MARGIN,\n threshold: number = 0,\n): UseNearViewportResult<T> {\n const [isNear, setIsNear] = useState(false);\n const elRef = useRef<T | null>(null);\n const key = observerKey(rootMargin, threshold);\n\n // Subscribe/unsubscribe on element change.\n const ref = useCallback(\n (node: T | null) => {\n const prev = elRef.current;\n\n // Unsubscribe previous, if any. Identity-check the callback so a\n // StrictMode re-mount that has already re-registered keeps its sub.\n if (prev) {\n const stillOurs = subscribers.get(prev);\n if (stillOurs) {\n subscribers.delete(prev);\n observers.get(key)?.unobserve(prev);\n }\n }\n\n elRef.current = node;\n if (!node) return;\n\n const cb = () => setIsNear(true);\n subscribers.set(node, cb);\n getObserverFor(rootMargin, threshold).observe(node);\n },\n [key, rootMargin, threshold],\n );\n\n // Unsubscribe on unmount. Identity check guards the StrictMode race.\n useEffect(() => {\n return () => {\n const el = elRef.current;\n if (!el) return;\n if (subscribers.get(el)) {\n subscribers.delete(el);\n observers.get(key)?.unobserve(el);\n }\n };\n }, [key]);\n\n return { ref, isNear };\n}\n","/**\n * Fenced-code tracker — THE definition of \"am I inside a code fence\".\n * Server-safe: no React, no DOM.\n *\n * Lives in its own module because it is a GENERAL CommonMark fence machine\n * with two unrelated consumers: the heading scanner\n * (`utils/markdown-heading-id`) and the streaming block splitter\n * (`components/ui/markdown/streaming`). It used to live inside\n * `markdown-heading-id`, which meant a streaming renderer imported its fence\n * state machine from a module named after heading slugs. `markdown-heading-id`\n * re-exports these names so the public `utils` surface is unchanged.\n */\n\n/**\n * CommonMark fenced-code opener/closer. Fence indent is capped at 3 spaces\n * (4+ is an indented code block, whose backticks are literal content), and the\n * run length is captured so a closer can be required to be at least as long as\n * its opener. Matched on the RAW line — `trimStart()` would let a fence inside\n * 4-space-indented code toggle the state.\n *\n * `\\r`-TOLERANT BY CONSTRUCTION. The previous spelling ended `(.*)$`, and `.`\n * excludes `\\r` while `$` (no `m` flag) anchors at end of input — so on a CRLF\n * document, split on `\\n`, NO line ever matched and the tracker was completely\n * fence-blind: `['```js\\r','const a = 1\\r','```\\r','after\\r']` classified as\n * four `text` lines where the LF spelling gives `open/inside/close/text`. The\n * sanitizer's closer haystack then never blanked the fenced region, so a\n * `</textarea>` written as a CRLF code sample satisfied `hasLaterCloser` and a\n * prose `<textarea>` above it stayed LIVE (reproduced in the DOM; the LF twin\n * of the same fixture escapes correctly). The `\\r` is absorbed by `\\r?$` rather\n * than normalized out of the text, because the sanitizer's mask is\n * LENGTH-PRESERVING and every offset in it is an index into the original\n * string — stripping `\\r` would shift them all.\n *\n * THE ONLY COPY. This constant plus its open/close state machine used to be\n * duplicated verbatim in `components/ui/markdown/streaming.ts`, kept in sync\n * by a code comment — which is exactly how the `~~~`-blind extractor drifted\n * in the first place. Both consumers now instantiate `createFenceTracker()`.\n */\nconst FENCE_RE = /^ {0,3}(`{3,}|~{3,})([^\\n]*?)\\r?$/;\n\n/**\n * CommonMark's BLANK LINE — spaces and tabs only. The single definition, shared\n * by every consumer of this module (the sanitizer's mask passes, the heading\n * scanner, the HTML-block range walk).\n *\n * NOT `String.prototype.trim()`. JS `trim()` strips the full Unicode\n * White_Space set — U+00A0 NBSP, U+000B VT, U+000C FF, U+2028/9, U+FEFF BOM —\n * none of which CommonMark (or remark) treats as blank. Every place that meant\n * \"is this a CommonMark blank line?\" and asked `trim() === ''` therefore\n * TERMINATED a construct one line early while remark kept it open. Verified\n * end-to-end: `<div>\\n\\u00A0\\n\\`<textarea>\\`\\n\\nrest\\n` left a LIVE `<textarea>`\n * in the DOM (`escapeUnknownHtmlTags` returned the input byte-identical),\n * because the tracked HTML-block range ended at the NBSP filler line and the\n * inline-code shelter below it stopped being inside a tracked block. Same for\n * VT / FF / BOM, for `<pre>` / `<details>` wrappers, and for a live third-party\n * `<iframe>`.\n *\n * `\\r` IS accepted: a CRLF document split on `\\n` spells its blank lines\n * `\"\\r\"`, and the mask is length-preserving so stripping `\\r` from the source\n * is not an option (see `FENCE_RE`'s `\\r?$`).\n */\nconst BLANK_LINE_RE = /^[ \\t\\r]*$/;\n\n/** True when `line` is a CommonMark blank line (spaces/tabs only, `\\r`-tolerant). */\nexport function isBlankLine(line: string): boolean {\n return BLANK_LINE_RE.test(line);\n}\n\n/**\n * What one source line is, relative to fenced code:\n * - `open` — this line opened a fence\n * - `close` — this line closed the open fence\n * - `inside`— this line is fence content (including a would-be delimiter that\n * does NOT close the open fence: wrong marker char, too short, or\n * carrying an info string)\n * - `text` — this line is ordinary markdown outside any fence\n *\n * NOTE: no consumer discriminates the three non-`text` roles today — both the\n * heading scanner and the streaming splitter only test `!== 'text'`, and the\n * closer-haystack mask below only tests `=== 'open'` / `=== 'close'`. The\n * four-value union is kept because the state machine already computes the\n * distinction for free and a future consumer (e.g. fence-aware syntax\n * post-processing) would otherwise have to re-derive it.\n */\nexport type FenceLineRole = 'open' | 'close' | 'inside' | 'text';\n\nexport interface FenceTracker {\n /** Advance across one raw source line (mutates) and classify it. */\n push(line: string): FenceLineRole;\n /** Marker CHARACTER of the currently open fence (`` ` `` / `~`), or null. */\n openChar(): string | null;\n /** Run length of the currently open fence (a closer must be ≥ this). */\n openLength(): number;\n}\n\n/** A fresh fenced-code state machine. Server-safe: no React, no DOM. */\nexport function createFenceTracker(): FenceTracker {\n let fenceChar: string | null = null;\n let fenceLength = 0;\n return {\n push(line: string): FenceLineRole {\n const fence = FENCE_RE.exec(line);\n if (fence) {\n const run = fence[1];\n if (fenceChar === null) {\n fenceChar = run[0];\n fenceLength = run.length;\n return 'open';\n }\n // A closer must use the SAME marker char, be at least as long as the\n // opener, and (per CommonMark) carry no info string. \"No info string\"\n // means spaces/tabs only — `trim()` would accept an NBSP tail and close\n // a fence CommonMark keeps open, i.e. blank LESS of the haystack\n // (fail-OPEN). Hence `isBlankLine`, not `trim()`.\n if (run[0] === fenceChar && run.length >= fenceLength && isBlankLine(fence[2])) {\n fenceChar = null;\n fenceLength = 0;\n return 'close';\n }\n return 'inside';\n }\n return fenceChar === null ? 'text' : 'inside';\n },\n openChar: () => fenceChar,\n openLength: () => fenceLength,\n };\n}\n","/**\n * Markdown heading-id slug SSOT (server-safe: no React, no DOM).\n *\n * This algorithm previously existed in THREE byte-identical copies:\n * - `components/ui/simple-markdown-renderer.tsx` (generateHeadingId)\n * - `components/ui/rich-markdown-renderer.tsx` (generateHeadingId)\n * - `utils/markdown-section-extractor.ts` (extractSections)\n *\n * The extractor is the PRODUCER of `sectionIds` and the renderers are the\n * CONSUMERS — if the two ever drift, deep-link anchors and scroll-spy\n * targets silently diverge. All three now call these helpers; the parity\n * test `components/ui/__tests__/markdown-parity.test.tsx` asserts\n * extractor-vs-renderer ID agreement over the fixture corpus.\n */\n\n/** Emoji ranges stripped from heading text before slugification. */\nexport const HEADING_EMOJI_RE = /[\\u{1F300}-\\u{1F9FF}]|[\\u{2600}-\\u{26FF}]|[\\u{2700}-\\u{27BF}]/gu;\n\n/** Remove emoji characters and trim. */\nexport function stripHeadingEmojis(text: string): string {\n return text.replace(HEADING_EMOJI_RE, '').trim();\n}\n\n/**\n * Core slug chain WITHOUT the emoji strip (the extractor exposes emoji\n * stripping as an option, so the two steps are kept separable):\n * lowercase → drop non-word/space/hyphen chars → spaces to hyphens →\n * trim leading/trailing hyphens. May return `''` for symbol-only input —\n * callers apply their own fallback (`section-N`).\n */\nexport function slugifyHeadingBase(text: string): string {\n return text\n .trim()\n .toLowerCase()\n .replace(/[^\\w\\s-]/g, '')\n .replace(/\\s+/g, '-')\n .replace(/^-+|-+$/g, '');\n}\n\n/** The full default chain used by the renderers: emoji strip + slugify. */\nexport function slugifyHeadingText(text: string): string {\n return slugifyHeadingBase(stripHeadingEmojis(text));\n}\n\n// ---------------------------------------------------------------------------\n// Heading SCANNER + DEDUPER (the other half of the producer/consumer contract)\n// ---------------------------------------------------------------------------\n/**\n * Sharing the slug chain alone was not enough. The PRODUCER\n * (`utils/markdown-section-extractor`) and the CONSUMER\n * (`components/ui/markdown/heading-ids`) each carried their own copy of\n * \"which lines are headings\" and \"how do duplicates get suffixed\", and the\n * copies drifted:\n * - the extractor toggled its code-block state on a bare\n * `line.startsWith('```')`, so a `~~~`-fenced (or wider-backtick, or\n * indented) block containing `## Setup` produced a SECTION from the\n * extractor and NO id from the renderer — `sectionIdMap` then missed\n * silently and the deep-link anchor pointed nowhere;\n * - the extractor anchored ATX at column 0 while the renderer allowed the\n * CommonMark 0..3-space indent;\n * - the dedupe counter was hand-copied in both files, each with a comment\n * saying \"the two must agree\".\n * Both now call `scanHeadings` + `createHeadingIdDeduper`. Server-safe: no\n * React, no DOM.\n */\n\n// The CommonMark fence machine used to live in THIS file, which meant a\n// streaming renderer imported its fence state from a module named after\n// heading slugs. It now lives in `./markdown-fences`; re-exported here so the\n// public `utils` surface (and every existing import) is unchanged.\nexport { createFenceTracker, isBlankLine, type FenceTracker, type FenceLineRole } from './markdown-fences';\nimport { createFenceTracker, isBlankLine } from './markdown-fences';\n\n/** A heading the renderer will emit, located in the source. */\nexport interface ScannedHeading {\n /** 1-based line of the heading's FIRST line (setext: the title line). */\n line: number;\n level: number;\n /** Raw title text, before `stripInlineMarkdown` / slugification. */\n text: string;\n}\n\nexport interface ScanHeadingsOptions {\n /**\n * Skip a leading YAML frontmatter block (`---` on line 1 through the next\n * `---`). Only a DOCUMENT-LEADING block counts: a bare `---` mid-document\n * is a thematic break or a setext underline, and treating it as a\n * frontmatter toggle (what the extractor used to do) silently swallowed\n * every heading until the next one.\n */\n skipFrontmatter?: boolean;\n /** Include raw-HTML headings (`<h2>Title</h2>`). Default true. */\n includeRawHtml?: boolean;\n /**\n * Skip fenced-code blocks. Default true — a `##` inside a code fence is\n * code, not a heading, and the renderer emits no id for it.\n */\n skipFences?: boolean;\n}\n\n/**\n * EVERY line-anchored regex in this module ends `\\r?$`, not `$`.\n *\n * Lines here come from `content.split('\\n')`, so on a CRLF document each one\n * carries a trailing `\\r`. `$` (no `m` flag) anchors at end of string and `\\r`\n * is not in `[ \\t]`, so a `$`-anchored pattern matches NOTHING on such a\n * document. That was a live REGRESSION: `main`'s looser `^(#{1,6})\\s+(.+)`\n * had no end anchor at all and tolerated the `\\r` (the title was `.trim()`ed\n * afterwards), so `'# Real\\r\\n\\r\\nbody\\r\\n\\r\\n## Second\\r\\n'` yielded\n * `[\"Real\",\"Second\"]` on `main` and `[]` here — every CRLF-stored doc / blog /\n * release body silently lost its TOC, its in-page anchors and its doc-SEO\n * heading links.\n *\n * The `\\r` is ABSORBED, not normalized away: `scanHeadings` reports 1-based\n * LINE numbers that the renderer's id map is keyed by, and the sanitizer's mask\n * (which shares `markdown-fences`) is length-preserving — neither may be fed a\n * rewritten source.\n */\n/** CommonMark ATX heading (closing `#` run is not part of the title). */\nconst ATX_HEADING_RE = /^ {0,3}(#{1,6})[ \\t]+(.*?)(?:[ \\t]+#+)?[ \\t]*\\r?$/;\n/** ATX with no title at all (`###`, `## ###`) — still a heading, empty text. */\nconst ATX_EMPTY_RE = /^ {0,3}(#{1,6})[ \\t]*#*[ \\t]*\\r?$/;\n/**\n * Raw-HTML heading (`<h2 id=\"x\">Title</h2>`) — rehype-raw renders these too.\n * GLOBAL: two `<h3>`s on one line are two headings, and a non-global scan\n * silently dropped the second (it then fell through to the renderer's\n * suffix-free fallback and emitted a DUPLICATE DOM id).\n */\nconst RAW_HEADING_RE = /<h([1-6])\\b[^>]*>([\\s\\S]*?)(?:<\\/h\\1>|$)/gi;\n/**\n * Blockquote markers and list-item markers preceding an ATX heading\n * (`> ## Setup`, `- ## Setup`, `1. ## Setup`). mdast emits a REAL `<h2>` for\n * these; scanning only column-0..3 missed them entirely, so two identical\n * `> ## Setup` headings both hit the suffix-free fallback and emitted the\n * duplicate DOM ids this whole module exists to prevent.\n */\nconst CONTAINER_PREFIX_RE = /^ {0,3}(?:(?:>[ \\t]?)+|(?:[-+*]|\\d{1,9}[.)])[ \\t]+)+/;\n/** Setext underline (`===` → h1, `---` → h2), AFTER container-prefix strip. */\nconst SETEXT_UNDERLINE_RE = /^ {0,3}(=+|-+)[ \\t]*\\r?$/;\n/** Thematic break — ends a paragraph run, never underlines it. */\nconst THEMATIC_BREAK_RE = /^ {0,3}(\\*|_){3,}[ \\t]*\\r?$/;\n/**\n * CommonMark HTML-block type 6 tag names — these open a raw HTML block no\n * matter what follows them on the line.\n */\nconst HTML_BLOCK_TAGS =\n 'address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h1|h2|h3|h4|h5|h6|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|nav|noframes|ol|optgroup|option|p|param|search|section|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul';\n\n/**\n * A line that OPENS a raw HTML block. mdast hands the whole block to the HTML\n * parser, so a `---` inside it is not a setext underline and the lines above\n * it are not a paragraph: `<div>\\nText\\n---\\n</div>` emits NO heading. The\n * scanner used to publish a phantom `Text` h2 there.\n *\n * The naive `^ {0,3}<` this replaced disqualified any run whose first line\n * merely STARTED with a tag, so `<span>x</span> more text` + `---` (a plain\n * paragraph in mdast, yielding an h2) was missed. The alternatives are spelled\n * out per CommonMark:\n * - types 1-5: `<script|pre|style|textarea`, `<!--`, `<?`, `<!X`, `<![CDATA[`\n * - type 6: a `HTML_BLOCK_TAGS` name, whatever follows it\n * - type 7: ANY complete open/closing tag ALONE on the line (trailing\n * whitespace only) — this is the clause the old regex got wrong\n * Attribute soup inside a type-7 tag is approximated with `[^>]*` rather than\n * a full attribute grammar; the error bias is unchanged (a missed run costs a\n * TOC entry, never a wrong id — the same direction as the documented\n * list-item-indent gap in `SetextRun.prefix`).\n */\nconst HTML_BLOCK_OPENER_RE = new RegExp(\n '^ {0,3}(?:' +\n // types 1-5\n '<(?:script|pre|style|textarea)(?:[\\\\s>]|$)|<!--|<\\\\?|<![a-zA-Z]|<!\\\\[CDATA\\\\[' +\n // type 6\n `|</?(?:${HTML_BLOCK_TAGS})(?:[\\\\s/>]|$)` +\n // type 7 — a complete tag standing alone on the line\n '|<[a-zA-Z][a-zA-Z0-9-]*(?:\\\\s[^>]*)?/?>[ \\\\t]*\\\\r?$' +\n '|</[a-zA-Z][a-zA-Z0-9-]*[ \\\\t]*>[ \\\\t]*\\\\r?$' +\n ')',\n 'i',\n);\n\n/**\n * The paragraph RUN that a setext underline would convert into a heading.\n *\n * mdast positions a setext heading at the FIRST line of the paragraph and its\n * text is the WHOLE run (`Foo\\nbar\\n===` → `{line:1, text:\"Foo\\nbar\"}`). The\n * scanner used to remember only the LAST line, so the renderer's line-keyed\n * lookup missed the heading entirely (falling back to a slug of the rendered\n * children, `foo-bar`) while the extractor published `bar` — a dead deep\n * link, plus a phantom TOC entry at the wrong line.\n */\ninterface SetextRun {\n /** 1-based line of the run's FIRST line — where mdast puts the heading. */\n line: number;\n /**\n * Container prefix (`> `, `- `, …) every line of the run shares. The\n * underline must carry the IDENTICAL prefix, which is why `> Quote` +\n * `> ---` scans as an h2 while `> Quote` + a bare `---` does not.\n *\n * KNOWN, DELIBERATE GAP: a setext underline indented to a LIST ITEM's\n * content column (`- item` / ` ---`) is a heading in mdast but has no\n * container prefix at all, so this equality test misses it. Closing that\n * would mean re-deriving list-item content columns here — the scanner\n * simply emits nothing (the pre-existing behavior), which costs a TOC entry\n * and never produces a WRONG id.\n */\n prefix: string;\n /** Run lines with the container prefix stripped, in order. */\n lines: string[];\n /** Run opened with a raw-HTML block line — mdast emits no heading for it. */\n disqualified: boolean;\n}\n\n/**\n * Collect every heading the markdown renderer will emit, in document order.\n * THE single definition of \"which lines are headings\" for both the extractor\n * and the renderer's id map.\n */\nexport function scanHeadings(content: string, options: ScanHeadingsOptions = {}): ScannedHeading[] {\n const { skipFrontmatter = true, includeRawHtml = true, skipFences = true } = options;\n const out: ScannedHeading[] = [];\n const lines = content.split('\\n');\n // ONE fenced-code state machine (utils/markdown-fences), shared with\n // markdown/streaming.ts.\n const fences = skipFences ? createFenceTracker() : null;\n\n let start = 0;\n if (skipFrontmatter && lines[0]?.trim() === '---') {\n for (let i = 1; i < lines.length; i++) {\n if (lines[i].trim() === '---' || lines[i].trim() === '...') {\n start = i + 1;\n break;\n }\n }\n }\n\n // The paragraph run a setext underline would convert (see SetextRun).\n let setextRun: SetextRun | null = null;\n\n for (let i = start; i < lines.length; i++) {\n const line = lines[i];\n if (fences) {\n const role = fences.push(line);\n if (role !== 'text') {\n setextRun = null;\n continue;\n }\n }\n\n // Container prefix (`> `, `- `, `1. `) is stripped from EVERY construct\n // below, so a blockquote/list-nested heading is scanned in both its ATX\n // (`> ## Setup`) and setext (`> Quote title` / `> ---`) spellings.\n const prefix = CONTAINER_PREFIX_RE.exec(line)?.[0] ?? '';\n const body = prefix ? line.slice(prefix.length) : line;\n\n // --- setext (`Title` / `===`): a REAL h1/h2 in mdast, positioned at the\n // run's FIRST line — which is the line the heading-id map is keyed by.\n // The underline must carry the SAME container prefix as the run it\n // closes; `> Quote` followed by a bare `---` is a quote and a thematic\n // break, not a heading.\n const setext = SETEXT_UNDERLINE_RE.exec(body);\n if (setext) {\n if (setextRun && !setextRun.disqualified && setextRun.prefix === prefix) {\n out.push({\n line: setextRun.line,\n level: setext[1][0] === '=' ? 1 : 2,\n text: setextRun.lines.join('\\n'),\n });\n }\n setextRun = null;\n continue;\n }\n\n // --- ATX, including inside a blockquote / list item.\n const atx = ATX_HEADING_RE.exec(body) ?? ATX_EMPTY_RE.exec(body);\n if (atx) {\n out.push({ line: i + 1, level: atx[1].length, text: (atx[2] ?? '').trim() });\n setextRun = null;\n continue;\n }\n\n if (includeRawHtml && line.indexOf('<') !== -1) {\n RAW_HEADING_RE.lastIndex = 0;\n let raw: RegExpExecArray | null;\n let matched = false;\n while ((raw = RAW_HEADING_RE.exec(line)) !== null) {\n matched = true;\n out.push({\n line: i + 1,\n level: Number(raw[1]),\n text: raw[2].replace(/<[^>]*>/g, '').trim(),\n });\n }\n if (matched) {\n setextRun = null;\n continue;\n }\n }\n\n // Paragraph accumulation. A blank line or a thematic break ends the run;\n // a change of container prefix starts a new block (a list or blockquote\n // interrupts a paragraph in CommonMark).\n // CommonMark's blank line (spaces/tabs), NOT `trim()` — an NBSP-only line\n // is paragraph CONTENT to remark, so ending the run there would move a\n // setext heading's reported line off the run's first line.\n if (isBlankLine(body) || THEMATIC_BREAK_RE.test(body)) {\n setextRun = null;\n continue;\n }\n if (setextRun && setextRun.prefix === prefix) {\n setextRun.lines.push(body.trim());\n } else {\n setextRun = {\n line: i + 1,\n prefix,\n lines: [body.trim()],\n disqualified: HTML_BLOCK_OPENER_RE.test(body),\n };\n }\n }\n\n return out;\n}\n\n/**\n * The duplicate-id suffix counter, as ONE closure both sides instantiate.\n * First occurrence keeps the bare slug; the Nth gets `-N`. Extractor and\n * renderer MUST use the same counter or `#anchor` deep links diverge.\n */\nexport function createHeadingIdDeduper(): (cleanId: string) => string {\n const counts: Record<string, number> = {};\n return (cleanId: string): string => {\n if (counts[cleanId]) {\n counts[cleanId]++;\n return `${cleanId}-${counts[cleanId]}`;\n }\n counts[cleanId] = 1;\n return cleanId;\n };\n}\n","/**\n * GitHub-compatible anchor resolution for in-page `#hash` links.\n *\n * ## The mismatch this exists to bridge\n *\n * Doc authors write their table of contents against GitHub's slugger, which\n * keeps the separator left behind by a stripped emoji:\n *\n * `## 📚 Table of Contents` → GitHub anchor `#-table-of-contents`\n *\n * Our heading ids come from the shared slug SSOT (`markdown-heading-id`),\n * whose `slugifyHeadingBase` trims leading and trailing hyphens — so the same\n * heading renders as `id=\"table-of-contents\"`. The \"On this page\" rail\n * navigates by those ids and works; the in-body TOC link points at an id that\n * does not exist and the browser silently does nothing.\n *\n * Rather than change the id shape (it is already baked into shared URLs and\n * the rail), resolution is made tolerant: compare a *normalized* form of both\n * sides. This module owns ONLY that tolerance — the slug chain itself is\n * imported, never re-implemented, so a change to the id shape lands in one\n * place and both sides move together.\n *\n * Callers do not reach for this directly: `getHashTargetElement`\n * (`utils/same-page-hash-nav`) is the ONE resolver every hash consumer goes\n * through, and it applies this as its last pass.\n */\nimport { slugifyHeadingText } from './markdown-heading-id';\n\n/**\n * Reduce an anchor id (ours or GitHub's) to a comparable form.\n *\n * Everything but the final hyphen-run collapse is the shared heading-slug\n * chain: emoji strip → lowercase → drop non-word chars → spaces to hyphens →\n * trim edge hyphens. The collapse is this module's own addition, covering\n * GitHub's *interior* divergence — `## 💬 Community & Support` slugs to\n * `#-community--support` (a double hyphen around the dropped `&`) against our\n * `community-support`.\n *\n * `'-getting-started'` → `'getting-started'`\n * `'-community--support'` → `'community-support'`\n * `'Getting Started'` → `'getting-started'`\n */\nexport function normalizeAnchorId(raw: string): string {\n return slugifyHeadingText(raw)\n .replace(/-+/g, '-')\n .replace(/^-+|-+$/g, '');\n}\n\n/** Heading elements carrying an id — the candidate set for the fuzzy pass. */\nconst HEADING_SELECTOR = 'h1[id], h2[id], h3[id], h4[id], h5[id], h6[id]';\n\n/** A `base-N` id, split into its base and ordinal. */\nconst DUPLICATE_SUFFIX_RE = /^(.+)-(\\d+)$/;\n\n/**\n * The two sides also disagree on where duplicate-heading numbering STARTS.\n * For a document with two `## Setup` headings GitHub emits `setup` and\n * `setup-1`, while `createHeadingIdDeduper` emits `setup` and `setup-2` (its\n * suffix is the occurrence COUNT, not the offset). So a GitHub `#setup-1`\n * needs our `setup-2` — every duplicate is off by exactly one.\n *\n * Runs LAST, after the exact lookup and the heading scan have both missed: a\n * heading that genuinely slugs to `step-2` is resolved long before this, and\n * the shift is the one pass that can match an id it was not asked for.\n */\nfunction findDuplicateShifted(normalized: string, doc: Document): HTMLElement | null {\n const parts = DUPLICATE_SUFFIX_RE.exec(normalized);\n if (!parts) return null;\n const ordinal = Number(parts[2]);\n if (!Number.isSafeInteger(ordinal)) return null;\n return doc.getElementById(`${parts[1]}-${ordinal + 1}`);\n}\n\n/**\n * Resolve a `#hash` id that no exact lookup matched, by comparing normalized\n * forms. Returns `null` when nothing in the document matches, so the caller\n * can leave the browser's default behavior alone rather than hijacking a\n * click that was never ours to handle.\n *\n * Three passes, in order of how much they assume:\n * 1. a single `getElementById` on the normalized id — catches the whole\n * emoji-hyphen family without walking the DOM;\n * 2. a heading scan — catches ids whose own raw form normalizes differently\n * (e.g. `a---b`, from a title containing a literal ` - `);\n * 3. the duplicate-heading shift (see `findDuplicateShifted`) — last,\n * because it is the only pass that resolves to an id the fragment did\n * not name.\n */\nexport function findAnchorElementByNormalizedId(rawId: string, doc: Document): HTMLElement | null {\n const normalized = normalizeAnchorId(rawId);\n if (!normalized) return null;\n\n if (normalized !== rawId) {\n const direct = doc.getElementById(normalized);\n if (direct) return direct;\n }\n\n for (const heading of Array.from(doc.querySelectorAll<HTMLElement>(HEADING_SELECTOR))) {\n if (normalizeAnchorId(heading.id) === normalized) return heading;\n }\n\n // Last: the shift would happily match an unrelated `x-3` for a `x-2` that\n // has no heading at all, so it runs only once the scan has come up empty.\n return findDuplicateShifted(normalized, doc);\n}\n","/**\n * `scrollElementIntoView` — canonical \"scroll an element to the top of the\n * viewport, account for sticky chrome, survive layout shifts\" helper.\n *\n * One shared implementation so every caller (the ticket drawer expand, the\n * hub's `useUnifiedNav` / `use-nav-link` hash scroll, doc-tree, delivery\n * `?focus=`, sticky-section-nav, …) inherits the SAME cancellation-proof\n * motion.\n *\n * WHY A SELF-DRIVEN rAF TWEEN INSTEAD OF `window.scrollTo({behavior:'smooth'})`:\n * the native smooth scroll is CANCELLABLE, and in real pages it gets cancelled\n * constantly:\n *\n * - Browser SCROLL ANCHORING: when content is inserted/removed above or\n * around the target (a collapsible drawer expanding, an async image\n * loading, a list re-rendering) the browser issues a synchronous scrollTop\n * correction to keep the anchored element stable. Per CSSOM-View \"perform a\n * scroll\" step 1 (\"abort any ongoing smooth scroll\"), that correction\n * ABORTS an in-flight native smooth scroll — so it lands as an instant jump.\n * Anchoring is suppressed when the scroll offset is 0, which is exactly why\n * a native smooth scroll appears to work the FIRST time (page at top) and\n * jumps on every repeat (page already scrolled). This was a multi-day\n * \"smooth only works once\" bug on the /tickets drawer.\n * - A second programmatic scroll on the same frame, or a `focus()` without\n * `{preventScroll:true}`, cancels it the same way.\n *\n * A tween that re-asserts the position with INSTANT writes every frame is\n * immune: there is no \"ongoing native smooth scroll\" for anchoring/focus to\n * abort, and any correction that lands between our frames is overwritten on the\n * next frame. We also RECOMPUTE the target each frame, so an element whose\n * final position is still settling (drawer still expanding, images loading)\n * is tracked to its resting place instead of animating to a stale pixel.\n *\n * Honors `prefers-reduced-motion` (jumps instantly) and cancels on genuine user\n * scroll intent (wheel / touch) so we never fight the user.\n *\n * WINDOW *OR* A SCROLLABLE ANCESTOR: the helper is not hard-wired to the window\n * scroller. It walks up from the target to the nearest ancestor that is an\n * actual scroll container (`overflow-y: auto | scroll | overlay` AND\n * `scrollHeight > clientHeight`) and drives THAT element; only when none exists\n * does it fall back to `window`. This is what makes it work inside app shells\n * that put page content in a fixed-height `<main class=\"overflow-y-auto\">`\n * (e.g. OpenFrame's `AppLayout`) where the document/window never scrolls — the\n * old window-only version was a silent no-op there. Note `overflow: clip` /\n * `hidden` are deliberately NOT treated as scroll containers, so a list wrapper\n * that uses `overflow-clip` only to round its corners still bubbles the scroll\n * up to the real container (matches the `<HelpCenterCard>` list intent).\n */\n\nexport interface ScrollElementIntoViewOptions {\n /** Pixels to subtract from the target element's `top` so it lands BELOW\n * sticky chrome. Defaults to 0. Pass `96` for the standard hub header. */\n headerOffset?: number;\n /** `'smooth'` (default) runs the self-driven tween; `'instant'` / `'auto'`\n * jump in one synchronous write (deep-link land, programmatic focus moves). */\n behavior?: ScrollBehavior;\n /** Optional adjustment applied to the computed pixel target each frame. The\n * callback receives the \"raw\" Y (`element.top + scrollY - headerOffset`) and\n * returns the FINAL target. Use when the caller knows about a layout shift\n * (e.g. a sibling drawer collapsing) the geometry can't yet reflect. */\n adjustTargetY?: (rawTargetY: number) => number;\n /** Tween duration in ms (smooth only). Default 320. */\n durationMs?: number;\n}\n\n/** Module-level handle to the in-flight tween so a new call (or a user\n * gesture) cancels the previous one — only ever one page-scroll animation at\n * a time. */\nlet activeRaf = 0;\nlet teardownActive: (() => void) | null = null;\n\nfunction cancelActiveScroll(): void {\n if (activeRaf) {\n cancelAnimationFrame(activeRaf);\n activeRaf = 0;\n }\n if (teardownActive) {\n teardownActive();\n teardownActive = null;\n }\n}\n\nconst easeOutCubic = (t: number): number => 1 - Math.pow(1 - t, 3);\n\n/** Nearest ancestor that is a *real* scroll container, or `null` when the\n * window/document is the scroller. Only `auto | scroll | overlay` count —\n * `clip` / `hidden` are intentionally excluded (a wrapper using `overflow-clip`\n * purely to round corners must let the scroll bubble to the page).\n *\n * THE one rule for \"what scrolls this element\": `useScrollSpy` resolves the\n * scroller it listens to with it, so the section a spy highlights is the\n * section this helper scrolls to — inside a `<main overflow-y-auto>` shell as\n * much as on a window-scrolled page. */\nexport function getScrollableAncestor(el: HTMLElement): HTMLElement | null {\n for (let node = el.parentElement; node; node = node.parentElement) {\n const overflowY = getComputedStyle(node).overflowY;\n if (\n (overflowY === 'auto' || overflowY === 'scroll' || overflowY === 'overlay') &&\n node.scrollHeight > node.clientHeight\n ) {\n return node;\n }\n }\n return null;\n}\n\n/**\n * Scroll the page so `target` lands at the top of the viewport (below sticky\n * chrome via `headerOffset`). SSR-safe; `null`/`undefined` target is a no-op so\n * callers can pass refs without defensive branching.\n */\nexport function scrollElementIntoView(\n target: HTMLElement | null | undefined,\n options: ScrollElementIntoViewOptions = {},\n): void {\n if (typeof window === 'undefined' || !target) return;\n const { headerOffset = 0, behavior = 'smooth', adjustTargetY, durationMs = 320 } = options;\n\n // Pick the scroller ONCE: a fixed-height `<main overflow-y-auto>` shell scrolls\n // the element, a plain document scrolls the window. The choice can't change\n // mid-tween, so resolve it up front and route every read/write through it.\n const container = getScrollableAncestor(target);\n const readCurrent = (): number => (container ? container.scrollTop : window.scrollY);\n const writeTo = (y: number): void => {\n if (container) container.scrollTop = y;\n else window.scrollTo(0, y);\n };\n\n // Target is recomputed every frame: the row's absolute position can move as\n // the page reflows (a sibling drawer collapsing) and the reachable max grows\n // as the just-opened drawer expands. Clamp to the LIVE max each frame.\n const computeTarget = (): number => {\n const raw = container\n ? container.scrollTop +\n (target.getBoundingClientRect().top - container.getBoundingClientRect().top) -\n headerOffset\n : target.getBoundingClientRect().top + window.scrollY - headerOffset;\n const adjusted = adjustTargetY ? adjustTargetY(raw) : raw;\n const maxScroll = container\n ? Math.max(0, container.scrollHeight - container.clientHeight)\n : Math.max(0, document.documentElement.scrollHeight - window.innerHeight);\n return Math.min(Math.max(0, adjusted), maxScroll);\n };\n\n // Any prior animation loses — one page scroll at a time.\n cancelActiveScroll();\n\n const prefersReduced =\n typeof window.matchMedia === 'function' && window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n\n // Instant paths: a single synchronous write. No tween, no anchoring race.\n if (behavior === 'instant' || behavior === 'auto' || prefersReduced) {\n writeTo(computeTarget());\n return;\n }\n\n // Smooth: self-driven tween with instant per-frame writes (anchoring-proof).\n let startY: number | null = null;\n let startTime = 0;\n\n // Bail the moment the user takes over with a real scroll gesture — we must\n // never fight them. (Not keydown: the ticket composer auto-focuses on open,\n // and typing there should not abort the scroll.)\n const onUserGesture = () => cancelActiveScroll();\n window.addEventListener('wheel', onUserGesture, { passive: true });\n window.addEventListener('touchmove', onUserGesture, { passive: true });\n teardownActive = () => {\n window.removeEventListener('wheel', onUserGesture);\n window.removeEventListener('touchmove', onUserGesture);\n };\n\n const step = (now: number) => {\n if (startY === null) {\n startY = readCurrent();\n startTime = now;\n }\n const targetY = computeTarget();\n const t = Math.min(1, (now - startTime) / durationMs);\n const y = startY + (targetY - startY) * easeOutCubic(t);\n writeTo(y);\n if (t < 1) {\n activeRaf = requestAnimationFrame(step);\n } else {\n // Final exact write in case easing left a sub-pixel gap, then teardown.\n writeTo(computeTarget());\n activeRaf = 0;\n if (teardownActive) {\n teardownActive();\n teardownActive = null;\n }\n }\n };\n activeRaf = requestAnimationFrame(step);\n}\n","import { findAnchorElementByNormalizedId } from './anchor-id';\nimport { scrollElementIntoView } from './scroll-into-view';\n\n/** Pages with a section-nav STRIP on top of the global hub header\n * (dev-center roadmap/delivery/tickets, FAQ category-pill nav).\n * Anchor lands BELOW both layers. */\nexport const STICKY_HEADER_OFFSET_PX = 96;\n\n/** Pages with only the global hub header (docs, blog, vendor detail).\n * Anchor lands BELOW the header bar. */\nexport const HUB_HEADER_OFFSET_PX = 80;\n\n/**\n * Take only the FIRST hash segment from a fragment that may contain extra\n * `#` characters. `'' → ''`, `'#a' → '#a'`, `'#a#b' → '#a'`.\n *\n * No real DOM id contains `#`, so a multi-fragment hash is always a bug at\n * the composer site; `navigateSamePageHash` + `useScrollToHash` both call\n * this so URL bar and `getElementById` stay in sync.\n */\nexport function normalizeHashFragment(hash: string): string {\n if (!hash) return '';\n const second = hash.indexOf('#', 1);\n return second < 0 ? hash : hash.slice(0, second);\n}\n\n/**\n * Resolve the DOM element a hash fragment id points at — THE hash resolver;\n * `navigateSamePageHash`, `useScrollToHash` and the markdown link renderer\n * all go through this one, so a fragment that resolves for a deep link\n * resolves for an in-body click too.\n *\n * Three passes, cheapest first:\n * 1. the raw id (ordinary anchors — the overwhelming common case);\n * 2. the percent-decoded form — href composers like `buildTicketOpenHref`\n * encode the id into the fragment while rows render the raw id, so\n * `#ticket-a%2Fb` must still find `id=\"ticket-a/b\"`. A malformed escape\n * sequence just falls through;\n * 3. a NORMALIZED match against the document's headings — doc bodies are\n * authored on GitHub, whose slugger keeps the hyphen left behind by a\n * stripped emoji (`## 📚 Table of Contents` → `#-table-of-contents`)\n * while our ids trim it. Without this every in-body TOC link in every\n * GitHub-authored doc resolves to nothing and clicking one does nothing.\n * See `utils/anchor-id`.\n */\nexport function getHashTargetElement(id: string): HTMLElement | null {\n if (!id) return null;\n const direct = document.getElementById(id);\n if (direct) return direct;\n\n let decoded = id;\n try {\n decoded = decodeURIComponent(id);\n if (decoded !== id) {\n const decodedEl = document.getElementById(decoded);\n if (decodedEl) return decodedEl;\n }\n } catch {\n // malformed escape — the raw lookup above already covered it\n decoded = id;\n }\n\n return findAnchorElementByNormalizedId(decoded, document);\n}\n\nexport interface NavigateSamePageHashOptions {\n /** Pixels to subtract for sticky chrome. */\n headerOffset?: number;\n /** `'push'` (default) — new history entry; `'replace'` — overwrite\n * current entry (use for TOC-style in-page navigators). */\n history?: 'push' | 'replace';\n}\n\n/**\n * Same-page hash navigation primitive: pushState + synthetic `hashchange`\n * + anchoring-proof smooth scroll. Replaces `router.push` for hash CTAs\n * (Next.js suppresses smooth-scroll during navigation; `router.push` on\n * an exact-URL match is a no-op). Returns `true` when the helper claimed\n * the nav (same pathname + search); `false` for cross-page targets so\n * callers fall through to `router.push`.\n *\n * `target` accepts an origin-stripped path (`/x#anchor`) or a bare hash\n * (`#anchor`); bare-hash callers don't need to reconstruct `pathname +\n * search` themselves.\n */\nexport function navigateSamePageHash(target: string, options: NavigateSamePageHashOptions = {}): boolean {\n if (typeof window === 'undefined') return false;\n const { headerOffset = 0, history: historyMode = 'push' } = options;\n const normalizedTarget = target.startsWith('#') ? window.location.pathname + window.location.search + target : target;\n // `new URL(absoluteUrl, base)` ignores `base` per RFC 3986; an absolute\n // cross-origin target sharing pathname/search would otherwise pass the\n // check below and trip pushState's same-origin enforcement. Parse with\n // an explicit base so malformed inputs cleanly fall through.\n let url: URL;\n try {\n url = new URL(normalizedTarget, window.location.href);\n } catch {\n return false;\n }\n if (\n url.origin !== window.location.origin ||\n url.pathname !== window.location.pathname ||\n url.search !== window.location.search\n ) {\n return false;\n }\n const current = window.location.pathname + window.location.search + window.location.hash;\n // Heal a malformed multi-fragment hash so the URL bar is clean and\n // `getElementById` resolves. Dev-warn fingers the upstream composer.\n const normalizedHash = normalizeHashFragment(url.hash);\n if (process.env.NODE_ENV === 'development' && normalizedHash !== url.hash) {\n console.warn(\n `[navigateSamePageHash] malformed fragment \"${url.hash}\" → normalizing to \"${normalizedHash}\". Fix the upstream composer.`,\n );\n }\n const next = url.pathname + url.search + normalizedHash;\n const id = normalizedHash && normalizedHash !== '#' ? normalizedHash.slice(1) : '';\n // Hash-less targets are only ours on an EXACT URL re-click.\n if (!id && next !== current) return false;\n if (next !== current) {\n const oldURL = window.location.href;\n if (historyMode === 'replace') {\n window.history.replaceState(null, '', next);\n } else {\n window.history.pushState(null, '', next);\n }\n // Synthetic `hashchange` — `pushState` doesn't fire it (HTML spec),\n // so URL-hash-bound listeners (FAQ auto-expand, etc.) wouldn't react.\n window.dispatchEvent(\n new HashChangeEvent('hashchange', {\n oldURL,\n newURL: window.location.href,\n }),\n );\n }\n const el = id ? getHashTargetElement(id) : null;\n if (id && !el && process.env.NODE_ENV === 'development') {\n console.warn(`[navigateSamePageHash] anchor \"#${id}\" not found — scrolling to top.`);\n }\n // Missing anchor → tween to page top. `documentElement` is at 0 by\n // definition, so one tween covers both branches.\n scrollElementIntoView(el ?? document.documentElement, {\n behavior: 'smooth',\n headerOffset,\n });\n return true;\n}\n"],"mappings":"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","names":["useState","useEffect","delay","useEffect","useState","useState","useEffect","useState","header","bar","useCallback","useEffect","useEffect","useEffect","useRef","useEffect","useRef","useState","useCallback"]}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|