@flamingo-stack/openframe-frontend-core 0.0.690 → 0.0.691-2504.7650.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/dist/{chunk-22RSJ57Z.js → chunk-2YVTGPM7.js} +2 -2
  2. package/dist/{chunk-V5NOURSR.js → chunk-4HZXOQRM.js} +5 -5
  3. package/dist/{chunk-MDYTAKJ5.cjs → chunk-4N2JOPAQ.cjs} +22 -22
  4. package/dist/{chunk-MDYTAKJ5.cjs.map → chunk-4N2JOPAQ.cjs.map} +1 -1
  5. package/dist/{chunk-QNWQQ4RT.cjs → chunk-53O6PQ3F.cjs} +7 -7
  6. package/dist/{chunk-QNWQQ4RT.cjs.map → chunk-53O6PQ3F.cjs.map} +1 -1
  7. package/dist/{chunk-X4WBGDNW.cjs → chunk-5N2ZSB42.cjs} +18 -18
  8. package/dist/{chunk-X4WBGDNW.cjs.map → chunk-5N2ZSB42.cjs.map} +1 -1
  9. package/dist/{chunk-XRSJER2B.js → chunk-6Z26ZMR4.js} +4 -4
  10. package/dist/{chunk-HLEXYTOE.js → chunk-7EPGJOKN.js} +4 -4
  11. package/dist/{chunk-DOUG4UEZ.js → chunk-7WYWPELF.js} +11 -11
  12. package/dist/{chunk-ZBQZJAC7.cjs → chunk-BXAGR3PO.cjs} +7 -7
  13. package/dist/chunk-BXAGR3PO.cjs.map +1 -0
  14. package/dist/{chunk-QUGV2GT3.cjs → chunk-BXE2RZEV.cjs} +34 -34
  15. package/dist/{chunk-QUGV2GT3.cjs.map → chunk-BXE2RZEV.cjs.map} +1 -1
  16. package/dist/{chunk-DLRE5Y3G.js → chunk-C2SVJVEB.js} +2 -2
  17. package/dist/{chunk-OC3QH5WG.js → chunk-CMYDGVTZ.js} +2 -2
  18. package/dist/{chunk-VLWUKFQE.cjs → chunk-CWXRZVDI.cjs} +751 -710
  19. package/dist/chunk-CWXRZVDI.cjs.map +1 -0
  20. package/dist/{chunk-2PQKZWWP.js → chunk-DC3G4XGP.js} +6 -6
  21. package/dist/{chunk-KZXKT5AX.cjs → chunk-DDWLF2JV.cjs} +14 -14
  22. package/dist/{chunk-KZXKT5AX.cjs.map → chunk-DDWLF2JV.cjs.map} +1 -1
  23. package/dist/{chunk-NMXL34Z2.cjs → chunk-DVSPRY2V.cjs} +3 -3
  24. package/dist/{chunk-NMXL34Z2.cjs.map → chunk-DVSPRY2V.cjs.map} +1 -1
  25. package/dist/{chunk-OQVHGPHA.js → chunk-DZMDG7PC.js} +3 -3
  26. package/dist/{chunk-2MGTNYHW.cjs → chunk-ESFPY5FF.cjs} +100 -100
  27. package/dist/{chunk-2MGTNYHW.cjs.map → chunk-ESFPY5FF.cjs.map} +1 -1
  28. package/dist/{chunk-S5M5SEW2.js → chunk-FNMQ2J44.js} +5 -5
  29. package/dist/{chunk-UAKWSR3U.js → chunk-GDZ7IC5B.js} +2 -2
  30. package/dist/{chunk-OD2WMUAO.cjs → chunk-GZRU2A4L.cjs} +197 -197
  31. package/dist/{chunk-OD2WMUAO.cjs.map → chunk-GZRU2A4L.cjs.map} +1 -1
  32. package/dist/{chunk-3ZAIHRQH.cjs → chunk-H54RDLVC.cjs} +35 -35
  33. package/dist/{chunk-3ZAIHRQH.cjs.map → chunk-H54RDLVC.cjs.map} +1 -1
  34. package/dist/{chunk-Q76YVNCQ.js → chunk-HM7ZGABI.js} +2 -2
  35. package/dist/{chunk-YCKGUQA2.cjs → chunk-I27PWVWE.cjs} +17 -12
  36. package/dist/chunk-I27PWVWE.cjs.map +1 -0
  37. package/dist/{chunk-25X43LSD.js → chunk-JSG7WZKV.js} +4 -4
  38. package/dist/{chunk-AIXPQT3G.js → chunk-KMW2PXBU.js} +52 -11
  39. package/dist/chunk-KMW2PXBU.js.map +1 -0
  40. package/dist/{chunk-TBJWPNHU.js → chunk-KVDLL4LU.js} +4 -4
  41. package/dist/chunk-KVDLL4LU.js.map +1 -0
  42. package/dist/{chunk-54X75QTC.cjs → chunk-LCENRN2N.cjs} +14 -14
  43. package/dist/{chunk-54X75QTC.cjs.map → chunk-LCENRN2N.cjs.map} +1 -1
  44. package/dist/{chunk-BJHM2OEY.cjs → chunk-LNKLB7GM.cjs} +17 -17
  45. package/dist/{chunk-BJHM2OEY.cjs.map → chunk-LNKLB7GM.cjs.map} +1 -1
  46. package/dist/{chunk-JGVJRUGB.cjs → chunk-MIZU2OTO.cjs} +4 -4
  47. package/dist/{chunk-JGVJRUGB.cjs.map → chunk-MIZU2OTO.cjs.map} +1 -1
  48. package/dist/{chunk-TSENCZUL.js → chunk-MJI5GVT7.js} +11 -11
  49. package/dist/{chunk-BOM6MMWB.cjs → chunk-N3526CTH.cjs} +3 -3
  50. package/dist/{chunk-BOM6MMWB.cjs.map → chunk-N3526CTH.cjs.map} +1 -1
  51. package/dist/{chunk-D7BFS7N6.cjs → chunk-N4S7RBUM.cjs} +3 -3
  52. package/dist/{chunk-D7BFS7N6.cjs.map → chunk-N4S7RBUM.cjs.map} +1 -1
  53. package/dist/{chunk-4HBYEZ3I.cjs → chunk-SA4KQCNB.cjs} +82 -82
  54. package/dist/{chunk-4HBYEZ3I.cjs.map → chunk-SA4KQCNB.cjs.map} +1 -1
  55. package/dist/{chunk-RF2EYX3B.js → chunk-SC44YJCF.js} +2 -2
  56. package/dist/{chunk-2OD2PPNO.cjs → chunk-SDORFSFK.cjs} +37 -37
  57. package/dist/{chunk-2OD2PPNO.cjs.map → chunk-SDORFSFK.cjs.map} +1 -1
  58. package/dist/{chunk-42RQ4NIL.js → chunk-TSNGVWQO.js} +6 -6
  59. package/dist/{chunk-DMXI5D2Q.cjs → chunk-USKQURNF.cjs} +3 -3
  60. package/dist/{chunk-DMXI5D2Q.cjs.map → chunk-USKQURNF.cjs.map} +1 -1
  61. package/dist/{chunk-2KILTCND.js → chunk-UVUNBXS6.js} +6 -1
  62. package/dist/chunk-UVUNBXS6.js.map +1 -0
  63. package/dist/{chunk-5CBQBTGN.cjs → chunk-WZYIAKIH.cjs} +7 -7
  64. package/dist/{chunk-5CBQBTGN.cjs.map → chunk-WZYIAKIH.cjs.map} +1 -1
  65. package/dist/{chunk-2V4KPBNZ.js → chunk-XAOAYDGR.js} +3 -3
  66. package/dist/{chunk-U4OJCJTH.js → chunk-XBYF4SGD.js} +4 -4
  67. package/dist/{chunk-FCQR6VY4.cjs → chunk-Y3BC7TOJ.cjs} +26 -26
  68. package/dist/{chunk-FCQR6VY4.cjs.map → chunk-Y3BC7TOJ.cjs.map} +1 -1
  69. package/dist/{chunk-P27OGSQL.cjs → chunk-Y3Q76RAA.cjs} +37 -37
  70. package/dist/{chunk-P27OGSQL.cjs.map → chunk-Y3Q76RAA.cjs.map} +1 -1
  71. package/dist/{chunk-4BUPMSLY.js → chunk-Y7ZEVTLC.js} +3 -3
  72. package/dist/{chunk-Z622ACFD.js → chunk-Y7ZQZOE6.js} +5 -5
  73. package/dist/{chunk-5A3WSWZD.js → chunk-Z5FHXCXF.js} +8 -8
  74. package/dist/{chunk-UFXV64WQ.cjs → chunk-ZLYLHP64.cjs} +9 -9
  75. package/dist/{chunk-UFXV64WQ.cjs.map → chunk-ZLYLHP64.cjs.map} +1 -1
  76. package/dist/components/case-studies/index.cjs +20 -20
  77. package/dist/components/case-studies/index.js +7 -7
  78. package/dist/components/chat/index.cjs +4 -4
  79. package/dist/components/chat/index.js +3 -3
  80. package/dist/components/contact/index.cjs +8 -8
  81. package/dist/components/contact/index.js +7 -7
  82. package/dist/components/docs/index.cjs +11 -11
  83. package/dist/components/docs/index.js +10 -10
  84. package/dist/components/embeds/index.cjs +8 -8
  85. package/dist/components/embeds/index.js +7 -7
  86. package/dist/components/faq/index.cjs +9 -9
  87. package/dist/components/faq/index.js +8 -8
  88. package/dist/components/features/index.cjs +7 -7
  89. package/dist/components/features/index.js +6 -6
  90. package/dist/components/help-center-pages/index.cjs +101 -101
  91. package/dist/components/help-center-pages/index.js +21 -21
  92. package/dist/components/index.cjs +65 -65
  93. package/dist/components/index.js +21 -21
  94. package/dist/components/meeting-scheduler/index.cjs +58 -58
  95. package/dist/components/meeting-scheduler/index.js +8 -8
  96. package/dist/components/navigation/index.cjs +10 -10
  97. package/dist/components/navigation/index.js +9 -9
  98. package/dist/components/onboarding-guides/index.cjs +11 -11
  99. package/dist/components/onboarding-guides/index.js +10 -10
  100. package/dist/components/related-content/index.cjs +6 -6
  101. package/dist/components/related-content/index.js +5 -5
  102. package/dist/components/tickets/index.cjs +12 -12
  103. package/dist/components/tickets/index.js +11 -11
  104. package/dist/components/tickets/ticket-live-provider.d.ts.map +1 -1
  105. package/dist/components/ui/index.cjs +7 -7
  106. package/dist/components/ui/index.js +6 -6
  107. package/dist/hooks/index.cjs +4 -4
  108. package/dist/hooks/index.js +3 -3
  109. package/dist/index.cjs +12 -12
  110. package/dist/index.js +10 -10
  111. package/dist/utils/embed-authed-fetch.d.ts +18 -0
  112. package/dist/utils/embed-authed-fetch.d.ts.map +1 -1
  113. package/dist/utils/index.cjs +50 -6
  114. package/dist/utils/index.cjs.map +1 -1
  115. package/dist/utils/index.js +50 -6
  116. package/dist/utils/index.js.map +1 -1
  117. package/dist/utils/sse-subscription.d.ts +29 -11
  118. package/dist/utils/sse-subscription.d.ts.map +1 -1
  119. package/package.json +1 -1
  120. package/src/components/tickets/ticket-live-provider.tsx +14 -12
  121. package/src/utils/embed-authed-fetch.ts +21 -0
  122. package/src/utils/sse-subscription.ts +107 -20
  123. package/dist/chunk-2KILTCND.js.map +0 -1
  124. package/dist/chunk-AIXPQT3G.js.map +0 -1
  125. package/dist/chunk-TBJWPNHU.js.map +0 -1
  126. package/dist/chunk-VLWUKFQE.cjs.map +0 -1
  127. package/dist/chunk-YCKGUQA2.cjs.map +0 -1
  128. package/dist/chunk-ZBQZJAC7.cjs.map +0 -1
  129. /package/dist/{chunk-22RSJ57Z.js.map → chunk-2YVTGPM7.js.map} +0 -0
  130. /package/dist/{chunk-V5NOURSR.js.map → chunk-4HZXOQRM.js.map} +0 -0
  131. /package/dist/{chunk-XRSJER2B.js.map → chunk-6Z26ZMR4.js.map} +0 -0
  132. /package/dist/{chunk-HLEXYTOE.js.map → chunk-7EPGJOKN.js.map} +0 -0
  133. /package/dist/{chunk-DOUG4UEZ.js.map → chunk-7WYWPELF.js.map} +0 -0
  134. /package/dist/{chunk-DLRE5Y3G.js.map → chunk-C2SVJVEB.js.map} +0 -0
  135. /package/dist/{chunk-OC3QH5WG.js.map → chunk-CMYDGVTZ.js.map} +0 -0
  136. /package/dist/{chunk-2PQKZWWP.js.map → chunk-DC3G4XGP.js.map} +0 -0
  137. /package/dist/{chunk-OQVHGPHA.js.map → chunk-DZMDG7PC.js.map} +0 -0
  138. /package/dist/{chunk-S5M5SEW2.js.map → chunk-FNMQ2J44.js.map} +0 -0
  139. /package/dist/{chunk-UAKWSR3U.js.map → chunk-GDZ7IC5B.js.map} +0 -0
  140. /package/dist/{chunk-Q76YVNCQ.js.map → chunk-HM7ZGABI.js.map} +0 -0
  141. /package/dist/{chunk-25X43LSD.js.map → chunk-JSG7WZKV.js.map} +0 -0
  142. /package/dist/{chunk-TSENCZUL.js.map → chunk-MJI5GVT7.js.map} +0 -0
  143. /package/dist/{chunk-RF2EYX3B.js.map → chunk-SC44YJCF.js.map} +0 -0
  144. /package/dist/{chunk-42RQ4NIL.js.map → chunk-TSNGVWQO.js.map} +0 -0
  145. /package/dist/{chunk-2V4KPBNZ.js.map → chunk-XAOAYDGR.js.map} +0 -0
  146. /package/dist/{chunk-U4OJCJTH.js.map → chunk-XBYF4SGD.js.map} +0 -0
  147. /package/dist/{chunk-4BUPMSLY.js.map → chunk-Y7ZEVTLC.js.map} +0 -0
  148. /package/dist/{chunk-Z622ACFD.js.map → chunk-Y7ZQZOE6.js.map} +0 -0
  149. /package/dist/{chunk-5A3WSWZD.js.map → chunk-Z5FHXCXF.js.map} +0 -0
@@ -453,6 +453,10 @@ function setEmbedAuthAdapter(adapter2) {
453
453
  function hasEmbedAuthAdapter() {
454
454
  return getRegisteredAuthAdapter() !== null;
455
455
  }
456
+ function subscribeEmbedCredentialChange(listener) {
457
+ return getRegisteredAuthAdapter()?.subscribe?.(listener) ?? (() => {
458
+ });
459
+ }
456
460
  function needsBearerAssetFetch(url) {
457
461
  if (typeof window === "undefined") return false;
458
462
  const adapter2 = getRegisteredAuthAdapter();
@@ -704,6 +708,7 @@ export {
704
708
  applyProxyAuth,
705
709
  setEmbedAuthAdapter,
706
710
  hasEmbedAuthAdapter,
711
+ subscribeEmbedCredentialChange,
707
712
  embedAuthedFetch,
708
713
  clearAuthedAssetCache,
709
714
  useAuthedAssetSrc,
@@ -712,4 +717,4 @@ export {
712
717
  buildOgPlaceholderUrl,
713
718
  useOgPlaceholderUrl
714
719
  };
715
- //# sourceMappingURL=chunk-2KILTCND.js.map
720
+ //# sourceMappingURL=chunk-UVUNBXS6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/hooks/ui/use-image-edge-color.ts","../src/utils/platform-identity.ts","../src/utils/app-config.ts","../src/utils/local-storage-adapter.ts","../src/utils/embed-proxy-auth-storage.ts","../src/utils/embed-authed-fetch.ts","../src/hooks/use-authed-asset-src.ts","../src/hooks/use-authed-image-src.ts","../src/utils/og-placeholder.ts","../src/hooks/use-og-placeholder-url.ts"],"sourcesContent":["'use client';\n\nimport { useState, useEffect } from 'react';\n\n/**\n * Extract the dominant edge color from an image using color bucketing.\n * Samples pixels along left and right edges (the visible letterbox areas),\n * groups them into color buckets, and returns the most common bucket.\n * This avoids muddy averages when edges have mixed colors (e.g., sky + sand).\n */\nfunction extractEdgeColor(img: HTMLImageElement): string {\n const canvas = document.createElement('canvas');\n const ctx = canvas.getContext('2d');\n if (!ctx) return '#000000';\n\n const maxSize = 100;\n const scale = Math.min(maxSize / img.naturalWidth, maxSize / img.naturalHeight);\n const w = Math.round(img.naturalWidth * scale);\n const h = Math.round(img.naturalHeight * scale);\n canvas.width = w;\n canvas.height = h;\n\n ctx.drawImage(img, 0, 0, w, h);\n\n const data = ctx.getImageData(0, 0, w, h).data;\n\n // Sample left edge, right edge, top edge, bottom edge (15% band)\n const edgeW = Math.max(2, Math.round(w * 0.15));\n const edgeH = Math.max(2, Math.round(h * 0.15));\n\n // Color bucketing: quantize to 32-step buckets for grouping similar colors\n const bucketSize = 32;\n const buckets = new Map<string, { r: number; g: number; b: number; count: number }>();\n\n for (let y = 0; y < h; y++) {\n for (let x = 0; x < w; x++) {\n const isEdge = x < edgeW || x >= w - edgeW || y < edgeH || y >= h - edgeH;\n\n if (!isEdge) continue;\n\n const i = (y * w + x) * 4;\n const a = data[i + 3];\n if (a < 128) continue;\n\n const r = data[i];\n const g = data[i + 1];\n const b = data[i + 2];\n\n // Quantize to bucket\n const br = Math.floor(r / bucketSize) * bucketSize;\n const bg = Math.floor(g / bucketSize) * bucketSize;\n const bb = Math.floor(b / bucketSize) * bucketSize;\n const key = `${br},${bg},${bb}`;\n\n const existing = buckets.get(key);\n if (existing) {\n existing.r += r;\n existing.g += g;\n existing.b += b;\n existing.count++;\n } else {\n buckets.set(key, { r, g, b, count: 1 });\n }\n }\n }\n\n if (buckets.size === 0) return '#000000';\n\n // Find the most common color bucket\n let bestBucket: { r: number; g: number; b: number; count: number } | null = null;\n for (const bucket of buckets.values()) {\n if (!bestBucket || bucket.count > bestBucket.count) {\n bestBucket = bucket;\n }\n }\n\n if (!bestBucket || bestBucket.count === 0) return '#000000';\n\n // Return average color within the winning bucket\n const r = Math.round(bestBucket.r / bestBucket.count);\n const g = Math.round(bestBucket.g / bestBucket.count);\n const b = Math.round(bestBucket.b / bestBucket.count);\n\n return `rgb(${r}, ${g}, ${b})`;\n}\n\n/**\n * Hook that extracts the dominant edge color from an image URL.\n * Returns a CSS color string for use as a background behind object-contain images.\n *\n * Always sets crossOrigin='anonymous' — required for canvas pixel access.\n * Most CDNs (Supabase, Cloudflare, our image proxy) return CORS headers.\n * If the server doesn't support CORS, onerror fires and we use the fallback.\n *\n * @param imageUrl - URL of the image to analyze\n * @param fallback - Fallback color if extraction fails (default: '#000000')\n * @returns CSS color string (e.g., 'rgb(34, 28, 22)')\n */\nexport function useImageEdgeColor(imageUrl: string | undefined | null, fallback = '#000000'): string {\n const [color, setColor] = useState(fallback);\n\n // Losing the url (or being given a different fallback) is a prop-driven\n // reset, so it happens while rendering — React's documented alternative to a\n // sync-from-props effect. From the effect it would commit one frame with the\n // departed image's colour still painted behind an image that is already gone.\n const [resetFor, setResetFor] = useState({ imageUrl, fallback });\n if (resetFor.imageUrl !== imageUrl || resetFor.fallback !== fallback) {\n setResetFor({ imageUrl, fallback });\n if (!imageUrl) setColor(fallback);\n }\n\n useEffect(() => {\n if (!imageUrl) return undefined;\n\n let cancelled = false;\n const img = new Image();\n img.crossOrigin = 'anonymous';\n\n img.onload = () => {\n if (cancelled) return;\n try {\n setColor(extractEdgeColor(img));\n } catch {\n setColor(fallback);\n }\n };\n\n img.onerror = () => {\n if (cancelled) return;\n setColor(fallback);\n };\n\n img.src = imageUrl;\n\n return () => {\n cancelled = true;\n };\n }, [imageUrl, fallback]);\n\n return color;\n}\n","/**\n * Platform identity + brand SSOT (single source of truth).\n *\n * ONE module owns every per-platform STRING (display/short/long name, description,\n * slogan, icon name) and every per-platform COLOUR DECISION (the accent/secondary\n * \"stem\", the theme, the surface). Everything downstream DERIVES from here:\n * `platform-config.tsx`'s icon + colour maps, `PlatformBadge`, `getPlatformConfig`,\n * the hub's `AppConfig.brandColors`, the hub's manifest generator and `theme-color`.\n *\n * JSX-FREE + zero-dependency ON PURPOSE: it carries its own tsup entry and\n * `exports` subpath (`@flamingo-stack/openframe-frontend-core/utils/platform-identity`)\n * so hub SCRIPTS and `server-only` modules can import it without dragging React,\n * the icon components, or the utils barrel into their graph.\n *\n * TWO accepted by-construction copies of the colour decision remain, both LOCKED\n * by `src/__tests__/mlg-platform-leaves.test.ts` in this package:\n * 1. `platformHexColors` below (hand-typed hex mirror of each accent token).\n * 2. The `[data-app-type='…']` blocks in `src/styles/ods-colors.css`.\n */\n\nimport type { PlatformName } from '../types/platform';\n\n// ── Strings ─────────────────────────────────────────────────────────────────\n\n/** Human-readable platform names. THE naming source for lib-side presentation. */\nexport const platformDisplayNames = {\n openmsp: 'OpenMSP',\n openframe: 'OpenFrame',\n flamingo: 'Flamingo',\n 'flamingo-teaser': 'Flamingo Teaser',\n 'marketing-hub': 'Flamingo Marketing Hub',\n 'product-hub': 'Flamingo Product Hub',\n 'revenue-hub': 'Flamingo Revenue Hub',\n 'people-hub': 'Flamingo People Hub',\n 'company-hub': 'Flamingo Company Hub',\n tmcg: 'TMCG',\n mlg: 'Major League GitHub',\n universal: 'Universal',\n};\n\n/**\n * Short-name OVERRIDES only. A platform without a row falls back to its display\n * name, so this map never lists a name that equals `platformDisplayNames`.\n */\nexport const platformShortNames: Partial<Record<PlatformName, string>> = {\n mlg: 'MLG',\n};\n\nexport const platformDescriptions = {\n openmsp:\n 'Comprehensive directory and comparison platform for managed service providers (MSPs) and technology vendors. Reduce vendor costs and discover open-source alternatives.',\n openframe: 'AI-driven open-source security operations center (SOC) and endpoint detection platform for MSPs.',\n flamingo:\n 'AI-driven open-source OS for MSPs. Swap bloated vendor tools for open ones. Automate the boring crap. Take your margin back.',\n 'flamingo-teaser': 'Preview of Flamingo - the AI-driven open-source OS for MSPs.',\n tmcg: 'The Miami Cyber Gang - A cybersecurity community focused on education and collaboration.',\n mlg: 'Scouting report for US open-source talent. Rank GitHub contributors by programming language, city, state, region, and nearest Major League Soccer club.',\n universal: 'Cross-platform universal content.',\n};\n\nexport const platformSlogans = {\n openmsp: 'Find Your Perfect MSP Partner',\n openframe: 'Open-Source Security Operations',\n flamingo: 'Open-Source OS for MSPs',\n 'flamingo-teaser': 'Coming Soon: Open-Source OS for MSPs',\n tmcg: 'Miami Cyber Community',\n mlg: 'GitHub Scouting Report: Major League Edition',\n universal: 'Universal Platform',\n};\n\nexport const platformIconNames = {\n openmsp: 'openmsp-logo',\n openframe: 'openframe-logo',\n flamingo: 'flamingo-logo',\n universal: 'globe',\n 'flamingo-teaser': 'flamingo-logo',\n 'marketing-hub': 'flamingo-logo',\n 'product-hub': 'flamingo-logo',\n 'revenue-hub': 'flamingo-logo',\n 'people-hub': 'flamingo-logo',\n 'company-hub': 'flamingo-logo',\n tmcg: 'tmcg-logo',\n mlg: 'mlg-logo',\n};\n\nexport function getPlatformDisplayName(platformName: string): string {\n return platformDisplayNames[platformName as keyof typeof platformDisplayNames] || platformName;\n}\n\n/** Compact label (nav chips, manifest `short_name`, JSON-LD `alternateName`). */\nexport function getPlatformShortName(platformName: string): string {\n return platformShortNames[platformName as PlatformName] ?? getPlatformDisplayName(platformName);\n}\n\nexport function getPlatformDescription(platformName: string): string {\n return platformDescriptions[platformName as keyof typeof platformDescriptions] || platformName;\n}\n\nexport function getPlatformSlogan(platformName: string): string {\n return platformSlogans[platformName as keyof typeof platformSlogans] || platformName;\n}\n\nexport function getDefaultIconForPlatform(platformName: string): string {\n return platformIconNames[platformName as keyof typeof platformIconNames] || platformIconNames.universal;\n}\n\n// ── Colour stems ────────────────────────────────────────────────────────────\n\n/**\n * The six brand/attention colours (plus the neutral) a platform may adopt, named\n * by STEM rather than by token so the token spelling has exactly one home below.\n */\nexport type OdsColorStem =\n 'flamingo-pink' | 'flamingo-cyan' | 'open-yellow' | 'success' | 'warning' | 'error' | 'text-secondary';\n\n/** Stem → ODS token name (without the `--ods-` prefix). THE token spelling. */\nexport const ODS_STEM_TOKENS: Record<OdsColorStem, string> = {\n 'flamingo-pink': 'flamingo-pink-base',\n 'flamingo-cyan': 'flamingo-cyan-base',\n 'open-yellow': 'open-yellow-base',\n success: 'attention-green-success',\n warning: 'attention-yellow-warning',\n error: 'attention-red-error',\n 'text-secondary': 'system-greys-grey',\n};\n\n/**\n * Stem → Tailwind class strings. STATIC LITERALS on purpose: the Tailwind\n * scanner reads source text, so a template-built class would be purged.\n * This is the ONLY place a stem-to-class literal exists.\n */\nexport const ODS_STEM_CLASSES: Record<OdsColorStem, { text: string; bg: string; bgSoft: string; borderSoft: string }> =\n {\n 'flamingo-pink': {\n text: 'text-ods-flamingo-pink',\n bg: 'bg-ods-flamingo-pink',\n bgSoft: 'bg-ods-flamingo-pink/10',\n borderSoft: 'border-ods-flamingo-pink/30',\n },\n 'flamingo-cyan': {\n text: 'text-ods-flamingo-cyan',\n bg: 'bg-ods-flamingo-cyan',\n bgSoft: 'bg-ods-flamingo-cyan/10',\n borderSoft: 'border-ods-flamingo-cyan/30',\n },\n 'open-yellow': {\n text: 'text-ods-open-yellow',\n bg: 'bg-ods-open-yellow',\n bgSoft: 'bg-ods-open-yellow/10',\n borderSoft: 'border-ods-open-yellow/30',\n },\n success: {\n text: 'text-ods-success',\n bg: 'bg-ods-success',\n bgSoft: 'bg-ods-success/10',\n borderSoft: 'border-ods-success/30',\n },\n warning: {\n text: 'text-ods-warning',\n bg: 'bg-ods-warning',\n bgSoft: 'bg-ods-warning/10',\n borderSoft: 'border-ods-warning/30',\n },\n error: {\n text: 'text-ods-error',\n bg: 'bg-ods-error',\n bgSoft: 'bg-ods-error/10',\n borderSoft: 'border-ods-error/30',\n },\n 'text-secondary': {\n text: 'text-ods-text-secondary',\n bg: 'bg-ods-text-secondary',\n bgSoft: 'bg-ods-text-secondary/10',\n borderSoft: 'border-ods-text-secondary/30',\n },\n };\n\n// ── Theme + surface ─────────────────────────────────────────────────────────\n\n/**\n * Every platform ships the dark theme today (the hub's flamingo config says so\n * explicitly, and every `[data-app-type]` block inherits the dark `:root`).\n * ONE constant instead of a per-platform field: reintroduce the field only when\n * a platform actually differs.\n */\nexport const PLATFORM_THEME = 'dark' as const;\n\n/** The dark surface's tokens, consumed by config `brandColors` and the manifest. */\nexport const PLATFORM_SURFACE = {\n background: 'system-greys-background',\n text: 'system-greys-white',\n} as const;\n\nexport const getPlatformBackgroundVarName = (): string => `--ods-${PLATFORM_SURFACE.background}`;\nexport const getPlatformTextVarName = (): string => `--ods-${PLATFORM_SURFACE.text}`;\n\n// ── Brand record ────────────────────────────────────────────────────────────\n\nexport interface PlatformBrand {\n /** Primary brand colour (`--color-accent-primary` in the platform's CSS block). */\n accentStem: OdsColorStem;\n /** Link colour (`--color-link`). Equals `accentStem` for most platforms. */\n secondaryStem: OdsColorStem;\n}\n\n/**\n * THE accent/link table. Every row is verified against that platform's\n * `[data-app-type='…']` block in `ods-colors.css` by the hub's parity test.\n */\nexport const PLATFORM_BRAND: Record<PlatformName, PlatformBrand> = {\n openframe: { accentStem: 'open-yellow', secondaryStem: 'flamingo-cyan' },\n openmsp: { accentStem: 'open-yellow', secondaryStem: 'open-yellow' },\n flamingo: { accentStem: 'flamingo-pink', secondaryStem: 'flamingo-pink' },\n 'flamingo-teaser': { accentStem: 'flamingo-pink', secondaryStem: 'flamingo-pink' },\n tmcg: { accentStem: 'flamingo-pink', secondaryStem: 'flamingo-pink' },\n 'marketing-hub': { accentStem: 'flamingo-pink', secondaryStem: 'flamingo-pink' },\n 'product-hub': { accentStem: 'success', secondaryStem: 'success' },\n 'revenue-hub': { accentStem: 'warning', secondaryStem: 'warning' },\n 'people-hub': { accentStem: 'flamingo-cyan', secondaryStem: 'flamingo-cyan' },\n 'company-hub': { accentStem: 'error', secondaryStem: 'error' },\n mlg: { accentStem: 'flamingo-pink', secondaryStem: 'flamingo-cyan' },\n universal: { accentStem: 'text-secondary', secondaryStem: 'text-secondary' },\n};\n\n/** The brand row for a platform, falling back to `universal` for anything unknown. */\nexport function getPlatformBrand(platformName: string): PlatformBrand {\n return PLATFORM_BRAND[platformName as PlatformName] ?? PLATFORM_BRAND.universal;\n}\n\nexport function platformAccentToken(platformName: string): string {\n return ODS_STEM_TOKENS[getPlatformBrand(platformName).accentStem];\n}\n\nexport function platformSecondaryToken(platformName: string): string {\n return ODS_STEM_TOKENS[getPlatformBrand(platformName).secondaryStem];\n}\n\n/** `--ods-…` CSS variable NAME for a platform's accent (input to `resolveOdsColor`). */\nexport function platformAccentVarName(platformName: string): string {\n return `--ods-${platformAccentToken(platformName)}`;\n}\n\nexport function platformSecondaryVarName(platformName: string): string {\n return `--ods-${platformSecondaryToken(platformName)}`;\n}\n\n/** `var(--ods-…)` VALUE for a platform's accent (config `brandColors` data). */\nexport function getPlatformAccentColor(platformName: string): string {\n return `var(${platformAccentVarName(platformName)})`;\n}\n\nexport function getPlatformSecondaryColor(platformName: string): string {\n return `var(${platformSecondaryVarName(platformName)})`;\n}\n\n/** Tailwind classes for a platform, DERIVED from its stems. */\nexport function getPlatformBrandClasses(platformName: string): {\n accentText: string;\n accentBg: string;\n accentBgSoft: string;\n accentBorderSoft: string;\n secondaryText: string;\n} {\n const brand = getPlatformBrand(platformName);\n const accent = ODS_STEM_CLASSES[brand.accentStem];\n const secondary = ODS_STEM_CLASSES[brand.secondaryStem];\n return {\n accentText: accent.text,\n accentBg: accent.bg,\n accentBgSoft: accent.bgSoft,\n accentBorderSoft: accent.borderSoft,\n secondaryText: secondary.text,\n };\n}\n\n/** Platform background classes, DERIVED (was a hand-typed hex map). */\nexport const platformColors: Record<PlatformName, string> = Object.fromEntries(\n (Object.keys(PLATFORM_BRAND) as PlatformName[]).map(p => [p, getPlatformBrandClasses(p).accentBg]),\n) as Record<PlatformName, string>;\n\nexport function getPlatformColor(platformName: string) {\n return platformColors[platformName as PlatformName] || platformColors.universal;\n}\n\n/**\n * Hand-typed hex mirror of each platform's accent token (the DB `default_color`\n * and other JS-value consumers). Accepted copy #1 — locked by\n * `src/__tests__/mlg-platform-leaves.test.ts` in this package, which asserts\n * every row against the `[data-app-type]` blocks in `ods-colors.css`.\n * (The hub has its OWN parity test, but it holds different copies: the asset\n * generator's map and the generated manifests.)\n */\nexport const platformHexColors: Record<PlatformName, string> = {\n openmsp: '#ffc008',\n openframe: '#ffc008',\n flamingo: '#f357bb',\n universal: '#888888',\n 'flamingo-teaser': '#f357bb',\n 'marketing-hub': '#f357bb',\n 'product-hub': '#5ea62e',\n 'revenue-hub': '#e1b32f',\n 'people-hub': '#5efaf0',\n 'company-hub': '#f36666',\n tmcg: '#f357bb',\n mlg: '#f357bb',\n};\n\nexport function getDefaultColorForPlatform(platformName: string): string {\n return platformHexColors[platformName as PlatformName] || platformHexColors.universal;\n}\n","// Stub app config\nexport const APP_CONFIG = {\n app: {\n type: 'openmsp',\n name: 'OpenMSP',\n domain: 'openmsp.ai',\n },\n features: {\n announcements: true,\n notifications: true,\n },\n} as const;\n\nexport function getAppConfig() {\n return APP_CONFIG;\n}\n\nexport function getAppType() {\n // The exact `process.env.NEXT_PUBLIC_APP_TYPE` member expression is kept so\n // Next/webpack can statically inline it. The try/catch makes the call safe\n // in non-Next React hosts (Vite/CRA embeds) where `process` does not exist\n // at runtime — those hosts get the 'openmsp' fallback, which is fine for\n // embeds: dismissal cookies are domain-scoped and an embed domain serves\n // one platform's announcements.\n try {\n return process.env.NEXT_PUBLIC_APP_TYPE || 'openmsp';\n } catch {\n return 'openmsp';\n }\n}\n","/**\n * Thin JSON-typed Web-Storage adapter (localStorage or sessionStorage).\n *\n * Centralizes the SSR-guard + try/catch + silent quota-failure pattern\n * that every per-feature storage util would otherwise re-implement.\n *\n * Optional `namespace` prefix supports platform / user partitioning —\n * the resolver runs lazily at call time so the namespace can vary across\n * the lifetime of the page (e.g. proxy-auth switches user, which switches\n * the key suffix).\n *\n * Backend selection (`backend: 'local' | 'session'`):\n * - `'local'` (default): persists across browser sessions. Use for\n * UI state, chat history metadata, feature-flag opt-ins.\n * - `'session'`: cleared when the tab closes. Use for ANY auth-adjacent\n * value (bearer tokens, act-as identity, proxy credentials). Reduces\n * the XSS-exfiltration attack window from \"indefinite\" to \"until tab\n * close\" without losing per-session ergonomics.\n */\n\nexport type WebStorageBackend = 'local' | 'session';\n\nexport interface LocalStorageAdapter<T> {\n load(): T | null;\n save(value: T): void;\n clear(): void;\n /** Resolved storage key for the current call. Useful for tests. */\n resolveKey(): string;\n}\n\nexport interface LocalStorageAdapterOptions<T> {\n /** Base storage key. Combined with `namespace()` when provided. */\n key: string;\n /** Optional dynamic namespace prefix appended via `.` separator.\n * Called on EVERY read/write so the key can vary across the page\n * lifetime (e.g. when the platform or user identity changes). */\n namespace?: () => string | null | undefined;\n /** Runtime shape check. Falsey return → `load()` yields null. */\n validate?: (parsed: unknown) => parsed is T;\n /** Diagnostic prefix written to `console.warn` on parse / write\n * failures. Defaults to `'[local-storage]'`. */\n logTag?: string;\n /** Which Web-Storage backend to use. Defaults to `'local'`. Pass\n * `'session'` for anything auth-adjacent so the value evaporates\n * when the tab closes. */\n backend?: WebStorageBackend;\n}\n\nfunction getStorage(backend: WebStorageBackend): Storage | null {\n if (typeof window === 'undefined') return null;\n try {\n return backend === 'session' ? window.sessionStorage : window.localStorage;\n } catch {\n // Some sandboxed contexts (Safari private mode older versions,\n // strict CSP) throw on storage access — treat as unavailable.\n return null;\n }\n}\n\nexport function createLocalStorageAdapter<T>(options: LocalStorageAdapterOptions<T>): LocalStorageAdapter<T> {\n const tag = options.logTag ?? '[local-storage]';\n const backend: WebStorageBackend = options.backend ?? 'local';\n const resolveKey = (): string => {\n const ns = options.namespace?.();\n return ns ? `${ns}.${options.key}` : options.key;\n };\n\n return {\n resolveKey,\n load() {\n const storage = getStorage(backend);\n if (!storage) return null;\n try {\n const raw = storage.getItem(resolveKey());\n if (!raw) return null;\n const parsed = JSON.parse(raw) as unknown;\n if (options.validate && !options.validate(parsed)) return null;\n return parsed as T;\n } catch (err) {\n console.warn(`${tag} parse failed for key ${resolveKey()}:`, err);\n return null;\n }\n },\n save(value: T) {\n const storage = getStorage(backend);\n if (!storage) return;\n try {\n storage.setItem(resolveKey(), JSON.stringify(value));\n } catch (err) {\n console.warn(`${tag} write failed for key ${resolveKey()}:`, err);\n }\n },\n clear() {\n const storage = getStorage(backend);\n if (!storage) return;\n try {\n storage.removeItem(resolveKey());\n } catch (err) {\n console.warn(`${tag} clear failed for key ${resolveKey()}:`, err);\n }\n },\n };\n}\n","'use client';\n\n/**\n * Client-side persistence for embed-surface proxy credentials (a\n * PLATFORM API KEY + impersonation email). Used by every embedded\n * surface — the chat widget AND the ticket center AND any future\n * embedded React component that needs to identify itself as the\n * impersonated customer.\n *\n * When set, the surface attaches the creds as\n * `Authorization: Bearer <platform API key>` + `X-Chat-Act-As: <email>`\n * on every call to `/api/docs/chat`, `/api/chat/*`, and any other route\n * gated by `requireChatAuth` — proving to the server that this session\n * is acting on behalf of <email>. The key is minted in the hub's\n * `/admin/api-keys` and is platform-bound (`fpk_<platform>_…`): a\n * SERVICE key may act as any email; a PERSONAL key only as its owner.\n *\n * **Naming history:** the stored field is still named `secret` and the\n * wire-side header names are `X-Chat-*`. Those are client/server\n * contracts predating the per-platform key migration (the old shared\n * `CHAT_PROXY_SECRET` env is removed hub-side); renaming them would\n * break persisted state + the wire shape for zero behavior change. The\n * CLIENT-side helpers were renamed `Embed*` so non-chat surfaces (e.g.\n * ticket center) don't have to import a chat-prefixed symbol just to\n * send the same headers.\n *\n * Persists to **`localStorage`** so the bearer token + act-as identity\n * survive tab close, new-tab opens, and browser restarts — the\n * `/debug` paste-creds UI is an admin tool and re-pasting every tab\n * cycle was rejected as a dev-experience tradeoff that wasn't worth\n * the security gain. An XSS sink on this origin can read the value\n * indefinitely (vs only-this-tab with sessionStorage), but `/debug`\n * is admin-gated behind the platform's `askAI.enabled` flag, the key\n * is server-verified (hash-at-rest, platform-bound, revocable in\n * `/admin/api-keys`), and the act-as reach is bounded by the key's\n * tier. Explicit \"Clear\" button on the creds bar is the supported\n * logout path; closing the tab is no longer.\n *\n * Namespaced under `<platform>.chat.proxy-auth.v1` (the storage key is\n * unchanged from the old chat-prefixed helper — that's a storage\n * contract; renaming it would log everyone out).\n */\n\nimport { getAppType } from './app-config';\nimport { createLocalStorageAdapter } from './local-storage-adapter';\n\n/**\n * Every OPTIONAL header of the chat-proxy contract, with the field it is stored\n * in: the ONE list storage, validation and `applyProxyAuth` all loop over, so a\n * new header is one row. `identity` rows are the acting user's display identity\n * (the hub threads them through `resolveChatProxyIdentity`); `visitor` rows are\n * the visitor context a gateway forwards (honoured hub-side only from a SERVICE\n * key). Empty or omitted = not sent.\n */\nexport const EMBED_PROXY_OPTIONAL_HEADERS = [\n { field: 'firstName', group: 'identity', header: 'X-Chat-First-Name' },\n { field: 'lastName', group: 'identity', header: 'X-Chat-Last-Name' },\n { field: 'avatarUrl', group: 'identity', header: 'X-Chat-Avatar-Url' },\n { field: 'originUrl', group: 'visitor', header: 'X-Chat-Origin-Url' },\n { field: 'ip', group: 'visitor', header: 'X-Chat-Ip' },\n { field: 'country', group: 'visitor', header: 'X-Chat-Country' },\n { field: 'visitorId', group: 'visitor', header: 'X-Chat-User-Id' },\n] as const;\n\nexport type EmbedProxyOptionalField = (typeof EMBED_PROXY_OPTIONAL_HEADERS)[number]['field'];\n\nexport type EmbedProxyAuth = {\n secret: string;\n email: string;\n} & Partial<Record<EmbedProxyOptionalField, string>>;\n\nfunction isValidPersistedAuth(value: unknown): value is EmbedProxyAuth {\n if (!value || typeof value !== 'object') return false;\n const v = value as Record<string, unknown>;\n if (\n typeof v.secret !== 'string' ||\n v.secret.trim().length === 0 ||\n typeof v.email !== 'string' ||\n v.email.trim().length === 0\n )\n return false;\n // Optional fields: when present must be strings. Empty string is treated\n // as absent later (in `getEmbedProxyAuth`).\n return EMBED_PROXY_OPTIONAL_HEADERS.every(({ field }) => v[field] == null || typeof v[field] === 'string');\n}\n\nconst adapter = createLocalStorageAdapter<EmbedProxyAuth>({\n // Storage key unchanged from the legacy chat-prefixed helper. Renaming\n // it would silently log every existing admin out — the key is a\n // storage contract, not a code identifier.\n key: 'chat.proxy-auth.v1',\n namespace: () => getAppType(),\n validate: isValidPersistedAuth,\n logTag: '[embed-proxy-auth-storage]',\n // localStorage — survives tab close, new tabs, and browser restarts.\n // Admin re-pasting creds every tab cycle was the dev-experience\n // tradeoff prior `sessionStorage` setup demanded — rejected. See\n // file-level doc comment for the security tradeoff rationale.\n backend: 'local',\n});\n\n/** Trim + null-coerce an optional identity field so consumers can do\n * `auth.firstName ?? ''` without worrying about whitespace-only strings. */\nfunction normalizeOptional(value: string | undefined): string | undefined {\n if (!value) return undefined;\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n}\n\n/** Every optional field of `value`, trimmed, blank ones dropped. */\nfunction normalizedOptionalFields(value: Partial<Record<EmbedProxyOptionalField, string>>) {\n const out: Partial<Record<EmbedProxyOptionalField, string>> = {};\n for (const { field } of EMBED_PROXY_OPTIONAL_HEADERS) {\n const normalized = normalizeOptional(value[field]);\n if (normalized) out[field] = normalized;\n }\n return out;\n}\n\n/**\n * Returns full credentials (secret + email + optional identity passthrough)\n * when secret + email are available. Returns `null` when nothing is saved —\n * callers treat that as \"fall back to cookie auth\".\n */\nexport function getEmbedProxyAuth(): EmbedProxyAuth | null {\n const persisted = adapter.load();\n if (!persisted) return null;\n return {\n secret: persisted.secret,\n email: persisted.email.trim().toLowerCase(),\n ...normalizedOptionalFields(persisted),\n };\n}\n\n/**\n * Returns the LAST email the admin saved. The proxy creds bar reads\n * this to pre-fill the email field on mount.\n */\nexport function getPersistedProxyEmail(): string | null {\n const persisted = adapter.load();\n return persisted?.email.trim().toLowerCase() ?? null;\n}\n\n/** Save the proxy creds. Secret + email are required; identity-passthrough\n * fields are persisted only when non-empty. */\nexport function setEmbedProxyAuth(value: EmbedProxyAuth): void {\n adapter.save({\n secret: value.secret,\n email: value.email.trim().toLowerCase(),\n ...normalizedOptionalFields(value),\n });\n}\n\n/** Drop the persisted creds. */\nexport function clearEmbedProxyAuth(): void {\n adapter.clear();\n}\n\n/**\n * Apply the embed-proxy auth (Bearer + X-Chat-Act-As) to a fetch call's\n * URL + headers. Used by every embedded-surface route that needs to\n * identify itself as the proxied customer (chat stream, agent-* routes,\n * ticket-center actions). When proxy auth is absent (regular\n * cookie-session users), returns the inputs unchanged so the cookie-auth\n * path still works.\n *\n * `X-Chat-Act-As` header (vs a URL query param) keeps PII out of access\n * logs, Sentry breadcrumbs, browser history, and CDN analytics.\n */\nexport function applyProxyAuth(\n url: string,\n baseHeaders: Record<string, string> = { 'Content-Type': 'application/json' },\n): { url: string; headers: Record<string, string> } {\n const auth = getEmbedProxyAuth();\n const headers = { ...baseHeaders };\n if (auth?.secret) {\n headers.Authorization = `Bearer ${auth.secret}`;\n }\n if (auth?.email) {\n headers['X-Chat-Act-As'] = auth.email;\n }\n // Optional headers — only attached when present so the server's\n // \"required vs optional\" header shape stays exact.\n for (const { field, header } of EMBED_PROXY_OPTIONAL_HEADERS) {\n const value = auth?.[field];\n if (value) headers[header] = value;\n }\n return { url, headers };\n}\n","'use client';\n\n/**\n * Shared `fetch` wrapper for any embedded surface (chat, ticket center,\n * future widgets) that needs to carry the bearer-act-as identity\n * (proxy `Authorization` + `X-Chat-Act-As` headers from\n * `embed-proxy-auth-storage.ts`).\n *\n * Wire header names are `X-Chat-*` for historical reasons — that's a\n * server contract, not a UI namespace. The wrapper itself is generic.\n *\n * Drop-in replacement for `fetch()` — `Authorization` / `X-Chat-Act-As`\n * are merged into `init.headers` when proxy creds are stashed in\n * sessionStorage, otherwise the call falls through to the cookie-auth\n * path unchanged.\n *\n * Use this for any client-side fetch hitting `/api/chat/*`, `/api/docs/chat/*`,\n * or `/api/storage/generate-upload-url` (chat-attachment surface — shared\n * with the ticket center). Routes that do NOT need bearer-act-as\n * (e.g. `/api/profile/me`) keep using vanilla `fetch`.\n */\n\nimport { applyProxyAuth } from './embed-proxy-auth-storage';\n\n// =============================================================================\n// Host-supplied auth adapter (opt-in)\n// =============================================================================\n\n/**\n * Hosts that have their own auth model (cookie sessions, app-specific\n * JWT in localStorage, OAuth access tokens, …) can register an adapter\n * to override the lib's default `embedProxyAuth` flow. When set, the\n * adapter's `getHeaders()` result is merged onto every `embedAuthedFetch`\n * call AFTER the default proxy-auth header step (so adapter headers\n * win over both caller and proxy values), and `credentials` overrides\n * the default `'same-origin'` behaviour.\n *\n * Default (no adapter): MPH-style proxy-impersonation — bearer + act-as\n * read from localStorage, `credentials: 'same-origin'`. No consumer\n * needs to touch this unless they want a different auth model.\n *\n * Use cases:\n * - openframe-frontend has its own JWT in `localStorage.of_access_token`\n * and cookie-based session; register an adapter to attach the JWT\n * and request `credentials: 'include'` so cookies travel cross-origin\n * to the openframe gateway.\n * - Future embed hosts with OAuth access tokens, signed URLs, etc.\n *\n * Lifetime: setter is module-level (intentionally — `embedAuthedFetch`\n * is a plain utility, not a hook, so it can't read React context). Host\n * runtime providers should call `setEmbedAuthAdapter(...)` on mount and\n * `setEmbedAuthAdapter(null)` on unmount. Multiple hosts registering at\n * once is a programming error (one chat panel per app).\n */\nexport interface EmbedAuthAdapter {\n /** Headers merged onto every embedded-fetch call. Return `{}` to add\n * nothing. Called per-request so reactive token refresh sees the latest\n * value from your auth store / storage. Values typed as\n * `string | undefined` so the common narrowed shape\n * `{ Authorization: token ? 'Bearer …' : undefined }` (or a conditional\n * `token ? { Authorization: … } : {}`) assigns cleanly — `undefined`\n * values are filtered before being merged into the request headers. */\n getHeaders?: () => Record<string, string | undefined>;\n /** `RequestInit.credentials` mode. Default when no adapter: callers'\n * `init.credentials` or `'same-origin'`. Use `'include'` for cookie\n * auth against a different origin (CORS + `SameSite=None` required). */\n credentials?: RequestCredentials;\n /**\n * Optional 401 self-heal. When a request comes back `401`,\n * `embedAuthedFetch` calls this once, and — if it resolves `true` —\n * retries the SAME request exactly once with freshly-recomputed\n * headers (so a rotated bearer from `getHeaders()` is picked up).\n * Resolve `false` to surface the 401 to the caller unchanged.\n *\n * This is the capability the openframe `apiClient` has had all along\n * (refresh-the-access-token-then-retry); registering it here gives the\n * embedded chat/ticket surfaces the same self-healing auth instead of\n * dying on an expired token. Concurrent 401s are de-duplicated by the\n * wrapper, so this fires at most once per refresh cycle even when a\n * stampede of chat requests all expire together — your implementation\n * does NOT need its own in-flight guard (though a token-refresh manager\n * that already dedups is harmless).\n *\n * Keep it idempotent and side-effect-light: on failure the wrapper just\n * returns the original 401 — logout/redirect decisions belong to the\n * host's own auth layer, not to this fetch wrapper.\n */\n refresh?: () => Promise<boolean>;\n /**\n * Exact extra origins (`scheme://host[:port]`, as produced by `URL.origin`)\n * the same-origin guard additionally accepts. The guard normally rejects\n * every cross-origin URL in production — but native-shell hosts serve the\n * page from a local pseudo-origin (`capacitor://localhost`,\n * `tauri://localhost`) with NO server behind it, so the gateway their\n * bearer already belongs to is unavoidably cross-origin. Listing it here\n * is the host's explicit sanction to send the adapter's credentials there.\n * Compared AFTER the http(s)-protocol check, so non-http(s) schemes can\n * never be allowlisted. Omit (or leave empty) everywhere else — same-origin\n * remains the rule, this is the narrow exception.\n */\n allowedOrigins?: string[];\n /**\n * Optional: call `listener` whenever `getHeaders()` starts returning a NEW\n * credential (a rotation the host ran on its own — e.g. a native shell's\n * background refresh). Returns an unsubscribe. Lets a long-lived surface\n * that stopped after a 401 retry as soon as a usable credential exists,\n * instead of on its next backoff step. Fire only on a real change: a\n * listener may respond with a request, and through `refresh` that request\n * can lead the host to notify again.\n */\n subscribe?: (listener: () => void) => () => void;\n}\n\n/**\n * The registered adapter is parked on `globalThis`, NOT in a module-private\n * `let`. Reason: this lib ships multiple entry points (`/utils`,\n * `/components/chat`, …) and a consumer's bundler can inline this file into\n * more than one chunk — giving each chunk its OWN module scope. If the host\n * calls `setEmbedAuthAdapter` from the `/utils` copy while the chat's\n * `embedAuthedFetch` runs from the `/components/chat` copy, a module-local\n * `let` would be set on one copy and read as `null` on the other (the exact\n * \"credentials: same-origin, no Bearer, no refresh\" symptom). A single\n * `globalThis` slot is shared across every copy, so registration always\n * reaches the fetch path.\n */\nconst ADAPTER_GLOBAL_KEY = '__embedAuthedFetchAdapter__';\n\nfunction getRegisteredAuthAdapter(): EmbedAuthAdapter | null {\n if (typeof globalThis === 'undefined') return null;\n return ((globalThis as Record<string, unknown>)[ADAPTER_GLOBAL_KEY] as EmbedAuthAdapter | null) ?? null;\n}\n\nfunction storeRegisteredAuthAdapter(adapter: EmbedAuthAdapter | null): void {\n if (typeof globalThis === 'undefined') return;\n (globalThis as Record<string, unknown>)[ADAPTER_GLOBAL_KEY] = adapter;\n}\n\n/**\n * Register a host-owned auth adapter for `embedAuthedFetch`. Pass `null`\n * to clear (typically on provider unmount).\n *\n * Module-level state — there is one chat panel per app, so a single\n * registration is sufficient. Calling this twice with different non-null\n * adapters replaces the previous one (the most recent registration wins);\n * a `console.warn` flags the overwrite so duplicate-provider mounts get\n * caught in dev.\n */\nexport function setEmbedAuthAdapter(adapter: EmbedAuthAdapter | null): void {\n if (adapter && getRegisteredAuthAdapter() && process.env.NODE_ENV !== 'production') {\n console.warn(\n '[setEmbedAuthAdapter] overwriting a previously-registered auth ' +\n 'adapter. Two chat-runtime providers should not coexist — verify ' +\n 'mount order and pass `null` from the unmounting provider.',\n );\n }\n storeRegisteredAuthAdapter(adapter);\n}\n\n/**\n * Whether a host auth adapter is currently registered. Lets sibling helpers\n * (e.g. `contentFetch`) route through `embedAuthedFetch` ONLY when a host has\n * opted into embedded auth, and stay a plain `fetch` otherwise — so there's a\n * single auth knob (the adapter), not a second content-fetch registration.\n */\nexport function hasEmbedAuthAdapter(): boolean {\n return getRegisteredAuthAdapter() !== null;\n}\n\n/**\n * Subscribe to the registered adapter's credential changes\n * (`EmbedAuthAdapter.subscribe`). A no-op when no adapter is registered, or\n * the adapter doesn't report changes. Reads the adapter once, at call time:\n * an adapter registered (or replaced) after a subscriber starts is not\n * picked up — register it before the surfaces that need it mount.\n */\nexport function subscribeEmbedCredentialChange(listener: () => void): () => void {\n return getRegisteredAuthAdapter()?.subscribe?.(listener) ?? (() => {});\n}\n\n/**\n * Whether `url` is an asset the browser CANNOT load natively because its\n * auth rides in request headers.\n *\n * Native asset loads (`<img src>`, `<track src>`, CSS `background-image`)\n * can't carry custom headers, so a URL this returns `true` for must go\n * through `embedAuthedFetch` → blob object-URL instead (see\n * `useAuthedAssetSrc` / `useAuthedImageSrc`).\n *\n * The single signal is the registered adapter: when it supplies an\n * `Authorization` header, the host authenticates by HEADER, so nothing the\n * browser fetches on its own is authenticated. That holds for BOTH shapes a\n * header-auth host takes:\n *\n * - a cross-origin gateway URL — sanctioned via `allowedOrigins` (native\n * shells, whose page origin is `capacitor://` / `tauri://`);\n * - a same-origin / relative reverse-proxy path (`/content/api/...`) —\n * dev-ticket web, where the token lives in localStorage and there is NO\n * session cookie to fall back on. This case used to be excluded on the\n * assumption that same-origin implies \"cookies work\"; true for a cookie\n * session, false for every header-auth host, which is exactly when this\n * predicate is consulted at all.\n *\n * Everything else loads natively, unchanged: cookie-auth web (the adapter\n * supplies no `Authorization`), no adapter at all (the hub), and third-party\n * origins the bearer does NOT belong to (public images) — those stay out of\n * reach of the token by the same origin rules `embedAuthedFetch` enforces.\n */\nexport function needsBearerAssetFetch(url: string): boolean {\n if (typeof window === 'undefined') return false;\n const adapter = getRegisteredAuthAdapter();\n if (!adapter || adapter.getHeaders?.().Authorization === undefined) return false;\n let target: URL;\n let pageOrigin: string;\n try {\n target = new URL(url, window.location.href);\n // Same reason `assertSameOrigin` derives it this way: test environments\n // mock `window.location` as a plain object with no `origin` field.\n pageOrigin = new URL(window.location.href).origin;\n } catch {\n return false;\n }\n if (target.protocol !== 'http:' && target.protocol !== 'https:') return false;\n if (target.origin === pageOrigin) return true;\n return adapter.allowedOrigins?.includes(target.origin) ?? false;\n}\n\n/**\n * `fetch` wrapper that attaches embed-proxy bearer headers (when\n * present in sessionStorage) and forces `credentials: 'same-origin'`\n * so Supabase auth cookies travel too.\n *\n * **Header merge direction (proxy WINS over caller):** the implementation\n * spreads `baseHeaders` first inside `applyProxyAuth`, then sets the\n * `Authorization` / `X-Chat-*` keys — so the proxy values take precedence\n * over anything the caller passed. The motivation is that the bearer +\n * act-as identity is the source of truth for embedded auth; a caller\n * accidentally passing a stale `Authorization` header should NOT override\n * the live proxy creds.\n *\n * **Cross-origin defense:** the wrapper assumes a same-origin `/api/…`\n * relative URL. Absolute URLs are accepted only when their origin matches\n * the current window's origin — or appears in the registered adapter's\n * `allowedOrigins` (the native-shell hatch; see that field's doc) — and\n * cross-origin URLs otherwise throw before the bearer leaves the page.\n * This is a defense-in-depth guard for future call sites — outside the\n * allowlisted-shell case there is no legitimate cross-origin use of this\n * fetch wrapper.\n *\n * **401 self-heal:** when a registered adapter supplies `refresh`, a `401`\n * response triggers a single token refresh + retry of the same request\n * (see `EmbedAuthAdapter.refresh`). This is the openframe `apiClient`'s\n * refresh-then-retry behaviour, lifted into the lib so embedded surfaces\n * no longer need a host-side `window.fetch` monkey-patch to survive an\n * expired access token mid-chat. With no adapter (or no `refresh`), the\n * 401 passes straight through unchanged.\n */\nexport function embedAuthedFetch(url: string, init: RequestInit = {}): Promise<Response> {\n // Same-origin guard runs SYNCHRONOUSLY (not awaited inside the async\n // helper below) so a bearer-leaking cross-origin URL throws before any\n // promise is created — callers and tests rely on the synchronous throw.\n assertSameOrigin(url);\n\n // `applyProxyAuth` accepts `Record<string, string>`; normalize the\n // caller's headers to that shape ONCE, up front. RequestInit accepts\n // `HeadersInit` which is broader (Headers instance OR array of tuples).\n // We re-derive the per-request headers from this base on every attempt\n // (initial + post-refresh retry) so a rotated bearer is picked up.\n //\n // When the caller passes no headers, fall back to the same default\n // `applyProxyAuth` uses internally — `Content-Type: application/json` —\n // so JSON POSTs keep their content-type when only `embedAuthedFetch(url)`\n // is used at the call site. GET callers that explicitly want no body\n // headers can pass `init.headers = {}` to opt out.\n let baseHeaders: Record<string, string>;\n if (init.headers === undefined) {\n baseHeaders = { 'Content-Type': 'application/json' };\n } else {\n baseHeaders = {};\n if (init.headers instanceof Headers) {\n init.headers.forEach((v, k) => {\n baseHeaders[k] = v;\n });\n } else if (Array.isArray(init.headers)) {\n for (const [k, v] of init.headers) baseHeaders[k] = v;\n } else {\n Object.assign(baseHeaders, init.headers);\n }\n }\n\n return fetchWithRefresh(url, init, baseHeaders, false);\n}\n\n/**\n * Single in-flight refresh shared across all concurrent `embedAuthedFetch`\n * callers. A stampede of chat requests that all 401 at the same moment must\n * trigger the adapter's `refresh()` ONCE, not N times — otherwise an\n * expiring session fires a thundering herd of refresh calls at the auth\n * server. Resets to `null` once settled so the next genuine expiry can\n * refresh again.\n */\n// Stored on `globalThis` rather than a module-local so the \"single refresh\"\n// guarantee survives module duplication. Bundlers can ship more than one copy\n// of this module (e.g. across chunks or a host + embedded build); a per-module\n// variable would let each copy run its own refresh cycle, re-creating the\n// thundering-herd this dedupe exists to prevent.\nconst IN_FLIGHT_REFRESH_GLOBAL_KEY = '__embedAuthedFetchInFlightRefresh__';\n\nfunction getInFlightRefresh(): Promise<boolean> | null {\n if (typeof globalThis === 'undefined') return null;\n return (\n ((globalThis as Record<string, unknown>)[IN_FLIGHT_REFRESH_GLOBAL_KEY] as Promise<boolean> | null | undefined) ??\n null\n );\n}\n\nfunction setInFlightRefresh(refresh: Promise<boolean> | null): void {\n if (typeof globalThis === 'undefined') return;\n (globalThis as Record<string, unknown>)[IN_FLIGHT_REFRESH_GLOBAL_KEY] = refresh;\n}\n\nfunction dedupedRefresh(): Promise<boolean> {\n const adapter = getRegisteredAuthAdapter();\n // Bound and captured BEFORE the closure below. `refresh` is an optional\n // property, so TypeScript drops the `if (!adapter?.refresh)` narrowing inside\n // the `.then()` callback — the property could in principle be reassigned\n // between the guard and the call. `bind` keeps the adapter as the receiver.\n const refresh = adapter?.refresh?.bind(adapter);\n if (!refresh) return Promise.resolve(false);\n let inFlightRefresh = getInFlightRefresh();\n if (!inFlightRefresh) {\n // Wrap in `Promise.resolve` so an adapter that throws synchronously\n // (rather than rejecting) still funnels through the shared slot and\n // clears it. A rejected refresh is treated as \"could not refresh\".\n inFlightRefresh = Promise.resolve()\n .then(() => refresh())\n .catch(() => false)\n .finally(() => {\n setInFlightRefresh(null);\n });\n setInFlightRefresh(inFlightRefresh);\n }\n return inFlightRefresh;\n}\n\n/**\n * Core fetch path: merge proxy-auth + adapter headers, issue the request,\n * and — on a `401` with a refresh-capable adapter — refresh once and retry\n * the identical request a single time. Mirrors the openframe `apiClient`'s\n * refresh-then-retry contract (`isRetry` guards against infinite loops).\n */\nasync function fetchWithRefresh(\n url: string,\n init: RequestInit,\n baseHeaders: Record<string, string>,\n isRetry: boolean,\n): Promise<Response> {\n // Re-run the merge each attempt: `applyProxyAuth` reads the latest stored\n // proxy creds and `getHeaders()` reads the latest bearer, so a retry after\n // refresh carries the rotated token rather than the stale one. `{...baseHeaders}`\n // keeps the caller's normalized headers immutable across attempts.\n const { url: authedUrl, headers } = applyProxyAuth(url, { ...baseHeaders });\n\n // Host-supplied auth adapter layer. Runs AFTER the proxy-auth merge so\n // adapter headers override both caller and proxy values — the adapter\n // is the host's explicit \"this is my auth model\" override, intentionally\n // last-writer-wins. When no adapter is registered, this is a zero-cost\n // no-op (object spread of `{}`).\n const adapter = getRegisteredAuthAdapter();\n if (adapter?.getHeaders) {\n // Filter `undefined` values — the adapter type allows them so consumers\n // don't have to narrow `{ Authorization: token ? '…' : undefined }`-shaped\n // returns, but `fetch` headers must be strings.\n for (const [k, v] of Object.entries(adapter.getHeaders())) {\n if (v !== undefined) headers[k] = v;\n }\n }\n const credentials = adapter?.credentials ?? init.credentials ?? 'same-origin';\n\n const response = await fetch(authedUrl, {\n ...init,\n headers,\n // Default `same-origin` carries Supabase cookies for the MPH proxy-\n // auth model. Hosts on different origins (openframe-frontend ↔\n // openframe gateway) register `credentials: 'include'` via the\n // adapter to make their own cookies travel cross-origin (CORS +\n // `SameSite=None` must be configured server-side for that to work).\n credentials,\n });\n\n // 401 self-heal: refresh the token once and retry. Only when an adapter\n // opted into `refresh`, and only on the first attempt — a 401 on the\n // retry means the fresh token is also unauthorized, so surface it.\n if (response.status === 401 && !isRetry && adapter?.refresh) {\n const refreshed = await dedupedRefresh();\n if (refreshed) {\n return fetchWithRefresh(url, init, baseHeaders, true);\n }\n }\n\n return response;\n}\n\n/**\n * Reject any URL that resolves to a cross-origin destination or to a\n * non-http(s) scheme. Every input is resolved against\n * `window.location.href` so the same rule covers path-only\n * (`/api/...`), absolute (`https://...`), protocol-relative\n * (`//host/...`), AND whitespace-prefixed forms (`\\t//evil.com/...`) —\n * the WHATWG fetch spec strips leading ASCII whitespace before\n * parsing, so any regex-based \"skip relative\" shortcut is bypassable\n * with a leading `\\t`/`\\n`/`\\r`/space. We resolve unconditionally\n * instead and compare origins.\n *\n * Also blocks `javascript:` / `data:` / `blob:` etc. — only `http(s):`\n * is allowed. This is explicit allowlisting rather than relying on\n * `origin === 'null'` to fall out wrong.\n *\n * Server-side rendering: when `typeof window === 'undefined'` we skip\n * the check — the bearer comes from sessionStorage which doesn't exist\n * on the server, so there's nothing to leak anyway.\n */\nfunction assertSameOrigin(url: string): void {\n if (typeof window === 'undefined') return;\n let target: URL;\n let pageOrigin: string;\n try {\n target = new URL(url, window.location.href);\n // Derive the page origin from `href` rather than reading\n // `window.location.origin` directly so the check works in test\n // environments that mock `window.location` to a plain object\n // without an `origin` field (jsdom setups do this).\n pageOrigin = new URL(window.location.href).origin;\n } catch {\n throw new Error(`embedAuthedFetch: refusing to fetch malformed URL (${JSON.stringify(url)})`);\n }\n if (target.protocol !== 'http:' && target.protocol !== 'https:') {\n throw new Error(\n `embedAuthedFetch: refusing non-http(s) URL (${target.protocol}) — pass a relative /api/* path instead`,\n );\n }\n if (target.origin !== pageOrigin) {\n // Host-sanctioned cross-origin target (`EmbedAuthAdapter.allowedOrigins`).\n // Native shells serve the page from a local pseudo-origin with no server\n // behind it, so their gateway — the same backend the host already trusts\n // with this bearer — is unavoidably cross-origin. Exact `URL.origin`\n // match, and only reachable for http(s) targets (checked above).\n if (getRegisteredAuthAdapter()?.allowedOrigins?.includes(target.origin)) {\n return;\n }\n // Dev-mode escape hatch — embedded apps (e.g. openframe-frontend)\n // run on a different origin from their gateway during local dev,\n // and forcing a Next.js `rewrites()` workaround is more error-prone\n // than relaxing the guard for the dev build. In production\n // (`NODE_ENV === 'production'`) the guard stays absolute — same\n // defense-in-depth bearer-leak protection as before. The check is\n // baked at build time by Next/webpack/Turbopack so prod bundles\n // contain only the throwing branch (no dev string in the artifact).\n if (process.env.NODE_ENV !== 'production') {\n console.warn(\n `[embedAuthedFetch] cross-origin fetch to ${target.origin} ` +\n `allowed in dev (NODE_ENV !== 'production'). Production builds ` +\n `will reject this — wire a same-origin proxy before shipping.`,\n );\n return;\n }\n throw new Error(\n `embedAuthedFetch: refusing cross-origin fetch to ${target.origin} — pass a relative /api/* path instead`,\n );\n }\n}\n","'use client';\n\nimport { useEffect, useReducer } from 'react';\nimport { embedAuthedFetch, needsBearerAssetFetch } from '../utils/embed-authed-fetch';\n\n/**\n * Resolve ANY asset URL the browser would load natively — `<img src>`,\n * `<track src>`, a font, a poster — for hosts whose auth rides in request\n * HEADERS instead of cookies: native shells (`capacitor://`, `tauri://`) and\n * dev-ticket web, both in bearer mode. A native subresource load can't carry\n * an `Authorization` header, so a gateway URL that works on the cookie-auth\n * web 401s there.\n *\n * When `needsBearerAssetFetch` says the URL is bearer-authed, this hook\n * fetches it through `embedAuthedFetch` (bearer + deduped 401-refresh-retry +\n * the adapter's cross-origin guard — the SAME single auth knob every embedded\n * `fetch` in the lib rides) and returns a blob object-URL. Every other URL is\n * returned unchanged, so on the cookie-auth web the hook is a pass-through.\n *\n * Returns `undefined` while the fetch is in flight OR after it fails, so\n * callers show their existing placeholder / \"no captions\" branch instead of a\n * broken asset; a failed fetch retries on the next mount.\n *\n * Cache: module-level, session-lifetime, keyed by full URL — so the two\n * `<track>` URLs of one player, or the same avatar in twenty rows, cost one\n * request. Gateway URLs carry a `?v=<content-hash>` cache-buster, so changed\n * content is a new key; entries are never revoked (a VTT / avatar is a few KB\n * — refcount churn isn't worth it) and are dropped wholesale at session end\n * via `clearAuthedAssetCache`.\n */\n\nconst resolvedCache = new Map<string, string>();\nconst inFlight = new Map<string, Promise<void>>();\nlet cacheGeneration = 0;\n\n/**\n * Drop every cached blob and revoke its object URL. Hosts call this at\n * session end (logout / forced re-login) so a follow-on login as a\n * different identity cannot be served blobs fetched under the previous\n * one's bearer. Fetches still in flight when the clear happens are fenced\n * by a generation counter — their results are revoked instead of cached.\n */\nexport function clearAuthedAssetCache(): void {\n cacheGeneration++;\n for (const url of resolvedCache.values()) URL.revokeObjectURL(url);\n resolvedCache.clear();\n inFlight.clear();\n}\n\nfunction fetchAsBlobUrl(src: string, accept: string): Promise<void> {\n let pending = inFlight.get(src);\n if (!pending) {\n const startedGeneration = cacheGeneration;\n pending = embedAuthedFetch(src, { headers: { Accept: accept } })\n .then(async response => {\n if (!response.ok) throw new Error(`asset fetch failed: ${response.status}`);\n const blobUrl = URL.createObjectURL(await response.blob());\n if (startedGeneration === cacheGeneration) {\n resolvedCache.set(src, blobUrl);\n } else {\n URL.revokeObjectURL(blobUrl);\n }\n })\n .finally(() => {\n if (inFlight.get(src) === pending) inFlight.delete(src);\n });\n inFlight.set(src, pending);\n }\n return pending;\n}\n\n/**\n * @param src the asset URL (relative or absolute), or null/undefined\n * @param accept `Accept` header for the authed fetch — narrow it to what the\n * consumer renders (`image/*`, `text/vtt`); it also replaces\n * `embedAuthedFetch`'s default JSON `Content-Type` on the GET.\n */\nexport function useAuthedAssetSrc(src?: string | null, accept = '*/*'): string | undefined {\n const bearerSrc = src && needsBearerAssetFetch(src) ? src : null;\n const [, rerender] = useReducer((c: number) => c + 1, 0);\n\n useEffect(() => {\n if (!bearerSrc || resolvedCache.has(bearerSrc)) return undefined;\n let cancelled = false;\n fetchAsBlobUrl(bearerSrc, accept)\n .catch(() => {})\n .finally(() => {\n if (!cancelled) rerender();\n });\n return () => {\n cancelled = true;\n };\n }, [bearerSrc, accept]);\n\n if (!src) return undefined;\n if (!bearerSrc) return src;\n return resolvedCache.get(bearerSrc);\n}\n","'use client';\n\nimport { clearAuthedAssetCache, useAuthedAssetSrc } from './use-authed-asset-src';\n\n/**\n * Image flavor of `useAuthedAssetSrc` — see that hook for the full contract.\n * Kept as its own export because it is the lib's most-used consumer (avatars,\n * entity images, markdown `<img>`) and because hosts already import this name.\n *\n * Distinct from the legacy `useAuthenticatedImage`, which ALWAYS blob-fetches\n * with `credentials: 'include'` and its own global config. This hook is\n * pass-through-first and keys off the single auth knob the lib already has:\n * the registered `EmbedAuthAdapter`.\n */\nexport function useAuthedImageSrc(src?: string | null): string | undefined {\n return useAuthedAssetSrc(src, 'image/*');\n}\n\n/**\n * Session-end cache drop. Now an alias of `clearAuthedAssetCache` — one cache\n * backs every authed asset (images AND caption tracks), so hosts keep calling\n * this single function on logout / forced re-login.\n */\nexport const clearAuthedImageCache = clearAuthedAssetCache;\n","/**\n * Branded OG-placeholder URL construction — the DEFAULT cover-image fallback\n * for entity cards (onboarding guides, blog/case-study/release/etc.) that have\n * no image.\n *\n * ALL of the logic lives here, in the lib. A consumer hands over its runtime\n * `endpoints` object and NOTHING else — base resolution AND the `?title=…`\n * concatenation happen inside `buildOgPlaceholderUrl`. For the entity-card +\n * onboarding-detail surfaces this is the single entry point — no card builds an\n * og-placeholder URL itself (that was the bug this replaced: each embedder\n * wired a per-surface callback that concatenated the URL, and a host that\n * forgot it rendered a blank slot with no request). (Other server-side OG paths\n * such as the hub's blog og:image generator construct their own URLs and are\n * out of this module's scope.)\n *\n * The base API URL is taken from the endpoints the host already configures:\n * 1. explicit `endpoints.ogPlaceholderUrl`\n * 2. derived from the sibling `endpoints.imageProxyUrlPrefix` (same API base)\n * 3. same-origin relative `/api/og-placeholder`\n * The base may already carry pre-existing query params; they're preserved and\n * `title` (+ dimensions) are layered on top. Per-platform brand colors are NOT\n * baked into this URL — the `/api/og-placeholder` route resolves them\n * server-side from the platform. Most hosts leave `ogPlaceholderUrl` unset and\n * let the base derive from `imageProxyUrlPrefix`.\n */\n\n/** The slice of `ChatRuntime.endpoints` this module needs. */\nexport interface OgPlaceholderEndpoints {\n /** Explicit base URL for the og-placeholder route. May already carry query\n * params — they're preserved. Per-platform colors are NOT baked here; the\n * route resolves them server-side from the platform. */\n ogPlaceholderUrl?: string;\n /** Sibling image route under the SAME API base. When `ogPlaceholderUrl` is\n * unset, the base is derived from this by swapping the trailing\n * `/image-proxy` segment for `/og-placeholder` — so a host that already\n * proxies images gets the placeholder for free, with zero extra wiring. */\n imageProxyUrlPrefix?: string;\n}\n\nexport interface BuildOgPlaceholderOptions {\n /** Site name shown under the title. Skipped when empty. */\n site?: string;\n /** `'wide'` (1200×630, the route default — no `w`/`h` emitted) or\n * `'square'` (1024×1024, for compact 56×56 chat-inline slots). */\n aspect?: 'wide' | 'square';\n /** Explicit pixel overrides (win over `aspect`). */\n width?: number;\n height?: number;\n}\n\n/** Same-origin default — for hosts that serve the route themselves (the hub).\n * Cross-origin embedders override via `ogPlaceholderUrl` or inherit it from\n * `imageProxyUrlPrefix`. */\nconst DEFAULT_OG_PLACEHOLDER_PATH = '/api/og-placeholder';\n\n/** Resolve the og-placeholder route base from the host's endpoints.\n * Internal — callers go through `buildOgPlaceholderUrl(endpoints, …)`. */\nfunction resolveOgPlaceholderBase(endpoints?: OgPlaceholderEndpoints | null): string {\n if (endpoints?.ogPlaceholderUrl) return endpoints.ogPlaceholderUrl;\n const imageProxy = endpoints?.imageProxyUrlPrefix;\n if (imageProxy) {\n // `/image-proxy` and `/og-placeholder` are sibling API routes under one\n // base. Anchor to a path-segment boundary so we only rewrite the route\n // name, never an incidental substring.\n const derived = imageProxy.replace(/\\/image-proxy(?=$|[?/])/, '/og-placeholder');\n if (derived !== imageProxy) return derived;\n }\n return DEFAULT_OG_PLACEHOLDER_PATH;\n}\n\n/**\n * Build the branded og-placeholder image URL from the host's `endpoints` + a\n * title. This is the single entry point for entity-card + onboarding-detail\n * cover fallbacks: it resolves the route base from `endpoints` AND concatenates\n * `title` (+ dimensions), so those consumers never construct a URL themselves.\n *\n * Pure string construction — SSR- and browser-safe. Always returns a usable\n * URL (relative default at worst), so a missing/unknown image degrades\n * gracefully via the `<img onError>` recovery in the card components.\n */\nexport function buildOgPlaceholderUrl(\n endpoints: OgPlaceholderEndpoints | null | undefined,\n title: string,\n options: BuildOgPlaceholderOptions = {},\n): string {\n const base = resolveOgPlaceholderBase(endpoints);\n const qIndex = base.indexOf('?');\n const path = qIndex === -1 ? base : base.slice(0, qIndex);\n const params = new URLSearchParams(qIndex === -1 ? '' : base.slice(qIndex + 1));\n\n params.set('title', title);\n if (options.site) params.set('site', options.site);\n\n // Square aspect → request a 1024×1024 image so `object-cover` doesn't crop\n // the title off in compact slots. Wide leaves dimensions to the route\n // default (1200×630). Explicit width/height always win.\n const width = options.width ?? (options.aspect === 'square' ? 1024 : undefined);\n const height = options.height ?? (options.aspect === 'square' ? 1024 : undefined);\n // `Number.isFinite` does not coerce, so it already rejects `undefined` — no\n // separate `typeof === 'number'` guard needed.\n if (Number.isFinite(width)) params.set('w', String(width));\n if (Number.isFinite(height)) params.set('h', String(height));\n\n return `${path}?${params.toString()}`;\n}\n","'use client';\n\nimport { useMemo } from 'react';\n\nimport { useChatRuntime } from '../contexts/chat-runtime-context';\nimport { buildOgPlaceholderUrl } from '../utils/og-placeholder';\n\n/**\n * Resolve a branded og-placeholder image URL for a title, driven entirely by\n * the runtime `endpoints` (no injected builder).\n *\n * THE one og-placeholder hook. It reads the host's `endpoints` from\n * `ChatRuntime` and hands them to `buildOgPlaceholderUrl`, which resolves the\n * route base (explicit `ogPlaceholderUrl` → derived from `imageProxyUrlPrefix`\n * → same-origin `/api/og-placeholder`) and appends `?title=…`. Per-platform\n * brand colors are resolved SERVER-SIDE by the route — nothing is baked here.\n *\n * Replaces the old builder-injection `useOgPlaceholder(buildUrl, …)`: callers\n * no longer pass a URL builder. `useEntityCardPlaceholder` delegates here too,\n * so every surface shares one memo + one code path.\n */\nexport interface UseOgPlaceholderUrlArgs {\n /** Text to display on the placeholder. */\n title: string | undefined | null;\n /** Site name shown below the title (optional). */\n siteName?: string;\n /** `'wide'` (1200×630 social-card; default) or `'square'` (1024×1024 — for\n * compact chat-inline slots so `object-cover` doesn't crop the title off). */\n aspect?: 'wide' | 'square';\n /** When `false`, returns `null` instead of a URL. */\n enabled?: boolean;\n}\n\nexport function useOgPlaceholderUrl({\n title,\n siteName = '',\n aspect = 'wide',\n enabled = true,\n}: UseOgPlaceholderUrlArgs): string | null {\n const endpoints = useChatRuntime()?.endpoints;\n\n return useMemo(() => {\n if (!enabled || !title) return null;\n return buildOgPlaceholderUrl(endpoints, title, { site: siteName || undefined, aspect });\n }, [endpoints, title, siteName, aspect, enabled]);\n}\n"],"mappings":"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","names":["r","g","b","adapter","useEffect","useEffect"]}
@@ -1,12 +1,12 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }"use client";
2
2
 
3
3
 
4
- var _chunkVLWUKFQEcjs = require('./chunk-VLWUKFQE.cjs');
4
+ var _chunkCWXRZVDIcjs = require('./chunk-CWXRZVDI.cjs');
5
5
 
6
6
 
7
7
 
8
8
 
9
- var _chunkQNWQQ4RTcjs = require('./chunk-QNWQQ4RT.cjs');
9
+ var _chunk53O6PQ3Fcjs = require('./chunk-53O6PQ3F.cjs');
10
10
 
11
11
 
12
12
  var _chunkWLSFBRT5cjs = require('./chunk-WLSFBRT5.cjs');
@@ -56,14 +56,14 @@ function DetailPageSkeleton({
56
56
  ] }, section))
57
57
  ] });
58
58
  if (bare) return content;
59
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkVLWUKFQEcjs.PageLayout, { showHeader: false, className: "mx-auto max-w-[1280px] bg-ods-bg px-6 py-6 md:px-20 md:py-10", children: content });
59
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkCWXRZVDIcjs.PageLayout, { showHeader: false, className: "mx-auto max-w-[1280px] bg-ods-bg px-6 py-6 md:px-20 md:py-10", children: content });
60
60
  }
61
61
 
62
62
  // src/components/shared/article-author-byline.tsx
63
63
  var _lucidereact = require('lucide-react');
64
64
 
65
65
  function formatDate(value) {
66
- return _chunkQNWQQ4RTcjs.formatDateWithTimezone.call(void 0, value, null, "long");
66
+ return _chunk53O6PQ3Fcjs.formatDateWithTimezone.call(void 0, value, null, "long");
67
67
  }
68
68
  function ArticleAuthorByline({
69
69
  author,
@@ -79,7 +79,7 @@ function ArticleAuthorByline({
79
79
  }) {
80
80
  const runtime = _chunkU2YLMFJKcjs.useChatRuntime.call(void 0, );
81
81
  if (!author) return null;
82
- const proxiedAvatar = avatar ? proxyImageUrl ? proxyImageUrl(avatar) : _nullishCoalesce(_chunkQNWQQ4RTcjs.getProxiedImageUrl.call(void 0, avatar, {
82
+ const proxiedAvatar = avatar ? proxyImageUrl ? proxyImageUrl(avatar) : _nullishCoalesce(_chunk53O6PQ3Fcjs.getProxiedImageUrl.call(void 0, avatar, {
83
83
  proxyPrefix: _optionalChain([runtime, 'optionalAccess', _2 => _2.endpoints, 'access', _3 => _3.imageProxyUrlPrefix]),
84
84
  skipDomains: _optionalChain([runtime, 'optionalAccess', _4 => _4.endpoints, 'access', _5 => _5.imageProxySkipDomains]),
85
85
  directHttps: true
@@ -87,7 +87,7 @@ function ArticleAuthorByline({
87
87
  const avatarSizeClass = size === "lg" ? "w-16 h-16" : "w-14 h-14";
88
88
  const avatarIconClass = size === "lg" ? "w-8 h-8" : "w-7 h-7";
89
89
  const avatarDim = size === "lg" ? 64 : 56;
90
- const formattedBio = _chunkQNWQQ4RTcjs.formatBioText.call(void 0, _nullishCoalesce(bio, () => ( null)));
90
+ const formattedBio = _chunk53O6PQ3Fcjs.formatBioText.call(void 0, _nullishCoalesce(bio, () => ( null)));
91
91
  const dateLabel = publishedAt ? formatDate(publishedAt) : "";
92
92
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
93
93
  "div",
@@ -137,4 +137,4 @@ function ArticleAuthorByline({
137
137
 
138
138
 
139
139
  exports.DetailPageSkeleton = DetailPageSkeleton; exports.ArticleAuthorByline = ArticleAuthorByline;
140
- //# sourceMappingURL=chunk-5CBQBTGN.cjs.map
140
+ //# sourceMappingURL=chunk-WZYIAKIH.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-5CBQBTGN.cjs","../src/components/shared/detail-page-skeleton.tsx","../src/components/shared/article-author-byline.tsx"],"names":["jsxs","jsx"],"mappings":"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","file":"/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-5CBQBTGN.cjs","sourcesContent":[null,"'use client';\n\nimport { PageLayout } from '../layout/page-layout';\n\nexport interface DetailPageSkeletonProps {\n metadataColumns?: number; // Number of metadata grid columns (default 4)\n showImageGallery?: boolean; // Show horizontal image gallery (default true)\n /** Render only the skeleton blocks, WITHOUT the self-contained page wrapper\n * — the caller supplies its own (e.g. `<PageShell>`) so the loading state\n * matches the loaded page's width, padding, and min-height. Default false\n * (self-contained `<PageLayout>` at the hub article width). */\n bare?: boolean;\n}\n\nexport function DetailPageSkeleton({\n metadataColumns = 4,\n showImageGallery = true,\n bare = false,\n}: DetailPageSkeletonProps = {}) {\n const content = (\n <div className=\"animate-pulse space-y-6 md:space-y-10\">\n {/* Title Block */}\n <div className=\"flex w-full flex-col gap-6\">\n <div className=\"h-16 w-full max-w-3xl rounded bg-ods-card md:h-20\"></div>\n </div>\n\n {/* Category Tags Skeleton */}\n <div className=\"flex w-full flex-wrap gap-2\">\n <div className=\"h-8 w-32 rounded bg-ods-card\"></div>\n <div className=\"h-8 w-28 rounded bg-ods-card\"></div>\n <div className=\"h-8 w-36 rounded bg-ods-card\"></div>\n </div>\n\n {/* Metadata Grid Skeleton */}\n <div\n className={`grid grid-cols-1 md:grid-cols-${metadataColumns} w-full overflow-hidden rounded-md border border-ods-border`}\n >\n {Array.from({ length: metadataColumns }).map((_, i) => (\n <div key={i} className=\"border-b border-ods-border bg-ods-card p-4 last:border-r-0 md:border-b-0 md:border-r\">\n <div className=\"mb-2 h-6 w-24 rounded bg-ods-border\"></div>\n <div className=\"h-5 w-20 rounded bg-ods-border\"></div>\n </div>\n ))}\n </div>\n\n {/* Image Gallery Skeleton */}\n {showImageGallery && (\n <div className=\"flex w-full gap-6 overflow-x-auto\">\n {[1, 2, 3, 4, 5].map(i => (\n <div key={i} className=\"h-[200px] w-[240px] shrink-0 rounded-md border border-ods-border bg-ods-card\"></div>\n ))}\n </div>\n )}\n\n {/* Featured Image Skeleton (for case studies) */}\n {!showImageGallery && <div className=\"aspect-[2560/1366] w-full rounded-md bg-ods-card\"></div>}\n\n {/* Content Sections Skeleton */}\n {[1, 2, 3].map(section => (\n <div key={section} className=\"space-y-6\">\n <div className=\"h-16 w-64 rounded bg-ods-card\"></div>\n <div className=\"space-y-3\">\n <div className=\"h-6 w-full rounded bg-ods-card\"></div>\n <div className=\"h-6 w-full rounded bg-ods-card\"></div>\n <div className=\"h-6 w-4/5 rounded bg-ods-card\"></div>\n </div>\n </div>\n ))}\n </div>\n );\n if (bare) return content;\n return (\n <PageLayout showHeader={false} className=\"mx-auto max-w-[1280px] bg-ods-bg px-6 py-6 md:px-20 md:py-10\">\n {content}\n </PageLayout>\n );\n}\n","'use client';\n\n/**\n * Shared \"author byline\" card used by article-shaped detail pages\n * (blog post, product release, onboarding guide, investor update).\n *\n * MOVED from the hub so any consuming app can embed it; hub call sites\n * import it directly and pass their platform-aware copy explicitly\n * (`fallbackBio={defaultAuthorFallbackBio()}` from the hub's app-config).\n *\n * Embed-readiness contract:\n * - `Link` / `Image` render through the embed-shims (plain `<a>` / `<img>`\n * in non-Next hosts; the real Next primitives once the host registers\n * them at app init).\n * - The avatar is proxied through the OPTIONAL ambient `ChatRuntime`\n * (`endpoints.imageProxyUrlPrefix`, same config `useProxiedImageUrl`\n * reads) — when no runtime is mounted the raw URL renders as-is, so the\n * component never throws outside a provider. Hosts can also inject\n * `proxyImageUrl` to bypass the runtime entirely.\n * - `fallbackBio` is a PLAIN prop (no app-config import): the lib has no\n * platform awareness; pass copy or leave it absent to render nothing.\n *\n * Render order: avatar → name + job title + date → bio (when present).\n * Returns null when `author` is empty (no card rendered).\n */\n\nimport { Calendar, User } from 'lucide-react';\nimport { useChatRuntime } from '../../contexts/chat-runtime-context';\nimport Image from '../../embed-shims/next-image';\nimport Link from '../../embed-shims/next-link';\nimport { cn } from '../../utils/cn';\nimport { formatBioText, formatDateWithTimezone } from '../../utils/format';\nimport { getProxiedImageUrl } from '../../utils/image-proxy';\n\nexport interface ArticleAuthorBylineProps {\n /** Author display name. Required — block is hidden when null/empty. */\n author: string | null;\n /** Avatar URL. Falls back to a placeholder when null. */\n avatar?: string | null;\n /** Optional bio paragraph rendered below the name. */\n bio?: string | null;\n /** Optional job title rendered immediately under the name. */\n jobTitle?: string | null;\n /** Optional published date (ISO string or Date). Rendered next to the name. */\n publishedAt?: string | Date | null;\n /** Optional link target for the author name (e.g. the author page). */\n href?: string | null;\n /**\n * Fallback paragraph when `bio` is empty. Plain copy — the lib has no\n * platform/config awareness, so hosts that want a branded default\n * (\"Contributing author on the {platform} platform\") pass it explicitly\n * (the hub wrapper derives it from `getAppConfig()`). Absent/null ⇒\n * nothing renders below the name when `bio` is empty.\n */\n fallbackBio?: string | null;\n /** Avatar size variant. `md` = 56px (default), `lg` = 64px. */\n size?: 'md' | 'lg';\n /**\n * Host-injected avatar-URL mapper. Wins over the ambient-runtime proxy\n * resolution. Use when the host proxies images outside the `ChatRuntime`\n * config (e.g. a bespoke CDN rewrite).\n */\n proxyImageUrl?: (url: string) => string;\n className?: string;\n}\n\nfunction formatDate(value: string | Date): string {\n // A published date is a content date: UTC-pinned through the one renderer, so\n // the server and every client agree on the day (React #418).\n return formatDateWithTimezone(value, null, 'long');\n}\n\nexport function ArticleAuthorByline({\n author,\n avatar,\n bio,\n jobTitle,\n publishedAt,\n href,\n fallbackBio,\n size = 'md',\n proxyImageUrl,\n className,\n}: ArticleAuthorBylineProps) {\n // Optional runtime — `useChatRuntime` returns null outside a provider, so\n // the byline works in bare embeds (raw avatar URL) and proxies whenever the\n // host mounted a runtime with `imageProxyUrlPrefix` (the hub always does).\n const runtime = useChatRuntime();\n if (!author) return null;\n\n const proxiedAvatar = avatar\n ? proxyImageUrl\n ? proxyImageUrl(avatar)\n : (getProxiedImageUrl(avatar, {\n proxyPrefix: runtime?.endpoints.imageProxyUrlPrefix,\n skipDomains: runtime?.endpoints.imageProxySkipDomains,\n directHttps: true,\n }) ?? avatar)\n : '';\n\n // Class-driven sizing (md = 56px, lg = 64px). The numeric pair feeds only\n // the Image element's intrinsic width/height attributes (required by the\n // Next image shim) — layout comes from the classes.\n const avatarSizeClass = size === 'lg' ? 'w-16 h-16' : 'w-14 h-14';\n const avatarIconClass = size === 'lg' ? 'w-8 h-8' : 'w-7 h-7';\n const avatarDim = size === 'lg' ? 64 : 56;\n const formattedBio = formatBioText(bio ?? null);\n const dateLabel = publishedAt ? formatDate(publishedAt) : '';\n\n return (\n <div\n className={cn(\n 'rounded-lg border border-ods-border bg-ods-card p-6',\n 'flex flex-col items-start gap-4 md:flex-row',\n className,\n )}\n >\n {/* Avatar */}\n <div className=\"flex-shrink-0\">\n {avatar ? (\n <Image\n src={proxiedAvatar || avatar}\n alt={author}\n width={avatarDim}\n height={avatarDim}\n className={cn('rounded-full border-2 border-ods-border object-cover', avatarSizeClass)}\n />\n ) : (\n <div\n className={cn(\n 'flex items-center justify-center rounded-full border-2 border-ods-border bg-ods-bg',\n avatarSizeClass,\n )}\n >\n <User className={cn('text-ods-text-secondary', avatarIconClass)} />\n </div>\n )}\n </div>\n\n {/* Name + meta + bio */}\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex flex-wrap items-baseline gap-x-3 gap-y-1\">\n <h3 className=\"text-ods-text-primary text-h5\">\n {href ? (\n <Link href={href} className=\"transition-colors hover:text-ods-accent\">\n {author}\n </Link>\n ) : (\n author\n )}\n </h3>\n {dateLabel && (\n <span className=\"text-body-sm inline-flex items-center gap-1 font-body text-ods-text-secondary\">\n <Calendar className=\"h-3 w-3\" />\n {dateLabel}\n </span>\n )}\n </div>\n {jobTitle && <p className=\"text-body-sm mt-0.5 font-body text-ods-text-secondary\">{jobTitle}</p>}\n {formattedBio ? (\n <p className=\"text-body-md mt-2 font-body leading-relaxed text-ods-text-secondary\">{formattedBio}</p>\n ) : fallbackBio ? (\n <p className=\"text-body-md mt-2 font-body italic text-ods-text-secondary\">{fallbackBio}</p>\n ) : null}\n </div>\n </div>\n );\n}\n"]}
1
+ {"version":3,"sources":["/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-WZYIAKIH.cjs","../src/components/shared/detail-page-skeleton.tsx","../src/components/shared/article-author-byline.tsx"],"names":["jsxs","jsx"],"mappings":"AAAA,6rBAAY;AACZ;AACE;AACF,wDAA6B;AAC7B;AACE;AACA;AACA;AACF,wDAA6B;AAC7B;AACE;AACF,wDAA6B;AAC7B;AACE;AACF,wDAA6B;AAC7B;AACE;AACF,wDAA6B;AAC7B;AACE;AACF,wDAA6B;AAC7B;AACA;ACCQ,+CAAA;AATD,SAAS,kBAAA,CAAmB;AAAA,EACjC,gBAAA,EAAkB,CAAA;AAAA,EAClB,iBAAA,EAAmB,IAAA;AAAA,EACnB,KAAA,EAAO;AACT,EAAA,EAA6B,CAAC,CAAA,EAAG;AAC/B,EAAA,MAAM,QAAA,kBACJ,8BAAA,KAAC,EAAA,EAAI,SAAA,EAAU,uCAAA,EAEb,QAAA,EAAA;AAAA,oBAAA,6BAAA,KAAC,EAAA,EAAI,SAAA,EAAU,4BAAA,EACb,QAAA,kBAAA,6BAAA,KAAC,EAAA,EAAI,SAAA,EAAU,oDAAA,CAAoD,EAAA,CACrE,CAAA;AAAA,oBAGA,8BAAA,KAAC,EAAA,EAAI,SAAA,EAAU,6BAAA,EACb,QAAA,EAAA;AAAA,sBAAA,6BAAA,KAAC,EAAA,EAAI,SAAA,EAAU,+BAAA,CAA+B,CAAA;AAAA,sBAC9C,6BAAA,KAAC,EAAA,EAAI,SAAA,EAAU,+BAAA,CAA+B,CAAA;AAAA,sBAC9C,6BAAA,KAAC,EAAA,EAAI,SAAA,EAAU,+BAAA,CAA+B;AAAA,IAAA,EAAA,CAChD,CAAA;AAAA,oBAGA,6BAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,SAAA,EAAW,CAAA,8BAAA,EAAiC,eAAe,CAAA,2DAAA,CAAA;AAAA,QAE1D,QAAA,EAAA,KAAA,CAAM,IAAA,CAAK,EAAE,MAAA,EAAQ,gBAAgB,CAAC,CAAA,CAAE,GAAA,CAAI,CAAC,CAAA,EAAG,CAAA,EAAA,mBAC/C,8BAAA,KAAC,EAAA,EAAY,SAAA,EAAU,sFAAA,EACrB,QAAA,EAAA;AAAA,0BAAA,6BAAA,KAAC,EAAA,EAAI,SAAA,EAAU,sCAAA,CAAsC,CAAA;AAAA,0BACrD,6BAAA,KAAC,EAAA,EAAI,SAAA,EAAU,iCAAA,CAAiC;AAAA,QAAA,EAAA,CAAA,EAFxC,CAGV,CACD;AAAA,MAAA;AAAA,IACH,CAAA;AAAA,IAGC,iBAAA,mBACC,6BAAA,KAAC,EAAA,EAAI,SAAA,EAAU,mCAAA,EACZ,QAAA,EAAA,CAAC,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,CAAC,CAAA,CAAE,GAAA,CAAI,CAAA,CAAA,EAAA,mBACnB,6BAAA,KAAC,EAAA,EAAY,SAAA,EAAU,+EAAA,CAAA,EAAb,CAA4F,CACvG,EAAA,CACH,CAAA;AAAA,IAID,CAAC,iBAAA,mBAAoB,6BAAA,KAAC,EAAA,EAAI,SAAA,EAAU,mDAAA,CAAmD,CAAA;AAAA,IAGvF,CAAC,CAAA,EAAG,CAAA,EAAG,CAAC,CAAA,CAAE,GAAA,CAAI,CAAA,OAAA,EAAA,mBACb,8BAAA,KAAC,EAAA,EAAkB,SAAA,EAAU,WAAA,EAC3B,QAAA,EAAA;AAAA,sBAAA,6BAAA,KAAC,EAAA,EAAI,SAAA,EAAU,gCAAA,CAAgC,CAAA;AAAA,sBAC/C,8BAAA,KAAC,EAAA,EAAI,SAAA,EAAU,WAAA,EACb,QAAA,EAAA;AAAA,wBAAA,6BAAA,KAAC,EAAA,EAAI,SAAA,EAAU,iCAAA,CAAiC,CAAA;AAAA,wBAChD,6BAAA,KAAC,EAAA,EAAI,SAAA,EAAU,iCAAA,CAAiC,CAAA;AAAA,wBAChD,6BAAA,KAAC,EAAA,EAAI,SAAA,EAAU,gCAAA,CAAgC;AAAA,MAAA,EAAA,CACjD;AAAA,IAAA,EAAA,CAAA,EANQ,OAOV,CACD;AAAA,EAAA,EAAA,CACH,CAAA;AAEF,EAAA,GAAA,CAAI,IAAA,EAAM,OAAO,OAAA;AACjB,EAAA,uBACE,6BAAA,4BAAC,EAAA,EAAW,UAAA,EAAY,KAAA,EAAO,SAAA,EAAU,8DAAA,EACtC,QAAA,EAAA,QAAA,CACH,CAAA;AAEJ;ADhBA;AACA;AEnCA,2CAA+B;AA8FrB;AAtDV,SAAS,UAAA,CAAW,KAAA,EAA8B;AAGhD,EAAA,OAAO,sDAAA,KAAuB,EAAO,IAAA,EAAM,MAAM,CAAA;AACnD;AAEO,SAAS,mBAAA,CAAoB;AAAA,EAClC,MAAA;AAAA,EACA,MAAA;AAAA,EACA,GAAA;AAAA,EACA,QAAA;AAAA,EACA,WAAA;AAAA,EACA,IAAA;AAAA,EACA,WAAA;AAAA,EACA,KAAA,EAAO,IAAA;AAAA,EACP,aAAA;AAAA,EACA;AACF,CAAA,EAA6B;AAI3B,EAAA,MAAM,QAAA,EAAU,8CAAA,CAAe;AAC/B,EAAA,GAAA,CAAI,CAAC,MAAA,EAAQ,OAAO,IAAA;AAEpB,EAAA,MAAM,cAAA,EAAgB,OAAA,EAClB,cAAA,EACE,aAAA,CAAc,MAAM,EAAA,mBACnB,kDAAA,MAAmB,EAAQ;AAAA,IAC1B,WAAA,kBAAa,OAAA,6BAAS,SAAA,qBAAU,qBAAA;AAAA,IAChC,WAAA,kBAAa,OAAA,6BAAS,SAAA,qBAAU,uBAAA;AAAA,IAChC,WAAA,EAAa;AAAA,EACf,CAAC,CAAA,UAAK,SAAA,EACR,EAAA;AAKJ,EAAA,MAAM,gBAAA,EAAkB,KAAA,IAAS,KAAA,EAAO,YAAA,EAAc,WAAA;AACtD,EAAA,MAAM,gBAAA,EAAkB,KAAA,IAAS,KAAA,EAAO,UAAA,EAAY,SAAA;AACpD,EAAA,MAAM,UAAA,EAAY,KAAA,IAAS,KAAA,EAAO,GAAA,EAAK,EAAA;AACvC,EAAA,MAAM,aAAA,EAAe,6CAAA,iBAAc,GAAA,UAAO,MAAI,CAAA;AAC9C,EAAA,MAAM,UAAA,EAAY,YAAA,EAAc,UAAA,CAAW,WAAW,EAAA,EAAI,EAAA;AAE1D,EAAA,uBACEA,8BAAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,SAAA,EAAW,kCAAA;AAAA,QACT,qDAAA;AAAA,QACA,6CAAA;AAAA,QACA;AAAA,MACF,CAAA;AAAA,MAGA,QAAA,EAAA;AAAA,wBAAAC,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,eAAA,EACZ,QAAA,EAAA,OAAA,kBACCA,6BAAAA;AAAA,UAAC,oCAAA;AAAA,UAAA;AAAA,YACC,GAAA,EAAK,cAAA,GAAiB,MAAA;AAAA,YACtB,GAAA,EAAK,MAAA;AAAA,YACL,KAAA,EAAO,SAAA;AAAA,YACP,MAAA,EAAQ,SAAA;AAAA,YACR,SAAA,EAAW,kCAAA,sDAAG,EAAwD,eAAe;AAAA,UAAA;AAAA,QACvF,EAAA,kBAEAA,6BAAAA;AAAA,UAAC,KAAA;AAAA,UAAA;AAAA,YACC,SAAA,EAAW,kCAAA;AAAA,cACT,oFAAA;AAAA,cACA;AAAA,YACF,CAAA;AAAA,YAEA,QAAA,kBAAAA,6BAAAA,iBAAC,EAAA,EAAK,SAAA,EAAW,kCAAA,yBAAG,EAA2B,eAAe,EAAA,CAAG;AAAA,UAAA;AAAA,QACnE,EAAA,CAEJ,CAAA;AAAA,wBAGAD,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,gBAAA,EACb,QAAA,EAAA;AAAA,0BAAAA,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,+CAAA,EACb,QAAA,EAAA;AAAA,4BAAAC,6BAAAA,IAAC,EAAA,EAAG,SAAA,EAAU,+BAAA,EACX,QAAA,EAAA,KAAA,kBACCA,6BAAAA,mCAAC,EAAA,EAAK,IAAA,EAAY,SAAA,EAAU,yCAAA,EACzB,QAAA,EAAA,OAAA,CACH,EAAA,EAEA,OAAA,CAEJ,CAAA;AAAA,YACC,UAAA,mBACCD,8BAAAA,MAAC,EAAA,EAAK,SAAA,EAAU,+EAAA,EACd,QAAA,EAAA;AAAA,8BAAAC,6BAAAA,qBAAC,EAAA,EAAS,SAAA,EAAU,UAAA,CAAU,CAAA;AAAA,cAC7B;AAAA,YAAA,EAAA,CACH;AAAA,UAAA,EAAA,CAEJ,CAAA;AAAA,UACC,SAAA,mBAAYA,6BAAAA,GAAC,EAAA,EAAE,SAAA,EAAU,uDAAA,EAAyD,QAAA,EAAA,SAAA,CAAS,CAAA;AAAA,UAC3F,aAAA,kBACCA,6BAAAA,GAAC,EAAA,EAAE,SAAA,EAAU,qEAAA,EAAuE,QAAA,EAAA,aAAA,CAAa,EAAA,EAC/F,YAAA,kBACFA,6BAAAA,GAAC,EAAA,EAAE,SAAA,EAAU,4DAAA,EAA8D,QAAA,EAAA,YAAA,CAAY,EAAA,EACrF;AAAA,QAAA,EAAA,CACN;AAAA,MAAA;AAAA,IAAA;AAAA,EACF,CAAA;AAEJ;AFjCA;AACA;AACE;AACA;AACF,mGAAC","file":"/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-WZYIAKIH.cjs","sourcesContent":[null,"'use client';\n\nimport { PageLayout } from '../layout/page-layout';\n\nexport interface DetailPageSkeletonProps {\n metadataColumns?: number; // Number of metadata grid columns (default 4)\n showImageGallery?: boolean; // Show horizontal image gallery (default true)\n /** Render only the skeleton blocks, WITHOUT the self-contained page wrapper\n * — the caller supplies its own (e.g. `<PageShell>`) so the loading state\n * matches the loaded page's width, padding, and min-height. Default false\n * (self-contained `<PageLayout>` at the hub article width). */\n bare?: boolean;\n}\n\nexport function DetailPageSkeleton({\n metadataColumns = 4,\n showImageGallery = true,\n bare = false,\n}: DetailPageSkeletonProps = {}) {\n const content = (\n <div className=\"animate-pulse space-y-6 md:space-y-10\">\n {/* Title Block */}\n <div className=\"flex w-full flex-col gap-6\">\n <div className=\"h-16 w-full max-w-3xl rounded bg-ods-card md:h-20\"></div>\n </div>\n\n {/* Category Tags Skeleton */}\n <div className=\"flex w-full flex-wrap gap-2\">\n <div className=\"h-8 w-32 rounded bg-ods-card\"></div>\n <div className=\"h-8 w-28 rounded bg-ods-card\"></div>\n <div className=\"h-8 w-36 rounded bg-ods-card\"></div>\n </div>\n\n {/* Metadata Grid Skeleton */}\n <div\n className={`grid grid-cols-1 md:grid-cols-${metadataColumns} w-full overflow-hidden rounded-md border border-ods-border`}\n >\n {Array.from({ length: metadataColumns }).map((_, i) => (\n <div key={i} className=\"border-b border-ods-border bg-ods-card p-4 last:border-r-0 md:border-b-0 md:border-r\">\n <div className=\"mb-2 h-6 w-24 rounded bg-ods-border\"></div>\n <div className=\"h-5 w-20 rounded bg-ods-border\"></div>\n </div>\n ))}\n </div>\n\n {/* Image Gallery Skeleton */}\n {showImageGallery && (\n <div className=\"flex w-full gap-6 overflow-x-auto\">\n {[1, 2, 3, 4, 5].map(i => (\n <div key={i} className=\"h-[200px] w-[240px] shrink-0 rounded-md border border-ods-border bg-ods-card\"></div>\n ))}\n </div>\n )}\n\n {/* Featured Image Skeleton (for case studies) */}\n {!showImageGallery && <div className=\"aspect-[2560/1366] w-full rounded-md bg-ods-card\"></div>}\n\n {/* Content Sections Skeleton */}\n {[1, 2, 3].map(section => (\n <div key={section} className=\"space-y-6\">\n <div className=\"h-16 w-64 rounded bg-ods-card\"></div>\n <div className=\"space-y-3\">\n <div className=\"h-6 w-full rounded bg-ods-card\"></div>\n <div className=\"h-6 w-full rounded bg-ods-card\"></div>\n <div className=\"h-6 w-4/5 rounded bg-ods-card\"></div>\n </div>\n </div>\n ))}\n </div>\n );\n if (bare) return content;\n return (\n <PageLayout showHeader={false} className=\"mx-auto max-w-[1280px] bg-ods-bg px-6 py-6 md:px-20 md:py-10\">\n {content}\n </PageLayout>\n );\n}\n","'use client';\n\n/**\n * Shared \"author byline\" card used by article-shaped detail pages\n * (blog post, product release, onboarding guide, investor update).\n *\n * MOVED from the hub so any consuming app can embed it; hub call sites\n * import it directly and pass their platform-aware copy explicitly\n * (`fallbackBio={defaultAuthorFallbackBio()}` from the hub's app-config).\n *\n * Embed-readiness contract:\n * - `Link` / `Image` render through the embed-shims (plain `<a>` / `<img>`\n * in non-Next hosts; the real Next primitives once the host registers\n * them at app init).\n * - The avatar is proxied through the OPTIONAL ambient `ChatRuntime`\n * (`endpoints.imageProxyUrlPrefix`, same config `useProxiedImageUrl`\n * reads) — when no runtime is mounted the raw URL renders as-is, so the\n * component never throws outside a provider. Hosts can also inject\n * `proxyImageUrl` to bypass the runtime entirely.\n * - `fallbackBio` is a PLAIN prop (no app-config import): the lib has no\n * platform awareness; pass copy or leave it absent to render nothing.\n *\n * Render order: avatar → name + job title + date → bio (when present).\n * Returns null when `author` is empty (no card rendered).\n */\n\nimport { Calendar, User } from 'lucide-react';\nimport { useChatRuntime } from '../../contexts/chat-runtime-context';\nimport Image from '../../embed-shims/next-image';\nimport Link from '../../embed-shims/next-link';\nimport { cn } from '../../utils/cn';\nimport { formatBioText, formatDateWithTimezone } from '../../utils/format';\nimport { getProxiedImageUrl } from '../../utils/image-proxy';\n\nexport interface ArticleAuthorBylineProps {\n /** Author display name. Required — block is hidden when null/empty. */\n author: string | null;\n /** Avatar URL. Falls back to a placeholder when null. */\n avatar?: string | null;\n /** Optional bio paragraph rendered below the name. */\n bio?: string | null;\n /** Optional job title rendered immediately under the name. */\n jobTitle?: string | null;\n /** Optional published date (ISO string or Date). Rendered next to the name. */\n publishedAt?: string | Date | null;\n /** Optional link target for the author name (e.g. the author page). */\n href?: string | null;\n /**\n * Fallback paragraph when `bio` is empty. Plain copy — the lib has no\n * platform/config awareness, so hosts that want a branded default\n * (\"Contributing author on the {platform} platform\") pass it explicitly\n * (the hub wrapper derives it from `getAppConfig()`). Absent/null ⇒\n * nothing renders below the name when `bio` is empty.\n */\n fallbackBio?: string | null;\n /** Avatar size variant. `md` = 56px (default), `lg` = 64px. */\n size?: 'md' | 'lg';\n /**\n * Host-injected avatar-URL mapper. Wins over the ambient-runtime proxy\n * resolution. Use when the host proxies images outside the `ChatRuntime`\n * config (e.g. a bespoke CDN rewrite).\n */\n proxyImageUrl?: (url: string) => string;\n className?: string;\n}\n\nfunction formatDate(value: string | Date): string {\n // A published date is a content date: UTC-pinned through the one renderer, so\n // the server and every client agree on the day (React #418).\n return formatDateWithTimezone(value, null, 'long');\n}\n\nexport function ArticleAuthorByline({\n author,\n avatar,\n bio,\n jobTitle,\n publishedAt,\n href,\n fallbackBio,\n size = 'md',\n proxyImageUrl,\n className,\n}: ArticleAuthorBylineProps) {\n // Optional runtime — `useChatRuntime` returns null outside a provider, so\n // the byline works in bare embeds (raw avatar URL) and proxies whenever the\n // host mounted a runtime with `imageProxyUrlPrefix` (the hub always does).\n const runtime = useChatRuntime();\n if (!author) return null;\n\n const proxiedAvatar = avatar\n ? proxyImageUrl\n ? proxyImageUrl(avatar)\n : (getProxiedImageUrl(avatar, {\n proxyPrefix: runtime?.endpoints.imageProxyUrlPrefix,\n skipDomains: runtime?.endpoints.imageProxySkipDomains,\n directHttps: true,\n }) ?? avatar)\n : '';\n\n // Class-driven sizing (md = 56px, lg = 64px). The numeric pair feeds only\n // the Image element's intrinsic width/height attributes (required by the\n // Next image shim) — layout comes from the classes.\n const avatarSizeClass = size === 'lg' ? 'w-16 h-16' : 'w-14 h-14';\n const avatarIconClass = size === 'lg' ? 'w-8 h-8' : 'w-7 h-7';\n const avatarDim = size === 'lg' ? 64 : 56;\n const formattedBio = formatBioText(bio ?? null);\n const dateLabel = publishedAt ? formatDate(publishedAt) : '';\n\n return (\n <div\n className={cn(\n 'rounded-lg border border-ods-border bg-ods-card p-6',\n 'flex flex-col items-start gap-4 md:flex-row',\n className,\n )}\n >\n {/* Avatar */}\n <div className=\"flex-shrink-0\">\n {avatar ? (\n <Image\n src={proxiedAvatar || avatar}\n alt={author}\n width={avatarDim}\n height={avatarDim}\n className={cn('rounded-full border-2 border-ods-border object-cover', avatarSizeClass)}\n />\n ) : (\n <div\n className={cn(\n 'flex items-center justify-center rounded-full border-2 border-ods-border bg-ods-bg',\n avatarSizeClass,\n )}\n >\n <User className={cn('text-ods-text-secondary', avatarIconClass)} />\n </div>\n )}\n </div>\n\n {/* Name + meta + bio */}\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex flex-wrap items-baseline gap-x-3 gap-y-1\">\n <h3 className=\"text-ods-text-primary text-h5\">\n {href ? (\n <Link href={href} className=\"transition-colors hover:text-ods-accent\">\n {author}\n </Link>\n ) : (\n author\n )}\n </h3>\n {dateLabel && (\n <span className=\"text-body-sm inline-flex items-center gap-1 font-body text-ods-text-secondary\">\n <Calendar className=\"h-3 w-3\" />\n {dateLabel}\n </span>\n )}\n </div>\n {jobTitle && <p className=\"text-body-sm mt-0.5 font-body text-ods-text-secondary\">{jobTitle}</p>}\n {formattedBio ? (\n <p className=\"text-body-md mt-2 font-body leading-relaxed text-ods-text-secondary\">{formattedBio}</p>\n ) : fallbackBio ? (\n <p className=\"text-body-md mt-2 font-body italic text-ods-text-secondary\">{fallbackBio}</p>\n ) : null}\n </div>\n </div>\n );\n}\n"]}
@@ -9,7 +9,7 @@ import {
9
9
  parseSchemaParams,
10
10
  positiveInt,
11
11
  shouldIncludeInUrl
12
- } from "./chunk-UAKWSR3U.js";
12
+ } from "./chunk-GDZ7IC5B.js";
13
13
  import {
14
14
  ToolIcon,
15
15
  apiErrorMessage,
@@ -36,7 +36,7 @@ import {
36
36
  import {
37
37
  createLocalStorageAdapter,
38
38
  getPlatformBrandClasses
39
- } from "./chunk-2KILTCND.js";
39
+ } from "./chunk-UVUNBXS6.js";
40
40
  import {
41
41
  MiamiCyberGangLogoFaceOnly,
42
42
  OpenFrameLogo
@@ -3597,4 +3597,4 @@ export {
3597
3597
  useFormRescue,
3598
3598
  useRescuedForm
3599
3599
  };
3600
- //# sourceMappingURL=chunk-2V4KPBNZ.js.map
3600
+ //# sourceMappingURL=chunk-XAOAYDGR.js.map
@@ -8,18 +8,18 @@ import {
8
8
  SelectTrigger,
9
9
  SelectValue,
10
10
  Textarea
11
- } from "./chunk-AIXPQT3G.js";
11
+ } from "./chunk-KMW2PXBU.js";
12
12
  import {
13
13
  useContactSubmission,
14
14
  useHumanitySignals,
15
15
  useRescuedForm
16
- } from "./chunk-2V4KPBNZ.js";
16
+ } from "./chunk-XAOAYDGR.js";
17
17
  import {
18
18
  ChatAttachmentAddButton,
19
19
  ChatAttachmentChipStrip,
20
20
  SECTION_HEADING_CLASS,
21
21
  useChatAttachments
22
- } from "./chunk-4BUPMSLY.js";
22
+ } from "./chunk-Y7ZEVTLC.js";
23
23
  import {
24
24
  Input,
25
25
  Label,
@@ -385,4 +385,4 @@ function ContactForm({
385
385
  export {
386
386
  ContactForm
387
387
  };
388
- //# sourceMappingURL=chunk-U4OJCJTH.js.map
388
+ //# sourceMappingURL=chunk-XBYF4SGD.js.map
@@ -8,18 +8,18 @@
8
8
 
9
9
 
10
10
 
11
- var _chunkVLWUKFQEcjs = require('./chunk-VLWUKFQE.cjs');
11
+ var _chunkCWXRZVDIcjs = require('./chunk-CWXRZVDI.cjs');
12
12
 
13
13
 
14
14
 
15
15
 
16
- var _chunkBJHM2OEYcjs = require('./chunk-BJHM2OEY.cjs');
16
+ var _chunkLNKLB7GMcjs = require('./chunk-LNKLB7GM.cjs');
17
17
 
18
18
 
19
19
 
20
20
 
21
21
 
22
- var _chunkOD2WMUAOcjs = require('./chunk-OD2WMUAO.cjs');
22
+ var _chunkGZRU2A4Lcjs = require('./chunk-GZRU2A4L.cjs');
23
23
 
24
24
 
25
25
 
@@ -110,14 +110,14 @@ function ContactForm({
110
110
  successToastMessage = "Redirecting you to join our community...",
111
111
  rescue: rescueForm = null
112
112
  } = {}) {
113
- const attachments = _chunkOD2WMUAOcjs.useChatAttachments.call(void 0, );
114
- const builtInSubmission = _chunkBJHM2OEYcjs.useContactSubmission.call(void 0, {
113
+ const attachments = _chunkGZRU2A4Lcjs.useChatAttachments.call(void 0, );
114
+ const builtInSubmission = _chunkLNKLB7GMcjs.useContactSubmission.call(void 0, {
115
115
  userId,
116
116
  successRedirectUrl,
117
117
  successToastMessage
118
118
  });
119
119
  const [customSubmitting, setCustomSubmitting] = _react.useState.call(void 0, false);
120
- const { honeypotInputProps, getSignals, resetSignals } = _chunkBJHM2OEYcjs.useHumanitySignals.call(void 0, );
120
+ const { honeypotInputProps, getSignals, resetSignals } = _chunkLNKLB7GMcjs.useHumanitySignals.call(void 0, );
121
121
  const isSubmitting = onCustomSubmit ? customSubmitting : builtInSubmission.isSubmitting;
122
122
  const isSuccess = onCustomSubmit ? false : builtInSubmission.isSuccess;
123
123
  const form = _reacthookform.useForm.call(void 0, {
@@ -143,7 +143,7 @@ function ContactForm({
143
143
  ),
144
144
  [hideFields]
145
145
  );
146
- const rescue = _chunkBJHM2OEYcjs.useRescuedForm.call(void 0, form, { rescue: rescueForm, fields: rescueFieldNames, getSignals });
146
+ const rescue = _chunkLNKLB7GMcjs.useRescuedForm.call(void 0, form, { rescue: rescueForm, fields: rescueFieldNames, getSignals });
147
147
  const handleFormSubmit = async (data) => {
148
148
  if (isSubmitting) return;
149
149
  if (attachmentsEnabled && attachments.hasInflightUploads) return;
@@ -181,7 +181,7 @@ function ContactForm({
181
181
  className: `flex h-full flex-col ${!noBorder ? "rounded-2xl border border-ods-border md:rounded-3xl" : ""} ${!noPadding ? "p-6 md:p-8 lg:p-10" : ""}`,
182
182
  children: [
183
183
  (title || subtitle) && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "mb-6 md:mb-8", children: [
184
- title && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h2", { className: `${_chunkOD2WMUAOcjs.SECTION_HEADING_CLASS} mb-3 md:mb-4`, children: title }),
184
+ title && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h2", { className: `${_chunkGZRU2A4Lcjs.SECTION_HEADING_CLASS} mb-3 md:mb-4`, children: title }),
185
185
  subtitle && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "text-ods-text-primary text-h4", children: subtitle })
186
186
  ] }),
187
187
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
@@ -194,13 +194,13 @@ function ContactForm({
194
194
  );
195
195
  }),
196
196
  className: "flex flex-grow flex-col space-y-4 md:space-y-6",
197
- ..._chunkVLWUKFQEcjs.HUBSPOT_DO_NOT_COLLECT_FORM_PROPS,
197
+ ..._chunkCWXRZVDIcjs.HUBSPOT_DO_NOT_COLLECT_FORM_PROPS,
198
198
  children: [
199
199
  !showName && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "input", { type: "hidden", ...register("name") }),
200
200
  !showEmail && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "input", { type: "hidden", ...register("email") }),
201
201
  !showHelpCategory && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "input", { type: "hidden", ...register("helpCategory") }),
202
202
  !showMessage && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "input", { type: "hidden", ...register("message") }),
203
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkVLWUKFQEcjs.HoneypotField, { ...honeypotInputProps }),
203
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkCWXRZVDIcjs.HoneypotField, { ...honeypotInputProps }),
204
204
  extraTopField,
205
205
  showNameEmailRow && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "grid grid-cols-1 gap-4 md:grid-cols-2 md:gap-6", children: [
206
206
  showName && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex flex-col", children: [
@@ -252,17 +252,17 @@ function ContactForm({
252
252
  {
253
253
  control,
254
254
  name: "companySize",
255
- render: ({ field }) => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkVLWUKFQEcjs.Select, { onValueChange: field.onChange, value: _nullishCoalesce(field.value, () => ( "")), children: [
255
+ render: ({ field }) => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkCWXRZVDIcjs.Select, { onValueChange: field.onChange, value: _nullishCoalesce(field.value, () => ( "")), children: [
256
256
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
257
- _chunkVLWUKFQEcjs.SelectTrigger,
257
+ _chunkCWXRZVDIcjs.SelectTrigger,
258
258
  {
259
259
  id: "companySize",
260
260
  "aria-label": "Company Size",
261
261
  className: "h-12 border-ods-border bg-ods-card px-3 text-ods-text-primary",
262
- children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkVLWUKFQEcjs.SelectValue, { placeholder: "Select company size" })
262
+ children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkCWXRZVDIcjs.SelectValue, { placeholder: "Select company size" })
263
263
  }
264
264
  ),
265
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkVLWUKFQEcjs.SelectContent, { children: companySizeOptions.map((opt) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkVLWUKFQEcjs.SelectItem, { value: opt, children: opt }, opt)) })
265
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkCWXRZVDIcjs.SelectContent, { children: companySizeOptions.map((opt) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkCWXRZVDIcjs.SelectItem, { value: opt, children: opt }, opt)) })
266
266
  ] })
267
267
  }
268
268
  ),
@@ -275,17 +275,17 @@ function ContactForm({
275
275
  {
276
276
  control,
277
277
  name: "referralSource",
278
- render: ({ field }) => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkVLWUKFQEcjs.Select, { onValueChange: field.onChange, value: _nullishCoalesce(field.value, () => ( "")), children: [
278
+ render: ({ field }) => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkCWXRZVDIcjs.Select, { onValueChange: field.onChange, value: _nullishCoalesce(field.value, () => ( "")), children: [
279
279
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
280
- _chunkVLWUKFQEcjs.SelectTrigger,
280
+ _chunkCWXRZVDIcjs.SelectTrigger,
281
281
  {
282
282
  id: "referralSource",
283
283
  "aria-label": "Referral Source",
284
284
  className: "h-12 border-ods-border bg-ods-card px-3 text-ods-text-primary",
285
- children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkVLWUKFQEcjs.SelectValue, { placeholder: "Select an option" })
285
+ children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkCWXRZVDIcjs.SelectValue, { placeholder: "Select an option" })
286
286
  }
287
287
  ),
288
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkVLWUKFQEcjs.SelectContent, { children: referralSourceOptions.map((opt) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkVLWUKFQEcjs.SelectItem, { value: opt, children: opt }, opt)) })
288
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkCWXRZVDIcjs.SelectContent, { children: referralSourceOptions.map((opt) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkCWXRZVDIcjs.SelectItem, { value: opt, children: opt }, opt)) })
289
289
  ] })
290
290
  }
291
291
  ),
@@ -302,18 +302,18 @@ function ContactForm({
302
302
  {
303
303
  control,
304
304
  name: "helpCategory",
305
- render: ({ field }) => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkVLWUKFQEcjs.Select, { onValueChange: field.onChange, value: _nullishCoalesce(field.value, () => ( "")), children: [
305
+ render: ({ field }) => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkCWXRZVDIcjs.Select, { onValueChange: field.onChange, value: _nullishCoalesce(field.value, () => ( "")), children: [
306
306
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
307
- _chunkVLWUKFQEcjs.SelectTrigger,
307
+ _chunkCWXRZVDIcjs.SelectTrigger,
308
308
  {
309
309
  id: "helpCategory",
310
310
  "aria-label": "Help Category",
311
311
  "aria-required": true,
312
312
  className: "h-12 border-ods-border bg-ods-card px-3 text-ods-text-primary",
313
- children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkVLWUKFQEcjs.SelectValue, { placeholder: "Choose your main interest" })
313
+ children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkCWXRZVDIcjs.SelectValue, { placeholder: "Choose your main interest" })
314
314
  }
315
315
  ),
316
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkVLWUKFQEcjs.SelectContent, { children: helpCategoryOptions.map((opt) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkVLWUKFQEcjs.SelectItem, { value: opt, children: opt }, opt)) })
316
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkCWXRZVDIcjs.SelectContent, { children: helpCategoryOptions.map((opt) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkCWXRZVDIcjs.SelectItem, { value: opt, children: opt }, opt)) })
317
317
  ] })
318
318
  }
319
319
  ),
@@ -325,7 +325,7 @@ function ContactForm({
325
325
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKWX65373cjs.RequiredMark, {})
326
326
  ] }),
327
327
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
328
- _chunkVLWUKFQEcjs.Textarea,
328
+ _chunkCWXRZVDIcjs.Textarea,
329
329
  {
330
330
  id: "message",
331
331
  ...register("message"),
@@ -340,7 +340,7 @@ function ContactForm({
340
340
  ] }),
341
341
  attachmentsEnabled && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex flex-col gap-2", children: [
342
342
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
343
- _chunkOD2WMUAOcjs.ChatAttachmentChipStrip,
343
+ _chunkGZRU2A4Lcjs.ChatAttachmentChipStrip,
344
344
  {
345
345
  attachments: attachments.attachments,
346
346
  onRemove: attachments.removeAttachment,
@@ -349,7 +349,7 @@ function ContactForm({
349
349
  ),
350
350
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex items-center gap-2", children: [
351
351
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
352
- _chunkOD2WMUAOcjs.ChatAttachmentAddButton,
352
+ _chunkGZRU2A4Lcjs.ChatAttachmentAddButton,
353
353
  {
354
354
  attachmentsEnabled: true,
355
355
  attachmentsCount: attachments.attachments.length,
@@ -385,4 +385,4 @@ function ContactForm({
385
385
 
386
386
 
387
387
  exports.ContactForm = ContactForm;
388
- //# sourceMappingURL=chunk-FCQR6VY4.cjs.map
388
+ //# sourceMappingURL=chunk-Y3BC7TOJ.cjs.map