diff --git a/bundle-budget.json b/bundle-budget.json
index 29d9de2641..9c9eb82cbf 100644
--- a/bundle-budget.json
+++ b/bundle-budget.json
@@ -2,25 +2,25 @@
"$comment": "Client JS bundle-size budgets captured from a known-good production build. `production` guards aggregate user-facing chunks, `routes` guards the five Lighthouse journeys against route-local growth, and `mockups` is a looser design-scratch hygiene ceiling. Refresh intentionally with `npm run check:bundle-budget -- --update`.",
"enforce": true,
"production": {
- "gzipBytes": 1695752,
+ "gzipBytes": 1694829,
"tolerancePct": 10
},
"mockups": {
- "gzipBytes": 499284,
+ "gzipBytes": 634923,
"tolerancePct": 25
},
"routes": {
"/": {
- "gzipBytes": 285184,
+ "gzipBytes": 238556,
"tolerancePct": 10
},
"/documents/search": {
- "gzipBytes": 288358,
+ "gzipBytes": 241755,
"tolerancePct": 10
}
},
- "totalGzipBytes": 2195036,
+ "totalGzipBytes": 2329752,
"tolerancePct": 10,
- "updatedAt": "2026-08-23T10:15:00.000Z",
- "baselineSource": "0764fb5813564cc1cb8933267597478ecff9c354"
+ "updatedAt": "2026-08-25T21:50:25.445Z",
+ "baselineSource": "6d12681970171a8572ef1dd9aa5f5dcdaf074969"
}
diff --git a/data/repo-awareness-snapshot.json b/data/repo-awareness-snapshot.json
index e040b9c31e..cd3f904129 100644
--- a/data/repo-awareness-snapshot.json
+++ b/data/repo-awareness-snapshot.json
@@ -1,8 +1,8 @@
{
"version": "repo-awareness-snapshot-v1",
"captured_revision": {
- "sha": "dc68600786530b60c732aefe050a02e742eb2421",
- "committed_at": "2026-08-26T05:55:49+08:00"
+ "sha": "6d12681970171a8572ef1dd9aa5f5dcdaf074969",
+ "committed_at": "2026-08-25T21:32:15+00:00"
},
"routes": {
"modes": [
@@ -793,6 +793,26 @@
"file": "src/app/mockups/sidebar-live/page.tsx",
"area": "mockup"
},
+ {
+ "path": "/mockups/therapy-compare-perfected",
+ "file": "src/app/mockups/therapy-compare-perfected/page.tsx",
+ "area": "mockup"
+ },
+ {
+ "path": "/mockups/therapy-compare-progressive",
+ "file": "src/app/mockups/therapy-compare-progressive/page.tsx",
+ "area": "mockup"
+ },
+ {
+ "path": "/mockups/therapy-compare-sheet",
+ "file": "src/app/mockups/therapy-compare-sheet/page.tsx",
+ "area": "mockup"
+ },
+ {
+ "path": "/mockups/therapy-compare-tray",
+ "file": "src/app/mockups/therapy-compare-tray/page.tsx",
+ "area": "mockup"
+ },
{
"path": "/mockups/therapy-navigation-context",
"file": "src/app/mockups/therapy-navigation-context/page.tsx",
@@ -1374,9 +1394,9 @@
],
"counts": {
"modes": 15,
- "pages": 192,
+ "pages": 196,
"product_pages": 55,
- "mockup_pages": 137,
+ "mockup_pages": 141,
"redirects": 17,
"api": 56
}
diff --git a/docs/site-map.md b/docs/site-map.md
index bb3153476a..2f2e76f2a2 100644
--- a/docs/site-map.md
+++ b/docs/site-map.md
@@ -1237,6 +1237,10 @@ This file is generated by `npm run docs:update` (or `npm run sitemap:update` dir
- `/mockups/settings-search-general` - Route discovered from app directory Source: `src/app/mockups/settings-search-general/page.tsx`.
- `/mockups/settings-search-privacy` - Route discovered from app directory Source: `src/app/mockups/settings-search-privacy/page.tsx`.
- `/mockups/sidebar-live` - Route discovered from app directory Source: `src/app/mockups/sidebar-live/page.tsx`.
+- `/mockups/therapy-compare-perfected` - Route discovered from app directory Source: `src/app/mockups/therapy-compare-perfected/page.tsx`.
+- `/mockups/therapy-compare-progressive` - Route discovered from app directory Source: `src/app/mockups/therapy-compare-progressive/page.tsx`.
+- `/mockups/therapy-compare-sheet` - Route discovered from app directory Source: `src/app/mockups/therapy-compare-sheet/page.tsx`.
+- `/mockups/therapy-compare-tray` - Route discovered from app directory Source: `src/app/mockups/therapy-compare-tray/page.tsx`.
- `/mockups/therapy-navigation-context` - Route discovered from app directory Source: `src/app/mockups/therapy-navigation-context/page.tsx`.
- `/mockups/therapy-navigation-dock` - Route discovered from app directory Source: `src/app/mockups/therapy-navigation-dock/page.tsx`.
- `/mockups/therapy-navigation-rail` - Route discovered from app directory Source: `src/app/mockups/therapy-navigation-rail/page.tsx`.
diff --git a/src/app/mockups/mockups-layout-client.tsx b/src/app/mockups/mockups-layout-client.tsx
index 6149ceb96e..54aa4050c8 100644
--- a/src/app/mockups/mockups-layout-client.tsx
+++ b/src/app/mockups/mockups-layout-client.tsx
@@ -84,6 +84,11 @@ export function MockupsLayoutClient({ children }: { children: ReactNode }) {
// These studies render their own top bar and composer inside each device
// frame. Suppress shared chrome so it cannot be mistaken for the concept.
const isTherapyNavigationMockup = pathname.startsWith("/mockups/therapy-navigation-");
+ // These three draw a complete phone — universal header, page body and the
+ // edge-to-edge composer — inside every frame, because the selection surface
+ // under study is defined by what sits between those two. Shared chrome above
+ // them would read as a second real header and a second real search bar.
+ const isTherapyComparePickerMockup = pathname.startsWith("/mockups/therapy-compare-");
// The calculators search page owns its own search input (top on desktop, docked
// at the bottom on phones), so the shared universal composer is suppressed here
// to avoid a second, floating search bar.
@@ -171,6 +176,7 @@ export function MockupsLayoutClient({ children }: { children: ReactNode }) {
!isServicesFilterOptionsMockup &&
!isFilterSheetRestyleMockup &&
!isTherapyNavigationMockup &&
+ !isTherapyComparePickerMockup &&
!isWarningConsolidationMockup &&
!isWarningLineMockup &&
!isAlsoMatchesAccentMockup &&
@@ -202,6 +208,7 @@ export function MockupsLayoutClient({ children }: { children: ReactNode }) {
!isFilterSheetRestyleMockup &&
!isPhoneInPageNavigationMockup &&
!isTherapyNavigationMockup &&
+ !isTherapyComparePickerMockup &&
!isWarningConsolidationMockup &&
!isWarningLineMockup &&
!isAlsoMatchesAccentMockup &&
diff --git a/src/app/mockups/therapy-compare-perfected/page.tsx b/src/app/mockups/therapy-compare-perfected/page.tsx
new file mode 100644
index 0000000000..1a85624414
--- /dev/null
+++ b/src/app/mockups/therapy-compare-perfected/page.tsx
@@ -0,0 +1,13 @@
+import type { Metadata } from "next";
+
+import { TherapyComparePerfectedMockups } from "@/components/therapy-compare-picker-mockups/tray-perfected";
+
+export const metadata: Metadata = {
+ title: "Therapy comparison picker C+ · Perfected tray - Clinical KB",
+ description:
+ "The perfected compare-tray direction: no tray until a set exists, one bottom stack that hides with the composer, and adding from a therapy record.",
+};
+
+export default function TherapyComparePerfectedMockupPage() {
+ return ;
+}
diff --git a/src/app/mockups/therapy-compare-progressive/page.tsx b/src/app/mockups/therapy-compare-progressive/page.tsx
new file mode 100644
index 0000000000..f689f60b67
--- /dev/null
+++ b/src/app/mockups/therapy-compare-progressive/page.tsx
@@ -0,0 +1,12 @@
+import type { Metadata } from "next";
+
+import { TherapyCompareProgressiveMockups } from "@/components/therapy-compare-picker-mockups/progressive";
+
+export const metadata: Metadata = {
+ title: "Therapy comparison picker A · Add as you go - Clinical KB",
+ description: "Direction A for the phone therapy-comparison picker: Add as you go, shown as a live phone prototype.",
+};
+
+export default function TherapyCompareProgressiveMockupPage() {
+ return ;
+}
diff --git a/src/app/mockups/therapy-compare-sheet/page.tsx b/src/app/mockups/therapy-compare-sheet/page.tsx
new file mode 100644
index 0000000000..fa3da8e87e
--- /dev/null
+++ b/src/app/mockups/therapy-compare-sheet/page.tsx
@@ -0,0 +1,13 @@
+import type { Metadata } from "next";
+
+import { TherapyCompareSheetMockups } from "@/components/therapy-compare-picker-mockups/sheet";
+
+export const metadata: Metadata = {
+ title: "Therapy comparison picker B · Build the set in one sheet - Clinical KB",
+ description:
+ "Direction B for the phone therapy-comparison picker: Build the set in one sheet, shown as a live phone prototype.",
+};
+
+export default function TherapyCompareSheetMockupPage() {
+ return ;
+}
diff --git a/src/app/mockups/therapy-compare-tray/page.tsx b/src/app/mockups/therapy-compare-tray/page.tsx
new file mode 100644
index 0000000000..4d28d275b2
--- /dev/null
+++ b/src/app/mockups/therapy-compare-tray/page.tsx
@@ -0,0 +1,13 @@
+import type { Metadata } from "next";
+
+import { TherapyCompareTrayMockups } from "@/components/therapy-compare-picker-mockups/tray";
+
+export const metadata: Metadata = {
+ title: "Therapy comparison picker C · Carry a compare tray - Clinical KB",
+ description:
+ "Direction C for the phone therapy-comparison picker: Carry a compare tray, shown as a live phone prototype.",
+};
+
+export default function TherapyCompareTrayMockupPage() {
+ return ;
+}
diff --git a/src/components/therapy-compare-picker-mockups/progressive.tsx b/src/components/therapy-compare-picker-mockups/progressive.tsx
new file mode 100644
index 0000000000..ccede720dc
--- /dev/null
+++ b/src/components/therapy-compare-picker-mockups/progressive.tsx
@@ -0,0 +1,392 @@
+"use client";
+
+import { ArrowLeft, Copy, Plus, Search, Trash2, X } from "lucide-react";
+import { useState } from "react";
+
+import { cn } from "@/components/ui-primitives";
+
+import {
+ AddedToast,
+ ComparisonPreview,
+ CurrentDefects,
+ MAX_COMPARE,
+ MockupSection,
+ MockupShell,
+ NoteCard,
+ PhoneComposer,
+ PhoneFrame,
+ PhoneTopBar,
+ ReviewPill,
+ SLOT_LETTERS,
+ SlotBadge,
+ THERAPY_COUNT,
+ bySlug,
+ filterTherapies,
+ focusRing,
+ useCompareSet,
+} from "./shared";
+
+/* ------------------------------------------------------------------ *
+ * Direction A — Add as you go.
+ *
+ * The screen shows what you have chosen and one way to add the next one.
+ * Zero selections means zero cards: a single primary button and two starter
+ * pairs. The slot letters still exist — they are what the comparison keys
+ * off — but they are minted as you fill them, never drawn empty.
+ * ------------------------------------------------------------------ */
+
+const STARTERS: Array<{ label: string; slugs: [string, string] }> = [
+ { label: "CBT vs ACT", slugs: ["cognitive-behavioural-therapy-cbt", "acceptance-and-commitment-therapy-act"] },
+ { label: "DBT vs MBT", slugs: ["dialectical-behaviour-therapy-dbt", "mentalisation-based-treatment-mbt"] },
+ {
+ label: "EMDR vs CPT",
+ slugs: ["eye-movement-desensitisation-and-reprocessing-emdr", "cognitive-processing-therapy-cpt"],
+ },
+];
+
+function ProgressivePhone() {
+ const set = useCompareSet();
+ const [picking, setPicking] = useState(false);
+ const [query, setQuery] = useState("");
+ const [toast, setToast] = useState(null);
+ const results = filterTherapies(query, "All");
+ const count = set.selected.length;
+
+ function choose(slug: string) {
+ set.add(slug);
+ setPicking(false);
+ setQuery("");
+ const therapy = bySlug(slug);
+ setToast(therapy ? `${therapy.short} added as ${SLOT_LETTERS[count] ?? "next"}` : null);
+ window.setTimeout(() => setToast(null), 1600);
+ }
+
+ return (
+
+
+
+
+ {/* Title block: one line, and a count that only exists once it counts. */}
+
+
+
Compare therapies
+
+ {count === 0
+ ? "Add two or more to see fit, cautions and delivery side by side."
+ : count === 1
+ ? "Add one more to start the comparison."
+ : `${count} of ${MAX_COMPARE} · fit, cautions, delivery and evidence.`}
+
+
+ {count > 0 ? (
+
+ ) : null}
+
+
+ {/* Chosen therapies — one row each, nothing more. */}
+ {count > 0 ? (
+
+ {set.items.map((therapy, index) => (
+
+
+
+
{therapy.name}
+
+ {therapy.category}
+
+
+
+
+
+
+
+ ))}
+
+ ) : null}
+
+ {/* The single add affordance. Primary while empty, quiet once filling. */}
+ {set.full ? (
+
+ {MAX_COMPARE} of {MAX_COMPARE} — remove one to swap in another
+
+ ) : (
+
+ )}
+
+ {/* Starter pairs — only while they can still fit. */}
+ {count === 0 ? (
+
+
+ Or start from a pair
+
+
+ {STARTERS.map((starter) => (
+
+ ))}
+
+
+ You can also add from a search result or from any therapy record — the set follows you.
+
+
+ ) : null}
+
+ {/* Everything that only makes sense with a comparison on screen. */}
+ {count >= 2 ? (
+ <>
+
+
+
+
+ >
+ ) : null}
+
+
+
+
+
+ {/* Full-screen picker. One tap adds and returns. */}
+ {picking ? (
+
+ The phone below is live — tap it. Comparison copy is placeholder text for layout only, not clinical content;
+ every record in the real catalogue is still needs review, which is why each card says so.
+
+
+
+ {children}
+
+
+ );
+}
+
+export const currentDefects: Array<{ title: string; body: string }> = [
+ {
+ title: "Four empty slots before a single choice",
+ body: "The screen opens with A, B, C and D already drawn as cards. Nothing is selected, so all four are dead placeholders that push the only real action — the one that adds a therapy — below the fold. Four is the ceiling, not the plan.",
+ },
+ {
+ title: "Three controls that all do the same thing",
+ body: "Each empty slot card is tappable, `Change therapies` sits under them, and `Add therapies to compare` sits under that. Three affordances, one outcome, and the biggest and most prominent of them is the one you reach last.",
+ },
+ {
+ title: "The toolbar is disabled at the moment you see it",
+ body: "Comfortable/Dense, Copy set and Clear all render at full strength above the fold with nothing to act on. Density in particular decides how a wide table breathes — a decision that has no meaning until there is a table, and none at all on a 390 px screen.",
+ },
+ {
+ title: "The count is chrome, not feedback",
+ body: "`0 of 4 selected` is a static badge in the header, detached from the slots it counts, and in the captured screen it sits partly under the page's own edge control.",
+ },
+ {
+ title: "The slot cards do not fill the phone",
+ body: "The strip is `grid-cols-1 sm:grid-cols-2 lg:grid-cols-3`, but on the phone the cards still stop short of the right edge while the buttons beneath them run full width — so the column looks broken rather than deliberate.",
+ },
+ {
+ title: "The payoff is a sideways-scrolling table",
+ body: "Once two are chosen, the comparison is a `min-w-[720px]` table inside a horizontal scroller. On a 390 px viewport that is two thirds of a column at a time, with the field labels scrolling away from the values they label.",
+ },
+];
+
+export function CurrentDefects() {
+ return (
+
+
+ {currentDefects.map((defect) => (
+
+ ))}
+
+
+ );
+}
diff --git a/src/components/therapy-compare-picker-mockups/sheet.tsx b/src/components/therapy-compare-picker-mockups/sheet.tsx
new file mode 100644
index 0000000000..b6b68414d1
--- /dev/null
+++ b/src/components/therapy-compare-picker-mockups/sheet.tsx
@@ -0,0 +1,473 @@
+"use client";
+
+import { Check, Copy, Layers, Pencil, Plus, Scale, Search, X } from "lucide-react";
+import { useState } from "react";
+
+import { cn } from "@/components/ui-primitives";
+
+import {
+ CATEGORY_FILTERS,
+ ComparisonPreview,
+ CurrentDefects,
+ MAX_COMPARE,
+ MockupSection,
+ MockupShell,
+ NoteCard,
+ PhoneComposer,
+ PhoneFrame,
+ PhoneTopBar,
+ SLOT_LETTERS,
+ THERAPY_COUNT,
+ bySlug,
+ filterTherapies,
+ focusRing,
+ useCompareSet,
+} from "./shared";
+
+/* ------------------------------------------------------------------ *
+ * Direction B — Build the set in one sheet.
+ *
+ * The page body never holds a picker. Choosing is one full-height sheet:
+ * search, category filter, a multi-select list, and a docked bar that
+ * counts what you have and commits the whole set in one action. You return
+ * to the comparison, not to another empty slot.
+ * ------------------------------------------------------------------ */
+
+function SheetPhone() {
+ const set = useCompareSet();
+ const [open, setOpen] = useState(false);
+ const [draft, setDraft] = useState([]);
+ const [query, setQuery] = useState("");
+ const [category, setCategory] = useState("All");
+ const results = filterTherapies(query, category);
+
+ function openSheet() {
+ setDraft(set.selected);
+ setQuery("");
+ setCategory("All");
+ setOpen(true);
+ }
+
+ function toggleDraft(slug: string) {
+ setDraft((current) => {
+ if (current.includes(slug)) return current.filter((entry) => entry !== slug);
+ return current.length >= MAX_COMPARE ? current : [...current, slug];
+ });
+ }
+
+ function commit() {
+ set.setSelected(draft);
+ setOpen(false);
+ }
+
+ const count = set.selected.length;
+ const draftItems = draft
+ .map((slug) => bySlug(slug))
+ .filter((entry): entry is NonNullable => Boolean(entry));
+
+ return (
+
+
+
+
+
Compare therapies
+
+ {count === 0 ? (
+ <>
+
+ Choose two to four therapies and see fit, cautions, delivery and evidence together.
+
+
+
+
+
+ What you get
+
+
+
· Cautions first, then best fit, time and clinician skill
+
· Every field links back to the source record
+
· Copy the whole set as text for a letter or note
+
+
+ >
+ ) : (
+ <>
+ {/* The whole selection as one wrapping line of pills. */}
+
+
+
+ );
+}
diff --git a/src/components/therapy-compare-picker-mockups/tray-perfected.tsx b/src/components/therapy-compare-picker-mockups/tray-perfected.tsx
new file mode 100644
index 0000000000..aa5a358b2f
--- /dev/null
+++ b/src/components/therapy-compare-picker-mockups/tray-perfected.tsx
@@ -0,0 +1,643 @@
+"use client";
+
+import { ArrowLeft, Check, ChevronUp, Copy, FileText, Plus, Scale, Search, Trash2, X } from "lucide-react";
+import { useEffect, useRef, useState, type UIEvent } from "react";
+
+import { cn } from "@/components/ui-primitives";
+
+import {
+ ComparisonPreview,
+ CurrentDefects,
+ MAX_COMPARE,
+ MockupSection,
+ MockupShell,
+ NoteCard,
+ PhoneFrame,
+ PhoneTopBar,
+ ReviewPill,
+ SLOT_LETTERS,
+ SlotBadge,
+ THERAPY_COUNT,
+ bySlug,
+ filterTherapies,
+ focusRing,
+ useCompareSet,
+} from "./shared";
+
+/* ------------------------------------------------------------------ *
+ * Direction C, perfected.
+ *
+ * Same model as the first tray study — you fill the set where the therapies
+ * are, and the compare screen only ever holds a comparison — with the four
+ * things that study got wrong fixed:
+ *
+ * 1. The tray does not exist until something is in it. An empty Therapy
+ * screen is exactly as tall as it is today.
+ * 2. The tray and the composer are one bottom stack that hides together on
+ * scroll and takes a zero content reserve while hidden, which is what
+ * `docs/search-chrome-behaviour.md` requires of phone chrome.
+ * 3. You can add from a therapy record, not only from a list — that is the
+ * whole point of carrying a set, and the first study never showed it.
+ * 4. Arrival is legible: the tray rises the first time, and the slot it
+ * filled marks itself, so a tap that changes state off-screen still
+ * reads as having done something.
+ * ------------------------------------------------------------------ */
+
+/**
+ * The real list is 205 records. The prototype shows every fixture rather than a
+ * handful, because a short list cannot demonstrate scroll-hide honestly — it
+ * un-scrolls the moment the reserve is released.
+ */
+
+/** Heights the reserve maths depends on, kept in one place and to scale. */
+const COMPOSER_H = 62;
+const TRAY_H = 64;
+
+function useReducedMotion() {
+ const [reduced, setReduced] = useState(false);
+ useEffect(() => {
+ const media = window.matchMedia("(prefers-reduced-motion: reduce)");
+ const update = () => setReduced(media.matches);
+ update();
+ media.addEventListener("change", update);
+ return () => media.removeEventListener("change", update);
+ }, []);
+ return reduced;
+}
+
+type Screen = { kind: "list" } | { kind: "record"; slug: string } | { kind: "compare" };
+
+function PerfectedPhone() {
+ const set = useCompareSet();
+ const reduced = useReducedMotion();
+ const [screen, setScreen] = useState({ kind: "list" });
+ const [expanded, setExpanded] = useState(false);
+ const [query, setQuery] = useState("");
+ const [hidden, setHidden] = useState(false);
+ const [landed, setLanded] = useState(null);
+ const lastScroll = useRef(0);
+
+ const count = set.selected.length;
+ const hasTray = count > 0;
+ // Derived, never latched: emptying the tray cannot strand you on a blank
+ // comparison, whichever control did the emptying.
+ const showCompare = screen.kind === "compare" && count >= 2;
+ const record = screen.kind === "record" ? bySlug(screen.slug) : undefined;
+
+ // Hidden chrome takes a zero reserve — not a residual pad, not the safe-area
+ // inset. Visible chrome reserves exactly its own height.
+ const chromeVisible = !hidden || expanded;
+ const reserve = chromeVisible ? COMPOSER_H + (hasTray ? TRAY_H : 0) : 0;
+
+ function onScroll(event: UIEvent) {
+ const top = event.currentTarget.scrollTop;
+ const delta = top - lastScroll.current;
+ if (Math.abs(delta) < 6) return;
+ lastScroll.current = top;
+ if (expanded) return;
+ setHidden(delta > 0 && top > 40);
+ }
+
+ function add(slug: string) {
+ if (set.full || set.selected.includes(slug)) return;
+ const index = count;
+ set.add(slug);
+ setHidden(false);
+ setLanded(index);
+ window.setTimeout(() => setLanded(null), 900);
+ }
+
+ const transition = reduced ? undefined : "transform 220ms cubic-bezier(0.22, 1, 0.36, 1)";
+
+ return (
+
+
+
+ {/* ---------------- Compare ---------------- */}
+ {showCompare ? (
+
+
+
+ {set.items.map((item) => item.short).join(" vs ")}
+
+
+
+
+
+
+ ) : record ? (
+ /* ---------------- A therapy record ---------------- */
+
+
+
+ {record.name}
+
+
{record.category}
+
+
+
+
+ {/* The point of the whole direction: add without leaving what you
+ are reading. */}
+
+
+
+ >
+ )}
+
+ {/* Scrim, only while the tray is open as a sheet. */}
+ {expanded ? (
+
+ );
+}
+
+function RecordBlock({ label, body, warn }: { label: string; body: string; warn?: boolean }) {
+ return (
+
+
+ {label}
+
+
+ {body}
+
+
+ );
+}
+
+const RESTING_ROWS = [
+ "cognitive-behavioural-therapy-cbt",
+ "acceptance-and-commitment-therapy-act",
+ "behavioural-activation-ba",
+ "interpersonal-psychotherapy-ipt",
+ "dialectical-behaviour-therapy-dbt",
+ "exposure-and-response-prevention-erp",
+];
+
+/** The zero state, drawn on its own so the cost of the idea is visible: none. */
+function RestingPhone() {
+ return (
+
+
+
+ )}
+
+ {/* The tray. Present on every Therapy screen, and drawn in flow directly
+ above the real phone composer so the bottom-chrome stack this
+ direction has to negotiate is visible rather than assumed away. */}
+ {expanded ? (
+ setExpanded(false)}
+ className="absolute inset-0 z-10"
+ style={{ backgroundColor: "color-mix(in srgb, var(--text-heading) 25%, transparent)" }}
+ />
+ ) : null}
+