@nswds/app 1.104.0 → 1.106.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -18269,8 +18269,9 @@ function ColorPairingToolV3Content({
18269
18269
  const [copiedKey, setCopiedKey] = React5.useState(null);
18270
18270
  const copiedKeyTimeoutRef = React5.useRef(null);
18271
18271
  const resultSectionRef = React5.useRef(null);
18272
+ const technicalDetailsSectionRef = React5.useRef(null);
18272
18273
  const noValidStateRef = React5.useRef(/* @__PURE__ */ new Set());
18273
- const technicalDetailsOpenedRef = React5.useRef(false);
18274
+ const technicalDetailsViewedRef = React5.useRef(false);
18274
18275
  const emitAnalyticsEvent = React5.useCallback(
18275
18276
  (event) => {
18276
18277
  onAnalyticsEvent(event);
@@ -18599,24 +18600,1535 @@ function ColorPairingToolV3Content({
18599
18600
  themeCategory
18600
18601
  ]);
18601
18602
  React5.useEffect(() => {
18602
- if (technicalDetailsOpenedRef.current) {
18603
+ if (technicalDetailsViewedRef.current) {
18603
18604
  return;
18604
18605
  }
18605
- technicalDetailsOpenedRef.current = true;
18606
+ const section = technicalDetailsSectionRef.current;
18607
+ if (!section) {
18608
+ return;
18609
+ }
18610
+ const emitViewedEvent = () => {
18611
+ if (technicalDetailsViewedRef.current) {
18612
+ return;
18613
+ }
18614
+ technicalDetailsViewedRef.current = true;
18615
+ emitAnalyticsEvent({
18616
+ name: "technical_details_viewed",
18617
+ ...buildAnalyticsContext({
18618
+ source: "details"
18619
+ })
18620
+ });
18621
+ };
18622
+ if (typeof IntersectionObserver === "undefined") {
18623
+ const rect = section.getBoundingClientRect();
18624
+ const viewportHeight = window.innerHeight || document.documentElement.clientHeight;
18625
+ if (rect.top < viewportHeight && rect.bottom > 0) {
18626
+ emitViewedEvent();
18627
+ }
18628
+ return;
18629
+ }
18630
+ const observer = new IntersectionObserver(
18631
+ (entries) => {
18632
+ const [entry] = entries;
18633
+ if (entry?.isIntersecting) {
18634
+ emitViewedEvent();
18635
+ observer.disconnect();
18636
+ }
18637
+ },
18638
+ {
18639
+ threshold: 0.2
18640
+ }
18641
+ );
18642
+ observer.observe(section);
18643
+ return () => {
18644
+ observer.disconnect();
18645
+ };
18646
+ }, [buildAnalyticsContext, emitAnalyticsEvent]);
18647
+ if (!selectedBackground) {
18648
+ return /* @__PURE__ */ jsxRuntime.jsxs(Card, { className: "px-6 py-6", children: [
18649
+ /* @__PURE__ */ jsxRuntime.jsx(Heading, { level: 2, size: 5, className: "text-foreground", trim: "normal", children: "No approved background tones available" }),
18650
+ /* @__PURE__ */ jsxRuntime.jsx(Text, { size: 2, className: "mt-3", children: "No approved tones are available for the current palette and colour selection." })
18651
+ ] });
18652
+ }
18653
+ const compactControlsSummary = `${themeCategory === "brand" ? "Brand" : "Aboriginal"} palette, ${context.primary.label} primary, ${context.accent.label} accent, ${getPairingColorDisplayName3(
18654
+ selectedBackground
18655
+ )} background.`;
18656
+ const renderControlsPanel = (isOverlay = false) => /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
18657
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-b border-grey-200 px-5 py-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-4", children: [
18658
+ /* @__PURE__ */ jsxRuntime.jsx(Heading, { level: 2, size: 5, className: "text-foreground", trim: "normal", children: "Configuration" }),
18659
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
18660
+ !isFirstDrawerStep ? /* @__PURE__ */ jsxRuntime.jsxs(Button2, { variant: "ghost", color: "grey", size: "sm", onClick: goToPreviousDrawerStep, children: [
18661
+ /* @__PURE__ */ jsxRuntime.jsx(Icons.west, { "data-slot": "icon", className: "size-5" }),
18662
+ "Back"
18663
+ ] }) : null,
18664
+ isOverlay ? /* @__PURE__ */ jsxRuntime.jsx(
18665
+ Button2,
18666
+ {
18667
+ variant: "ghost",
18668
+ color: "grey",
18669
+ size: "icon",
18670
+ onClick: () => setIsCompactControlsOpen(false),
18671
+ "aria-label": "Close configuration",
18672
+ children: /* @__PURE__ */ jsxRuntime.jsx(Icons.close, { "data-slot": "icon", className: "size-5" })
18673
+ }
18674
+ ) : null
18675
+ ] })
18676
+ ] }) }),
18677
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-b border-grey-200 px-5 py-4", children: [
18678
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
18679
+ COLOR_PAIRING_TOOL_DRAWER_STEPS.map((step, index) => /* @__PURE__ */ jsxRuntime.jsx(
18680
+ "button",
18681
+ {
18682
+ type: "button",
18683
+ "aria-current": index === drawerStepIndex ? "step" : void 0,
18684
+ "aria-label": `${step.eyebrow}: ${step.title}`,
18685
+ onClick: () => goToDrawerStep(index),
18686
+ className: "flex-1 rounded-sm focus-visible:ring-2 focus-visible:ring-primary-700 focus-visible:ring-offset-2 focus-visible:outline-hidden",
18687
+ children: /* @__PURE__ */ jsxRuntime.jsx(
18688
+ "span",
18689
+ {
18690
+ className: cn(
18691
+ "block h-1.5 rounded-full transition-colors",
18692
+ index <= drawerStepIndex ? "bg-primary-800" : "bg-grey-200"
18693
+ )
18694
+ }
18695
+ )
18696
+ },
18697
+ step.id
18698
+ )),
18699
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mt-2 basis-full text-[0.68rem] font-semibold tracking-[0.12em] text-primary-800 uppercase sm:mt-0 sm:ml-2 sm:basis-auto sm:text-[0.72rem]", children: activeDrawerStep.eyebrow })
18700
+ ] }),
18701
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-4 space-y-2", children: [
18702
+ /* @__PURE__ */ jsxRuntime.jsx(Heading, { level: 3, size: 5, className: "text-foreground", trim: "normal", children: activeDrawerStep.title }),
18703
+ /* @__PURE__ */ jsxRuntime.jsx(Text, { size: 2, children: activeDrawerStep.description })
18704
+ ] })
18705
+ ] }),
18706
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative flex-1 overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsxs(
18707
+ "div",
18708
+ {
18709
+ className: "flex h-full transition-transform duration-300 ease-out",
18710
+ style: { transform: `translateX(-${drawerStepIndex * 100}%)` },
18711
+ children: [
18712
+ /* @__PURE__ */ jsxRuntime.jsx("section", { className: "h-full w-full shrink-0 overflow-y-auto px-5 py-5", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-6 pr-1", children: [
18713
+ /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "space-y-3", children: [
18714
+ /* @__PURE__ */ jsxRuntime.jsx(Heading, { level: 4, size: 6, className: "mb-3 text-foreground", trim: "normal", children: "Palette" }),
18715
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-1 gap-2 sm:grid-cols-2", children: ["brand", "aboriginal"].map((palette) => {
18716
+ const isSelected = themeCategory === palette;
18717
+ const label = palette === "brand" ? "Brand palette" : "Aboriginal palette";
18718
+ return /* @__PURE__ */ jsxRuntime.jsx(
18719
+ SelectorButton,
18720
+ {
18721
+ label,
18722
+ isSelected,
18723
+ onClick: () => handleThemeCategoryChange(palette)
18724
+ },
18725
+ palette
18726
+ );
18727
+ }) })
18728
+ ] }),
18729
+ /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "space-y-3", children: [
18730
+ /* @__PURE__ */ jsxRuntime.jsx(Heading, { level: 4, size: 6, className: "mb-3 text-foreground", trim: "normal", children: "Primary colour family" }),
18731
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-1 gap-2 sm:grid-cols-2", children: selectableFamilies.map((family) => {
18732
+ const label = getFamilySelectorLabel3(family, themeCategory, "primary colour");
18733
+ return /* @__PURE__ */ jsxRuntime.jsx(
18734
+ SelectorButton,
18735
+ {
18736
+ label,
18737
+ isSelected: family.key === context.primary.key,
18738
+ onClick: () => handlePrimaryColorChange(family.key),
18739
+ swatch: getFamilySwatchColor3(family, 800)
18740
+ },
18741
+ family.key
18742
+ );
18743
+ }) })
18744
+ ] }),
18745
+ /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "space-y-3", children: [
18746
+ /* @__PURE__ */ jsxRuntime.jsx(Heading, { level: 4, size: 6, className: "mb-3 text-foreground", trim: "normal", children: "Accent colour family" }),
18747
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-1 gap-2 sm:grid-cols-2", children: selectableAccentFamilies.map((family) => {
18748
+ const label = getFamilySelectorLabel3(family, themeCategory, "accent colour");
18749
+ return /* @__PURE__ */ jsxRuntime.jsx(
18750
+ SelectorButton,
18751
+ {
18752
+ label,
18753
+ isSelected: family.key === context.accent.key,
18754
+ onClick: () => handleAccentColorChange(family.key),
18755
+ swatch: getFamilySwatchColor3(family, 600)
18756
+ },
18757
+ family.key
18758
+ );
18759
+ }) })
18760
+ ] }),
18761
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-t border-grey-200 pt-4", children: [
18762
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-semibold text-foreground", children: "Grey is always included" }),
18763
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 text-sm text-muted-foreground", children: "Neutral grey options are automatically added so every selection includes a practical fallback family." })
18764
+ ] })
18765
+ ] }) }),
18766
+ /* @__PURE__ */ jsxRuntime.jsx("section", { className: "h-full w-full shrink-0 overflow-y-auto px-5 py-5", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-6 pr-1", children: context.backgroundGroups.map((group, index) => /* @__PURE__ */ jsxRuntime.jsxs(
18767
+ "section",
18768
+ {
18769
+ className: cn("space-y-3", index > 0 && "border-t border-grey-200 pt-6"),
18770
+ children: [
18771
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
18772
+ /* @__PURE__ */ jsxRuntime.jsxs(Heading, { level: 4, size: 6, className: "text-foreground", trim: "normal", children: [
18773
+ group.family.label,
18774
+ " backgrounds"
18775
+ ] }),
18776
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: group.label })
18777
+ ] }),
18778
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid gap-3", children: group.backgrounds.map((background) => /* @__PURE__ */ jsxRuntime.jsx(
18779
+ BackgroundSwatchButton,
18780
+ {
18781
+ background,
18782
+ hasPairs: (context.pairsByBackground[background.token]?.length ?? 0) > 0,
18783
+ isSelected: selectedBackground.token === background.token,
18784
+ onClick: () => handleBackgroundChange(background.token)
18785
+ },
18786
+ background.token
18787
+ )) })
18788
+ ]
18789
+ },
18790
+ group.key
18791
+ )) }) }),
18792
+ /* @__PURE__ */ jsxRuntime.jsx("section", { className: "h-full w-full shrink-0 overflow-y-auto px-5 py-5", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-4 pr-1", children: recommendationItems.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-3", children: recommendationItems.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
18793
+ RecommendationCard,
18794
+ {
18795
+ copiedKey,
18796
+ item,
18797
+ isSelected: item.pair.id === selectedPair?.id,
18798
+ onCopyPairing: () => copyValue(
18799
+ `pair:${item.pair.id}`,
18800
+ getPairingCopyText(item.pair),
18801
+ "Pairing copied",
18802
+ item.pair.id === bestRecommendedPair?.id ? {
18803
+ name: "best_recommendation_copied",
18804
+ ...buildAnalyticsContext({
18805
+ foregroundToken: item.pair.foreground.token,
18806
+ pairId: item.pair.id,
18807
+ source: "recommendations-list"
18808
+ })
18809
+ } : void 0
18810
+ ),
18811
+ onSelect: () => handlePairChange(item.pair.id, "recommendations-list")
18812
+ },
18813
+ item.pair.id
18814
+ )) }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-sm border border-grey-200 bg-white px-5 py-5", children: [
18815
+ /* @__PURE__ */ jsxRuntime.jsx(Heading, { level: 4, size: 6, className: "text-foreground", trim: "normal", children: "No valid combinations available" }),
18816
+ /* @__PURE__ */ jsxRuntime.jsx(Text, { size: 2, className: "mt-3", children: "No AAA-compliant foreground options are available for this background." })
18817
+ ] }) }) })
18818
+ ]
18819
+ }
18820
+ ) }),
18821
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-t border-grey-200 px-5 py-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between", children: [
18822
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-10 flex-1", children: isLastDrawerStep ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: "Select a combination to update the result immediately." }) : null }),
18823
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex w-full items-center gap-2 sm:w-auto sm:flex-none", children: [
18824
+ isOverlay && isLastDrawerStep ? /* @__PURE__ */ jsxRuntime.jsx(
18825
+ Button2,
18826
+ {
18827
+ color: "primary",
18828
+ className: "w-full sm:w-auto sm:min-w-40",
18829
+ onClick: () => setIsCompactControlsOpen(false),
18830
+ children: "Review result"
18831
+ }
18832
+ ) : null,
18833
+ !isLastDrawerStep ? /* @__PURE__ */ jsxRuntime.jsxs(Button2, { color: "primary", className: "w-full sm:w-auto", onClick: goToNextDrawerStep, children: [
18834
+ "Continue",
18835
+ /* @__PURE__ */ jsxRuntime.jsx(Icons.east, { "data-slot": "icon", className: "size-5" })
18836
+ ] }) : null
18837
+ ] })
18838
+ ] }) })
18839
+ ] });
18840
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-8 xl:grid-cols-[minmax(18rem,24rem)_minmax(0,1fr)]", children: [
18841
+ /* @__PURE__ */ jsxRuntime.jsx(
18842
+ "div",
18843
+ {
18844
+ "data-slot": "tool-sidebar",
18845
+ className: "hidden xl:sticky xl:top-[var(--tool-sidebar-top)] xl:block xl:self-start",
18846
+ style: desktopSidebarStyle,
18847
+ children: /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "h-[40rem] max-h-[40rem] gap-0 overflow-hidden py-0 sm:h-[44rem] sm:max-h-[44rem] xl:h-[var(--tool-sidebar-max-height)] xl:max-h-[var(--tool-sidebar-max-height)]", children: renderControlsPanel() })
18848
+ }
18849
+ ),
18850
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-slot": "tool-results", className: "space-y-6", children: [
18851
+ /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "space-y-4 xl:hidden", children: [
18852
+ /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "gap-4 px-5 py-5", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between", children: [
18853
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
18854
+ /* @__PURE__ */ jsxRuntime.jsx(Heading, { level: 2, size: 5, className: "text-foreground", trim: "normal", children: "Configuration" }),
18855
+ /* @__PURE__ */ jsxRuntime.jsx(Text, { size: 2, children: "Open the colour pairing drawer to change palette, background, and recommended combinations." }),
18856
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: compactControlsSummary })
18857
+ ] }),
18858
+ /* @__PURE__ */ jsxRuntime.jsxs(
18859
+ Button2,
18860
+ {
18861
+ color: "primary",
18862
+ className: "w-full sm:w-auto",
18863
+ onClick: () => setIsCompactControlsOpen(true),
18864
+ children: [
18865
+ /* @__PURE__ */ jsxRuntime.jsx(Icons.edit_square, { "data-slot": "icon", className: "size-5" }),
18866
+ "Open configuration"
18867
+ ]
18868
+ }
18869
+ )
18870
+ ] }) }),
18871
+ /* @__PURE__ */ jsxRuntime.jsx(Sheet, { open: isCompactControlsOpen, onOpenChange: setIsCompactControlsOpen, children: /* @__PURE__ */ jsxRuntime.jsxs(
18872
+ SheetContent,
18873
+ {
18874
+ side: "left",
18875
+ showClose: false,
18876
+ className: "w-[30rem] max-w-[90vw] overflow-hidden p-0",
18877
+ children: [
18878
+ /* @__PURE__ */ jsxRuntime.jsx(SheetTitle, { className: "sr-only", children: "Colour pairing configuration" }),
18879
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full flex-col bg-background", children: renderControlsPanel(true) })
18880
+ ]
18881
+ }
18882
+ ) })
18883
+ ] }),
18884
+ /* @__PURE__ */ jsxRuntime.jsxs("section", { ref: resultSectionRef, className: "space-y-6", "aria-label": "Current result", children: [
18885
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sr-only", "aria-live": "polite", children: liveAnnouncement }),
18886
+ /* @__PURE__ */ jsxRuntime.jsx(
18887
+ CurrentResultCard,
18888
+ {
18889
+ bestPair: bestRecommendedPair,
18890
+ familySummary,
18891
+ pair: previewPair,
18892
+ selectedBackground
18893
+ }
18894
+ )
18895
+ ] }),
18896
+ bestRecommendedPair ? /* @__PURE__ */ jsxRuntime.jsx("section", { className: "space-y-4", children: /* @__PURE__ */ jsxRuntime.jsx(
18897
+ BestRecommendationCard,
18898
+ {
18899
+ copiedKey,
18900
+ isCurrentSelection: selectedPair?.id === bestRecommendedPair.id,
18901
+ pair: bestRecommendedPair,
18902
+ reason: getBestRecommendationReason(bestRecommendedPair, context),
18903
+ onCopyPairing: () => copyValue(
18904
+ "best-pairing",
18905
+ getPairingCopyText(bestRecommendedPair),
18906
+ "Pairing copied",
18907
+ {
18908
+ name: "best_recommendation_copied",
18909
+ ...buildAnalyticsContext({
18910
+ foregroundToken: bestRecommendedPair.foreground.token,
18911
+ pairId: bestRecommendedPair.id,
18912
+ source: "best-recommendation"
18913
+ })
18914
+ }
18915
+ ),
18916
+ onUsePairing: () => handlePairChange(bestRecommendedPair.id, "best-recommendation")
18917
+ }
18918
+ ) }) : /* @__PURE__ */ jsxRuntime.jsxs(Card, { className: "px-6 py-6", children: [
18919
+ /* @__PURE__ */ jsxRuntime.jsx(Heading, { level: 2, size: 5, className: "text-foreground", trim: "normal", children: "Best recommended pairing" }),
18920
+ /* @__PURE__ */ jsxRuntime.jsx(Text, { size: 2, className: "mt-3", children: "No AAA-compliant foreground options available for this selection." })
18921
+ ] }),
18922
+ /* @__PURE__ */ jsxRuntime.jsxs("section", { ref: technicalDetailsSectionRef, className: "space-y-4", children: [
18923
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
18924
+ /* @__PURE__ */ jsxRuntime.jsx(Heading, { level: 2, size: 5, className: "text-foreground", trim: "normal", children: "Technical colour values" }),
18925
+ /* @__PURE__ */ jsxRuntime.jsx(Text, { size: 2, children: "Token, tone, HEX, RGB, HSL, and OKLCH values for the current selection." })
18926
+ ] }),
18927
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-4 lg:grid-cols-2", children: [
18928
+ /* @__PURE__ */ jsxRuntime.jsx(
18929
+ TechnicalDetailsPanel,
18930
+ {
18931
+ title: "Background values",
18932
+ color: selectedBackground,
18933
+ visibleFormats,
18934
+ copiedKey,
18935
+ onCopyValue: (copyKey, value, toastLabel) => copyValue(copyKey, value, toastLabel)
18936
+ }
18937
+ ),
18938
+ /* @__PURE__ */ jsxRuntime.jsx(
18939
+ TechnicalDetailsPanel,
18940
+ {
18941
+ title: "Foreground values",
18942
+ color: detailForeground,
18943
+ visibleFormats,
18944
+ copiedKey,
18945
+ onCopyValue: (copyKey, value, toastLabel) => copyValue(copyKey, value, toastLabel)
18946
+ }
18947
+ )
18948
+ ] }),
18949
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
18950
+ /* @__PURE__ */ jsxRuntime.jsx(Heading, { level: 3, size: 6, className: "mb-3 text-foreground", trim: "normal", children: "Share colour pairing" }),
18951
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-sm border border-grey-200 bg-white p-4", children: [
18952
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-start gap-3 sm:flex-row sm:items-start sm:justify-between", children: [
18953
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: "Copy this URL to share your current colour pairing:" }),
18954
+ /* @__PURE__ */ jsxRuntime.jsx(
18955
+ Button2,
18956
+ {
18957
+ variant: "ghost",
18958
+ color: "grey",
18959
+ size: "sm",
18960
+ className: "shrink-0",
18961
+ onClick: () => copyValue(
18962
+ "share-url",
18963
+ typeof window === "undefined" ? shareUrl : new URL(shareUrl, window.location.origin).toString(),
18964
+ "Colour pairing link copied"
18965
+ ),
18966
+ "aria-label": copiedKey === "share-url" ? "Colour pairing URL copied to clipboard" : "Copy colour pairing URL to clipboard",
18967
+ title: copiedKey === "share-url" ? "Copied" : "Copy URL",
18968
+ children: copiedKey === "share-url" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
18969
+ /* @__PURE__ */ jsxRuntime.jsx(Icons.check, { "data-slot": "icon", className: "size-5" }),
18970
+ "Copied"
18971
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
18972
+ /* @__PURE__ */ jsxRuntime.jsx(Icons.content_copy, { "data-slot": "icon", className: "size-5" }),
18973
+ "Copy URL"
18974
+ ] })
18975
+ }
18976
+ )
18977
+ ] }),
18978
+ /* @__PURE__ */ jsxRuntime.jsx("code", { className: "mt-3 block w-full rounded-sm border border-grey-200 bg-background px-3 py-2 font-mono text-[11px] break-all text-foreground sm:text-xs", children: shareUrl })
18979
+ ] })
18980
+ ] })
18981
+ ] })
18982
+ ] })
18983
+ ] });
18984
+ }
18985
+ function ColorPairingToolV3({
18986
+ onAnalyticsEvent = () => {
18987
+ },
18988
+ visibleFormats = DEFAULT_VISIBLE_FORMATS3
18989
+ } = {}) {
18990
+ const normalizedVisibleFormats = [...new Set(visibleFormats)];
18991
+ return /* @__PURE__ */ jsxRuntime.jsx(React5.Suspense, { fallback: /* @__PURE__ */ jsxRuntime.jsx(ColorPairingToolV3Loading, {}), children: /* @__PURE__ */ jsxRuntime.jsx(
18992
+ ColorPairingToolV3Content,
18993
+ {
18994
+ onAnalyticsEvent,
18995
+ visibleFormats: normalizedVisibleFormats
18996
+ }
18997
+ ) });
18998
+ }
18999
+ var PREFERRED_BACKGROUND_TONES4 = [400, 600, 200, 800, 100, 50];
19000
+ var DEFAULT_VISIBLE_FORMATS4 = ["hex", "rgb", "hsl", "oklch"];
19001
+ var DEFAULT_INITIAL_BACKGROUND_TOKEN4 = "nsw-blue-800";
19002
+ var DEFAULT_INITIAL_PAIR_ID4 = "nsw-blue-800:nsw-blue-200";
19003
+ var COLOR_PAIRING_TOOL_V4_PATH = "/core/colour/colour-pairing-tool-4";
19004
+ var MOBILE_RESULT_SCROLL_QUERY2 = "(max-width: 1023px)";
19005
+ var PERSISTENT_DRAWER_MIN_WIDTH_QUERY2 = "(min-width: 1280px)";
19006
+ var INITIAL_VISIBLE_ALTERNATIVES = 6;
19007
+ var COLOR_PAIRING_TOOL_DRAWER_STEPS2 = [
19008
+ {
19009
+ id: "colours",
19010
+ title: "Choose colours",
19011
+ eyebrow: "Step 1 of 3",
19012
+ description: "Select the palette, primary family, and accent family."
19013
+ },
19014
+ {
19015
+ id: "backgrounds",
19016
+ title: "Choose background",
19017
+ eyebrow: "Step 2 of 3",
19018
+ description: "Pick the background tone that you want to pair."
19019
+ },
19020
+ {
19021
+ id: "refine",
19022
+ title: "Refine or override recommendation",
19023
+ eyebrow: "Step 3 of 3",
19024
+ description: "Use the result panel to review the NSW default pairing and override it only when a different approved option is needed."
19025
+ }
19026
+ ];
19027
+ var COLOR_PAIRING_TOOL_V4_ANALYTICS_EVENT = "nsw-colour-pairing-tool-v4";
19028
+ function getToneFromToken4(token) {
19029
+ if (!token) return null;
19030
+ const match = token.match(/-(\d+)$/);
19031
+ return match ? Number.parseInt(match[1], 10) : null;
19032
+ }
19033
+ function getFamilySwatchColor4(family, preferredTone = 600) {
19034
+ const exactMatch = family.colors.find((color2) => color2.tone === preferredTone);
19035
+ if (exactMatch) {
19036
+ return exactMatch.hex;
19037
+ }
19038
+ const closestMatch = [...family.colors].sort(
19039
+ (left, right) => Math.abs(left.tone - preferredTone) - Math.abs(right.tone - preferredTone)
19040
+ )[0];
19041
+ return closestMatch?.hex ?? "transparent";
19042
+ }
19043
+ function getFamilySelectorLabel4(family, themeCategory, selectionRole) {
19044
+ if (themeCategory !== "aboriginal") {
19045
+ return family.label;
19046
+ }
19047
+ const preferredTone = selectionRole === "primary colour" ? 800 : 600;
19048
+ return family.colors.find((color2) => color2.tone === preferredTone)?.name ?? family.label;
19049
+ }
19050
+ function getPairingColorDisplayName4(color2) {
19051
+ return color2.name ?? `${color2.familyLabel} ${color2.tone}`;
19052
+ }
19053
+ function isWhiteForegroundPair4(pair) {
19054
+ return pair.foreground.token === "white";
19055
+ }
19056
+ function isLargeTextOnlyPair2(pair) {
19057
+ return pair.passes.aaaLarge && !pair.passes.aaaText;
19058
+ }
19059
+ function getWhiteForegroundGuidance4(pair) {
19060
+ if (pair.passes.aaaText) {
19061
+ return "White is approved for headings, body copy, and calls to action on this background.";
19062
+ }
19063
+ if (pair.passes.aaaLarge) {
19064
+ return "Use white only for WCAG large text on this background, such as headings at 24px+ or bold text at 18.5px+. Keep sentence-case body copy at 16px+ and use a darker recommended foreground instead.";
19065
+ }
19066
+ return "Do not use white on this background. Choose one of the recommended foregrounds below instead.";
19067
+ }
19068
+ function getPreviewGuidance4(pair, isRecommended) {
19069
+ if (!isWhiteForegroundPair4(pair)) {
19070
+ return isRecommended ? "Use this pairing for headings, body copy, and actions on the selected background." : "Use this approved alternative when you need a different tone or stronger emphasis.";
19071
+ }
19072
+ if (isRecommended) {
19073
+ return "Use white text on this background for headings, body copy, and actions only while it maintains AAA contrast.";
19074
+ }
19075
+ return getWhiteForegroundGuidance4(pair);
19076
+ }
19077
+ function getPreferredPairForBackground4(pairs, preferredPairId) {
19078
+ if (preferredPairId) {
19079
+ const preferredPair = pairs.find((pair) => pair.id === preferredPairId);
19080
+ if (preferredPair) {
19081
+ return preferredPair;
19082
+ }
19083
+ }
19084
+ return pairs.find((pair) => !isWhiteForegroundPair4(pair)) ?? pairs[0] ?? null;
19085
+ }
19086
+ function getDefaultBackgroundToken4(context) {
19087
+ for (const tone of PREFERRED_BACKGROUND_TONES4) {
19088
+ for (const group of context.backgroundGroups) {
19089
+ const match = group.backgrounds.find(
19090
+ (background) => background.tone === tone && (context.pairsByBackground[background.token]?.length ?? 0) > 0
19091
+ );
19092
+ if (match) {
19093
+ return match.token;
19094
+ }
19095
+ }
19096
+ }
19097
+ for (const tone of PREFERRED_BACKGROUND_TONES4) {
19098
+ for (const group of context.backgroundGroups) {
19099
+ const match = group.backgrounds.find((background) => background.tone === tone);
19100
+ if (match) {
19101
+ return match.token;
19102
+ }
19103
+ }
19104
+ }
19105
+ return context.backgrounds[0]?.token ?? "";
19106
+ }
19107
+ function resolveBackgroundToken4(context, preferredToken, preferredTone) {
19108
+ if (preferredToken && context.backgrounds.some((background) => background.token === preferredToken)) {
19109
+ return preferredToken;
19110
+ }
19111
+ if (preferredTone !== null && preferredTone !== void 0) {
19112
+ for (const group of context.backgroundGroups) {
19113
+ const match = group.backgrounds.find((background) => background.tone === preferredTone);
19114
+ if (match) {
19115
+ return match.token;
19116
+ }
19117
+ }
19118
+ }
19119
+ return getDefaultBackgroundToken4(context);
19120
+ }
19121
+ function getInitialPairingState4(searchParams) {
19122
+ const paletteParam = searchParams.get("palette");
19123
+ const primaryParam = searchParams.get("primary");
19124
+ const accentParam = searchParams.get("accent");
19125
+ const pairParam = searchParams.get("pair");
19126
+ const backgroundParam = searchParams.get("background");
19127
+ const themeCategory = paletteParam === "brand" || paletteParam === "aboriginal" ? paletteParam : "brand";
19128
+ const context = getPairingContext(themeCategory, primaryParam, accentParam);
19129
+ const shouldUseDefaultBrandExample = !backgroundParam && !pairParam && themeCategory === "brand" && context.primary.key === "blue" && context.accent.key === "red";
19130
+ const defaultBackgroundToken = shouldUseDefaultBrandExample ? context.backgrounds.some(
19131
+ (background) => background.token === DEFAULT_INITIAL_BACKGROUND_TOKEN4
19132
+ ) ? DEFAULT_INITIAL_BACKGROUND_TOKEN4 : null : null;
19133
+ const defaultPairId = shouldUseDefaultBrandExample && defaultBackgroundToken && context.pairsByBackground[defaultBackgroundToken]?.some(
19134
+ (pair) => pair.id === DEFAULT_INITIAL_PAIR_ID4
19135
+ ) ? DEFAULT_INITIAL_PAIR_ID4 : null;
19136
+ const pairBackgroundToken = context.recommendedPairs.find((pair) => pair.id === pairParam)?.background.token ?? null;
19137
+ const selectedBackgroundToken = resolveBackgroundToken4(
19138
+ context,
19139
+ backgroundParam ?? pairBackgroundToken ?? defaultBackgroundToken,
19140
+ getToneFromToken4(backgroundParam ?? pairBackgroundToken ?? defaultBackgroundToken)
19141
+ );
19142
+ const selectedPairId = getPreferredPairForBackground4(
19143
+ context.pairsByBackground[selectedBackgroundToken] ?? [],
19144
+ pairParam ?? defaultPairId
19145
+ )?.id ?? "";
19146
+ return {
19147
+ accentKey: context.accent.key,
19148
+ primaryKey: context.primary.key,
19149
+ selectedBackgroundToken,
19150
+ selectedPairId,
19151
+ themeCategory
19152
+ };
19153
+ }
19154
+ function getReadableTextColor3(tone) {
19155
+ return tone >= 600 ? "#ffffff" : "#002664";
19156
+ }
19157
+ function getBackgroundOptionName2(background) {
19158
+ return background.name ?? `${background.familyLabel} ${background.tone}`;
19159
+ }
19160
+ function getMainPairingLabel(pair, bestPair) {
19161
+ if (!pair) {
19162
+ return "No AAA pairing available";
19163
+ }
19164
+ if (pair.id === bestPair?.id) {
19165
+ return "NSW recommended pairing";
19166
+ }
19167
+ if (isWhiteForegroundPair4(pair) && isLargeTextOnlyPair2(pair)) {
19168
+ return "Large-text example only";
19169
+ }
19170
+ if (isWhiteForegroundPair4(pair) && !pair.passes.aaaLarge) {
19171
+ return "Example only";
19172
+ }
19173
+ return "Approved alternative";
19174
+ }
19175
+ function getMainPairingSupportCopy(pair, bestPair, context) {
19176
+ if (!pair) {
19177
+ return "No AAA-compliant foreground available for this background.";
19178
+ }
19179
+ if (pair.id === bestPair?.id) {
19180
+ return getBestRecommendationReason2(pair, context);
19181
+ }
19182
+ if (isWhiteForegroundPair4(pair) && isLargeTextOnlyPair2(pair)) {
19183
+ return "White works here for large text only. Use the NSW default for standard body copy and smaller interface text.";
19184
+ }
19185
+ if (isWhiteForegroundPair4(pair) && !pair.passes.aaaLarge) {
19186
+ return "White does not meet AAA on this background. Choose a different approved background or foreground.";
19187
+ }
19188
+ return "Approved AAA alternative when a different tone or emphasis is needed.";
19189
+ }
19190
+ function getBestRecommendationReason2(pair, context) {
19191
+ if (isWhiteForegroundPair4(pair)) {
19192
+ return "Recommended because this background is dark enough to support white text at AAA contrast.";
19193
+ }
19194
+ if (pair.foreground.familyKey === pair.background.familyKey) {
19195
+ return "Recommended because it stays within the same colour family while keeping clear AAA contrast.";
19196
+ }
19197
+ if (pair.foreground.familyKey === context.grey.key) {
19198
+ return "Recommended because grey provides a strong neutral contrast without competing with the background.";
19199
+ }
19200
+ if (pair.foreground.familyKey === context.accent.key) {
19201
+ return "Recommended because the selected accent family adds emphasis while preserving AAA contrast.";
19202
+ }
19203
+ if (pair.foreground.familyKey === context.primary.key) {
19204
+ return "Recommended because the selected primary family gives the clearest AAA-compliant contrast for this background.";
19205
+ }
19206
+ return "Recommended because it is the clearest NSW-approved AAA pairing for this background.";
19207
+ }
19208
+ function getRecommendationCategory2(pair, bestPair, context) {
19209
+ if (pair.id === bestPair?.id) return "best";
19210
+ if (isWhiteForegroundPair4(pair)) return "white";
19211
+ if (pair.foreground.familyKey === pair.background.familyKey) return "same-family";
19212
+ if (pair.foreground.familyKey === context.accent.key) return "accent-family";
19213
+ if (pair.foreground.familyKey === context.grey.key) return "grey-option";
19214
+ if (pair.foreground.familyKey === context.primary.key) return "primary-family";
19215
+ return "approved";
19216
+ }
19217
+ function getRecommendationSortRank2(category) {
19218
+ switch (category) {
19219
+ case "best":
19220
+ return 0;
19221
+ case "same-family":
19222
+ return 1;
19223
+ case "primary-family":
19224
+ return 2;
19225
+ case "accent-family":
19226
+ return 3;
19227
+ case "grey-option":
19228
+ return 4;
19229
+ case "white":
19230
+ return 5;
19231
+ default:
19232
+ return 6;
19233
+ }
19234
+ }
19235
+ function getRecommendationGroupId(category) {
19236
+ switch (category) {
19237
+ case "best":
19238
+ case "same-family":
19239
+ case "primary-family":
19240
+ return "closest";
19241
+ case "accent-family":
19242
+ case "white":
19243
+ case "approved":
19244
+ return "expressive";
19245
+ case "grey-option":
19246
+ return "neutral";
19247
+ default:
19248
+ return null;
19249
+ }
19250
+ }
19251
+ function buildRecommendationGroups(items) {
19252
+ const groupedItems = items.reduce(
19253
+ (accumulator, item) => {
19254
+ const groupId = getRecommendationGroupId(item.category);
19255
+ if (!groupId) {
19256
+ return accumulator;
19257
+ }
19258
+ accumulator[groupId].push(item);
19259
+ return accumulator;
19260
+ },
19261
+ {
19262
+ closest: [],
19263
+ expressive: [],
19264
+ neutral: []
19265
+ }
19266
+ );
19267
+ const groups = [
19268
+ {
19269
+ id: "closest",
19270
+ title: "Closest to default",
19271
+ description: "Stays closest to the NSW default in tone and overall treatment.",
19272
+ items: groupedItems.closest
19273
+ },
19274
+ {
19275
+ id: "expressive",
19276
+ title: "More expressive (accent)",
19277
+ description: "Brings more accent or emphasis into the foreground treatment.",
19278
+ items: groupedItems.expressive
19279
+ },
19280
+ {
19281
+ id: "neutral",
19282
+ title: "Neutral/system",
19283
+ description: "Uses quieter neutral contrast for system or utility treatments.",
19284
+ items: groupedItems.neutral
19285
+ }
19286
+ ];
19287
+ return groups.filter((group) => group.items.length > 0);
19288
+ }
19289
+ function getAvailabilityMeta2(isSelected, hasPairs) {
19290
+ if (isSelected && hasPairs) {
19291
+ return {
19292
+ description: "Selected background with AAA foreground options.",
19293
+ icon: Icons.check_circle,
19294
+ label: "Selected",
19295
+ tone: "selected"
19296
+ };
19297
+ }
19298
+ if (isSelected) {
19299
+ return {
19300
+ description: "Selected background with no AAA-compliant foreground available.",
19301
+ icon: Icons.warning,
19302
+ label: "Selected, no AAA",
19303
+ tone: "unavailable"
19304
+ };
19305
+ }
19306
+ if (hasPairs) {
19307
+ return {
19308
+ description: "AAA foreground options available.",
19309
+ icon: Icons.radio_button_checked,
19310
+ label: "Available",
19311
+ tone: "available"
19312
+ };
19313
+ }
19314
+ return {
19315
+ description: "No AAA-compliant foreground available for this background.",
19316
+ icon: Icons.close,
19317
+ label: "No AAA",
19318
+ tone: "unavailable"
19319
+ };
19320
+ }
19321
+ function getPairingCopyText2(pair) {
19322
+ const accessibilityLine = pair.passes.aaaText ? "Accessibility: Meets AAA for normal and large text" : pair.passes.aaaLarge ? "Accessibility: Meets AAA for large text only" : "Accessibility: Fails AAA for normal and large text";
19323
+ return [
19324
+ `Background: ${pair.background.token} (${pair.background.hex})`,
19325
+ `Foreground: ${pair.foreground.token} (${pair.foreground.hex})`,
19326
+ `Contrast ratio: ${pair.contrastRatio.toFixed(2)}:1`,
19327
+ accessibilityLine
19328
+ ].join("\n");
19329
+ }
19330
+ function getAccessibilitySummaryLine(pair) {
19331
+ if (!pair) {
19332
+ return "Contrast unavailable";
19333
+ }
19334
+ if (pair.passes.aaaText) {
19335
+ return `${pair.contrastRatio.toFixed(2)}:1 \u2014 AAA compliant (normal + large text)`;
19336
+ }
19337
+ if (pair.passes.aaaLarge) {
19338
+ return `${pair.contrastRatio.toFixed(2)}:1 \u2014 AAA compliant (large text only)`;
19339
+ }
19340
+ return `${pair.contrastRatio.toFixed(2)}:1 \u2014 Not AAA compliant`;
19341
+ }
19342
+ function getLiveAnnouncement2(pair, background) {
19343
+ if (!background) {
19344
+ return "No approved background tones available.";
19345
+ }
19346
+ if (!pair) {
19347
+ return `${background.token} selected. No AAA-compliant foreground options available for this selection.`;
19348
+ }
19349
+ if (isWhiteForegroundPair4(pair) && isLargeTextOnlyPair2(pair)) {
19350
+ return `${background.token} with white. Contrast ratio ${pair.contrastRatio.toFixed(2)} to 1. Meets AAA for large text only.`;
19351
+ }
19352
+ if (isWhiteForegroundPair4(pair) && !pair.passes.aaaLarge) {
19353
+ return `${background.token} with white. Contrast ratio ${pair.contrastRatio.toFixed(2)} to 1. Fails AAA for normal and large text.`;
19354
+ }
19355
+ return `${background.token} with ${pair.foreground.token}. Contrast ratio ${pair.contrastRatio.toFixed(2)} to 1. Meets AAA for normal and large text.`;
19356
+ }
19357
+ function formatValueRows2(color2, visibleFormats) {
19358
+ const hasDisplayTone = color2.token !== "white";
19359
+ return [
19360
+ { key: "token", label: "Token", value: color2.token, copyable: true },
19361
+ {
19362
+ key: "tone",
19363
+ label: "Tone",
19364
+ value: hasDisplayTone ? String(color2.tone) : "Not applicable",
19365
+ copyable: hasDisplayTone
19366
+ },
19367
+ ...visibleFormats.map((format) => ({
19368
+ key: format,
19369
+ label: format.toUpperCase(),
19370
+ value: color2[format],
19371
+ copyable: true
19372
+ }))
19373
+ ];
19374
+ }
19375
+ function SelectorButton2({
19376
+ description,
19377
+ isSelected,
19378
+ label,
19379
+ onClick,
19380
+ swatch
19381
+ }) {
19382
+ return /* @__PURE__ */ jsxRuntime.jsx(
19383
+ "button",
19384
+ {
19385
+ type: "button",
19386
+ "aria-pressed": isSelected,
19387
+ onClick,
19388
+ className: cn(
19389
+ "w-full rounded-sm border px-3 py-3 text-left transition-colors focus-visible:ring-2 focus-visible:ring-primary-700 focus-visible:ring-offset-2 focus-visible:outline-hidden",
19390
+ isSelected ? "border-primary-800 bg-primary-50" : "border-grey-300 bg-white hover:border-primary-500 hover:bg-grey-50"
19391
+ ),
19392
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-3", children: [
19393
+ swatch ? /* @__PURE__ */ jsxRuntime.jsx(
19394
+ "span",
19395
+ {
19396
+ "aria-hidden": "true",
19397
+ className: "mt-0.5 size-4 shrink-0 rounded-full border border-black/10",
19398
+ style: { backgroundColor: swatch }
19399
+ }
19400
+ ) : null,
19401
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 space-y-1", children: [
19402
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-semibold text-foreground", children: label }),
19403
+ description ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs/5 text-muted-foreground", children: description }) : null
19404
+ ] })
19405
+ ] })
19406
+ }
19407
+ );
19408
+ }
19409
+ function BackgroundSwatchButton2({
19410
+ background,
19411
+ hasPairs,
19412
+ isSelected,
19413
+ onClick
19414
+ }) {
19415
+ const status = getAvailabilityMeta2(isSelected, hasPairs);
19416
+ const StatusIcon = status.icon;
19417
+ return /* @__PURE__ */ jsxRuntime.jsxs(
19418
+ "button",
19419
+ {
19420
+ type: "button",
19421
+ "aria-pressed": isSelected,
19422
+ "aria-label": `Select ${background.token} background. ${status.description}`,
19423
+ onClick,
19424
+ className: cn(
19425
+ "w-full rounded-sm border bg-white p-3 text-left transition-colors focus-visible:ring-2 focus-visible:ring-primary-700 focus-visible:ring-offset-2 focus-visible:outline-hidden",
19426
+ isSelected ? "border-primary-800 bg-primary-50" : hasPairs ? "border-grey-300 hover:border-primary-500 hover:bg-grey-50" : "border-grey-300 hover:border-grey-500"
19427
+ ),
19428
+ children: [
19429
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-3", children: [
19430
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0", children: [
19431
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-semibold text-foreground", children: getBackgroundOptionName2(background) }),
19432
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 font-mono text-[0.72rem] break-all text-muted-foreground", children: background.token })
19433
+ ] }),
19434
+ /* @__PURE__ */ jsxRuntime.jsxs(
19435
+ "span",
19436
+ {
19437
+ className: cn(
19438
+ "inline-flex items-center gap-1 rounded-sm border px-2 py-1 text-[0.68rem] font-semibold uppercase",
19439
+ status.tone === "selected" && "border-primary-800 bg-primary-800 text-white dark:border-primary-500 dark:bg-primary-500",
19440
+ status.tone === "available" && "border-grey-300 bg-grey-50 text-foreground",
19441
+ status.tone === "unavailable" && "border-danger-300 bg-danger-50 text-danger-900 dark:border-danger-800 dark:bg-danger-950/20 dark:text-danger-200"
19442
+ ),
19443
+ children: [
19444
+ /* @__PURE__ */ jsxRuntime.jsx(StatusIcon, { "data-slot": "icon", className: "size-4" }),
19445
+ status.label
19446
+ ]
19447
+ }
19448
+ )
19449
+ ] }),
19450
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-3 overflow-hidden rounded-[2px] border border-black/10", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-14 w-full", style: { backgroundColor: background.hex } }) }),
19451
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-3 text-xs/5 text-muted-foreground", children: status.description })
19452
+ ]
19453
+ }
19454
+ );
19455
+ }
19456
+ function CurrentResultCard2({
19457
+ bestPair,
19458
+ copiedKey,
19459
+ onCopyPairing,
19460
+ pair,
19461
+ supportText,
19462
+ selectedBackground
19463
+ }) {
19464
+ const previewForeground = pair?.foreground.hex ?? getReadableTextColor3(selectedBackground.tone);
19465
+ const isRecommended = pair ? pair.id === bestPair?.id : false;
19466
+ const pairingLabel = getMainPairingLabel(pair, bestPair);
19467
+ const accessibilitySummary = getAccessibilitySummaryLine(pair);
19468
+ const fauxButtonStyle = pair ? {
19469
+ "--btn-bg": pair.foreground.hex,
19470
+ "--btn-border": pair.foreground.hex,
19471
+ "--btn-text": pair.background.hex,
19472
+ "--btn-icon": pair.background.hex,
19473
+ "--btn-hover-overlay": pair.background.hex
19474
+ } : null;
19475
+ return /* @__PURE__ */ jsxRuntime.jsx(Card, { className: cn("gap-0 overflow-hidden py-0", isRecommended && "border-primary-200"), children: /* @__PURE__ */ jsxRuntime.jsx(
19476
+ "div",
19477
+ {
19478
+ className: "p-4 sm:min-h-[24rem] sm:p-8",
19479
+ style: {
19480
+ backgroundColor: selectedBackground.hex,
19481
+ color: previewForeground
19482
+ },
19483
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-[18rem] flex-col justify-between gap-8 sm:min-h-[20rem]", children: [
19484
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-w-3xl rounded-sm bg-white px-4 py-4 text-foreground sm:px-6 sm:py-6", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-5 lg:flex-row lg:items-start lg:justify-between", children: [
19485
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 space-y-4", children: [
19486
+ /* @__PURE__ */ jsxRuntime.jsxs(
19487
+ "span",
19488
+ {
19489
+ className: cn(
19490
+ "inline-flex items-center gap-2 rounded-sm px-3 py-1 text-[0.72rem] font-semibold tracking-[0.12em] uppercase",
19491
+ isRecommended ? "bg-primary-800 text-white" : "border border-grey-300 bg-grey-50 text-foreground"
19492
+ ),
19493
+ children: [
19494
+ isRecommended ? /* @__PURE__ */ jsxRuntime.jsx(Icons.check_circle, { "data-slot": "icon", className: "size-4" }) : /* @__PURE__ */ jsxRuntime.jsx(Icons.info, { "data-slot": "icon", className: "size-4" }),
19495
+ pairingLabel
19496
+ ]
19497
+ }
19498
+ ),
19499
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
19500
+ /* @__PURE__ */ jsxRuntime.jsx(Heading, { level: 2, size: 4, className: "text-foreground", trim: "normal", children: pair ? `${getPairingColorDisplayName4(pair.foreground)} on ${getPairingColorDisplayName4(pair.background)}` : `${getPairingColorDisplayName4(selectedBackground)} selected` }),
19501
+ pair ? /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "font-mono text-[0.74rem] break-all text-muted-foreground", children: [
19502
+ pair.foreground.token,
19503
+ " on ",
19504
+ pair.background.token
19505
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-mono text-[0.74rem] break-all text-muted-foreground", children: selectedBackground.token }),
19506
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-semibold text-foreground", children: accessibilitySummary }),
19507
+ /* @__PURE__ */ jsxRuntime.jsx(Text, { size: 2, className: "max-w-3xl text-grey-700", children: supportText })
19508
+ ] })
19509
+ ] }),
19510
+ pair ? /* @__PURE__ */ jsxRuntime.jsx(
19511
+ Button2,
19512
+ {
19513
+ color: "primary",
19514
+ className: "w-full justify-center lg:w-auto lg:min-w-48",
19515
+ onClick: onCopyPairing,
19516
+ children: copiedKey === "current-pairing" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
19517
+ /* @__PURE__ */ jsxRuntime.jsx(Icons.check, { "data-slot": "icon", className: "size-5" }),
19518
+ "Pairing copied"
19519
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
19520
+ /* @__PURE__ */ jsxRuntime.jsx(Icons.content_copy, { "data-slot": "icon", className: "size-5" }),
19521
+ "Copy pairing"
19522
+ ] })
19523
+ }
19524
+ ) : null
19525
+ ] }) }),
19526
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "max-w-xl space-y-4", children: [
19527
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
19528
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "max-w-lg text-3xl leading-tight font-bold text-current sm:text-4xl", children: "Example heading" }),
19529
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "max-w-md text-base/7", children: pair ? getPreviewGuidance4(pair, isRecommended) : "This approved background does not currently have an AAA foreground in this tool. Choose another approved background to continue." })
19530
+ ] }),
19531
+ pair && fauxButtonStyle ? /* @__PURE__ */ jsxRuntime.jsx(
19532
+ "span",
19533
+ {
19534
+ "aria-hidden": "true",
19535
+ "data-variant": "solid",
19536
+ className: cn(
19537
+ buttonVariants({ variant: "solid", size: "default" }),
19538
+ "pointer-events-none cursor-default self-start px-6 text-[16px] font-[700] select-none sm:px-6 sm:text-[16px] sm:font-[700]"
19539
+ ),
19540
+ style: fauxButtonStyle,
19541
+ children: "Primary action"
19542
+ }
19543
+ ) : null
19544
+ ] })
19545
+ ] })
19546
+ }
19547
+ ) });
19548
+ }
19549
+ function RecommendationCard2({
19550
+ copiedKey,
19551
+ isSelected,
19552
+ item,
19553
+ onCopyPairing,
19554
+ onSelect
19555
+ }) {
19556
+ const { pair } = item;
19557
+ return /* @__PURE__ */ jsxRuntime.jsxs(
19558
+ "div",
19559
+ {
19560
+ className: cn(
19561
+ "relative rounded-sm border bg-white px-3 py-2.5 text-left transition-colors focus-visible:ring-2 focus-visible:ring-primary-700 focus-visible:ring-offset-2 focus-visible:outline-hidden",
19562
+ isSelected ? "border-primary-800 bg-primary-50/40" : "border-grey-200 hover:border-grey-400"
19563
+ ),
19564
+ children: [
19565
+ /* @__PURE__ */ jsxRuntime.jsx(
19566
+ "button",
19567
+ {
19568
+ type: "button",
19569
+ "aria-pressed": isSelected,
19570
+ "aria-label": `Use ${pair.foreground.token} on ${pair.background.token}`,
19571
+ onClick: onSelect,
19572
+ className: "absolute inset-0 rounded-sm focus-visible:ring-2 focus-visible:ring-primary-700 focus-visible:ring-offset-2 focus-visible:outline-hidden"
19573
+ }
19574
+ ),
19575
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pointer-events-none relative z-10 flex items-start gap-3", children: [
19576
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-0.5 w-12 shrink-0 overflow-hidden rounded-[2px] border border-black/10", children: [
19577
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-9 w-full", style: { backgroundColor: pair.background.hex } }),
19578
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-2.5 w-full", style: { backgroundColor: pair.foreground.hex } })
19579
+ ] }),
19580
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1 space-y-1", children: [
19581
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
19582
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm font-semibold text-foreground", children: [
19583
+ getPairingColorDisplayName4(pair.foreground),
19584
+ " on",
19585
+ " ",
19586
+ getPairingColorDisplayName4(pair.background)
19587
+ ] }),
19588
+ item.category === "best" ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-flex rounded-sm bg-primary-800 px-2 py-1 text-[0.68rem] font-semibold tracking-[0.12em] text-white uppercase", children: "Default" }) : null,
19589
+ isSelected ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-flex rounded-sm border border-primary-800 px-2 py-1 text-[0.68rem] font-semibold tracking-[0.12em] text-primary-800 uppercase", children: "Current" }) : null
19590
+ ] }),
19591
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "font-mono text-[0.72rem] break-all text-muted-foreground", children: [
19592
+ pair.foreground.token,
19593
+ " on ",
19594
+ pair.background.token
19595
+ ] }),
19596
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm text-muted-foreground", children: [
19597
+ pair.contrastRatio.toFixed(2),
19598
+ ":1 contrast"
19599
+ ] })
19600
+ ] }),
19601
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pointer-events-auto shrink-0 self-start", children: /* @__PURE__ */ jsxRuntime.jsx(
19602
+ Button2,
19603
+ {
19604
+ variant: "ghost",
19605
+ color: "grey",
19606
+ size: "icon",
19607
+ className: "size-8 text-muted-foreground hover:text-foreground",
19608
+ onClick: onCopyPairing,
19609
+ "aria-label": `Copy pairing ${pair.background.token} and ${pair.foreground.token}`,
19610
+ title: copiedKey === `pair:${pair.id}` ? "Copied" : "Copy pairing",
19611
+ children: copiedKey === `pair:${pair.id}` ? /* @__PURE__ */ jsxRuntime.jsx(Icons.check, { "data-slot": "icon", className: "size-4" }) : /* @__PURE__ */ jsxRuntime.jsx(Icons.content_copy, { "data-slot": "icon", className: "size-4" })
19612
+ }
19613
+ ) })
19614
+ ] })
19615
+ ]
19616
+ }
19617
+ );
19618
+ }
19619
+ function RecommendationGroupSection({
19620
+ children,
19621
+ description,
19622
+ title
19623
+ }) {
19624
+ return /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "space-y-2", children: [
19625
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", children: [
19626
+ /* @__PURE__ */ jsxRuntime.jsx(Heading, { level: 3, size: 6, className: "text-foreground", trim: "normal", children: title }),
19627
+ /* @__PURE__ */ jsxRuntime.jsx(Text, { size: 2, className: "text-muted-foreground", children: description })
19628
+ ] }),
19629
+ children
19630
+ ] });
19631
+ }
19632
+ function TechnicalDetailsPanel2({
19633
+ color: color2,
19634
+ copiedKey,
19635
+ onCopyValue,
19636
+ title,
19637
+ visibleFormats
19638
+ }) {
19639
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-sm border border-grey-200 bg-white", children: [
19640
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-b border-grey-200 px-4 py-4 sm:px-5", children: [
19641
+ /* @__PURE__ */ jsxRuntime.jsx(Heading, { level: 3, size: 6, className: "text-foreground", trim: "normal", children: title }),
19642
+ color2 ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
19643
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 text-sm text-muted-foreground", children: getPairingColorDisplayName4(color2) }),
19644
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-4 overflow-hidden rounded-sm border border-black/10 bg-white", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-20 w-full sm:h-24", style: { backgroundColor: color2.hex } }) })
19645
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 text-sm text-muted-foreground", children: "No AAA foreground available." })
19646
+ ] }),
19647
+ color2 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "divide-y divide-grey-200", children: formatValueRows2(color2, visibleFormats).map((row) => {
19648
+ const copyKey = `${title}-${row.key}`;
19649
+ return /* @__PURE__ */ jsxRuntime.jsxs(
19650
+ "div",
19651
+ {
19652
+ className: "flex flex-col items-start gap-3 px-4 py-4 sm:flex-row sm:items-start sm:justify-between sm:gap-4 sm:px-5",
19653
+ children: [
19654
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
19655
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[0.72rem] font-semibold tracking-[0.12em] text-muted-foreground uppercase", children: row.label }),
19656
+ /* @__PURE__ */ jsxRuntime.jsx(
19657
+ "p",
19658
+ {
19659
+ className: cn(
19660
+ "mt-2 text-sm text-foreground",
19661
+ row.key !== "tone" && "font-mono break-all"
19662
+ ),
19663
+ children: row.value
19664
+ }
19665
+ )
19666
+ ] }),
19667
+ row.copyable ? /* @__PURE__ */ jsxRuntime.jsx(
19668
+ Button2,
19669
+ {
19670
+ variant: "ghost",
19671
+ color: "grey",
19672
+ size: "sm",
19673
+ className: "self-start",
19674
+ onClick: () => onCopyValue(copyKey, row.value, `${row.label} copied`),
19675
+ "aria-label": `Copy ${title.toLowerCase()} ${row.label.toLowerCase()}`,
19676
+ children: copiedKey === copyKey ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
19677
+ /* @__PURE__ */ jsxRuntime.jsx(Icons.check_circle, { "data-slot": "icon", className: "size-5" }),
19678
+ "Copied"
19679
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
19680
+ /* @__PURE__ */ jsxRuntime.jsx(Icons.content_copy, { "data-slot": "icon", className: "size-5" }),
19681
+ "Copy"
19682
+ ] })
19683
+ }
19684
+ ) : null
19685
+ ]
19686
+ },
19687
+ row.key
19688
+ );
19689
+ }) }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-5 py-5", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: "No technical values are shown because there is no AAA-compliant foreground for this background." }) })
19690
+ ] });
19691
+ }
19692
+ function resolveSelectionState2(nextThemeCategory, nextPrimaryKey, nextAccentKey, preferredBackgroundToken, preferredPairId) {
19693
+ const context = getPairingContext(nextThemeCategory, nextPrimaryKey, nextAccentKey);
19694
+ const selectedBackgroundToken = resolveBackgroundToken4(
19695
+ context,
19696
+ preferredBackgroundToken,
19697
+ getToneFromToken4(preferredBackgroundToken)
19698
+ );
19699
+ const selectedPairId = getPreferredPairForBackground4(
19700
+ context.pairsByBackground[selectedBackgroundToken] ?? [],
19701
+ preferredPairId
19702
+ )?.id ?? "";
19703
+ return {
19704
+ accentKey: context.accent.key,
19705
+ context,
19706
+ primaryKey: context.primary.key,
19707
+ selectedBackgroundToken,
19708
+ selectedPairId,
19709
+ themeCategory: nextThemeCategory
19710
+ };
19711
+ }
19712
+ function ColorPairingToolV4Loading() {
19713
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-8 xl:grid-cols-[minmax(18rem,24rem)_minmax(0,1fr)]", children: [
19714
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-6", children: [
19715
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-52 rounded-sm border border-grey-200 bg-grey-50" }),
19716
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[32rem] rounded-sm border border-grey-200 bg-grey-50" })
19717
+ ] }),
19718
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-6", children: [
19719
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-96 rounded-sm border border-grey-200 bg-grey-50" }),
19720
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-64 rounded-sm border border-grey-200 bg-grey-50" }),
19721
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-72 rounded-sm border border-grey-200 bg-grey-50" })
19722
+ ] })
19723
+ ] });
19724
+ }
19725
+ function ColorPairingToolV4Content({
19726
+ onAnalyticsEvent,
19727
+ visibleFormats
19728
+ }) {
19729
+ const searchParams = navigation.useSearchParams();
19730
+ const stickyOffset = useStickyOffset(24);
19731
+ const [initialState] = React5.useState(() => getInitialPairingState4(searchParams));
19732
+ const [themeCategory, setThemeCategory] = React5.useState(initialState.themeCategory);
19733
+ const [primaryFamilyKey, setPrimaryFamilyKey] = React5.useState(initialState.primaryKey);
19734
+ const [accentFamilyKey, setAccentFamilyKey] = React5.useState(initialState.accentKey);
19735
+ const [selectedBackgroundToken, setSelectedBackgroundToken] = React5.useState(
19736
+ initialState.selectedBackgroundToken
19737
+ );
19738
+ const [selectedPairId, setSelectedPairId] = React5.useState(initialState.selectedPairId);
19739
+ const [drawerStepIndex, setDrawerStepIndex] = React5.useState(0);
19740
+ const [isCompactControlsOpen, setIsCompactControlsOpen] = React5.useState(false);
19741
+ const [isShowingAllAlternatives, setIsShowingAllAlternatives] = React5.useState(false);
19742
+ const [isTechnicalDetailsOpen, setIsTechnicalDetailsOpen] = React5.useState(false);
19743
+ const [, copyToClipboardRaw] = usehooks.useCopyToClipboard();
19744
+ const [copiedKey, setCopiedKey] = React5.useState(null);
19745
+ const copiedKeyTimeoutRef = React5.useRef(null);
19746
+ const resultSectionRef = React5.useRef(null);
19747
+ const noValidStateRef = React5.useRef(/* @__PURE__ */ new Set());
19748
+ const technicalDetailsViewedRef = React5.useRef(false);
19749
+ const emitAnalyticsEvent = React5.useCallback(
19750
+ (event) => {
19751
+ onAnalyticsEvent(event);
19752
+ if (typeof window !== "undefined") {
19753
+ window.dispatchEvent(
19754
+ new CustomEvent(COLOR_PAIRING_TOOL_V4_ANALYTICS_EVENT, {
19755
+ detail: event
19756
+ })
19757
+ );
19758
+ }
19759
+ },
19760
+ [onAnalyticsEvent]
19761
+ );
19762
+ React5.useEffect(() => {
19763
+ return () => {
19764
+ if (copiedKeyTimeoutRef.current) {
19765
+ clearTimeout(copiedKeyTimeoutRef.current);
19766
+ }
19767
+ };
19768
+ }, []);
19769
+ React5.useEffect(() => {
19770
+ if (typeof window === "undefined") {
19771
+ return;
19772
+ }
19773
+ const mediaQuery = window.matchMedia(PERSISTENT_DRAWER_MIN_WIDTH_QUERY2);
19774
+ const handleChange = (event) => {
19775
+ if (event?.matches ?? mediaQuery.matches) {
19776
+ setIsCompactControlsOpen(false);
19777
+ }
19778
+ };
19779
+ handleChange();
19780
+ mediaQuery.addEventListener("change", handleChange);
19781
+ return () => {
19782
+ mediaQuery.removeEventListener("change", handleChange);
19783
+ };
19784
+ }, []);
19785
+ const themeFamilies = React5.useMemo(() => getPairingFamilies(themeCategory), [themeCategory]);
19786
+ const context = React5.useMemo(
19787
+ () => getPairingContext(themeCategory, primaryFamilyKey, accentFamilyKey),
19788
+ [themeCategory, primaryFamilyKey, accentFamilyKey]
19789
+ );
19790
+ const selectableFamilies = React5.useMemo(
19791
+ () => themeFamilies.filter((family) => family.key !== context.grey.key),
19792
+ [themeFamilies, context.grey.key]
19793
+ );
19794
+ const selectableAccentFamilies = React5.useMemo(
19795
+ () => selectableFamilies.filter((family) => family.key !== context.primary.key),
19796
+ [selectableFamilies, context.primary.key]
19797
+ );
19798
+ const selectedBackground = React5.useMemo(
19799
+ () => context.backgrounds.find((background) => background.token === selectedBackgroundToken) ?? context.backgrounds[0] ?? null,
19800
+ [context.backgrounds, selectedBackgroundToken]
19801
+ );
19802
+ const selectedBackgroundPairs = React5.useMemo(
19803
+ () => selectedBackground ? context.pairsByBackground[selectedBackground.token] ?? [] : [],
19804
+ [context.pairsByBackground, selectedBackground]
19805
+ );
19806
+ const bestRecommendedPair = React5.useMemo(
19807
+ () => getPreferredPairForBackground4(selectedBackgroundPairs),
19808
+ [selectedBackgroundPairs]
19809
+ );
19810
+ const selectedPair = React5.useMemo(
19811
+ () => getPreferredPairForBackground4(selectedBackgroundPairs, selectedPairId),
19812
+ [selectedBackgroundPairs, selectedPairId]
19813
+ );
19814
+ const whiteForegroundExample = React5.useMemo(
19815
+ () => selectedBackground && supportsWhiteForegroundPreview(selectedBackground) ? getWhiteForegroundPair(selectedBackground) : null,
19816
+ [selectedBackground]
19817
+ );
19818
+ const previewPair = selectedPair ?? whiteForegroundExample ?? null;
19819
+ const detailForeground = selectedPair?.foreground ?? whiteForegroundExample?.foreground ?? null;
19820
+ const currentPairSupportText = React5.useMemo(
19821
+ () => getMainPairingSupportCopy(previewPair, bestRecommendedPair, context),
19822
+ [bestRecommendedPair, context, previewPair]
19823
+ );
19824
+ const recommendationItems = React5.useMemo(() => {
19825
+ return selectedBackgroundPairs.map((pair) => {
19826
+ const category = getRecommendationCategory2(pair, bestRecommendedPair, context);
19827
+ return {
19828
+ category,
19829
+ pair
19830
+ };
19831
+ }).sort(
19832
+ (left, right) => getRecommendationSortRank2(left.category) - getRecommendationSortRank2(right.category)
19833
+ );
19834
+ }, [bestRecommendedPair, context, selectedBackgroundPairs]);
19835
+ const alternativeRecommendationGroups = React5.useMemo(
19836
+ () => buildRecommendationGroups(
19837
+ recommendationItems.filter((item) => item.pair.id !== selectedPair?.id)
19838
+ ),
19839
+ [recommendationItems, selectedPair?.id]
19840
+ );
19841
+ const visibleAlternativeRecommendationGroups = React5.useMemo(() => {
19842
+ if (isShowingAllAlternatives) {
19843
+ return alternativeRecommendationGroups;
19844
+ }
19845
+ let remaining = INITIAL_VISIBLE_ALTERNATIVES;
19846
+ return alternativeRecommendationGroups.reduce((groups, group) => {
19847
+ if (remaining <= 0) {
19848
+ return groups;
19849
+ }
19850
+ const items = group.items.slice(0, remaining);
19851
+ if (items.length > 0) {
19852
+ groups.push({
19853
+ ...group,
19854
+ items
19855
+ });
19856
+ remaining -= items.length;
19857
+ }
19858
+ return groups;
19859
+ }, []);
19860
+ }, [alternativeRecommendationGroups, isShowingAllAlternatives]);
19861
+ const hiddenAlternativeCount = React5.useMemo(() => {
19862
+ const totalAlternatives = alternativeRecommendationGroups.reduce(
19863
+ (count, group) => count + group.items.length,
19864
+ 0
19865
+ );
19866
+ const visibleAlternatives = visibleAlternativeRecommendationGroups.reduce(
19867
+ (count, group) => count + group.items.length,
19868
+ 0
19869
+ );
19870
+ return Math.max(0, totalAlternatives - visibleAlternatives);
19871
+ }, [alternativeRecommendationGroups, visibleAlternativeRecommendationGroups]);
19872
+ const liveAnnouncement = React5.useMemo(
19873
+ () => getLiveAnnouncement2(previewPair, selectedBackground),
19874
+ [previewPair, selectedBackground]
19875
+ );
19876
+ const shareBackgroundToken = selectedBackground?.token ?? selectedBackgroundToken;
19877
+ const shareUrl = React5.useMemo(() => {
19878
+ const params = new URLSearchParams();
19879
+ params.set("palette", themeCategory);
19880
+ params.set("primary", context.primary.key);
19881
+ params.set("accent", context.accent.key);
19882
+ if (shareBackgroundToken) {
19883
+ params.set("background", shareBackgroundToken);
19884
+ }
19885
+ if (selectedPairId) {
19886
+ params.set("pair", selectedPairId);
19887
+ }
19888
+ const query = params.toString();
19889
+ return `${COLOR_PAIRING_TOOL_V4_PATH}${query ? `?${query}` : ""}`;
19890
+ }, [context.accent.key, context.primary.key, shareBackgroundToken, selectedPairId, themeCategory]);
19891
+ const activeDrawerStep = COLOR_PAIRING_TOOL_DRAWER_STEPS2[drawerStepIndex];
19892
+ const isFirstDrawerStep = drawerStepIndex === 0;
19893
+ const isLastDrawerStep = drawerStepIndex === COLOR_PAIRING_TOOL_DRAWER_STEPS2.length - 1;
19894
+ const desktopSidebarStyle = React5.useMemo(
19895
+ () => ({
19896
+ "--tool-sidebar-max-height": `calc(100vh - ${stickyOffset}px)`,
19897
+ "--tool-sidebar-top": `${stickyOffset}px`
19898
+ }),
19899
+ [stickyOffset]
19900
+ );
19901
+ const goToDrawerStep = React5.useCallback((stepIndex) => {
19902
+ setDrawerStepIndex(Math.max(0, Math.min(stepIndex, COLOR_PAIRING_TOOL_DRAWER_STEPS2.length - 1)));
19903
+ }, []);
19904
+ const goToNextDrawerStep = React5.useCallback(() => {
19905
+ setDrawerStepIndex(
19906
+ (currentStep) => Math.min(currentStep + 1, COLOR_PAIRING_TOOL_DRAWER_STEPS2.length - 1)
19907
+ );
19908
+ }, []);
19909
+ const goToPreviousDrawerStep = React5.useCallback(() => {
19910
+ setDrawerStepIndex((currentStep) => Math.max(currentStep - 1, 0));
19911
+ }, []);
19912
+ const updateUrlParams = React5.useCallback((nextState) => {
19913
+ const params = new URLSearchParams(window.location.search);
19914
+ params.delete("family");
19915
+ params.set("palette", nextState.themeCategory);
19916
+ params.set("primary", nextState.primaryKey);
19917
+ params.set("accent", nextState.accentKey);
19918
+ params.set("background", nextState.selectedBackgroundToken);
19919
+ if (nextState.selectedPairId) {
19920
+ params.set("pair", nextState.selectedPairId);
19921
+ } else {
19922
+ params.delete("pair");
19923
+ }
19924
+ window.history.replaceState(
19925
+ null,
19926
+ "",
19927
+ `${window.location.pathname}?${params.toString()}${window.location.hash}`
19928
+ );
19929
+ }, []);
19930
+ const applyResolvedSelection = React5.useCallback(
19931
+ (nextState) => {
19932
+ setThemeCategory(nextState.themeCategory);
19933
+ setPrimaryFamilyKey(nextState.primaryKey);
19934
+ setAccentFamilyKey(nextState.accentKey);
19935
+ setSelectedBackgroundToken(nextState.selectedBackgroundToken);
19936
+ setSelectedPairId(nextState.selectedPairId);
19937
+ updateUrlParams(nextState);
19938
+ },
19939
+ [updateUrlParams]
19940
+ );
19941
+ const buildAnalyticsContext = React5.useCallback(
19942
+ (overrides) => ({
19943
+ accentKey: context.accent.key,
19944
+ backgroundToken: selectedBackground?.token,
19945
+ foregroundToken: selectedPair?.foreground.token,
19946
+ pairId: selectedPair?.id,
19947
+ palette: themeCategory,
19948
+ primaryKey: context.primary.key,
19949
+ ...overrides
19950
+ }),
19951
+ [context.accent.key, context.primary.key, selectedBackground, selectedPair, themeCategory]
19952
+ );
19953
+ const copyValue = React5.useCallback(
19954
+ (copyKey, value, toastLabel, analyticsEvent) => {
19955
+ copyToClipboardRaw(value);
19956
+ setCopiedKey(copyKey);
19957
+ sonner.toast(toastLabel, { duration: 2e3 });
19958
+ if (analyticsEvent) {
19959
+ emitAnalyticsEvent(analyticsEvent);
19960
+ }
19961
+ if (copiedKeyTimeoutRef.current) {
19962
+ clearTimeout(copiedKeyTimeoutRef.current);
19963
+ }
19964
+ copiedKeyTimeoutRef.current = setTimeout(() => {
19965
+ setCopiedKey(null);
19966
+ copiedKeyTimeoutRef.current = null;
19967
+ }, 2e3);
19968
+ },
19969
+ [copyToClipboardRaw, emitAnalyticsEvent]
19970
+ );
19971
+ const handleThemeCategoryChange = (nextThemeCategory) => {
19972
+ const nextState = resolveSelectionState2(
19973
+ nextThemeCategory,
19974
+ primaryFamilyKey,
19975
+ accentFamilyKey,
19976
+ selectedBackgroundToken,
19977
+ null
19978
+ );
19979
+ setIsShowingAllAlternatives(false);
19980
+ applyResolvedSelection(nextState);
19981
+ emitAnalyticsEvent({
19982
+ name: "palette_change",
19983
+ accentKey: nextState.accentKey,
19984
+ backgroundToken: nextState.selectedBackgroundToken,
19985
+ pairId: nextState.selectedPairId,
19986
+ palette: nextState.themeCategory,
19987
+ primaryKey: nextState.primaryKey
19988
+ });
19989
+ };
19990
+ const handlePrimaryColorChange = (nextPrimaryKey) => {
19991
+ const nextAccentKey = nextPrimaryKey === accentFamilyKey ? getDefaultAccentFamilyKey(themeCategory, nextPrimaryKey) : accentFamilyKey;
19992
+ const nextState = resolveSelectionState2(
19993
+ themeCategory,
19994
+ nextPrimaryKey,
19995
+ nextAccentKey,
19996
+ selectedBackgroundToken,
19997
+ null
19998
+ );
19999
+ setIsShowingAllAlternatives(false);
20000
+ applyResolvedSelection(nextState);
20001
+ emitAnalyticsEvent({
20002
+ name: "primary_change",
20003
+ accentKey: nextState.accentKey,
20004
+ backgroundToken: nextState.selectedBackgroundToken,
20005
+ pairId: nextState.selectedPairId,
20006
+ palette: nextState.themeCategory,
20007
+ primaryKey: nextState.primaryKey
20008
+ });
20009
+ };
20010
+ const handleAccentColorChange = (nextAccentKey) => {
20011
+ if (nextAccentKey === primaryFamilyKey) return;
20012
+ const nextState = resolveSelectionState2(
20013
+ themeCategory,
20014
+ primaryFamilyKey,
20015
+ nextAccentKey,
20016
+ selectedBackgroundToken,
20017
+ null
20018
+ );
20019
+ setIsShowingAllAlternatives(false);
20020
+ applyResolvedSelection(nextState);
20021
+ emitAnalyticsEvent({
20022
+ name: "accent_change",
20023
+ accentKey: nextState.accentKey,
20024
+ backgroundToken: nextState.selectedBackgroundToken,
20025
+ pairId: nextState.selectedPairId,
20026
+ palette: nextState.themeCategory,
20027
+ primaryKey: nextState.primaryKey
20028
+ });
20029
+ };
20030
+ const handleBackgroundChange = (nextSelectedBackgroundToken) => {
20031
+ const nextSelectedPairId = getPreferredPairForBackground4(context.pairsByBackground[nextSelectedBackgroundToken] ?? [])?.id ?? "";
20032
+ const nextState = {
20033
+ accentKey: context.accent.key,
20034
+ context,
20035
+ primaryKey: context.primary.key,
20036
+ selectedBackgroundToken: nextSelectedBackgroundToken,
20037
+ selectedPairId: nextSelectedPairId,
20038
+ themeCategory
20039
+ };
20040
+ setIsShowingAllAlternatives(false);
20041
+ applyResolvedSelection(nextState);
20042
+ emitAnalyticsEvent({
20043
+ name: "background_selection",
20044
+ ...buildAnalyticsContext({
20045
+ backgroundToken: nextSelectedBackgroundToken,
20046
+ foregroundToken: context.pairsByBackground[nextSelectedBackgroundToken]?.find(
20047
+ (pair) => pair.id === nextSelectedPairId
20048
+ )?.foreground.token,
20049
+ pairId: nextSelectedPairId
20050
+ })
20051
+ });
20052
+ if (typeof window !== "undefined" && window.matchMedia(MOBILE_RESULT_SCROLL_QUERY2).matches) {
20053
+ requestAnimationFrame(() => {
20054
+ resultSectionRef.current?.scrollIntoView({ behavior: "smooth", block: "start" });
20055
+ });
20056
+ }
20057
+ };
20058
+ const handlePairChange = (nextSelectedPairId, source) => {
20059
+ if (!selectedBackground) return;
20060
+ const nextPair = selectedBackgroundPairs.find((pair) => pair.id === nextSelectedPairId) ?? null;
20061
+ setSelectedPairId(nextSelectedPairId);
20062
+ updateUrlParams({
20063
+ accentKey: context.accent.key,
20064
+ context,
20065
+ primaryKey: context.primary.key,
20066
+ selectedBackgroundToken: selectedBackground.token,
20067
+ selectedPairId: nextSelectedPairId,
20068
+ themeCategory
20069
+ });
20070
+ emitAnalyticsEvent({
20071
+ name: "foreground_selection",
20072
+ ...buildAnalyticsContext({
20073
+ foregroundToken: nextPair?.foreground.token,
20074
+ pairId: nextSelectedPairId,
20075
+ source
20076
+ })
20077
+ });
20078
+ if (bestRecommendedPair && nextSelectedPairId !== bestRecommendedPair.id) {
20079
+ emitAnalyticsEvent({
20080
+ name: "alternative_combination_selected",
20081
+ ...buildAnalyticsContext({
20082
+ foregroundToken: nextPair?.foreground.token,
20083
+ pairId: nextSelectedPairId,
20084
+ source
20085
+ })
20086
+ });
20087
+ }
20088
+ };
20089
+ React5.useEffect(() => {
20090
+ if (!selectedBackground || selectedBackgroundPairs.length > 0) {
20091
+ return;
20092
+ }
20093
+ const key = `${themeCategory}:${context.primary.key}:${context.accent.key}:${selectedBackground.token}`;
20094
+ if (noValidStateRef.current.has(key)) {
20095
+ return;
20096
+ }
20097
+ noValidStateRef.current.add(key);
20098
+ emitAnalyticsEvent({
20099
+ name: "no_valid_combination_state",
20100
+ ...buildAnalyticsContext({
20101
+ backgroundToken: selectedBackground.token
20102
+ })
20103
+ });
20104
+ }, [
20105
+ buildAnalyticsContext,
20106
+ context.accent.key,
20107
+ context.primary.key,
20108
+ emitAnalyticsEvent,
20109
+ selectedBackground,
20110
+ selectedBackgroundPairs.length,
20111
+ themeCategory
20112
+ ]);
20113
+ React5.useEffect(() => {
20114
+ if (!isTechnicalDetailsOpen || technicalDetailsViewedRef.current) {
20115
+ return;
20116
+ }
20117
+ technicalDetailsViewedRef.current = true;
18606
20118
  emitAnalyticsEvent({
18607
- name: "technical_details_opened",
20119
+ name: "technical_details_viewed",
18608
20120
  ...buildAnalyticsContext({
18609
20121
  source: "details"
18610
20122
  })
18611
20123
  });
18612
- }, [buildAnalyticsContext, emitAnalyticsEvent]);
20124
+ }, [buildAnalyticsContext, emitAnalyticsEvent, isTechnicalDetailsOpen]);
18613
20125
  if (!selectedBackground) {
18614
20126
  return /* @__PURE__ */ jsxRuntime.jsxs(Card, { className: "px-6 py-6", children: [
18615
20127
  /* @__PURE__ */ jsxRuntime.jsx(Heading, { level: 2, size: 5, className: "text-foreground", trim: "normal", children: "No approved background tones available" }),
18616
20128
  /* @__PURE__ */ jsxRuntime.jsx(Text, { size: 2, className: "mt-3", children: "No approved tones are available for the current palette and colour selection." })
18617
20129
  ] });
18618
20130
  }
18619
- const compactControlsSummary = `${themeCategory === "brand" ? "Brand" : "Aboriginal"} palette, ${context.primary.label} primary, ${context.accent.label} accent, ${getPairingColorDisplayName3(
20131
+ const compactControlsSummary = `${themeCategory === "brand" ? "Brand" : "Aboriginal"} palette, ${context.primary.label} primary, ${context.accent.label} accent, ${getPairingColorDisplayName4(
18620
20132
  selectedBackground
18621
20133
  )} background.`;
18622
20134
  const renderControlsPanel = (isOverlay = false) => /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
@@ -18642,7 +20154,7 @@ function ColorPairingToolV3Content({
18642
20154
  ] }) }),
18643
20155
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-b border-grey-200 px-5 py-4", children: [
18644
20156
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
18645
- COLOR_PAIRING_TOOL_DRAWER_STEPS.map((step, index) => /* @__PURE__ */ jsxRuntime.jsx(
20157
+ COLOR_PAIRING_TOOL_DRAWER_STEPS2.map((step, index) => /* @__PURE__ */ jsxRuntime.jsx(
18646
20158
  "button",
18647
20159
  {
18648
20160
  type: "button",
@@ -18682,7 +20194,7 @@ function ColorPairingToolV3Content({
18682
20194
  const isSelected = themeCategory === palette;
18683
20195
  const label = palette === "brand" ? "Brand palette" : "Aboriginal palette";
18684
20196
  return /* @__PURE__ */ jsxRuntime.jsx(
18685
- SelectorButton,
20197
+ SelectorButton2,
18686
20198
  {
18687
20199
  label,
18688
20200
  isSelected,
@@ -18695,14 +20207,14 @@ function ColorPairingToolV3Content({
18695
20207
  /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "space-y-3", children: [
18696
20208
  /* @__PURE__ */ jsxRuntime.jsx(Heading, { level: 4, size: 6, className: "mb-3 text-foreground", trim: "normal", children: "Primary colour family" }),
18697
20209
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-1 gap-2 sm:grid-cols-2", children: selectableFamilies.map((family) => {
18698
- const label = getFamilySelectorLabel3(family, themeCategory, "primary colour");
20210
+ const label = getFamilySelectorLabel4(family, themeCategory, "primary colour");
18699
20211
  return /* @__PURE__ */ jsxRuntime.jsx(
18700
- SelectorButton,
20212
+ SelectorButton2,
18701
20213
  {
18702
20214
  label,
18703
20215
  isSelected: family.key === context.primary.key,
18704
20216
  onClick: () => handlePrimaryColorChange(family.key),
18705
- swatch: getFamilySwatchColor3(family, 800)
20217
+ swatch: getFamilySwatchColor4(family, 800)
18706
20218
  },
18707
20219
  family.key
18708
20220
  );
@@ -18711,14 +20223,14 @@ function ColorPairingToolV3Content({
18711
20223
  /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "space-y-3", children: [
18712
20224
  /* @__PURE__ */ jsxRuntime.jsx(Heading, { level: 4, size: 6, className: "mb-3 text-foreground", trim: "normal", children: "Accent colour family" }),
18713
20225
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-1 gap-2 sm:grid-cols-2", children: selectableAccentFamilies.map((family) => {
18714
- const label = getFamilySelectorLabel3(family, themeCategory, "accent colour");
20226
+ const label = getFamilySelectorLabel4(family, themeCategory, "accent colour");
18715
20227
  return /* @__PURE__ */ jsxRuntime.jsx(
18716
- SelectorButton,
20228
+ SelectorButton2,
18717
20229
  {
18718
20230
  label,
18719
20231
  isSelected: family.key === context.accent.key,
18720
20232
  onClick: () => handleAccentColorChange(family.key),
18721
- swatch: getFamilySwatchColor3(family, 600)
20233
+ swatch: getFamilySwatchColor4(family, 600)
18722
20234
  },
18723
20235
  family.key
18724
20236
  );
@@ -18742,7 +20254,7 @@ function ColorPairingToolV3Content({
18742
20254
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: group.label })
18743
20255
  ] }),
18744
20256
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid gap-3", children: group.backgrounds.map((background) => /* @__PURE__ */ jsxRuntime.jsx(
18745
- BackgroundSwatchButton,
20257
+ BackgroundSwatchButton2,
18746
20258
  {
18747
20259
  background,
18748
20260
  hasPairs: (context.pairsByBackground[background.token]?.length ?? 0) > 0,
@@ -18755,37 +20267,15 @@ function ColorPairingToolV3Content({
18755
20267
  },
18756
20268
  group.key
18757
20269
  )) }) }),
18758
- /* @__PURE__ */ jsxRuntime.jsx("section", { className: "h-full w-full shrink-0 overflow-y-auto px-5 py-5", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-4 pr-1", children: recommendationItems.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-3", children: recommendationItems.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
18759
- RecommendationCard,
18760
- {
18761
- copiedKey,
18762
- item,
18763
- isSelected: item.pair.id === selectedPair?.id,
18764
- onCopyPairing: () => copyValue(
18765
- `pair:${item.pair.id}`,
18766
- getPairingCopyText(item.pair),
18767
- "Pairing copied",
18768
- item.pair.id === bestRecommendedPair?.id ? {
18769
- name: "best_recommendation_copied",
18770
- ...buildAnalyticsContext({
18771
- foregroundToken: item.pair.foreground.token,
18772
- pairId: item.pair.id,
18773
- source: "recommendations-list"
18774
- })
18775
- } : void 0
18776
- ),
18777
- onSelect: () => handlePairChange(item.pair.id, "recommendations-list")
18778
- },
18779
- item.pair.id
18780
- )) }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-sm border border-grey-200 bg-white px-5 py-5", children: [
18781
- /* @__PURE__ */ jsxRuntime.jsx(Heading, { level: 4, size: 6, className: "text-foreground", trim: "normal", children: "No valid combinations available" }),
18782
- /* @__PURE__ */ jsxRuntime.jsx(Text, { size: 2, className: "mt-3", children: "No AAA-compliant foreground options are available for this background." })
18783
- ] }) }) })
20270
+ /* @__PURE__ */ jsxRuntime.jsx("section", { className: "h-full w-full shrink-0 overflow-y-auto px-5 py-5", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-4 pr-1", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-sm border border-grey-200 bg-grey-50 px-4 py-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
20271
+ /* @__PURE__ */ jsxRuntime.jsx(Heading, { level: 4, size: 6, className: "text-foreground", trim: "normal", children: "Review the result on the right" }),
20272
+ /* @__PURE__ */ jsxRuntime.jsx(Text, { size: 2, children: "The right panel shows one recommended pairing first. Use approved alternatives there only when you need a different tone or emphasis." })
20273
+ ] }) }) }) })
18784
20274
  ]
18785
20275
  }
18786
20276
  ) }),
18787
20277
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-t border-grey-200 px-5 py-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between", children: [
18788
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-10 flex-1", children: isLastDrawerStep ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: "Select a combination to update the result immediately." }) : null }),
20278
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-10 flex-1", children: isLastDrawerStep ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: "Review the NSW recommendation in the result panel. Use alternatives there only if you need a different approved treatment." }) : null }),
18789
20279
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex w-full items-center gap-2 sm:w-auto sm:flex-none", children: [
18790
20280
  isOverlay && isLastDrawerStep ? /* @__PURE__ */ jsxRuntime.jsx(
18791
20281
  Button2,
@@ -18810,7 +20300,7 @@ function ColorPairingToolV3Content({
18810
20300
  "data-slot": "tool-sidebar",
18811
20301
  className: "hidden xl:sticky xl:top-[var(--tool-sidebar-top)] xl:block xl:self-start",
18812
20302
  style: desktopSidebarStyle,
18813
- children: /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "h-[40rem] max-h-[40rem] gap-0 overflow-hidden py-0 sm:h-[44rem] sm:max-h-[44rem] xl:h-[var(--tool-sidebar-max-height)] xl:max-h-[var(--tool-sidebar-max-height)]", children: renderControlsPanel() })
20303
+ children: /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "h-[40rem] max-h-[40rem] gap-0 overflow-hidden border-grey-200 bg-grey-50/60 py-0 sm:h-[44rem] sm:max-h-[44rem] xl:h-[var(--tool-sidebar-max-height)] xl:max-h-[var(--tool-sidebar-max-height)]", children: renderControlsPanel() })
18814
20304
  }
18815
20305
  ),
18816
20306
  /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-slot": "tool-results", className: "space-y-6", children: [
@@ -18818,7 +20308,7 @@ function ColorPairingToolV3Content({
18818
20308
  /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "gap-4 px-5 py-5", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between", children: [
18819
20309
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
18820
20310
  /* @__PURE__ */ jsxRuntime.jsx(Heading, { level: 2, size: 5, className: "text-foreground", trim: "normal", children: "Configuration" }),
18821
- /* @__PURE__ */ jsxRuntime.jsx(Text, { size: 2, children: "Open the colour pairing drawer to change palette, background, and recommended combinations." }),
20311
+ /* @__PURE__ */ jsxRuntime.jsx(Text, { size: 2, children: "Open the drawer to set palette, colour families, and background. The result panel shows the NSW recommendation first." }),
18822
20312
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: compactControlsSummary })
18823
20313
  ] }),
18824
20314
  /* @__PURE__ */ jsxRuntime.jsxs(
@@ -18850,49 +20340,124 @@ function ColorPairingToolV3Content({
18850
20340
  /* @__PURE__ */ jsxRuntime.jsxs("section", { ref: resultSectionRef, className: "space-y-6", "aria-label": "Current result", children: [
18851
20341
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sr-only", "aria-live": "polite", children: liveAnnouncement }),
18852
20342
  /* @__PURE__ */ jsxRuntime.jsx(
18853
- CurrentResultCard,
20343
+ CurrentResultCard2,
18854
20344
  {
18855
20345
  bestPair: bestRecommendedPair,
18856
- familySummary,
20346
+ copiedKey,
20347
+ onCopyPairing: () => previewPair ? copyValue(
20348
+ "current-pairing",
20349
+ getPairingCopyText2(previewPair),
20350
+ "Pairing copied",
20351
+ previewPair.id === bestRecommendedPair?.id ? {
20352
+ name: "best_recommendation_copied",
20353
+ ...buildAnalyticsContext({
20354
+ foregroundToken: previewPair.foreground.token,
20355
+ pairId: previewPair.id,
20356
+ source: "current-result"
20357
+ })
20358
+ } : void 0
20359
+ ) : void 0,
18857
20360
  pair: previewPair,
20361
+ supportText: currentPairSupportText,
18858
20362
  selectedBackground
18859
20363
  }
18860
20364
  )
18861
20365
  ] }),
18862
- bestRecommendedPair ? /* @__PURE__ */ jsxRuntime.jsx("section", { className: "space-y-4", children: /* @__PURE__ */ jsxRuntime.jsx(
18863
- BestRecommendationCard,
18864
- {
18865
- copiedKey,
18866
- isCurrentSelection: selectedPair?.id === bestRecommendedPair.id,
18867
- pair: bestRecommendedPair,
18868
- reason: getBestRecommendationReason(bestRecommendedPair, context),
18869
- onCopyPairing: () => copyValue(
18870
- "best-pairing",
18871
- getPairingCopyText(bestRecommendedPair),
18872
- "Pairing copied",
20366
+ bestRecommendedPair || alternativeRecommendationGroups.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "space-y-4", children: [
20367
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
20368
+ /* @__PURE__ */ jsxRuntime.jsx(Heading, { level: 2, size: 5, className: "text-foreground", trim: "normal", children: "Approved alternatives" }),
20369
+ /* @__PURE__ */ jsxRuntime.jsx(Text, { size: 2, className: "text-muted-foreground", children: "These are approved alternatives for different tone or emphasis." })
20370
+ ] }),
20371
+ alternativeRecommendationGroups.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
20372
+ visibleAlternativeRecommendationGroups.map((group) => /* @__PURE__ */ jsxRuntime.jsx(
20373
+ RecommendationGroupSection,
18873
20374
  {
18874
- name: "best_recommendation_copied",
18875
- ...buildAnalyticsContext({
18876
- foregroundToken: bestRecommendedPair.foreground.token,
18877
- pairId: bestRecommendedPair.id,
18878
- source: "best-recommendation"
18879
- })
20375
+ title: group.title,
20376
+ description: group.description,
20377
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-2", children: group.items.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
20378
+ RecommendationCard2,
20379
+ {
20380
+ copiedKey,
20381
+ item,
20382
+ isSelected: item.pair.id === selectedPair?.id,
20383
+ onCopyPairing: () => copyValue(
20384
+ `pair:${item.pair.id}`,
20385
+ getPairingCopyText2(item.pair),
20386
+ "Pairing copied",
20387
+ item.pair.id === bestRecommendedPair?.id ? {
20388
+ name: "best_recommendation_copied",
20389
+ ...buildAnalyticsContext({
20390
+ foregroundToken: item.pair.foreground.token,
20391
+ pairId: item.pair.id,
20392
+ source: "recommendations-list"
20393
+ })
20394
+ } : void 0
20395
+ ),
20396
+ onSelect: () => handlePairChange(item.pair.id, "recommendations-list")
20397
+ },
20398
+ item.pair.id
20399
+ )) })
20400
+ },
20401
+ group.id
20402
+ )),
20403
+ hiddenAlternativeCount > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(
20404
+ Button2,
20405
+ {
20406
+ variant: "ghost",
20407
+ color: "grey",
20408
+ className: "w-full justify-center border border-grey-200 bg-white sm:w-auto",
20409
+ onClick: () => setIsShowingAllAlternatives(true),
20410
+ children: [
20411
+ "Show ",
20412
+ hiddenAlternativeCount,
20413
+ " more"
20414
+ ]
18880
20415
  }
18881
- ),
18882
- onUsePairing: () => handlePairChange(bestRecommendedPair.id, "best-recommendation")
18883
- }
18884
- ) }) : /* @__PURE__ */ jsxRuntime.jsxs(Card, { className: "px-6 py-6", children: [
18885
- /* @__PURE__ */ jsxRuntime.jsx(Heading, { level: 2, size: 5, className: "text-foreground", trim: "normal", children: "Best recommended pairing" }),
18886
- /* @__PURE__ */ jsxRuntime.jsx(Text, { size: 2, className: "mt-3", children: "No AAA-compliant foreground options available for this selection." })
18887
- ] }),
18888
- /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "space-y-4", children: [
18889
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
18890
- /* @__PURE__ */ jsxRuntime.jsx(Heading, { level: 2, size: 5, className: "text-foreground", trim: "normal", children: "Technical colour values" }),
18891
- /* @__PURE__ */ jsxRuntime.jsx(Text, { size: 2, children: "Token, tone, HEX, RGB, HSL, and OKLCH values for the current selection." })
18892
- ] }),
18893
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-4 lg:grid-cols-2", children: [
20416
+ ) : null
20417
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(Card, { className: "px-6 py-6", children: [
20418
+ /* @__PURE__ */ jsxRuntime.jsx(Heading, { level: 3, size: 6, className: "text-foreground", trim: "normal", children: "No other AAA alternatives" }),
20419
+ /* @__PURE__ */ jsxRuntime.jsx(Text, { size: 2, className: "mt-3", children: "The NSW recommended pairing is the only AAA-compliant foreground option for this background." })
20420
+ ] })
20421
+ ] }) : null,
20422
+ /* @__PURE__ */ jsxRuntime.jsx("section", { className: "space-y-3", children: /* @__PURE__ */ jsxRuntime.jsx(Collapsible, { open: isTechnicalDetailsOpen, onOpenChange: setIsTechnicalDetailsOpen, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-sm border border-grey-200 bg-white", children: [
20423
+ /* @__PURE__ */ jsxRuntime.jsx(CollapsibleTrigger2, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
20424
+ "button",
20425
+ {
20426
+ type: "button",
20427
+ className: "flex w-full items-center justify-between gap-4 px-4 py-4 text-left transition-colors hover:bg-grey-50 focus-visible:ring-2 focus-visible:ring-primary-700 focus-visible:ring-offset-2 focus-visible:outline-hidden sm:px-5",
20428
+ children: [
20429
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", children: [
20430
+ /* @__PURE__ */ jsxRuntime.jsx(Heading, { level: 2, size: 6, className: "text-foreground", trim: "normal", children: "Technical colour values" }),
20431
+ /* @__PURE__ */ jsxRuntime.jsx(Text, { size: 2, className: "text-muted-foreground", children: "Token, tone, HEX, RGB, HSL, and OKLCH values for advanced use." })
20432
+ ] }),
20433
+ /* @__PURE__ */ jsxRuntime.jsxs(
20434
+ "span",
20435
+ {
20436
+ className: cn(
20437
+ "inline-flex items-center gap-2 text-sm font-semibold text-primary-800",
20438
+ isTechnicalDetailsOpen && "text-foreground"
20439
+ ),
20440
+ children: [
20441
+ isTechnicalDetailsOpen ? "Hide" : "Show",
20442
+ /* @__PURE__ */ jsxRuntime.jsx(
20443
+ Icons.keyboard_arrow_down,
20444
+ {
20445
+ "data-slot": "icon",
20446
+ className: cn(
20447
+ "size-5 transition-transform",
20448
+ isTechnicalDetailsOpen && "rotate-180"
20449
+ )
20450
+ }
20451
+ )
20452
+ ]
20453
+ }
20454
+ )
20455
+ ]
20456
+ }
20457
+ ) }),
20458
+ /* @__PURE__ */ jsxRuntime.jsx(CollapsibleContent2, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-t border-grey-200 px-4 py-4 sm:px-5", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-4 lg:grid-cols-2", children: [
18894
20459
  /* @__PURE__ */ jsxRuntime.jsx(
18895
- TechnicalDetailsPanel,
20460
+ TechnicalDetailsPanel2,
18896
20461
  {
18897
20462
  title: "Background values",
18898
20463
  color: selectedBackground,
@@ -18902,7 +20467,7 @@ function ColorPairingToolV3Content({
18902
20467
  }
18903
20468
  ),
18904
20469
  /* @__PURE__ */ jsxRuntime.jsx(
18905
- TechnicalDetailsPanel,
20470
+ TechnicalDetailsPanel2,
18906
20471
  {
18907
20472
  title: "Foreground values",
18908
20473
  color: detailForeground,
@@ -18911,51 +20476,51 @@ function ColorPairingToolV3Content({
18911
20476
  onCopyValue: (copyKey, value, toastLabel) => copyValue(copyKey, value, toastLabel)
18912
20477
  }
18913
20478
  )
18914
- ] }),
18915
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
18916
- /* @__PURE__ */ jsxRuntime.jsx(Heading, { level: 3, size: 6, className: "mb-3 text-foreground", trim: "normal", children: "Share colour pairing" }),
18917
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-sm border border-grey-200 bg-white p-4", children: [
18918
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-start gap-3 sm:flex-row sm:items-start sm:justify-between", children: [
18919
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: "Copy this URL to share your current colour pairing:" }),
18920
- /* @__PURE__ */ jsxRuntime.jsx(
18921
- Button2,
18922
- {
18923
- variant: "ghost",
18924
- color: "grey",
18925
- size: "sm",
18926
- className: "shrink-0",
18927
- onClick: () => copyValue(
18928
- "share-url",
18929
- typeof window === "undefined" ? shareUrl : new URL(shareUrl, window.location.origin).toString(),
18930
- "Colour pairing link copied"
18931
- ),
18932
- "aria-label": copiedKey === "share-url" ? "Colour pairing URL copied to clipboard" : "Copy colour pairing URL to clipboard",
18933
- title: copiedKey === "share-url" ? "Copied" : "Copy URL",
18934
- children: copiedKey === "share-url" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
18935
- /* @__PURE__ */ jsxRuntime.jsx(Icons.check, { "data-slot": "icon", className: "size-5" }),
18936
- "Copied"
18937
- ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
18938
- /* @__PURE__ */ jsxRuntime.jsx(Icons.content_copy, { "data-slot": "icon", className: "size-5" }),
18939
- "Copy URL"
18940
- ] })
18941
- }
18942
- )
18943
- ] }),
18944
- /* @__PURE__ */ jsxRuntime.jsx("code", { className: "mt-3 block w-full rounded-sm border border-grey-200 bg-background px-3 py-2 font-mono text-[11px] break-all text-foreground sm:text-xs", children: shareUrl })
18945
- ] })
20479
+ ] }) }) })
20480
+ ] }) }) }),
20481
+ /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "space-y-2", children: [
20482
+ /* @__PURE__ */ jsxRuntime.jsx(Heading, { level: 3, size: 6, className: "text-foreground", trim: "normal", children: "Share colour pairing" }),
20483
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-sm border border-grey-200 bg-white px-4 py-4", children: [
20484
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between", children: [
20485
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: "Copy a link to this current result." }),
20486
+ /* @__PURE__ */ jsxRuntime.jsx(
20487
+ Button2,
20488
+ {
20489
+ variant: "ghost",
20490
+ color: "grey",
20491
+ size: "sm",
20492
+ className: "shrink-0 self-start sm:self-center",
20493
+ onClick: () => copyValue(
20494
+ "share-url",
20495
+ typeof window === "undefined" ? shareUrl : new URL(shareUrl, window.location.origin).toString(),
20496
+ "Colour pairing link copied"
20497
+ ),
20498
+ "aria-label": copiedKey === "share-url" ? "Colour pairing URL copied to clipboard" : "Copy colour pairing URL to clipboard",
20499
+ title: copiedKey === "share-url" ? "Copied" : "Copy URL",
20500
+ children: copiedKey === "share-url" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
20501
+ /* @__PURE__ */ jsxRuntime.jsx(Icons.check, { "data-slot": "icon", className: "size-5" }),
20502
+ "Copied"
20503
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
20504
+ /* @__PURE__ */ jsxRuntime.jsx(Icons.content_copy, { "data-slot": "icon", className: "size-5" }),
20505
+ "Copy URL"
20506
+ ] })
20507
+ }
20508
+ )
20509
+ ] }),
20510
+ /* @__PURE__ */ jsxRuntime.jsx("code", { className: "mt-3 block w-full rounded-sm border border-grey-200 bg-background px-3 py-2 font-mono text-[11px] break-all text-foreground sm:text-xs", children: shareUrl })
18946
20511
  ] })
18947
20512
  ] })
18948
20513
  ] })
18949
20514
  ] });
18950
20515
  }
18951
- function ColorPairingToolV3({
20516
+ function ColorPairingToolV4({
18952
20517
  onAnalyticsEvent = () => {
18953
20518
  },
18954
- visibleFormats = DEFAULT_VISIBLE_FORMATS3
20519
+ visibleFormats = DEFAULT_VISIBLE_FORMATS4
18955
20520
  } = {}) {
18956
20521
  const normalizedVisibleFormats = [...new Set(visibleFormats)];
18957
- return /* @__PURE__ */ jsxRuntime.jsx(React5.Suspense, { fallback: /* @__PURE__ */ jsxRuntime.jsx(ColorPairingToolV3Loading, {}), children: /* @__PURE__ */ jsxRuntime.jsx(
18958
- ColorPairingToolV3Content,
20522
+ return /* @__PURE__ */ jsxRuntime.jsx(React5.Suspense, { fallback: /* @__PURE__ */ jsxRuntime.jsx(ColorPairingToolV4Loading, {}), children: /* @__PURE__ */ jsxRuntime.jsx(
20523
+ ColorPairingToolV4Content,
18959
20524
  {
18960
20525
  onAnalyticsEvent,
18961
20526
  visibleFormats: normalizedVisibleFormats
@@ -21586,7 +23151,7 @@ function FormatToggle({ format, setFormat }) {
21586
23151
 
21587
23152
  // package.json
21588
23153
  var package_default = {
21589
- version: "1.103.0"};
23154
+ version: "1.105.0"};
21590
23155
  function Logo(props) {
21591
23156
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
21592
23157
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sr-only", children: "NSW Government" }),
@@ -37349,8 +38914,20 @@ function getHeadings(slugify$1 = slugify.slugifyWithCounter()) {
37349
38914
  });
37350
38915
  return result;
37351
38916
  }
38917
+ function areHeadingNodesEqual(left, right) {
38918
+ if (left.level !== right.level || left.id !== right.id || left.title !== right.title) {
38919
+ return false;
38920
+ }
38921
+ if (left.children.length !== right.children.length) {
38922
+ return false;
38923
+ }
38924
+ return left.children.every((child, index) => areHeadingNodesEqual(child, right.children[index]));
38925
+ }
37352
38926
  function areHeadingTreesEqual(left, right) {
37353
- return JSON.stringify(left) === JSON.stringify(right);
38927
+ if (left.length !== right.length) {
38928
+ return false;
38929
+ }
38930
+ return left.every((heading, index) => areHeadingNodesEqual(heading, right[index]));
37354
38931
  }
37355
38932
  function usePageHeadings(enabled = true) {
37356
38933
  const [headings, setHeadings] = React5.useState([]);
@@ -37557,6 +39134,7 @@ exports.ColorCard = ColorCard;
37557
39134
  exports.ColorPairingTool = ColorPairingTool;
37558
39135
  exports.ColorPairingToolV2 = ColorPairingToolV2;
37559
39136
  exports.ColorPairingToolV3 = ColorPairingToolV3;
39137
+ exports.ColorPairingToolV4 = ColorPairingToolV4;
37560
39138
  exports.ColorSwatches = ColorSwatches;
37561
39139
  exports.ColourScale = ColourScale;
37562
39140
  exports.ComboChart = ComboChart;