@flamingo-stack/openframe-frontend-core 0.0.406 → 0.0.407-snapshot.20260711133234

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 (167) hide show
  1. package/dist/{chunk-3KEBI6TP.cjs → chunk-2B6773QM.cjs} +1 -1
  2. package/dist/chunk-2B6773QM.cjs.map +1 -0
  3. package/dist/{chunk-TFDNJTBX.js → chunk-5HOII3WB.js} +4 -4
  4. package/dist/{chunk-E6YHDT3D.js → chunk-7Q52HP45.js} +2 -2
  5. package/dist/chunk-7Q52HP45.js.map +1 -0
  6. package/dist/{chunk-VZGU4BV6.cjs → chunk-A3RW7M6B.cjs} +29 -29
  7. package/dist/{chunk-VZGU4BV6.cjs.map → chunk-A3RW7M6B.cjs.map} +1 -1
  8. package/dist/{chunk-QAFRLO5N.cjs → chunk-CUN4CJUN.cjs} +10 -11
  9. package/dist/chunk-CUN4CJUN.cjs.map +1 -0
  10. package/dist/{chunk-6WMVBCNA.js → chunk-E4J5KKHH.js} +3 -3
  11. package/dist/{chunk-P43ZHPM6.cjs → chunk-F4KE5AZO.cjs} +40 -40
  12. package/dist/{chunk-P43ZHPM6.cjs.map → chunk-F4KE5AZO.cjs.map} +1 -1
  13. package/dist/{chunk-PQN5R5II.js → chunk-IK5AR6QG.js} +7 -7
  14. package/dist/{chunk-SOK32D6C.js → chunk-IVM47EGI.js} +346 -297
  15. package/dist/chunk-IVM47EGI.js.map +1 -0
  16. package/dist/{chunk-AYFWYOL6.js → chunk-J6M2G6TD.js} +7 -7
  17. package/dist/{chunk-NA7XJRWB.js → chunk-KFQXO7QB.js} +1 -1
  18. package/dist/chunk-KFQXO7QB.js.map +1 -0
  19. package/dist/{chunk-ISYVNPRT.cjs → chunk-KPGKNUJF.cjs} +42 -42
  20. package/dist/{chunk-ISYVNPRT.cjs.map → chunk-KPGKNUJF.cjs.map} +1 -1
  21. package/dist/{chunk-GNS65HNC.cjs → chunk-KRLBVBYU.cjs} +18 -18
  22. package/dist/{chunk-GNS65HNC.cjs.map → chunk-KRLBVBYU.cjs.map} +1 -1
  23. package/dist/{chunk-ZVUR3A33.js → chunk-KZPVN7AZ.js} +2 -2
  24. package/dist/{chunk-7RVSGGO6.js → chunk-L6OPVKVR.js} +6 -6
  25. package/dist/{chunk-OATWTTCI.js → chunk-LQWYXIKB.js} +6 -6
  26. package/dist/{chunk-COERBDG4.cjs → chunk-NEMUCNCG.cjs} +75 -75
  27. package/dist/{chunk-COERBDG4.cjs.map → chunk-NEMUCNCG.cjs.map} +1 -1
  28. package/dist/{chunk-PNJ5LMVK.js → chunk-NO2KT42M.js} +9 -10
  29. package/dist/{chunk-PNJ5LMVK.js.map → chunk-NO2KT42M.js.map} +1 -1
  30. package/dist/{chunk-LSPBPUQM.js → chunk-O5W26SCL.js} +2 -2
  31. package/dist/{chunk-VANFD4FX.cjs → chunk-RL344I7V.cjs} +7 -7
  32. package/dist/{chunk-VANFD4FX.cjs.map → chunk-RL344I7V.cjs.map} +1 -1
  33. package/dist/{chunk-CF3LBCNI.cjs → chunk-RPGFIUN3.cjs} +35 -35
  34. package/dist/{chunk-CF3LBCNI.cjs.map → chunk-RPGFIUN3.cjs.map} +1 -1
  35. package/dist/{chunk-LL4QQSAD.cjs → chunk-SOALFYU6.cjs} +479 -430
  36. package/dist/chunk-SOALFYU6.cjs.map +1 -0
  37. package/dist/{chunk-CCIBZ7TO.cjs → chunk-STGWQFYT.cjs} +5 -5
  38. package/dist/chunk-STGWQFYT.cjs.map +1 -0
  39. package/dist/{chunk-VIB4SGCD.cjs → chunk-T6ZUBXPA.cjs} +14 -14
  40. package/dist/{chunk-VIB4SGCD.cjs.map → chunk-T6ZUBXPA.cjs.map} +1 -1
  41. package/dist/{chunk-HJJXNMM7.cjs → chunk-V3EZQWHQ.cjs} +7 -7
  42. package/dist/{chunk-HJJXNMM7.cjs.map → chunk-V3EZQWHQ.cjs.map} +1 -1
  43. package/dist/{chunk-3KZXOLFK.js → chunk-VUW4G6SU.js} +4 -4
  44. package/dist/{chunk-JAPP2F4M.cjs → chunk-WGOATV3P.cjs} +77 -77
  45. package/dist/{chunk-JAPP2F4M.cjs.map → chunk-WGOATV3P.cjs.map} +1 -1
  46. package/dist/{chunk-SF4G7M6U.cjs → chunk-YRKPMYI3.cjs} +15 -15
  47. package/dist/{chunk-SF4G7M6U.cjs.map → chunk-YRKPMYI3.cjs.map} +1 -1
  48. package/dist/{chunk-XXG2Z2JT.js → chunk-ZF5O3KUQ.js} +3 -3
  49. package/dist/{chunk-C3NPMZBI.js → chunk-ZMFQWMM7.js} +3 -3
  50. package/dist/components/announcement-bar.d.ts.map +1 -1
  51. package/dist/components/case-studies/index.cjs +11 -10
  52. package/dist/components/case-studies/index.cjs.map +1 -1
  53. package/dist/components/case-studies/index.js +5 -4
  54. package/dist/components/case-studies/index.js.map +1 -1
  55. package/dist/components/chat/index.cjs +5 -4
  56. package/dist/components/chat/index.cjs.map +1 -1
  57. package/dist/components/chat/index.js +4 -3
  58. package/dist/components/chat/quick-action-chip.d.ts +11 -2
  59. package/dist/components/chat/quick-action-chip.d.ts.map +1 -1
  60. package/dist/components/contact/index.cjs +6 -5
  61. package/dist/components/contact/index.cjs.map +1 -1
  62. package/dist/components/contact/index.js +5 -4
  63. package/dist/components/docs/index.cjs +8 -8
  64. package/dist/components/docs/index.js +7 -7
  65. package/dist/components/embeds/index.cjs +6 -5
  66. package/dist/components/embeds/index.cjs.map +1 -1
  67. package/dist/components/embeds/index.js +5 -4
  68. package/dist/components/embeds/reddit-embed-client.d.ts.map +1 -1
  69. package/dist/components/faq/index.cjs +7 -6
  70. package/dist/components/faq/index.cjs.map +1 -1
  71. package/dist/components/faq/index.js +6 -5
  72. package/dist/components/features/index.cjs +5 -4
  73. package/dist/components/features/index.cjs.map +1 -1
  74. package/dist/components/features/index.js +4 -3
  75. package/dist/components/help-center-pages/index.cjs +24 -24
  76. package/dist/components/help-center-pages/index.js +15 -15
  77. package/dist/components/index.cjs +186 -180
  78. package/dist/components/index.cjs.map +1 -1
  79. package/dist/components/index.d.ts +1 -0
  80. package/dist/components/index.d.ts.map +1 -1
  81. package/dist/components/index.js +39 -33
  82. package/dist/components/index.js.map +1 -1
  83. package/dist/components/media-carousel.d.ts +28 -4
  84. package/dist/components/media-carousel.d.ts.map +1 -1
  85. package/dist/components/navigation/index.cjs +5 -4
  86. package/dist/components/navigation/index.cjs.map +1 -1
  87. package/dist/components/navigation/index.js +4 -3
  88. package/dist/components/onboarding-guides/index.cjs +9 -9
  89. package/dist/components/onboarding-guides/index.js +8 -8
  90. package/dist/components/related-content/index.cjs +7 -6
  91. package/dist/components/related-content/index.cjs.map +1 -1
  92. package/dist/components/related-content/index.js +6 -5
  93. package/dist/components/tickets/index.cjs +8 -8
  94. package/dist/components/tickets/index.js +7 -7
  95. package/dist/components/ui/index.cjs +5 -4
  96. package/dist/components/ui/index.cjs.map +1 -1
  97. package/dist/components/ui/index.js +4 -3
  98. package/dist/components/ui/tag.d.ts +3 -2
  99. package/dist/components/ui/tag.d.ts.map +1 -1
  100. package/dist/contexts/endpoints-runtime-context.d.ts +1 -1
  101. package/dist/contexts/endpoints-runtime-context.d.ts.map +1 -1
  102. package/dist/contexts/index.cjs +2 -2
  103. package/dist/contexts/index.js +1 -1
  104. package/dist/hooks/index.cjs +3 -3
  105. package/dist/hooks/index.js +2 -2
  106. package/dist/hooks/use-self-fetch.d.ts.map +1 -1
  107. package/dist/index.cjs +5 -4
  108. package/dist/index.cjs.map +1 -1
  109. package/dist/index.js +6 -5
  110. package/dist/types/announcement.cjs.map +1 -1
  111. package/dist/types/announcement.d.ts +0 -6
  112. package/dist/types/announcement.d.ts.map +1 -1
  113. package/dist/types/announcement.js.map +1 -1
  114. package/dist/types/index.cjs.map +1 -1
  115. package/dist/types/index.js.map +1 -1
  116. package/dist/utils/announcement-storage.d.ts +3 -0
  117. package/dist/utils/announcement-storage.d.ts.map +1 -1
  118. package/dist/utils/app-config.d.ts.map +1 -1
  119. package/dist/utils/color-analysis.d.ts +0 -4
  120. package/dist/utils/color-analysis.d.ts.map +1 -1
  121. package/dist/utils/index.cjs +186 -177
  122. package/dist/utils/index.cjs.map +1 -1
  123. package/dist/utils/index.js +185 -176
  124. package/dist/utils/index.js.map +1 -1
  125. package/dist/utils/media-carousel-utils-stub.d.ts +3 -1
  126. package/dist/utils/media-carousel-utils-stub.d.ts.map +1 -1
  127. package/dist/utils/ods-color-utils.d.ts +2 -1
  128. package/dist/utils/ods-color-utils.d.ts.map +1 -1
  129. package/package.json +1 -1
  130. package/src/components/announcement-bar.tsx +38 -21
  131. package/src/components/chat/quick-action-chip.tsx +18 -4
  132. package/src/components/embeds/reddit-embed-client.tsx +0 -3
  133. package/src/components/index.ts +5 -0
  134. package/src/components/media-carousel.tsx +195 -149
  135. package/src/components/ui/tag.tsx +21 -3
  136. package/src/contexts/endpoints-runtime-context.tsx +1 -1
  137. package/src/hooks/use-self-fetch.ts +10 -9
  138. package/src/stories/AnnouncementBar.stories.tsx +8 -14
  139. package/src/styles/ods-design-tokens.css +2 -0
  140. package/src/types/announcement.ts +0 -8
  141. package/src/utils/announcement-storage.ts +17 -0
  142. package/src/utils/app-config.ts +3 -2
  143. package/src/utils/color-analysis.ts +5 -2
  144. package/src/utils/media-carousel-utils-stub.ts +3 -1
  145. package/src/utils/ods-color-utils.ts +4 -7
  146. package/dist/chunk-3KEBI6TP.cjs.map +0 -1
  147. package/dist/chunk-CCIBZ7TO.cjs.map +0 -1
  148. package/dist/chunk-E6YHDT3D.js.map +0 -1
  149. package/dist/chunk-LL4QQSAD.cjs.map +0 -1
  150. package/dist/chunk-NA7XJRWB.js.map +0 -1
  151. package/dist/chunk-QAFRLO5N.cjs.map +0 -1
  152. package/dist/chunk-SOK32D6C.js.map +0 -1
  153. package/dist/utils/media-carousel-utils.d.ts +0 -10
  154. package/dist/utils/media-carousel-utils.d.ts.map +0 -1
  155. package/src/utils/.media-carousel-utils.md +0 -31
  156. package/src/utils/media-carousel-utils.ts +0 -22
  157. /package/dist/{chunk-TFDNJTBX.js.map → chunk-5HOII3WB.js.map} +0 -0
  158. /package/dist/{chunk-6WMVBCNA.js.map → chunk-E4J5KKHH.js.map} +0 -0
  159. /package/dist/{chunk-PQN5R5II.js.map → chunk-IK5AR6QG.js.map} +0 -0
  160. /package/dist/{chunk-AYFWYOL6.js.map → chunk-J6M2G6TD.js.map} +0 -0
  161. /package/dist/{chunk-ZVUR3A33.js.map → chunk-KZPVN7AZ.js.map} +0 -0
  162. /package/dist/{chunk-7RVSGGO6.js.map → chunk-L6OPVKVR.js.map} +0 -0
  163. /package/dist/{chunk-OATWTTCI.js.map → chunk-LQWYXIKB.js.map} +0 -0
  164. /package/dist/{chunk-LSPBPUQM.js.map → chunk-O5W26SCL.js.map} +0 -0
  165. /package/dist/{chunk-3KZXOLFK.js.map → chunk-VUW4G6SU.js.map} +0 -0
  166. /package/dist/{chunk-XXG2Z2JT.js.map → chunk-ZF5O3KUQ.js.map} +0 -0
  167. /package/dist/{chunk-C3NPMZBI.js.map → chunk-ZMFQWMM7.js.map} +0 -0
@@ -26,7 +26,7 @@ import {
26
26
  useOgPlaceholderUrl,
27
27
  useOnboardingState,
28
28
  useToast
29
- } from "./chunk-E6YHDT3D.js";
29
+ } from "./chunk-7Q52HP45.js";
30
30
  import {
31
31
  AlertCircleIcon,
32
32
  AnthropicLogoGreyIcon,
@@ -90,6 +90,7 @@ import {
90
90
  GraduationCapIcon,
91
91
  HeadphoneIcon,
92
92
  HubspotLogoGreyIcon,
93
+ ImageOffIcon,
93
94
  ImagePlusIcon,
94
95
  InfoCircleIcon,
95
96
  LayersIcon,
@@ -150,7 +151,7 @@ import {
150
151
  import {
151
152
  useChatRuntime,
152
153
  useRequiredChatRuntime
153
- } from "./chunk-NA7XJRWB.js";
154
+ } from "./chunk-KFQXO7QB.js";
154
155
  import {
155
156
  dynamic
156
157
  } from "./chunk-EL5YVPD5.js";
@@ -424,11 +425,18 @@ init_cn();
424
425
  import { jsx, jsxs } from "react/jsx-runtime";
425
426
  var tagVariants = cva(
426
427
  [
427
- "text-h5 h-8 inline-flex items-center justify-center gap-[var(--spacing-system-xxs)] p-[var(--spacing-system-xsf)] rounded-md",
428
+ "inline-flex items-center justify-center rounded-md",
428
429
  "transition-colors duration-150"
429
430
  ],
430
431
  {
431
432
  variants: {
433
+ // Chip scale. `default` is the classic 32px mono-uppercase tag; `large`
434
+ // is the Figma "Feature Item" chip (48px, h3 bold body label) used by
435
+ // chip groups acting as tabs (OpenFrame categories).
436
+ size: {
437
+ default: "text-h5 h-8 gap-[var(--spacing-system-xxs)] p-[var(--spacing-system-xsf)]",
438
+ large: "text-h3 font-bold h-12 gap-[var(--spacing-system-xs)] p-[var(--spacing-system-s)]"
439
+ },
432
440
  variant: {
433
441
  primary: [
434
442
  "bg-[var(--ods-open-yellow-base)] text-[var(--ods-system-greys-black)]",
@@ -459,6 +467,15 @@ var tagVariants = cva(
459
467
  "bg-[var(--ods-system-greys-soft-grey)] text-[var(--ods-system-greys-grey)]",
460
468
  "hover:bg-[var(--ods-system-greys-soft-grey-hover)] active:bg-[var(--ods-system-greys-soft-grey-action)]"
461
469
  ],
470
+ // Active/selected chip state (Figma "Feature Item" active): pink
471
+ // border + pink-secondary fill. A dedicated variant (not appended
472
+ // utilities) so its own hover rules win — the outline variant's
473
+ // hover:bg/hover:border would otherwise repaint an active chip grey.
474
+ selected: [
475
+ "bg-[var(--ods-flamingo-pink-secondary)] text-[var(--ods-system-greys-white)] border border-[var(--ods-flamingo-pink-base)]",
476
+ "hover:bg-[var(--ods-flamingo-pink-secondary-hover)] hover:border-[var(--ods-flamingo-pink-base)]",
477
+ "active:bg-[var(--ods-flamingo-pink-secondary-action)]"
478
+ ],
462
479
  // Matches the EntityTagBadges / StatusBadge tag skin (ods-card + ods-border,
463
480
  // mono uppercase) so the tag-editor chips render identically to the public
464
481
  // tag badges. Used for FilterChipData variant 'tag' (see search-input).
@@ -469,7 +486,8 @@ var tagVariants = cva(
469
486
  }
470
487
  },
471
488
  defaultVariants: {
472
- variant: "primary"
489
+ variant: "primary",
490
+ size: "default"
473
491
  }
474
492
  }
475
493
  );
@@ -482,6 +500,7 @@ var disabledTagClasses = [
482
500
  function Tag({
483
501
  label,
484
502
  variant,
503
+ size: size2,
485
504
  icon,
486
505
  onClose,
487
506
  className,
@@ -494,14 +513,14 @@ function Tag({
494
513
  Comp,
495
514
  {
496
515
  className: cn(
497
- tagVariants({ variant }),
516
+ tagVariants({ variant, size: size2 }),
498
517
  disabled && disabledTagClasses,
499
518
  className
500
519
  ),
501
520
  "aria-disabled": disabled || void 0,
502
521
  ...props,
503
522
  children: [
504
- icon && /* @__PURE__ */ jsx("span", { className: "flex items-center justify-center size-5 shrink-0", children: icon }),
523
+ icon && /* @__PURE__ */ jsx("span", { className: cn("flex items-center justify-center shrink-0", size2 === "large" ? "size-6" : "size-5"), children: icon }),
505
524
  /* @__PURE__ */ jsx("span", { className: cn("truncate", labelClassName), title: typeof label === "string" ? label : void 0, children: label }),
506
525
  onClose && /* @__PURE__ */ jsx(
507
526
  "button",
@@ -2745,6 +2764,174 @@ function ColorSwatch({ color, className }) {
2745
2764
  // src/components/ui/ticket-status-tag.tsx
2746
2765
  init_cn();
2747
2766
 
2767
+ // src/utils/color-analysis.ts
2768
+ var DESIGN_PALETTE = [
2769
+ { name: "yellow", hex: "#FFC008", rgb: [255, 192, 8] },
2770
+ { name: "black", hex: "#161616", rgb: [22, 22, 22] },
2771
+ { name: "gray", hex: "#888888", rgb: [136, 136, 136] }
2772
+ ];
2773
+ function hexToRgb(hex) {
2774
+ const normalized = hex.replace(/^#?([a-f\d])([a-f\d])([a-f\d])$/i, (_, r, g, b) => `${r}${r}${g}${g}${b}${b}`);
2775
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(normalized);
2776
+ return result ? [parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16)] : [0, 0, 0];
2777
+ }
2778
+ function getLuminance(rgb) {
2779
+ const [r, g, b] = rgb.map((c) => {
2780
+ c = c / 255;
2781
+ return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
2782
+ });
2783
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
2784
+ }
2785
+ function getContrastRatio(color1, color2) {
2786
+ const lum1 = getLuminance(color1);
2787
+ const lum2 = getLuminance(color2);
2788
+ const brightest = Math.max(lum1, lum2);
2789
+ const darkest = Math.min(lum1, lum2);
2790
+ return (brightest + 0.05) / (darkest + 0.05);
2791
+ }
2792
+ function pickReadableTextColor(bgHex) {
2793
+ const bg = hexToRgb(bgHex);
2794
+ const darkContrast = getContrastRatio(bg, [26, 26, 26]);
2795
+ const lightContrast = getContrastRatio(bg, [250, 250, 250]);
2796
+ return darkContrast >= lightContrast ? "dark" : "light";
2797
+ }
2798
+ function extractDominantColor(canvas) {
2799
+ const ctx = canvas.getContext("2d");
2800
+ if (!ctx) return [128, 128, 128];
2801
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
2802
+ const data = imageData.data;
2803
+ const sampleSize = Math.max(1, Math.floor(data.length / (4 * 1e3)));
2804
+ const colorCounts = {};
2805
+ for (let i = 0; i < data.length; i += 4 * sampleSize) {
2806
+ const r = data[i];
2807
+ const g = data[i + 1];
2808
+ const b = data[i + 2];
2809
+ const alpha = data[i + 3];
2810
+ if (alpha < 128) continue;
2811
+ const bucketR = Math.round(r / 32) * 32;
2812
+ const bucketG = Math.round(g / 32) * 32;
2813
+ const bucketB = Math.round(b / 32) * 32;
2814
+ const key = `${bucketR},${bucketG},${bucketB}`;
2815
+ colorCounts[key] = (colorCounts[key] || 0) + 1;
2816
+ }
2817
+ let maxCount = 0;
2818
+ let dominantColor = [128, 128, 128];
2819
+ for (const [colorKey, count] of Object.entries(colorCounts)) {
2820
+ if (count > maxCount) {
2821
+ maxCount = count;
2822
+ const [r, g, b] = colorKey.split(",").map(Number);
2823
+ dominantColor = [r, g, b];
2824
+ }
2825
+ }
2826
+ return dominantColor;
2827
+ }
2828
+ function getBestContrastColor(imageColor) {
2829
+ let bestColor = DESIGN_PALETTE[0];
2830
+ let bestContrast = 0;
2831
+ for (const color of DESIGN_PALETTE) {
2832
+ const contrast = getContrastRatio(imageColor, color.rgb);
2833
+ if (contrast > bestContrast) {
2834
+ bestContrast = contrast;
2835
+ bestColor = color;
2836
+ }
2837
+ }
2838
+ return bestContrast >= 3 ? bestColor : DESIGN_PALETTE.find((c) => c.name === "black") || bestColor;
2839
+ }
2840
+ function analyzeImageColor(imageSrc) {
2841
+ return new Promise((resolve, reject) => {
2842
+ const img = new Image();
2843
+ img.crossOrigin = "anonymous";
2844
+ img.onload = () => {
2845
+ try {
2846
+ const canvas = document.createElement("canvas");
2847
+ const ctx = canvas.getContext("2d");
2848
+ if (!ctx) {
2849
+ reject(new Error("Could not get canvas context"));
2850
+ return;
2851
+ }
2852
+ const maxSize = 100;
2853
+ const scale = Math.min(maxSize / img.width, maxSize / img.height);
2854
+ canvas.width = img.width * scale;
2855
+ canvas.height = img.height * scale;
2856
+ ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
2857
+ const dominantColor = extractDominantColor(canvas);
2858
+ const bestContrastColor = getBestContrastColor(dominantColor);
2859
+ resolve(bestContrastColor);
2860
+ } catch (error) {
2861
+ reject(error);
2862
+ }
2863
+ };
2864
+ img.onerror = () => reject(new Error("Failed to load image"));
2865
+ img.src = imageSrc;
2866
+ });
2867
+ }
2868
+ async function extractImageEdgeColorAsync(imageUrl) {
2869
+ return new Promise((resolve) => {
2870
+ if (typeof window === "undefined") {
2871
+ resolve("#000000");
2872
+ return;
2873
+ }
2874
+ const img = new Image();
2875
+ img.crossOrigin = "anonymous";
2876
+ img.onload = () => {
2877
+ try {
2878
+ const canvas = document.createElement("canvas");
2879
+ const ctx = canvas.getContext("2d");
2880
+ if (!ctx) {
2881
+ resolve("#000000");
2882
+ return;
2883
+ }
2884
+ const maxSize = 50;
2885
+ const scale = Math.min(maxSize / img.naturalWidth, maxSize / img.naturalHeight);
2886
+ const w = Math.round(img.naturalWidth * scale);
2887
+ const h = Math.round(img.naturalHeight * scale);
2888
+ canvas.width = w;
2889
+ canvas.height = h;
2890
+ ctx.drawImage(img, 0, 0, w, h);
2891
+ const data = ctx.getImageData(0, 0, w, h).data;
2892
+ const edgeW = Math.max(2, Math.round(w * 0.15));
2893
+ const edgeH = Math.max(2, Math.round(h * 0.15));
2894
+ const bucketSize = 32;
2895
+ const buckets = /* @__PURE__ */ new Map();
2896
+ for (let y = 0; y < h; y++) {
2897
+ for (let x = 0; x < w; x++) {
2898
+ if (!(x < edgeW || x >= w - edgeW || y < edgeH || y >= h - edgeH)) continue;
2899
+ const i = (y * w + x) * 4;
2900
+ if (data[i + 3] < 128) continue;
2901
+ const br = Math.floor(data[i] / bucketSize) * bucketSize;
2902
+ const bg = Math.floor(data[i + 1] / bucketSize) * bucketSize;
2903
+ const bb = Math.floor(data[i + 2] / bucketSize) * bucketSize;
2904
+ const key = `${br},${bg},${bb}`;
2905
+ const existing = buckets.get(key);
2906
+ if (existing) {
2907
+ existing.r += data[i];
2908
+ existing.g += data[i + 1];
2909
+ existing.b += data[i + 2];
2910
+ existing.count++;
2911
+ } else {
2912
+ buckets.set(key, { r: data[i], g: data[i + 1], b: data[i + 2], count: 1 });
2913
+ }
2914
+ }
2915
+ }
2916
+ let best = { r: 0, g: 0, b: 0, count: 0 };
2917
+ for (const b2 of buckets.values()) {
2918
+ if (b2.count > best.count) best = b2;
2919
+ }
2920
+ if (best.count === 0) {
2921
+ resolve("#000000");
2922
+ return;
2923
+ }
2924
+ const r = Math.round(best.r / best.count), g = Math.round(best.g / best.count), b = Math.round(best.b / best.count);
2925
+ resolve(`#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`);
2926
+ } catch {
2927
+ resolve("#000000");
2928
+ }
2929
+ };
2930
+ img.onerror = () => resolve("#000000");
2931
+ img.src = imageUrl;
2932
+ });
2933
+ }
2934
+
2748
2935
  // src/utils/ods-color-utils.ts
2749
2936
  function getPlatformAccentColor(platform) {
2750
2937
  const currentPlatform = platform || getCurrentPlatform();
@@ -2769,18 +2956,13 @@ function getCurrentPlatform() {
2769
2956
  return process.env.NEXT_PUBLIC_APP_TYPE || "openmsp";
2770
2957
  }
2771
2958
  function getReadableTextColor(hex) {
2772
- const n = parseInt(hex.replace("#", ""), 16);
2773
- const r = n >> 16 & 255;
2774
- const g = n >> 8 & 255;
2775
- const b = n & 255;
2776
- const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
2777
- return luminance > 0.5 ? "#212121" : "#fafafa";
2959
+ return pickReadableTextColor(hex) === "dark" ? "#212121" : "#fafafa";
2778
2960
  }
2779
2961
  var HEX_PATTERN = /^#[0-9a-fA-F]{6}$/;
2780
2962
  function clamp(n, min, max) {
2781
2963
  return Math.max(min, Math.min(max, n));
2782
2964
  }
2783
- function hexToRgb(hex) {
2965
+ function hexToRgb2(hex) {
2784
2966
  if (!HEX_PATTERN.test(hex)) return null;
2785
2967
  const n = Number.parseInt(hex.slice(1), 16);
2786
2968
  return { r: n >> 16 & 255, g: n >> 8 & 255, b: n & 255 };
@@ -2792,7 +2974,7 @@ function rgbToHex(r, g, b) {
2792
2974
  var HOVER_DARKEN_STEP = 10;
2793
2975
  var ACTIVE_DARKEN_STEP = 20;
2794
2976
  function darkenByStep(hex, step) {
2795
- const rgb = hexToRgb(hex);
2977
+ const rgb = hexToRgb2(hex);
2796
2978
  if (!rgb) return hex;
2797
2979
  return rgbToHex(rgb.r - step, rgb.g - step, rgb.b - step);
2798
2980
  }
@@ -6341,13 +6523,14 @@ function composeChipLabel(label, lozenge) {
6341
6523
  function ChipSkelBar({ className, style }) {
6342
6524
  return /* @__PURE__ */ jsx34("span", { "aria-hidden": true, className: cn("animate-pulse rounded-md bg-ods-border", className), style });
6343
6525
  }
6344
- function QuickActionChipSkeleton({ labelCh = 16, icon = true, lozenge = false, className }) {
6526
+ function QuickActionChipSkeleton({ labelCh = 16, icon = true, lozenge = false, size: size2 = "default", className }) {
6345
6527
  return /* @__PURE__ */ jsx34(
6346
6528
  Tag,
6347
6529
  {
6348
6530
  variant: "outline",
6531
+ size: size2,
6349
6532
  className,
6350
- icon: icon ? /* @__PURE__ */ jsx34(ChipSkelBar, { className: "block size-4" }) : void 0,
6533
+ icon: icon ? /* @__PURE__ */ jsx34(ChipSkelBar, { className: size2 === "large" ? "block size-5" : "block size-4" }) : void 0,
6351
6534
  label: /* @__PURE__ */ jsxs28(Fragment8, { children: [
6352
6535
  lozenge && /* @__PURE__ */ jsx34(ChipSkelBar, { className: "mr-2 inline-block h-[16px] w-[26px] translate-y-[2px]" }),
6353
6536
  /* @__PURE__ */ jsx34(ChipSkelBar, { className: "inline-block h-[0.9em] translate-y-[0.08em]", style: { width: `${labelCh}ch` } })
@@ -6360,6 +6543,8 @@ function QuickActionChipButton({
6360
6543
  icon,
6361
6544
  lozenge,
6362
6545
  variant = "outline",
6546
+ selected = false,
6547
+ size: size2 = "default",
6363
6548
  onSelect,
6364
6549
  onHoverStart,
6365
6550
  onHoverEnd,
@@ -6368,8 +6553,9 @@ function QuickActionChipButton({
6368
6553
  }) {
6369
6554
  const resolvedIcon = renderQuickActionIcon(icon);
6370
6555
  const resolvedLabel = composeChipLabel(label, lozenge);
6556
+ const tagVariant = selected ? "selected" : variant;
6371
6557
  if (!interactive) {
6372
- return /* @__PURE__ */ jsx34(Tag, { variant, icon: resolvedIcon, label: resolvedLabel, className });
6558
+ return /* @__PURE__ */ jsx34(Tag, { variant: tagVariant, size: size2, icon: resolvedIcon, label: resolvedLabel, className });
6373
6559
  }
6374
6560
  return /* @__PURE__ */ jsx34(
6375
6561
  "button",
@@ -6380,11 +6566,12 @@ function QuickActionChipButton({
6380
6566
  onMouseLeave: onHoverEnd,
6381
6567
  onFocus: onHoverStart,
6382
6568
  onBlur: onHoverEnd,
6569
+ "aria-pressed": selected || void 0,
6383
6570
  className: cn(
6384
6571
  "shrink-0 rounded-md focus:outline-none focus-visible:ring-2 focus-visible:ring-ods-accent",
6385
6572
  className
6386
6573
  ),
6387
- children: /* @__PURE__ */ jsx34(Tag, { variant, icon: resolvedIcon, label: resolvedLabel })
6574
+ children: /* @__PURE__ */ jsx34(Tag, { variant: tagVariant, size: size2, icon: resolvedIcon, label: resolvedLabel })
6388
6575
  }
6389
6576
  );
6390
6577
  }
@@ -18521,6 +18708,7 @@ var socialCache = SocialEmbedCache.getInstance();
18521
18708
  // src/components/media-carousel.tsx
18522
18709
  init_cn();
18523
18710
  import { useState as useState39, useRef as useRef23, useEffect as useEffect32, memo as memo4, useCallback as useCallback19 } from "react";
18711
+ init_button2();
18524
18712
  import { Fragment as Fragment30, jsx as jsx135, jsxs as jsxs104 } from "react/jsx-runtime";
18525
18713
  var ChevronLeftIcon = () => /* @__PURE__ */ jsx135("svg", { width: "24", height: "24", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx135("polyline", { points: "15,18 9,12 15,6" }) });
18526
18714
  var ChevronRightIcon = () => /* @__PURE__ */ jsx135("svg", { width: "24", height: "24", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx135("polyline", { points: "9,18 15,12 9,6" }) });
@@ -18528,27 +18716,28 @@ var MediaCarousel = memo4(function MediaCarousel2({
18528
18716
  media,
18529
18717
  className,
18530
18718
  aspectRatio = "16/9",
18531
- showThumbnails = true,
18532
- autoPlay = false,
18533
- objectFit = "contain"
18719
+ showArrows = true,
18720
+ objectFit = "contain",
18721
+ posterPriority = false,
18722
+ transformImageSrc,
18723
+ reserveDotRow = false
18534
18724
  }) {
18535
18725
  const [currentIndex, setCurrentIndex] = useState39(0);
18536
18726
  const [touchStart, setTouchStart] = useState39(null);
18537
18727
  const [touchEnd, setTouchEnd] = useState39(null);
18728
+ const [brokenResourceUrls, setBrokenResourceUrls] = useState39({});
18729
+ const [loadedImageSrc, setLoadedImageSrc] = useState39(null);
18538
18730
  const carouselRef = useRef23(null);
18539
- const thumbnailsRef = useRef23(null);
18731
+ const onImageError = useCallback19((originalUrl) => {
18732
+ const key = originalUrl.trim() || "__missing_src__";
18733
+ setBrokenResourceUrls((b) => b[key] ? b : { ...b, [key]: true });
18734
+ }, []);
18540
18735
  useEffect32(() => {
18541
18736
  if (currentIndex >= media.length && media.length > 0) {
18542
18737
  setCurrentIndex(media.length - 1);
18543
18738
  }
18544
18739
  }, [media.length, currentIndex]);
18545
- if (!media || media.length === 0) {
18546
- return null;
18547
- }
18548
18740
  const currentItem = media[currentIndex] || media[0];
18549
- if (!currentItem) {
18550
- return null;
18551
- }
18552
18741
  const nextSlide = useCallback19(() => {
18553
18742
  setCurrentIndex((prev) => (prev + 1) % media.length);
18554
18743
  }, [media.length]);
@@ -18556,9 +18745,8 @@ var MediaCarousel = memo4(function MediaCarousel2({
18556
18745
  setCurrentIndex((prev) => (prev - 1 + media.length) % media.length);
18557
18746
  }, [media.length]);
18558
18747
  const selectSlide = useCallback19((index) => {
18559
- if (index === currentIndex) return;
18560
- setCurrentIndex(index);
18561
- }, [currentIndex]);
18748
+ setCurrentIndex((prev) => index === prev ? prev : index);
18749
+ }, []);
18562
18750
  const handleKeyDown = useCallback19((e) => {
18563
18751
  if (media.length <= 1) return;
18564
18752
  if (e.key === "ArrowLeft") {
@@ -18569,6 +18757,12 @@ var MediaCarousel = memo4(function MediaCarousel2({
18569
18757
  nextSlide();
18570
18758
  }
18571
18759
  }, [nextSlide, prevSlide, media.length]);
18760
+ if (!media || media.length === 0) {
18761
+ return null;
18762
+ }
18763
+ if (!currentItem) {
18764
+ return null;
18765
+ }
18572
18766
  const minSwipeDistance = 50;
18573
18767
  const onTouchStart = (e) => {
18574
18768
  setTouchEnd(null);
@@ -18578,7 +18772,7 @@ var MediaCarousel = memo4(function MediaCarousel2({
18578
18772
  setTouchEnd(e.targetTouches[0].clientX);
18579
18773
  };
18580
18774
  const onTouchEnd = () => {
18581
- if (!touchStart || !touchEnd) return;
18775
+ if (touchStart === null || touchEnd === null) return;
18582
18776
  const distance = touchStart - touchEnd;
18583
18777
  const isLeftSwipe = distance > minSwipeDistance;
18584
18778
  const isRightSwipe = distance < -minSwipeDistance;
@@ -18589,43 +18783,77 @@ var MediaCarousel = memo4(function MediaCarousel2({
18589
18783
  prevSlide();
18590
18784
  }
18591
18785
  };
18592
- const renderYouTubeEmbed = (item, index) => /* @__PURE__ */ jsx135(
18593
- Video2,
18594
- {
18595
- kind: "youtube",
18596
- url: item.src,
18597
- title: item.alt || `Video ${index + 1}`,
18598
- layout: "fill",
18599
- priority: index === currentIndex
18600
- }
18601
- );
18786
+ const displayImageSrc = (url) => transformImageSrc ? transformImageSrc(url) : url;
18602
18787
  const renderVideo = (item, index) => /* @__PURE__ */ jsx135(
18603
18788
  Video2,
18604
18789
  {
18605
18790
  url: item.src,
18606
- poster: item.poster,
18791
+ poster: item.poster ? displayImageSrc(item.poster) : void 0,
18792
+ title: item.alt || `Video ${index + 1}`,
18607
18793
  muted: true,
18608
18794
  layout: "fill",
18609
- priority: index === currentIndex
18610
- }
18795
+ priority: Boolean(posterPriority && index === 0),
18796
+ className: "w-full h-full bg-black"
18797
+ },
18798
+ `video-${index}-${item.src}`
18611
18799
  );
18612
- const renderImage = (item, index) => /* @__PURE__ */ jsx135("div", { className: "absolute inset-0 bg-black", children: /* @__PURE__ */ jsx135(
18613
- "img",
18614
- {
18615
- src: item.src,
18616
- alt: item.alt || `Media ${index + 1}`,
18617
- className: `w-full h-full object-${objectFit}`,
18618
- loading: "lazy",
18619
- onError: (e) => {
18620
- const target = e.target;
18621
- target.style.display = "none";
18800
+ const renderImageUnavailable = (failedUrl) => /* @__PURE__ */ jsxs104("div", { className: "absolute inset-0 flex flex-col items-center justify-center gap-3 bg-ods-card px-6 text-center", children: [
18801
+ /* @__PURE__ */ jsx135(ImageOffIcon, { className: "size-8 text-ods-text-secondary" }),
18802
+ /* @__PURE__ */ jsx135("p", { className: "text-ods-text-secondary text-sm max-w-md", children: "This image is no longer available at the original URL." }),
18803
+ /* @__PURE__ */ jsx135(
18804
+ next_link_default,
18805
+ {
18806
+ href: failedUrl,
18807
+ target: "_blank",
18808
+ rel: "noopener noreferrer",
18809
+ className: "text-ods-accent text-sm underline break-all",
18810
+ children: "Open link"
18622
18811
  }
18623
- }
18624
- ) });
18812
+ )
18813
+ ] });
18814
+ const renderImage = (item, index) => {
18815
+ const rawSrc = typeof item.src === "string" ? item.src.trim() : "";
18816
+ if (!rawSrc) {
18817
+ return /* @__PURE__ */ jsx135("div", { className: "absolute inset-0 flex flex-col items-center justify-center gap-3 bg-ods-card px-6 text-center", children: /* @__PURE__ */ jsx135("p", { className: "text-ods-text-secondary text-sm max-w-md", children: "No image URL for this slide." }) });
18818
+ }
18819
+ if (brokenResourceUrls[rawSrc]) {
18820
+ return renderImageUnavailable(rawSrc);
18821
+ }
18822
+ const mainImagePriority = Boolean(posterPriority && index === 0);
18823
+ const imageLoaded = loadedImageSrc === rawSrc;
18824
+ return /* @__PURE__ */ jsxs104("div", { className: "absolute inset-0 bg-ods-bg", children: [
18825
+ !imageLoaded && /* @__PURE__ */ jsx135(
18826
+ "div",
18827
+ {
18828
+ className: "absolute inset-0 z-[1] bg-ods-card/90 animate-pulse",
18829
+ "aria-hidden": true
18830
+ }
18831
+ ),
18832
+ /* @__PURE__ */ jsx135(
18833
+ next_image_default,
18834
+ {
18835
+ src: displayImageSrc(rawSrc),
18836
+ alt: item.alt || `Media ${index + 1}`,
18837
+ className: cn(
18838
+ `w-full h-full object-${objectFit} relative z-[2]`,
18839
+ !imageLoaded && "opacity-0",
18840
+ imageLoaded && "opacity-100 transition-opacity duration-200"
18841
+ ),
18842
+ priority: mainImagePriority,
18843
+ loading: mainImagePriority ? "eager" : "lazy",
18844
+ sizes: "(max-width: 1279px) 100vw, 1428px",
18845
+ width: 1428,
18846
+ height: 802,
18847
+ unoptimized: true,
18848
+ onLoad: () => setLoadedImageSrc(rawSrc),
18849
+ onError: () => onImageError(rawSrc)
18850
+ }
18851
+ )
18852
+ ] });
18853
+ };
18625
18854
  const renderMainMedia = (item, index) => {
18626
18855
  switch (item.type) {
18627
18856
  case "youtube":
18628
- return renderYouTubeEmbed(item, index);
18629
18857
  case "video":
18630
18858
  return renderVideo(item, index);
18631
18859
  case "image":
@@ -18633,41 +18861,6 @@ var MediaCarousel = memo4(function MediaCarousel2({
18633
18861
  return renderImage(item, index);
18634
18862
  }
18635
18863
  };
18636
- const renderThumbnail = (item, index) => {
18637
- const isActive = index === currentIndex;
18638
- let thumbnailSrc = item.src;
18639
- if (item.type === "youtube") {
18640
- const videoId = extractYouTubeId(item.src);
18641
- thumbnailSrc = videoId ? `https://img.youtube.com/vi/${videoId}/mqdefault.jpg` : item.src;
18642
- } else if (item.type === "video" && item.poster) {
18643
- thumbnailSrc = item.poster;
18644
- }
18645
- return /* @__PURE__ */ jsxs104(
18646
- "button",
18647
- {
18648
- onClick: () => selectSlide(index),
18649
- className: cn(
18650
- "relative flex-shrink-0 overflow-hidden transition-all duration-200",
18651
- "w-20 h-20 md:w-24 md:h-24 rounded-lg border-2",
18652
- isActive ? "border-[#FFC008] ring-2 ring-[#FFC008]/20" : "border-ods-border hover:border-[#888888]"
18653
- ),
18654
- children: [
18655
- /* @__PURE__ */ jsx135(
18656
- "img",
18657
- {
18658
- src: thumbnailSrc,
18659
- alt: item.alt || `Thumbnail ${index + 1}`,
18660
- className: "w-full h-full object-cover",
18661
- loading: "lazy"
18662
- }
18663
- ),
18664
- (item.type === "video" || item.type === "youtube") && /* @__PURE__ */ jsx135("div", { className: "absolute inset-0 flex items-center justify-center bg-black/30", children: /* @__PURE__ */ jsx135(VideoPlayBadge, { size: "sm" }) }),
18665
- isActive && /* @__PURE__ */ jsx135("div", { className: "absolute bottom-1 right-1 w-2 h-2 bg-[#FFC008] rounded-full" })
18666
- ]
18667
- },
18668
- index
18669
- );
18670
- };
18671
18864
  const getAspectRatioClass = () => {
18672
18865
  switch (aspectRatio) {
18673
18866
  case "4/3":
@@ -18681,13 +18874,14 @@ var MediaCarousel = memo4(function MediaCarousel2({
18681
18874
  return "aspect-video";
18682
18875
  }
18683
18876
  };
18684
- return /* @__PURE__ */ jsxs104("div", { className: cn("space-y-4", className), children: [
18877
+ return /* @__PURE__ */ jsxs104("div", { className: cn("flex flex-col items-center gap-[var(--spacing-system-l)]", className), children: [
18685
18878
  /* @__PURE__ */ jsxs104(
18686
18879
  "div",
18687
18880
  {
18688
18881
  ref: carouselRef,
18689
18882
  className: cn(
18690
- "relative bg-ods-bg border border-ods-border rounded-2xl overflow-hidden group w-full",
18883
+ "relative bg-ods-bg border border-ods-border rounded-md overflow-hidden group w-full",
18884
+ "[box-shadow:var(--shadow-media-frame)]",
18691
18885
  getAspectRatioClass()
18692
18886
  ),
18693
18887
  onTouchStart,
@@ -18699,43 +18893,54 @@ var MediaCarousel = memo4(function MediaCarousel2({
18699
18893
  "aria-label": media.length > 1 ? "Media carousel, use arrow keys to navigate" : void 0,
18700
18894
  children: [
18701
18895
  renderMainMedia(currentItem, currentIndex),
18702
- media.length > 1 && /* @__PURE__ */ jsxs104(Fragment30, { children: [
18896
+ showArrows && media.length > 1 && /* @__PURE__ */ jsxs104(Fragment30, { children: [
18703
18897
  /* @__PURE__ */ jsx135(
18704
- "button",
18898
+ Button,
18705
18899
  {
18900
+ variant: "transparent",
18706
18901
  onClick: prevSlide,
18707
- className: "absolute left-3 top-1/2 transform -translate-y-1/2 bg-black/50 text-white rounded-full p-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200 hover:bg-black/70 z-10",
18902
+ className: "absolute left-3 top-1/2 h-auto md:h-auto -translate-y-1/2 rounded-full bg-black/50 p-2 text-white opacity-0 transition-opacity duration-200 group-hover:opacity-100 hover:bg-black/70 z-10",
18708
18903
  "aria-label": "Previous media",
18709
18904
  children: /* @__PURE__ */ jsx135(ChevronLeftIcon, {})
18710
18905
  }
18711
18906
  ),
18712
18907
  /* @__PURE__ */ jsx135(
18713
- "button",
18908
+ Button,
18714
18909
  {
18910
+ variant: "transparent",
18715
18911
  onClick: nextSlide,
18716
- className: "absolute right-3 top-1/2 transform -translate-y-1/2 bg-black/50 text-white rounded-full p-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200 hover:bg-black/70 z-10",
18912
+ className: "absolute right-3 top-1/2 h-auto md:h-auto -translate-y-1/2 rounded-full bg-black/50 p-2 text-white opacity-0 transition-opacity duration-200 group-hover:opacity-100 hover:bg-black/70 z-10",
18717
18913
  "aria-label": "Next media",
18718
18914
  children: /* @__PURE__ */ jsx135(ChevronRightIcon, {})
18719
18915
  }
18720
- ),
18721
- /* @__PURE__ */ jsxs104("div", { className: "absolute top-3 right-3 bg-black/70 text-white px-3 py-1 rounded-lg text-sm font-medium", children: [
18722
- currentIndex + 1,
18723
- " / ",
18724
- media.length
18725
- ] })
18916
+ )
18726
18917
  ] })
18727
18918
  ]
18728
18919
  }
18729
18920
  ),
18730
- media.length > 1 && showThumbnails && /* @__PURE__ */ jsx135("div", { className: "w-full", children: /* @__PURE__ */ jsx135(
18731
- "div",
18732
- {
18733
- ref: thumbnailsRef,
18734
- className: "flex gap-2 overflow-x-auto scrollbar-none py-2",
18735
- style: { scrollbarWidth: "none", msOverflowStyle: "none" },
18736
- children: media.map((item, index) => renderThumbnail(item, index))
18737
- }
18738
- ) })
18921
+ (media.length > 1 || reserveDotRow) && /* @__PURE__ */ jsx135("div", { className: "flex h-6 items-center gap-[var(--spacing-system-xs)]", role: "group", "aria-label": "Choose slide", children: media.length > 1 && media.map((item, index) => {
18922
+ const isActive = index === currentIndex;
18923
+ return /* @__PURE__ */ jsx135(
18924
+ Button,
18925
+ {
18926
+ variant: "transparent",
18927
+ "aria-current": isActive ? "true" : void 0,
18928
+ "aria-label": `Go to slide ${index + 1}`,
18929
+ onClick: () => selectSlide(index),
18930
+ className: "flex h-6 w-6 md:h-6 shrink-0 items-center justify-center rounded-full p-0",
18931
+ children: /* @__PURE__ */ jsx135(
18932
+ "span",
18933
+ {
18934
+ className: cn(
18935
+ "size-2 rounded-full transition-colors duration-150",
18936
+ isActive ? "bg-[var(--ods-flamingo-pink-base)]" : "bg-[var(--ods-system-greys-soft-grey)] hover:bg-[var(--ods-system-greys-soft-grey-hover)]"
18937
+ )
18938
+ }
18939
+ )
18940
+ },
18941
+ index
18942
+ );
18943
+ }) })
18739
18944
  ] });
18740
18945
  });
18741
18946
 
@@ -19071,7 +19276,6 @@ function RedditEmbedClient({ url, maxWidth = 700 }) {
19071
19276
  width: m.width,
19072
19277
  height: m.height
19073
19278
  }));
19074
- console.log("\u{1F3AC} MediaCarousel will render with:", mediaContent.length, "items", mediaContent);
19075
19279
  const formatTimeAgo = (timestamp) => {
19076
19280
  const now = Math.floor(Date.now() / 1e3);
19077
19281
  const diffSeconds = now - timestamp;
@@ -19118,8 +19322,7 @@ function RedditEmbedClient({ url, maxWidth = 700 }) {
19118
19322
  MediaCarousel,
19119
19323
  {
19120
19324
  media: carouselItems,
19121
- aspectRatio: "16/9",
19122
- showThumbnails: carouselItems.length > 1
19325
+ aspectRatio: "16/9"
19123
19326
  }
19124
19327
  ),
19125
19328
  /* @__PURE__ */ jsxs105("div", { className: "flex items-center space-x-6 text-ods-text-secondary text-sm", children: [
@@ -22901,173 +23104,6 @@ function isGenericWebsiteDomain(website) {
22901
23104
  return isGenericDomain(normalizeDomain(website));
22902
23105
  }
22903
23106
 
22904
- // src/utils/color-analysis.ts
22905
- var DESIGN_PALETTE = [
22906
- { name: "yellow", hex: "#FFC008", rgb: [255, 192, 8] },
22907
- { name: "black", hex: "#161616", rgb: [22, 22, 22] },
22908
- { name: "gray", hex: "#888888", rgb: [136, 136, 136] }
22909
- ];
22910
- function hexToRgb2(hex) {
22911
- const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
22912
- return result ? [parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16)] : [0, 0, 0];
22913
- }
22914
- function getLuminance(rgb) {
22915
- const [r, g, b] = rgb.map((c) => {
22916
- c = c / 255;
22917
- return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
22918
- });
22919
- return 0.2126 * r + 0.7152 * g + 0.0722 * b;
22920
- }
22921
- function getContrastRatio(color1, color2) {
22922
- const lum1 = getLuminance(color1);
22923
- const lum2 = getLuminance(color2);
22924
- const brightest = Math.max(lum1, lum2);
22925
- const darkest = Math.min(lum1, lum2);
22926
- return (brightest + 0.05) / (darkest + 0.05);
22927
- }
22928
- function pickReadableTextColor(bgHex) {
22929
- const bg = hexToRgb2(bgHex);
22930
- const darkContrast = getContrastRatio(bg, [26, 26, 26]);
22931
- const lightContrast = getContrastRatio(bg, [250, 250, 250]);
22932
- return darkContrast >= lightContrast ? "dark" : "light";
22933
- }
22934
- function extractDominantColor(canvas) {
22935
- const ctx = canvas.getContext("2d");
22936
- if (!ctx) return [128, 128, 128];
22937
- const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
22938
- const data = imageData.data;
22939
- const sampleSize = Math.max(1, Math.floor(data.length / (4 * 1e3)));
22940
- const colorCounts = {};
22941
- for (let i = 0; i < data.length; i += 4 * sampleSize) {
22942
- const r = data[i];
22943
- const g = data[i + 1];
22944
- const b = data[i + 2];
22945
- const alpha = data[i + 3];
22946
- if (alpha < 128) continue;
22947
- const bucketR = Math.round(r / 32) * 32;
22948
- const bucketG = Math.round(g / 32) * 32;
22949
- const bucketB = Math.round(b / 32) * 32;
22950
- const key = `${bucketR},${bucketG},${bucketB}`;
22951
- colorCounts[key] = (colorCounts[key] || 0) + 1;
22952
- }
22953
- let maxCount = 0;
22954
- let dominantColor = [128, 128, 128];
22955
- for (const [colorKey, count] of Object.entries(colorCounts)) {
22956
- if (count > maxCount) {
22957
- maxCount = count;
22958
- const [r, g, b] = colorKey.split(",").map(Number);
22959
- dominantColor = [r, g, b];
22960
- }
22961
- }
22962
- return dominantColor;
22963
- }
22964
- function getBestContrastColor(imageColor) {
22965
- let bestColor = DESIGN_PALETTE[0];
22966
- let bestContrast = 0;
22967
- for (const color of DESIGN_PALETTE) {
22968
- const contrast = getContrastRatio(imageColor, color.rgb);
22969
- if (contrast > bestContrast) {
22970
- bestContrast = contrast;
22971
- bestColor = color;
22972
- }
22973
- }
22974
- return bestContrast >= 3 ? bestColor : DESIGN_PALETTE.find((c) => c.name === "black") || bestColor;
22975
- }
22976
- function analyzeImageColor(imageSrc) {
22977
- return new Promise((resolve, reject) => {
22978
- const img = new Image();
22979
- img.crossOrigin = "anonymous";
22980
- img.onload = () => {
22981
- try {
22982
- const canvas = document.createElement("canvas");
22983
- const ctx = canvas.getContext("2d");
22984
- if (!ctx) {
22985
- reject(new Error("Could not get canvas context"));
22986
- return;
22987
- }
22988
- const maxSize = 100;
22989
- const scale = Math.min(maxSize / img.width, maxSize / img.height);
22990
- canvas.width = img.width * scale;
22991
- canvas.height = img.height * scale;
22992
- ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
22993
- const dominantColor = extractDominantColor(canvas);
22994
- const bestContrastColor = getBestContrastColor(dominantColor);
22995
- resolve(bestContrastColor);
22996
- } catch (error) {
22997
- reject(error);
22998
- }
22999
- };
23000
- img.onerror = () => reject(new Error("Failed to load image"));
23001
- img.src = imageSrc;
23002
- });
23003
- }
23004
- async function extractImageEdgeColorAsync(imageUrl) {
23005
- return new Promise((resolve) => {
23006
- if (typeof window === "undefined") {
23007
- resolve("#000000");
23008
- return;
23009
- }
23010
- const img = new Image();
23011
- img.crossOrigin = "anonymous";
23012
- img.onload = () => {
23013
- try {
23014
- const canvas = document.createElement("canvas");
23015
- const ctx = canvas.getContext("2d");
23016
- if (!ctx) {
23017
- resolve("#000000");
23018
- return;
23019
- }
23020
- const maxSize = 50;
23021
- const scale = Math.min(maxSize / img.naturalWidth, maxSize / img.naturalHeight);
23022
- const w = Math.round(img.naturalWidth * scale);
23023
- const h = Math.round(img.naturalHeight * scale);
23024
- canvas.width = w;
23025
- canvas.height = h;
23026
- ctx.drawImage(img, 0, 0, w, h);
23027
- const data = ctx.getImageData(0, 0, w, h).data;
23028
- const edgeW = Math.max(2, Math.round(w * 0.15));
23029
- const edgeH = Math.max(2, Math.round(h * 0.15));
23030
- const bucketSize = 32;
23031
- const buckets = /* @__PURE__ */ new Map();
23032
- for (let y = 0; y < h; y++) {
23033
- for (let x = 0; x < w; x++) {
23034
- if (!(x < edgeW || x >= w - edgeW || y < edgeH || y >= h - edgeH)) continue;
23035
- const i = (y * w + x) * 4;
23036
- if (data[i + 3] < 128) continue;
23037
- const br = Math.floor(data[i] / bucketSize) * bucketSize;
23038
- const bg = Math.floor(data[i + 1] / bucketSize) * bucketSize;
23039
- const bb = Math.floor(data[i + 2] / bucketSize) * bucketSize;
23040
- const key = `${br},${bg},${bb}`;
23041
- const existing = buckets.get(key);
23042
- if (existing) {
23043
- existing.r += data[i];
23044
- existing.g += data[i + 1];
23045
- existing.b += data[i + 2];
23046
- existing.count++;
23047
- } else {
23048
- buckets.set(key, { r: data[i], g: data[i + 1], b: data[i + 2], count: 1 });
23049
- }
23050
- }
23051
- }
23052
- let best = { r: 0, g: 0, b: 0, count: 0 };
23053
- for (const b2 of buckets.values()) {
23054
- if (b2.count > best.count) best = b2;
23055
- }
23056
- if (best.count === 0) {
23057
- resolve("#000000");
23058
- return;
23059
- }
23060
- const r = Math.round(best.r / best.count), g = Math.round(best.g / best.count), b = Math.round(best.b / best.count);
23061
- resolve(`#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`);
23062
- } catch {
23063
- resolve("#000000");
23064
- }
23065
- };
23066
- img.onerror = () => resolve("#000000");
23067
- img.src = imageUrl;
23068
- });
23069
- }
23070
-
23071
23107
  // src/utils/announcement-storage.ts
23072
23108
  function announcementDismissCookieName(platform) {
23073
23109
  return `${platform}-announcement-dismissed`;
@@ -23096,6 +23132,19 @@ function isAnnouncementDismissed(platform, id) {
23096
23132
  return false;
23097
23133
  }
23098
23134
  }
23135
+ function clearAnnouncementDismissals(platform) {
23136
+ if (typeof document === "undefined") return;
23137
+ document.cookie = `${announcementDismissCookieName(platform)}=; path=/; max-age=0`;
23138
+ try {
23139
+ const prefix = `${platform}-announcement-`;
23140
+ Object.keys(localStorage).forEach((key) => {
23141
+ if (key.startsWith(prefix) && key.endsWith("-dismissed")) {
23142
+ localStorage.removeItem(key);
23143
+ }
23144
+ });
23145
+ } catch {
23146
+ }
23147
+ }
23099
23148
  function clearLegacyAnnouncementCache(platform) {
23100
23149
  if (typeof window === "undefined") return;
23101
23150
  try {
@@ -41858,7 +41907,7 @@ function ChannelInput({
41858
41907
  );
41859
41908
  }
41860
41909
  function RgbInputs({ value, onChange }) {
41861
- const rgb = hexToRgb(value) ?? { r: 0, g: 0, b: 0 };
41910
+ const rgb = hexToRgb2(value) ?? { r: 0, g: 0, b: 0 };
41862
41911
  const update = (next) => {
41863
41912
  const r = next.r ?? rgb.r;
41864
41913
  const g = next.g ?? rgb.g;
@@ -41872,7 +41921,7 @@ function RgbInputs({ value, onChange }) {
41872
41921
  ] });
41873
41922
  }
41874
41923
  function HslInputs({ value, onChange }) {
41875
- const rgb = hexToRgb(value) ?? { r: 0, g: 0, b: 0 };
41924
+ const rgb = hexToRgb2(value) ?? { r: 0, g: 0, b: 0 };
41876
41925
  const hsl = rgbToHsl(rgb.r, rgb.g, rgb.b);
41877
41926
  const update = (next) => {
41878
41927
  const h = next.h ?? hsl.h;
@@ -52466,11 +52515,18 @@ export {
52466
52515
  ActionsMenu,
52467
52516
  ActionsMenuDropdown,
52468
52517
  ColorSwatch,
52518
+ DESIGN_PALETTE,
52519
+ getContrastRatio,
52520
+ pickReadableTextColor,
52521
+ extractDominantColor,
52522
+ getBestContrastColor,
52523
+ analyzeImageColor,
52524
+ extractImageEdgeColorAsync,
52469
52525
  getPlatformAccentColor,
52470
52526
  getCurrentPlatform,
52471
52527
  getReadableTextColor,
52472
52528
  HEX_PATTERN,
52473
- hexToRgb,
52529
+ hexToRgb2 as hexToRgb,
52474
52530
  rgbToHex,
52475
52531
  deriveHoverColor,
52476
52532
  deriveActiveColor,
@@ -53033,18 +53089,11 @@ export {
53033
53089
  isGenericDomain,
53034
53090
  hasGenericEmailDomain,
53035
53091
  isGenericWebsiteDomain,
53036
- DESIGN_PALETTE,
53037
- getLuminance,
53038
- getContrastRatio,
53039
- pickReadableTextColor,
53040
- extractDominantColor,
53041
- getBestContrastColor,
53042
- analyzeImageColor,
53043
- extractImageEdgeColorAsync,
53044
53092
  announcementDismissCookieName,
53045
53093
  isDismissedCookieValue,
53046
53094
  dismissAnnouncement,
53047
53095
  isAnnouncementDismissed,
53096
+ clearAnnouncementDismissals,
53048
53097
  clearLegacyAnnouncementCache,
53049
53098
  readLeadingDecisionFrame,
53050
53099
  ANNOUNCEMENT_CTA_DEFAULTS,
@@ -53360,4 +53409,4 @@ export {
53360
53409
  LogsList,
53361
53410
  assets
53362
53411
  };
53363
- //# sourceMappingURL=chunk-SOK32D6C.js.map
53412
+ //# sourceMappingURL=chunk-IVM47EGI.js.map